From 5f2dd5f04efdeda65a52b77edfc7ee8748b34901 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 19 Jan 2025 16:33:05 -0800
Subject: [PATCH] tray: fixed multi-threading issues with GTk implementation
GTK+ documentation states that all GDK and GTK+ calls should be made from the main thread.
Fixes https://github.com/libsdl-org/SDL/issues/11984
---
include/SDL3/SDL_tray.h | 11 ++++
src/dynapi/SDL_dynapi.sym | 1 +
src/dynapi/SDL_dynapi_overrides.h | 1 +
src/dynapi/SDL_dynapi_procs.h | 1 +
src/events/SDL_events.c | 2 +
src/tray/cocoa/SDL_tray.m | 4 ++
src/tray/dummy/SDL_tray.c | 4 ++
src/tray/unix/SDL_tray.c | 83 ++++++++++++++-----------------
src/tray/windows/SDL_tray.c | 4 ++
9 files changed, 64 insertions(+), 47 deletions(-)
diff --git a/include/SDL3/SDL_tray.h b/include/SDL3/SDL_tray.h
index 54123b5020342..f1fbc01cb5c94 100644
--- a/include/SDL3/SDL_tray.h
+++ b/include/SDL3/SDL_tray.h
@@ -498,6 +498,17 @@ extern SDL_DECLSPEC SDL_TrayEntry *SDLCALL SDL_GetTrayMenuParentEntry(SDL_TrayMe
*/
extern SDL_DECLSPEC SDL_Tray *SDLCALL SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu);
+/**
+ * Update the trays.
+ *
+ * This is called automatically by the event loop and is only needed if you're using trays but aren't handling SDL events.
+ *
+ * \since This function is available since SDL 3.2.0.
+ *
+ * \threadsafety This function should only be called on the main thread.
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_UpdateTrays(void);
+
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index f0e66fc8348d8..b4dcc38396bb1 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1232,6 +1232,7 @@ SDL3_0.0.0 {
SDL_GetThreadState;
SDL_AudioStreamDevicePaused;
SDL_ClickTrayEntry;
+ SDL_UpdateTrays;
# extra symbols go here (don't modify this line)
local: *;
};
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index e23fe2ed2841b..d90b6fd074b54 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1257,3 +1257,4 @@
#define SDL_GetThreadState SDL_GetThreadState_REAL
#define SDL_AudioStreamDevicePaused SDL_AudioStreamDevicePaused_REAL
#define SDL_ClickTrayEntry SDL_ClickTrayEntry_REAL
+#define SDL_UpdateTrays SDL_UpdateTrays_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index d74f9597c694a..e57603d185522 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1265,3 +1265,4 @@ SDL_DYNAPI_PROC(SDL_Tray*,SDL_GetTrayMenuParentTray,(SDL_TrayMenu *a),(a),return
SDL_DYNAPI_PROC(SDL_ThreadState,SDL_GetThreadState,(SDL_Thread *a),(a),return)
SDL_DYNAPI_PROC(bool,SDL_AudioStreamDevicePaused,(SDL_AudioStream *a),(a),return)
SDL_DYNAPI_PROC(void,SDL_ClickTrayEntry,(SDL_TrayEntry *a),(a),)
+SDL_DYNAPI_PROC(void,SDL_UpdateTrays,(void),(),)
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index f7b598304b3ab..c82fed18b7a48 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -1399,6 +1399,8 @@ static void SDL_PumpEventsInternal(bool push_sentinel)
}
#endif
+ SDL_UpdateTrays();
+
SDL_SendPendingSignalEvents(); // in case we had a signal handler fire, etc.
if (push_sentinel && SDL_EventEnabled(SDL_EVENT_POLL_SENTINEL)) {
diff --git a/src/tray/cocoa/SDL_tray.m b/src/tray/cocoa/SDL_tray.m
index 9d2f55e2db347..ae8d6be835f27 100644
--- a/src/tray/cocoa/SDL_tray.m
+++ b/src/tray/cocoa/SDL_tray.m
@@ -78,6 +78,10 @@ static void DestroySDLMenu(SDL_TrayMenu *menu)
SDL_free(menu);
}
+void SDL_UpdateTrays(void)
+{
+}
+
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{
if (!SDL_IsMainThread()) {
diff --git a/src/tray/dummy/SDL_tray.c b/src/tray/dummy/SDL_tray.c
index 55a1e645586f4..db76db25269bf 100644
--- a/src/tray/dummy/SDL_tray.c
+++ b/src/tray/dummy/SDL_tray.c
@@ -25,6 +25,10 @@
#include "../SDL_tray_utils.h"
+void SDL_UpdateTrays(void)
+{
+}
+
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{
SDL_Unsupported();
diff --git a/src/tray/unix/SDL_tray.c b/src/tray/unix/SDL_tray.c
index c26da85a58a61..5f017c2f59c20 100644
--- a/src/tray/unix/SDL_tray.c
+++ b/src/tray/unix/SDL_tray.c
@@ -54,9 +54,10 @@ typedef enum
G_CONNECT_AFTER = 1 << 0,
G_CONNECT_SWAPPED = 1 << 1
} GConnectFlags;
-gulong (*g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags);
-void (*g_object_unref)(gpointer object);
-gchar *(*g_mkdtemp)(gchar *template);
+
+static gulong (*g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags);
+static void (*g_object_unref)(gpointer object);
+static gchar *(*g_mkdtemp)(gchar *template);
#define g_signal_connect(instance, detailed_signal, c_handler, data) \
g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0)
@@ -78,24 +79,23 @@ typedef struct _GtkMenuShell GtkMenuShell;
typedef struct _GtkWidget GtkWidget;
typedef struct _GtkCheckMenuItem GtkCheckMenuItem;
-gboolean (*gtk_init_check)(int *argc, char ***argv);
-void (*gtk_main)(void);
-void (*gtk_main_quit)(void);
-GtkWidget* (*gtk_menu_new)(void);
-GtkWidget* (*gtk_separator_menu_item_new)(void);
-GtkWidget* (*gtk_menu_item_new_with_label)(const gchar *label);
-void (*gtk_menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu);
-GtkWidget* (*gtk_check_menu_item_new_with_label)(const gchar *label);
-void (*gtk_check_menu_item_set_active)(GtkCheckMenuItem *check_menu_item, gboolean is_active);
-void (*gtk_widget_set_sensitive)(GtkWidget *widget, gboolean sensitive);
-void (*gtk_widget_show)(GtkWidget *widget);
-void (*gtk_menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child);
-void (*gtk_menu_shell_insert)(GtkMenuShell *menu_shell, GtkWidget *child, gint position);
-void (*gtk_widget_destroy)(GtkWidget *widget);
-const gchar *(*gtk_menu_item_get_label)(GtkMenuItem *menu_item);
-void (*gtk_menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label);
-gboolean (*gtk_check_menu_item_get_active)(GtkCheckMenuItem *check_menu_item);
-gboolean (*gtk_widget_get_sensitive)(GtkWidget *widget);
+static gboolean (*gtk_init_check)(int *argc, char ***argv);
+static gboolean (*gtk_main_iteration_do)(gboolean blocking);
+static GtkWidget* (*gtk_menu_new)(void);
+static GtkWidget* (*gtk_separator_menu_item_new)(void);
+static GtkWidget* (*gtk_menu_item_new_with_label)(const gchar *label);
+static void (*gtk_menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu);
+static GtkWidget* (*gtk_check_menu_item_new_with_label)(const gchar *label);
+static void (*gtk_check_menu_item_set_active)(GtkCheckMenuItem *check_menu_item, gboolean is_active);
+static void (*gtk_widget_set_sensitive)(GtkWidget *widget, gboolean sensitive);
+static void (*gtk_widget_show)(GtkWidget *widget);
+static void (*gtk_menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child);
+static void (*gtk_menu_shell_insert)(GtkMenuShell *menu_shell, GtkWidget *child, gint position);
+static void (*gtk_widget_destroy)(GtkWidget *widget);
+static const gchar *(*gtk_menu_item_get_label)(GtkMenuItem *menu_item);
+static void (*gtk_menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label);
+static gboolean (*gtk_check_menu_item_get_active)(GtkCheckMenuItem *check_menu_item);
+static gboolean (*gtk_widget_get_sensitive)(GtkWidget *widget);
#define GTK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU_ITEM, GtkMenuItem))
#define GTK_WIDGET(widget) (G_TYPE_CHECK_INSTANCE_CAST ((widget), GTK_TYPE_WIDGET, GtkWidget))
@@ -119,23 +119,17 @@ typedef enum {
} AppIndicatorStatus;
typedef struct _AppIndicator AppIndicator;
-AppIndicator *(*app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category);
-void (*app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status);
-void (*app_indicator_set_icon)(AppIndicator *self, const gchar *icon_name);
-void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu);
+
+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 int main_gtk_thread(void *data)
-{
- gtk_main();
- return 0;
-}
-
-static bool gtk_thread_active = false;
-
#ifdef APPINDICATOR_HEADER
static void quit_gtk(void)
@@ -232,8 +226,7 @@ static bool init_gtk(void)
}
gtk_init_check = dlsym(libgtk, "gtk_init_check");
- gtk_main = dlsym(libgtk, "gtk_main");
- gtk_main_quit = dlsym(libgtk, "gtk_main_quit");
+ gtk_main_iteration_do = dlsym(libgtk, "gtk_main_iteration_do");
gtk_menu_new = dlsym(libgtk, "gtk_menu_new");
gtk_separator_menu_item_new = dlsym(libgtk, "gtk_separator_menu_item_new");
gtk_menu_item_new_with_label = dlsym(libgtk, "gtk_menu_item_new_with_label");
@@ -262,8 +255,7 @@ static bool init_gtk(void)
app_indicator_set_menu = dlsym(libappindicator, "app_indicator_set_menu");
if (!gtk_init_check ||
- !gtk_main ||
- !gtk_main_quit ||
+ !gtk_main_iteration_do ||
!gtk_menu_new ||
!gtk_separator_menu_item_new ||
!gtk_menu_item_new_with_label ||
@@ -396,6 +388,13 @@ static void DestroySDLMenu(SDL_TrayMenu *menu)
SDL_free(menu);
}
+void SDL_UpdateTrays(void)
+{
+ if (SDL_HasActiveTrays()) {
+ gtk_main_iteration_do(FALSE);
+ }
+}
+
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{
if (!SDL_IsMainThread()) {
@@ -407,11 +406,6 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
return NULL;
}
- if (!gtk_thread_active) {
- SDL_DetachThread(SDL_CreateThread(main_gtk_thread, "tray gtk", NULL));
- gtk_thread_active = true;
- }
-
SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
if (!tray) {
return NULL;
@@ -794,9 +788,4 @@ void SDL_DestroyTray(SDL_Tray *tray)
}
SDL_free(tray);
-
- if (!SDL_HasActiveTrays()) {
- gtk_main_quit();
- gtk_thread_active = false;
- }
}
diff --git a/src/tray/windows/SDL_tray.c b/src/tray/windows/SDL_tray.c
index 0afd62768f0e3..a7e27c069914d 100644
--- a/src/tray/windows/SDL_tray.c
+++ b/src/tray/windows/SDL_tray.c
@@ -209,6 +209,10 @@ static HICON load_default_icon()
return LoadIcon(NULL, IDI_APPLICATION);
}
+void SDL_UpdateTrays(void)
+{
+}
+
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{
if (!SDL_IsMainThread()) {