darktable-org / darktable-org/darktable
`dt_dev_exposure_handle_event()` has no darkroom guard, unlike its three sibling getters
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 13.1k
- Forks
- 1.4k
- Avg merge
- 22h 14m
- Merged PRs (30d)
- 198
Description
Found by bots via code analysis, 2026-08-24.
Severity: low / inconsistency. Probably inert today; the guard is missing on the one
entry point that writes.
Summary
Three of the four public exposure-proxy entry points bail unless the current view is the
darkroom. The fourth — the only one that changes parameters and pushes history — does
not:
static dt_dev_proxy_exposure_t *_dev_exposure_proxy_available(dt_develop_t *dev)
{ // src/develop/develop.c:3627
if(!dev->proxy.exposure.module || dt_view_get_current() != DT_VIEW_DARKROOM) return NULL;
...
}
float dt_dev_exposure_get_exposure(dt_develop_t *dev) // :3635 guarded
float dt_dev_exposure_get_effective_exposure(dt_develop_t *dev) // :3641 guarded
float dt_dev_exposure_get_black(dt_develop_t *dev) // :3678 guarded
void dt_dev_exposure_handle_event(int n_press, gdouble delta, // :3684 NOT guarded
GdkModifierType state, const gboolean is_blackpoint)
{
if(darktable.develop->proxy.exposure.handle_event)
darktable.develop->proxy.exposure.handle_event(n_press, delta, state, is_blackpoint);
}
This matters because the caller is live outside the darkroom. src/libs/histogram.c
declares:
dt_view_type_flags_t views(dt_lib_module_t *self) // :58-61
{
return DT_VIEW_DARKROOM | DT_VIEW_TETHERING;
}
and nothing in _drawable_motion() (:376-397) gates s->highlight on the view, so the
scope offers its exposure-drag affordance in tethering view too, calling
dt_dev_exposure_handle_event() from the drag (:353), double-click (:369) and scroll
(:516) handlers.
Why it is probably inert
_exposure_proxy_handle_event() starts with
const dt_iop_module_t *const self = darktable.develop->proxy.exposure.module; // :860
if(self && self->gui_data) { ... }
and exposure's gui_cleanup() NULLs proxy.exposure.module when the departing
instance is the bound one (:1367-1368). Darkroom leave() runs gui_cleanup() for
every module, so on leaving the darkroom the pointer should be NULL and the call a no-op.
That is a chain of three assumptions holding, not a guard. The function pointer itself
is never cleared (installed by gui_init(), :1355-1359), so "pointers installed" does
not imply "a live instance exists".
Suggested fix
Add the same view check the getters use, in dt_dev_exposure_handle_event(). One line.
Optionally also gate s->highlight in src/libs/histogram.c on
dt_view_get_current() == DT_VIEW_DARKROOM, so the scope does not show a "grab" cursor
for an action it cannot perform in tethering view — that is a UX question, not a
correctness one.
Note on placement
handle_event is a write path — it drives bauhaus widgets, dt_action_widget_toast()
and therefore history — living in a struct otherwise made of getters
(dt_dev_proxy_exposure_t, src/develop/develop.h:100-119). Splitting the struct along
that line is proposed while investigating #22005; this issue is the minimal
standalone fix and does not depend on it.
Environment
Present in 56898c27b8. Static analysis only — nothing verified at runtime.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read dt_dev_exposure_handle_event() in src/develop/develop.c alongside the guarded exposure getters, then review the callers in src/libs/histogram.c. Add the same darkroom view check to the write path; the minimal fix is done when exposure events are blocked outside the darkroom without changing the optional cursor-gating question.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100