SDL: SNI/DBus tray support (#15189)

From 8c024f4f3ad72e87384935d8a311f08a05d5c52a Mon Sep 17 00:00:00 2001
From: eafton <[EMAIL REDACTED]>
Date: Sat, 11 Apr 2026 00:11:38 +0300
Subject: [PATCH] SNI/DBus tray support (#15189)

---
 src/SDL_list.c               |   87 +++
 src/SDL_list.h               |    3 +
 src/SDL_menu.h               |   64 ++
 src/core/linux/SDL_dbus.c    | 1088 ++++++++++++++++++++++++++++++-
 src/core/linux/SDL_dbus.h    |   28 +-
 src/tray/SDL_tray_utils.c    |    8 +-
 src/tray/SDL_tray_utils.h    |    1 +
 src/tray/unix/SDL_dbustray.c | 1177 ++++++++++++++++++++++++++++++++++
 src/tray/unix/SDL_tray.c     |  692 ++++----------------
 src/tray/unix/SDL_unixtray.h |   81 +++
 10 files changed, 2652 insertions(+), 577 deletions(-)
 create mode 100644 src/SDL_menu.h
 create mode 100644 src/tray/unix/SDL_dbustray.c
 create mode 100644 src/tray/unix/SDL_unixtray.h

diff --git a/src/SDL_list.c b/src/SDL_list.c
index a17c40787afee..558a38d823c81 100644
--- a/src/SDL_list.c
+++ b/src/SDL_list.c
@@ -22,6 +22,82 @@
 
 #include "./SDL_list.h"
 
+// Append
+bool SDL_ListAppend(SDL_ListNode **head, void *ent)
+{
+    SDL_ListNode *cursor;
+    SDL_ListNode *node;
+
+    if (!head) {
+        return false;
+    }
+
+    node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
+    if (!node) {
+        return false;
+    }
+    node->entry = ent;
+    node->next = NULL;
+
+    if (*head) {
+        cursor = *head;
+        while (cursor->next) {
+            cursor = cursor->next;
+        }
+        cursor->next = node;
+    } else {
+        *head = node;
+    }
+
+    return true;
+}
+
+bool SDL_ListInsertAtPosition(SDL_ListNode **head, int pos, void *ent)
+{
+    SDL_ListNode *cursor;
+    SDL_ListNode *node;
+    int i;
+
+    if (pos == -1) {
+        return SDL_ListAppend(head, ent);
+    }
+
+    if (!pos) {
+        node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
+        if (!node) {
+            return false;
+        }
+        node->entry = ent;
+
+        if (*head) {
+            node->next = *head;
+        } else {
+            node->next = NULL;
+        }
+
+        *head = node;
+    }
+
+    cursor = *head;
+    for (i = 1; i < pos - 1 && cursor; i++) {
+        cursor = cursor->next;
+    }
+
+    if (!cursor) {
+        return SDL_ListAppend(head, ent);
+    }
+
+    node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
+    if (!node) {
+        return false;
+    }
+    node->entry = ent;
+    node->next = cursor->next;
+    cursor->next = node;
+
+    return true;
+}
+
 // Push
 bool SDL_ListAdd(SDL_ListNode **head, void *ent)
 {
@@ -84,3 +160,14 @@ void SDL_ListClear(SDL_ListNode **head)
         SDL_free(tmp);
     }
 }
+
+int SDL_ListCountEntries(SDL_ListNode **head)
+{
+    SDL_ListNode *node;
+    int count = 0;
+
+    for (node = *head; node; node = node->next) {
+        ++count;
+    }
+    return count;
+}
diff --git a/src/SDL_list.h b/src/SDL_list.h
index bdfbc35160dc1..b1e6fdbfea7fc 100644
--- a/src/SDL_list.h
+++ b/src/SDL_list.h
@@ -28,9 +28,12 @@ typedef struct SDL_ListNode
     struct SDL_ListNode *next;
 } SDL_ListNode;
 
