diff --git a/src/SDL_list.c b/src/SDL_list.c index 04cc0825b447e..2c3206de63579 100644 --- a/src/SDL_list.c +++ b/src/SDL_list.c @@ -37,6 +37,36 @@ bool SDL_ListAdd(SDL_ListNode **head, void *ent) return true; } +// Append +bool SDL_ListAppend(SDL_ListNode **head, void *ent) +{ + SDL_ListNode *cursor; + SDL_ListNode *node; + + if (!head) { + return false; + } + + node = (SDL_ListNode *)SDL_malloc(sizeof(*node)); + if (!node) { + return false; + } + node->entry = ent; + node->next = NULL; + + if (*head) { + cursor = *head; + while (cursor->next) { + cursor = cursor->next; + } + cursor->next = node; + } else { + *head = node; + } + + return true; +} + // Pop from end as a FIFO (if add with SDL_ListAdd) void SDL_ListPop(SDL_ListNode **head, void **ent) { @@ -84,3 +114,17 @@ void SDL_ListClear(SDL_ListNode **head) SDL_free(tmp); } } + +int SDL_ListCountEntries(SDL_ListNode **head) { + SDL_ListNode *l; + int i; + + i = 0; + l = *head; + while (l) { + i++; + l = l->next; + } + + return i; +} diff --git a/src/SDL_list.h b/src/SDL_list.h index 83ec844b018f2..10f48e84da244 100644 --- a/src/SDL_list.h +++ b/src/SDL_list.h @@ -29,8 +29,10 @@ typedef struct SDL_ListNode } SDL_ListNode; bool SDL_ListAdd(SDL_ListNode **head, void *ent); +bool SDL_ListAppend(SDL_ListNode **head, void *ent); void SDL_ListPop(SDL_ListNode **head, void **ent); void SDL_ListRemove(SDL_ListNode **head, void *ent); void SDL_ListClear(SDL_ListNode **head); +int SDL_ListCountEntries(SDL_ListNode **head); #endif // SDL_list_h_ diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index b675f1872473f..12c173bb75650 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -18,9 +18,10 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ + #include "SDL_internal.h" -#include "SDL_dbus.h" #include "../../stdlib/SDL_vacopy.h" +#include "SDL_dbus.h" #ifdef SDL_USE_LIBDBUS // we never link directly to libdbus. @@ -31,21 +32,23 @@ static char *inhibit_handle = NULL; static unsigned int screensaver_cookie = 0; static SDL_DBusContext dbus; +#define DBUS_MENU_INTERFACE "com.canonical.dbusmenu" +#define DBUS_MENU_OBJECT_PATH "/Menu" + SDL_ELF_NOTE_DLOPEN( "core-libdbus", "Support for D-Bus IPC", SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - SDL_DRIVER_DBUS_DYNAMIC -); + SDL_DRIVER_DBUS_DYNAMIC); static bool LoadDBUSSyms(void) { -#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \ +#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \ dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y) #define SDL_DBUS_SYM2(TYPE, x, y) \ if (!(dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y))) \ - return false + return false #define SDL_DBUS_SYM_OPTIONAL(TYPE, x) \ SDL_DBUS_SYM2_OPTIONAL(TYPE, x, dbus_##x) @@ -53,48 +56,54 @@ static bool LoadDBUSSyms(void) #define SDL_DBUS_SYM(TYPE, x) \ SDL_DBUS_SYM2(TYPE, x, dbus_##x) - SDL_DBUS_SYM(DBusConnection *(*)(DBusBusType, DBusError *), bus_get_private); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register); + SDL_DBUS_SYM(DBusConnection * (*)(DBusBusType, DBusError *), bus_get_private); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, DBusError *), bus_register); SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match); SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_remove_match); SDL_DBUS_SYM(const char *(*)(DBusConnection *), bus_get_unique_name); - SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private); + SDL_DBUS_SYM(int (*)(DBusConnection *, const char *, unsigned int, DBusError *), bus_request_name); + SDL_DBUS_SYM(DBusConnection * (*)(const char *, DBusError *), connection_open_private); SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction), connection_add_filter); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *), connection_remove_filter); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *, const DBusObjectPathVTable *, void *, DBusError *), connection_try_register_object_path); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusMessage *, dbus_uint32_t *), connection_send); - SDL_DBUS_SYM(DBusMessage *(*)(DBusConnection *, DBusMessage *, int, DBusError *), connection_send_with_reply_and_block); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *), connection_get_is_connected); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction), connection_add_filter); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, DBusHandleMessageFunction, void *), connection_remove_filter); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, const char *, const DBusObjectPathVTable *, void *, DBusError *), connection_try_register_object_path); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, DBusMessage *, dbus_uint32_t *), connection_send); + SDL_DBUS_SYM(DBusMessage * (*)(DBusConnection *, DBusMessage *, int, DBusError *), connection_send_with_reply_and_block); SDL_DBUS_SYM(void (*)(DBusConnection *), connection_close); SDL_DBUS_SYM(void (*)(DBusConnection *), connection_ref); SDL_DBUS_SYM(void (*)(DBusConnection *), connection_unref); SDL_DBUS_SYM(void (*)(DBusConnection *), connection_flush); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write_dispatch); - SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path); - SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call); - SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *), message_new_signal); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, int), connection_read_write); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusConnection *, int), connection_read_write_dispatch); + SDL_DBUS_SYM(DBusDispatchStatus(*)(DBusConnection *), connection_dispatch); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, const char *, const char *), message_is_signal); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, const char *, const char *), message_is_method_call); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, const char *), message_has_path); + SDL_DBUS_SYM(DBusMessage * (*)(DBusMessage *, const char *, const char *), message_new_error); + SDL_DBUS_SYM(DBusMessage * (*)(const char *, const char *, const char *, const char *), message_new_method_call); + SDL_DBUS_SYM(DBusMessage * (*)(DBusMessage *), message_new_method_return); + SDL_DBUS_SYM(DBusMessage * (*)(const char *, const char *, const char *), message_new_signal); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, int, ...), message_append_args); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, int, va_list), message_append_args_valist); SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const char *, DBusMessageIter *), message_iter_open_container); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const void *), message_iter_append_basic); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, DBusMessageIter *), message_iter_close_container); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, ...), message_get_args); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, va_list), message_get_args_valist); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusMessageIter *), message_iter_init); - SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *), message_iter_next); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessageIter *, int, const char *, DBusMessageIter *), message_iter_open_container); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessageIter *, int, const void *), message_iter_append_basic); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessageIter *, int, const void *, int), message_iter_append_fixed_array); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessageIter *, DBusMessageIter *), message_iter_close_container); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, DBusError *, int, ...), message_get_args); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, DBusError *, int, va_list), message_get_args_valist); + SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *, int *), message_iter_get_fixed_array); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessage *, DBusMessageIter *), message_iter_init); + SDL_DBUS_SYM(dbus_bool_t(*)(DBusMessageIter *), message_iter_next); SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *), message_iter_get_basic); SDL_DBUS_SYM(int (*)(DBusMessageIter *), message_iter_get_arg_type); SDL_DBUS_SYM(void (*)(DBusMessageIter *, DBusMessageIter *), message_iter_recurse); SDL_DBUS_SYM(void (*)(DBusMessage *), message_unref); - SDL_DBUS_SYM(dbus_bool_t (*)(void), threads_init_default); + SDL_DBUS_SYM(dbus_bool_t(*)(void), threads_init_default); SDL_DBUS_SYM(void (*)(DBusError *), error_init); - SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *), error_is_set); - SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *, const char *), error_has_name); + SDL_DBUS_SYM(dbus_bool_t(*)(const DBusError *), error_is_set); + SDL_DBUS_SYM(dbus_bool_t(*)(const DBusError *, const char *), error_has_name); SDL_DBUS_SYM(void (*)(DBusError *), error_free); SDL_DBUS_SYM(char *(*)(void), get_local_machine_id); SDL_DBUS_SYM_OPTIONAL(char *(*)(DBusError *), try_get_local_machine_id); @@ -439,12 +448,12 @@ static bool SDL_DBus_AppendDictWithKeysAndValues(DBusMessageIter *iterInit, cons static bool SDL_DBus_AppendDictWithKeyValue(DBusMessageIter *iterInit, const char *key, const char *value) { - const char *keys[1]; - const char *values[1]; + const char *keys[1]; + const char *values[1]; - keys[0] = key; - values[0] = value; - return SDL_DBus_AppendDictWithKeysAndValues(iterInit, keys, values, 1); + keys[0] = key; + values[0] = value; + return SDL_DBus_AppendDictWithKeysAndValues(iterInit, keys, values, 1); } bool SDL_DBus_ScreensaverInhibit(bool inhibit) @@ -622,7 +631,7 @@ char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count) * The spec doesn't define any entries yet so it's empty. */ dbus.message_iter_init_append(msg, &iter); if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) || - !dbus.message_iter_close_container(&iter, &iterDict)) { + !dbus.message_iter_close_container(&iter, &iterDict)) { SDL_OutOfMemory(); dbus.message_unref(msg); goto failed; @@ -666,10 +675,10 @@ static DBusHandlerResult SDL_DBus_CameraPortalMessageHandler(DBusConnection *con if (dbus.message_is_signal(msg, "org.freedesktop.DBus", "NameOwnerChanged")) { if (!dbus.message_get_args(msg, data->err, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &old, - DBUS_TYPE_STRING, &new, - DBUS_TYPE_INVALID)) { + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) { data->done = true; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } @@ -831,4 +840,749 @@ int SDL_DBus_CameraPortalRequestAccess(void) return -1; } +static SDL_DBusMenuItem *GetMenuItemById(SDL_ListNode *menu, long id) +{ + SDL_ListNode *cursor; + + cursor = menu; + while (cursor) { + SDL_DBusMenuItem *item; + + item = cursor->entry; + + if (item->id == id) { + return item; + } + + if (item->sub_menu) { + SDL_DBusMenuItem *found; + + found = GetMenuItemById(item->sub_menu, id); + if (found) { + return found; + } + } + + cursor = cursor->next; + } + return NULL; +} + +static void AppendMenuItemProperties(SDL_DBusContext *ctx, SDL_DBusMenuItem *item, DBusMessageIter *dict_iter) +{ + DBusMessageIter entry_iter; + DBusMessageIter variant_iter; + const char *key; + const char *value; + dbus_bool_t value_bool; + + if (item->utf8) { + key = "label"; + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &item->utf8); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + } + + key = "type"; + if (item->type == SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR) { + value = "separator"; + } else { + value = "standard"; + } + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + + key = "enabled"; + value_bool = !(item->flags & SDL_DBUS_MENU_ITEM_FLAGS_DISABLED); + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &value_bool); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + + key = "visible"; + value_bool = TRUE; + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &value_bool); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + + if (item->type == SDL_DBUS_MENU_ITEM_TYPE_CHECKBOX) { + int value_int; + + key = "toggle-type"; + value = "checkmark"; + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + + key = "toggle-state"; + value_int = (item->flags & SDL_DBUS_MENU_ITEM_FLAGS_CHECKED) ? 1 : 0; + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "i", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_INT32, &value_int); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + } + + if (item->sub_menu) { + key = "children-display"; + value = "submenu"; + ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(dict_iter, &entry_iter); + } +} + +static void AppendMenuItem(SDL_DBusContext *ctx, SDL_DBusMenuItem *item, DBusMessageIter *array_iter, int depth) +{ + DBusMessageIter struct_iter, dict_iter, children_iter; + + ctx->message_iter_open_container(array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); + ctx->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &item->id); + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + AppendMenuItemProperties(ctx, item, &dict_iter); + ctx->message_iter_close_container(&struct_iter, &dict_iter); + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "v", &children_iter); + if (item->sub_menu && depth > 0) { + SDL_ListNode *cursor; + + cursor = item->sub_menu; + while (cursor) { + SDL_DBusMenuItem *child; + DBusMessageIter variant_iter; + + child = cursor->entry; + ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &variant_iter); + AppendMenuItem(ctx, child, &variant_iter, depth - 1); + ctx->message_iter_close_container(&children_iter, &variant_iter); + cursor = cursor->next; + } + } + ctx->message_iter_close_container(&struct_iter, &children_iter); + ctx->message_iter_close_container(array_iter, &struct_iter); +} + +static DBusHandlerResult HandleGetLayout(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ + DBusMessage *reply; + DBusMessageIter reply_iter, struct_iter, dict_iter, children_iter; + DBusMessageIter entry_iter, variant_iter; + DBusMessageIter args; + const char *key; + const char *val; + long parent_id; + long recursion_depth; + long root_id; + unsigned long revision; + + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_INT32) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &parent_id); + ctx->message_iter_next(&args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_INT32) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &recursion_depth); + if (recursion_depth == -1) { + recursion_depth = 100; + } + + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &reply_iter); + + revision = 0; + if (menu) { + if (menu->entry) { + revision = ((SDL_DBusMenuItem *)menu->entry)->revision; + } + } + ctx->message_iter_append_basic(&reply_iter, DBUS_TYPE_UINT32, &revision); + + ctx->message_iter_open_container(&reply_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); + + root_id = 0; + ctx->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &root_id); + + key = "children-display"; + val = "submenu"; + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + ctx->message_iter_close_container(&struct_iter, &dict_iter); + + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "v", &children_iter); + if (!parent_id && menu) { + SDL_ListNode *cursor; + + cursor = menu; + while (cursor) { + SDL_DBusMenuItem *item; + DBusMessageIter cvariant_iter, item_struct, item_dict, item_children; + + 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, &item->id); + ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "{sv}", &item_dict); + AppendMenuItemProperties(ctx, item, &item_dict); + ctx->message_iter_close_container(&item_struct, &item_dict); + ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "v", &item_children); + if (item->sub_menu && recursion_depth) { + SDL_ListNode *child_cursor; + + child_cursor = item->sub_menu; + while (child_cursor) { + SDL_DBusMenuItem *child; + DBusMessageIter child_variant; + + child = child_cursor->entry; + ctx->message_iter_open_container(&item_children, DBUS_TYPE_VARIANT, "(ia{sv}av)", &child_variant); + AppendMenuItem(ctx, child, &child_variant, recursion_depth - 1); + ctx->message_iter_close_container(&item_children, &child_variant); + child_cursor = child_cursor->next; + } + } + ctx->message_iter_close_container(&item_struct, &item_children); + ctx->message_iter_close_container(&cvariant_iter, &item_struct); + + ctx->message_iter_close_container(&children_iter, &cvariant_iter); + cursor = cursor->next; + } + } else if (parent_id) { + SDL_DBusMenuItem *parent; + + parent = GetMenuItemById(menu, parent_id); + if (parent && parent->sub_menu) { + SDL_ListNode *cursor; + + cursor = parent->sub_menu; + while (cursor) { + SDL_DBusMenuItem *item; + DBusMessageIter cvariant_iter, item_struct, item_dict, item_children; + + 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, &item->id); + ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "{sv}", &item_dict); + AppendMenuItemProperties(ctx, item, &item_dict); + ctx->message_iter_close_container(&item_struct, &item_dict); + ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "v", &item_children); + if (item->sub_menu && recursion_depth) { + SDL_ListNode *child_cursor; + + child_cursor = item->sub_menu; + while (child_cursor) { + SDL_DBusMenuItem *child; + DBusMessageIter child_variant; + + child = child_cursor->entry; + ctx->message_iter_open_container(&item_children, DBUS_TYPE_VARIANT, "(ia{sv}av)", &child_variant); + AppendMenuItem(ctx, child, &child_variant, recursion_depth - 1); + ctx->message_iter_close_container(&item_children, &child_variant); + child_cursor = child_cursor->next; + } + } + ctx->message_iter_close_container(&item_struct, &item_children); + ctx->message_iter_close_container(&cvariant_iter, &item_struct); + + ctx->message_iter_close_container(&children_iter, &cvariant_iter); + + cursor = cursor->next; + } + } + } + ctx->message_iter_close_container(&struct_iter, &children_iter); + ctx->message_iter_close_container(&reply_iter, &struct_iter); + + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult HandleEvent(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ + DBusMessage *reply; + const char *event_id; + SDL_DBusMenuItem *item; + 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; + if (!SDL_strcmp(event_id, "clicked")) { + item = GetMenuItemById(menu, id); + } + + reply = ctx->message_new_method_return(msg); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + + if (item) { + if (item->cb) { + item->cb(item, item->cb_data); + } + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult HandleGetProperty(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ + DBusMessage *reply; + SDL_DBusMenuItem *item; + const char *property; + const char *val; + DBusMessageIter args; + DBusMessageIter iter, variant_iter; + long id; + int int_val; + dbus_bool_t bool_val; + + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_INT32) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &id); + ctx->message_iter_next(&args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &property); + + item = GetMenuItemById(menu, id); + if (!item) { + DBusMessage *error; + + error = ctx->message_new_error(msg, "com.canonical.dbusmenu.Error", "Item not found"); + ctx->connection_send(conn, error, NULL); + ctx->message_unref(error); + return DBUS_HANDLER_RESULT_HANDLED; + } + + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &iter); + if (!SDL_strcmp(property, "label")) { + val = item->utf8 ? item->utf8 : ""; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "enabled")) { + bool_val = !(item->flags & SDL_DBUS_MENU_ITEM_FLAGS_DISABLED); + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "visible")) { + bool_val = 1; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "type")) { + if (item->type == SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR) { + val = "separator"; + } else { + val = "standard"; + } + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "toggle-state")) { + int_val = (item->flags & SDL_DBUS_MENU_ITEM_FLAGS_CHECKED) ? 1 : 0; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "i", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_INT32, &int_val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "children-display")) { + val = item->sub_menu ? "submenu" : ""; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&iter, &variant_iter); + } else { + val = ""; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&iter, &variant_iter); + } + + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult HandleGetGroupProperties(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg) +{ + #define FILTER_PROPS_SZ 32 + DBusMessage *reply; + DBusMessageIter args, array_iter, prop_iter; + DBusMessageIter iter, reply_array_iter; + const char *filter_props[FILTER_PROPS_SZ]; + long *ids; + int ids_sz; + int filter_sz; + int i; + int j; + + ids_sz = 0; + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + ctx->message_iter_recurse(&args, &array_iter); + if (ctx->message_iter_get_arg_type(&array_iter) == DBUS_TYPE_INT32) { + ctx->message_iter_get_fixed_array(&array_iter, &ids, &ids_sz); + } + + ctx->message_iter_next(&args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + ctx->message_iter_recurse(&args, &prop_iter); + filter_sz = 0; + while (ctx->message_iter_get_arg_type(&prop_iter) == DBUS_TYPE_STRING) { + if (filter_sz < FILTER_PROPS_SZ) { + ctx->message_iter_get_basic(&prop_iter, &filter_props[filter_sz]); + filter_sz++; + } + ctx->message_iter_next(&prop_iter); + } + + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &iter); + ctx->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ia{sv})", &reply_array_iter); + + for (i = 0; i < ids_sz; i++) { + SDL_DBusMenuItem *item; + + item = GetMenuItemById(menu, ids[i]); + if (item) { + DBusMessageIter struct_iter, dict_iter; + + ctx->message_iter_open_container(&reply_array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); + ctx->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &ids[i]); + ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + + if (filter_sz == 0) { + AppendMenuItemProperties(ctx, item, &dict_iter); + } else { + for (j = 0; j < filter_sz; j++) { + DBusMessageIter entry_iter, variant_iter; + const char *prop; + const char *val; + int int_val; + dbus_bool_t bool_val; + + prop = filter_props[j]; + if (!SDL_strcmp(prop, "label") && item->utf8) { + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &item->utf8); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "type")) { + val = (item->type == SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR) ? "separator" : "standard"; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "enabled")) { + bool_val = !(item->flags & SDL_DBUS_MENU_ITEM_FLAGS_DISABLED); + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "visible")) { + bool_val = 1; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &bool_val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "toggle-type") && item->type == SDL_DBUS_MENU_ITEM_TYPE_CHECKBOX) { + val = "checkmark"; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "toggle-state")) { + int_val = (item->flags & SDL_DBUS_MENU_ITEM_FLAGS_CHECKED) ? 1 : 0; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "i", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_INT32, &int_val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } else if (!SDL_strcmp(prop, "children-display") && item->sub_menu) { + val = "submenu"; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &prop); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + } + } + } + + ctx->message_iter_close_container(&struct_iter, &dict_iter); + ctx->message_iter_close_container(&reply_array_iter, &struct_iter); + } + } + + ctx->message_iter_close_container(&iter, &reply_array_iter); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static long GetMaxMenuItemId(SDL_ListNode *menu) +{ + SDL_ListNode *cursor; + long max_id; + + max_id = 0; + cursor = menu; + while (cursor) { + SDL_DBusMenuItem *item; + + item = cursor->entry; + if (item) { + if (item->id > max_id) { + max_id = item->id; + } + + if (item->sub_menu) { + long sub_max; + + sub_max = GetMaxMenuItemId(item->sub_menu); + if (sub_max > max_id) { + max_id = sub_max; + } + } + } + cursor = cursor->next; + } + return max_id; +} + +static void AssignMenuItemIds(SDL_ListNode *menu, long *next_id) +{ + SDL_ListNode *cursor; + + cursor = menu; + while (cursor) { + SDL_DBusMenuItem *item; + + item = cursor->entry; + if (item) { + if (!item->id) { + item->id = (*next_id)++; + } + if (item->sub_menu) { + AssignMenuItemIds(item->sub_menu, next_id); + } + } + cursor = cursor->next; + } +} + +void SDL_DBus_InitMenuItemInternals(SDL_DBusMenuItem *item) +{ + item->id = 0; +} + +static DBusHandlerResult DBusMenuMessageFilter(DBusConnection *conn, DBusMessage *msg, void *user_data) +{ + SDL_ListNode *menu; + SDL_DBusContext *ctx; + + menu = user_data; + if (!menu || !menu->entry) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + ctx = ((SDL_DBusMenuItem *)menu->entry)->dbus; + if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "GetLayout")) { + return HandleGetLayout(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "Event")) { + return HandleEvent(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "AboutToShow")) { + DBusMessage *reply; + dbus_bool_t need_update; + + need_update = TRUE; + reply = ctx->message_new_method_return(msg); + ctx->message_append_args(reply, DBUS_TYPE_BOOLEAN, &need_update, DBUS_TYPE_INVALID); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "GetGroupProperties")) { + return HandleGetGroupProperties(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, DBUS_MENU_INTERFACE, "GetProperty")) { + return HandleGetProperty(ctx, menu, conn, msg); + } else if (ctx->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "Get")) { + DBusMessage *reply; + const char *interface_name; + const char *property_name; + DBusMessageIter args, iter, variant_iter; + dbus_uint32_t version; + + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &interface_name); + ctx->message_iter_next(&args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &property_name); + + if (!SDL_strcmp(interface_name, DBUS_MENU_INTERFACE) && !SDL_strcmp(property_name, "Version")) { + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &iter); + version = 3; + ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "u", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_UINT32, &version); + ctx->message_iter_close_container(&iter, &variant_iter); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + } else if (ctx->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "GetAll")) { + DBusMessage *reply; + const char *interface_name; + const char *key; + DBusMessageIter args, iter, dict_iter, entry_iter, variant_iter; + dbus_uint32_t version; + + ctx->message_iter_init(msg, &args); + if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + ctx->message_iter_get_basic(&args, &interface_name); + + if (!SDL_strcmp(interface_name, DBUS_MENU_INTERFACE)) { + reply = ctx->message_new_method_return(msg); + ctx->message_iter_init_append(reply, &iter); + ctx->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + + key = "Version"; + version = 3; + ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "u", &variant_iter); + ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_UINT32, &version); + ctx->message_iter_close_container(&entry_iter, &variant_iter); + ctx->message_iter_close_container(&dict_iter, &entry_iter); + + ctx->message_iter_close_container(&iter, &dict_iter); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +const char *SDL_DBus_ExportMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu) +{ + DBusObjectPathVTable vtable; + long next_id; + + if (!ctx || !menu) { + return NULL; + } + + next_id = 1; + AssignMenuItemIds(menu, &next_id); + + if (menu->entry) { + SDL_DBusMenuItem *item; + + item = menu->entry; + item->dbus = ctx; + item->revision++; + } + + vtable.message_function = DBusMenuMessageFilter; + vtable.unregister_function = NULL; + if (!ctx->connection_try_register_object_path(conn, DBUS_MENU_OBJECT_PATH, &vtable, menu, NULL)) { + return NULL; + } + + return DBUS_MENU_OBJECT_PATH; +} + +void SDL_DBus_UpdateMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu) +{ + DBusMessage *signal; + unsigned long revision; + long next_id; + + if (!ctx || !menu) { + return; + } + + next_id = GetMaxMenuItemId(menu) + 1; + AssignMenuItemIds(menu, &next_id); + + revision = 0; + if (menu->entry) { + SDL_DBusMenuItem *item; + + item = menu->entry; + item->revision++; + revision = item->revision; + } + + signal = ctx->message_new_signal(DBUS_MENU_OBJECT_PATH, DBUS_MENU_INTERFACE, "LayoutUpdated"); + if (signal) { + long parent; + + parent = 0; + ctx->message_append_args(signal, DBUS_TYPE_UINT32, &revision, DBUS_TYPE_INT32, &parent, DBUS_TYPE_INVALID); + ctx->connection_send(conn, signal, NULL); + ctx->message_unref(signal); + } +} + #endif diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index 230b20fded057..5aa9478a4036e 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -26,16 +26,20 @@ #ifdef HAVE_DBUS_DBUS_H #define SDL_USE_LIBDBUS 1 +#include "../../SDL_list.h" #include #ifndef DBUS_TIMEOUT_USE_DEFAULT #define DBUS_TIMEOUT_USE_DEFAULT -1 #endif #ifndef DBUS_TIMEOUT_INFINITE -#define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff) +#define DBUS_TIMEOUT_INFINITE ((int)0x7fffffff) #endif #ifndef DBUS_TYPE_UNIX_FD -#define DBUS_TYPE_UNIX_FD ((int) 'h') +#define DBUS_TYPE_UNIX_FD ((int)'h') +#endif +#ifndef DBUS_ERROR_UNKNOWN_PROPERTY +#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" #endif typedef struct SDL_DBusContext @@ -47,6 +51,7 @@ typedef struct SDL_DBusContext dbus_bool_t (*bus_register)(DBusConnection *, DBusError *); void (*bus_add_match)(DBusConnection *, const char *, DBusError *); void (*bus_remove_match)(DBusConnection *, const char *, DBusError *); + int (*bus_request_name)(DBusConnection *, const char *, unsigned int, DBusError *); const char *(*bus_get_unique_name)(DBusConnection *); DBusConnection *(*connection_open_private)(const char *, DBusError *); void (*connection_set_exit_on_disconnect)(DBusConnection *, dbus_bool_t); @@ -65,17 +70,22 @@ typedef struct SDL_DBusContext dbus_bool_t (*connection_read_write_dispatch)(DBusConnection *, int); DBusDispatchStatus (*connection_dispatch)(DBusConnection *); dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *); + dbus_bool_t (*message_is_method_call)(DBusMessage *, const char *, const char *); dbus_bool_t (*message_has_path)(DBusMessage *, const char *); + DBusMessage *(*message_new_error)(DBusMessage *, const char *, const char *); DBusMessage *(*message_new_method_call)(const char *, const char *, const char *, const char *); + DBusMessage *(*message_new_method_return)(DBusMessage *); DBusMessage *(*message_new_signal)(const char *, const char *, const char *); dbus_bool_t (*message_append_args)(DBusMessage *, int, ...); dbus_bool_t (*message_append_args_valist)(DBusMessage *, int, va_list); void (*message_iter_init_append)(DBusMessage *, DBusMessageIter *); dbus_bool_t (*message_iter_open_container)(DBusMessageIter *, int, const char *, DBusMessageIter *); dbus_bool_t (*message_iter_append_basic)(DBusMessageIter *, int, const void *); + dbus_bool_t (*message_iter_append_fixed_array)(DBusMessageIter *, int, const void *, int); dbus_bool_t (*message_iter_close_container)(DBusMessageIter *, DBusMessageIter *); dbus_bool_t (*message_get_args)(DBusMessage *, DBusError *, int, ...); dbus_bool_t (*message_get_args_valist)(DBusMessage *, DBusError *, int, va_list); + void (*message_iter_get_fixed_array)(DBusMessageIter *, void *, int *); dbus_bool_t (*message_iter_init)(DBusMessage *, DBusMessageIter *); dbus_bool_t (*message_iter_next)(DBusMessageIter *); void (*message_iter_get_basic)(DBusMessageIter *, void *); @@ -95,6 +105,39 @@ typedef struct SDL_DBusContext } SDL_DBusContext; +typedef enum SDL_DBusMenuItemType +{ + SDL_DBUS_MENU_ITEM_TYPE_NORMAL, + SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR, + SDL_DBUS_MENU_ITEM_TYPE_CHECKBOX +} SDL_DBusMenuItemType; + +typedef enum SDL_DBusMenuItemFlags +{ + SDL_DBUS_MENU_ITEM_FLAGS_NONE = 0, + SDL_DBUS_MENU_ITEM_FLAGS_DISABLED = 1 << 0, + SDL_DBUS_MENU_ITEM_FLAGS_CHECKED = 1 << 1, + SDL_DBUS_MENU_ITEM_FLAGS_BAR_ITEM = 1 << 2 +} SDL_DBusMenuItemFlags; + +/* This struct is meant to be in an SDL_ListNode just like sub_menu */ +typedef struct SDL_DBusMenuItem +{ + const char *utf8 /* Text */; + SDL_DBusMenuItemType type; + SDL_DBusMenuItemFlags flags; + void *cb_data; + void (*cb)(struct SDL_DBusMenuItem *, void *); + SDL_ListNode *sub_menu; /* NULL if no submenu */ + void *udata; + void *udata2; + + /* Internal use */ + SDL_DBusContext *dbus; + long id; + unsigned long revision; +} SDL_DBusMenuItem; + extern void SDL_DBus_Init(void); extern void SDL_DBus_Quit(void); extern SDL_DBusContext *SDL_DBus_GetContext(void); @@ -119,6 +162,11 @@ extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_ extern int SDL_DBus_CameraPortalRequestAccess(void); +/* Menu functions */ +extern void SDL_DBus_InitMenuItemInternals(SDL_DBusMenuItem *item); +extern const char *SDL_DBus_ExportMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu); +extern void SDL_DBus_UpdateMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu); + #endif // HAVE_DBUS_DBUS_H #endif // SDL_dbus_h_ diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index 3ecaed71b5779..d366dee57f95c 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -1486,7 +1486,7 @@ void SDL_PumpEventMaintenance(void) SDL_UpdateCursorAnimation(); SDL_UpdateTrays(); - + SDL_SendPendingSignalEvents(); // in case we had a signal handler fire, etc. } diff --git a/src/tray/SDL_tray_utils.c b/src/tray/SDL_tray_utils.c index c08c46c7388d4..65c6f635cf6d6 100644 --- a/src/tray/SDL_tray_utils.c +++ b/src/tray/SDL_tray_utils.c @@ -37,7 +37,7 @@ void SDL_RegisterTray(SDL_Tray *tray) void SDL_UnregisterTray(SDL_Tray *tray) { SDL_assert(SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)); - + SDL_SetObjectValid(tray, SDL_OBJECT_TYPE_TRAY, false); --active_trays; @@ -91,3 +91,7 @@ bool SDL_HasActiveTrays(void) { return (active_trays > 0); } + +int SDL_GetActiveTrayCount(void) { + return active_trays; +} diff --git a/src/tray/SDL_tray_utils.h b/src/tray/SDL_tray_utils.h index 76745ef4557da..a631a77bf747d 100644 --- a/src/tray/SDL_tray_utils.h +++ b/src/tray/SDL_tray_utils.h @@ -26,3 +26,4 @@ extern void SDL_RegisterTray(SDL_Tray *tray); extern void SDL_UnregisterTray(SDL_Tray *tray); extern void SDL_CleanupTrays(void); extern bool SDL_HasActiveTrays(void); +extern int SDL_GetActiveTrayCount(void); diff --git a/src/tray/unix/SDL_dbustray.c b/src/tray/unix/SDL_dbustray.c new file mode 100644 index 0000000000000..eae4a17287362 --- /dev/null +++ b/src/tray/unix/SDL_dbustray.c @@ -0,0 +1,940 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" +#include "../../core/linux/SDL_dbus.h" + +#ifdef SDL_USE_LIBDBUS + +#include +#include "../SDL_tray_utils.h" +#include "SDL_unixtray.h" +#include "../../video/SDL_surface_c.h" + +#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" + +/* TODO: Destroy all menus and entires on destroy tray */ + +typedef struct ItemToFree +{ + void *item; + void (*func)(void *); +} ItemToFree; + +typedef struct SDL_TrayDriverDBus +{ + SDL_TrayDriver _parent; + + SDL_DBusContext *dbus; +} SDL_TrayDriverDBus; + +typedef struct SDL_TrayDBus +{ + SDL_Tray _parent; + + DBusConnection *connection; + char *service_name; + + char *tooltip; + SDL_Surface *surface; + + SDL_ListNode *free_list; + + bool break_update; +} SDL_TrayDBus; + +typedef struct SDL_TrayMenuDBus +{ + SDL_TrayMenu _parent; + + SDL_ListNode *menu; + const char *menu_path; +} SDL_TrayMenuDBus; + +typedef struct SDL_TrayEntryDBus +{ + SDL_TrayEntry _parent; + + SDL_DBusMenuItem item; + SDL_TrayMenuDBus *sub_menu; +} SDL_TrayEntryDBus; + +static DBusHandlerResult HandleGetAllProps(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, SDL_TrayDriverDBus *driver, DBusMessage *msg) +{ + SDL_TrayMenuDBus *menu_dbus; + DBusMessageIter iter, dict_iter, entry_iter, variant_iter; + DBusMessageIter struct_iter, array_iter; + DBusMessage *reply; + const char *interface; + const char *key; + const char *value; + const char *empty; + dbus_uint32_t uint32_val; + dbus_bool_t bool_value; + + empty = ""; + driver->dbus->message_iter_init(msg, &iter); + driver->dbus->message_iter_get_basic(&iter, &interface); + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->message_iter_init_append(reply, &iter); + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + + key = "Category"; + value = "ApplicationStatus"; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + + key = "Id"; + driver->dbus->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + driver->dbus->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + driver->dbus->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &tray_dbus->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, "u", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_UINT32, &uint32_val); + driver->dbus->message_iter_close_container(&entry_iter, &variant_iter); + driver->dbus->message_iter_close_container(&dict_iter, &entry_iter); + + 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 { + 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(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult HandleGetProp(SDL_Tray *tray, SDL_TrayDBus *tray_dbus, SDL_TrayDriverDBus *driver, DBusMessage *msg) +{ + SDL_TrayMenuDBus *menu_dbus; + DBusMessageIter iter, variant_iter; + DBusMessage *reply; + const char *interface, *property; + const char *value; + const char *empty; + dbus_bool_t bool_value; + + empty = ""; + driver->dbus->message_iter_init(msg, &iter); + driver->dbus->message_iter_get_basic(&iter, &interface); + driver->dbus->message_iter_next(&iter); + driver->dbus->message_iter_get_basic(&iter, &property); + + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->message_iter_init_append(reply, &iter); + + menu_dbus = (SDL_TrayMenuDBus *)tray->menu; + if (!SDL_strcmp(property, "Category")) { + value = "ApplicationStatus"; + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else if (!SDL_strcmp(property, "Id")) { + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &tray_dbus->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, "u", &variant_iter); + driver->dbus->message_iter_append_basic(&variant_iter, DBUS_TYPE_UINT32, &uint32_val); + driver->dbus->message_iter_close_container(&iter, &variant_iter); + } else { + driver->dbus->message_unref(reply); + reply = driver->dbus->message_new_error(msg, DBUS_ERROR_UNKNOWN_PROPERTY, "Unknown property"); + } + + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult MessageHandler(DBusConnection *connection, DBusMessage *msg, void *user_data) +{ + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + DBusMessage *reply; + + tray = user_data; + driver = (SDL_TrayDriverDBus *)tray->driver; + + if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "Get")) { + return HandleGetProp(tray, (SDL_TrayDBus *)tray, driver, msg); + } else if (driver->dbus->message_is_method_call(msg, "org.freedesktop.DBus.Properties", "GetAll")) { + return HandleGetAllProps(tray, (SDL_TrayDBus *)tray, driver, msg); + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "ContextMenu")) { + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "Activate")) { + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "SecondaryActivate")) { + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "Scroll")) { + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +SDL_Tray *CreateTray(SDL_TrayDriver *driver, SDL_Surface *icon, const char *tooltip) +{ + SDL_TrayDriverDBus *dbus_driver; + SDL_TrayDBus *tray_dbus; + SDL_Tray *tray; + const char *object_path; + 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() + + /* alloc */ + tray_dbus = SDL_malloc(sizeof(SDL_TrayDBus)); + tray = (SDL_Tray *)tray_dbus; + if (!tray_dbus) { + return NULL; + } + + /* fill */ + tray->menu = NULL; + tray->driver = driver; + if (tooltip) { + tray_dbus->tooltip = SDL_strdup(tooltip); + } else { + tray_dbus->tooltip = NULL; + } + tray_dbus->surface = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_ARGB32); + tray_dbus->free_list = NULL; + tray_dbus->break_update = 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 = MessageHandler; + bool_status = dbus_driver->dbus->connection_try_register_object_path(tray_dbus->connection, object_path, &vtable, tray_dbus, &err); + if (dbus_driver->dbus->error_is_set(&err)) { + SDL_SetError("Unable to create tray: %s", err.message); + dbus_driver->dbus->error_free(&err); + CLEANUP2(); + return NULL; + } + if (!bool_status) { + SDL_SetError("Unable to create tray: unable to register object path!"); + CLEANUP2(); + return NULL; + } + + /* register */ + SDL_asprintf(®ister_name, "%s%s", tray_dbus->service_name, SNI_OBJECT_PATH); + if (!SDL_DBus_CallVoidMethodOnConnection(tray_dbus->connection, SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "RegisterStatusNotifierItem", DBUS_TYPE_STRING, &object_path, DBUS_TYPE_INVALID)) { + SDL_free(register_name); + SDL_SetError("Unable to create tray: unable to register status notifier item!"); + CLEANUP2(); + return NULL; + } + SDL_free(register_name); + + return tray; +} + +void DestroyTray(SDL_Tray *tray) +{ + SDL_TrayDBus *tray_dbus; + SDL_TrayDriverDBus *driver; + SDL_ListNode *cursor; + + driver = (SDL_TrayDriverDBus *)tray->driver; + tray_dbus = (SDL_TrayDBus *)tray; + + /* destroy connection */ + driver->dbus->connection_flush(tray_dbus->connection); + tray_dbus->break_update = true; + driver->dbus->connection_close(tray_dbus->connection); + tray_dbus->connection = NULL; + + /* destroy icon and tooltip */ + SDL_free(tray_dbus->tooltip); + SDL_DestroySurface(tray_dbus->surface); + + /* free items to be freed */ + cursor = tray_dbus->free_list; + while (cursor) { + ItemToFree *free_item; + + free_item = cursor->entry; + free_item->func(free_item->item); + SDL_free(free_item); + + cursor = cursor->next; + } + SDL_ListClear(&tray_dbus->free_list); + + /* 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 (tray_dbus->break_update) { + return; + } + + driver->dbus->connection_read_write(tray_dbus->connection, 0); + while (driver->dbus->connection_dispatch(tray_dbus->connection) == DBUS_DISPATCH_DATA_REMAINS) { + if (tray_dbus->break_update) { + break; + } + SDL_DelayNS(SDL_US_TO_NS(10)); + } +} + +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; + + return tray->menu; +} + +SDL_TrayMenu *CreateTraySubmenu(SDL_TrayEntry *entry) +{ + SDL_TrayMenuDBus *menu_dbus; + SDL_TrayMenu *menu; + SDL_TrayEntryDBus *entry_dbus; + + entry_dbus = (SDL_TrayEntryDBus *)entry; + menu_dbus = SDL_malloc(sizeof(SDL_TrayMenuDBus)); + menu = (SDL_TrayMenu *)menu_dbus; + if (!menu_dbus) { + SDL_SetError("Unable to create tray submenu: allocation failure!"); + return NULL; + } + + menu_dbus->menu = NULL; + menu_dbus->menu_path = NULL; + menu->parent_tray = entry->parent->parent_tray; + menu->parent_entry = entry; + entry_dbus->sub_menu = menu_dbus; + + return menu; +} + +SDL_TrayMenu *GetTraySubmenu(SDL_TrayEntry *entry) +{ + SDL_TrayEntryDBus *entry_dbus; + + entry_dbus = (SDL_TrayEntryDBus *)entry; + + return (SDL_TrayMenu *)entry_dbus->sub_menu; +} + +SDL_TrayEntry *InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) +{ + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayMenuDBus *menu_dbus; + SDL_TrayDBus *tray_dbus; + SDL_TrayEntry *entry; + SDL_TrayEntryDBus *entry_dbus; + bool update; + + tray = menu->parent_tray; + menu_dbus = (SDL_TrayMenuDBus *)menu; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + + /* TODD: pos */ + entry_dbus = SDL_malloc(sizeof(SDL_TrayEntryDBus)); + entry = (SDL_TrayEntry *)entry_dbus; + if (!entry_dbus) { + SDL_SetError("Unable to create tray entry: allocation failure!"); + return NULL; + } + + entry->parent = menu; + entry_dbus->item.utf8 = label; + if (!label) { + entry_dbus->item.type = SDL_DBUS_MENU_ITEM_TYPE_SEPERATOR; + } else if (flags & SDL_TRAYENTRY_CHECKBOX) { + entry_dbus->item.type = SDL_DBUS_MENU_ITEM_TYPE_CHECKBOX; + } else { + entry_dbus->item.type = SDL_DBUS_MENU_ITEM_TYPE_NORMAL; + } + entry_dbus->item.flags = SDL_DBUS_MENU_ITEM_FLAGS_NONE; + entry_dbus->item.cb_data = NULL; + entry_dbus->item.cb = NULL; + entry_dbus->item.sub_menu = NULL; + entry_dbus->item.udata = entry_dbus; + entry_dbus->sub_menu = NULL; + SDL_DBus_InitMenuItemInternals(&entry_dbus->item); + + if (menu_dbus->menu) { + update = true; + } else { + update = false; + } + + if (menu->parent_entry) { + update = true; + } + + SDL_ListAppend(&menu_dbus->menu, &entry_dbus->item); + + if (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); + } else { + menu_dbus->menu_path = SDL_DBus_ExportMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); + + if (menu_dbus->menu_path) { + DBusMessage *signal; + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewMenu"); + if (signal) { + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); + } + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged"); + if (signal) { + DBusMessageIter iter, dict, ientry, value; + const char *iface; + const char *prop; + const char *path; + dbus_bool_t bool_val; + + iface = SNI_INTERFACE; + prop = "Menu"; + path = menu_dbus->menu_path; + bool_val = TRUE; + driver->dbus->message_iter_init_append(signal, &iter); + driver->dbus->message_iter_append_basic(&iter, DBUS_TYPE_STRING, &iface); + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + driver->dbus->message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &ientry); + driver->dbus->message_iter_append_basic(&ientry, DBUS_TYPE_STRING, &prop); + driver->dbus->message_iter_open_container(&ientry, DBUS_TYPE_VARIANT, "o", &value); + driver->dbus->message_iter_append_basic(&value, DBUS_TYPE_OBJECT_PATH, &path); + driver->dbus->message_iter_close_container(&ientry, &value); + driver->dbus->message_iter_close_container(&dict, &ientry); + prop = "ItemIsMenu"; + driver->dbus->message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &ientry); + driver->dbus->message_iter_append_basic(&ientry, DBUS_TYPE_STRING, &prop); + driver->dbus->message_iter_open_container(&ientry, DBUS_TYPE_VARIANT, "b", &value); + driver->dbus->message_iter_append_basic(&value, DBUS_TYPE_BOOLEAN, &bool_val); + driver->dbus->message_iter_close_container(&ientry, &value); + driver->dbus->message_iter_close_container(&dict, &ientry); + driver->dbus->message_iter_close_container(&iter, &dict); + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &dict); + driver->dbus->message_iter_close_container(&iter, &dict); + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); + } + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewStatus"); + if (signal) { + const char *status_val; + + status_val = "Active"; + driver->dbus->message_append_args(signal, DBUS_TYPE_STRING, &status_val, DBUS_TYPE_INVALID); + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); + } + } + } + + return entry; +} + +SDL_TrayEntry **GetTrayEntries(SDL_TrayMenu *menu, int *count) { + SDL_TrayEntry **ret; + SDL_Tray *tray; + SDL_TrayDBus *tray_dbus; + SDL_TrayMenuDBus *menu_dbus; + SDL_ListNode *cursor; + ItemToFree *free_item; + int sz; + int i; + + tray = menu->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + menu_dbus = (SDL_TrayMenuDBus *)menu; + + cursor = tray_dbus->free_list; + while (cursor) { + free_item = cursor->entry; + free_item->func(free_item->item); + SDL_free(free_item); + + cursor = cursor->next; + } + SDL_ListClear(&tray_dbus->free_list); + + sz = SDL_ListCountEntries(&menu_dbus->menu); + ret = SDL_calloc(sz + 1, sizeof(SDL_TrayEntry *)); + if (!ret) { + SDL_SetError("Memory allocation failure!"); + return NULL; + } + free_item = SDL_malloc(sizeof(ItemToFree)); + free_item->item = ret; + free_item->func = SDL_free; + SDL_ListAdd(&tray_dbus->free_list, free_item); + + i = 0; + cursor = menu_dbus->menu; + while (cursor) { + SDL_DBusMenuItem *item; + + item = cursor->entry; + ret[i] = item->udata; + cursor = cursor->next; + i++; + } + ret[sz] = NULL; + + *count = sz; + return ret; +} + +void RemoveTrayEntry(SDL_TrayEntry *entry) +{ + SDL_TrayEntryDBus *entry_dbus; + SDL_TrayMenuDBus *menu_dbus; + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + + tray = entry->parent->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + entry_dbus = (SDL_TrayEntryDBus *)entry; + menu_dbus = (SDL_TrayMenuDBus *)entry->parent; + + SDL_ListRemove(&menu_dbus->menu, &entry_dbus->item); + /* TODO: destroy submenu */ + SDL_free(entry); + + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); +} + +void EntryCallback(SDL_DBusMenuItem *item, void *udata) { + SDL_TrayCallback entry_cb; + + entry_cb = item->udata2; + entry_cb(udata, item->udata); +} + +void SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) +{ + SDL_TrayEntryDBus *entry_dbus; + SDL_TrayMenuDBus *menu_dbus; + SDL_Tray *tray; + SDL_TrayDriverDBus *driver; + SDL_TrayDBus *tray_dbus; + + tray = entry->parent->parent_tray; + tray_dbus = (SDL_TrayDBus *)tray; + driver = (SDL_TrayDriverDBus *)tray->driver; + entry_dbus = (SDL_TrayEntryDBus *)entry; + menu_dbus = (SDL_TrayMenuDBus *)entry->parent; + + entry_dbus->item.cb = EntryCallback; + entry_dbus->item.cb_data = userdata; + entry_dbus->item.udata2 = callback; + + SDL_DBus_UpdateMenu(driver->dbus, tray_dbus->connection, menu_dbus->menu); +} + +void DestroyDriver(SDL_TrayDriver *driver) +{ + SDL_DBus_Quit(); + SDL_free(driver); +} + +SDL_TrayDriver *SDL_Tray_CreateDBusDriver(void) +{ + SDL_TrayDriverDBus *dbus_driver; + SDL_TrayDriver *driver; + SDL_DBusContext *ctx; + char **paths; + int count; + int i; + bool sni_supported; + + /* init dbus, get ctx */ + SDL_DBus_Init(); + ctx = SDL_DBus_GetContext(); + if (!ctx) { + return NULL; + } + + /* detect if we have a watcher and a host */ + sni_supported = false; + paths = NULL; + count = 0; + if (SDL_DBus_CallMethod("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames", DBUS_TYPE_INVALID, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &paths, &count, DBUS_TYPE_INVALID)) { + if (paths) { + bool watcher_found; + + watcher_found = false; + for (i = 0; i < count; i++) { + if (!SDL_strcmp(paths[i], SNI_WATCHER_SERVICE)) { + watcher_found = true; + } + } + ctx->free_string_array(paths); + + if (watcher_found) { + dbus_bool_t host_registered; + + host_registered = FALSE; + SDL_DBus_QueryProperty(SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "IsStatusNotifierHostRegistered", DBUS_TYPE_BOOLEAN, &host_registered); + if (host_registered) { + sni_supported = true; + } + } + } + } + if (!sni_supported) { + SDL_SetError("Unable to create tray: no SNI support!"); + SDL_DBus_Quit(); + return NULL; + } + + /* alloc */ + dbus_driver = SDL_malloc(sizeof(SDL_TrayDriverDBus)); + driver = (SDL_TrayDriver *)dbus_driver; + if (!dbus_driver) { + return NULL; + } + + /* fill */ + dbus_driver->dbus = ctx; + driver->name = "dbus"; + driver->count = 0; + driver->CreateTray = CreateTray; + driver->DestroyTray = DestroyTray; + driver->UpdateTray = UpdateTray; + driver->SetTrayIcon = SetTrayIcon; + driver->SetTrayTooltip = SetTrayTooltip; + driver->CreateTrayMenu = CreateTrayMenu; + driver->InsertTrayEntryAt = InsertTrayEntryAt; + driver->CreateTraySubmenu = CreateTraySubmenu; + driver->GetTraySubmenu = GetTraySubmenu; + driver->GetTrayEntries = GetTrayEntries; + driver->RemoveTrayEntry = RemoveTrayEntry; + driver->SetTrayEntryCallback = SetTrayEntryCallback; + driver->DestroyDriver = DestroyDriver; + + return driver; +} + +#endif diff --git a/src/tray/unix/SDL_tray.c b/src/tray/unix/SDL_tray.c deleted file mode 100644 index faf3545b93894..0000000000000 --- a/src/tray/unix/SDL_tray.c +++ /dev/null @@ -1,766 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ - -#include "SDL_internal.h" - -#include "../SDL_tray_utils.h" -#include "../../video/SDL_stb_c.h" - -#include -#include - -/* getpid() */ -#include - -/* APPINDICATOR_HEADER is not exposed as a build setting, but the code has been - written nevertheless to make future maintenance easier. */ -#ifdef APPINDICATOR_HEADER -#include APPINDICATOR_HEADER -#else -#include "../../core/unix/SDL_gtk.h" - -/* ------------------------------------------------------------------------- */ -/* BEGIN THIRD-PARTY HEADER CONTENT */ -/* ------------------------------------------------------------------------- */ -/* AppIndicator */ - -typedef enum { - APP_INDICATOR_CATEGORY_APPLICATION_STATUS, - APP_INDICATOR_CATEGORY_COMMUNICATIONS, - APP_INDICATOR_CATEGORY_SYSTEM_SERVICES, - APP_INDICATOR_CATEGORY_HARDWARE, - APP_INDICATOR_CATEGORY_OTHER -} AppIndicatorCategory; - -typedef enum { - APP_INDICATOR_STATUS_PASSIVE, - APP_INDICATOR_STATUS_ACTIVE, - APP_INDICATOR_STATUS_ATTENTION -} AppIndicatorStatus; - -typedef struct _AppIndicator AppIndicator; - -static AppIndicator *(*app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category); -static void (*app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status); -static void (*app_indicator_set_icon)(AppIndicator *self, const gchar *icon_name); -static void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu); - -/* ------------------------------------------------------------------------- */ -/* END THIRD-PARTY HEADER CONTENT */ -/* ------------------------------------------------------------------------- */ -#endif - -static void *libappindicator = NULL; - -static void quit_appindicator(void) -{ - if (libappindicator) { - dlclose(libappindicator); - libappindicator = NULL; - } -} - -const char *appindicator_names[] = { -#ifdef SDL_PLATFORM_OPENBSD - "libayatana-appindicator3.so", - "libappindicator3.so", -#else - "libayatana-appindicator3.so.1", - "libappindicator3.so.1", -#endif - NULL -}; - -static void *find_lib(const char **names) -{ - const char **name_ptr = names; - void *handle = NULL; - - do { - handle = dlopen(*name_ptr, RTLD_LAZY); - } while (*++name_ptr && !handle); - - return handle; -} - -static bool init_appindicator(void) -{ - if (libappindicator) { - return true; - } - - libappindicator = find_lib(appindicator_names); - - if (!libappindicator) { - quit_appindicator(); - return SDL_SetError("Could not load AppIndicator libraries"); - } - - app_indicator_new = dlsym(libappindicator, "app_indicator_new"); - app_indicator_set_status = dlsym(libappindicator, "app_indicator_set_status"); - app_indicator_set_icon = dlsym(libappindicator, "app_indicator_set_icon"); - app_indicator_set_menu = dlsym(libappindicator, "app_indicator_set_menu"); - - if (!app_indicator_new || - !app_indicator_set_status || - !app_indicator_set_icon || - !app_indicator_set_menu) { - quit_appindicator(); - return SDL_SetError("Could not load AppIndicator functions"); - } - - return true; -} - -struct SDL_TrayMenu { - GtkMenuShell *menu; - - int nEntries; - SDL_TrayEntry **entries; - - SDL_Tray *parent_tray; - SDL_TrayEntry *parent_entry; -}; - -struct SDL_TrayEntry { - SDL_TrayMenu *parent; - GtkWidget *item; - - /* Checkboxes are "activated" when programmatically checked/unchecked; this - is a workaround. */ - bool ignore_signal; - - SDL_TrayEntryFlags flags; - SDL_TrayCallback callback; - void *userdata; - SDL_TrayMenu *submenu; -}; - -struct SDL_Tray { - AppIndicator *indicator; - SDL_TrayMenu *menu; - char *icon_dir; - char *icon_path; - - GtkMenuShell *menu_cached; -}; - -static void call_callback(GtkMenuItem *item, gpointer ptr) -{ - SDL_TrayEntry *entry = ptr; - - /* Not needed with AppIndicator, may be needed with other frameworks */ - /* if (entry->flags & SDL_TRAYENTRY_CHECKBOX) { - SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry)); - } */ - - if (entry->ignore_signal) { - return; - } - - if (entry->callback) { - entry->callback(entry->userdata, entry); - } -} - -static bool new_tmp_filename(SDL_Tray *tray) -{ - static int count = 0; - - int would_have_written = SDL_asprintf(&tray->icon_path, "%s/%d.png", tray->icon_dir, count++); - - if (would_have_written >= 0) { - return true; - } - - tray->icon_path = NULL; - SDL_SetError("Failed to format new temporary filename"); - return false; -} - -static const char *get_appindicator_id(void) -{ - static int count = 0; - static char buffer[256]; - - int would_have_written = SDL_snprintf(buffer, sizeof(buffer), "sdl-appindicator-%d-%d", getpid(), count++); - - if (would_have_written <= 0 || would_have_written >= sizeof(buffer) - 1) { - SDL_SetError("Couldn't fit %d bytes in buffer of size %d", would_have_written, (int) sizeof(buffer)); - return NULL; - } - - return buffer; -} - -static void DestroySDLMenu(SDL_TrayMenu *menu) -{ - for (int i = 0; i < menu->nEntries; i++) { - if (menu->entries[i] && menu->entries[i]->submenu) { - DestroySDLMenu(menu->entries[i]->submenu); - } - SDL_free(menu->entries[i]); - } - - if (menu->menu) { - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - gtk->g.object_unref(menu->menu); - SDL_Gtk_ExitContext(gtk); - } - } - - SDL_free(menu->entries); - SDL_free(menu); -} - -void SDL_UpdateTrays(void) -{ - if (SDL_HasActiveTrays()) { - SDL_UpdateGtk(); - } -} - -SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) -{ - if (!SDL_IsMainThread()) { - SDL_SetError("This function should be called on the main thread"); - return NULL; - } - - if (!init_appindicator()) { - return NULL; - } - - SDL_Tray *tray = NULL; - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (!gtk) { - goto tray_error; - } - - tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray)); - if (!tray) { - goto tray_error; - } - - const gchar *cache_dir = gtk->g.get_user_cache_dir(); - if (!cache_dir) { - SDL_SetError("Cannot get user cache directory: %s", strerror(errno)); - goto tray_error; - } - - char *sdl_dir; - SDL_asprintf(&sdl_dir, "%s/SDL", cache_dir); - if (!SDL_GetPathInfo(sdl_dir, NULL)) { - if (!SDL_CreateDirectory(sdl_dir)) { - SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno)); - goto sdl_dir_error; - } - } - - /* On success, g_mkdtemp edits its argument in-place to replace the Xs - * with a random directory name, which it creates safely and atomically. - * On failure, it sets errno. */ - SDL_asprintf(&tray->icon_dir, "%s/tray-XXXXXX", sdl_dir); - if (!gtk->g.mkdtemp(tray->icon_dir)) { - SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno)); - goto icon_dir_error; - } - - if (icon) { - if (!new_tmp_filename(tray)) { - goto icon_dir_error; - } - - SDL_SavePNG(icon, tray->icon_path); - } else { - // allocate a dummy icon path - SDL_asprintf(&tray->icon_path, " "); - } - - tray->indicator = app_indicator_new(get_appindicator_id(), tray->icon_path, - APP_INDICATOR_CATEGORY_APPLICATION_STATUS); - - app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE); - - // The tray icon isn't shown before a menu is created; create one early. - tray->menu_cached = (GtkMenuShell *)gtk->g.object_ref_sink(gtk->gtk.menu_new()); - app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu_cached)); - - SDL_RegisterTray(tray); - SDL_Gtk_ExitContext(gtk); - SDL_free(sdl_dir); - - return tray; - -icon_dir_error: - SDL_free(tray->icon_dir); - -sdl_dir_error: - SDL_free(sdl_dir); - -tray_error: - SDL_free(tray); - - if (gtk) { - SDL_Gtk_ExitContext(gtk); - } - - return NULL; -} - -void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) -{ - if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { - return; - } - - if (tray->icon_path) { - SDL_RemovePath(tray->icon_path); - SDL_free(tray->icon_path); - tray->icon_path = NULL; - } - - /* AppIndicator caches the icon files; always change filename to avoid caching */ - - if (icon && new_tmp_filename(tray)) { - SDL_SavePNG(icon, tray->icon_path); - app_indicator_set_icon(tray->indicator, tray->icon_path); - } else { - SDL_free(tray->icon_path); - tray->icon_path = NULL; - app_indicator_set_icon(tray->indicator, NULL); - } -} - -void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip) -{ - /* AppIndicator provides no tooltip support. */ -} - -SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray) -{ - CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { - SDL_InvalidParamError("tray"); - return NULL; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (!gtk) { - return NULL; - } - - tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu)); - if (!tray->menu) { - SDL_Gtk_ExitContext(gtk); - return NULL; - } - - tray->menu->menu = gtk->g.object_ref(tray->menu_cached); - tray->menu->parent_tray = tray; - tray->menu->parent_entry = NULL; - tray->menu->nEntries = 0; - tray->menu->entries = NULL; - - SDL_Gtk_ExitContext(gtk); - - return tray->menu; -} - -SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray) -{ - CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { - SDL_InvalidParamError("tray"); - return NULL; - } - - return tray->menu; -} - -SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) -{ - CHECK_PARAM(!entry) { - SDL_InvalidParamError("entry"); - return NULL; - } - - if (entry->submenu) { - SDL_SetError("Tray entry submenu already exists"); - return NULL; - } - - if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) { - SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU"); - return NULL; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (!gtk) { - return NULL; - } - - entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu)); - if (!entry->submenu) { - SDL_Gtk_ExitContext(gtk); - return NULL; - } - - entry->submenu->menu = gtk->g.object_ref_sink(gtk->gtk.menu_new()); - entry->submenu->parent_tray = NULL; - entry->submenu->parent_entry = entry; - entry->submenu->nEntries = 0; - entry->submenu->entries = NULL; - - gtk->gtk.menu_item_set_submenu(GTK_MENU_ITEM(entry->item), GTK_WIDGET(entry->submenu->menu)); - - SDL_Gtk_ExitContext(gtk); - - return entry->submenu; -} - -SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry) -{ - CHECK_PARAM(!entry) { - SDL_InvalidParamError("entry"); - return NULL; - } - - return entry->submenu; -} - -const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count) -{ - CHECK_PARAM(!menu) { - SDL_InvalidParamError("menu"); - return NULL; - } - - if (count) { - *count = menu->nEntries; - } - return (const SDL_TrayEntry **)menu->entries; -} - -void SDL_RemoveTrayEntry(SDL_TrayEntry *entry) -{ - if (!entry) { - return; - } - - SDL_TrayMenu *menu = entry->parent; - - bool found = false; - for (int i = 0; i < menu->nEntries - 1; i++) { - if (menu->entries[i] == entry) { - found = true; - } - - if (found) { - menu->entries[i] = menu->entries[i + 1]; - } - } - - if (entry->submenu) { - DestroySDLMenu(entry->submenu); - } - - menu->nEntries--; - SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries)); - - /* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */ - if (new_entries) { - menu->entries = new_entries; - menu->entries[menu->nEntries] = NULL; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - gtk->gtk.widget_destroy(entry->item); - SDL_Gtk_ExitContext(gtk); - } - SDL_free(entry); -} - -SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) -{ - CHECK_PARAM(!menu) { - SDL_InvalidParamError("menu"); - return NULL; - } - - CHECK_PARAM(pos < -1 || pos > menu->nEntries) { - SDL_InvalidParamError("pos"); - return NULL; - } - - if (pos == -1) { - pos = menu->nEntries; - } - - SDL_TrayEntry *entry = NULL; - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (!gtk) { - goto error; - } - - entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry)); - if (!entry) { - goto error; - } - - entry->parent = menu; - entry->item = NULL; - entry->ignore_signal = false; - entry->flags = flags; - entry->callback = NULL; - entry->userdata = NULL; - entry->submenu = NULL; - - if (label == NULL) { - entry->item = gtk->gtk.separator_menu_item_new(); - } else if (flags & SDL_TRAYENTRY_CHECKBOX) { - entry->item = gtk->gtk.check_menu_item_new_with_label(label); - gboolean active = ((flags & SDL_TRAYENTRY_CHECKED) != 0); - gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), active); - } else { - entry->item = gtk->gtk.menu_item_new_with_label(label); - } - - gboolean sensitive = ((flags & SDL_TRAYENTRY_DISABLED) == 0); - gtk->gtk.widget_set_sensitive(entry->item, sensitive); - - SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries)); - - if (!new_entries) { - goto error; - } - - menu->entries = new_entries; - menu->nEntries++; - - for (int i = menu->nEntries - 1; i > pos; i--) { - menu->entries[i] = menu->entries[i - 1]; - } - - new_entries[pos] = entry; - new_entries[menu->nEntries] = NULL; - - gtk->gtk.widget_show(entry->item); - gtk->gtk.menu_shell_insert(menu->menu, entry->item, (pos == menu->nEntries) ? -1 : pos); - - gtk->g.signal_connect(entry->item, "activate", call_callback, entry); - - SDL_Gtk_ExitContext(gtk); - - return entry; - -error: - if (entry) { - SDL_free(entry); - } - - if (gtk) { - SDL_Gtk_ExitContext(gtk); - } - - return NULL; -} - -void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) -{ - if (!entry) { - return; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - gtk->gtk.menu_item_set_label(GTK_MENU_ITEM(entry->item), label); - SDL_Gtk_ExitContext(gtk); - } -} - -const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry) -{ - CHECK_PARAM(!entry) { - SDL_InvalidParamError("entry"); - return NULL; - } - - const char *label = NULL; - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - label = gtk->gtk.menu_item_get_label(GTK_MENU_ITEM(entry->item)); - SDL_Gtk_ExitContext(gtk); - } - - return label; -} - -void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked) -{ - if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { - return; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - entry->ignore_signal = true; - gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), checked); - entry->ignore_signal = false; - SDL_Gtk_ExitContext(gtk); - } -} - -bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry) -{ - if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { - return false; - } - - bool checked = false; - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - checked = gtk->gtk.check_menu_item_get_active(GTK_CHECK_MENU_ITEM(entry->item)); - SDL_Gtk_ExitContext(gtk); - } - - return checked; -} - -void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled) -{ - if (!entry) { - return; - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - gtk->gtk.widget_set_sensitive(entry->item, enabled); - SDL_Gtk_ExitContext(gtk); - } -} - -bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry) -{ - if (!entry) { - return false; - } - - bool enabled = false; - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - enabled = gtk->gtk.widget_get_sensitive(entry->item); - SDL_Gtk_ExitContext(gtk); - } - - return enabled; -} - -void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) -{ - if (!entry) { - return; - } - - entry->callback = callback; - entry->userdata = userdata; -} - -void SDL_ClickTrayEntry(SDL_TrayEntry *entry) -{ - if (!entry) { - return; - } - - if (entry->flags & SDL_TRAYENTRY_CHECKBOX) { - SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry)); - } - - if (entry->callback) { - entry->callback(entry->userdata, entry); - } -} - -SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) -{ - CHECK_PARAM(!entry) { - SDL_InvalidParamError("entry"); - return NULL; - } - - return entry->parent; -} - -SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu) -{ - return menu->parent_entry; -} - -SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu) -{ - CHECK_PARAM(!menu) { - SDL_InvalidParamError("menu"); - return NULL; - } - - return menu->parent_tray; -} - -void SDL_DestroyTray(SDL_Tray *tray) -{ - if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { - return; - } - - SDL_UnregisterTray(tray); - - if (tray->menu) { - DestroySDLMenu(tray->menu); - } - - if (tray->icon_path) { - SDL_RemovePath(tray->icon_path); - SDL_free(tray->icon_path); - } - - if (tray->icon_dir) { - SDL_RemovePath(tray->icon_dir); - SDL_free(tray->icon_dir); - } - - SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); - if (gtk) { - if (tray->menu_cached) { - gtk->g.object_unref(tray->menu_cached); - } - - if (tray->indicator) { - gtk->g.object_unref(tray->indicator); - } - - SDL_Gtk_ExitContext(gtk); - } - - SDL_free(tray); -} diff --git a/src/tray/unix/SDL_unixtray.c b/src/tray/unix/SDL_unixtray.c new file mode 100644 index 0000000000000..fc3685059116b --- /dev/null +++ b/src/tray/unix/SDL_unixtray.c @@ -0,0 +1,280 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#include "../../core/linux/SDL_dbus.h" +#include "../SDL_tray_utils.h" +#include "SDL_unixtray.h" + +static SDL_TrayDriver *driver = NULL; + +void SDL_UpdateTrays(void) +{ + SDL_Tray **trays; + int active_trays; + int count; + int i; + + active_trays = SDL_GetActiveTrayCount(); + if (!active_trays) { + return; + } + + trays = SDL_calloc(active_trays, sizeof(SDL_Tray *)); + if (!trays) { + return; + } + + count = SDL_GetObjects(SDL_OBJECT_TYPE_TRAY, (void **)trays, active_trays); + SDL_assert(count == active_trays); + for (i = 0; i < count; i++) { + if (trays[i]) { + if (SDL_ObjectValid(trays[i], SDL_OBJECT_TYPE_TRAY)) { + trays[i]->driver->UpdateTray(trays[i]); + } + } + } + + SDL_free(trays); +} + +SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) +{ + SDL_Tray *tray; + + if (!driver) { +#ifdef SDL_USE_LIBDBUS + driver = SDL_Tray_CreateDBusDriver(); +#endif + } + + if (driver) { + tray = driver->CreateTray(driver, icon, tooltip); + if (tray) { + SDL_RegisterTray(tray); + } + } else { + tray = NULL; + } + + return tray; +} + +void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) +{ + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { + SDL_InvalidParamError("tray"); + return; + } + + tray->driver->SetTrayIcon(tray, icon); +} + +void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip) +{ + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { + SDL_InvalidParamError("tray"); + return; + } + + tray->driver->SetTrayTooltip(tray, tooltip); +} + +SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray) +{ + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { + SDL_InvalidParamError("tray"); + return NULL; + } + + return tray->driver->CreateTrayMenu(tray); +} + +SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray) +{ + CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) + { + SDL_InvalidParamError("tray"); + return NULL; + } + + return tray->menu; +} + +SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) +{ + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return NULL; + } + + return entry->parent->parent_tray->driver->CreateTraySubmenu(entry); +} + +SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry) +{ + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return NULL; + } + + return entry->parent->parent_tray->driver->GetTraySubmenu(entry); +} + +const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count) +{ + CHECK_PARAM(!menu) + { + SDL_InvalidParamError("menu"); + return NULL; + } + + CHECK_PARAM(!count) + { + SDL_InvalidParamError("count"); + return NULL; + } + + return (const SDL_TrayEntry **)menu->parent_tray->driver->GetTrayEntries(menu, count); +} + +void SDL_RemoveTrayEntry(SDL_TrayEntry *entry) +{ + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return; + } + + entry->parent->parent_tray->driver->RemoveTrayEntry(entry); +} + +SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) +{ + CHECK_PARAM(!menu) + { + SDL_InvalidParamError("menu"); + return NULL; + } + + return menu->parent_tray->driver->InsertTrayEntryAt(menu, pos, label, flags); +} + +void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) +{ +} + +const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry) +{ + SDL_InvalidParamError("entry"); + return NULL; +} + +void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked) +{ +} + +bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry) +{ + return SDL_InvalidParamError("entry"); +} + +void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled) +{ +} + +bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry) +{ + return SDL_InvalidParamError("entry"); +} + +void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) +{ + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return; + } + + CHECK_PARAM(!callback) + { + SDL_InvalidParamError("callback"); + return; + } + + entry->parent->parent_tray->driver->SetTrayEntryCallback(entry, callback, userdata); +} + +void SDL_ClickTrayEntry(SDL_TrayEntry *entry) +{ +} + +SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) +{ + CHECK_PARAM(!entry) + { + SDL_InvalidParamError("entry"); + return NULL; + } + + return entry->parent; +} + +SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu) +{ + CHECK_PARAM(!menu) + { + SDL_InvalidParamError("menu"); + return NULL; + } + + return menu->parent_entry; +} + +SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu) +{ + CHECK_PARAM(!menu) + { + SDL_InvalidParamError("menu"); + return NULL; + } + + return menu->parent_tray; +} + +void SDL_DestroyTray(SDL_Tray *tray) +{ + if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { + return; + } + + tray->driver->DestroyTray(tray); + SDL_UnregisterTray(tray); + if (!SDL_GetActiveTrayCount()) { + driver->DestroyDriver(driver); + } +} diff --git a/src/tray/unix/SDL_unixtray.h b/src/tray/unix/SDL_unixtray.h new file mode 100644 index 0000000000000..e59873673442b --- /dev/null +++ b/src/tray/unix/SDL_unixtray.h @@ -0,0 +1,73 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_unixtray_h_ +#define SDL_unixtray_h_ + +#include "../../core/linux/SDL_dbus.h" +#include "../SDL_tray_utils.h" + +typedef struct SDL_TrayDriver +{ + const char *name; + + unsigned int count; + + SDL_Tray *(*CreateTray)(struct SDL_TrayDriver *, SDL_Surface *, const char *); + void (*DestroyTray)(SDL_Tray *); + void (*UpdateTray)(SDL_Tray *); + void (*SetTrayIcon)(SDL_Tray *, SDL_Surface *); + void (*SetTrayTooltip)(SDL_Tray *, const char *); + SDL_TrayMenu *(*CreateTrayMenu)(SDL_Tray *); + SDL_TrayEntry *(*InsertTrayEntryAt)(SDL_TrayMenu *, int, const char *, SDL_TrayEntryFlags); + SDL_TrayMenu *(*CreateTraySubmenu)(SDL_TrayEntry *); + SDL_TrayMenu *(*GetTraySubmenu)(SDL_TrayEntry *); + SDL_TrayEntry **(*GetTrayEntries)(SDL_TrayMenu *, int *); + void (*RemoveTrayEntry)(SDL_TrayEntry *); + void (*SetTrayEntryCallback)(SDL_TrayEntry *, SDL_TrayCallback, void *); + + void (*DestroyDriver)(struct SDL_TrayDriver *); +} SDL_TrayDriver; + +struct SDL_TrayMenu +{ + SDL_Tray *parent_tray; + SDL_TrayEntry *parent_entry; +}; + +struct SDL_TrayEntry +{ + SDL_TrayMenu *parent; +}; + +struct SDL_Tray +{ + SDL_TrayDriver *driver; + + SDL_TrayMenu *menu; +}; + +#ifdef SDL_USE_LIBDBUS +extern SDL_TrayDriver *SDL_Tray_CreateDBusDriver(void); +#endif + +#endif // SDL_unixtray_h_ diff --git a/src/video/x11/SDL_x11toolkit.h b/src/video/x11/SDL_x11toolkit.h index 0e882b32a928a..f9a9a20cbeb03 100644 --- a/src/video/x11/SDL_x11toolkit.h +++ b/src/video/x11/SDL_x11toolkit.h @@ -27,7 +27,6 @@ #include "SDL_x11video.h" #include "SDL_x11dyn.h" #include "SDL_x11settings.h" -#include "SDL_x11toolkit.h" #include "xsettings-client.h" #ifdef HAVE_FRIBIDI_H #include "../../core/unix/SDL_fribidi.h" diff --git a/test/testtray.c b/test/testtray.c index e4c3fd02bf646..e4ad8672acc5b 100644 --- a/test/testtray.c +++ b/test/testtray.c @@ -553,11 +553,12 @@ int main(int argc, char **argv) SDL_DestroySurface(icon); SDL_DestroySurface(icon2); - + + /* Uncomment the goto below before merging! */ #define CHECK(name) \ if (!name) { \ SDL_Log("Couldn't create " #name ": %s", SDL_GetError()); \ - goto clean_all; \ + /* goto clean_all; */ \ } SDL_TrayMenu *menu = SDL_CreateTrayMenu(tray);