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 "system/atomic.h"
36#include "system/macros.h"
37#include "system/mem_alloc.h"
39#include "system/dtpthread.h"
40#include "control/control.h"
41#include "control/progress.h"
42#include "develop/develop.h"
43#include "widgets/button.h"
44#include "libs/lib.h"
45#include "libs/lib_api.h"
46#include "gui/window_manager.h"
47#include "widgets/container.h"
50
51DT_MODULE(1)
52
54{
55 GtkWidget *widget, *label, *progressbar, *hbox;
56 // Proxy calls (updated/message_updated/cancellable/destroyed) run on arbitrary worker threads
57 // and only ever schedule a same-named _*_gui_thread() callback to run later, on the GUI thread,
58 // via g_main_context_invoke(). A job can finish (and get destroyed) while one of those callbacks
59 // is still queued: without this refcount, _destroyed_gui_thread() would free `instance` right
60 // out from under it, and the queued callback would touch freed GTK widgets on the next main-loop
61 // iteration (Sentry issue 130394919: AccessViolation in gtk_label_set_text). Every scheduled
62 // callback holds one reference, taken by _instance_ref() before g_main_context_invoke() and
63 // dropped by _instance_unref() at the end of the callback; the struct is freed once the count
64 // reaches zero, whichever callback that happens to be.
66 // Set by _destroyed_gui_thread(), checked by _added_gui_thread(): a job can be destroyed before
67 // its own _added_gui_thread() callback (which does the actual widget creation, see below) has
68 // run, if both were scheduled in quick succession from a worker thread. Both callbacks only
69 // ever run on the GUI thread, so plain gboolean read/write is safe without an atomic.
70 gboolean destroyed;
72
73// Takes a reference on `instance` before scheduling a deferred GUI callback that touches it.
75{
76 if(!IS_NULL_PTR(instance)) dt_atomic_add_int(&instance->refcount, 1);
77 return instance;
78}
79
80// Drops a reference taken by _instance_ref(); frees `instance` once the last reference is dropped.
82{
83 if(!IS_NULL_PTR(instance) && dt_atomic_sub_int(&instance->refcount, 1) == 1) dt_free(instance);
84}
85
86/* proxy functions */
87static void *_lib_backgroundjobs_added(dt_lib_module_t *self, gboolean has_progress_bar, const gchar *message);
90 dt_progress_t *progress);
92 double value);
94 const gchar *message);
95
96
97const char *name(struct dt_lib_module_t *self)
98{
99 return _("background jobs");
100}
101
102const char **views(dt_lib_module_t *self)
103{
104 static const char *v[] = {"*", NULL};
105 return v;
106}
107
109{
111}
112
114{
115 return 1;
116}
117
119{
120 return 0;
121}
122
124{
125 /* initialize base */
126 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
127 gtk_widget_set_no_show_all(self->widget, TRUE);
128
129 /* setup proxy */
130 dt_control_t *const control = dt_control_get_global();
132
139
140 // iterate over dt_control_get_global()->progress_system.list and add everything that is already there and update
141 // its gui_data!
142 for(const GList *iter = dt_control_get_global()->progress_system.list; iter; iter = g_list_next(iter))
143 {
144 dt_progress_t *progress = (dt_progress_t *)iter->data;
145 void *gui_data = dt_control_progress_get_gui_data(progress);
146 dt_free(gui_data);
149 dt_control_progress_set_gui_data(progress, gui_data);
150 if(dt_control_progress_cancellable(progress)) _lib_backgroundjobs_cancellable(self, gui_data, progress);
152 }
153
155}
156
169
179
180static gboolean _added_gui_thread(gpointer user_data)
181{
182 _added_gui_thread_t *params = (_added_gui_thread_t *)user_data;
183 dt_lib_backgroundjob_element_t *instance = params->instance;
184
185 // The job may already have been destroyed (params->self, the proxy module, could even be gone
186 // by now) if _lib_backgroundjobs_destroyed() ran first -- see the struct comment on `destroyed`.
187 if(!instance->destroyed)
188 {
189 instance->widget = gtk_event_box_new();
190
191 /* initialize the ui elements for job */
192 gtk_widget_set_name(GTK_WIDGET(instance->widget), "background-job-eventbox");
193 dt_gui_add_class(GTK_WIDGET(instance->widget), "dt_big_btn_canvas");
194 GtkBox *vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
195 instance->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
196 gtk_container_add(GTK_CONTAINER(instance->widget), GTK_WIDGET(vbox));
197
198 /* add job label */
199 instance->label = gtk_label_new(params->message);
200 gtk_widget_set_halign(instance->label, GTK_ALIGN_START);
201 gtk_label_set_ellipsize(GTK_LABEL(instance->label), PANGO_ELLIPSIZE_END);
202 gtk_box_pack_start(GTK_BOX(instance->hbox), GTK_WIDGET(instance->label), TRUE, TRUE, 0);
203 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(instance->hbox), TRUE, TRUE, 0);
204
205 /* use progressbar ? */
206 if(params->has_progress_bar)
207 {
208 instance->progressbar = gtk_progress_bar_new();
209 gtk_box_pack_start(GTK_BOX(vbox), instance->progressbar, TRUE, FALSE, 0);
210 }
211
212 /* lets show jobbox if its hidden */
213 gtk_box_pack_start(GTK_BOX(params->self_widget), instance->widget, TRUE, FALSE, 0);
214 gtk_box_reorder_child(GTK_BOX(params->self_widget), instance->widget, 1);
215 gtk_widget_show_all(instance->widget);
216 gtk_widget_show(params->self_widget);
217 }
218
219 _instance_unref(instance);
220 dt_free(params->message);
221 dt_free(params);
222 return FALSE;
223}
224
225static void *_lib_backgroundjobs_added(dt_lib_module_t *self, gboolean has_progress_bar, const gchar *message)
226{
227 // Only allocate `instance` here (no GTK call) and return it right away: dt_control_progress_create()
228 // (control/progress.c) can call this proxy from any worker thread -- e.g. dt_folder_survey's
229 // DT_JOB_QUEUE_SYSTEM_BG job auto-importing tethered captures calls dt_control_import() straight
230 // from its own job-execution thread, unlike a manual File > Import which runs on the GUI thread.
231 // GTK widget construction is not thread-safe; doing it here made the progress bar's very existence
232 // depend on which thread happened to trigger the import, so it silently, intermittently failed to
233 // appear for folder-survey auto-imports specifically. All actual widget creation now happens in
234 // _added_gui_thread(), deferred like every other proxy callback in this file.
237 if(IS_NULL_PTR(instance)) return NULL;
238 // The reference held by progress->gui_data itself, dropped by _destroyed_gui_thread().
239 dt_atomic_set_int(&instance->refcount, 1);
240
242 if(IS_NULL_PTR(params))
243 {
244 dt_free(instance);
245 return NULL;
246 }
247 params->self_widget = self->widget;
248 params->instance = _instance_ref(instance);
249 params->message = g_strdup(message);
250 params->has_progress_bar = has_progress_bar;
251 g_main_context_invoke(NULL, _added_gui_thread, params);
252
253 // return the gui thingy container
254 return instance;
255}
256
262
263static gboolean _destroyed_gui_thread(gpointer user_data)
264{
266
267 // Mark first: if _added_gui_thread() for this same instance hasn't run yet, this tells it to
268 // skip building/packing widgets nobody will ever remove (see the struct comment on `destroyed`).
269 params->instance->destroyed = TRUE;
270
271 /* remove job widget from jobbox */
272 if(params->instance->widget && GTK_IS_WIDGET(params->instance->widget))
273 gtk_container_remove(GTK_CONTAINER(params->self->widget), params->instance->widget);
274
275 // gtk_container_remove() above drops the last ref on the whole subtree (widget, hbox, label,
276 // progressbar), destroying it. NULL every pointer so any update/message/cancellable callback
277 // still in flight for this instance (see the refcount comment on the struct) finds them NULL
278 // and skips touching freed GTK widgets instead of crashing.
279 params->instance->widget = NULL;
280 params->instance->label = NULL;
281 params->instance->progressbar = NULL;
282 params->instance->hbox = NULL;
283
284 /* if jobbox is empty let's hide */
285 if(!dt_gui_container_has_children(GTK_CONTAINER(params->self->widget)))
286 gtk_widget_hide(params->self->widget);
287
288 _instance_unref(params->instance);
289 dt_free(params);
290 return FALSE;
291}
292
293// remove the gui that is pointed to in instance
295{
296 if(IS_NULL_PTR(instance)) return;
298 if(IS_NULL_PTR(params)) return;
299 params->self = self;
300 params->instance = instance;
301 g_main_context_invoke(NULL, _destroyed_gui_thread, params);
302}
303
304static void _lib_backgroundjobs_cancel_callback_new(GtkWidget *w, gpointer user_data)
305{
306 dt_progress_t *progress = (dt_progress_t *)user_data;
308}
309
315
316static gboolean _cancellable_gui_thread(gpointer user_data)
317{
319
320 if(!IS_NULL_PTR(params->instance->hbox))
321 {
322 GtkBox *hbox = GTK_BOX(params->instance->hbox);
324 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_lib_backgroundjobs_cancel_callback_new), params->progress);
325 gtk_box_pack_start(hbox, GTK_WIDGET(button), FALSE, FALSE, 0);
326 gtk_widget_show_all(button);
327 }
328
329 _instance_unref(params->instance);
330 dt_free(params);
331 return FALSE;
332}
333
335 dt_progress_t *progress)
336{
337 // add a cancel button to the gui. when clicked we want dt_control_progress_cancel(dt_control_get_global(),
338 // progress); to be called
339 if(!dt_control_get_global()->running || IS_NULL_PTR(instance)) return;
340
342 if(IS_NULL_PTR(params)) return;
343 params->instance = _instance_ref(instance);
344 params->progress = progress;
345 g_main_context_invoke(NULL, _cancellable_gui_thread, params);
346}
347
353
354static gboolean _update_gui_thread(gpointer user_data)
355{
356 _update_gui_thread_t *params = (_update_gui_thread_t *)user_data;
357
358 if(!IS_NULL_PTR(params->instance->progressbar))
359 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(params->instance->progressbar), CLAMP(params->value, 0, 1.0));
360
361 _instance_unref(params->instance);
362 dt_free(params);
363 return FALSE;
364}
365
367 double value)
368{
369 // update the progress bar
370 if(!dt_control_get_global()->running || IS_NULL_PTR(instance)) return;
371
373 if(IS_NULL_PTR(params)) return;
374 params->instance = _instance_ref(instance);
375 params->value = value;
376 g_main_context_invoke(NULL, _update_gui_thread, params);
377}
378
384
385static gboolean _update_message_gui_thread(gpointer user_data)
386{
388
389 if(!IS_NULL_PTR(params->instance->label))
390 gtk_label_set_text(GTK_LABEL(params->instance->label), params->message);
391
392 _instance_unref(params->instance);
393 dt_free(params->message);
394 dt_free(params);
395 return FALSE;
396}
397
399 const char *message)
400{
401 // update the progress bar
402 if(!dt_control_get_global()->running || IS_NULL_PTR(instance)) return;
403
405 if(IS_NULL_PTR(params)) return;
406 params->instance = _instance_ref(instance);
407 params->message = g_strdup(message);
408 g_main_context_invoke(NULL, _update_message_gui_thread, params);
409}
410
411// clang-format off
412// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
413// vim: shiftwidth=2 expandtab tabstop=2 cindent
414// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
415// 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:68
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:135
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
gboolean dt_gui_container_has_children(GtkContainer *container)
Definition container.c:23
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
#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 macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
const char * name
Definition pdf.h:90
double dt_control_progress_get_progress(dt_progress_t *progress)
Definition progress.c:379
gboolean dt_control_progress_cancellable(dt_progress_t *progress)
Definition progress.c:433
const gchar * dt_control_progress_get_message(dt_progress_t *progress)
Definition progress.c:387
void dt_control_progress_set_gui_data(dt_progress_t *progress, void *data)
Definition progress.c:410
void dt_control_progress_cancel(dt_control_t *control, dt_progress_t *progress)
Definition progress.c:344
gboolean dt_control_progress_has_progress_bar(dt_progress_t *progress)
Definition progress.c:425
void * dt_control_progress_get_gui_data(dt_progress_t *progress)
Definition progress.c:417
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
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
dt_pthread_mutex_t mutex
Definition control.h:268
void(* destroyed)(dt_lib_module_t *self, struct dt_lib_backgroundjob_element_t *instance)
Definition control.h:279
GList * list
Definition control.h:264
void(* message_updated)(dt_lib_module_t *self, struct dt_lib_backgroundjob_element_t *instance, const char *message)
Definition control.h:283
void(* updated)(dt_lib_module_t *self, struct dt_lib_backgroundjob_element_t *instance, double value)
Definition control.h:282
dt_lib_module_t *void *(* added)(dt_lib_module_t *self, gboolean has_progress_bar, const gchar *message)
Definition control.h:278
void(* cancellable)(dt_lib_module_t *self, struct dt_lib_backgroundjob_element_t *instance, dt_progress_t *progress)
Definition control.h:280
struct dt_control_t::@8 progress_system
struct dt_control_t::@8::@10 proxy
GtkWidget * widget
Definition lib.h:83
#define DT_GUI_BOX_SPACING
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
void dtgtk_cairo_paint_cancel(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ DT_UI_CONTAINER_PANEL_LEFT_BOTTOM