/* This file is part of darktable, Copyright (C) 2011-2025 darktable developers. darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. darktable is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with darktable. If not, see . */ #include "control/signal.h" #include "control/control.h" #include #include #ifdef DT_HAVE_SIGNAL_TRACE #include #endif typedef struct dt_control_signal_t { /* the sinks for the signals */ GObject *sink; } dt_control_signal_t; /* GSignalFlags signal_flags, ...); */ typedef struct dt_signal_description { const char *name; GSignalAccumulator accumulator; gpointer accu_data; GType return_type; GSignalCMarshaller c_marshaller; guint n_params; GType *param_types; GCallback destructor; gboolean synchronous; } dt_signal_description; static GType uint_arg[] = { G_TYPE_UINT }; static GType pointer_arg[] = { G_TYPE_POINTER }; static GType pointer_2arg[] = { G_TYPE_POINTER, G_TYPE_POINTER }; static GType pointer_trouble[] = { G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_STRING }; static GType collection_args[] = { G_TYPE_UINT, G_TYPE_UINT, G_TYPE_POINTER, G_TYPE_UINT }; static GType image_export_arg[] = { G_TYPE_UINT, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER }; static GType geotag_arg[] = { G_TYPE_POINTER, G_TYPE_UINT }; // callback for the destructor of DT_SIGNAL_COLLECTION_CHANGED static void _collection_changed_destroy_callback(gpointer instance, int query_change, int changed_property, gpointer imgs, const int next, gpointer user_data) { if(imgs) { g_list_free(imgs); imgs = NULL; } } // callback for the destructor of DT_SIGNAL_IMAGE_INFO_CHANGED static void _image_info_changed_destroy_callback(gpointer instance, gpointer imgs, gpointer user_data) { if(imgs) { g_list_free(imgs); imgs = NULL; } } // callback for the destructor of DT_SIGNAL_PRESETS_CHANGED static void _presets_changed_destroy_callback(gpointer instance, gpointer module, gpointer user_data) { g_free(module); } // callback for the destructor of DT_SIGNAL_GEOTAG_CHANGED static void _image_geotag_destroy_callback(gpointer instance, gpointer imgs, const int locid, gpointer user_data) { if(imgs) { g_list_free(imgs); imgs = NULL; } } #define G_CALLBACK(f) ((GCallback) (f)) static dt_signal_description _signal_description[DT_SIGNAL_COUNT] = { /* Global signals */ [DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE] = { "dt-global-mouse-over-image-change", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_ACTIVE_IMAGES_CHANGE] = { "dt-global-active-images-change", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_CONTROL_REDRAW_ALL] = { "dt-control-redraw-all", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_CONTROL_REDRAW_CENTER] = { "dt-control-redraw-center", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_VIEWMANAGER_VIEW_CHANGED] = { "dt-viewmanager-view-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 2, pointer_2arg, NULL, FALSE }, [DT_SIGNAL_VIEWMANAGER_VIEW_CANNOT_CHANGE] = { "dt-viewmanager-view-cannot-change", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 2, pointer_2arg, NULL, FALSE }, [DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE] = { "dt-viewmanager-thumbtable-activate", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__UINT, 1, uint_arg, NULL, FALSE }, [DT_SIGNAL_COLLECTION_CHANGED] = { "dt-collection-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 4, collection_args, G_CALLBACK(_collection_changed_destroy_callback), FALSE }, [DT_SIGNAL_SELECTION_CHANGED] = { "dt-selection-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_TAG_CHANGED] = { "dt-tag-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_GEOTAG_CHANGED] = { "dt-geotag-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 2, geotag_arg, G_CALLBACK(_image_geotag_destroy_callback), FALSE }, [DT_SIGNAL_METADATA_CHANGED] = { "dt-metadata-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__UINT, 1, uint_arg, NULL, FALSE }, [DT_SIGNAL_IMAGE_INFO_CHANGED] = { "dt-image-info-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 1, pointer_arg, G_CALLBACK(_image_info_changed_destroy_callback), FALSE }, [DT_SIGNAL_STYLE_CHANGED] = { "dt-style-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_IMAGES_ORDER_CHANGE] = { "dt-images-order-change", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 1, pointer_arg, NULL, FALSE }, [DT_SIGNAL_FILMROLLS_CHANGED] = { "dt-filmrolls-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_FILMROLLS_IMPORTED] = { "dt-filmrolls-imported", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__UINT, 1, uint_arg, NULL, FALSE }, [DT_SIGNAL_FILMROLLS_REMOVED] = { "dt-filmrolls-removed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_PRESETS_CHANGED] = { "dt-presets-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 1, pointer_arg, G_CALLBACK(_presets_changed_destroy_callback), FALSE }, [DT_SIGNAL_PRESET_APPLIED] = { "dt-preset-applied", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, /* AI models related signals */ [DT_SIGNAL_AI_MODELS_CHANGED] = { "dt-ai-models-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_AI_UPDATE_CHECK_DONE] = { "dt-ai-update-check-done", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, /* Develop related signals */ [DT_SIGNAL_DEVELOP_INITIALIZE] = { "dt-develop-initialized", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_DEVELOP_MIPMAP_UPDATED] = { "dt-develop-mipmap-updated", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__UINT, 1, uint_arg, NULL, FALSE }, [DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED] = { "dt-develop-preview-pipe-finished", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_DEVELOP_PREVIEW2_PIPE_FINISHED] = { "dt-develop-preview2-pipe-finished", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_DEVELOP_UI_PIPE_FINISHED] = { "dt-develop-ui-pipe-finished", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_DEVELOP_HISTORY_WILL_CHANGE] = { "dt-develop-history-will-change", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_DEVELOP_HISTORY_CHANGE] = { "dt-develop-history-change", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_DEVELOP_HISTORY_INVALIDATED] = { "dt-develop-history-invalidated", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_DEVELOP_MODULE_REMOVE] = { "dt-develop-module-remove", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 1, pointer_arg, NULL, TRUE }, [DT_SIGNAL_DEVELOP_MODULE_MOVED] = { "dt-develop-module-moved", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_DEVELOP_IMAGE_CHANGED] = { "dt-develop-image-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_DEVELOP_DISTORT] = { "dt-develop-distort", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_IMAGE_REMOVED] = { "dt-image-removed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__UINT, 1, uint_arg, NULL, FALSE }, [DT_SIGNAL_CONTROL_PROFILE_CHANGED] = { "dt-control-profile-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_CONTROL_PROFILE_USER_CHANGED] = { "dt-control-profile-user-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__UINT, 1, uint_arg, NULL, FALSE }, [DT_SIGNAL_IMAGE_IMPORT] = { "dt-image-import", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__UINT, 1, uint_arg, NULL, FALSE }, [DT_SIGNAL_IMAGE_EXPORT_TMPFILE] = { "dt-image-export-tmpfile", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 6, image_export_arg, NULL, TRUE }, [DT_SIGNAL_IMAGEIO_STORAGE_CHANGE] = { "dt-imageio-storage-change", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_PREFERENCES_CHANGE] = { "dt-preferences-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_CAMERA_DETECTED] = { "dt-camera-detected", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_CONTROL_NAVIGATION_REDRAW] = { "dt-control-navigation-redraw", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_CONTROL_LOG_REDRAW] = { "dt-control-log-redraw", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_CONTROL_TOAST_REDRAW] = { "dt-control-toast-redraw", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_CONTROL_PICKERDATA_READY] = { "dt-control-pickerdata-ready", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 2, pointer_2arg, NULL, FALSE }, [DT_SIGNAL_METADATA_UPDATE] = { "dt-metadata-update", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, [DT_SIGNAL_TROUBLE_MESSAGE] = { "dt-trouble-message", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 3, pointer_trouble, NULL, FALSE }, [DT_SIGNAL_LOCATION_CHANGED] = { "dt-location-changed", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_generic, 1, pointer_arg, NULL, TRUE }, [DT_SIGNAL_IMAGEIO_STORAGE_EXPORT_ENABLE] = { "dt-imageio-storage-export-enable", NULL, NULL, G_TYPE_NONE, g_cclosure_marshal_VOID__VOID, 0, NULL, NULL, FALSE }, }; static GType _signal_type; dt_control_signal_t *dt_control_signal_init() { dt_control_signal_t *ctlsig = g_malloc0(sizeof(dt_control_signal_t)); /* setup dummy gobject typeinfo */ GTypeQuery query; GTypeInfo type_info = { 0, (GBaseInitFunc)NULL, (GBaseFinalizeFunc)NULL, (GClassInitFunc)NULL, (GClassFinalizeFunc)NULL, NULL, 0, 0, (GInstanceInitFunc)NULL }; g_type_query(G_TYPE_OBJECT, &query); type_info.class_size = query.class_size; type_info.instance_size = query.instance_size; _signal_type = g_type_register_static(G_TYPE_OBJECT, "DarktableSignals", &type_info, 0); /* create our pretty empty gobject */ ctlsig->sink = g_object_new(_signal_type, NULL); /* create the signals */ for(int k = 0; k < DT_SIGNAL_COUNT; k++) { g_signal_newv(_signal_description[k].name, _signal_type, G_SIGNAL_RUN_LAST, 0, _signal_description[k].accumulator, _signal_description[k].accu_data, _signal_description[k].c_marshaller, _signal_description[k].return_type, _signal_description[k].n_params, _signal_description[k].param_types); if(_signal_description[k].destructor) { g_signal_connect_after(G_OBJECT(ctlsig->sink), _signal_description[k].name, _signal_description[k].destructor, NULL); } } return ctlsig; } typedef struct _signal_param_t { GValue *instance_and_params; guint signal_id; guint n_params; } _signal_param_t; static gboolean _signal_raise(gpointer user_data) { _signal_param_t *params = (_signal_param_t *)user_data; g_signal_emitv(params->instance_and_params, params->signal_id, 0, NULL); for(int i = 0; i <= params->n_params; i++) g_value_unset(¶ms->instance_and_params[i]); free(params->instance_and_params); free(params); return G_SOURCE_REMOVE; } typedef struct async_com_data { GCond end_cond; GMutex end_mutex; gpointer user_data; gboolean finished; } async_com_data; gboolean _async_com_callback(gpointer data) { async_com_data *communication = (async_com_data*)data; g_mutex_lock(&communication->end_mutex); _signal_raise(communication->user_data); communication->finished = TRUE; g_cond_signal(&communication->end_cond); g_mutex_unlock(&communication->end_mutex); return G_SOURCE_REMOVE; } static void _print_trace(int signal, dt_debug_signal_action_t action, const char* op) { #ifdef DT_HAVE_SIGNAL_TRACE if((signal == -1 || darktable.unmuted_signal_dbg[signal]) && darktable.unmuted_signal_dbg_acts & DT_DEBUG_SIGNAL_ACT_PRINT_TRACE && darktable.unmuted_signal_dbg_acts & action) { void *array[10]; size_t size; char **strings; size_t i; size = backtrace (array, 10); strings = backtrace_symbols (array, size); for(i = 0; i < size; i++) dt_print(DT_DEBUG_SIGNAL, "[signal-trace-%s]: %s", op, strings[i]); free (strings); } #endif } void dt_control_signal_raise(const dt_control_signal_t *ctlsig, dt_signal_t signal, ...) { // ignore all signals on shutdown if(!dt_control_running()) return; dt_signal_description *signal_description = &_signal_description[signal]; _signal_param_t *params = malloc(sizeof(_signal_param_t)); if(!params) return; GValue *instance_and_params = calloc(1 + signal_description->n_params, sizeof(GValue)); if(!instance_and_params) { free(params); return; } _print_trace(signal, DT_DEBUG_SIGNAL_ACT_RAISE, "raise"); // 0th element has to be the instance to call g_value_init(instance_and_params, _signal_type); g_value_set_object(instance_and_params, ctlsig->sink); // the rest of instance_and_params will be the params for the callback va_list extra_args; va_start(extra_args, signal); for(int i = 1; i <= signal_description->n_params; i++) { GType type = signal_description->param_types[i-1]; g_value_init(&instance_and_params[i], type); switch(type) { case G_TYPE_UINT: g_value_set_uint(&instance_and_params[i], va_arg(extra_args, guint)); break; case G_TYPE_STRING: g_value_set_string(&instance_and_params[i], va_arg(extra_args, const char *)); break; case G_TYPE_POINTER: g_value_set_pointer(&instance_and_params[i], va_arg(extra_args, void *)); break; default: dt_print(DT_DEBUG_ALWAYS, "error: unsupported parameter type `%s' for signal `%s'", g_type_name(type), signal_description->name); va_end(extra_args); for(int j = 0; j <= i; j++) g_value_unset(&instance_and_params[j]); free(instance_and_params); free(params); return; } } va_end(extra_args); params->instance_and_params = instance_and_params; params->signal_id = g_signal_lookup(_signal_description[signal].name, _signal_type); params->n_params = signal_description->n_params; if(!signal_description->synchronous) { g_main_context_invoke_full(NULL, G_PRIORITY_HIGH_IDLE, _signal_raise, params, NULL); } else { if(pthread_equal(darktable.control->gui_thread, pthread_self())) { _signal_raise(params); } else { async_com_data communication = {}; g_mutex_init(&communication.end_mutex); g_cond_init(&communication.end_cond); communication.user_data = params; g_main_context_invoke_full(NULL,G_PRIORITY_HIGH_IDLE, _async_com_callback,&communication, NULL); g_mutex_lock(&communication.end_mutex); while(!communication.finished) g_cond_wait(&communication.end_cond,&communication.end_mutex); g_mutex_unlock(&communication.end_mutex); g_mutex_clear(&communication.end_mutex); } } } void dt_control_signal_connect(const dt_control_signal_t *ctlsig, dt_signal_t signal, GCallback cb, gpointer user_data) { _print_trace(signal, DT_DEBUG_SIGNAL_ACT_CONNECT, "connect"); g_signal_connect_data(G_OBJECT(ctlsig->sink), _signal_description[signal].name, G_CALLBACK(cb), user_data, NULL, 0); } void dt_control_signal_disconnect(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data) { _print_trace(-1, DT_DEBUG_SIGNAL_ACT_DISCONNECT, "disconnect"); g_signal_handlers_disconnect_matched(G_OBJECT(ctlsig->sink), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, cb, user_data); } guint dt_control_signal_disconnect_all(const struct dt_control_signal_t *ctlsig, gpointer user_data) { _print_trace(-1, DT_DEBUG_SIGNAL_ACT_DISCONNECT, "disconnect"); return g_signal_handlers_disconnect_matched(G_OBJECT(ctlsig->sink), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, user_data); } void dt_control_signal_block_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data) { g_signal_handlers_block_by_func(G_OBJECT(ctlsig->sink), cb, user_data); } void dt_control_signal_unblock_by_func(const struct dt_control_signal_t *ctlsig, GCallback cb, gpointer user_data) { g_signal_handlers_unblock_by_func(G_OBJECT(ctlsig->sink), cb, user_data); } // clang-format off // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py // vim: shiftwidth=2 expandtab tabstop=2 cindent // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; // clang-format on