Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
backgroundjobs.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011 Henrik Andersson.
4 Copyright (C) 2011, 2013 Pascal de Bruijn.
5 Copyright (C) 2012-2013 parafin.
6 Copyright (C) 2012 Richard Wonka.
7 Copyright (C) 2012, 2014-2018 Tobias Ellinghaus.
8 Copyright (C) 2013 johannes hanika.
9 Copyright (C) 2013 Simon Spannagel.
10 Copyright (C) 2014 Edouard Gomez.
11 Copyright (C) 2014-2015 Jérémy Rosen.
12 Copyright (C) 2014, 2020-2021 Pascal Obry.
13 Copyright (C) 2014-2016 Roman Lebedev.
14 Copyright (C) 2019, 2025 Aurélien PIERRE.
15 Copyright (C) 2020 Marco.
16 Copyright (C) 2021 Ralf Brown.
17 Copyright (C) 2022 Aldric Renaudin.
18 Copyright (C) 2022 Martin Bařinka.
19 Copyright (C) 2022 Nicolas Auffray.
20
21 darktable is free software: you can redistribute it and/or modify
22 it under the terms of the GNU General Public License as published by
23 the Free Software Foundation, either version 3 of the License, or
24 (at your option) any later version.
25
26 darktable is distributed in the hope that it will be useful,
27 but WITHOUT ANY WARRANTY; without even the implied warranty of
28 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 GNU General Public License for more details.
30
31 You should have received a copy of the GNU General Public License
32 along with darktable. If not, see <http://www.gnu.org/licenses/>.
33*/
34
35#include "common/atomic.h"
36#include "common/darktable.h"
37#include "common/debug.h"
38#include "common/image_cache.h"
39#include "control/conf.h"
40#include "control/control.h"
41#include "control/progress.h"
42#include "develop/develop.h"
43#include "dtgtk/button.h"
44#include "gui/draw.h"
45#include "gui/gtk.h"
46#include "libs/lib.h"
47#include "libs/lib_api.h"
48
49DT_MODULE(1)
50
52{
53 GtkWidget *widget, *label, *progressbar, *hbox;
54 // Proxy calls (updated/message_updated/cancellable/destroyed) run on arbitrary worker threads
55 // and only ever schedule a same-named _*_gui_thread() callback to run later, on the GUI thread,
56 // via g_main_context_invoke(). A job can finish (and get destroyed) while one of those callbacks
57 // is still queued: without this refcount, _destroyed_gui_thread() would free `instance` right
58 // out from under it, and the queued callback would touch freed GTK widgets on the next main-loop
59 // iteration (Sentry issue 130394919: AccessViolation in gtk_label_set_text). Every scheduled
60 // callback holds one reference, taken by _instance_ref() before g_main_context_invoke() and
61 // dropped by _instance_unref() at the end of the callback; the struct is freed once the count
62 // reaches zero, whichever callback that happens to be.
64 // Set by _destroyed_gui_thread(), checked by _added_gui_thread(): a job can be destroyed before
65 // its own _added_gui_thread() callback (which does the actual widget creation, see below) has
66 // run, if both were scheduled in quick succession from a worker thread. Both callbacks only
67 // ever run on the GUI thread, so plain gboolean read/write is safe without an atomic.
68 gboolean destroyed;
70
71// Takes a reference on `instance` before scheduling a deferred GUI callback that touches it.
73{
74 if(!IS_NULL_PTR(instance)) dt_atomic_add_int(&instance->refcount, 1);
75 return instance;
76}
77
78// Drops a reference taken by _instance_ref(); frees `instance` once the last reference is dropped.
80{
81 if(!IS_NULL_PTR(instance) && dt_atomic_sub_int(&instance->refcount, 1) == 1) dt_free(instance);
82}
83
84/* proxy functions */
85static void *_lib_backgroundjobs_added(dt_lib_module_t *self, gboolean has_progress_bar, const gchar *message);
88 dt_progress_t *progress);
90 double value);
92 const gchar *message);
93
94
95const char *name(struct dt_lib_module_t *self)
96{
97 return _("background jobs");
98}
99
100const char **views(dt_lib_module_t *self)
101{
102 static const char *v[] = {"*", NULL};
103 return v;
104}
105
107{
109}
110
112{
113 return 1;
114}
115
117{
118 return 0;
119}
120
122{
123 /* initialize base */
124 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
125 gtk_widget_set_no_show_all(self->widget, TRUE);
126
127 /* setup proxy */
129
130 darktable.control->progress_system.proxy.module = self;
136
137 // iterate over darktable.control->progress_system.list and add everything that is already there and update
138 // its gui_data!
139 for(const GList *iter = darktable.control->progress_system.list; iter; iter = g_list_next(iter))
140 {
141 dt_progress_t *progress = (dt_progress_t *)iter->data;
142 void *gui_data = dt_control_progress_get_gui_data(progress);
143 dt_free(gui_data);
146 dt_control_progress_set_gui_data(progress, gui_data);
147 if(dt_control_progress_cancellable(progress)) _lib_backgroundjobs_cancellable(self, gui_data, progress);
149 }
150
152}
153
165
175
176static gboolean _added_gui_thread(gpointer user_data)
177{
178 _added_gui_thread_t *params = (_added_gui_thread_t *)user_data;
179 dt_lib_backgroundjob_element_t *instance = params->instance;
180
181 // The job may already have been destroyed (params->self, the proxy module, could even be gone
182 // by now) if _lib_backgroundjobs_destroyed() ran first -- see the struct comment on `destroyed`.
183 if(!instance->destroyed)
184 {
185 instance->widget = gtk_event_box_new();
186
187 /* initialize the ui elements for job */
188 gtk_widget_set_name(GTK_WIDGET(instance->widget), "background-job-eventbox");
189 dt_gui_add_class(GTK_WIDGET(instance->widget), "dt_big_btn_canvas");
190 GtkBox *vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
191 instance->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
192 gtk_container_add(GTK_CONTAINER(instance->widget), GTK_WIDGET(vbox));
193
194 /* add job label */
195 instance->label = gtk_label_new(params->message);
196 gtk_widget_set_halign(instance->label, GTK_ALIGN_START);
197 gtk_label_set_ellipsize(GTK_LABEL(instance->label), PANGO_ELLIPSIZE_END);
198 gtk_box_pack_start(GTK_BOX(instance->hbox), GTK_WIDGET(instance->label), TRUE, TRUE, 0);
199 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(instance->hbox), TRUE, TRUE, 0);
200
201 /* use progressbar ? */
202 if(params->has_progress_bar)
203 {
204 instance->progressbar = gtk_progress_bar_new();
205 gtk_box_pack_start(GTK_BOX(vbox), instance->progressbar, TRUE, FALSE, 0);
206 }
207
208 /* lets show jobbox if its hidden */
209 gtk_box_pack_start(GTK_BOX(params->self_widget), instance->widget, TRUE, FALSE, 0);
210 gtk_box_reorder_child(GTK_BOX(params->self_widget), instance->widget, 1);
211 gtk_widget_show_all(instance->widget);
212 gtk_widget_show(params->self_widget);
213 }
214
215 _instance_unref(instance);
216 dt_free(params->message);
217 dt_free(params);
218 return FALSE;
219}
220
221static void *_lib_backgroundjobs_added(dt_lib_module_t *self, gboolean has_progress_bar, const gchar *message)
222{
223 // Only allocate `instance` here (no GTK call) and return it right away: dt_control_progress_create()
224 // (control/progress.c) can call this proxy from any worker thread -- e.g. dt_folder_survey's
225 // DT_JOB_QUEUE_SYSTEM_BG job auto-importing tethered captures calls dt_control_import() straight
226 // from its own job-execution thread, unlike a manual File > Import which runs on the GUI thread.
227 // GTK widget construction is not thread-safe; doing it here made the progress bar's very existence
228 // depend on which thread happened to trigger the import, so it silently, intermittently failed to
229 // appear for folder-survey auto-imports specifically. All actual widget creation now happens in
230 // _added_gui_thread(), deferred like every other proxy callback in this file.
233 if(IS_NULL_PTR(instance)) return NULL;
234 // The reference held by progress->gui_data itself, dropped by _destroyed_gui_thread().
235 dt_atomic_set_int(&instance->refcount, 1);
236
238 if(IS_NULL_PTR(params))
239 {
240 dt_free(instance);
241 return NULL;
242 }
243 params->self_widget = self->widget;
244 params->instance = _instance_ref(instance);
245 params->message = g_strdup(message);
246 params->has_progress_bar = has_progress_bar;
247 g_main_context_invoke(NULL, _added_gui_thread, params);
248
249 // return the gui thingy container
250 return instance;
251}
252
258
259static gboolean _destroyed_gui_thread(gpointer user_data)
260{
262
263 // Mark first: if _added_gui_thread() for this same instance hasn't run yet, this tells it to
264 // skip building/packing widgets nobody will ever remove (see the struct comment on `destroyed`).
265 params->instance->destroyed = TRUE;
266
267 /* remove job widget from jobbox */
268 if(params->instance->widget && GTK_IS_WIDGET(params->instance->widget))
269 gtk_container_remove(GTK_CONTAINER(params->self->widget), params->instance->widget);
270
271 // gtk_container_remove() above drops the last ref on the whole subtree (widget, hbox, label,
272 // progressbar), destroying it. NULL every pointer so any update/message/cancellable callback
273 // still in flight for this instance (see the refcount comment on the struct) finds them NULL
274 // and skips touching freed GTK widgets instead of crashing.
275 params->instance->widget = NULL;
276 params->instance->label = NULL;
277 params->instance->progressbar = NULL;
278 params->instance->hbox = NULL;
279
280 /* if jobbox is empty let's hide */
281 if(!dt_gui_container_has_children(GTK_CONTAINER(params->self->widget)))
282 gtk_widget_hide(params->self->widget);
283
284 _instance_unref(params->instance);
285 dt_free(params);
286 return FALSE;
287}
288
289// remove the gui that is pointed to in instance
291{
292 if(IS_NULL_PTR(instance)) return;
294 if(IS_NULL_PTR(params)) return;
295 params->self = self;
296 params->instance = instance;
297 g_main_context_invoke(NULL, _destroyed_gui_thread, params);
298}
299
300static void _lib_backgroundjobs_cancel_callback_new(GtkWidget *w, gpointer user_data)
301{
302 dt_progress_t *progress = (dt_progress_t *)user_data;
304}
305
311
312static gboolean _cancellable_gui_thread(gpointer user_data)
313{
315
316 if(!IS_NULL_PTR(params->instance->hbox))
317 {
318 GtkBox *hbox = GTK_BOX(params->instance->hbox);
320 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_lib_backgroundjobs_cancel_callback_new), params->progress);
321 gtk_box_pack_start(hbox, GTK_WIDGET(button), FALSE, FALSE, 0);
322 gtk_widget_show_all(button);
323 }
324
325 _instance_unref(params->instance);
326 dt_free(params);
327 return FALSE;
328}
329
331 dt_progress_t *progress)
332{
333 // add a cancel button to the gui. when clicked we want dt_control_progress_cancel(darktable.control,
334 // progress); to be called
335 if(!darktable.control->running || IS_NULL_PTR(instance)) return;
336
338 if(IS_NULL_PTR(params)) return;
339 params->instance = _instance_ref(instance);
340 params->progress = progress;
341 g_main_context_invoke(NULL, _cancellable_gui_thread, params);
342}
343
349
350static gboolean _update_gui_thread(gpointer user_data)
351{
352 _update_gui_thread_t *params = (_update_gui_thread_t *)user_data;
353
354 if(!IS_NULL_PTR(params->instance->progressbar))
355 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(params->instance->progressbar), CLAMP(params->value, 0, 1.0));
356
357 _instance_unref(params->instance);
358 dt_free(params);
359 return FALSE;
360}
361
363 double value)
364{
365 // update the progress bar
366 if(!darktable.control->running || IS_NULL_PTR(instance)) return;
367
369 if(IS_NULL_PTR(params)) return;
370 params->instance = _instance_ref(instance);
371 params->value = value;
372 g_main_context_invoke(NULL, _update_gui_thread, params);
373}
374
380
381static gboolean _update_message_gui_thread(gpointer user_data)
382{
384
385 if(!IS_NULL_PTR(params->instance->label))
386 gtk_label_set_text(GTK_LABEL(params->instance->label), params->message);
387
388 _instance_unref(params->instance);
389 dt_free(params->message);
390 dt_free(params);
391 return FALSE;
392}
393
395 const char *message)
396{
397 // update the progress bar
398 if(!darktable.control->running || IS_NULL_PTR(instance)) return;
399
401 if(IS_NULL_PTR(params)) return;
402 params->instance = _instance_ref(instance);
403 params->message = g_strdup(message);
404 g_main_context_invoke(NULL, _update_message_gui_thread, params);
405}
406
407// clang-format off
408// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
409// vim: shiftwidth=2 expandtab tabstop=2 cindent
410// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
411// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_atomic_set_int(dt_atomic_int *var, int value)
int dt_atomic_sub_int(dt_atomic_int *var, int decr)
int dt_atomic_add_int(dt_atomic_int *var, int incr)
atomic_int dt_atomic_int
Definition atomic.h:66
static void _instance_unref(dt_lib_backgroundjob_element_t *instance)
static gboolean _added_gui_thread(gpointer user_data)
static void * _lib_backgroundjobs_added(dt_lib_module_t *self, gboolean has_progress_bar, const gchar *message)
static void _lib_backgroundjobs_updated(dt_lib_module_t *self, dt_lib_backgroundjob_element_t *instance, double value)
static gboolean _destroyed_gui_thread(gpointer user_data)
static gboolean _cancellable_gui_thread(gpointer user_data)
static dt_lib_backgroundjob_element_t * _instance_ref(dt_lib_backgroundjob_element_t *instance)
static void _lib_backgroundjobs_destroyed(dt_lib_module_t *self, dt_lib_backgroundjob_element_t *instance)
void gui_cleanup(dt_lib_module_t *self)
static void _lib_backgroundjobs_cancel_callback_new(GtkWidget *w, gpointer user_data)
static gboolean _update_gui_thread(gpointer user_data)
static void _lib_backgroundjobs_cancellable(dt_lib_module_t *self, dt_lib_backgroundjob_element_t *instance, dt_progress_t *progress)
uint32_t container(dt_lib_module_t *self)
static gboolean _update_message_gui_thread(gpointer user_data)
void gui_init(dt_lib_module_t *self)
int position()
const char ** views(dt_lib_module_t *self)
int expandable(dt_lib_module_t *self)
static void _lib_backgroundjobs_message_updated(dt_lib_module_t *self, dt_lib_backgroundjob_element_t *instance, const gchar *message)
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:134
char * name
darktable_t darktable
Definition darktable.c:183
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:478
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
void dtgtk_cairo_paint_cancel(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:384
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
gboolean dt_gui_container_has_children(GtkContainer *container)
Definition gtk.c:3094
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
const float v
double dt_control_progress_get_progress(dt_progress_t *progress)
Definition progress.c:373
gboolean dt_control_progress_cancellable(dt_progress_t *progress)
Definition progress.c:427
const gchar * dt_control_progress_get_message(dt_progress_t *progress)
Definition progress.c:381
void dt_control_progress_set_gui_data(dt_progress_t *progress, void *data)
Definition progress.c:404
void dt_control_progress_cancel(dt_control_t *control, dt_progress_t *progress)
Definition progress.c:338
gboolean dt_control_progress_has_progress_bar(dt_progress_t *progress)
Definition progress.c:419
void * dt_control_progress_get_gui_data(dt_progress_t *progress)
Definition progress.c:411
struct _GtkWidget GtkWidget
Definition splash.h:29
dt_lib_backgroundjob_element_t * instance
dt_lib_backgroundjob_element_t * instance
dt_lib_backgroundjob_element_t * instance
dt_lib_module_t * self
dt_lib_backgroundjob_element_t * instance
dt_lib_backgroundjob_element_t * instance
struct dt_control_t * control
Definition darktable.h:801
dt_pthread_mutex_t mutex
Definition control.h:280
int32_t running
Definition control.h:258
void(* destroyed)(dt_lib_module_t *self, struct dt_lib_backgroundjob_element_t *instance)
Definition control.h:291
struct dt_control_t::@13 progress_system
struct dt_control_t::@13::@15 proxy
GList * list
Definition control.h:276
void(* message_updated)(dt_lib_module_t *self, struct dt_lib_backgroundjob_element_t *instance, const char *message)
Definition control.h:295
void(* updated)(dt_lib_module_t *self, struct dt_lib_backgroundjob_element_t *instance, double value)
Definition control.h:294
dt_lib_module_t *void *(* added)(dt_lib_module_t *self, gboolean has_progress_bar, const gchar *message)
Definition control.h:290
void(* cancellable)(dt_lib_module_t *self, struct dt_lib_backgroundjob_element_t *instance, dt_progress_t *progress)
Definition control.h:292
GtkWidget * widget
Definition lib.h:84
@ DT_UI_CONTAINER_PANEL_LEFT_BOTTOM