darktable-org / darktable-org/darktable

`dt_dev_exposure_handle_event()` has no darkroom guard, unlike its three sibling getters

Open Beginner friendly
#22,008 0 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.