diff --git a/src/SDL_list.c b/src/SDL_list.c index a17c40787a..558a38d823 100644 --- a/src/SDL_list.c +++ b/src/SDL_list.c @@ -22,6 +22,82 @@ #include "./SDL_list.h" +// 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; +} + +bool SDL_ListInsertAtPosition(SDL_ListNode **head, int pos, void *ent) +{ + SDL_ListNode *cursor; + SDL_ListNode *node; + int i; + + if (pos == -1) { + return SDL_ListAppend(head, ent); + } + + if (!pos) { + node = (SDL_ListNode *)SDL_malloc(sizeof(*node)); + if (!node) { + return false; + } + node->entry = ent; + + if (*head) { + node->next = *head; + } else { + node->next = NULL; + } + + *head = node; + } + + cursor = *head; + for (i = 1; i < pos - 1 && cursor; i++) { + cursor = cursor->next; + } + + if (!cursor) { + return SDL_ListAppend(head, ent); + } + + node = (SDL_ListNode *)SDL_malloc(sizeof(*node)); + if (!node) { + return false; + } + node->entry = ent; + node->next = cursor->next; + cursor->next = node; + + return true; +} + // Push bool SDL_ListAdd(SDL_ListNode **head, void *ent) { @@ -84,3 +160,14 @@ void SDL_ListClear(SDL_ListNode **head) SDL_free(tmp); } } + +int SDL_ListCountEntries(SDL_ListNode **head) +{ + SDL_ListNode *node; + int count = 0; + + for (node = *head; node; node = node->next) { + ++count; + } + return count; +} diff --git a/src/SDL_list.h b/src/SDL_list.h index bdfbc35160..b1e6fdbfea 100644 --- a/src/SDL_list.h +++ b/src/SDL_list.h @@ -28,9 +28,12 @@ typedef struct SDL_ListNode struct SDL_ListNode *next; } SDL_ListNode; +bool SDL_ListAppend(SDL_ListNode **head, void *ent); +bool SDL_ListInsertAtPosition(SDL_ListNode **head, int pos, void *ent); bool SDL_ListAdd(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/SDL_menu.h b/src/SDL_menu.h new file mode 100644 index 0000000000..2f0d0bd5ea --- /dev/null +++ b/src/SDL_menu.h @@ -0,0 +1,64 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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. +*/ + +#ifndef SDL_menu_h_ +#define SDL_menu_h_ + +#include "./SDL_list.h" + +typedef enum SDL_MenuItemType +{ + SDL_MENU_ITEM_TYPE_NORMAL, + SDL_MENU_ITEM_TYPE_SEPERATOR, + SDL_MENU_ITEM_TYPE_CHECKBOX +} SDL_MenuItemType; + +typedef enum SDL_MenuItemFlags +{ + SDL_MENU_ITEM_FLAGS_NONE = 0, + SDL_MENU_ITEM_FLAGS_DISABLED = 1 << 0, + SDL_MENU_ITEM_FLAGS_CHECKED = 1 << 1, + SDL_MENU_ITEM_FLAGS_BAR_ITEM = 1 << 2 +} SDL_MenuItemFlags; + +/* Do not create this struct directly, users of this structure like the DBUSMENU layer use extended versions of it which need to be allocated by specfic functions. */ +/* This struct is meant to be in an SDL_List just like sub_menu */ +typedef struct SDL_MenuItem +{ + /* Basic properties */ + const char *utf8; + SDL_MenuItemType type; + SDL_MenuItemFlags flags; + + /* Callback */ + void *cb_data; + void (*cb)(struct SDL_MenuItem *, void *); + + /* Submenu, set to NULL if none */ + SDL_ListNode *sub_menu; + + /* User data slots */ + void *udata; + void *udata2; + void *udata3; +} SDL_MenuItem; + +#endif // SDL_menu_h_ diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 9a2bef87e1..0532fb87c9 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -18,14 +18,31 @@ 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 "../../SDL_list.h" +#include "../../SDL_menu.h" #include "../../stdlib/SDL_vacopy.h" #include #include #ifdef SDL_USE_LIBDBUS + +typedef struct SDL_DBusMenuItem +{ + SDL_MenuItem _parent; + + SDL_DBusContext *dbus; + dbus_int32_t id; + dbus_uint32_t revision; + + /* Right click event handler */ + void *cbdata; + bool (*cb)(SDL_ListNode *, void *); +} SDL_DBusMenuItem; + // we never link directly to libdbus. #define SDL_DRIVER_DBUS_DYNAMIC "libdbus-1.so.3" static const char *dbus_library = SDL_DRIVER_DBUS_DYNAMIC; @@ -34,6 +51,11 @@ 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" +#define SDL_DBUS_UPDATE_MENU_FLAG_DO_NOT_REPLACE (1 << 0) +static const char *menu_introspect = ""; + SDL_ELF_NOTE_DLOPEN( "core-libdbus", "Support for D-Bus IPC", @@ -43,12 +65,12 @@ SDL_ELF_NOTE_DLOPEN( 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) @@ -107,6 +129,16 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(void (*)(char **), free_string_array); SDL_DBUS_SYM(void (*)(void), shutdown); + /* New symbols for SNI and menu export */ + SDL_DBUS_SYM(int (*)(DBusConnection *, const char *, unsigned int, DBusError *), bus_request_name); + SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_method_call); + SDL_DBUS_SYM(DBusMessage *(*)(DBusMessage *, const char *, const char *), message_new_error); + SDL_DBUS_SYM(DBusMessage *(*)(DBusMessage *), message_new_method_return); + SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const void *, int), message_iter_append_fixed_array); + SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *, int *), message_iter_get_fixed_array); + SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *, void **), connection_get_object_path_data); + SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *), connection_unregister_object_path); + #undef SDL_DBUS_SYM #undef SDL_DBUS_SYM2 @@ -466,12 +498,12 @@ failed: 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_OpenURI(const char *uri, const char *window_id, const char *activation_token) @@ -753,7 +785,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; @@ -797,10 +829,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; } @@ -962,4 +994,1036 @@ failed: return -1; } +/* DBUSMENU LAYER BEGINS HERE */ + +/* Special thanks to the kind Hayden Gray (thag_iceman/A1029384756) from the SDL community for his help! */ + +static SDL_DBusMenuItem *MenuGetItemById(SDL_ListNode *menu, dbus_int32_t id) +{ + SDL_ListNode *cursor; + + cursor = menu; + while (cursor) { + SDL_MenuItem *item; + SDL_DBusMenuItem *dbus_item; + + item = cursor->entry; + dbus_item = cursor->entry; + + if (dbus_item->id == id) { + return dbus_item; + } + + if (item->sub_menu) { + SDL_DBusMenuItem *found; + + found = MenuGetItemById(item->sub_menu, id); + if (found) { + return found; + } + } + + cursor = cursor->next; + } + return NULL; +} + +static void MenuAppendItemProperties(SDL_DBusContext *ctx, SDL_DBusMenuItem *dbus_item, DBusMessageIter *dict_iter) +{ + SDL_MenuItem *item; + DBusMessageIter entry_iter; + DBusMessageIter variant_iter; + const char *key; + const char *value; + int value_int; + dbus_bool_t value_bool; + + item = (SDL_MenuItem *)dbus_item; + + key = "label"; + value = item->utf8 ? 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, &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 = "type"; + if (item->type == SDL_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_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); + + key = "toggle-type"; + value = (item->type == SDL_MENU_ITEM_TYPE_CHECKBOX) ? "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_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); + + key = "children-display"; + value = item->sub_menu ? "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 MenuAppendItem(SDL_DBusContext *ctx, SDL_DBusMenuItem *dbus_item, DBusMessageIter *array_iter, int depth) +{ + SDL_MenuItem *item; + DBusMessageIter struct_iter, dict_iter, children_iter; + + item = (SDL_MenuItem *)dbus_item; + + ctx->message_iter_open_container(array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); + ctx->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &dbus_item->id); + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + MenuAppendItemProperties(ctx, dbus_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); + MenuAppendItem(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 MenuHandleGetLayout(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; + dbus_int32_t parent_id; + dbus_int32_t recursion_depth; + dbus_int32_t root_id; + dbus_uint32_t 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_MenuItem *item; + SDL_DBusMenuItem *dbus_item; + DBusMessageIter cvariant_iter, item_struct, item_dict, item_children; + + item = cursor->entry; + dbus_item = cursor->entry; + ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &cvariant_iter); + ctx->message_iter_open_container(&cvariant_iter, DBUS_TYPE_STRUCT, NULL, &item_struct); + ctx->message_iter_append_basic(&item_struct, DBUS_TYPE_INT32, &dbus_item->id); + ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "{sv}", &item_dict); + MenuAppendItemProperties(ctx, dbus_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); + MenuAppendItem(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; + } + } else if (parent_id) { + SDL_DBusMenuItem *parent; + SDL_MenuItem *parent_item; + + parent = MenuGetItemById(menu, parent_id); + parent_item = (SDL_MenuItem *)parent; + if (parent_item && parent_item->sub_menu) { + SDL_ListNode *cursor; + + cursor = parent_item->sub_menu; + while (cursor) { + SDL_MenuItem *item; + SDL_DBusMenuItem *dbus_item; + DBusMessageIter cvariant_iter, item_struct, item_dict, item_children; + + item = cursor->entry; + dbus_item = cursor->entry; + ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &cvariant_iter); + ctx->message_iter_open_container(&cvariant_iter, DBUS_TYPE_STRUCT, NULL, &item_struct); + ctx->message_iter_append_basic(&item_struct, DBUS_TYPE_INT32, &dbus_item->id); + ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "{sv}", &item_dict); + MenuAppendItemProperties(ctx, dbus_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); + MenuAppendItem(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; + } + } + } + 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 MenuHandleEvent(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ + SDL_MenuItem *item; + SDL_DBusMenuItem *dbus_item; + DBusMessage *reply; + const char *event_id; + DBusMessageIter args; + 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); + + item = NULL; + dbus_item = NULL; + if (!SDL_strcmp(event_id, "clicked")) { + dbus_item = MenuGetItemById(menu, id); + item = (SDL_MenuItem *)dbus_item; + } + + reply = ctx->message_new_method_return(msg); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + + if (item) { + if (item->type == SDL_MENU_ITEM_TYPE_CHECKBOX) { + item->flags ^= SDL_MENU_ITEM_FLAGS_CHECKED; + SDL_DBus_UpdateMenu(ctx, conn, menu, NULL, NULL, NULL, SDL_DBUS_UPDATE_MENU_FLAG_DO_NOT_REPLACE); + } + + if (item->cb) { + item->cb(item, item->cb_data); + } + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult MenuHandleEventGroup(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ + DBusMessage *reply; + DBusMessageIter reply_iter, id_errors_iter; + DBusMessageIter args, array_iter; + + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) == DBUS_TYPE_ARRAY) { + ctx->message_iter_recurse(&args, &array_iter); + while (ctx->message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRUCT) { + DBusMessageIter struct_iter; + const char *event_id; + dbus_int32_t id; + + ctx->message_iter_recurse(&array_iter, &struct_iter); + if (ctx->message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_INT32) { + ctx->message_iter_get_basic(&struct_iter, &id); + ctx->message_iter_next(&struct_iter); + if (ctx->message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRING) { + ctx->message_iter_get_basic(&struct_iter, &event_id); + + if (!SDL_strcmp(event_id, "clicked")) { + SDL_DBusMenuItem *dbus_item; + SDL_MenuItem *item; + + dbus_item = MenuGetItemById(menu, id); + item = (SDL_MenuItem *)dbus_item; + + if (item) { + if (item->type == SDL_MENU_ITEM_TYPE_CHECKBOX) { + item->flags ^= SDL_MENU_ITEM_FLAGS_CHECKED; + SDL_DBus_UpdateMenu(ctx, conn, menu, NULL, NULL, NULL, SDL_DBUS_UPDATE_MENU_FLAG_DO_NOT_REPLACE); + } + + if (item->cb) { + item->cb(item, item->cb_data); + } + } + } + } + } + ctx->message_iter_next(&array_iter); + } + } + + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &reply_iter); + ctx->message_iter_open_container(&reply_iter, DBUS_TYPE_ARRAY, "i", &id_errors_iter); + ctx->message_iter_close_container(&reply_iter, &id_errors_iter); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult MenuHandleGetProperty(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ + SDL_MenuItem *item; + SDL_DBusMenuItem *dbus_item; + DBusMessage *reply; + const char *property; + const char *val; + DBusMessageIter args; + DBusMessageIter iter, variant_iter; + dbus_int32_t 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); + + dbus_item = MenuGetItemById(menu, id); + item = (SDL_MenuItem *)dbus_item; + 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_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_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-type")) { + if (item->type == SDL_MENU_ITEM_TYPE_CHECKBOX) { + val = "checkmark"; + } 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); + } else if (!SDL_strcmp(property, "toggle-state")) { + int_val = (item->flags & SDL_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 MenuHandleGetGroupProperties(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]; + dbus_int32_t *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_MenuItem *item; + SDL_DBusMenuItem *dbus_item; + + dbus_item = MenuGetItemById(menu, ids[i]); + item = (SDL_MenuItem *)dbus_item; + 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) { + MenuAppendItemProperties(ctx, dbus_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")) { + val = (item->utf8) ? 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, &val); + 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_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_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")) { + val = (item->type == SDL_MENU_ITEM_TYPE_CHECKBOX) ? "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_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")) { + val = (item->sub_menu) ? "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 dbus_int32_t MenuGetMaxItemId(SDL_ListNode *menu) +{ + SDL_ListNode *cursor; + dbus_int32_t max_id; + + max_id = 0; + cursor = menu; + while (cursor) { + SDL_MenuItem *item; + SDL_DBusMenuItem *dbus_item; + + dbus_item = cursor->entry; + item = cursor->entry; + if (item) { + if (dbus_item->id > max_id) { + max_id = dbus_item->id; + } + + if (item->sub_menu) { + dbus_int32_t sub_max; + + sub_max = MenuGetMaxItemId(item->sub_menu); + if (sub_max > max_id) { + max_id = sub_max; + } + } + } + cursor = cursor->next; + } + return max_id; +} + +static void MenuAssignItemIds(SDL_ListNode *menu, dbus_int32_t *next_id) +{ + SDL_ListNode *cursor; + + cursor = menu; + while (cursor) { + SDL_MenuItem *item; + SDL_DBusMenuItem *dbus_item; + + dbus_item = cursor->entry; + item = cursor->entry; + if (item) { + if (!dbus_item->id) { + dbus_item->id = (*next_id)++; + } + + if (item->sub_menu) { + MenuAssignItemIds(item->sub_menu, next_id); + } + } + cursor = cursor->next; + } +} + +SDL_MenuItem *SDL_DBus_CreateMenuItem(void) +{ + SDL_DBusMenuItem *item; + item = SDL_malloc(sizeof(SDL_DBusMenuItem)); + item->id = 0; + item->revision = 0; + item->cb = NULL; + item->cbdata = NULL; + return (SDL_MenuItem *)item; +} + +static DBusHandlerResult MenuMessageHandler(DBusConnection *conn, DBusMessage *msg, void *user_data) +{ + SDL_ListNode *menu; + SDL_DBusMenuItem *item; + SDL_DBusContext *ctx; + + menu = user_data; + if (!menu) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (!menu->entry) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + item = (SDL_DBusMenuItem *)menu->entry; + ctx = item->dbus; + + if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "GetLayout")) { + return MenuHandleGetLayout(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "Event")) { + return MenuHandleEvent(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "EventGroup")) { + return MenuHandleEventGroup(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "AboutToShow")) { + DBusMessage *reply; + dbus_bool_t need_update; + + if (item->cb) { + item->cb(menu, item->cbdata); + } + + 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); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "AboutToShowGroup")) { + DBusMessage *reply; + DBusMessageIter iter, arr_iter; + + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &iter); + ctx->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "i", &arr_iter); + ctx->message_iter_close_container(&iter, &arr_iter); + ctx->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "i", &arr_iter); + ctx->message_iter_close_container(&iter, &arr_iter); + 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 MenuHandleGetGroupProperties(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "GetProperty")) { + return MenuHandleGetProperty(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; + const char *str_val; + 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)) { + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &iter); + if (!SDL_strcmp(property_name, "Version")) { + 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); + } else if (!SDL_strcmp(property_name, "Status")) { + str_val = "normal"; + + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &str_val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property_name, "TextDirection")) { + str_val = "ltr"; + + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &str_val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property_name, "IconThemePath")) { + DBusMessageIter array_iter; + + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "as", &variant_iter); + ctx->message_iter_open_container(&variant_iter, DBUS_TYPE_ARRAY, "s", &array_iter); + ctx->message_iter_close_container(&variant_iter, &array_iter); + ctx->message_iter_close_container(&iter, &variant_iter); + } else { + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + 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)) { + DBusMessageIter array_iter; + const char *str_val; + + 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); + + key = "Status"; + str_val = "normal"; + 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, &str_val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + + key = "TextDirection"; + str_val = "ltr"; + 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, &str_val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + + key = "IconThemePath"; + 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, "as", &variant_iter); + ctx->message_iter_open_container(&variant_iter, DBUS_TYPE_ARRAY, "s", &array_iter); + ctx->message_iter_close_container(&variant_iter, &array_iter); + 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; + } + } 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; +} + +const char *SDL_DBus_ExportMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu) +{ + DBusObjectPathVTable vtable; + dbus_int32_t next_id; + + if (!ctx || !menu) { + return NULL; + } + + next_id = 1; + MenuAssignItemIds(menu, &next_id); + + if (menu->entry) { + SDL_DBusMenuItem *item; + + item = menu->entry; + item->dbus = ctx; + item->revision++; + } + + vtable.message_function = MenuMessageHandler; + 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, const char *path, void (*cb)(SDL_ListNode *, const char *, void *), void *cbdata, unsigned char flags) +{ + DBusMessage *signal; + dbus_uint32_t revision; + dbus_int32_t next_id; + + if (!ctx) { + return; + } + + if (!menu) { + goto REPLACE_MENU; + } + + next_id = MenuGetMaxItemId(menu) + 1; + MenuAssignItemIds(menu, &next_id); + + revision = 0; + if (menu->entry) { + SDL_DBusMenuItem *item; + + item = menu->entry; + item->revision++; + item->dbus = ctx; + revision = item->revision; + } + + if (flags & SDL_DBUS_UPDATE_MENU_FLAG_DO_NOT_REPLACE) { + goto SEND_SIGNAL; + } + +REPLACE_MENU: + if (path) { + void *udata; + + ctx->connection_get_object_path_data(conn, path, &udata); + + if (udata != menu) { + DBusObjectPathVTable vtable; + + vtable.message_function = MenuMessageHandler; + vtable.unregister_function = NULL; + ctx->connection_unregister_object_path(conn, path); + ctx->connection_try_register_object_path(conn, path, &vtable, menu, NULL); + ctx->connection_flush(conn); + + if (cb) { + cb(menu, NULL, cbdata); + } + } + } else { + DBusObjectPathVTable vtable; + SDL_DBusMenuItem *item; + + if (!menu) { + goto SEND_SIGNAL; + } + + next_id = MenuGetMaxItemId(menu) + 1; + MenuAssignItemIds(menu, &next_id); + revision = 0; + if (menu->entry) { + item = menu->entry; + item->dbus = ctx; + item->revision++; + revision = item->revision; + } + + vtable.message_function = MenuMessageHandler; + vtable.unregister_function = NULL; + ctx->connection_try_register_object_path(conn, DBUS_MENU_OBJECT_PATH, &vtable, menu, NULL); + ctx->connection_flush(conn); + + if (cb) { + cb(menu, DBUS_MENU_OBJECT_PATH, cbdata); + } + ctx->connection_flush(conn); + } + +SEND_SIGNAL: + if (path) { + signal = ctx->message_new_signal(path, DBUS_MENU_INTERFACE, "LayoutUpdated"); + } else { + signal = ctx->message_new_signal(DBUS_MENU_OBJECT_PATH, DBUS_MENU_INTERFACE, "LayoutUpdated"); + } + + if (signal) { + dbus_int32_t 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); + ctx->connection_flush(conn); + } +} + +void SDL_DBus_RegisterMenuOpenCallback(SDL_ListNode *menu, bool (*cb)(SDL_ListNode *, void *), void *cbdata) +{ + SDL_DBusMenuItem *item; + + item = (SDL_DBusMenuItem *)menu->entry; + item->cb = cb; + item->cbdata = cbdata; +} + +void SDL_DBus_TransferMenuItemProperties(SDL_MenuItem *src, SDL_MenuItem *dst) +{ + SDL_DBusMenuItem *src_dbus; + SDL_DBusMenuItem *dst_dbus; + + src_dbus = (SDL_DBusMenuItem *)src; + dst_dbus = (SDL_DBusMenuItem *)dst; + dst_dbus->dbus = src_dbus->dbus; + dst_dbus->revision = src_dbus->revision; + dst_dbus->cb = src_dbus->cb; + dst_dbus->cbdata = src_dbus->cbdata; +} + +void SDL_DBus_RetractMenu(SDL_DBusContext *ctx, DBusConnection *conn, const char **path) +{ + ctx->connection_unregister_object_path(conn, *path); + *path = NULL; +} + #endif + diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 568dd74bb2..7a70b5e787 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -28,14 +28,20 @@ #define SDL_USE_LIBDBUS 1 #include +#include "../../SDL_list.h" +#include "../../SDL_menu.h" + #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 +#ifndef DBUS_ERROR_UNKNOWN_PROPERTY +#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" #endif typedef struct SDL_DBusContext @@ -95,6 +101,15 @@ typedef struct SDL_DBusContext void (*free_string_array)(char **); void (*shutdown)(void); + /* New symbols for SNI and menu export */ + int (*bus_request_name)(DBusConnection *, const char *, unsigned int, DBusError *); + dbus_bool_t (*message_is_method_call)(DBusMessage *, const char *, const char *); + DBusMessage *(*message_new_error)(DBusMessage *, const char *, const char *); + DBusMessage *(*message_new_method_return)(DBusMessage *); + dbus_bool_t (*message_iter_append_fixed_array)(DBusMessageIter *, int, const void *, int); + void (*message_iter_get_fixed_array)(DBusMessageIter *, void *, int *); + dbus_bool_t (*connection_unregister_object_path)(DBusConnection *, const char *); + dbus_bool_t (*connection_get_object_path_data)(DBusConnection *, const char *, void **); } SDL_DBusContext; extern void SDL_DBus_Init(void); @@ -128,6 +143,15 @@ extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_ extern int SDL_DBus_CameraPortalRequestAccess(void); +// Menu export functions +#define SDL_DBUS_UPDATE_MENU_FLAGS_NONE 0 +extern SDL_MenuItem *SDL_DBus_CreateMenuItem(void); +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, const char *path, void (*cb)(SDL_ListNode *, const char *, void *), void *cbdata, unsigned char flags); +extern void SDL_DBus_RegisterMenuOpenCallback(SDL_ListNode *menu, bool (*cb)(SDL_ListNode *, void *), void *cbdata); +extern void SDL_DBus_TransferMenuItemProperties(SDL_MenuItem *src, SDL_MenuItem *dst); +extern void SDL_DBus_RetractMenu(SDL_DBusContext *ctx, DBusConnection *conn, const char **path); + #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 0b5eb04715..d265eecccb 100644 --- a/src/tray/SDL_tray_utils.c +++ b/src/tray/SDL_tray_utils.c @@ -20,11 +20,10 @@ */ #include "SDL_internal.h" -#include "../video/SDL_sysvideo.h" #include "../events/SDL_events_c.h" +#include "../video/SDL_sysvideo.h" #include "SDL_tray_utils.h" - static int active_trays = 0; void SDL_RegisterTray(SDL_Tray *tray) @@ -91,3 +90,8 @@ 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 49c1c05452..1aacf18146 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 0000000000..b84525a130 --- /dev/null +++ b/src/tray/unix/SDL_dbustray.c @@ -0,0 +1,1177 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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. +*/ + +/* Special thanks to the kind Hayden Gray (thag_iceman/A1029384756) from the SDL community for his help! */ + +#include "SDL_internal.h" +#include "../../core/linux/SDL_dbus.h" + +#ifdef SDL_USE_LIBDBUS + +#include "../../video/SDL_surface_c.h" +#include "../SDL_tray_utils.h" +#include "SDL_unixtray.h" +#include + +typedef struct SDL_TrayDriverDBus +{ + SDL_TrayDriver _parent; + + SDL_DBusContext *dbus; +} SDL_TrayDriverDBus; + +typedef struct SDL_TrayDBus +{ + SDL_Tray _parent; + + DBusConnection *connection; + char *service_name; + + char *tooltip; + SDL_Surface *surface; + + SDL_TrayClickCallback l_cb; + SDL_TrayClickCallback r_cb; + SDL_TrayClickCallback m_cb; + void *udata; + + bool block; +} SDL_TrayDBus; + +typedef struct SDL_TrayMenuDBus +{ + SDL_TrayMenu _parent; + + SDL_ListNode *menu; + const char *menu_path; + + SDL_TrayEntry **array_representation; +} SDL_TrayMenuDBus; + +typedef struct SDL_TrayEntryDBus +{ + SDL_TrayEntry _parent; + + SDL_MenuItem *item; + SDL_TrayMenuDBus *sub_menu; +} SDL_TrayEntryDBus; + +#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" +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"; + +static DBusHandlerResult TrayHandleGetAllProps(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_uint32_t uint32_val; + dbus_bool_t bool_value; + + menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + + 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->service_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 = "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, "i", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_INT32, &uint32_val); + 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 && menu_dbus->menu_path) { + bool_value = TRUE; + } 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 && 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 { + key = "Menu"; + 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); + } + + 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(tray_dbus->connection, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult TrayHandleGetProp(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; + + menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + + 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); + + 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->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); + 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")) { + if (menu_dbus && menu_dbus->menu_path) { + 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); + } else if (!SDL_strcmp(property, "Menu")) { + 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 { + 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); + } + } 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 if (!SDL_strcmp(property, "WindowId")) { + dbus_uint32_t uint32_val; + + uint32_val = 0; + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "i", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_INT32, &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"); + } + + driver->dbus->connection_send(tray_dbus->connection, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult TrayMessageHandler(DBusConnection *connection, DBusMessage *msg, void *user_data) +{ + SDL_Tray *tray; + SDL_TrayDBus *tray_dbus; + SDL_TrayDriverDBus *driver; + DBusMessage *reply; + + tray = user_data; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + + if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "Get")) { + return TrayHandleGetProp(tray, tray_dbus, driver, msg); + } else if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "GetAll")) { + return TrayHandleGetAllProps(tray, tray_dbus, 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(tray_dbus->connection, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "ContextMenu")) { + if (tray_dbus->r_cb) { + tray_dbus->r_cb(tray_dbus->udata, tray); + } + + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(tray_dbus->connection, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "Activate")) { + if (tray_dbus->l_cb) { + tray_dbus->l_cb(tray_dbus->udata, tray); + } + + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(tray_dbus->connection, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "SecondaryActivate")) { + if (tray_dbus->m_cb) { + tray_dbus->m_cb(tray_dbus->udata, tray); + } + + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(tray_dbus->connection, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "Scroll")) { + DBusError err; + char *orientation; + Sint32 delta; + + driver->dbus->error_init(&err); + driver->dbus->message_get_args(msg, &err, DBUS_TYPE_INT32, &delta, DBUS_TYPE_STRING, &orientation, DBUS_TYPE_INVALID); + if (!driver->dbus->error_is_set(&err)) { + /* Scroll callback support will come later :) */ + } else { + driver->dbus->error_free(&err); + } + + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(tray_dbus->connection, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_PropertiesID props) +{ + SDL_TrayDriverDBus *dbus_driver; + SDL_TrayDBus *tray_dbus; + SDL_Tray *tray; + SDL_Surface *icon; + const char *tooltip; + const char *object_path; + char *register_name; + 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() + + /* Get properties */ + tooltip = SDL_GetStringProperty(props, SDL_PROP_TRAY_CREATE_TOOLTIP_STRING, NULL); + icon = (SDL_Surface *)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, NULL); + + /* Allocate the tray structure */ + tray_dbus = SDL_malloc(sizeof(SDL_TrayDBus)); + tray = (SDL_Tray *)tray_dbus; + if (!tray_dbus) { + return NULL; + } + + /* Populate */ + tray->menu = NULL; + tray->driver = driver; + if (tooltip) { + tray_dbus->tooltip = SDL_strdup(tooltip); + } else { + tray_dbus->tooltip = NULL; + } + tray_dbus->surface = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_ARGB32); + tray_dbus->block = false; + + /* 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->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); + 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 = TrayMessageHandler; + 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; + } + + /* 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_SetError("Unable to create tray: unable to register status notifier item!"); + CLEANUP2(); + return NULL; + } + + /* Icon mouse event callbacks */ + tray_dbus->l_cb = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_LEFTCLICK_CALLBACK_POINTER, NULL); + tray_dbus->r_cb = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_RIGHTCLICK_CALLBACK_POINTER, NULL); + tray_dbus->m_cb = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_MIDDLECLICK_CALLBACK_POINTER, NULL); + tray_dbus->udata = SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_USERDATA_POINTER, NULL); + + return tray; +} + +void DestroyDriver(SDL_TrayDriver *driver) +{ + SDL_DBus_Quit(); + SDL_free(driver); +} + +void DestroyMenu(SDL_TrayMenu *menu) +{ + SDL_TrayMenuDBus *menu_dbus; + + if (!menu) { + return; + } + + menu_dbus = (SDL_TrayMenuDBus *)menu; + + if (menu_dbus->menu) { + SDL_ListNode *cursor; + + cursor = menu_dbus->menu; + while (cursor) { + SDL_MenuItem *item; + SDL_TrayEntryDBus *entry; + + item = cursor->entry; + entry = item->udata; + + if (entry->sub_menu) { + DestroyMenu((SDL_TrayMenu *)entry->sub_menu); + entry->sub_menu = NULL; + } + SDL_free(item); + SDL_free(entry); + + cursor = cursor->next; + } + SDL_ListClear(&menu_dbus->menu); + } + + if (menu_dbus->array_representation) { + SDL_free(menu_dbus->array_representation); + } + + SDL_free(menu_dbus); +} + +void DestroyTray(SDL_Tray *tray) +{ + SDL_TrayDBus *tray_dbus; + SDL_TrayDriverDBus *driver; + + driver = (SDL_TrayDriverDBus *)tray->driver; + tray_dbus = (SDL_TrayDBus *)tray; + + /* Destroy connection */ + driver->dbus->connection_flush(tray_dbus->connection); + tray_dbus->block = 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); + + /* Destroy the menus and entries */ + if (tray->menu) { + DestroyMenu(tray->menu); + tray->menu = NULL; + } + + /* Free the tray */ + 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; + + if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { + return; + } + + if (tray_dbus->block) { + return; + } + + driver->dbus->connection_read_write(tray_dbus->connection, 0); + while (driver->dbus->connection_dispatch(tray_dbus->connection) == DBUS_DISPATCH_DATA_REMAINS) { + if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { + break; + } + + if (tray_dbus->block) { + break; + } + + 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); + } + + 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); + 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; + menu_dbus->array_representation = NULL; + + 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; + menu_dbus->array_representation = NULL; + + return menu; +} + +SDL_TrayMenu *GetTraySubmenu(SDL_TrayEntry *entry) +{ + SDL_TrayEntryDBus *entry_dbus; + + entry_dbus = (SDL_TrayEntryDBus *)entry; + + return (SDL_TrayMenu *)entry_dbus->sub_menu; +} + +bool TrayRightClickHandler(SDL_ListNode *menu, void *udata) +{ + SDL_TrayDBus *tray_dbus; + + tray_dbus = (SDL_TrayDBus *)udata; + + return tray_dbus->r_cb(tray_dbus->udata, (SDL_Tray *)tray_dbus); +} + +void TraySendNewMenu(SDL_Tray *tray, const char *new_path) { + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + SDL_TrayMenuDBus *menu_dbus; + DBusMessage *signal; + + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + + 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; + const char *iface; + const char *prop; + const char *path; + dbus_bool_t bool_val; + + iface = SNI_INTERFACE; + prop = "Menu"; + if (new_path) { + path = menu_dbus->menu_path = new_path; + } else { + 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, "NewMenu"); + if (signal) { + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); + } + + driver->dbus->connection_flush(tray_dbus->connection); +} + + +void TrayNewMenuOnMenuUpdateCallback(SDL_ListNode *menu, const char *path, void *cbdata) { + TraySendNewMenu((SDL_Tray *)cbdata, path); +} + +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; + + 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->parent = menu; + entry_dbus->item = SDL_DBus_CreateMenuItem(); + entry_dbus->item->utf8 = label; + if (!label) { + entry_dbus->item->type = SDL_MENU_ITEM_TYPE_SEPERATOR; + } else if (flags & SDL_TRAYENTRY_CHECKBOX) { + entry_dbus->item->type = SDL_MENU_ITEM_TYPE_CHECKBOX; + } else { + entry_dbus->item->type = SDL_MENU_ITEM_TYPE_NORMAL; + } + entry_dbus->item->flags = SDL_MENU_ITEM_FLAGS_NONE; + entry_dbus->item->cb_data = NULL; + entry_dbus->item->cb = NULL; + entry_dbus->item->sub_menu = NULL; + entry_dbus->item->udata = entry_dbus; + entry_dbus->sub_menu = NULL; + + if (menu_dbus->menu) { + update = true; + } else { + update = false; + } + + if (menu->parent_entry) { + update = true; + } + + SDL_ListInsertAtPosition(&menu_dbus->menu, pos, 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; + + main_menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, main_menu_dbus->menu, main_menu_dbus->menu_path, TrayNewMenuOnMenuUpdateCallback, tray, SDL_DBUS_UPDATE_MENU_FLAGS_NONE); + } else { + menu_dbus->menu_path = SDL_DBus_ExportMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); + + if (menu_dbus->menu_path) { + TraySendNewMenu(tray, NULL); + } + } + + if (menu->parent_tray && !menu->parent_entry && tray_dbus->r_cb) { + SDL_DBus_RegisterMenuOpenCallback(menu_dbus->menu, TrayRightClickHandler, tray); + } + + return entry; +} + +SDL_TrayEntry **GetTrayEntries(SDL_TrayMenu *menu, int *count) +{ + SDL_TrayEntry **array_representation; + SDL_TrayMenuDBus *menu_dbus; + SDL_ListNode *cursor; + int sz; + int i; + + menu_dbus = (SDL_TrayMenuDBus *)menu; + + if (menu_dbus->array_representation) { + SDL_free(menu_dbus->array_representation); + } + + sz = SDL_ListCountEntries(&menu_dbus->menu); + array_representation = SDL_calloc(sz + 1, sizeof(SDL_TrayEntry *)); + if (!array_representation) { + SDL_SetError("Memory allocation failure!"); + return NULL; + } + + i = 0; + cursor = menu_dbus->menu; + while (cursor) { + SDL_MenuItem *item; + + item = cursor->entry; + array_representation[i] = item->udata; + cursor = cursor->next; + i++; + } + array_representation[sz] = NULL; + + *count = sz; + return array_representation; +} + +void RemoveTrayEntry(SDL_TrayEntry *entry) +{ + SDL_TrayEntryDBus *entry_dbus; + SDL_TrayMenuDBus *menu_dbus; + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + SDL_TrayMenuDBus *main_menu_dbus; + const char *old_path; + + 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; + main_menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + + tray_dbus->block = true; + if (menu_dbus->menu->entry == entry_dbus->item && menu_dbus->menu->next) { + SDL_DBus_TransferMenuItemProperties(entry_dbus->item, (SDL_MenuItem *)menu_dbus->menu->next->entry); + } + + old_path = NULL; + if (!main_menu_dbus->menu->next) { + old_path = main_menu_dbus->menu_path; + } + + driver->dbus->connection_flush(tray_dbus->connection); + DestroyMenu((SDL_TrayMenu *)entry_dbus->sub_menu); + SDL_ListRemove(&menu_dbus->menu, entry_dbus->item); + SDL_free(entry_dbus->item); + SDL_free(entry); + + if (old_path) { + SDL_DBus_RetractMenu(driver->dbus, tray_dbus->connection, &main_menu_dbus->menu_path); + } + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, main_menu_dbus->menu, main_menu_dbus->menu_path, TrayNewMenuOnMenuUpdateCallback, tray, SDL_DBUS_UPDATE_MENU_FLAGS_NONE); + driver->dbus->connection_flush(tray_dbus->connection); + tray_dbus->block = false; +} + +void EntryCallback(SDL_MenuItem *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_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + SDL_TrayMenuDBus *main_menu_dbus; + + tray = entry->parent->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + entry_dbus = (SDL_TrayEntryDBus *)entry; + main_menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + + entry_dbus->item->cb = EntryCallback; + entry_dbus->item->cb_data = userdata; + entry_dbus->item->udata2 = callback; + + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, main_menu_dbus->menu, main_menu_dbus->menu_path, TrayNewMenuOnMenuUpdateCallback, tray, SDL_DBUS_UPDATE_MENU_FLAGS_NONE); +} + +void SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) +{ + SDL_TrayEntryDBus *entry_dbus; + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + SDL_TrayMenuDBus *main_menu_dbus; + + tray = entry->parent->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + entry_dbus = (SDL_TrayEntryDBus *)entry; + main_menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + + entry_dbus->item->utf8 = label; + + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, main_menu_dbus->menu, main_menu_dbus->menu_path, TrayNewMenuOnMenuUpdateCallback, tray, SDL_DBUS_UPDATE_MENU_FLAGS_NONE); +} + +const char *GetTrayEntryLabel(SDL_TrayEntry *entry) +{ + return ((SDL_TrayEntryDBus *)entry)->item->utf8; +} + +void SetTrayEntryChecked(SDL_TrayEntry *entry, bool val) +{ + SDL_TrayEntryDBus *entry_dbus; + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + SDL_TrayMenuDBus *main_menu_dbus; + + tray = entry->parent->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + entry_dbus = (SDL_TrayEntryDBus *)entry; + main_menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + + if (val) { + entry_dbus->item->flags |= SDL_MENU_ITEM_FLAGS_CHECKED; + } else { + entry_dbus->item->flags &= ~(SDL_MENU_ITEM_FLAGS_CHECKED); + } + + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, main_menu_dbus->menu, main_menu_dbus->menu_path, TrayNewMenuOnMenuUpdateCallback, tray, SDL_DBUS_UPDATE_MENU_FLAGS_NONE); +} + +bool GetTrayEntryChecked(SDL_TrayEntry *entry) +{ + return ((SDL_TrayEntryDBus *)entry)->item->flags & SDL_MENU_ITEM_FLAGS_CHECKED; +} + +void SetTrayEntryEnabled(SDL_TrayEntry *entry, bool val) +{ + SDL_TrayEntryDBus *entry_dbus; + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + SDL_TrayMenuDBus *main_menu_dbus; + + tray = entry->parent->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + entry_dbus = (SDL_TrayEntryDBus *)entry; + main_menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + + if (!val) { + entry_dbus->item->flags |= SDL_MENU_ITEM_FLAGS_DISABLED; + } else { + entry_dbus->item->flags &= ~(SDL_MENU_ITEM_FLAGS_DISABLED); + } + + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, main_menu_dbus->menu, main_menu_dbus->menu_path, TrayNewMenuOnMenuUpdateCallback, tray, SDL_DBUS_UPDATE_MENU_FLAGS_NONE); +} + +bool GetTrayEntryEnabled(SDL_TrayEntry *entry) +{ + return !(((SDL_TrayEntryDBus *)entry)->item->flags & SDL_MENU_ITEM_FLAGS_DISABLED); +} + +void ClickTrayEntry(SDL_TrayEntry *entry) +{ + SDL_TrayEntryDBus *dbus_entry; + SDL_TrayCallback entry_cb; + + dbus_entry = (SDL_TrayEntryDBus *)entry; + if (dbus_entry->item->type == SDL_MENU_ITEM_TYPE_CHECKBOX) { + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + SDL_TrayMenuDBus *main_menu_dbus; + + tray = entry->parent->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + main_menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + + dbus_entry->item->flags ^= SDL_MENU_ITEM_FLAGS_CHECKED; + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, main_menu_dbus->menu, main_menu_dbus->menu_path, TrayNewMenuOnMenuUpdateCallback, tray, SDL_DBUS_UPDATE_MENU_FLAGS_NONE); + } + entry_cb = dbus_entry->item->udata2; + entry_cb(dbus_entry->item->cb_data, dbus_entry->item->udata); +} + +SDL_TrayDriver *SDL_Tray_CreateDBusDriver(void) +{ + SDL_TrayDriverDBus *dbus_driver; + SDL_TrayDriver *driver; + SDL_DBusContext *ctx; + DBusMessage *saved; + char **paths; + int count; + int i; + bool sni_supported; + + /* Init DBus and get context */ + SDL_DBus_Init(); + ctx = SDL_DBus_GetContext(); + if (!ctx) { + return NULL; + } + + /* SNI support detection */ + sni_supported = false; + paths = NULL; + count = 0; + + if (SDL_DBus_CallMethod(&saved, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames", DBUS_TYPE_INVALID, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &paths, &count, DBUS_TYPE_INVALID)) { + SDL_DBus_FreeReply(&saved); + + 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(&saved, SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "IsStatusNotifierHostRegistered", DBUS_TYPE_BOOLEAN, &host_registered); + SDL_DBus_FreeReply(&saved); + if (host_registered) { + sni_supported = true; + } + } + } + } + + if (!sni_supported) { + SDL_SetError("Unable to create tray: no SNI support!"); + SDL_DBus_Quit(); + return NULL; + } + + /* Allocate the driver struct */ + dbus_driver = SDL_malloc(sizeof(SDL_TrayDriverDBus)); + driver = (SDL_TrayDriver *)dbus_driver; + if (!dbus_driver) { + return NULL; + } + + /* Populate */ + 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->CreateTraySubmenu = CreateTraySubmenu; + driver->GetTraySubmenu = GetTraySubmenu; + driver->GetTrayEntries = GetTrayEntries; + driver->RemoveTrayEntry = RemoveTrayEntry; + driver->SetTrayEntryCallback = SetTrayEntryCallback; + driver->SetTrayEntryLabel = SetTrayEntryLabel; + driver->GetTrayEntryLabel = GetTrayEntryLabel; + driver->SetTrayEntryChecked = SetTrayEntryChecked; + driver->GetTrayEntryChecked = GetTrayEntryChecked; + driver->SetTrayEntryEnabled = SetTrayEntryEnabled; + driver->GetTrayEntryEnabled = GetTrayEntryEnabled; + driver->ClickTrayEntry = ClickTrayEntry; + driver->DestroyDriver = DestroyDriver; + + return driver; +} + +#endif diff --git a/src/tray/unix/SDL_tray.c b/src/tray/unix/SDL_tray.c index d932dd3754..607c24df46 100644 --- a/src/tray/unix/SDL_tray.c +++ b/src/tray/unix/SDL_tray.c @@ -22,326 +22,87 @@ #include "SDL_internal.h" #include "../SDL_tray_utils.h" -#include "../../video/SDL_stb_c.h" +#include "SDL_unixtray.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); -} +static SDL_TrayDriver *driver = NULL; void SDL_UpdateTrays(void) { - if (SDL_HasActiveTrays()) { - SDL_UpdateGtk(); + 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++) { + if (trays[i]) { + if (SDL_ObjectValid(trays[i], SDL_OBJECT_TYPE_TRAY)) { + trays[i]->driver->UpdateTray(trays[i]); + } + } + } + + SDL_free(trays); } SDL_Tray *SDL_CreateTrayWithProperties(SDL_PropertiesID props) { + SDL_Tray *tray; + if (!SDL_IsMainThread()) { SDL_SetError("This function should be called on the main thread"); return NULL; } - - if (!init_appindicator()) { - return NULL; + + if (!driver) { +#ifdef SDL_USE_LIBDBUS + driver = SDL_Tray_CreateDBusDriver(); +#endif } - SDL_Surface *icon = (SDL_Surface *)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, NULL); + if (driver) { + tray = driver->CreateTray(driver, props); - 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; + if (tray) { + SDL_RegisterTray(tray); } - } - - /* 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 = NULL; } - 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; } SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) { SDL_Tray *tray; - SDL_PropertiesID props = SDL_CreateProperties(); + SDL_PropertiesID props; + + props = SDL_CreateProperties(); + if (!props) { return NULL; } + if (icon) { SDL_SetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, icon); } + if (tooltip) { SDL_SetStringProperty(props, SDL_PROP_TRAY_CREATE_TOOLTIP_STRING, tooltip); } + tray = SDL_CreateTrayWithProperties(props); SDL_DestroyProperties(props); return tray; @@ -349,65 +110,41 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) { - if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { + SDL_InvalidParamError("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); - } + tray->driver->SetTrayIcon(tray, icon); } void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip) { - /* AppIndicator provides no tooltip support. */ + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { + SDL_InvalidParamError("tray"); + return; + } + + tray->driver->SetTrayTooltip(tray, tooltip); } SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray) { - CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_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; + return tray->driver->CreateTrayMenu(tray); } SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray) { - CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { SDL_InvalidParamError("tray"); return NULL; } @@ -417,314 +154,163 @@ SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray) SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) { - CHECK_PARAM(!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; + return entry->parent->parent_tray->driver->CreateTraySubmenu(entry); } SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry) { - CHECK_PARAM(!entry) { + CHECK_PARAM(!entry) + { SDL_InvalidParamError("entry"); return NULL; } - return entry->submenu; + return entry->parent->parent_tray->driver->GetTraySubmenu(entry); } const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count) { - CHECK_PARAM(!menu) { + CHECK_PARAM(!menu) + { SDL_InvalidParamError("menu"); return NULL; } - if (count) { - *count = menu->nEntries; + CHECK_PARAM(!count) + { + SDL_InvalidParamError("count"); + return NULL; } - return (const SDL_TrayEntry **)menu->entries; + + return (const SDL_TrayEntry **)menu->parent_tray->driver->GetTrayEntries(menu, count); } void SDL_RemoveTrayEntry(SDL_TrayEntry *entry) { - if (!entry) { + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("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); + entry->parent->parent_tray->driver->RemoveTrayEntry(entry); } SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) { - CHECK_PARAM(!menu) { + 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; + return menu->parent_tray->driver->InsertTrayEntryAt(menu, pos, label, flags); } void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) { - if (!entry) { + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("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); - } + entry->parent->parent_tray->driver->SetTrayEntryLabel(entry, label); } const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry) { - CHECK_PARAM(!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; + return entry->parent->parent_tray->driver->GetTrayEntryLabel(entry); } void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked) { - if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); 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); - } + entry->parent->parent_tray->driver->SetTrayEntryChecked(entry, checked); } bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry) { - if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { - return false; + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return NULL; } - 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; + return entry->parent->parent_tray->driver->GetTrayEntryChecked(entry); } void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled) { - if (!entry) { + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); return; } - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - gtk->gtk.widget_set_sensitive(entry->item, enabled); - SDL_Gtk_ExitContext(gtk); - } + entry->parent->parent_tray->driver->SetTrayEntryEnabled(entry, enabled); } bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry) { - if (!entry) { - return false; + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return NULL; } - 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; + return entry->parent->parent_tray->driver->GetTrayEntryEnabled(entry); } void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) { - if (!entry) { + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); return; } - entry->callback = callback; - entry->userdata = userdata; + CHECK_PARAM(!callback) + { + SDL_InvalidParamError("callback"); + return; + } + + entry->parent->parent_tray->driver->SetTrayEntryCallback(entry, callback, userdata); } void SDL_ClickTrayEntry(SDL_TrayEntry *entry) { - if (!entry) { - return; - } + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return; + } - if (entry->flags & SDL_TRAYENTRY_CHECKBOX) { - SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry)); - } - - if (entry->callback) { - entry->callback(entry->userdata, entry); - } + entry->parent->parent_tray->driver->GetTrayEntryEnabled(entry); } SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) { - CHECK_PARAM(!entry) { + CHECK_PARAM(!entry) + { SDL_InvalidParamError("entry"); return NULL; } @@ -734,12 +320,19 @@ SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu) { + CHECK_PARAM(!menu) + { + SDL_InvalidParamError("menu"); + return NULL; + } + return menu->parent_entry; } SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu) { - CHECK_PARAM(!menu) { + CHECK_PARAM(!menu) + { SDL_InvalidParamError("menu"); return NULL; } @@ -754,33 +347,10 @@ void SDL_DestroyTray(SDL_Tray *tray) } SDL_UnregisterTray(tray); + tray->driver->DestroyTray(tray); - if (tray->menu) { - DestroySDLMenu(tray->menu); + if (!SDL_GetActiveTrayCount()) { + driver->DestroyDriver(driver); + driver = NULL; } - - 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.h b/src/tray/unix/SDL_unixtray.h new file mode 100644 index 0000000000..1a31ffc1b5 --- /dev/null +++ b/src/tray/unix/SDL_unixtray.h @@ -0,0 +1,81 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_PropertiesID); + 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); + SDL_TrayMenu *(*CreateTraySubmenu)(SDL_TrayEntry *); + SDL_TrayMenu *(*GetTraySubmenu)(SDL_TrayEntry *); + SDL_TrayEntry **(*GetTrayEntries)(SDL_TrayMenu *, int *); + void (*RemoveTrayEntry)(SDL_TrayEntry *); + void (*SetTrayEntryLabel)(SDL_TrayEntry *, const char *); + const char *(*GetTrayEntryLabel)(SDL_TrayEntry *); + void (*SetTrayEntryChecked)(SDL_TrayEntry *, bool); + void (*ClickTrayEntry)(SDL_TrayEntry *); + bool (*GetTrayEntryChecked)(SDL_TrayEntry *); + void (*SetTrayEntryEnabled)(SDL_TrayEntry *, bool); + bool (*GetTrayEntryEnabled)(SDL_TrayEntry *); + void (*SetTrayEntryCallback)(SDL_TrayEntry *, SDL_TrayCallback, void *); + + 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_