+bool SDL_ListAppend(SDL_ListNode **head, void *ent);
+bool SDL_ListInsertAtPosition(SDL_ListNode **head, int pos, void *ent);
 bool SDL_ListAdd(SDL_ListNode **head, void *ent);
 void SDL_ListPop(SDL_ListNode **head, void **ent);
 void SDL_ListRemove(SDL_ListNode **head, void *ent);
 void SDL_ListClear(SDL_ListNode **head);
+int SDL_ListCountEntries(SDL_ListNode **head);
 
 #endif // SDL_list_h_
diff --git a/src/SDL_menu.h b/src/SDL_menu.h
new file mode 100644
index 0000000000000..2f0d0bd5eab96
--- /dev/null
+++ b/src/SDL_menu.h
@@ -0,0 +1,64 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef SDL_menu_h_
+#define SDL_menu_h_
+
+#include "./SDL_list.h"
+
+typedef enum SDL_MenuItemType
+{
+    SDL_MENU_ITEM_TYPE_NORMAL,
+    SDL_MENU_ITEM_TYPE_SEPERATOR,
+    SDL_MENU_ITEM_TYPE_CHECKBOX
+} SDL_MenuItemType;
+
+typedef enum SDL_MenuItemFlags
+{
+    SDL_MENU_ITEM_FLAGS_NONE = 0,
+    SDL_MENU_ITEM_FLAGS_DISABLED = 1 << 0,
+    SDL_MENU_ITEM_FLAGS_CHECKED = 1 << 1,
+    SDL_MENU_ITEM_FLAGS_BAR_ITEM = 1 << 2
+} SDL_MenuItemFlags;
+
+/* Do not create this struct directly, users of this structure like the DBUSMENU layer use extended versions of it which need to be allocated by specfic functions. */
+/* This struct is meant to be in an SDL_List just like sub_menu */
+typedef struct SDL_MenuItem
+{
+    /* Basic properties */
+    const char *utf8;
+    SDL_MenuItemType type;
+    SDL_MenuItemFlags flags;
+
+    /* Callback */
+    void *cb_data;
+    void (*cb)(struct SDL_MenuItem *, void *);
+
+    /* Submenu, set to NULL if none */
+    SDL_ListNode *sub_menu;
+
+    /* User data slots */
+    void *udata;
+    void *udata2;
+    void *udata3;
+} SDL_MenuItem;
+
+#endif // SDL_menu_h_
diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c
index 9a2bef87e1e04..0532fb87c9ecb 100644
--- a/src/core/linux/SDL_dbus.c
+++ b/src/core/linux/SDL_dbus.c
@@ -18,14 +18,31 @@
      misrepresented as being the original software.
   3. This notice may not be removed or altered from any source distribution.
 */
+
 #include "SDL_internal.h"
 #include "SDL_dbus.h"
+#include "../../SDL_list.h"
+#include "../../SDL_menu.h"
 #include "../../stdlib/SDL_vacopy.h"
 
 #include <fcntl.h>
 #include <unistd.h>
 
 #ifdef SDL_USE_LIBDBUS
+
+typedef struct SDL_DBusMenuItem
+{
+    SDL_MenuItem _parent;
+
+    SDL_DBusContext *dbus;
+    dbus_int32_t id;
+    dbus_uint32_t revision;
+
+    /* Right click event handler */
+    void *cbdata;
+    bool (*cb)(SDL_ListNode *, void *);
+} SDL_DBusMenuItem;
+
 // we never link directly to libdbus.
 #define SDL_DRIVER_DBUS_DYNAMIC "libdbus-1.so.3"
 static const char *dbus_library = SDL_DRIVER_DBUS_DYNAMIC;
@@ -34,6 +51,11 @@ static char *inhibit_handle = NULL;
 static unsigned int screensaver_cookie = 0;
 static SDL_DBusContext dbus;
 
