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..4aebc509cec77 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,25 @@ 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" + +static const char *menu_introspect= "\r\n\r\n\r\n \r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\tName\r\n\t\t\tType\r\n\t\t\tDescription\r\n\t\t\tDefault Value\r\n\t\t\r\n\t\t\r\n\t\t\ttype\r\n\t\t\tString\r\n\t\t\tCan be one of:\r\n\t\t\t- \"standard\": an item which can be clicked to trigger an action or\r\n\t\t\t show another menu\r\n\t\t\t- \"separator\": a separator\r\n\r\n\t\t\tVendor specific types can be added by prefixing them with\r\n\t\t\t\"x--\".\r\n\t\t\t\r\n\t\t\t\"standard\"\r\n\t\t\r\n\t\t\r\n\t\t\tlabel\r\n\t\t\tstring\r\n\t\t\tText of the item, except that:\r\n\t\t\t-# two consecutive underscore characters \"__\" are displayed as a\r\n\t\t\tsingle underscore,\r\n\t\t\t-# any remaining underscore characters are not displayed at all,\r\n\t\t\t-# the first of those remaining underscore characters (unless it is\r\n\t\t\tthe last character in the string) indicates that the following\r\n\t\t\tcharacter is the access key.\r\n\t\t\t\r\n\t\t\t\"\"\r\n\t\t\r\n\t\t\r\n\t\t\tenabled\r\n\t\t\tboolean\r\n\t\t\tWhether the item can be activated or not.\r\n\t\t\ttrue\r\n\t\t\r\n\t\t\r\n\t\t\tvisible\r\n\t\t\tboolean\r\n\t\t\tTrue if the item is visible in the menu.\r\n\t\t\ttrue\r\n\t\t\r\n\t\t\r\n\t\t\ticon-name\r\n\t\t\tstring\r\n\t\t\tIcon name of the item, following the freedesktop.org icon spec.\r\n\t\t\t\"\"\r\n\t\t\r\n\t\t\r\n\t\t\ticon-data\r\n\t\t\tbinary\r\n\t\t\tPNG data of the icon.\r\n\t\t\tEmpty\r\n\t\t\r\n\t\t\r\n\t\t\tshortcut\r\n\t\t\tarray of arrays of strings\r\n\t\t\tThe shortcut of the item. Each array represents the key press\r\n\t\t\tin the list of keypresses. Each list of strings contains a list of\r\n\t\t\tmodifiers and then the key that is used. The modifier strings\r\n\t\t\tallowed are: \"Control\", \"Alt\", \"Shift\" and \"Super\".\r\n\r\n\t\t\t- A simple shortcut like Ctrl+S is represented as:\r\n\t\t\t [[\"Control\", \"S\"]]\r\n\t\t\t- A complex shortcut like Ctrl+Q, Alt+X is represented as:\r\n\t\t\t [[\"Control\", \"Q\"], [\"Alt\", \"X\"]]\r\n\t\t\tEmpty\r\n\t\t\r\n\t\t\r\n\t\t\ttoggle-type\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\tIf the item can be toggled, this property should be set to:\r\n\t\t\t- \"checkmark\": Item is an independent togglable item\r\n\t\t\t- \"radio\": Item is part of a group where only one item can be\r\n\t\t\t toggled at a time\r\n\t\t\t- \"\": Item cannot be toggled\r\n\t\t\t\r\n\t\t\t\"\"\r\n\t\t\r\n\t\t\r\n\t\t\ttoggle-state\r\n\t\t\tint\r\n\t\t\t\r\n\t\t\tDescribe the current state of a \"togglable\" item. Can be one of:\r\n\t\t\t- 0 = off\r\n\t\t\t- 1 = on\r\n\t\t\t- anything else = indeterminate\r\n\r\n\t\t\tNote:\r\n\t\t\tThe implementation does not itself handle ensuring that only one\r\n\t\t\titem in a radio group is set to \"on\", or that a group does not have\r\n\t\t\t\"on\" and \"indeterminate\" items simultaneously; maintaining this\r\n\t\t\tpolicy is up to the toolkit wrappers.\r\n\t\t\t\r\n\t\t\t-1\r\n\t\t\r\n\t\t\r\n\t\t\tchildren-display\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\tIf the menu item has children this property should be set to\r\n\t\t\t\"submenu\".\r\n\t\t\t\r\n\t\t\t\"\"\r\n\t\t\r\n\t\t\r\n\r\n\t\tVendor specific properties can be added by prefixing them with\r\n\t\t\"x--\".\r\n\t\t]]>\r\n\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\tProvides the version of the DBusmenu API that this API is\r\n\t\t\timplementing.\r\n\t\t\t\r\n\t\t\r\n\r\n \r\n \r\n Tells if the menus are in a normal state or they believe that they\r\n could use some attention. Cases for showing them would be if help\r\n were referring to them or they accessors were being highlighted.\r\n This property can have two values: \"normal\" in almost all cases and\r\n \"notice\" when they should have a higher priority to be shown.\r\n \r\n \r\n\r\n\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t Provides the layout and propertiers that are attached to the entries\r\n\t\t\t that are in the layout. It only gives the items that are children\r\n\t\t\t of the item that is specified in @a parentId. It will return all of the\r\n\t\t\t properties or specific ones depending of the value in @a propertyNames.\r\n\r\n\t\t\t The format is recursive, where the second \'v\' is in the same format\r\n\t\t\t as the original \'a(ia{sv}av)\'. Its content depends on the value\r\n\t\t\t of @a recursionDepth.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe ID of the parent node for the layout. For\r\n\t\t\t\tgrabbing the layout from the root node use zero.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t The amount of levels of recursion to use. This affects the\r\n\t\t\t\t content of the second variant array.\r\n\t\t\t\t - -1: deliver all the items under the @a parentId.\r\n\t\t\t\t - 0: no recursion, the array will be empty.\r\n\t\t\t\t - n: array will contains items up to \'n\' level depth.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tThe list of item properties we are\r\n\t\t\t\t\tinterested in. If there are no entries in the list all of\r\n\t\t\t\t\tthe properties will be sent.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe revision number of the layout. For matching\r\n\t\t\t\twith layoutUpdated signals.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe layout, as a recursive structure.\r\n\t\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\tReturns the list of items which are children of @a parentId.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tA list of ids that we should be finding the properties\r\n\t\t\t\t\ton. If the list is empty, all menu items should be sent.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tThe list of item properties we are\r\n\t\t\t\t\tinterested in. If there are no entries in the list all of\r\n\t\t\t\t\tthe properties will be sent.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tAn array of property values.\r\n\t\t\t\t\tAn item in this area is represented as a struct following\r\n\t\t\t\t\tthis format:\r\n\t\t\t\t\t@li id unsigned the item id\r\n\t\t\t\t\t@li properties map(string => variant) the requested item properties\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\t Get a signal property on a single item. This is not useful if you\'re\r\n\t\t\t going to implement this interface, it should only be used if you\'re\r\n\t\t\t debugging via a commandline tool.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tthe id of the item which received the event\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tthe name of the property to get\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tthe value of the property\r\n\t\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\t-\"\r\n\t\t\t]]>\r\n\t\t\t\r\n\t\t\t\tthe id of the item which received the event\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tthe type of event\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tevent-specific data\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe time that the event occured if available or the time the message was sent if not\r\n\t\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\tThis is called by the applet to notify the application that it is about\r\n\t\t\tto show the menu under the specified item.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\tWhich menu item represents the parent of the item about to be shown.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\tWhether this AboutToShow event should result in the menu being updated.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\tTriggered when there are lots of property updates across many items\r\n\t\t\tso they all get grouped into a single dbus message. The format is\r\n\t\t\tthe ID of the item with a hashtable of names and values for those\r\n\t\t\tproperties.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\tTriggered by the application to notify display of a layout update, up to\r\n\t\t\trevision\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe revision of the layout that we\'re currently on\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\tIf the layout update is only of a subtree, this is the\r\n\t\t\t\tparent item for the entries that have changed. It is zero if\r\n\t\t\t\tthe whole layout should be considered invalid.\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\t\r\n\t\t\t The server is requesting that all clients displaying this\r\n\t\t\t menu open it to the user. This would be for things like\r\n\t\t\t hotkeys that when the user presses them the menu should\r\n\t\t\t open and display itself to the user.\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tID of the menu that should be activated\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\tThe time that the event occured\r\n\t\t\t\r\n\t\t\r\n\r\n\r\n\r\n\t\r\n"; + SDL_ELF_NOTE_DLOPEN( "core-libdbus", "Support for D-Bus IPC", 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 +58,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 +450,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 +633,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 +677,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 +842,757 @@ 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; + } + } else if (ctx->message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect")) { + DBusMessage *reply; + + reply = ctx->message_new_method_return(msg); + ctx->message_append_args(reply, DBUS_TYPE_STRING, &menu_introspect, DBUS_TYPE_INVALID); + ctx->connection_send(conn, reply, NULL); + ctx->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +const char *SDL_DBus_ExportMenu(SDL_DBusContext *ctx, DBusConnection *conn, SDL_ListNode *menu) +{ + DBusObjectPathVTable vtable; + 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..c169f90c63cdb --- /dev/null +++ b/src/tray/unix/SDL_dbustray.c @@ -0,0 +1,938 @@ +/* + 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" +static const char *sni_introspect = "\r\n\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n\r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n\r\n \r\n \r\n \r\n\r\n\r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n\r\n\r\n\r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n \r\n \r\n\r\n \r\n"; + +/* TODO: Destroy all menus and entires on destroy tray */ + +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, "org.freedesktop.DBus.Introspectable", "Introspect")) { + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->message_append_args(reply, DBUS_TYPE_STRING, &sni_introspect, DBUS_TYPE_INVALID); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "ContextMenu")) { + printf("ContextMenu: %p\n", tray); + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (driver->dbus->message_is_method_call(msg, SNI_INTERFACE, "Activate")) { + printf("Activate: %p\n", tray); + reply = driver->dbus->message_new_method_return(msg); + driver->dbus->connection_send(driver->dbus->session_conn, reply, NULL); + driver->dbus->message_unref(reply); + 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 */ + register_name = tray_dbus->service_name; + if (!SDL_DBus_CallVoidMethodOnConnection(tray_dbus->connection, SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, SNI_WATCHER_INTERFACE, "RegisterStatusNotifierItem", DBUS_TYPE_STRING, ®ister_name, DBUS_TYPE_INVALID)) { + SDL_SetError("Unable to create tray: unable to register status notifier item!"); + CLEANUP2(); + return NULL; + } + + 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; + + driver->dbus->connection_flush(tray_dbus->connection); + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged"); + if (signal) { + DBusMessageIter iter, dict, ientry, value; + const char *iface; + const char *prop; + const char *path; + dbus_bool_t bool_val; + + iface = SNI_INTERFACE; + prop = "Menu"; + path = menu_dbus->menu_path; + bool_val = TRUE; + driver->dbus->message_iter_init_append(signal, &iter); + driver->dbus->message_iter_append_basic(&iter, DBUS_TYPE_STRING, &iface); + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + driver->dbus->message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &ientry); + driver->dbus->message_iter_append_basic(&ientry, DBUS_TYPE_STRING, &prop); + driver->dbus->message_iter_open_container(&ientry, DBUS_TYPE_VARIANT, "o", &value); + driver->dbus->message_iter_append_basic(&value, DBUS_TYPE_OBJECT_PATH, &path); + driver->dbus->message_iter_close_container(&ientry, &value); + driver->dbus->message_iter_close_container(&dict, &ientry); + prop = "ItemIsMenu"; + driver->dbus->message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &ientry); + driver->dbus->message_iter_append_basic(&ientry, DBUS_TYPE_STRING, &prop); + driver->dbus->message_iter_open_container(&ientry, DBUS_TYPE_VARIANT, "b", &value); + driver->dbus->message_iter_append_basic(&value, DBUS_TYPE_BOOLEAN, &bool_val); + driver->dbus->message_iter_close_container(&ientry, &value); + driver->dbus->message_iter_close_container(&dict, &ientry); + driver->dbus->message_iter_close_container(&iter, &dict); + driver->dbus->message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &dict); + driver->dbus->message_iter_close_container(&iter, &dict); + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); + } + + signal = driver->dbus->message_new_signal(SNI_OBJECT_PATH, SNI_INTERFACE, "NewMenu"); + if (signal) { + driver->dbus->connection_send(tray_dbus->connection, signal, NULL); + driver->dbus->connection_flush(tray_dbus->connection); + driver->dbus->message_unref(signal); + } + } + } + + 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);