From bee5771f225026b30ac26cdc7c1ae948d7dfddbc Mon Sep 17 00:00:00 2001 From: eafton Date: Wed, 3 Dec 2025 22:03:52 +0300 Subject: [PATCH 01/19] Use libdbus for trays on Linux instead of Ayantana/GTK --- src/SDL_list.c | 30 ++ src/SDL_list.h | 1 + src/core/linux/SDL_dbus.c | 665 ++++++++++++++++++++++++++-- src/core/linux/SDL_dbus.h | 46 +- src/tray/SDL_tray_utils.c | 4 + src/tray/SDL_tray_utils.h | 1 + src/tray/unix/SDL_dbustray.c | 611 ++++++++++++++++++++++++++ src/tray/unix/SDL_tray.c | 766 --------------------------------- src/tray/unix/SDL_unixtray.c | 221 ++++++++++ src/tray/unix/SDL_unixtray.h | 68 +++ src/video/x11/SDL_x11toolkit.h | 1 - test/testtray.c | 5 +- 12 files changed, 1605 insertions(+), 814 deletions(-) create mode 100644 src/tray/unix/SDL_dbustray.c delete mode 100644 src/tray/unix/SDL_tray.c create mode 100644 src/tray/unix/SDL_unixtray.c create mode 100644 src/tray/unix/SDL_unixtray.h diff --git a/src/SDL_list.c b/src/SDL_list.c index 04cc0825b447e..d02cd3ae578ba 100644 --- a/src/SDL_list.c +++ b/src/SDL_list.c @@ -37,6 +37,36 @@ bool SDL_ListAdd(SDL_ListNode **head, void *ent) return true; } +// Append +bool SDL_ListAppend(SDL_ListNode **head, void *ent) +{ + SDL_ListNode *cursor; + SDL_ListNode *node; + + if (!head) { + return false; + } + + node = (SDL_ListNode *)SDL_malloc(sizeof(*node)); + if (!node) { + return false; + } + node->entry = ent; + node->next = NULL; + + if (*head) { + cursor = *head; + while (cursor->next) { + cursor = cursor->next; + } + cursor->next = node; + } else { + *head = node; + } + + return true; +} + // Pop from end as a FIFO (if add with SDL_ListAdd) void SDL_ListPop(SDL_ListNode **head, void **ent) { diff --git a/src/SDL_list.h b/src/SDL_list.h index 83ec844b018f2..5ce6bf9e8f42d 100644 --- a/src/SDL_list.h +++ b/src/SDL_list.h @@ -29,6 +29,7 @@ typedef struct SDL_ListNode } SDL_ListNode; bool SDL_ListAdd(SDL_ListNode **head, void *ent); +bool SDL_ListAppend(SDL_ListNode **head, void *ent); void SDL_ListPop(SDL_ListNode **head, void **ent); void SDL_ListRemove(SDL_ListNode **head, void *ent); void SDL_ListClear(SDL_ListNode **head); diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index b675f1872473f..c426d3f36ee7a 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -18,9 +18,9 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ -#include "SDL_internal.h" #include "SDL_dbus.h" #include "../../stdlib/SDL_vacopy.h" +#include "SDL_internal.h" #ifdef SDL_USE_LIBDBUS // we never link directly to libdbus. @@ -31,21 +31,23 @@ static char *inhibit_handle = NULL; static unsigned int screensaver_cookie = 0; static SDL_DBusContext dbus; +#define DBUS_MENU_INTERFACE "com.canonical.dbusmenu" +#define DBUS_MENU_OBJECT_PATH "/Menu" + SDL_ELF_NOTE_DLOPEN( "core-libdbus", "Support for D-Bus IPC", SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - SDL_DRIVER_DBUS_DYNAMIC -); + SDL_DRIVER_DBUS_DYNAMIC); static bool LoadDBUSSyms(void) { -#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \ +#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \ dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y) #define SDL_DBUS_SYM2(TYPE, x, y) \ if (!(dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y))) \ - return false + return false #define SDL_DBUS_SYM_OPTIONAL(TYPE, x) \ SDL_DBUS_SYM2_OPTIONAL(TYPE, x, dbus_##x) @@ -53,48 +55,54 @@ static bool LoadDBUSSyms(void) #define SDL_DBUS_SYM(TYPE, x) \ SDL_DBUS_SYM2(TYPE, x, dbus_##x) - SDL_DBUS_SYM(DBusConnection *(*)(DBusBusType, DBusError *), bus_get_private); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register); + SDL_DBUS_SYM(DBusConnection * (*)(DBusBusType, DBusError *), bus_get_private); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, DBusError *), bus_register); SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match); SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_remove_match); SDL_DBUS_SYM(const char *(*)(DBusConnection *), bus_get_unique_name); - SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private); + SDL_DBUS_SYM(int (*)(DBusConnection *, const char *, unsigned int, DBusError *), bus_request_name); + SDL_DBUS_SYM(DBusConnection * (*)(const char *, DBusError *), connection_open_private); SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction), connection_add_filter); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *), connection_remove_filter); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *, const DBusObjectPathVTable *, void *, DBusError *), connection_try_register_object_path); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusMessage *, dbus_uint32_t *), connection_send); - SDL_DBUS_SYM(DBusMessage *(*)(DBusConnection *, DBusMessage *, int, DBusError *), connection_send_with_reply_and_block); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *), connection_get_is_connected); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction), connection_add_filter); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, DBusHandleMessageFunction, void *), connection_remove_filter); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, const char *, const DBusObjectPathVTable *, void *, DBusError *), connection_try_register_object_path); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, DBusMessage *, dbus_uint32_t *), connection_send); + SDL_DBUS_SYM(DBusMessage * (*)(DBusConnection *, DBusMessage *, int, DBusError *), connection_send_with_reply_and_block); SDL_DBUS_SYM(void (*)(DBusConnection *), connection_close); SDL_DBUS_SYM(void (*)(DBusConnection *), connection_ref); SDL_DBUS_SYM(void (*)(DBusConnection *), connection_unref); SDL_DBUS_SYM(void (*)(DBusConnection *), connection_flush); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write_dispatch); - SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path); - SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call); - SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *), message_new_signal); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, int), connection_read_write); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, int), connection_read_write_dispatch); + SDL_DBUS_SYM(DBusDispatchStatus(*)(DBusConnection *), connection_dispatch); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, const char *, const char *), message_is_signal); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, const char *, const char *), message_is_method_call); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, const char *), message_has_path); + SDL_DBUS_SYM(DBusMessage * (*)(DBusMessage *, const char *, const char *), message_new_error); + SDL_DBUS_SYM(DBusMessage * (*)(const char *, const char *, const char *, const char *), message_new_method_call); + SDL_DBUS_SYM(DBusMessage * (*)(DBusMessage *), message_new_method_return); + SDL_DBUS_SYM(DBusMessage * (*)(const char *, const char *, const char *), message_new_signal); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, int, ...), message_append_args); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, int, va_list), message_append_args_valist); SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const char *, DBusMessageIter *), message_iter_open_container); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const void *), message_iter_append_basic); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, DBusMessageIter *), message_iter_close_container); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, ...), message_get_args); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, va_list), message_get_args_valist); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusMessageIter *), message_iter_init); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *), message_iter_next); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessageIter *, int, const char *, DBusMessageIter *), message_iter_open_container); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessageIter *, int, const void *), message_iter_append_basic); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessageIter *, int, const void *, int), message_iter_append_fixed_array); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessageIter *, DBusMessageIter *), message_iter_close_container); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, DBusError *, int, ...), message_get_args); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, DBusError *, int, va_list), message_get_args_valist); + SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *, int *), message_iter_get_fixed_array); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, DBusMessageIter *), message_iter_init); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessageIter *), message_iter_next); SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *), message_iter_get_basic); SDL_DBUS_SYM(int (*)(DBusMessageIter *), message_iter_get_arg_type); SDL_DBUS_SYM(void (*)(DBusMessageIter *, DBusMessageIter *), message_iter_recurse); SDL_DBUS_SYM(void (*)(DBusMessage *), message_unref); - SDL_DBUS_SYM(dbus_bool_t (*)(void), threads_init_default); + SDL_DBUS_SYM(dbus_bool_t(*)(void), threads_init_default); SDL_DBUS_SYM(void (*)(DBusError *), error_init); - SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *), error_is_set); - SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *, const char *), error_has_name); + SDL_DBUS_SYM(dbus_bool_t(*)(const DBusError *), error_is_set); + SDL_DBUS_SYM(dbus_bool_t(*)(const DBusError *, const char *), error_has_name); SDL_DBUS_SYM(void (*)(DBusError *), error_free); SDL_DBUS_SYM(char *(*)(void), get_local_machine_id); SDL_DBUS_SYM_OPTIONAL(char *(*)(DBusError *), try_get_local_machine_id); @@ -439,12 +447,12 @@ static bool SDL_DBus_AppendDictWithKeysAndValues(DBusMessageIter *iterInit, cons static bool SDL_DBus_AppendDictWithKeyValue(DBusMessageIter *iterInit, const char *key, const char *value) { - const char *keys[1]; - const char *values[1]; + const char *keys[1]; + const char *values[1]; - keys[0] = key; - values[0] = value; - return SDL_DBus_AppendDictWithKeysAndValues(iterInit, keys, values, 1); + keys[0] = key; + values[0] = value; + return SDL_DBus_AppendDictWithKeysAndValues(iterInit, keys, values, 1); } bool SDL_DBus_ScreensaverInhibit(bool inhibit) @@ -622,7 +630,7 @@ char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count) * The spec doesn't define any entries yet so it's empty. */ dbus.message_iter_init_append(msg, &iter); if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) || - !dbus.message_iter_close_container(&iter, &iterDict)) { + !dbus.message_iter_close_container(&iter, &iterDict)) { SDL_OutOfMemory(); dbus.message_unref(msg); goto failed; @@ -666,10 +674,10 @@ static DBusHandlerResult SDL_DBus_CameraPortalMessageHandler(DBusConnection *con if (dbus.message_is_signal(msg, "org.freedesktop.DBus", "NameOwnerChanged")) { if (!dbus.message_get_args(msg, data->err, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &old, - DBUS_TYPE_STRING, &new, - DBUS_TYPE_INVALID)) { + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) { data->done = true; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } @@ -831,4 +839,575 @@ int SDL_DBus_CameraPortalRequestAccess(void) return -1; } +static SDL_DBusMenuItem *GetMenuItemById(SDL_ListNode *menu, long id) +{ + SDL_ListNode *cursor; + + cursor = menu; + while (cursor) { + SDL_DBusMenuItem *item; + + item = cursor->entry; + if (item->id == id) { + return item; + } + + if (item->sub_menu) { + SDL_DBusMenuItem *found; + + found = GetMenuItemById(item->sub_menu, id); + if (found) { + return found; + } + } + cursor = cursor->next; + } + return NULL; +} + +static void AppendMenuItemProperties(SDL_DBusContext *ctx, SDL_DBusMenuItem *item, DBusMessageIter *dict_iter) +{ + DBusMessageIter entry_iter; + DBusMessageIter variant_iter; + const char *key; + const char *value; + dbus_bool_t value_bool; + + if (item->utf8) { + key = "label"; + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &item->utf8); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + } + + key = "type"; + if (item->type == SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR) { + value = "separator"; + } else { + value = "standard"; + } + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + + key = "enabled"; + value_bool = !(item->flags & SDL_DBUS_MENU_ITEM_FLAGS_DISABLED); + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &value_bool); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + + key = "visible"; + value_bool = TRUE; + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &value_bool); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + + if (item->type == SDL_DBUS_MENU_ITEM_TYPE_CHECKBOX) { + int value_int; + + key = "toggle-type"; + value = "checkmark"; + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + + key = "toggle-state"; + value_int = (item->flags & SDL_DBUS_MENU_ITEM_FLAGS_CHECKED) ? 1 : 0; + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "i", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_INT32, &value_int); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + } + + if (item->sub_menu) { + key = "children-display"; + value = "submenu"; + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + } +} + +static void AppendMenuItem(SDL_DBusContext *ctx, SDL_DBusMenuItem *item, DBusMessageIter *array_iter, int depth) +{ + DBusMessageIter struct_iter, dict_iter, children_iter; + + ctx->message_iter_open_container(array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); + ctx->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &item->id); + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + AppendMenuItemProperties(ctx, item, &dict_iter); + ctx->message_iter_close_container(&struct_iter, &dict_iter); + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "v", &children_iter); + if (item->sub_menu && depth > 0) { + SDL_ListNode *cursor; + + cursor = item->sub_menu; + while (cursor) { + SDL_DBusMenuItem *child; + DBusMessageIter variant_iter; + + child = cursor->entry; + ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &variant_iter); + AppendMenuItem(ctx, child, &variant_iter, depth - 1); + ctx->message_iter_close_container(&children_iter, &variant_iter); + cursor = cursor->next; + } + } + ctx->message_iter_close_container(&struct_iter, &children_iter); + ctx->message_iter_close_container(array_iter, &struct_iter); +} + +static DBusHandlerResult HandleGetLayout(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ + DBusMessage *reply; + DBusMessageIter reply_iter, struct_iter, dict_iter, children_iter; + DBusMessageIter entry_iter, variant_iter; + DBusMessageIter args; + const char *key; + const char *val; + long parent_id; + long recursion_depth; + long root_id; + unsigned long revision; + + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_INT32) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &parent_id); + ctx->message_iter_next(&args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_INT32) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &recursion_depth); + if (recursion_depth == -1) { + recursion_depth = 100; + } + + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &reply_iter); + + revision = 0; + if (menu) { + if (menu->entry) { + revision = ((SDL_DBusMenuItem *)menu->entry)->revision; + } + } + ctx->message_iter_append_basic(&reply_iter, DBUS_TYPE_UINT32, &revision); + + ctx->message_iter_open_container(&reply_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); + + root_id = 0; + ctx->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &root_id); + + key = "children-display"; + val = "submenu"; + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + ctx->message_iter_close_container(&struct_iter, &dict_iter); + + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "v", &children_iter); + if (!parent_id && menu) { + SDL_ListNode *cursor; + + cursor = menu; + while (cursor) { + SDL_DBusMenuItem *item; + DBusMessageIter cvariant_iter; + + item = cursor->entry; + ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &cvariant_iter); + AppendMenuItem(ctx, item, &variant_iter, recursion_depth); + ctx->message_iter_close_container(&children_iter, &cvariant_iter); + cursor = cursor->next; + } + } else if (parent_id) { + SDL_DBusMenuItem *parent; + + parent = GetMenuItemById(menu, parent_id); + if (parent && parent->sub_menu) { + SDL_ListNode *cursor; + + cursor = parent->sub_menu; + while (cursor) { + SDL_DBusMenuItem *item; + DBusMessageIter cvariant_iter; + + item = cursor->entry; + ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &cvariant_iter); + AppendMenuItem(ctx, item, &cvariant_iter, recursion_depth); + ctx->message_iter_close_container(&children_iter, &cvariant_iter); + + cursor = cursor->next; + } + } + } + ctx->message_iter_close_container(&struct_iter, &children_iter); + ctx->message_iter_close_container(&reply_iter, &struct_iter); + + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult HandleEvent(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ + DBusMessage *reply; + const char *event_id; + DBusMessageIter args; + long id; + + ctx->message_iter_init(msg, &args); + ctx->message_iter_get_basic(&args, &id); + ctx->message_iter_next(&args); + ctx->message_iter_get_basic(&args, &event_id); + + if (!SDL_strcmp(event_id, "clicked")) { + SDL_DBusMenuItem *item; + + item = GetMenuItemById(menu, id); + if (item) { + if (item->cb) { + item->cb(item, item->cb_data); + } + } + } + + reply = ctx->message_new_method_return(msg); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult HandleGetProperty(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ + DBusMessage *reply; + SDL_DBusMenuItem *item; + const char *property; + const char *val; + DBusMessageIter args; + DBusMessageIter iter, variant_iter; + long id; + int int_val; + dbus_bool_t bool_val; + + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_INT32) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &id); + ctx->message_iter_next(&args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &property); + + item = GetMenuItemById(menu, id); + if (!item) { + DBusMessage *error; + + error = ctx->message_new_error(msg, "com.canonical.dbusmenu.Error", "Item not found"); + ctx->connection_send(conn, error, NULL); + ctx->message_unref(error); + return DBUS_HANDLER_RESULT_HANDLED; + } + + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &iter); + if (!SDL_strcmp(property, "label")) { + val = item->utf8 ? item->utf8 : ""; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "enabled")) { + bool_val = !(item->flags & SDL_DBUS_MENU_ITEM_FLAGS_DISABLED); + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "visible")) { + bool_val = 1; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "type")) { + if (item->type == SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR) { + val = "separator"; + } else { + val = "standard"; + } + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "toggle-state")) { + int_val = (item->flags & SDL_DBUS_MENU_ITEM_FLAGS_CHECKED) ? 1 : 0; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "i", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_INT32, &int_val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "children-display")) { + val = item->sub_menu ? "submenu" : ""; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else { + val = ""; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&iter, &variant_iter); + } + + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult HandleGetGroupProperties(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ +#define FILTER_PROPS_SZ 32 + DBusMessage *reply; + DBusMessageIter args, array_iter, prop_iter; + DBusMessageIter iter, reply_array_iter; + const char *filter_props[FILTER_PROPS_SZ]; + long *ids; + int ids_sz; + int filter_sz; + int i; + int j; + + ids_sz = 0; + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + ctx->message_iter_recurse(&args, &array_iter); + if (ctx->message_iter_get_arg_type(&array_iter) == DBUS_TYPE_INT32) { + ctx->message_iter_get_fixed_array(&array_iter, &ids, &ids_sz); + } + + ctx->message_iter_next(&args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + ctx->message_iter_recurse(&args, &prop_iter); + filter_sz = 0; + while (ctx->message_iter_get_arg_type(&prop_iter) == DBUS_TYPE_STRING) { + if (filter_sz < FILTER_PROPS_SZ) { + ctx->message_iter_get_basic(&prop_iter, &filter_props[filter_sz]); + filter_sz++; + } + ctx->message_iter_next(&prop_iter); + } + + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &iter); + ctx->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ia{sv})", &reply_array_iter); + + for (i = 0; i < ids_sz; i++) { + SDL_DBusMenuItem *item; + + item = GetMenuItemById(menu, ids[i]); + if (item) { + DBusMessageIter struct_iter, dict_iter; + + ctx->message_iter_open_container(&reply_array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); + ctx->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &ids[i]); + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + + if (filter_sz == 0) { + AppendMenuItemProperties(ctx, item, &dict_iter); + } else { + for (j = 0; j < filter_sz; j++) { + DBusMessageIter entry_iter, variant_iter; + const char *prop; + const char *val; + int int_val; + dbus_bool_t bool_val; + + prop = filter_props[j]; + if (!SDL_strcmp(prop, "label") && item->utf8) { + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &item->utf8); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "type")) { + val = (item->type == SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR) ? "separator" : "standard"; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "enabled")) { + bool_val = !(item->flags & SDL_DBUS_MENU_ITEM_FLAGS_DISABLED); + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "visible")) { + bool_val = 1; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "toggle-type") && item->type == SDL_DBUS_MENU_ITEM_TYPE_CHECKBOX) { + val = "checkmark"; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "toggle-state")) { + int_val = (item->flags & SDL_DBUS_MENU_ITEM_FLAGS_CHECKED) ? 1 : 0; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "i", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_INT32, &int_val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "children-display") && item->sub_menu) { + val = "submenu"; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } + } + } + + ctx->message_iter_close_container(&struct_iter, &dict_iter); + ctx->message_iter_close_container(&reply_array_iter, &struct_iter); + } + } + + ctx->message_iter_close_container(&iter, &reply_array_iter); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult DBusMenuMessageFilter(DBusConnection *conn, DBusMessage *msg, void *user_data) +{ + SDL_ListNode *menu; + SDL_DBusContext *ctx; + + menu = user_data; + if (!menu || !menu->entry) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + ctx = ((SDL_DBusMenuItem *)menu->entry)->dbus; + if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "GetLayout")) { + return HandleGetLayout(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "Event")) { + return HandleEvent(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "AboutToShow")) { + DBusMessage *reply; + dbus_bool_t need_update; + + need_update = 0; + reply = ctx->message_new_method_return(msg); + ctx->message_append_args(reply, DBUS_TYPE_BOOLEAN, &need_update, DBUS_TYPE_INVALID); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "GetGroupProperties")) { + return HandleGetGroupProperties(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "GetProperty")) { + return HandleGetProperty(ctx, menu, conn, msg); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +const char *SDL_DBus_ExportMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu) +{ + DBusObjectPathVTable vtable; + + if (!ctx || !menu) { + return NULL; + } + + if (menu->entry) { + SDL_DBusMenuItem *item; + + item = menu->entry; + item->dbus = ctx; + item->revision++; + } + + vtable.message_function = DBusMenuMessageFilter; + vtable.unregister_function = NULL; + if (!ctx->connection_try_register_object_path(conn, DBUS_MENU_OBJECT_PATH, &vtable, menu, NULL)) { + return NULL; + } + + return DBUS_MENU_OBJECT_PATH; +} + +void SDL_DBus_UpdateMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu) +{ + DBusMessage *signal; + unsigned long revision; + + if (!ctx || !menu) { + return; + } + + revision = 0; + if (menu->entry) { + SDL_DBusMenuItem *item; + + item = menu->entry; + item->revision++; + revision = item->revision; + } + + signal = ctx->message_new_signal(DBUS_MENU_OBJECT_PATH, DBUS_MENU_INTERFACE, "LayoutUpdated"); + if (signal) { + long parent; + + parent = 0; + ctx->message_append_args(signal, DBUS_TYPE_UINT32, &revision, DBUS_TYPE_INT32, &parent, DBUS_TYPE_INVALID); + ctx->connection_send(conn, signal, NULL); + ctx->message_unref(signal); + } +} + #endif diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 230b20fded057..738fc064a5ccd 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -26,16 +26,17 @@ #ifdef HAVE_DBUS_DBUS_H #define SDL_USE_LIBDBUS 1 +#include "../../SDL_list.h" #include #ifndef DBUS_TIMEOUT_USE_DEFAULT #define DBUS_TIMEOUT_USE_DEFAULT -1 #endif #ifndef DBUS_TIMEOUT_INFINITE -#define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff) +#define DBUS_TIMEOUT_INFINITE ((int)0x7fffffff) #endif #ifndef DBUS_TYPE_UNIX_FD -#define DBUS_TYPE_UNIX_FD ((int) 'h') +#define DBUS_TYPE_UNIX_FD ((int)'h') #endif typedef struct SDL_DBusContext @@ -47,6 +48,7 @@ typedef struct SDL_DBusContext dbus_bool_t (*bus_register)(DBusConnection *, DBusError *); void (*bus_add_match)(DBusConnection *, const char *, DBusError *); void (*bus_remove_match)(DBusConnection *, const char *, DBusError *); + int (*bus_request_name)(DBusConnection *, const char *, unsigned int, DBusError *); const char *(*bus_get_unique_name)(DBusConnection *); DBusConnection *(*connection_open_private)(const char *, DBusError *); void (*connection_set_exit_on_disconnect)(DBusConnection *, dbus_bool_t); @@ -65,17 +67,22 @@ typedef struct SDL_DBusContext dbus_bool_t (*connection_read_write_dispatch)(DBusConnection *, int); DBusDispatchStatus (*connection_dispatch)(DBusConnection *); dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *); + dbus_bool_t (*message_is_method_call)(DBusMessage *, const char *, const char *); dbus_bool_t (*message_has_path)(DBusMessage *, const char *); + DBusMessage *(*message_new_error)(DBusMessage *, const char *, const char *); DBusMessage *(*message_new_method_call)(const char *, const char *, const char *, const char *); + DBusMessage *(*message_new_method_return)(DBusMessage *); DBusMessage *(*message_new_signal)(const char *, const char *, const char *); dbus_bool_t (*message_append_args)(DBusMessage *, int, ...); dbus_bool_t (*message_append_args_valist)(DBusMessage *, int, va_list); void (*message_iter_init_append)(DBusMessage *, DBusMessageIter *); dbus_bool_t (*message_iter_open_container)(DBusMessageIter *, int, const char *, DBusMessageIter *); dbus_bool_t (*message_iter_append_basic)(DBusMessageIter *, int, const void *); + dbus_bool_t (*message_iter_append_fixed_array)(DBusMessageIter *, int, const void *, int); dbus_bool_t (*message_iter_close_container)(DBusMessageIter *, DBusMessageIter *); dbus_bool_t (*message_get_args)(DBusMessage *, DBusError *, int, ...); dbus_bool_t (*message_get_args_valist)(DBusMessage *, DBusError *, int, va_list); + void (*message_iter_get_fixed_array)(DBusMessageIter *, void *, int *); dbus_bool_t (*message_iter_init)(DBusMessage *, DBusMessageIter *); dbus_bool_t (*message_iter_next)(DBusMessageIter *); void (*message_iter_get_basic)(DBusMessageIter *, void *); @@ -95,6 +102,37 @@ typedef struct SDL_DBusContext } SDL_DBusContext; +typedef enum SDL_DBusMenuItemType +{ + SDL_DBUS_MENU_ITEM_TYPE_NORMAL, + SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR, + SDL_DBUS_MENU_ITEM_TYPE_CHECKBOX +} SDL_DBusMenuItemType; + +typedef enum SDL_DBusMenuItemFlags +{ + SDL_DBUS_MENU_ITEM_FLAGS_NONE = 0, + SDL_DBUS_MENU_ITEM_FLAGS_DISABLED = 1 << 0, + SDL_DBUS_MENU_ITEM_FLAGS_CHECKED = 1 << 1, + SDL_DBUS_MENU_ITEM_FLAGS_BAR_ITEM = 1 << 2 +} SDL_DBusMenuItemFlags; + +/* This struct is meant to be in an SDL_ListNode just like sub_menu */ +typedef struct SDL_DBusMenuItem +{ + const char *utf8 /* Text */; + SDL_DBusMenuItemType type; + SDL_DBusMenuItemFlags flags; + void *cb_data; + void (*cb)(struct SDL_DBusMenuItem *, void *); + SDL_ListNode *sub_menu; /* NULL if no submenu */ + + /* Internal use */ + SDL_DBusContext *dbus; + long id; + unsigned long revision; +} SDL_DBusMenuItem; + extern void SDL_DBus_Init(void); extern void SDL_DBus_Quit(void); extern SDL_DBusContext *SDL_DBus_GetContext(void); @@ -119,6 +157,10 @@ extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_ extern int SDL_DBus_CameraPortalRequestAccess(void); +/* Menu functions */ +extern const char *SDL_DBus_ExportMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu); +extern void SDL_DBus_UpdateMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu); + #endif // HAVE_DBUS_DBUS_H #endif // SDL_dbus_h_ diff --git a/src/tray/SDL_tray_utils.c b/src/tray/SDL_tray_utils.c index c08c46c7388d4..89d500859b01a 100644 --- a/src/tray/SDL_tray_utils.c +++ b/src/tray/SDL_tray_utils.c @@ -91,3 +91,7 @@ bool SDL_HasActiveTrays(void) { return (active_trays > 0); } + +int SDL_GetActiveTrayCount(void) { + return active_trays; +} diff --git a/src/tray/SDL_tray_utils.h b/src/tray/SDL_tray_utils.h index 76745ef4557da..a631a77bf747d 100644 --- a/src/tray/SDL_tray_utils.h +++ b/src/tray/SDL_tray_utils.h @@ -26,3 +26,4 @@ extern void SDL_RegisterTray(SDL_Tray *tray); extern void SDL_UnregisterTray(SDL_Tray *tray); extern void SDL_CleanupTrays(void); extern bool SDL_HasActiveTrays(void); +extern int SDL_GetActiveTrayCount(void); diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c new file mode 100644 index 0000000000000..24ecb164700d5 --- /dev/null +++ b/src/tray/unix/SDL_dbustray.c @@ -0,0 +1,611 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "../../core/linux/SDL_dbus.h" +#include "SDL_internal.h" + +#ifdef SDL_USE_LIBDBUS + +#include "../../video/SDL_surface_c.h" +#include "../SDL_tray_utils.h" +#include "SDL_unixtray.h" +#include + +#define SNI_INTERFACE "org.kde.StatusNotifierItem" +#define SNI_WATCHER_SERVICE "org.kde.StatusNotifierWatcher" +#define SNI_WATCHER_PATH "/StatusNotifierWatcher" +#define SNI_WATCHER_INTERFACE "org.kde.StatusNotifierWatcher" +#define SNI_OBJECT_PATH "/StatusNotifierItem" + +typedef struct SDL_TrayDriverDBus +{ + SDL_TrayDriver _parent; + + SDL_DBusContext *dbus; +} SDL_TrayDriverDBus; + +typedef struct SDL_TrayDBus +{ + SDL_Tray _parent; + + DBusConnection *connection; + char *object_name; + + char *tooltip; + SDL_Surface *surface; +} SDL_TrayDBus; + +typedef struct SDL_TrayMenuDBus +{ + SDL_TrayMenu _parent; + + SDL_ListNode *menu; + const char *menu_path; +} SDL_TrayMenuDBus; + +typedef struct SDL_TrayEntryDBus +{ + SDL_TrayEntry _parent; + + SDL_DBusMenuItem item; +} SDL_TrayEntryDBus; + +static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, SDL_TrayDriverDBus *driver, DBusMessage *msg) +{ + SDL_TrayMenuDBus *menu_dbus; + DBusMessageIter iter, dict_iter, entry_iter, variant_iter; + DBusMessageIter struct_iter, array_iter; + DBusMessage *reply; + const char *interface; + const char *key; + const char *value; + const char *empty; + dbus_bool_t bool_value; + + empty = ""; + driver->dbus->message_iter_init(msg, &iter); + driver->dbus->message_iter_get_basic(&iter, &interface); + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->message_iter_init_append(reply, &iter); + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + + key = "Category"; + value = "ApplicationStatus"; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + + key = "Id"; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &tray_dbus->object_name); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + + key = "Title"; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &empty); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + + key = "Status"; + value = "Active"; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + + key = "IconName"; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &empty); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + + key = "ItemIsMenu"; + bool_value = TRUE; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_value); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + + menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + if (menu_dbus) { + if (menu_dbus->menu_path) { + key = "Menu"; + value = menu_dbus->menu_path; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "o", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + } + } + + if (tray_dbus->surface) { + DBusMessageIter pixmap_array_iter, pixmap_struct_iter, pixmap_byte_array_iter; + + key = "IconPixmap"; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "a(iiay)", &variant_iter); + driver->dbus->message_iter_open_container(&variant_iter, DBUS_TYPE_ARRAY, "(iiay)", &pixmap_array_iter); + driver->dbus->message_iter_open_container(&pixmap_array_iter, DBUS_TYPE_STRUCT, NULL, &pixmap_struct_iter); + driver->dbus->message_iter_append_basic(&pixmap_struct_iter, DBUS_TYPE_INT32, &tray_dbus->surface->w); + driver->dbus->message_iter_append_basic(&pixmap_struct_iter, DBUS_TYPE_INT32, &tray_dbus->surface->h); + driver->dbus->message_iter_open_container(&pixmap_struct_iter, DBUS_TYPE_ARRAY, "y", &pixmap_byte_array_iter); + driver->dbus->message_iter_append_fixed_array(&pixmap_byte_array_iter, DBUS_TYPE_BYTE, &tray_dbus->surface->pixels, tray_dbus->surface->pitch * tray_dbus->surface->h); + driver->dbus->message_iter_close_container(&pixmap_struct_iter, &pixmap_byte_array_iter); + driver->dbus->message_iter_close_container(&pixmap_array_iter, &pixmap_struct_iter); + driver->dbus->message_iter_close_container(&variant_iter, &pixmap_array_iter); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + } + + if (tray_dbus->tooltip) { + key = "ToolTip"; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "(sa(iiay)ss)", &variant_iter); + driver->dbus->message_iter_open_container(&variant_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); + driver->dbus->message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &empty); + driver->dbus->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(iiay)", &array_iter); + driver->dbus->message_iter_close_container(&struct_iter, &array_iter); + driver->dbus->message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &empty); + driver->dbus->message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &tray_dbus->tooltip); + driver->dbus->message_iter_close_container(&variant_iter, &struct_iter); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + driver->dbus->message_iter_close_container(&iter, &dict_iter); + } + + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult HandleGetProp(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, SDL_TrayDriverDBus *driver, DBusMessage *msg) +{ + SDL_TrayMenuDBus *menu_dbus; + DBusMessageIter iter, variant_iter; + DBusMessage *reply; + const char *interface, *property; + const char *value; + const char *empty; + dbus_bool_t bool_value; + + empty = ""; + driver->dbus->message_iter_init(msg, &iter); + driver->dbus->message_iter_get_basic(&iter, &interface); + driver->dbus->message_iter_next(&iter); + driver->dbus->message_iter_get_basic(&iter, &property); + + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->message_iter_init_append(reply, &iter); + + menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + if (!SDL_strcmp(property, "Category")) { + value = "ApplicationStatus"; + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "Id")) { + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &tray_dbus->object_name); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "Title")) { + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &empty); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "Status")) { + value = "Active"; + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "IconName")) { + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &empty); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "ItemIsMenu")) { + bool_value = TRUE; + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_value); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "Menu") && menu_dbus) { + if (menu_dbus->menu_path) { + value = menu_dbus->menu_path; + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "o", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } + } else if (!SDL_strcmp(property, "IconPixmap") && tray_dbus->surface) { + DBusMessageIter array_iter, struct_iter; + DBusMessageIter byte_array_iter; + + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "a(iiay)", &variant_iter); + driver->dbus->message_iter_open_container(&variant_iter, DBUS_TYPE_ARRAY, "(iiay)", &array_iter); + driver->dbus->message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); + driver->dbus->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &tray_dbus->surface->w); + driver->dbus->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &tray_dbus->surface->h); + driver->dbus->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "y", &byte_array_iter); + driver->dbus->message_iter_append_fixed_array(&byte_array_iter, DBUS_TYPE_BYTE, &tray_dbus->surface->pixels, tray_dbus->surface->pitch * tray_dbus->surface->h); + driver->dbus->message_iter_close_container(&struct_iter, &byte_array_iter); + driver->dbus->message_iter_close_container(&array_iter, &struct_iter); + driver->dbus->message_iter_close_container(&variant_iter, &array_iter); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "ToolTip") && tray_dbus->tooltip) { + DBusMessageIter struct_iter, array_iter; + + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "(sa(iiay)ss)", &variant_iter); + driver->dbus->message_iter_open_container(&variant_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); + driver->dbus->message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &empty); + driver->dbus->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(iiay)", &array_iter); + driver->dbus->message_iter_close_container(&struct_iter, &array_iter); + driver->dbus->message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &empty); + driver->dbus->message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &tray_dbus->tooltip); + driver->dbus->message_iter_close_container(&variant_iter, &struct_iter); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else { + driver->dbus->message_unref(reply); + reply = driver->dbus->message_new_error(msg, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); + } + + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult MessageHandler(DBusConnection *connection, DBusMessage *msg, void *user_data) +{ + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + + tray = user_data; + driver = (SDL_TrayDriverDBus *)tray->driver; + + if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "Get")) { + return HandleGetProp(tray, (SDL_TrayDBus *)tray, driver, msg); + } else if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "GetAll")) { + return HandleGetAllProps(tray, (SDL_TrayDBus *)tray, driver, msg); + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "ContextMenu")) { + puts("hello"); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tooltip) +{ + SDL_TrayDriverDBus *dbus_driver; + SDL_TrayDBus *tray_dbus; + SDL_Tray *tray; + const char *object_path; + DBusObjectPathVTable vtable; + DBusError err; + int status; + dbus_bool_t bool_status; +#define CLEANUP() \ + SDL_free(tray_dbus->tooltip); \ + SDL_DestroySurface(tray_dbus->surface); \ + SDL_free(tray_dbus) +#define CLEANUP2() \ + dbus_driver->dbus->connection_close(tray_dbus->connection); \ + CLEANUP() + + /* alloc */ + tray_dbus = SDL_malloc(sizeof(SDL_TrayDBus)); + tray = (SDL_Tray *)tray_dbus; + if (!tray_dbus) { + return NULL; + } + + /* fill */ + tray->menu = NULL; + tray->driver = driver; + tray_dbus->tooltip = SDL_strdup(tooltip); + tray_dbus->surface = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_ARGB32); + + /* connect */ + dbus_driver = (SDL_TrayDriverDBus *)driver; + dbus_driver->dbus->error_init(&err); + tray_dbus->connection = dbus_driver->dbus->bus_get_private(DBUS_BUS_SESSION, &err); + if (dbus_driver->dbus->error_is_set(&err)) { + SDL_SetError("Unable to create tray: %s", err.message); + dbus_driver->dbus->error_free(&err); + CLEANUP(); + return NULL; + } + if (!tray_dbus->connection) { + SDL_SetError("Unable to create tray: unable to get connection!"); + CLEANUP(); + return NULL; + } + + /* request name */ + driver->count++; + SDL_asprintf(&tray_dbus->object_name, "org.kde.StatusNotifierItem-%d-%d", getpid(), driver->count); + status = dbus_driver->dbus->bus_request_name(tray_dbus->connection, tray_dbus->object_name, DBUS_NAME_FLAG_REPLACE_EXISTING, &err); + if (dbus_driver->dbus->error_is_set(&err)) { + SDL_SetError("Unable to create tray: %s", err.message); + dbus_driver->dbus->error_free(&err); + CLEANUP2(); + return NULL; + } + if (status != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + SDL_SetError("Unable to create tray: unable to request a unique name!"); + CLEANUP2(); + return NULL; + } + + /* create object */ + object_path = SNI_OBJECT_PATH; + vtable.message_function = MessageHandler; + bool_status = dbus_driver->dbus->connection_try_register_object_path(tray_dbus->connection, object_path, &vtable, tray_dbus, &err); + if (dbus_driver->dbus->error_is_set(&err)) { + SDL_SetError("Unable to create tray: %s", err.message); + dbus_driver->dbus->error_free(&err); + CLEANUP2(); + return NULL; + } + if (!bool_status) { + SDL_SetError("Unable to create tray: unable to register object path!"); + CLEANUP2(); + return NULL; + } + if (!SDL_DBus_CallVoidMethodOnConnection(tray_dbus->connection, SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "RegisterStatusNotifierItem", DBUS_TYPE_STRING, &object_path, DBUS_TYPE_INVALID)) { + SDL_SetError("Unable to create tray: unable to register status notifier item!"); + CLEANUP2(); + return NULL; + } + + return tray; +} + +void DestroyTray(SDL_Tray *tray) +{ + SDL_TrayDBus *tray_dbus; + SDL_TrayDriverDBus *driver; + + driver = (SDL_TrayDriverDBus *)tray->driver; + tray_dbus = (SDL_TrayDBus *)tray; + + driver->dbus->connection_close(tray_dbus->connection); + SDL_free(tray_dbus->tooltip); + SDL_DestroySurface(tray_dbus->surface); + SDL_free(tray); +} + +void UpdateTray(SDL_Tray *tray) +{ + SDL_TrayDBus *tray_dbus; + SDL_TrayDriverDBus *driver; + + driver = (SDL_TrayDriverDBus *)tray->driver; + tray_dbus = (SDL_TrayDBus *)tray; + + driver->dbus->connection_read_write(tray_dbus->connection, 0); + while (driver->dbus->connection_dispatch(tray_dbus->connection) == DBUS_DISPATCH_DATA_REMAINS) { + SDL_DelayNS(SDL_US_TO_NS(10)); + } +} + +void SetTrayIcon(SDL_Tray *tray, SDL_Surface *surface) +{ + SDL_TrayDBus *tray_dbus; + SDL_TrayDriverDBus *driver; + DBusMessage *signal; + + driver = (SDL_TrayDriverDBus *)tray->driver; + tray_dbus = (SDL_TrayDBus *)tray; + + if (tray_dbus->surface) { + SDL_DestroySurface(tray_dbus->surface); + } + tray_dbus->surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB32); + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewIcon"); + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); +} + +void SetTrayTooltip(SDL_Tray *tray, const char *text) +{ + SDL_TrayDBus *tray_dbus; + SDL_TrayDriverDBus *driver; + DBusMessage *signal; + + driver = (SDL_TrayDriverDBus *)tray->driver; + tray_dbus = (SDL_TrayDBus *)tray; + + if (tray_dbus->tooltip) { + SDL_free(tray_dbus->tooltip); + } + tray_dbus->tooltip = SDL_strdup(text); + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewToolTip"); + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); +} + +SDL_TrayMenu *CreateTrayMenu(SDL_Tray *tray) +{ + SDL_TrayMenuDBus *menu_dbus; + + menu_dbus = SDL_malloc(sizeof(SDL_TrayMenuDBus)); + tray->menu = (SDL_TrayMenu *)menu_dbus; + if (!menu_dbus) { + SDL_SetError("Unable to create tray menu: allocation failure!"); + return NULL; + } + + menu_dbus->menu = NULL; + menu_dbus->menu_path = NULL; + tray->menu->parent_tray = tray; + tray->menu->parent_entry = NULL; + + return tray->menu; +} + +SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) +{ + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayMenuDBus *menu_dbus; + SDL_TrayDBus *tray_dbus; + SDL_TrayEntry *entry; + SDL_TrayEntryDBus *entry_dbus; + bool update; + + tray = menu->parent_tray; + menu_dbus = (SDL_TrayMenuDBus *)menu; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + + /* TODD: pos */ + entry_dbus = SDL_malloc(sizeof(SDL_TrayEntryDBus)); + entry = (SDL_TrayEntry *)entry_dbus; + if (!entry_dbus) { + SDL_SetError("Unable to create tray entry: allocation failure!"); + return NULL; + } + + entry_dbus->item.utf8 = label; + if (!label) { + entry_dbus->item.type = SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR; + } else if (flags & SDL_TRAYENTRY_CHECKBOX) { + entry_dbus->item.type = SDL_DBUS_MENU_ITEM_TYPE_CHECKBOX; + } else { + entry_dbus->item.type = SDL_DBUS_MENU_ITEM_TYPE_NORMAL; + } + entry_dbus->item.flags = SDL_DBUS_MENU_ITEM_FLAGS_NONE; + entry_dbus->item.cb_data = NULL; + entry_dbus->item.cb = NULL; + entry_dbus->item.sub_menu = NULL; + + if (menu_dbus->menu) { + update = true; + } else { + update = false; + } + SDL_ListAppend(&menu_dbus->menu, &entry_dbus->item); + if (update) { + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); + } else { + menu_dbus->menu_path = SDL_DBus_ExportMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); + } + + return entry; +} + +void DestroyDriver(SDL_TrayDriver *driver) +{ + SDL_DBus_Quit(); + SDL_free(driver); +} + +SDL_TrayDriver *SDL_Tray_CreateDBusDriver(void) +{ + SDL_TrayDriverDBus *dbus_driver; + SDL_TrayDriver *driver; + SDL_DBusContext *ctx; + char **paths; + int count; + int i; + bool sni_supported; + + /* init dbus, get ctx */ + SDL_DBus_Init(); + ctx = SDL_DBus_GetContext(); + if (!ctx) { + return NULL; + } + + /* detect if we have a watcher and a host */ + sni_supported = false; + paths = NULL; + count = 0; + if (SDL_DBus_CallMethod("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames", DBUS_TYPE_INVALID, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &paths, &count, DBUS_TYPE_INVALID)) { + if (paths) { + bool watcher_found; + + watcher_found = false; + for (i = 0; i < count; i++) { + if (!SDL_strcmp(paths[i], SNI_WATCHER_SERVICE)) { + watcher_found = true; + } + } + ctx->free_string_array(paths); + + if (watcher_found) { + dbus_bool_t host_registered; + + host_registered = FALSE; + SDL_DBus_QueryProperty(SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "IsStatusNotifierHostRegistered", DBUS_TYPE_BOOLEAN, &host_registered); + if (watcher_found && host_registered) { + sni_supported = true; + } + } + } + } + if (!sni_supported) { + SDL_SetError("Unable to create tray: no SNI support!"); + SDL_DBus_Quit(); + return NULL; + } + + /* alloc */ + dbus_driver = SDL_malloc(sizeof(SDL_TrayDriverDBus)); + driver = (SDL_TrayDriver *)dbus_driver; + if (!dbus_driver) { + return NULL; + } + + /* fill */ + dbus_driver->dbus = ctx; + driver->name = "dbus"; + driver->count = 0; + driver->CreateTray = CreateTray; + driver->DestroyTray = DestroyTray; + driver->UpdateTray = UpdateTray; + driver->SetTrayIcon = SetTrayIcon; + driver->SetTrayTooltip = SetTrayTooltip; + driver->CreateTrayMenu = CreateTrayMenu; + driver->InsertTrayEntryAt = InsertTrayEntryAt; + driver->DestroyDriver = DestroyDriver; + + return driver; +} + +#endif diff --git a/src/tray/unix/SDL_tray.c b/src/tray/unix/SDL_tray.c deleted file mode 100644 index faf3545b93894..0000000000000 --- a/src/tray/unix/SDL_tray.c +++ /dev/null @@ -1,766 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ - -#include "SDL_internal.h" - -#include "../SDL_tray_utils.h" -#include "../../video/SDL_stb_c.h" - -#include -#include - -/* getpid() */ -#include - -/* APPINDICATOR_HEADER is not exposed as a build setting, but the code has been - written nevertheless to make future maintenance easier. */ -#ifdef APPINDICATOR_HEADER -#include APPINDICATOR_HEADER -#else -#include "../../core/unix/SDL_gtk.h" - -/* ------------------------------------------------------------------------- */ -/* BEGIN THIRD-PARTY HEADER CONTENT */ -/* ------------------------------------------------------------------------- */ -/* AppIndicator */ - -typedef enum { - APP_INDICATOR_CATEGORY_APPLICATION_STATUS, - APP_INDICATOR_CATEGORY_COMMUNICATIONS, - APP_INDICATOR_CATEGORY_SYSTEM_SERVICES, - APP_INDICATOR_CATEGORY_HARDWARE, - APP_INDICATOR_CATEGORY_OTHER -} AppIndicatorCategory; - -typedef enum { - APP_INDICATOR_STATUS_PASSIVE, - APP_INDICATOR_STATUS_ACTIVE, - APP_INDICATOR_STATUS_ATTENTION -} AppIndicatorStatus; - -typedef struct _AppIndicator AppIndicator; - -static AppIndicator *(*app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category); -static void (*app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status); -static void (*app_indicator_set_icon)(AppIndicator *self, const gchar *icon_name); -static void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu); - -/* ------------------------------------------------------------------------- */ -/* END THIRD-PARTY HEADER CONTENT */ -/* ------------------------------------------------------------------------- */ -#endif - -static void *libappindicator = NULL; - -static void quit_appindicator(void) -{ - if (libappindicator) { - dlclose(libappindicator); - libappindicator = NULL; - } -} - -const char *appindicator_names[] = { -#ifdef SDL_PLATFORM_OPENBSD - "libayatana-appindicator3.so", - "libappindicator3.so", -#else - "libayatana-appindicator3.so.1", - "libappindicator3.so.1", -#endif - NULL -}; - -static void *find_lib(const char **names) -{ - const char **name_ptr = names; - void *handle = NULL; - - do { - handle = dlopen(*name_ptr, RTLD_LAZY); - } while (*++name_ptr && !handle); - - return handle; -} - -static bool init_appindicator(void) -{ - if (libappindicator) { - return true; - } - - libappindicator = find_lib(appindicator_names); - - if (!libappindicator) { - quit_appindicator(); - return SDL_SetError("Could not load AppIndicator libraries"); - } - - app_indicator_new = dlsym(libappindicator, "app_indicator_new"); - app_indicator_set_status = dlsym(libappindicator, "app_indicator_set_status"); - app_indicator_set_icon = dlsym(libappindicator, "app_indicator_set_icon"); - app_indicator_set_menu = dlsym(libappindicator, "app_indicator_set_menu"); - - if (!app_indicator_new || - !app_indicator_set_status || - !app_indicator_set_icon || - !app_indicator_set_menu) { - quit_appindicator(); - return SDL_SetError("Could not load AppIndicator functions"); - } - - return true; -} - -struct SDL_TrayMenu { - GtkMenuShell *menu; - - int nEntries; - SDL_TrayEntry **entries; - - SDL_Tray *parent_tray; - SDL_TrayEntry *parent_entry; -}; - -struct SDL_TrayEntry { - SDL_TrayMenu *parent; - GtkWidget *item; - - /* Checkboxes are "activated" when programmatically checked/unchecked; this - is a workaround. */ - bool ignore_signal; - - SDL_TrayEntryFlags flags; - SDL_TrayCallback callback; - void *userdata; - SDL_TrayMenu *submenu; -}; - -struct SDL_Tray { - AppIndicator *indicator; - SDL_TrayMenu *menu; - char *icon_dir; - char *icon_path; - - GtkMenuShell *menu_cached; -}; - -static void call_callback(GtkMenuItem *item, gpointer ptr) -{ - SDL_TrayEntry *entry = ptr; - - /* Not needed with AppIndicator, may be needed with other frameworks */ - /* if (entry->flags & SDL_TRAYENTRY_CHECKBOX) { - SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry)); - } */ - - if (entry->ignore_signal) { - return; - } - - if (entry->callback) { - entry->callback(entry->userdata, entry); - } -} - -static bool new_tmp_filename(SDL_Tray *tray) -{ - static int count = 0; - - int would_have_written = SDL_asprintf(&tray->icon_path, "%s/%d.png", tray->icon_dir, count++); - - if (would_have_written >= 0) { - return true; - } - - tray->icon_path = NULL; - SDL_SetError("Failed to format new temporary filename"); - return false; -} - -static const char *get_appindicator_id(void) -{ - static int count = 0; - static char buffer[256]; - - int would_have_written = SDL_snprintf(buffer, sizeof(buffer), "sdl-appindicator-%d-%d", getpid(), count++); - - if (would_have_written <= 0 || would_have_written >= sizeof(buffer) - 1) { - SDL_SetError("Couldn't fit %d bytes in buffer of size %d", would_have_written, (int) sizeof(buffer)); - return NULL; - } - - return buffer; -} - -static void DestroySDLMenu(SDL_TrayMenu *menu) -{ - for (int i = 0; i < menu->nEntries; i++) { - if (menu->entries[i] && menu->entries[i]->submenu) { - DestroySDLMenu(menu->entries[i]->submenu); - } - SDL_free(menu->entries[i]); - } - - if (menu->menu) { - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - gtk->g.object_unref(menu->menu); - SDL_Gtk_ExitContext(gtk); - } - } - - SDL_free(menu->entries); - SDL_free(menu); -} - -void SDL_UpdateTrays(void) -{ - if (SDL_HasActiveTrays()) { - SDL_UpdateGtk(); - } -} - -SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) -{ - if (!SDL_IsMainThread()) { - SDL_SetError("This function should be called on the main thread"); - return NULL; - } - - if (!init_appindicator()) { - return NULL; - } - - SDL_Tray *tray = NULL; - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (!gtk) { - goto tray_error; - } - - tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray)); - if (!tray) { - goto tray_error; - } - - const gchar *cache_dir = gtk->g.get_user_cache_dir(); - if (!cache_dir) { - SDL_SetError("Cannot get user cache directory: %s", strerror(errno)); - goto tray_error; - } - - char *sdl_dir; - SDL_asprintf(&sdl_dir, "%s/SDL", cache_dir); - if (!SDL_GetPathInfo(sdl_dir, NULL)) { - if (!SDL_CreateDirectory(sdl_dir)) { - SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno)); - goto sdl_dir_error; - } - } - - /* On success, g_mkdtemp edits its argument in-place to replace the Xs - * with a random directory name, which it creates safely and atomically. - * On failure, it sets errno. */ - SDL_asprintf(&tray->icon_dir, "%s/tray-XXXXXX", sdl_dir); - if (!gtk->g.mkdtemp(tray->icon_dir)) { - SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno)); - goto icon_dir_error; - } - - if (icon) { - if (!new_tmp_filename(tray)) { - goto icon_dir_error; - } - - SDL_SavePNG(icon, tray->icon_path); - } else { - // allocate a dummy icon path - SDL_asprintf(&tray->icon_path, " "); - } - - tray->indicator = app_indicator_new(get_appindicator_id(), tray->icon_path, - APP_INDICATOR_CATEGORY_APPLICATION_STATUS); - - app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE); - - // The tray icon isn't shown before a menu is created; create one early. - tray->menu_cached = (GtkMenuShell *)gtk->g.object_ref_sink(gtk->gtk.menu_new()); - app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu_cached)); - - SDL_RegisterTray(tray); - SDL_Gtk_ExitContext(gtk); - SDL_free(sdl_dir); - - return tray; - -icon_dir_error: - SDL_free(tray->icon_dir); - -sdl_dir_error: - SDL_free(sdl_dir); - -tray_error: - SDL_free(tray); - - if (gtk) { - SDL_Gtk_ExitContext(gtk); - } - - return NULL; -} - -void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) -{ - if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { - return; - } - - if (tray->icon_path) { - SDL_RemovePath(tray->icon_path); - SDL_free(tray->icon_path); - tray->icon_path = NULL; - } - - /* AppIndicator caches the icon files; always change filename to avoid caching */ - - if (icon && new_tmp_filename(tray)) { - SDL_SavePNG(icon, tray->icon_path); - app_indicator_set_icon(tray->indicator, tray->icon_path); - } else { - SDL_free(tray->icon_path); - tray->icon_path = NULL; - app_indicator_set_icon(tray->indicator, NULL); - } -} - -void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip) -{ - /* AppIndicator provides no tooltip support. */ -} - -SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray) -{ - CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { - SDL_InvalidParamError("tray"); - return NULL; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (!gtk) { - return NULL; - } - - tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu)); - if (!tray->menu) { - SDL_Gtk_ExitContext(gtk); - return NULL; - } - - tray->menu->menu = gtk->g.object_ref(tray->menu_cached); - tray->menu->parent_tray = tray; - tray->menu->parent_entry = NULL; - tray->menu->nEntries = 0; - tray->menu->entries = NULL; - - SDL_Gtk_ExitContext(gtk); - - return tray->menu; -} - -SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray) -{ - CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { - SDL_InvalidParamError("tray"); - return NULL; - } - - return tray->menu; -} - -SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) -{ - CHECK_PARAM(!entry) { - SDL_InvalidParamError("entry"); - return NULL; - } - - if (entry->submenu) { - SDL_SetError("Tray entry submenu already exists"); - return NULL; - } - - if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) { - SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU"); - return NULL; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (!gtk) { - return NULL; - } - - entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu)); - if (!entry->submenu) { - SDL_Gtk_ExitContext(gtk); - return NULL; - } - - entry->submenu->menu = gtk->g.object_ref_sink(gtk->gtk.menu_new()); - entry->submenu->parent_tray = NULL; - entry->submenu->parent_entry = entry; - entry->submenu->nEntries = 0; - entry->submenu->entries = NULL; - - gtk->gtk.menu_item_set_submenu(GTK_MENU_ITEM(entry->item), GTK_WIDGET(entry->submenu->menu)); - - SDL_Gtk_ExitContext(gtk); - - return entry->submenu; -} - -SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry) -{ - CHECK_PARAM(!entry) { - SDL_InvalidParamError("entry"); - return NULL; - } - - return entry->submenu; -} - -const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count) -{ - CHECK_PARAM(!menu) { - SDL_InvalidParamError("menu"); - return NULL; - } - - if (count) { - *count = menu->nEntries; - } - return (const SDL_TrayEntry **)menu->entries; -} - -void SDL_RemoveTrayEntry(SDL_TrayEntry *entry) -{ - if (!entry) { - return; - } - - SDL_TrayMenu *menu = entry->parent; - - bool found = false; - for (int i = 0; i < menu->nEntries - 1; i++) { - if (menu->entries[i] == entry) { - found = true; - } - - if (found) { - menu->entries[i] = menu->entries[i + 1]; - } - } - - if (entry->submenu) { - DestroySDLMenu(entry->submenu); - } - - menu->nEntries--; - SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries)); - - /* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */ - if (new_entries) { - menu->entries = new_entries; - menu->entries[menu->nEntries] = NULL; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - gtk->gtk.widget_destroy(entry->item); - SDL_Gtk_ExitContext(gtk); - } - SDL_free(entry); -} - -SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) -{ - CHECK_PARAM(!menu) { - SDL_InvalidParamError("menu"); - return NULL; - } - - CHECK_PARAM(pos < -1 || pos > menu->nEntries) { - SDL_InvalidParamError("pos"); - return NULL; - } - - if (pos == -1) { - pos = menu->nEntries; - } - - SDL_TrayEntry *entry = NULL; - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (!gtk) { - goto error; - } - - entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry)); - if (!entry) { - goto error; - } - - entry->parent = menu; - entry->item = NULL; - entry->ignore_signal = false; - entry->flags = flags; - entry->callback = NULL; - entry->userdata = NULL; - entry->submenu = NULL; - - if (label == NULL) { - entry->item = gtk->gtk.separator_menu_item_new(); - } else if (flags & SDL_TRAYENTRY_CHECKBOX) { - entry->item = gtk->gtk.check_menu_item_new_with_label(label); - gboolean active = ((flags & SDL_TRAYENTRY_CHECKED) != 0); - gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), active); - } else { - entry->item = gtk->gtk.menu_item_new_with_label(label); - } - - gboolean sensitive = ((flags & SDL_TRAYENTRY_DISABLED) == 0); - gtk->gtk.widget_set_sensitive(entry->item, sensitive); - - SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries)); - - if (!new_entries) { - goto error; - } - - menu->entries = new_entries; - menu->nEntries++; - - for (int i = menu->nEntries - 1; i > pos; i--) { - menu->entries[i] = menu->entries[i - 1]; - } - - new_entries[pos] = entry; - new_entries[menu->nEntries] = NULL; - - gtk->gtk.widget_show(entry->item); - gtk->gtk.menu_shell_insert(menu->menu, entry->item, (pos == menu->nEntries) ? -1 : pos); - - gtk->g.signal_connect(entry->item, "activate", call_callback, entry); - - SDL_Gtk_ExitContext(gtk); - - return entry; - -error: - if (entry) { - SDL_free(entry); - } - - if (gtk) { - SDL_Gtk_ExitContext(gtk); - } - - return NULL; -} - -void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) -{ - if (!entry) { - return; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - gtk->gtk.menu_item_set_label(GTK_MENU_ITEM(entry->item), label); - SDL_Gtk_ExitContext(gtk); - } -} - -const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry) -{ - CHECK_PARAM(!entry) { - SDL_InvalidParamError("entry"); - return NULL; - } - - const char *label = NULL; - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - label = gtk->gtk.menu_item_get_label(GTK_MENU_ITEM(entry->item)); - SDL_Gtk_ExitContext(gtk); - } - - return label; -} - -void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked) -{ - if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { - return; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - entry->ignore_signal = true; - gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), checked); - entry->ignore_signal = false; - SDL_Gtk_ExitContext(gtk); - } -} - -bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry) -{ - if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { - return false; - } - - bool checked = false; - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - checked = gtk->gtk.check_menu_item_get_active(GTK_CHECK_MENU_ITEM(entry->item)); - SDL_Gtk_ExitContext(gtk); - } - - return checked; -} - -void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled) -{ - if (!entry) { - return; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - gtk->gtk.widget_set_sensitive(entry->item, enabled); - SDL_Gtk_ExitContext(gtk); - } -} - -bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry) -{ - if (!entry) { - return false; - } - - bool enabled = false; - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - enabled = gtk->gtk.widget_get_sensitive(entry->item); - SDL_Gtk_ExitContext(gtk); - } - - return enabled; -} - -void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) -{ - if (!entry) { - return; - } - - entry->callback = callback; - entry->userdata = userdata; -} - -void SDL_ClickTrayEntry(SDL_TrayEntry *entry) -{ - if (!entry) { - return; - } - - if (entry->flags & SDL_TRAYENTRY_CHECKBOX) { - SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry)); - } - - if (entry->callback) { - entry->callback(entry->userdata, entry); - } -} - -SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) -{ - CHECK_PARAM(!entry) { - SDL_InvalidParamError("entry"); - return NULL; - } - - return entry->parent; -} - -SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu) -{ - return menu->parent_entry; -} - -SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu) -{ - CHECK_PARAM(!menu) { - SDL_InvalidParamError("menu"); - return NULL; - } - - return menu->parent_tray; -} - -void SDL_DestroyTray(SDL_Tray *tray) -{ - if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { - return; - } - - SDL_UnregisterTray(tray); - - if (tray->menu) { - DestroySDLMenu(tray->menu); - } - - if (tray->icon_path) { - SDL_RemovePath(tray->icon_path); - SDL_free(tray->icon_path); - } - - if (tray->icon_dir) { - SDL_RemovePath(tray->icon_dir); - SDL_free(tray->icon_dir); - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - if (tray->menu_cached) { - gtk->g.object_unref(tray->menu_cached); - } - - if (tray->indicator) { - gtk->g.object_unref(tray->indicator); - } - - SDL_Gtk_ExitContext(gtk); - } - - SDL_free(tray); -} diff --git a/src/tray/unix/SDL_unixtray.c b/src/tray/unix/SDL_unixtray.c new file mode 100644 index 0000000000000..5c4b91f47084a --- /dev/null +++ b/src/tray/unix/SDL_unixtray.c @@ -0,0 +1,221 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#include "../../core/linux/SDL_dbus.h" +#include "../SDL_tray_utils.h" +#include "SDL_unixtray.h" + +static SDL_TrayDriver *driver = NULL; + +void SDL_UpdateTrays(void) +{ + SDL_Tray **trays; + int active_trays; + int count; + int i; + + active_trays = SDL_GetActiveTrayCount(); + if (!active_trays) { + return; + } + + trays = SDL_calloc(active_trays, sizeof(SDL_Tray *)); + if (!trays) { + return; + } + + count = SDL_GetObjects(SDL_OBJECT_TYPE_TRAY, (void **)trays, active_trays); + SDL_assert(count == active_trays); + for (i = 0; i < count; i++) { + trays[i]->driver->UpdateTray(trays[i]); + } + + SDL_free(trays); +} + +SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) +{ + SDL_Tray *tray; + + if (!driver) { +#ifdef SDL_USE_LIBDBUS + driver = SDL_Tray_CreateDBusDriver(); +#endif + } + + if (driver) { + tray = driver->CreateTray(driver, icon, tooltip); + if (tray) { + SDL_RegisterTray(tray); + } + } else { + tray = NULL; + } + + return tray; +} + +void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) +{ + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { + SDL_InvalidParamError("tray"); + return NULL; + } + + tray->driver->SetTrayIcon(tray, icon); +} + +void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip) +{ + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { + SDL_InvalidParamError("tray"); + return NULL; + } + + tray->driver->SetTrayTooltip(tray, tooltip); +} + +SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray) +{ + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { + SDL_InvalidParamError("tray"); + return NULL; + } + + return tray->driver->CreateTrayMenu(tray); +} + +SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray) +{ + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { + SDL_InvalidParamError("tray"); + return NULL; + } + + return tray->menu; +} + +SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) +{ + SDL_InvalidParamError("entry"); + return NULL; +} + +SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry) +{ + return NULL; +} + +const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count) +{ + SDL_InvalidParamError("menu"); + return NULL; +} + +void SDL_RemoveTrayEntry(SDL_TrayEntry *entry) +{ +} + +SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) +{ + CHECK_PARAM(!menu) + { + SDL_InvalidParamError("menu"); + return NULL; + } + + return menu->parent_tray->driver->InsertTrayEntryAt(menu, pos, label, flags); +} + +void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) +{ +} + +const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry) +{ + SDL_InvalidParamError("entry"); + return NULL; +} + +void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked) +{ +} + +bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry) +{ + return SDL_InvalidParamError("entry"); +} + +void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled) +{ +} + +bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry) +{ + return SDL_InvalidParamError("entry"); +} + +void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) +{ +} + +void SDL_ClickTrayEntry(SDL_TrayEntry *entry) +{ +} + +SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) +{ + SDL_InvalidParamError("entry"); + return NULL; +} + +SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu) +{ + SDL_InvalidParamError("menu"); + return NULL; +} + +SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu) +{ + SDL_InvalidParamError("menu"); + return NULL; +} + +void SDL_DestroyTray(SDL_Tray *tray) +{ + if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { + return; + } + + SDL_UnregisterTray(tray); + + tray->driver->DestroyTray(tray); + + if (!SDL_GetActiveTrayCount()) { + driver->DestroyDriver(driver); + } +} diff --git a/src/tray/unix/SDL_unixtray.h b/src/tray/unix/SDL_unixtray.h new file mode 100644 index 0000000000000..9465a6e48e30c --- /dev/null +++ b/src/tray/unix/SDL_unixtray.h @@ -0,0 +1,68 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_unixtray_h_ +#define SDL_unixtray_h_ + +#include "../../core/linux/SDL_dbus.h" +#include "../SDL_tray_utils.h" + +typedef struct SDL_TrayDriver +{ + const char *name; + + unsigned int count; + + SDL_Tray *(*CreateTray)(struct SDL_TrayDriver *, SDL_Surface *, const char *); + void (*DestroyTray)(SDL_Tray *); + void (*UpdateTray)(SDL_Tray *); + void (*SetTrayIcon)(SDL_Tray *, SDL_Surface *); + void (*SetTrayTooltip)(SDL_Tray *, const char *); + SDL_TrayMenu *(*CreateTrayMenu)(SDL_Tray *); + SDL_TrayEntry *(*InsertTrayEntryAt)(SDL_TrayMenu *, int, const char *, SDL_TrayEntryFlags); + + void (*DestroyDriver)(struct SDL_TrayDriver *); +} SDL_TrayDriver; + +struct SDL_TrayMenu +{ + SDL_Tray *parent_tray; + SDL_TrayEntry *parent_entry; +}; + +struct SDL_TrayEntry +{ + SDL_TrayMenu *parent; +}; + +struct SDL_Tray +{ + SDL_TrayDriver *driver; + + SDL_TrayMenu *menu; +}; + +#ifdef SDL_USE_LIBDBUS +extern SDL_TrayDriver *SDL_Tray_CreateDBusDriver(void); +#endif + +#endif // SDL_unixtray_h_ diff --git a/src/video/x11/SDL_x11toolkit.h b/src/video/x11/SDL_x11toolkit.h index 0e882b32a928a..f9a9a20cbeb03 100644 --- a/src/video/x11/SDL_x11toolkit.h +++ b/src/video/x11/SDL_x11toolkit.h @@ -27,7 +27,6 @@ #include "SDL_x11video.h" #include "SDL_x11dyn.h" #include "SDL_x11settings.h" -#include "SDL_x11toolkit.h" #include "xsettings-client.h" #ifdef HAVE_FRIBIDI_H #include "../../core/unix/SDL_fribidi.h" diff --git a/test/testtray.c b/test/testtray.c index e4c3fd02bf646..e4ad8672acc5b 100644 --- a/test/testtray.c +++ b/test/testtray.c @@ -553,11 +553,12 @@ int main(int argc, char **argv) SDL_DestroySurface(icon); SDL_DestroySurface(icon2); - + + /* Uncomment the goto below before merging! */ #define CHECK(name) \ if (!name) { \ SDL_Log("Couldn't create " #name ": %s", SDL_GetError()); \ - goto clean_all; \ + /* goto clean_all; */ \ } SDL_TrayMenu *menu = SDL_CreateTrayMenu(tray); From 14ffcd22b290d24279c014f672993f587a0ddc11 Mon Sep 17 00:00:00 2001 From: eafton Date: Wed, 3 Dec 2025 22:05:42 +0300 Subject: [PATCH 02/19] Use libdbus for trays on Linux instead of Ayantana/GTK --- src/core/linux/SDL_dbus.c | 3 ++- src/tray/unix/SDL_dbustray.c | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index c426d3f36ee7a..af9f134b6ed01 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -18,9 +18,10 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ + +#include "SDL_internal.h" #include "SDL_dbus.h" #include "../../stdlib/SDL_vacopy.h" -#include "SDL_internal.h" #ifdef SDL_USE_LIBDBUS // we never link directly to libdbus. diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index 24ecb164700d5..a2bd919e558c7 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -19,15 +19,15 @@ 3. This notice may not be removed or altered from any source distribution. */ -#include "../../core/linux/SDL_dbus.h" #include "SDL_internal.h" +#include "../../core/linux/SDL_dbus.h" #ifdef SDL_USE_LIBDBUS -#include "../../video/SDL_surface_c.h" +#include #include "../SDL_tray_utils.h" #include "SDL_unixtray.h" -#include +#include "../../video/SDL_surface_c.h" #define SNI_INTERFACE "org.kde.StatusNotifierItem" #define SNI_WATCHER_SERVICE "org.kde.StatusNotifierWatcher" From d319ac2c58daa8b97c4592fde104877a02ec3460 Mon Sep 17 00:00:00 2001 From: eafton Date: Wed, 3 Dec 2025 22:22:45 +0300 Subject: [PATCH 03/19] Fix errors --- src/tray/unix/SDL_unixtray.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tray/unix/SDL_unixtray.c b/src/tray/unix/SDL_unixtray.c index 5c4b91f47084a..9150411507eca 100644 --- a/src/tray/unix/SDL_unixtray.c +++ b/src/tray/unix/SDL_unixtray.c @@ -80,7 +80,7 @@ void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { SDL_InvalidParamError("tray"); - return NULL; + return; } tray->driver->SetTrayIcon(tray, icon); @@ -91,7 +91,7 @@ void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip) CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { SDL_InvalidParamError("tray"); - return NULL; + return; } tray->driver->SetTrayTooltip(tray, tooltip); From 01d67f72220e1df5c744ba6eadb0031bed0d2bb4 Mon Sep 17 00:00:00 2001 From: eafton Date: Wed, 3 Dec 2025 23:47:38 +0300 Subject: [PATCH 04/19] Patch for older systems --- src/core/linux/SDL_dbus.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 738fc064a5ccd..27dcd4186eb27 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -38,6 +38,9 @@ #ifndef DBUS_TYPE_UNIX_FD #define DBUS_TYPE_UNIX_FD ((int)'h') #endif +#ifndef DBUS_ERROR_UNKNOWN_PROPERTY +#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" +#endif typedef struct SDL_DBusContext { From daa2fbb156614b79efdc59b9e851b2e0fb5dd8bd Mon Sep 17 00:00:00 2001 From: eafton Date: Thu, 4 Dec 2025 12:18:11 +0300 Subject: [PATCH 05/19] Make the menu show up properly --- src/core/linux/SDL_dbus.c | 124 +++++++++++++++++++++++++++++++++-- src/core/linux/SDL_dbus.h | 1 + src/tray/unix/SDL_dbustray.c | 28 ++++---- 3 files changed, 134 insertions(+), 19 deletions(-) diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index af9f134b6ed01..0acfaa8625902 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -19,9 +19,9 @@ 3. This notice may not be removed or altered from any source distribution. */ -#include "SDL_internal.h" #include "SDL_dbus.h" #include "../../stdlib/SDL_vacopy.h" +#include "SDL_internal.h" #ifdef SDL_USE_LIBDBUS // we never link directly to libdbus. @@ -1039,11 +1039,34 @@ static DBusHandlerResult HandleGetLayout(SDL_DBusContext *ctx, SDL_ListNode *men cursor = menu; while (cursor) { SDL_DBusMenuItem *item; - DBusMessageIter cvariant_iter; + DBusMessageIter cvariant_iter, item_struct, item_dict, item_children; item = cursor->entry; ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &cvariant_iter); - AppendMenuItem(ctx, item, &variant_iter, recursion_depth); + ctx->message_iter_open_container(&cvariant_iter, DBUS_TYPE_STRUCT, NULL, &item_struct); + ctx->message_iter_append_basic(&item_struct, DBUS_TYPE_INT32, &item->id); + ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "{sv}", &item_dict); + AppendMenuItemProperties(ctx, item, &item_dict); + ctx->message_iter_close_container(&item_struct, &item_dict); + ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "v", &item_children); + if (item->sub_menu && recursion_depth) { + SDL_ListNode *child_cursor; + + child_cursor = item->sub_menu; + while (child_cursor) { + SDL_DBusMenuItem *child; + DBusMessageIter child_variant; + + child = child_cursor->entry; + ctx->message_iter_open_container(&item_children, DBUS_TYPE_VARIANT, "(ia{sv}av)", &child_variant); + AppendMenuItem(ctx, child, &child_variant, recursion_depth - 1); + ctx->message_iter_close_container(&item_children, &child_variant); + child_cursor = child_cursor->next; + } + } + ctx->message_iter_close_container(&item_struct, &item_children); + ctx->message_iter_close_container(&cvariant_iter, &item_struct); + ctx->message_iter_close_container(&children_iter, &cvariant_iter); cursor = cursor->next; } @@ -1057,11 +1080,34 @@ static DBusHandlerResult HandleGetLayout(SDL_DBusContext *ctx, SDL_ListNode *men cursor = parent->sub_menu; while (cursor) { SDL_DBusMenuItem *item; - DBusMessageIter cvariant_iter; + DBusMessageIter cvariant_iter, item_struct, item_dict, item_children; item = cursor->entry; ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &cvariant_iter); - AppendMenuItem(ctx, item, &cvariant_iter, recursion_depth); + ctx->message_iter_open_container(&cvariant_iter, DBUS_TYPE_STRUCT, NULL, &item_struct); + ctx->message_iter_append_basic(&item_struct, DBUS_TYPE_INT32, &item->id); + ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "{sv}", &item_dict); + AppendMenuItemProperties(ctx, item, &item_dict); + ctx->message_iter_close_container(&item_struct, &item_dict); + ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "v", &item_children); + if (item->sub_menu && recursion_depth) { + SDL_ListNode *child_cursor; + + child_cursor = item->sub_menu; + while (child_cursor) { + SDL_DBusMenuItem *child; + DBusMessageIter child_variant; + + child = child_cursor->entry; + ctx->message_iter_open_container(&item_children, DBUS_TYPE_VARIANT, "(ia{sv}av)", &child_variant); + AppendMenuItem(ctx, child, &child_variant, recursion_depth - 1); + ctx->message_iter_close_container(&item_children, &child_variant); + child_cursor = child_cursor->next; + } + } + ctx->message_iter_close_container(&item_struct, &item_children); + ctx->message_iter_close_container(&cvariant_iter, &item_struct); + ctx->message_iter_close_container(&children_iter, &cvariant_iter); cursor = cursor->next; @@ -1190,7 +1236,7 @@ static DBusHandlerResult HandleGetProperty(SDL_DBusContext *ctx, SDL_ListNode *m static DBusHandlerResult HandleGetGroupProperties(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) { -#define FILTER_PROPS_SZ 32 + #define FILTER_PROPS_SZ 32 DBusMessage *reply; DBusMessageIter args, array_iter, prop_iter; DBusMessageIter iter, reply_array_iter; @@ -1323,6 +1369,62 @@ static DBusHandlerResult HandleGetGroupProperties(SDL_DBusContext *ctx, SDL_List return DBUS_HANDLER_RESULT_HANDLED; } +static long GetMaxMenuItemId(SDL_ListNode *menu) +{ + SDL_ListNode *cursor; + long max_id; + + max_id = 0; + cursor = menu; + while (cursor) { + SDL_DBusMenuItem *item; + + item = cursor->entry; + if (item) { + if (item->id > max_id) { + max_id = item->id; + } + + if (item->sub_menu) { + long sub_max; + + sub_max = GetMaxMenuItemId(item->sub_menu); + if (sub_max > max_id) { + max_id = sub_max; + } + } + } + cursor = cursor->next; + } + return max_id; +} + +static void AssignMenuItemIds(SDL_ListNode *menu, long *next_id) +{ + SDL_ListNode *cursor; + + cursor = menu; + while (cursor) { + SDL_DBusMenuItem *item; + + item = cursor->entry; + if (item) { + if (!item->id) { + item->id = (*next_id)++; + } + if (item->sub_menu) { + AssignMenuItemIds(item->sub_menu, next_id); + } + } + cursor = cursor->next; + } +} + +void SDL_DBus_InitMenuItemInternals(SDL_DBusMenuItem *item) +{ + item->id = 0; +} + static DBusHandlerResult DBusMenuMessageFilter(DBusConnection *conn, DBusMessage *msg, void *user_data) { SDL_ListNode *menu; @@ -1342,7 +1444,7 @@ static DBusHandlerResult DBusMenuMessageFilter(DBusConnection *conn, DBusMessage DBusMessage *reply; dbus_bool_t need_update; - need_update = 0; + need_update = FALSE; reply = ctx->message_new_method_return(msg); ctx->message_append_args(reply, DBUS_TYPE_BOOLEAN, &need_update, DBUS_TYPE_INVALID); ctx->connection_send(conn, reply, NULL); @@ -1360,11 +1462,15 @@ static DBusHandlerResult DBusMenuMessageFilter(DBusConnection *conn, DBusMessage const char *SDL_DBus_ExportMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu) { DBusObjectPathVTable vtable; + long next_id; if (!ctx || !menu) { return NULL; } + next_id = 1; + AssignMenuItemIds(menu, &next_id); + if (menu->entry) { SDL_DBusMenuItem *item; @@ -1386,11 +1492,15 @@ void SDL_DBus_UpdateMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNod { DBusMessage *signal; unsigned long revision; + long next_id; if (!ctx || !menu) { return; } + next_id = GetMaxMenuItemId(menu) + 1; + AssignMenuItemIds(menu, &next_id); + revision = 0; if (menu->entry) { SDL_DBusMenuItem *item; diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 27dcd4186eb27..54fc198294aa9 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -161,6 +161,7 @@ extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_ extern int SDL_DBus_CameraPortalRequestAccess(void); /* Menu functions */ +extern void SDL_DBus_InitMenuItemInternals(SDL_DBusMenuItem *item); extern const char *SDL_DBus_ExportMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu); extern void SDL_DBus_UpdateMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu); diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index a2bd919e558c7..67b9e54857118 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -129,16 +129,19 @@ static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_db driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); - key = "ItemIsMenu"; - bool_value = TRUE; - driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); - driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); - driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); - driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_value); - driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); - driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + if (menu_dbus) { + key = "ItemIsMenu"; + bool_value = TRUE; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_value); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + } + if (menu_dbus) { if (menu_dbus->menu_path) { key = "Menu"; @@ -236,7 +239,7 @@ static DBusHandlerResult HandleGetProp(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &empty); driver->dbus->message_iter_close_container(&iter, &variant_iter); - } else if (!SDL_strcmp(property, "ItemIsMenu")) { + } else if (!SDL_strcmp(property, "ItemIsMenu") && menu_dbus) { bool_value = TRUE; driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "b", &variant_iter); driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_value); @@ -297,8 +300,8 @@ static DBusHandlerResult MessageHandler(DBusConnection *connection, DBusMessage return HandleGetProp(tray, (SDL_TrayDBus *)tray, driver, msg); } else if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "GetAll")) { return HandleGetAllProps(tray, (SDL_TrayDBus *)tray, driver, msg); - } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "ContextMenu")) { - puts("hello"); + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "Activate")) { + /* Activate */ } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -513,7 +516,8 @@ SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, entry_dbus->item.cb_data = NULL; entry_dbus->item.cb = NULL; entry_dbus->item.sub_menu = NULL; - + SDL_DBus_InitMenuItemInternals(&entry_dbus->item); + if (menu_dbus->menu) { update = true; } else { From 0dcee9033c3bf37cbb88999365cae83b33048e8c Mon Sep 17 00:00:00 2001 From: eafton Date: Thu, 4 Dec 2025 13:03:47 +0300 Subject: [PATCH 06/19] Fix header order --- src/core/linux/SDL_dbus.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 0acfaa8625902..30f2f431ea7f8 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -19,9 +19,9 @@ 3. This notice may not be removed or altered from any source distribution. */ -#include "SDL_dbus.h" -#include "../../stdlib/SDL_vacopy.h" #include "SDL_internal.h" +#include "../../stdlib/SDL_vacopy.h" +#include "SDL_dbus.h" #ifdef SDL_USE_LIBDBUS // we never link directly to libdbus. From 7fe09cc7c6d29568140bbeb24b761827bc8de811 Mon Sep 17 00:00:00 2001 From: eafton Date: Thu, 4 Dec 2025 19:13:41 +0300 Subject: [PATCH 07/19] Implement more tray funcs, make callbacks work --- src/SDL_list.c | 14 +++ src/SDL_list.h | 1 + src/core/linux/SDL_dbus.c | 6 +- src/core/linux/SDL_dbus.h | 2 + src/tray/unix/SDL_dbustray.c | 181 ++++++++++++++++++++++++++++++++++- src/tray/unix/SDL_unixtray.c | 56 +++++++++-- src/tray/unix/SDL_unixtray.h | 5 + 7 files changed, 252 insertions(+), 13 deletions(-) diff --git a/src/SDL_list.c b/src/SDL_list.c index d02cd3ae578ba..9831cb6303f2c 100644 --- a/src/SDL_list.c +++ b/src/SDL_list.c @@ -114,3 +114,17 @@ void SDL_ListClear(SDL_ListNode **head) SDL_free(tmp); } } + +int SDL_ListCountEntries(SDL_ListNode **head) { + SDL_ListNode *l; + int i; + + i = 0; + l = *head; + while (l) { + i++; + l = l->next; + } + + return 0; +} diff --git a/src/SDL_list.h b/src/SDL_list.h index 5ce6bf9e8f42d..10f48e84da244 100644 --- a/src/SDL_list.h +++ b/src/SDL_list.h @@ -33,5 +33,6 @@ bool SDL_ListAppend(SDL_ListNode **head, void *ent); void SDL_ListPop(SDL_ListNode **head, void **ent); void SDL_ListRemove(SDL_ListNode **head, void *ent); void SDL_ListClear(SDL_ListNode **head); +int SDL_ListCountEntries(SDL_ListNode **head); #endif // SDL_list_h_ diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 0acfaa8625902..6ef4e0a341912 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -849,6 +849,7 @@ static SDL_DBusMenuItem *GetMenuItemById(SDL_ListNode *menu, long id) SDL_DBusMenuItem *item; item = cursor->entry; + if (item->id == id) { return item; } @@ -861,6 +862,7 @@ static SDL_DBusMenuItem *GetMenuItemById(SDL_ListNode *menu, long id) return found; } } + cursor = cursor->next; } return NULL; @@ -1128,13 +1130,13 @@ static DBusHandlerResult HandleEvent(SDL_DBusContext *ctx, SDL_ListNode *menu, D DBusMessage *reply; const char *event_id; DBusMessageIter args; - long id; + Uint32 id; ctx->message_iter_init(msg, &args); ctx->message_iter_get_basic(&args, &id); ctx->message_iter_next(&args); ctx->message_iter_get_basic(&args, &event_id); - + if (!SDL_strcmp(event_id, "clicked")) { SDL_DBusMenuItem *item; diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 54fc198294aa9..5aa9478a4036e 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -129,6 +129,8 @@ typedef struct SDL_DBusMenuItem void *cb_data; void (*cb)(struct SDL_DBusMenuItem *, void *); SDL_ListNode *sub_menu; /* NULL if no submenu */ + void *udata; + void *udata2; /* Internal use */ SDL_DBusContext *dbus; diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index 67b9e54857118..da5f75f6219f3 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -35,6 +35,14 @@ #define SNI_WATCHER_INTERFACE "org.kde.StatusNotifierWatcher" #define SNI_OBJECT_PATH "/StatusNotifierItem" +/* TODO: Destroy all menus and entires on destroy tray */ + +typedef struct ItemToFree +{ + void *item; + void (*func)(void *); +} ItemToFree; + typedef struct SDL_TrayDriverDBus { SDL_TrayDriver _parent; @@ -51,6 +59,8 @@ typedef struct SDL_TrayDBus char *tooltip; SDL_Surface *surface; + + SDL_ListNode *free_list; } SDL_TrayDBus; typedef struct SDL_TrayMenuDBus @@ -66,6 +76,7 @@ typedef struct SDL_TrayEntryDBus SDL_TrayEntry _parent; SDL_DBusMenuItem item; + SDL_TrayMenuDBus *sub_menu; } SDL_TrayEntryDBus; static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, SDL_TrayDriverDBus *driver, DBusMessage *msg) @@ -337,6 +348,7 @@ SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tool tray->driver = driver; tray_dbus->tooltip = SDL_strdup(tooltip); tray_dbus->surface = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_ARGB32); + tray_dbus->free_list = NULL; /* connect */ dbus_driver = (SDL_TrayDriverDBus *)driver; @@ -398,14 +410,27 @@ void DestroyTray(SDL_Tray *tray) { SDL_TrayDBus *tray_dbus; SDL_TrayDriverDBus *driver; - + SDL_ListNode *cursor; + driver = (SDL_TrayDriverDBus *)tray->driver; tray_dbus = (SDL_TrayDBus *)tray; driver->dbus->connection_close(tray_dbus->connection); + tray_dbus->connection = NULL; SDL_free(tray_dbus->tooltip); SDL_DestroySurface(tray_dbus->surface); SDL_free(tray); + cursor = tray_dbus->free_list; + while (cursor) { + ItemToFree *free_item; + + free_item = cursor->entry; + free_item->func(free_item->item); + SDL_free(free_item); + + cursor = cursor->next; + } + SDL_ListClear(&tray_dbus->free_list); } void UpdateTray(SDL_Tray *tray) @@ -481,6 +506,38 @@ SDL_TrayMenu *CreateTrayMenu(SDL_Tray *tray) return tray->menu; } +SDL_TrayMenu *CreateTraySubmenu(SDL_TrayEntry *entry) +{ + SDL_TrayMenuDBus *menu_dbus; + SDL_TrayMenu *menu; + SDL_TrayEntryDBus *entry_dbus; + + entry_dbus = (SDL_TrayEntryDBus *)entry; + menu_dbus = SDL_malloc(sizeof(SDL_TrayMenuDBus)); + menu = (SDL_TrayMenu *)menu_dbus; + if (!menu_dbus) { + SDL_SetError("Unable to create tray submenu: allocation failure!"); + return NULL; + } + + menu_dbus->menu = NULL; + menu_dbus->menu_path = NULL; + menu->parent_tray = entry->parent->parent_tray; + menu->parent_entry = entry; + entry_dbus->sub_menu = menu_dbus; + + return menu; +} + +SDL_TrayMenu *GetTraySubmenu(SDL_TrayEntry *entry) +{ + SDL_TrayEntryDBus *entry_dbus; + + entry_dbus = (SDL_TrayEntryDBus *)entry; + + return (SDL_TrayMenu *)entry_dbus->sub_menu; +} + SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) { SDL_Tray *tray; @@ -503,7 +560,8 @@ SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_SetError("Unable to create tray entry: allocation failure!"); return NULL; } - + + entry->parent = menu; entry_dbus->item.utf8 = label; if (!label) { entry_dbus->item.type = SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR; @@ -516,14 +574,22 @@ SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, entry_dbus->item.cb_data = NULL; entry_dbus->item.cb = NULL; entry_dbus->item.sub_menu = NULL; - SDL_DBus_InitMenuItemInternals(&entry_dbus->item); - + entry_dbus->item.udata = entry_dbus; + entry_dbus->sub_menu = NULL; + SDL_DBus_InitMenuItemInternals(&entry_dbus->item); + if (menu_dbus->menu) { update = true; } else { update = false; } + + if (menu->parent_entry) { + update = true; + } + SDL_ListAppend(&menu_dbus->menu, &entry_dbus->item); + if (update) { SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); } else { @@ -533,6 +599,106 @@ SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, return entry; } +SDL_TrayEntry **GetTrayEntries(SDL_TrayMenu *menu, int *count) { + SDL_TrayEntry **ret; + SDL_Tray *tray; + SDL_TrayDBus *tray_dbus; + SDL_TrayMenuDBus *menu_dbus; + SDL_ListNode *cursor; + ItemToFree *free_item; + int sz; + int i; + + tray = menu->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + menu_dbus = (SDL_TrayMenuDBus *)menu; + + cursor = tray_dbus->free_list; + while (cursor) { + free_item = cursor->entry; + free_item->func(free_item->item); + SDL_free(free_item); + + cursor = cursor->next; + } + SDL_ListClear(&tray_dbus->free_list); + + sz = SDL_ListCountEntries(&menu_dbus->menu); + ret = SDL_calloc(sz + 1, sizeof(SDL_TrayEntry *)); + if (!ret) { + SDL_SetError("Memory allocation failure!"); + return NULL; + } + free_item = SDL_malloc(sizeof(ItemToFree)); + free_item->item = ret; + free_item->func = SDL_free; + SDL_ListAdd(&tray_dbus->free_list, free_item); + + i = 0; + cursor = menu_dbus->menu; + while (cursor) { + SDL_DBusMenuItem *item; + + item = cursor->entry; + ret[i] = item->udata; + cursor = cursor->next; + i++; + } + ret[sz] = NULL; + + *count = sz; + return ret; +} + +void RemoveTrayEntry(SDL_TrayEntry *entry) +{ + SDL_TrayEntryDBus *entry_dbus; + SDL_TrayMenuDBus *menu_dbus; + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + + tray = entry->parent->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + entry_dbus = (SDL_TrayEntryDBus *)entry; + menu_dbus = (SDL_TrayMenuDBus *)entry->parent; + + SDL_ListRemove(&menu_dbus->menu, &entry_dbus->item); + /* TODO: destroy submenu */ + SDL_free(entry); + + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); +} + +void EntryCallback(SDL_DBusMenuItem *item, void *udata) { + SDL_TrayCallback entry_cb; + + entry_cb = item->udata2; + entry_cb(udata, item->udata); +} + +void SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) +{ + SDL_TrayEntryDBus *entry_dbus; + SDL_TrayMenuDBus *menu_dbus; + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + + tray = entry->parent->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + entry_dbus = (SDL_TrayEntryDBus *)entry; + menu_dbus = (SDL_TrayMenuDBus *)entry->parent; + + entry_dbus->item.cb = EntryCallback; + entry_dbus->item.cb_data = userdata; + entry_dbus->item.udata2 = callback; + + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); +} + void DestroyDriver(SDL_TrayDriver *driver) { SDL_DBus_Quit(); @@ -607,8 +773,13 @@ SDL_TrayDriver *SDL_Tray_CreateDBusDriver(void) driver->SetTrayTooltip = SetTrayTooltip; driver->CreateTrayMenu = CreateTrayMenu; driver->InsertTrayEntryAt = InsertTrayEntryAt; + driver->CreateTraySubmenu = CreateTraySubmenu; + driver->GetTraySubmenu = GetTraySubmenu; + driver->GetTrayEntries = GetTrayEntries; + driver->RemoveTrayEntry = RemoveTrayEntry; + driver->SetTrayEntryCallback = SetTrayEntryCallback; driver->DestroyDriver = DestroyDriver; - + return driver; } diff --git a/src/tray/unix/SDL_unixtray.c b/src/tray/unix/SDL_unixtray.c index 9150411507eca..9c306cc63e734 100644 --- a/src/tray/unix/SDL_unixtray.c +++ b/src/tray/unix/SDL_unixtray.c @@ -47,7 +47,9 @@ void SDL_UpdateTrays(void) count = SDL_GetObjects(SDL_OBJECT_TYPE_TRAY, (void **)trays, active_trays); SDL_assert(count == active_trays); for (i = 0; i < count; i++) { - trays[i]->driver->UpdateTray(trays[i]); + if (trays[i]) { + trays[i]->driver->UpdateTray(trays[i]); + } } SDL_free(trays); @@ -121,23 +123,52 @@ SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray) SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) { - SDL_InvalidParamError("entry"); - return NULL; + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return NULL; + } + + return entry->parent->parent_tray->driver->CreateTraySubmenu(entry); } SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry) { - return NULL; + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return NULL; + } + + return entry->parent->parent_tray->driver->GetTraySubmenu(entry); } const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count) { - SDL_InvalidParamError("menu"); - return NULL; + CHECK_PARAM(!menu) + { + SDL_InvalidParamError("menu"); + return NULL; + } + + CHECK_PARAM(!count) + { + SDL_InvalidParamError("count"); + return NULL; + } + + return (const SDL_TrayEntry **)menu->parent_tray->driver->GetTrayEntries(menu, count); } void SDL_RemoveTrayEntry(SDL_TrayEntry *entry) { + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return; + } + + entry->parent->parent_tray->driver->RemoveTrayEntry(entry); } SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) @@ -181,6 +212,19 @@ bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry) void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) { + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("menu"); + return; + } + + CHECK_PARAM(!callback) + { + SDL_InvalidParamError("callback"); + return; + } + + entry->parent->parent_tray->driver->SetTrayEntryCallback(entry, callback, userdata); } void SDL_ClickTrayEntry(SDL_TrayEntry *entry) diff --git a/src/tray/unix/SDL_unixtray.h b/src/tray/unix/SDL_unixtray.h index 9465a6e48e30c..e59873673442b 100644 --- a/src/tray/unix/SDL_unixtray.h +++ b/src/tray/unix/SDL_unixtray.h @@ -39,6 +39,11 @@ typedef struct SDL_TrayDriver void (*SetTrayTooltip)(SDL_Tray *, const char *); SDL_TrayMenu *(*CreateTrayMenu)(SDL_Tray *); SDL_TrayEntry *(*InsertTrayEntryAt)(SDL_TrayMenu *, int, const char *, SDL_TrayEntryFlags); + SDL_TrayMenu *(*CreateTraySubmenu)(SDL_TrayEntry *); + SDL_TrayMenu *(*GetTraySubmenu)(SDL_TrayEntry *); + SDL_TrayEntry **(*GetTrayEntries)(SDL_TrayMenu *, int *); + void (*RemoveTrayEntry)(SDL_TrayEntry *); + void (*SetTrayEntryCallback)(SDL_TrayEntry *, SDL_TrayCallback, void *); void (*DestroyDriver)(struct SDL_TrayDriver *); } SDL_TrayDriver; From c4599c3abc985f20e6de649ca5e996c0b167d257 Mon Sep 17 00:00:00 2001 From: eafton Date: Thu, 4 Dec 2025 19:18:14 +0300 Subject: [PATCH 08/19] Fix error in list --- src/SDL_list.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SDL_list.c b/src/SDL_list.c index 9831cb6303f2c..2c3206de63579 100644 --- a/src/SDL_list.c +++ b/src/SDL_list.c @@ -126,5 +126,5 @@ int SDL_ListCountEntries(SDL_ListNode **head) { l = l->next; } - return 0; + return i; } From a3f86d143efcccba7b8f5e777fab8ab86565f73d Mon Sep 17 00:00:00 2001 From: eafton Date: Thu, 4 Dec 2025 19:21:14 +0300 Subject: [PATCH 09/19] Implement more tray funcs --- src/tray/unix/SDL_unixtray.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/tray/unix/SDL_unixtray.c b/src/tray/unix/SDL_unixtray.c index 9c306cc63e734..75c8b1923ed67 100644 --- a/src/tray/unix/SDL_unixtray.c +++ b/src/tray/unix/SDL_unixtray.c @@ -214,7 +214,7 @@ void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, v { CHECK_PARAM(!entry) { - SDL_InvalidParamError("menu"); + SDL_InvalidParamError("entry"); return; } @@ -233,8 +233,13 @@ void SDL_ClickTrayEntry(SDL_TrayEntry *entry) SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) { - SDL_InvalidParamError("entry"); - return NULL; + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return NULL; + } + + return entry->parent; } SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu) From 406385f0d02fa26603a5ef85166fbcc52911c39e Mon Sep 17 00:00:00 2001 From: eafton Date: Sat, 6 Dec 2025 21:47:26 +0300 Subject: [PATCH 10/19] Attempt at getting this working on KDE --- src/tray/unix/SDL_dbustray.c | 58 ++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index da5f75f6219f3..ccc4be4c868a3 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -89,6 +89,7 @@ static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_db const char *key; const char *value; const char *empty; + dbus_uint32_t uint32_val; dbus_bool_t bool_value; empty = ""; @@ -140,6 +141,14 @@ static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_db driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + key = "WindowId"; + uint32_val = 0; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "u", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_UINT32, &uint32_val); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); menu_dbus = (SDL_TrayMenuDBus *)tray->menu; if (menu_dbus) { @@ -289,6 +298,13 @@ static DBusHandlerResult HandleGetProp(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, driver->dbus->message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &tray_dbus->tooltip); driver->dbus->message_iter_close_container(&variant_iter, &struct_iter); driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "WindowId")) { + dbus_uint32_t uint32_val; + + uint32_val = 0; + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "u", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_UINT32, &uint32_val); + driver->dbus->message_iter_close_container(&iter, &variant_iter); } else { driver->dbus->message_unref(reply); reply = driver->dbus->message_new_error(msg, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); @@ -303,7 +319,8 @@ static DBusHandlerResult MessageHandler(DBusConnection *connection, DBusMessage { SDL_Tray *tray; SDL_TrayDriverDBus *driver; - + DBusMessage *reply; + tray = user_data; driver = (SDL_TrayDriverDBus *)tray->driver; @@ -311,8 +328,26 @@ static DBusHandlerResult MessageHandler(DBusConnection *connection, DBusMessage return HandleGetProp(tray, (SDL_TrayDBus *)tray, driver, msg); } else if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "GetAll")) { return HandleGetAllProps(tray, (SDL_TrayDBus *)tray, driver, msg); + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "ContextMenu")) { + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "Activate")) { - /* Activate */ + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "SecondaryActivate")) { + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "Scroll")) { + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -346,7 +381,11 @@ SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tool /* fill */ tray->menu = NULL; tray->driver = driver; - tray_dbus->tooltip = SDL_strdup(tooltip); + if (tooltip) { + tray_dbus->tooltip = SDL_strdup(tooltip); + } else { + tray_dbus->tooltip = NULL; + } tray_dbus->surface = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_ARGB32); tray_dbus->free_list = NULL; @@ -397,6 +436,7 @@ SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tool CLEANUP2(); return NULL; } + object_path = tray_dbus->object_name; if (!SDL_DBus_CallVoidMethodOnConnection(tray_dbus->connection, SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "RegisterStatusNotifierItem", DBUS_TYPE_STRING, &object_path, DBUS_TYPE_INVALID)) { SDL_SetError("Unable to create tray: unable to register status notifier item!"); CLEANUP2(); @@ -479,7 +519,12 @@ void SetTrayTooltip(SDL_Tray *tray, const char *text) if (tray_dbus->tooltip) { SDL_free(tray_dbus->tooltip); } - tray_dbus->tooltip = SDL_strdup(text); + + if (text) { + tray_dbus->tooltip = SDL_strdup(text); + } else { + tray_dbus->tooltip = NULL; + } signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewToolTip"); driver->dbus->connection_send(tray_dbus->connection, signal, NULL); @@ -591,7 +636,10 @@ SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_ListAppend(&menu_dbus->menu, &entry_dbus->item); if (update) { - SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); + SDL_TrayMenuDBus *main_menu_dbus; + + main_menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, main_menu_dbus->menu); } else { menu_dbus->menu_path = SDL_DBus_ExportMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); } From 7e6422e3958a140292c7c97da579f2ffd2891773 Mon Sep 17 00:00:00 2001 From: eafton Date: Sun, 7 Dec 2025 00:31:48 +0300 Subject: [PATCH 11/19] Fix a nasty segfault --- src/core/linux/SDL_dbus.c | 17 +++++++------- src/events/SDL_events.c | 2 +- src/tray/SDL_tray_utils.c | 2 +- src/tray/unix/SDL_dbustray.c | 45 ++++++++++++++++++++++++++++-------- src/tray/unix/SDL_unixtray.c | 10 ++++---- 5 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 205b2de960e89..860d46b268001 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -1129,6 +1129,7 @@ static DBusHandlerResult HandleEvent(SDL_DBusContext *ctx, SDL_ListNode *menu, D { DBusMessage *reply; const char *event_id; + SDL_DBusMenuItem *item; DBusMessageIter args; Uint32 id; @@ -1137,21 +1138,21 @@ static DBusHandlerResult HandleEvent(SDL_DBusContext *ctx, SDL_ListNode *menu, D ctx->message_iter_next(&args); ctx->message_iter_get_basic(&args, &event_id); + item = NULL; if (!SDL_strcmp(event_id, "clicked")) { - SDL_DBusMenuItem *item; - item = GetMenuItemById(menu, id); - if (item) { - if (item->cb) { - item->cb(item, item->cb_data); - } - } } reply = ctx->message_new_method_return(msg); ctx->connection_send(conn, reply, NULL); ctx->message_unref(reply); - + + if (item) { + if (item->cb) { + item->cb(item, item->cb_data); + } + } + return DBUS_HANDLER_RESULT_HANDLED; } diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 3ecaed71b5779..d366dee57f95c 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -1486,7 +1486,7 @@ void SDL_PumpEventMaintenance(void) SDL_UpdateCursorAnimation(); SDL_UpdateTrays(); - + SDL_SendPendingSignalEvents(); // in case we had a signal handler fire, etc. } diff --git a/src/tray/SDL_tray_utils.c b/src/tray/SDL_tray_utils.c index 89d500859b01a..65c6f635cf6d6 100644 --- a/src/tray/SDL_tray_utils.c +++ b/src/tray/SDL_tray_utils.c @@ -37,7 +37,7 @@ void SDL_RegisterTray(SDL_Tray *tray) void SDL_UnregisterTray(SDL_Tray *tray) { SDL_assert(SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)); - + SDL_SetObjectValid(tray, SDL_OBJECT_TYPE_TRAY, false); --active_trays; diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index ccc4be4c868a3..5946837f14f63 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -61,6 +61,8 @@ typedef struct SDL_TrayDBus SDL_Surface *surface; SDL_ListNode *free_list; + + bool break_update; } SDL_TrayDBus; typedef struct SDL_TrayMenuDBus @@ -150,17 +152,19 @@ static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_db driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + key = "ItemIsMenu"; menu_dbus = (SDL_TrayMenuDBus *)tray->menu; if (menu_dbus) { - key = "ItemIsMenu"; bool_value = TRUE; - driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); - driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); - driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); - driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_value); - driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); - driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + } else { + bool_value = FALSE; } + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_value); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); if (menu_dbus) { if (menu_dbus->menu_path) { @@ -259,8 +263,12 @@ static DBusHandlerResult HandleGetProp(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &empty); driver->dbus->message_iter_close_container(&iter, &variant_iter); - } else if (!SDL_strcmp(property, "ItemIsMenu") && menu_dbus) { - bool_value = TRUE; + } else if (!SDL_strcmp(property, "ItemIsMenu")) { + if (menu_dbus) { + bool_value = TRUE; + } else { + bool_value = FALSE; + } driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "b", &variant_iter); driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_value); driver->dbus->message_iter_close_container(&iter, &variant_iter); @@ -388,6 +396,7 @@ SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tool } tray_dbus->surface = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_ARGB32); tray_dbus->free_list = NULL; + tray_dbus->break_update = false; /* connect */ dbus_driver = (SDL_TrayDriverDBus *)driver; @@ -455,11 +464,17 @@ void DestroyTray(SDL_Tray *tray) driver = (SDL_TrayDriverDBus *)tray->driver; tray_dbus = (SDL_TrayDBus *)tray; + /* destroy connection */ + driver->dbus->connection_flush(tray_dbus->connection); + tray_dbus->break_update = true; driver->dbus->connection_close(tray_dbus->connection); tray_dbus->connection = NULL; + + /* destroy icon and tooltip */ SDL_free(tray_dbus->tooltip); SDL_DestroySurface(tray_dbus->surface); - SDL_free(tray); + + /* free items to be freed */ cursor = tray_dbus->free_list; while (cursor) { ItemToFree *free_item; @@ -471,6 +486,9 @@ void DestroyTray(SDL_Tray *tray) cursor = cursor->next; } SDL_ListClear(&tray_dbus->free_list); + + /* free the tray */ + SDL_free(tray); } void UpdateTray(SDL_Tray *tray) @@ -481,8 +499,15 @@ void UpdateTray(SDL_Tray *tray) driver = (SDL_TrayDriverDBus *)tray->driver; tray_dbus = (SDL_TrayDBus *)tray; + if (tray_dbus->break_update) { + return; + } + driver->dbus->connection_read_write(tray_dbus->connection, 0); while (driver->dbus->connection_dispatch(tray_dbus->connection) == DBUS_DISPATCH_DATA_REMAINS) { + if (tray_dbus->break_update) { + break; + } SDL_DelayNS(SDL_US_TO_NS(10)); } } diff --git a/src/tray/unix/SDL_unixtray.c b/src/tray/unix/SDL_unixtray.c index 75c8b1923ed67..c4aa281b77248 100644 --- a/src/tray/unix/SDL_unixtray.c +++ b/src/tray/unix/SDL_unixtray.c @@ -48,7 +48,9 @@ void SDL_UpdateTrays(void) SDL_assert(count == active_trays); for (i = 0; i < count; i++) { if (trays[i]) { - trays[i]->driver->UpdateTray(trays[i]); + if (SDL_ObjectValid(trays[i], SDL_OBJECT_TYPE_TRAY)) { + trays[i]->driver->UpdateTray(trays[i]); + } } } @@ -259,11 +261,9 @@ void SDL_DestroyTray(SDL_Tray *tray) if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { return; } - - SDL_UnregisterTray(tray); - + tray->driver->DestroyTray(tray); - + SDL_UnregisterTray(tray); if (!SDL_GetActiveTrayCount()) { driver->DestroyDriver(driver); } From 8821dd7249176323428e760b3e0500083cd5fd3b Mon Sep 17 00:00:00 2001 From: eafton Date: Sun, 7 Dec 2025 22:28:15 +0300 Subject: [PATCH 12/19] Attempt at getting this working on KDE --- src/core/linux/SDL_dbus.c | 63 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 860d46b268001..12c173bb75650 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -1447,7 +1447,7 @@ static DBusHandlerResult DBusMenuMessageFilter(DBusConnection *conn, DBusMessage DBusMessage *reply; dbus_bool_t need_update; - need_update = FALSE; + need_update = TRUE; reply = ctx->message_new_method_return(msg); ctx->message_append_args(reply, DBUS_TYPE_BOOLEAN, &need_update, DBUS_TYPE_INVALID); ctx->connection_send(conn, reply, NULL); @@ -1457,6 +1457,67 @@ static DBusHandlerResult DBusMenuMessageFilter(DBusConnection *conn, DBusMessage return HandleGetGroupProperties(ctx, menu, conn, msg); } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "GetProperty")) { return HandleGetProperty(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "Get")) { + DBusMessage *reply; + const char *interface_name; + const char *property_name; + DBusMessageIter args, iter, variant_iter; + dbus_uint32_t version; + + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &interface_name); + ctx->message_iter_next(&args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &property_name); + + if (!SDL_strcmp(interface_name, DBUS_MENU_INTERFACE) && !SDL_strcmp(property_name, "Version")) { + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &iter); + version = 3; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "u", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_UINT32, &version); + ctx->message_iter_close_container(&iter, &variant_iter); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + } else if (ctx->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "GetAll")) { + DBusMessage *reply; + const char *interface_name; + const char *key; + DBusMessageIter args, iter, dict_iter, entry_iter, variant_iter; + dbus_uint32_t version; + + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &interface_name); + + if (!SDL_strcmp(interface_name, DBUS_MENU_INTERFACE)) { + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &iter); + ctx->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + + key = "Version"; + version = 3; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "u", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_UINT32, &version); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + + ctx->message_iter_close_container(&iter, &dict_iter); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; From 877bfae850adf92ddff23806562be9535b07f151 Mon Sep 17 00:00:00 2001 From: eafton Date: Mon, 8 Dec 2025 16:22:15 +0300 Subject: [PATCH 13/19] More fixes for GNOME/KDE --- src/tray/unix/SDL_dbustray.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index 5946837f14f63..be7cf1801a04f 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -166,18 +166,19 @@ static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_db driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + key = "Menu"; + value = "/NO_DBUSMENU"; if (menu_dbus) { if (menu_dbus->menu_path) { - key = "Menu"; value = menu_dbus->menu_path; - driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); - driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); - driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "o", &variant_iter); - driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); - driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); - driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); } } + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "o", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); if (tray_dbus->surface) { DBusMessageIter pixmap_array_iter, pixmap_struct_iter, pixmap_byte_array_iter; @@ -272,13 +273,16 @@ static DBusHandlerResult HandleGetProp(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "b", &variant_iter); driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_value); driver->dbus->message_iter_close_container(&iter, &variant_iter); - } else if (!SDL_strcmp(property, "Menu") && menu_dbus) { - if (menu_dbus->menu_path) { - value = menu_dbus->menu_path; - driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "o", &variant_iter); - driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); - driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "Menu")) { + value = "/NO_DBUSMENU"; + if (menu_dbus) { + if (menu_dbus->menu_path) { + value = menu_dbus->menu_path; + } } + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "o", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); + driver->dbus->message_iter_close_container(&iter, &variant_iter); } else if (!SDL_strcmp(property, "IconPixmap") && tray_dbus->surface) { DBusMessageIter array_iter, struct_iter; DBusMessageIter byte_array_iter; @@ -816,7 +820,7 @@ SDL_TrayDriver *SDL_Tray_CreateDBusDriver(void) host_registered = FALSE; SDL_DBus_QueryProperty(SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "IsStatusNotifierHostRegistered", DBUS_TYPE_BOOLEAN, &host_registered); - if (watcher_found && host_registered) { + if (host_registered) { sni_supported = true; } } From b345b7173a3e7209da68ecf60615d506f59cac53 Mon Sep 17 00:00:00 2001 From: eafton Date: Mon, 8 Dec 2025 16:41:18 +0300 Subject: [PATCH 14/19] More fixes for GNOME/KDE --- src/tray/unix/SDL_dbustray.c | 86 +++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index be7cf1801a04f..85d9ff432177c 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -81,6 +81,34 @@ typedef struct SDL_TrayEntryDBus SDL_TrayMenuDBus *sub_menu; } SDL_TrayEntryDBus; +static bool IsGNOME(void) +{ + const char *desktop; + const char *gnome; + + desktop = SDL_getenv("XDG_CURRENT_DESKTOP"); + if (desktop) { + gnome = SDL_strstr(desktop, "GNOME"); + while (gnome) { + bool valid_start = (gnome == desktop) || (*(gnome - 1) == ':'); + bool valid_end = (gnome[5] == '\0') || (gnome[5] == ':'); + + if (valid_start && valid_end) { + return true; + } + + gnome = SDL_strstr(gnome + 1, "GNOME"); + } + } + + desktop = SDL_getenv("DESKTOP_SESSION"); + if (desktop && SDL_strcasestr(desktop, "gnome")) { + return true; + } + + return false; +} + static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, SDL_TrayDriverDBus *driver, DBusMessage *msg) { SDL_TrayMenuDBus *menu_dbus; @@ -166,20 +194,25 @@ static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_db driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); - key = "Menu"; - value = "/NO_DBUSMENU"; - if (menu_dbus) { - if (menu_dbus->menu_path) { - value = menu_dbus->menu_path; - } + if (menu_dbus && menu_dbus->menu_path) { + key = "Menu"; + value = menu_dbus->menu_path; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "o", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + } else if (IsGNOME()) { + value = "/NO_DBUSMENU"; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "o", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); } - driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); - driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); - driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "o", &variant_iter); - driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); - driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); - driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); - + if (tray_dbus->surface) { DBusMessageIter pixmap_array_iter, pixmap_struct_iter, pixmap_byte_array_iter; @@ -274,15 +307,17 @@ static DBusHandlerResult HandleGetProp(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_value); driver->dbus->message_iter_close_container(&iter, &variant_iter); } else if (!SDL_strcmp(property, "Menu")) { - value = "/NO_DBUSMENU"; - if (menu_dbus) { - if (menu_dbus->menu_path) { - value = menu_dbus->menu_path; - } + if (menu_dbus && menu_dbus->menu_path) { + value = menu_dbus->menu_path; + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "o", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (IsGNOME()) { + value = "/NO_DBUSMENU"; + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "o", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); + driver->dbus->message_iter_close_container(&iter, &variant_iter); } - driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "o", &variant_iter); - driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); - driver->dbus->message_iter_close_container(&iter, &variant_iter); } else if (!SDL_strcmp(property, "IconPixmap") && tray_dbus->surface) { DBusMessageIter array_iter, struct_iter; DBusMessageIter byte_array_iter; @@ -663,7 +698,14 @@ SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, } SDL_ListAppend(&menu_dbus->menu, &entry_dbus->item); - + + if (menu->parent_entry) { + SDL_TrayEntryDBus *parent_entry_dbus; + + parent_entry_dbus = (SDL_TrayEntryDBus *)menu->parent_entry; + parent_entry_dbus->item.sub_menu = menu_dbus->menu; + } + if (update) { SDL_TrayMenuDBus *main_menu_dbus; From 5b22e706f99b0a11a0233945759cd607ef8f1e32 Mon Sep 17 00:00:00 2001 From: eafton Date: Tue, 9 Dec 2025 19:46:30 +0300 Subject: [PATCH 15/19] Hopefully this should get it to work on KDE --- src/tray/unix/SDL_dbustray.c | 58 +++++++++++++++++++++++------------- src/tray/unix/SDL_unixtray.c | 18 ++++++++--- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index 85d9ff432177c..4993d365eb683 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -55,7 +55,7 @@ typedef struct SDL_TrayDBus SDL_Tray _parent; DBusConnection *connection; - char *object_name; + char *service_name; char *tooltip; SDL_Surface *surface; @@ -83,26 +83,37 @@ typedef struct SDL_TrayEntryDBus static bool IsGNOME(void) { - const char *desktop; - const char *gnome; + const char *desktop_env; + char *desktop; + char *token; + char *saveptr; + bool is_gnome; - desktop = SDL_getenv("XDG_CURRENT_DESKTOP"); - if (desktop) { - gnome = SDL_strstr(desktop, "GNOME"); - while (gnome) { - bool valid_start = (gnome == desktop) || (*(gnome - 1) == ':'); - bool valid_end = (gnome[5] == '\0') || (gnome[5] == ':'); - - if (valid_start && valid_end) { - return true; + desktop_env = SDL_getenv("XDG_CURRENT_DESKTOP"); + if (desktop_env) { + desktop = SDL_strdup(desktop_env); + if (!desktop) { + return false; + } + + is_gnome = false; + token = SDL_strtok_r(desktop, ":", &saveptr); + while (token) { + if (SDL_strcmp(token, "GNOME") == 0) { + is_gnome = true; + break; } - - gnome = SDL_strstr(gnome + 1, "GNOME"); + token = SDL_strtok_r(NULL, ":", &saveptr); + } + + SDL_free(desktop); + if (is_gnome) { + return true; } } - desktop = SDL_getenv("DESKTOP_SESSION"); - if (desktop && SDL_strcasestr(desktop, "gnome")) { + desktop_env = SDL_getenv("DESKTOP_SESSION"); + if (desktop_env && SDL_strcasestr(desktop_env, "gnome")) { return true; } @@ -142,7 +153,7 @@ static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_db driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); - driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &tray_dbus->object_name); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &tray_dbus->service_name); driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); @@ -282,7 +293,7 @@ static DBusHandlerResult HandleGetProp(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, driver->dbus->message_iter_close_container(&iter, &variant_iter); } else if (!SDL_strcmp(property, "Id")) { driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); - driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &tray_dbus->object_name); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &tray_dbus->service_name); driver->dbus->message_iter_close_container(&iter, &variant_iter); } else if (!SDL_strcmp(property, "Title")) { driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); @@ -406,6 +417,7 @@ SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tool SDL_TrayDBus *tray_dbus; SDL_Tray *tray; const char *object_path; + char *register_name; DBusObjectPathVTable vtable; DBusError err; int status; @@ -455,8 +467,8 @@ SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tool /* request name */ driver->count++; - SDL_asprintf(&tray_dbus->object_name, "org.kde.StatusNotifierItem-%d-%d", getpid(), driver->count); - status = dbus_driver->dbus->bus_request_name(tray_dbus->connection, tray_dbus->object_name, DBUS_NAME_FLAG_REPLACE_EXISTING, &err); + SDL_asprintf(&tray_dbus->service_name, "org.kde.StatusNotifierItem-%d-%d", getpid(), driver->count); + status = dbus_driver->dbus->bus_request_name(tray_dbus->connection, tray_dbus->service_name, DBUS_NAME_FLAG_REPLACE_EXISTING, &err); if (dbus_driver->dbus->error_is_set(&err)) { SDL_SetError("Unable to create tray: %s", err.message); dbus_driver->dbus->error_free(&err); @@ -484,12 +496,16 @@ SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tool CLEANUP2(); return NULL; } - object_path = tray_dbus->object_name; + + /* register */ + SDL_asprintf(®ister_name, "%s%s", tray_dbus->service_name, SNI_OBJECT_PATH); if (!SDL_DBus_CallVoidMethodOnConnection(tray_dbus->connection, SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "RegisterStatusNotifierItem", DBUS_TYPE_STRING, &object_path, DBUS_TYPE_INVALID)) { + SDL_free(register_name); SDL_SetError("Unable to create tray: unable to register status notifier item!"); CLEANUP2(); return NULL; } + SDL_free(register_name); return tray; } diff --git a/src/tray/unix/SDL_unixtray.c b/src/tray/unix/SDL_unixtray.c index c4aa281b77248..fc3685059116b 100644 --- a/src/tray/unix/SDL_unixtray.c +++ b/src/tray/unix/SDL_unixtray.c @@ -246,14 +246,24 @@ SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu) { - SDL_InvalidParamError("menu"); - return NULL; + CHECK_PARAM(!menu) + { + SDL_InvalidParamError("menu"); + return NULL; + } + + return menu->parent_entry; } SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu) { - SDL_InvalidParamError("menu"); - return NULL; + CHECK_PARAM(!menu) + { + SDL_InvalidParamError("menu"); + return NULL; + } + + return menu->parent_tray; } void SDL_DestroyTray(SDL_Tray *tray) From 9e7b5b1698ec8ea8ae56d2f41443ea8f2afa11cc Mon Sep 17 00:00:00 2001 From: eafton Date: Wed, 10 Dec 2025 22:09:06 +0300 Subject: [PATCH 16/19] Hopefully this should get it to work on KDE --- src/tray/unix/SDL_dbustray.c | 105 +++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index 4993d365eb683..eae4a17287362 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -81,45 +81,6 @@ typedef struct SDL_TrayEntryDBus SDL_TrayMenuDBus *sub_menu; } SDL_TrayEntryDBus; -static bool IsGNOME(void) -{ - const char *desktop_env; - char *desktop; - char *token; - char *saveptr; - bool is_gnome; - - desktop_env = SDL_getenv("XDG_CURRENT_DESKTOP"); - if (desktop_env) { - desktop = SDL_strdup(desktop_env); - if (!desktop) { - return false; - } - - is_gnome = false; - token = SDL_strtok_r(desktop, ":", &saveptr); - while (token) { - if (SDL_strcmp(token, "GNOME") == 0) { - is_gnome = true; - break; - } - token = SDL_strtok_r(NULL, ":", &saveptr); - } - - SDL_free(desktop); - if (is_gnome) { - return true; - } - } - - desktop_env = SDL_getenv("DESKTOP_SESSION"); - if (desktop_env && SDL_strcasestr(desktop_env, "gnome")) { - return true; - } - - return false; -} - static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, SDL_TrayDriverDBus *driver, DBusMessage *msg) { SDL_TrayMenuDBus *menu_dbus; @@ -193,7 +154,7 @@ static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_db key = "ItemIsMenu"; menu_dbus = (SDL_TrayMenuDBus *)tray->menu; - if (menu_dbus) { + if (menu_dbus && menu_dbus->menu_path) { bool_value = TRUE; } else { bool_value = FALSE; @@ -214,7 +175,7 @@ static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_db driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); - } else if (IsGNOME()) { + } else { value = "/NO_DBUSMENU"; driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); @@ -309,7 +270,7 @@ static DBusHandlerResult HandleGetProp(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &empty); driver->dbus->message_iter_close_container(&iter, &variant_iter); } else if (!SDL_strcmp(property, "ItemIsMenu")) { - if (menu_dbus) { + if (menu_dbus && menu_dbus->menu_path) { bool_value = TRUE; } else { bool_value = FALSE; @@ -323,7 +284,7 @@ static DBusHandlerResult HandleGetProp(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "o", &variant_iter); driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); driver->dbus->message_iter_close_container(&iter, &variant_iter); - } else if (IsGNOME()) { + } else { value = "/NO_DBUSMENU"; driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "o", &variant_iter); driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_OBJECT_PATH, &value); @@ -729,6 +690,64 @@ SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, main_menu_dbus->menu); } else { menu_dbus->menu_path = SDL_DBus_ExportMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); + + if (menu_dbus->menu_path) { + DBusMessage *signal; + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewMenu"); + if (signal) { + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); + } + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged"); + if (signal) { + DBusMessageIter iter, dict, ientry, value; + const char *iface; + const char *prop; + const char *path; + dbus_bool_t bool_val; + + iface = SNI_INTERFACE; + prop = "Menu"; + path = menu_dbus->menu_path; + bool_val = TRUE; + driver->dbus->message_iter_init_append(signal, &iter); + driver->dbus->message_iter_append_basic(&iter, DBUS_TYPE_STRING, &iface); + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + driver->dbus->message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &ientry); + driver->dbus->message_iter_append_basic(&ientry, DBUS_TYPE_STRING, &prop); + driver->dbus->message_iter_open_container(&ientry, DBUS_TYPE_VARIANT, "o", &value); + driver->dbus->message_iter_append_basic(&value, DBUS_TYPE_OBJECT_PATH, &path); + driver->dbus->message_iter_close_container(&ientry, &value); + driver->dbus->message_iter_close_container(&dict, &ientry); + prop = "ItemIsMenu"; + driver->dbus->message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &ientry); + driver->dbus->message_iter_append_basic(&ientry, DBUS_TYPE_STRING, &prop); + driver->dbus->message_iter_open_container(&ientry, DBUS_TYPE_VARIANT, "b", &value); + driver->dbus->message_iter_append_basic(&value, DBUS_TYPE_BOOLEAN, &bool_val); + driver->dbus->message_iter_close_container(&ientry, &value); + driver->dbus->message_iter_close_container(&dict, &ientry); + driver->dbus->message_iter_close_container(&iter, &dict); + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &dict); + driver->dbus->message_iter_close_container(&iter, &dict); + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); + } + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewStatus"); + if (signal) { + const char *status_val; + + status_val = "Active"; + driver->dbus->message_append_args(signal, DBUS_TYPE_STRING, &status_val, DBUS_TYPE_INVALID); + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); + } + } } return entry; From cb8e9620a46433a651338880dcedaf9252a2ec5f Mon Sep 17 00:00:00 2001 From: eafton Date: Fri, 12 Dec 2025 21:39:25 +0300 Subject: [PATCH 17/19] KDE fix attempt --- src/tray/unix/SDL_dbustray.c | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index eae4a17287362..17ca6d77c8fec 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -348,11 +348,13 @@ static DBusHandlerResult MessageHandler(DBusConnection *connection, DBusMessage } else if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "GetAll")) { return HandleGetAllProps(tray, (SDL_TrayDBus *)tray, driver, msg); } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "ContextMenu")) { - reply = driver->dbus->message_new_method_return(msg); + printf("ContextMenu: %p\n", tray); + reply = driver->dbus->message_new_method_return(msg); driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); driver->dbus->message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "Activate")) { + printf("Activate: %p\n", tray); reply = driver->dbus->message_new_method_return(msg); driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); driver->dbus->message_unref(reply); @@ -459,14 +461,13 @@ SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tool } /* register */ - SDL_asprintf(®ister_name, "%s%s", tray_dbus->service_name, SNI_OBJECT_PATH); - if (!SDL_DBus_CallVoidMethodOnConnection(tray_dbus->connection, SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "RegisterStatusNotifierItem", DBUS_TYPE_STRING, &object_path, DBUS_TYPE_INVALID)) { + register_name = tray_dbus->service_name; + if (!SDL_DBus_CallVoidMethodOnConnection(tray_dbus->connection, SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "RegisterStatusNotifierItem", DBUS_TYPE_STRING, ®ister_name, DBUS_TYPE_INVALID)) { SDL_free(register_name); SDL_SetError("Unable to create tray: unable to register status notifier item!"); CLEANUP2(); return NULL; } - SDL_free(register_name); return tray; } @@ -690,17 +691,10 @@ SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, main_menu_dbus->menu); } else { menu_dbus->menu_path = SDL_DBus_ExportMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); - + if (menu_dbus->menu_path) { DBusMessage *signal; - signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewMenu"); - if (signal) { - driver->dbus->connection_send(tray_dbus->connection, signal, NULL); - driver->dbus->connection_flush(tray_dbus->connection); - driver->dbus->message_unref(signal); - } - signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged"); if (signal) { DBusMessageIter iter, dict, ientry, value; @@ -736,13 +730,9 @@ SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, driver->dbus->connection_flush(tray_dbus->connection); driver->dbus->message_unref(signal); } - - signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewStatus"); + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewMenu"); if (signal) { - const char *status_val; - - status_val = "Active"; - driver->dbus->message_append_args(signal, DBUS_TYPE_STRING, &status_val, DBUS_TYPE_INVALID); driver->dbus->connection_send(tray_dbus->connection, signal, NULL); driver->dbus->connection_flush(tray_dbus->connection); driver->dbus->message_unref(signal); From 8d1b26d4d26fd5bfec86b720bcd4a091b89f06e6 Mon Sep 17 00:00:00 2001 From: eafton Date: Wed, 17 Dec 2025 22:13:23 +0300 Subject: [PATCH 18/19] Add introspection support in the hope of getting this to work on KDE 6 --- src/core/linux/SDL_dbus.c | 10 ++++++++++ src/tray/unix/SDL_dbustray.c | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 12c173bb75650..4aebc509cec77 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -35,6 +35,8 @@ static SDL_DBusContext dbus; #define DBUS_MENU_INTERFACE "com.canonical.dbusmenu" #define DBUS_MENU_OBJECT_PATH "/Menu" +static const char *menu_introspect= "\r\n\r\n\r\n \r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\tName\r\n\t\t\tType\r\n\t\t\tDescription\r\n\t\t\tDefault Value\r\n\t\t\r\n\t\t\r\n\t\t\ttype\r\n\t\t\tString\r\n\t\t\tCan be one of:\r\n\t\t\t- \"standard\": an item which can be clicked to trigger an action or\r\n\t\t\t show another menu\r\n\t\t\t- \"separator\": a separator\r\n\r\n\t\t\tVendor specific types can be added by prefixing them with\r\n\t\t\t\"x--\".\r\n\t\t\t\r\n\t\t\t\"standard\"\r\n\t\t\r\n\t\t\r\n\t\t\tlabel\r\n\t\t\tstring\r\n\t\t\tText of the item, except that:\r\n\t\t\t-# two consecutive underscore characters \"__\" are displayed as a\r\n\t\t\tsingle underscore,\r\n\t\t\t-# any remaining underscore characters are not displayed at all,\r\n\t\t\t-# the first of those remaining underscore characters (unless it is\r\n\t\t\tthe last character in the string) indicates that the following\r\n\t\t\tcharacter is the access key.\r\n\t\t\t\r\n\t\t\t\"\"\r\n\t\t\r\n\t\t\r\n\t\t\tenabled\r\n\t\t\tboolean\r\n\t\t\tWhether the item can be activated or not.\r\n\t\t\ttrue\r\n\t\t\r\n\t\t\r\n\t\t\tvisible\r\n\t\t\tboolean\r\n\t\t\tTrue if the item is visible in the menu.\r\n\t\t\ttrue\r\n\t\t\r\n\t\t\r\n\t\t\ticon-name\r\n\t\t\tstring\r\n\t\t\tIcon name of the item, following the freedesktop.org icon spec.\r\n\t\t\t\"\"\r\n\t\t\r\n\t\t\r\n\t\t\ticon-data\r\n\t\t\tbinary\r\n\t\t\tPNG data of the icon.\r\n\t\t\tEmpty\r\n\t\t\r\n\t\t\r\n\t\t\tshortcut\r\n\t\t\tarray of arrays of strings\r\n\t\t\tThe shortcut of the item. Each array represents the key press\r\n\t\t\tin the list of keypresses. Each list of strings contains a list of\r\n\t\t\tmodifiers and then the key that is used. The modifier strings\r\n\t\t\tallowed are: \"Control\", \"Alt\", \"Shift\" and \"Super\".\r\n\r\n\t\t\t- A simple shortcut like Ctrl+S is represented as:\r\n\t\t\t [[\"Control\", \"S\"]]\r\n\t\t\t- A complex shortcut like Ctrl+Q, Alt+X is represented as:\r\n\t\t\t [[\"Control\", \"Q\"], [\"Alt\", \"X\"]]\r\n\t\t\tEmpty\r\n\t\t\r\n\t\t\r\n\t\t\ttoggle-type\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\tIf the item can be toggled, this property should be set to:\r\n\t\t\t- \"checkmark\": Item is an independent togglable item\r\n\t\t\t- \"radio\": Item is part of a group where only one item can be\r\n\t\t\t toggled at a time\r\n\t\t\t- \"\": Item cannot be toggled\r\n\t\t\t\r\n\t\t\t\"\"\r\n\t\t\r\n\t\t\r\n\t\t\ttoggle-state\r\n\t\t\tint\r\n\t\t\t\r\n\t\t\tDescribe the current state of a \"togglable\" item. Can be one of:\r\n\t\t\t- 0 = off\r\n\t\t\t- 1 = on\r\n\t\t\t- anything else = indeterminate\r\n\r\n\t\t\tNote:\r\n\t\t\tThe implementation does not itself handle ensuring that only one\r\n\t\t\titem in a radio group is set to \"on\", or that a group does not have\r\n\t\t\t\"on\" and \"indeterminate\" items simultaneously; maintaining this\r\n\t\t\tpolicy is up to the toolkit wrappers.\r\n\t\t\t\r\n\t\t\t-1\r\n\t\t\r\n\t\t\r\n\t\t\tchildren-display\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\tIf the menu item has children this property should be set to\r\n\t\t\t\"submenu\".\r\n\t\t\t\r\n\t\t\t\"\"\r\n\t\t\r\n\t\t\r\n\r\n\t\tVendor specific properties can be added by prefixing them with\r\n\t\t\"x--\".\r\n\t\t]]>\r\n\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\tProvides the version of the DBusmenu API that this API is\r\n\t\t\timplementing.\r\n\t\t\t\r\n\t\t\r\n\r\n \r\n \r\n Tells if the menus are in a normal state or they believe that they\r\n could use some attention. Cases for showing them would be if help\r\n were referring to them or they accessors were being highlighted.\r\n This property can have two values: \"normal\" in almost all cases and\r\n \"notice\" when they should have a higher priority to be shown.\r\n \r\n \r\n\r\n\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t Provides the layout and propertiers that are attached to the entries\r\n\t\t\t that are in the layout. It only gives the items that are children\r\n\t\t\t of the item that is specified in @a parentId. It will return all of the\r\n\t\t\t properties or specific ones depending of the value in @a propertyNames.\r\n\r\n\t\t\t The format is recursive, where the second \'v\' is in the same format\r\n\t\t\t as the original \'a(ia{sv}av)\'. Its content depends on the value\r\n\t\t\t of @a recursionDepth.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe ID of the parent node for the layout. For\r\n\t\t\t\tgrabbing the layout from the root node use zero.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t The amount of levels of recursion to use. This affects the\r\n\t\t\t\t content of the second variant array.\r\n\t\t\t\t - -1: deliver all the items under the @a parentId.\r\n\t\t\t\t - 0: no recursion, the array will be empty.\r\n\t\t\t\t - n: array will contains items up to \'n\' level depth.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tThe list of item properties we are\r\n\t\t\t\t\tinterested in. If there are no entries in the list all of\r\n\t\t\t\t\tthe properties will be sent.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe revision number of the layout. For matching\r\n\t\t\t\twith layoutUpdated signals.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe layout, as a recursive structure.\r\n\t\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\tReturns the list of items which are children of @a parentId.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tA list of ids that we should be finding the properties\r\n\t\t\t\t\ton. If the list is empty, all menu items should be sent.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tThe list of item properties we are\r\n\t\t\t\t\tinterested in. If there are no entries in the list all of\r\n\t\t\t\t\tthe properties will be sent.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tAn array of property values.\r\n\t\t\t\t\tAn item in this area is represented as a struct following\r\n\t\t\t\t\tthis format:\r\n\t\t\t\t\t@li id unsigned the item id\r\n\t\t\t\t\t@li properties map(string => variant) the requested item properties\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\t Get a signal property on a single item. This is not useful if you\'re\r\n\t\t\t going to implement this interface, it should only be used if you\'re\r\n\t\t\t debugging via a commandline tool.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tthe id of the item which received the event\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tthe name of the property to get\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tthe value of the property\r\n\t\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\t-\"\r\n\t\t\t]]>\r\n\t\t\t\r\n\t\t\t\tthe id of the item which received the event\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tthe type of event\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tevent-specific data\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe time that the event occured if available or the time the message was sent if not\r\n\t\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\tThis is called by the applet to notify the application that it is about\r\n\t\t\tto show the menu under the specified item.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\tWhich menu item represents the parent of the item about to be shown.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\tWhether this AboutToShow event should result in the menu being updated.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\tTriggered when there are lots of property updates across many items\r\n\t\t\tso they all get grouped into a single dbus message. The format is\r\n\t\t\tthe ID of the item with a hashtable of names and values for those\r\n\t\t\tproperties.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\tTriggered by the application to notify display of a layout update, up to\r\n\t\t\trevision\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe revision of the layout that we\'re currently on\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\tIf the layout update is only of a subtree, this is the\r\n\t\t\t\tparent item for the entries that have changed. It is zero if\r\n\t\t\t\tthe whole layout should be considered invalid.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\t\r\n\t\t\t The server is requesting that all clients displaying this\r\n\t\t\t menu open it to the user. This would be for things like\r\n\t\t\t hotkeys that when the user presses them the menu should\r\n\t\t\t open and display itself to the user.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tID of the menu that should be activated\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe time that the event occured\r\n\t\t\t\r\n\t\t\r\n\r\n\r\n\r\n\t\r\n"; + SDL_ELF_NOTE_DLOPEN( "core-libdbus", "Support for D-Bus IPC", @@ -1518,6 +1520,14 @@ static DBusHandlerResult DBusMenuMessageFilter(DBusConnection *conn, DBusMessage ctx->message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } + } else if (ctx->message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect")) { + DBusMessage *reply; + + reply = ctx->message_new_method_return(msg); + ctx->message_append_args(reply, DBUS_TYPE_STRING, &menu_introspect, DBUS_TYPE_INVALID); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index 17ca6d77c8fec..eba44b0f0645d 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -34,6 +34,7 @@ #define SNI_WATCHER_PATH "/StatusNotifierWatcher" #define SNI_WATCHER_INTERFACE "org.kde.StatusNotifierWatcher" #define SNI_OBJECT_PATH "/StatusNotifierItem" +static const char *sni_introspect = "\r\n\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n\r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n\r\n \r\n \r\n \r\n\r\n\r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n\r\n\r\n\r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n \r\n \r\n\r\n \r\n"; /* TODO: Destroy all menus and entires on destroy tray */ @@ -347,6 +348,12 @@ static DBusHandlerResult MessageHandler(DBusConnection *connection, DBusMessage return HandleGetProp(tray, (SDL_TrayDBus *)tray, driver, msg); } else if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "GetAll")) { return HandleGetAllProps(tray, (SDL_TrayDBus *)tray, driver, msg); + } else if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect")) { + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->message_append_args(reply, DBUS_TYPE_STRING, &sni_introspect, DBUS_TYPE_INVALID); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "ContextMenu")) { printf("ContextMenu: %p\n", tray); reply = driver->dbus->message_new_method_return(msg); @@ -463,7 +470,6 @@ SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tool /* register */ register_name = tray_dbus->service_name; if (!SDL_DBus_CallVoidMethodOnConnection(tray_dbus->connection, SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "RegisterStatusNotifierItem", DBUS_TYPE_STRING, ®ister_name, DBUS_TYPE_INVALID)) { - SDL_free(register_name); SDL_SetError("Unable to create tray: unable to register status notifier item!"); CLEANUP2(); return NULL; From 849ae490068e59252f5992e920712c24d739bc63 Mon Sep 17 00:00:00 2001 From: eafton Date: Sun, 28 Dec 2025 12:33:36 +0300 Subject: [PATCH 19/19] Will this fix the KDE 6 issues? --- src/tray/unix/SDL_dbustray.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c index eba44b0f0645d..c169f90c63cdb 100644 --- a/src/tray/unix/SDL_dbustray.c +++ b/src/tray/unix/SDL_dbustray.c @@ -701,6 +701,8 @@ SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, if (menu_dbus->menu_path) { DBusMessage *signal; + driver->dbus->connection_flush(tray_dbus->connection); + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged"); if (signal) { DBusMessageIter iter, dict, ientry, value;