+#define DBUS_MENU_INTERFACE   "com.canonical.dbusmenu"
+#define DBUS_MENU_OBJECT_PATH "/Menu"
+#define SDL_DBUS_UPDATE_MENU_FLAG_DO_NOT_REPLACE (1 << 0)
+static const char *menu_introspect = "<?xml version=\"1.0\"?><node name=\"/\"><interface name=\"com.canonical.dbusmenu\"><property name=\"Version\" type=\"u\" access=\"read\"></property><property name=\"TextDirection\" type=\"s\" access=\"read\"></property><property name=\"Status\" type=\"s\" access=\"read\"></property><property name=\"IconThemePath\" type=\"as\" access=\"read\"></property><method name=\"GetLayout\"><arg type=\"i\" name=\"parentId\" direction=\"in\"></arg><arg type=\"i\" name=\"recursionDepth\" direction=\"in\"></arg><arg type=\"as\" name=\"propertyNames\" direction=\"in\"></arg><arg type=\"u\" name=\"revision\" direction=\"out\"></arg><arg type=\"(ia{sv}av)\" name=\"layout\" direction=\"out\"></arg></method><method name=\"GetGroupProperties\"><arg type=\"ai\" name=\"ids\" direction=\"in\"></arg><arg type=\"as\" name=\"propertyNames\" direction=\"in\"></arg><arg type=\"a(ia{sv})\" name=\"properties\" direction=\"out\"></arg></method><method name=\"GetProperty\"><arg type=\"i\" name=\"id\" direction=\"in\"></arg><arg type=\"s\" name=\"name\" direction=\"in\"></arg><arg type=\"v\" name=\"value\" direction=\"out\"></arg></method><method name=\"Event\"><arg type=\"i\" name=\"id\" direction=\"in\"></arg><arg type=\"s\" name=\"eventId\" direction=\"in\"></arg><arg type=\"v\" name=\"data\" direction=\"in\"></arg><arg type=\"u\" name=\"timestamp\" direction=\"in\"></arg></method><method name=\"EventGroup\"><arg type=\"a(isvu)\" name=\"events\" direction=\"in\"></arg><arg type=\"ai\" name=\"idErrors\" direction=\"out\"></arg></method><method name=\"AboutToShow\"><arg type=\"i\" name=\"id\" direction=\"in\"></arg><arg type=\"b\" name=\"needUpdate\" direction=\"out\"></arg></method><method name=\"AboutToShowGroup\"><arg type=\"ai\" name=\"ids\" direction=\"in\"></arg><arg type=\"ai\" name=\"updatesNeeded\" direction=\"out\"></arg><arg type=\"ai\" name=\"idErrors\" direction=\"out\"></arg></method><signal name=\"ItemsPropertiesUpdated\"><arg type=\"a(ia{sv})\" name=\"updatedProps\" direction=\"out\"/><arg type=\"a(ias)\" name=\"removedProps\" direction=\"out\"/></signal><signal name=\"LayoutUpdated\"><arg type=\"u\" name=\"revision\" direction=\"out\"></arg><arg type=\"i\" name=\"parent\" direction=\"out\"></arg></signal><signal name=\"ItemActivationRequested\"><arg type=\"i\" name=\"id\" direction=\"out\"></arg><arg type=\"u\" name=\"timestamp\" direction=\"out\"></arg></signal></interface></node>";
+
 SDL_ELF_NOTE_DLOPEN(
     "core-libdbus",
     "Support for D-Bus IPC",
@@ -43,12 +65,12 @@ SDL_ELF_NOTE_DLOPEN(
 
 static bool LoadDBUSSyms(void)
 {
-#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y)                   \
+#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \
     dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y)
 
 #define SDL_DBUS_SYM2(TYPE, x, y)                            \
     if (!(dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y))) \
-        return false
+    return false
 
 #define SDL_DBUS_SYM_OPTIONAL(TYPE, x) \
     SDL_DBUS_SYM2_OPTIONAL(TYPE, x, dbus_##x)
@@ -107,6 +129,16 @@ static bool LoadDBUSSyms(void)
     SDL_DBUS_SYM(void (*)(char **), free_string_array);
     SDL_DBUS_SYM(void (*)(void), shutdown);
 
+    /* New symbols for SNI and menu export */
+    SDL_DBUS_SYM(int (*)(DBusConnection *, const char *, unsigned int, DBusError *), bus_request_name);
+    SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_method_call);
+    SDL_DBUS_SYM(DBusMessage *(*)(DBusMessage *, const char *, const char *), message_new_error);
+    SDL_DBUS_SYM(DBusMessage *(*)(DBusMessage *), message_new_method_return);
+    SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const void *, int), message_iter_append_fixed_array);
+    SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *, int *), message_iter_get_fixed_array);
+    SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *, void **), connection_get_object_path_data);
+    SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *), connection_unregister_object_path);
+
 #undef SDL_DBUS_SYM
 #undef SDL_DBUS_SYM2
 
@@ -466,12 +498,12 @@ 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_OpenURI(const char *uri, const char *window_id, const char *activation_token)
@@ -753,7 +785,7 @@ char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count)
      * The spec doesn't define any entries yet so it's empty. */
     dbus.message_iter_init_append(msg, &iter);
     if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
-        !dbus.message_iter_close_container(&iter,  &iterDict)) {
+        !dbus.message_iter_close_container(&iter, &iterDict)) {
         SDL_OutOfMemory();
         dbus.message_unref(msg);
         goto failed;
@@ -797,10 +829,10 @@ static DBusHandlerResult SDL_DBus_CameraPortalMessageHandler(DBusConnection *con
 
     if (dbus.message_is_signal(msg, "org.freedesktop.DBus", "NameOwnerChanged")) {
         if (!dbus.message_get_args(msg, data->err,
-                DBUS_TYPE_STRING, &name,
-                DBUS_TYPE_STRING, &old,
-                DBUS_TYPE_STRING, &new,
-                DBUS_TYPE_INVALID)) {
+                                   DBUS_TYPE_STRING, &name,
+                                   DBUS_TYPE_STRING, &old,
+                                   DBUS_TYPE_STRING, &new,
+                                   DBUS_TYPE_INVALID)) {
             data->done = true;
             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
         }
@@ -962,4 +994,1036 @@ int SDL_DBus_CameraPortalRequestAccess(void)
     return -1;
 }
 
+/* DBUSMENU LAYER BEGINS HERE */
+
+/* Special thanks to the kind Hayden Gray (thag_iceman/A1029384756) from the SDL community for his help! */
+
+static SDL_DBusMenuItem *MenuGetItemById(SDL_ListNode *menu, dbus_int32_t id)
+{
+    SDL_ListNode *cursor;
+
+    cursor = menu;
+    while (cursor) {
+        SDL_MenuItem *item;
+        SDL_DBusMenuItem *dbus_item;
+
+        item = cursor->entry;
+        dbus_item = cursor->entry;
+
+        if (dbus_item->id == id) {
+            return dbus_item;
+        }
+
+        if (item->sub_menu) {
+            SDL_DBusMenuItem *found;
+
+            found = MenuGetItemById(item->sub_menu, id);
+            if (found) {
+                return found;
+            }
+        }
+
+        cursor = cursor->next;
+    }
+    return NULL;
+}
+
+static void MenuAppendItemProperties(SDL_DBusContext *ctx, SDL_DBusMenuItem *dbus_item, DBusMessageIter *dict_iter)
+{
+    SDL_MenuItem *item;
+    DBusMessageIter entry_iter;
+    DBusMessageIter variant_iter;
+    const char *key;
+    const char *value;
+    int value_int;
+    dbus_bool_t value_bool;
+
+    item = (SDL_MenuItem *)dbus_item;
+
+    key = "label";
+    value = item->utf8 ? item->utf8 : "";
+    ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter);
+    ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key);
+    ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter);
+    ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value);
+    ctx->message_iter_close_container(&entry_iter, &variant_iter);
+    ctx->message_iter_close_container(dict_iter, &entry_iter);
+
+    key = "type";
+    if (item->type == SDL_MENU_ITEM_TYPE_SEPERATOR) {
+        value = "separator";
+    } else {
+        value = "standard";
+    }
+    ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter);
+    ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key);
+    ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter);
+    ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value);
+    ctx->message_iter_close_container(&entry_iter, &variant_iter);
+    ctx->message_iter_close_container(dict_iter, &entry_iter);
+
+    key = "enabled";
+    value_bool = !(item->flags & SDL_MENU_ITEM_FLAGS_DISABLED);
+    ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter);
+    ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key);
+    ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter);
+    ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &value_bool);
+    ctx->message_iter_close_container(&entry_iter, &variant_iter);
+    ctx->message_iter_close_container(dict_iter, &entry_iter);
+
+    key = "visible";
+    value_bool = TRUE;
+    ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter);
+    ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key);
+    ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "b", &variant_iter);
+    ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_BOOLEAN, &value_bool);
+    ctx->message_iter_close_container(&entry_iter, &variant_iter);
+    ctx->message_iter_close_container(dict_iter, &entry_iter);
+
+    key = "toggle-type";
+    value = (item->type == SDL_MENU_ITEM_TYPE_CHECKBOX) ? "checkmark" : "";
+    ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter);
+    ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key);
+    ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter);
+    ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value);
+    ctx->message_iter_close_container(&entry_iter, &variant_iter);
+    ctx->message_iter_close_container(dict_iter, &entry_iter);
+
+    key = "toggle-state";
+    value_int = (item->flags & SDL_MENU_ITEM_FLAGS_CHECKED) ? 1 : 0;
+    ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter);
+    ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key);
+    ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "i", &variant_iter);
+    ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_INT32, &value_int);
+    ctx->message_iter_close_container(&entry_iter, &variant_iter);
+    ctx->message_iter_close_container(dict_iter, &entry_iter);
+
+    key = "children-display";
+    value = item->sub_menu ? "submenu" : "";
+    ctx->message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter);
+    ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key);
+    ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter);
+    ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &value);
+    ctx->message_iter_close_container(&entry_iter, &variant_iter);
+    ctx->message_iter_close_container(dict_iter, &entry_iter);
+}
+
+static void MenuAppendItem(SDL_DBusContext *ctx, SDL_DBusMenuItem *dbus_item, DBusMessageIter *array_iter, int depth)
+{
+    SDL_MenuItem *item;
+    DBusMessageIter struct_iter, dict_iter, children_iter;
+
+    item = (SDL_MenuItem *)dbus_item;
+
+    ctx->message_iter_open_container(array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter);
+    ctx->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &dbus_item->id);
+    ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter);
+    MenuAppendItemProperties(ctx, dbus_item, &dict_iter);
+    ctx->message_iter_close_container(&struct_iter, &dict_iter);
+    ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "v", &children_iter);
+
+    if (item->sub_menu && depth > 0) {
+        SDL_ListNode *cursor;
+
+        cursor = item->sub_menu;
+        while (cursor) {
+            SDL_DBusMenuItem *child;
+            DBusMessageIter variant_iter;
+
+            child = cursor->entry;
+            ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &variant_iter);
+            MenuAppendItem(ctx, child, &variant_iter, depth - 1);
+            ctx->message_iter_close_container(&children_iter, &variant_iter);
+            cursor = cursor->next;
+        }
+    }
+
+    ctx->message_iter_close_container(&struct_iter, &children_iter);
+    ctx->message_iter_close_container(array_iter, &struct_iter);
+}
+
+static DBusHandlerResult MenuHandleGetLayout(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg)
+{
+    DBusMessage *reply;
+    DBusMessageIter reply_iter, struct_iter, dict_iter, children_iter;
+    DBusMessageIter entry_iter, variant_iter;
+    DBusMessageIter args;
+    const char *key;
+    const char *val;
+    dbus_int32_t parent_id;
+    dbus_int32_t recursion_depth;
+    dbus_int32_t root_id;
+    dbus_uint32_t revision;
+
+    ctx->message_iter_init(msg, &args);
+    if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_INT32) {
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+    ctx->message_iter_get_basic(&args, &parent_id);
+    ctx->message_iter_next(&args);
+    if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_INT32) {
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+    ctx->message_iter_get_basic(&args, &recursion_depth);
+    if (recursion_depth == -1) {
+        recursion_depth = 100;
+    }
+
+    reply = ctx->message_new_method_return(msg);
+    ctx->message_iter_init_append(reply, &reply_iter);
+
+    revision = 0;
+    if (menu) {
+        if (menu->entry) {
+            revision = ((SDL_DBusMenuItem *)menu->entry)->revision;
+        }
+    }
+    ctx->message_iter_append_basic(&reply_iter, DBUS_TYPE_UINT32, &revision);
+
+    ctx->message_iter_open_container(&reply_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter);
+
+    root_id = 0;
+    ctx->message_iter_append_basic(&struct_iter, DBUS_TYPE_INT32, &root_id);
+
+    key = "children-display";
+    val = "submenu";
+    ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter);
+    ctx->message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter);
+    ctx->message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key);
+    ctx->message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter);
+    ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val);
+    ctx->message_iter_close_container(&entry_iter, &variant_iter);
+    ctx->message_iter_close_container(&dict_iter, &entry_iter);
+    ctx->message_iter_close_container(&struct_iter, &dict_iter);
+
+    ctx->message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "v", &children_iter);
+    if (!parent_id && menu) {
+        SDL_ListNode *cursor;
+
+        cursor = menu;
+        while (cursor) {
+            SDL_MenuItem *item;
+            SDL_DBusMenuItem *dbus_item;
+            DBusMessageIter cvariant_iter, item_struct, item_dict, item_children;
+
+            item = cursor->entry;
+            dbus_item = cursor->entry;
+            ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &cvariant_iter);
+            ctx->message_iter_open_container(&cvariant_iter, DBUS_TYPE_STRUCT, NULL, &item_struct);
+            ctx->message_iter_append_basic(&item_struct, DBUS_TYPE_INT32, &dbus_item->id);
+            ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "{sv}", &item_dict);
+            MenuAppendItemProperties(ctx, dbus_item, &item_dict);
+            ctx->message_iter_close_container(&item_struct, &item_dict);
+            ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "v", &item_children);
+            if (item->sub_menu && recursion_depth) {
+                SDL_ListNode *child_cursor;
+
+                child_cursor = item->sub_menu;
+                while (child_cursor) {
+                    SDL_DBusMenuItem *child;
+                    DBusMessageIter child_variant;
+
+                    child = child_cursor->entry;
+                    ctx->message_iter_open_container(&item_children, DBUS_TYPE_VARIANT, "(ia{sv}av)", &child_variant);
+                    MenuAppendItem(ctx, child, &child_variant, recursion_depth - 1);
+                    ctx->message_iter_close_container(&item_children, &child_variant);
+                    child_cursor = child_cursor->next;
+                }
+            }
+            ctx->message_iter_close_container(&item_struct, &item_children);
+            ctx->message_iter_close_container(&cvariant_iter, &item_struct);
+
+            ctx->message_iter_close_container(&children_iter, &cvariant_iter);
+            cursor = cursor->next;
+        }
+    } else if (parent_id) {
+        SDL_DBusMenuItem *parent;
+        SDL_MenuItem *parent_item;
+
+        parent = MenuGetItemById(menu, parent_id);
+        parent_item = (SDL_MenuItem *)parent;
+        if (parent_item && parent_item->sub_menu) {
+            SDL_ListNode *cursor;
+
+            cursor = parent_item->sub_menu;
+            while (cursor) {
+                SDL_MenuItem *item;
+                SDL_DBusMenuItem *dbus_item;
+                DBusMessageIter cvariant_iter, item_struct, item_dict, item_children;
+
+                item = cursor->entry;
+                dbus_item = cursor->entry;
+                ctx->message_iter_open_container(&children_iter, DBUS_TYPE_VARIANT, "(ia{sv}av)", &cvariant_iter);
+                ctx->message_iter_open_container(&cvariant_iter, DBUS_TYPE_STRUCT, NULL, &item_struct);
+                ctx->message_iter_append_basic(&item_struct, DBUS_TYPE_INT32, &dbus_item->id);
+                ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "{sv}", &item_dict);
+                MenuAppendItemProperties(ctx, dbus_item, &item_dict);
+                ctx->message_iter_close_container(&item_struct, &item_dict);
+                ctx->message_iter_open_container(&item_struct, DBUS_TYPE_ARRAY, "v", &item_children);
+                if (item->sub_menu && recursion_depth) {
+                    SDL_ListNode *child_cursor;
+
+                    child_cursor = item->sub_menu;
+                    while (child_cursor) {
+                        SDL_DBusMenuItem *child;
+                        DBusMessageIter child_variant;
+
+                        child = child_cursor->entry;
+                        ctx->message_iter_open_container(&item_children, DBUS_TYPE_VARIANT, "(ia{sv}av)", &child_variant);
+                        MenuAppendItem(ctx, child, &child_variant, recursion_depth - 1);
+                        ctx->message_iter_close_container(&item_children, &child_variant);
+                        child_cursor = child_cursor->next;
+                    }
+                }
+                ctx->message_iter_close_container(&item_struct, &item_children);
+                ctx->message_iter_close_container(&cvariant_iter, &item_struct);
+
+                ctx->message_iter_close_container(&children_iter, &cvariant_iter);
+
+                cursor = cursor->next;
+            }
+        }
+    }
+    ctx->message_iter_close_container(&struct_iter, &children_iter);
+    ctx->message_iter_close_container(&reply_iter, &struct_iter);
+
+    ctx->connection_send(conn, reply, NULL);
+    ctx->message_unref(reply);
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult MenuHandleEvent(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg)
+{
+    SDL_MenuItem *item;
+    SDL_DBusMenuItem *dbus_item;
+    DBusMessage *reply;
+    const char *event_id;
+    DBusMessageIter args;
+    Uint32 id;
+
+    ctx->message_iter_init(msg, &args);
+    ctx->message_iter_get_basic(&args, &id);
+    ctx->message_iter_next(&args);
+    ctx->message_iter_get_basic(&args, &event_id);
+
+    item = NULL;
+    dbus_item = NULL;
+    if (!SDL_strcmp(event_id, "clicked")) {
+        dbus_item = MenuGetItemById(menu, id);
+        item = (SDL_MenuItem *)dbus_item;
+    }
+
+    reply = ctx->message_new_method_return(msg);
+    ctx->connection_send(conn, reply, NULL);
+    ctx->message_unref(reply);
+
+    if (item) {        
+        if (item->type == SDL_MENU_ITEM_TYPE_CHECKBOX) {
+			item->flags ^= SDL_MENU_ITEM_FLAGS_CHECKED; 
+			SDL_DBus_UpdateMenu(ctx, conn, menu, NULL, NULL, NULL, SDL_DBUS_UPDATE_MENU_FLAG_DO_NOT_REPLACE);
+		}
+
+        if (item->cb) {
+            item->cb(item, item->cb_data);
+        }
+    }
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult MenuHandleEventGroup(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg)
+{
+    DBusMessage *reply;
+    DBusMessageIter reply_iter, id_errors_iter;
+    DBusMessageIter args, array_iter;
+
+    ctx->message_iter_init(msg, &args);
+    if (ctx->message_iter_get_arg_type(&args) == DBUS_TYPE_ARRAY) {
+        ctx->message_iter_recurse(&args, &array_iter);
+        while (ctx->message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRUCT) {
+            DBusMessageIter struct_iter;
+            const char *event_id;
+            dbus_int32_t id;
+
+            ctx->message_iter_recurse(&array_iter, &struct_iter);
+            if (ctx->message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_INT32) {
+                ctx->message_iter_get_basic(&struct_iter, &id);
+                ctx->message_iter_next(&struct_iter);
+                if (ctx->message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRING) {
+                    ctx->message_iter_get_basic(&struct_iter, &event_id);
+
+                    if (!SDL_strcmp(event_id, "clicked")) {
+                        SDL_DBusMenuItem *dbus_item;
+                        SDL_MenuItem *item;
+
+                        dbus_item = MenuGetItemById(menu, id);
+                        item = (SDL_MenuItem *)dbus_item;
+
+                        if (item) {
+                            if (item->type == SDL_MENU_ITEM_TYPE_CHECKBOX) {
+                                item->flags ^= SDL_MENU_ITEM_FLAGS_CHECKED; 
+                                SDL_DBus_UpdateMenu(ctx, conn, menu, NULL, NULL, NULL, SDL_DBUS_UPDATE_MENU_FLAG_DO_NOT_REPLACE);
+                            }
+                            
+                            if (item->cb) {
+                                item->cb(item, item->cb_data);
+                            }
+                        }
+                    }
+                }
+            }
+            ctx->message_iter_next(&array_iter);
+        }
+    }
+
+    reply = ctx->message_new_method_return(msg);
+    ctx->message_iter_init_append(reply, &reply_iter);
+    ctx->message_iter_open_container(&reply_iter, DBUS_TYPE_ARRAY, "i", &id_errors_iter);
+    ctx->message_iter_close_container(&reply_iter, &id_errors_iter);
+    ctx->connection_send(conn, reply, NULL);
+    ctx->message_unref(reply);
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult MenuHandleGetProperty(SDL_DBusContext *ctx, SDL_ListNode *menu, DBusConnection *conn, DBusMessage *msg)
+{
+    SDL_MenuItem *item;
+    SDL_DBusMenuItem *dbus_item;
+    DBusMessage *reply;
+    const char *property;
+    const char *val;
+    DBusMessageIter args;
+    DBusMessageIter iter, variant_iter;
+    dbus_int32_t id;
+    int int_val;
+    dbus_bool_t bool_val;
+
+    ctx->message_iter_init(msg, &args);
+    if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_INT32) {
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+    ctx->message_iter_get_basic(&args, &id);
+    ctx->message_iter_next(&args);
+    if (ctx->message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) {
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+    ctx->message_iter_get_basic(&args, &property);
+
+    dbus_item = MenuGetItemById(menu, id);
+    item = (SDL_MenuItem *)dbus_item;
+    if (!item) {
+        DBusMessage *error;
+
+        error = ctx->message_new_error(msg, "com.canonical.dbusmenu.Error", "Item not found");
+        ctx->connection_send(conn, error, NULL);
+        ctx->message_unref(error);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    }
+
+    reply = ctx->message_new_method_return(msg);
+    ctx->message_iter_init_append(reply, &iter);
+    if (!SDL_strcmp(property, "label")) {
+        val = item->utf8 ? item->utf8 : "";
+        ctx->message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &variant_iter);
+        ctx->message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &val);
+        ctx->message_iter_close_container(&iter, &variant_iter);
+    } else if (!SDL_strcmp(property, "enab

(Patch may be truncated, please check the link at the top of this post.)