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
This commit is contained in:
Sam Lantinga 2025-01-19 16:33:05 -08:00
parent dfdc120268
commit 5f2dd5f04e
9 changed files with 64 additions and 47 deletions

View file

@ -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); 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++ */ /* Ends C function definitions when using C++ */
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -1232,6 +1232,7 @@ SDL3_0.0.0 {
SDL_GetThreadState; SDL_GetThreadState;
SDL_AudioStreamDevicePaused; SDL_AudioStreamDevicePaused;
SDL_ClickTrayEntry; SDL_ClickTrayEntry;
SDL_UpdateTrays;
# extra symbols go here (don't modify this line) # extra symbols go here (don't modify this line)
local: *; local: *;
}; };

View file

@ -1257,3 +1257,4 @@
#define SDL_GetThreadState SDL_GetThreadState_REAL #define SDL_GetThreadState SDL_GetThreadState_REAL
#define SDL_AudioStreamDevicePaused SDL_AudioStreamDevicePaused_REAL #define SDL_AudioStreamDevicePaused SDL_AudioStreamDevicePaused_REAL
#define SDL_ClickTrayEntry SDL_ClickTrayEntry_REAL #define SDL_ClickTrayEntry SDL_ClickTrayEntry_REAL
#define SDL_UpdateTrays SDL_UpdateTrays_REAL

View file

@ -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(SDL_ThreadState,SDL_GetThreadState,(SDL_Thread *a),(a),return)
SDL_DYNAPI_PROC(bool,SDL_AudioStreamDevicePaused,(SDL_AudioStream *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_ClickTrayEntry,(SDL_TrayEntry *a),(a),)
SDL_DYNAPI_PROC(void,SDL_UpdateTrays,(void),(),)

View file

@ -1399,6 +1399,8 @@ static void SDL_PumpEventsInternal(bool push_sentinel)
} }
#endif #endif
SDL_UpdateTrays();
SDL_SendPendingSignalEvents(); // in case we had a signal handler fire, etc. SDL_SendPendingSignalEvents(); // in case we had a signal handler fire, etc.
if (push_sentinel && SDL_EventEnabled(SDL_EVENT_POLL_SENTINEL)) { if (push_sentinel && SDL_EventEnabled(SDL_EVENT_POLL_SENTINEL)) {

View file

@ -78,6 +78,10 @@ static void DestroySDLMenu(SDL_TrayMenu *menu)
SDL_free(menu); SDL_free(menu);
} }
void SDL_UpdateTrays(void)
{
}
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{ {
if (!SDL_IsMainThread()) { if (!SDL_IsMainThread()) {

View file

@ -25,6 +25,10 @@
#include "../SDL_tray_utils.h" #include "../SDL_tray_utils.h"
void SDL_UpdateTrays(void)
{
}
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{ {
SDL_Unsupported(); SDL_Unsupported();

View file

@ -54,9 +54,10 @@ typedef enum
G_CONNECT_AFTER = 1 << 0, G_CONNECT_AFTER = 1 << 0,
G_CONNECT_SWAPPED = 1 << 1 G_CONNECT_SWAPPED = 1 << 1
} GConnectFlags; } 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); static gulong (*g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags);
gchar *(*g_mkdtemp)(gchar *template); static void (*g_object_unref)(gpointer object);
static gchar *(*g_mkdtemp)(gchar *template);
#define g_signal_connect(instance, detailed_signal, c_handler, data) \ #define g_signal_connect(instance, detailed_signal, c_handler, data) \
g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0) 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 _GtkWidget GtkWidget;
typedef struct _GtkCheckMenuItem GtkCheckMenuItem; typedef struct _GtkCheckMenuItem GtkCheckMenuItem;
gboolean (*gtk_init_check)(int *argc, char ***argv); static gboolean (*gtk_init_check)(int *argc, char ***argv);
void (*gtk_main)(void); static gboolean (*gtk_main_iteration_do)(gboolean blocking);
void (*gtk_main_quit)(void); static GtkWidget* (*gtk_menu_new)(void);
GtkWidget* (*gtk_menu_new)(void); static GtkWidget* (*gtk_separator_menu_item_new)(void);
GtkWidget* (*gtk_separator_menu_item_new)(void); static GtkWidget* (*gtk_menu_item_new_with_label)(const gchar *label);
GtkWidget* (*gtk_menu_item_new_with_label)(const gchar *label); static void (*gtk_menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu);
void (*gtk_menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu); static GtkWidget* (*gtk_check_menu_item_new_with_label)(const gchar *label);
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);
void (*gtk_check_menu_item_set_active)(GtkCheckMenuItem *check_menu_item, gboolean is_active); static void (*gtk_widget_set_sensitive)(GtkWidget *widget, gboolean sensitive);
void (*gtk_widget_set_sensitive)(GtkWidget *widget, gboolean sensitive); static void (*gtk_widget_show)(GtkWidget *widget);
void (*gtk_widget_show)(GtkWidget *widget); static void (*gtk_menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child);
void (*gtk_menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child); static void (*gtk_menu_shell_insert)(GtkMenuShell *menu_shell, GtkWidget *child, gint position);
void (*gtk_menu_shell_insert)(GtkMenuShell *menu_shell, GtkWidget *child, gint position); static void (*gtk_widget_destroy)(GtkWidget *widget);
void (*gtk_widget_destroy)(GtkWidget *widget); static const gchar *(*gtk_menu_item_get_label)(GtkMenuItem *menu_item);
const gchar *(*gtk_menu_item_get_label)(GtkMenuItem *menu_item); static void (*gtk_menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label);
void (*gtk_menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label); static gboolean (*gtk_check_menu_item_get_active)(GtkCheckMenuItem *check_menu_item);
gboolean (*gtk_check_menu_item_get_active)(GtkCheckMenuItem *check_menu_item); static gboolean (*gtk_widget_get_sensitive)(GtkWidget *widget);
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_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)) #define GTK_WIDGET(widget) (G_TYPE_CHECK_INSTANCE_CAST ((widget), GTK_TYPE_WIDGET, GtkWidget))
@ -119,23 +119,17 @@ typedef enum {
} AppIndicatorStatus; } AppIndicatorStatus;
typedef struct _AppIndicator AppIndicator; 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); static AppIndicator *(*app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category);
void (*app_indicator_set_icon)(AppIndicator *self, const gchar *icon_name); static void (*app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status);
void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu); 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 */ /* END THIRD-PARTY HEADER CONTENT */
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
#endif #endif
static int main_gtk_thread(void *data)
{
gtk_main();
return 0;
}
static bool gtk_thread_active = false;
#ifdef APPINDICATOR_HEADER #ifdef APPINDICATOR_HEADER
static void quit_gtk(void) static void quit_gtk(void)
@ -232,8 +226,7 @@ static bool init_gtk(void)
} }
gtk_init_check = dlsym(libgtk, "gtk_init_check"); gtk_init_check = dlsym(libgtk, "gtk_init_check");
gtk_main = dlsym(libgtk, "gtk_main"); gtk_main_iteration_do = dlsym(libgtk, "gtk_main_iteration_do");
gtk_main_quit = dlsym(libgtk, "gtk_main_quit");
gtk_menu_new = dlsym(libgtk, "gtk_menu_new"); gtk_menu_new = dlsym(libgtk, "gtk_menu_new");
gtk_separator_menu_item_new = dlsym(libgtk, "gtk_separator_menu_item_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"); 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"); app_indicator_set_menu = dlsym(libappindicator, "app_indicator_set_menu");
if (!gtk_init_check || if (!gtk_init_check ||
!gtk_main || !gtk_main_iteration_do ||
!gtk_main_quit ||
!gtk_menu_new || !gtk_menu_new ||
!gtk_separator_menu_item_new || !gtk_separator_menu_item_new ||
!gtk_menu_item_new_with_label || !gtk_menu_item_new_with_label ||
@ -396,6 +388,13 @@ static void DestroySDLMenu(SDL_TrayMenu *menu)
SDL_free(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) SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{ {
if (!SDL_IsMainThread()) { if (!SDL_IsMainThread()) {
@ -407,11 +406,6 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
return NULL; 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)); SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
if (!tray) { if (!tray) {
return NULL; return NULL;
@ -794,9 +788,4 @@ void SDL_DestroyTray(SDL_Tray *tray)
} }
SDL_free(tray); SDL_free(tray);
if (!SDL_HasActiveTrays()) {
gtk_main_quit();
gtk_thread_active = false;
}
} }

View file

@ -209,6 +209,10 @@ static HICON load_default_icon()
return LoadIcon(NULL, IDI_APPLICATION); return LoadIcon(NULL, IDI_APPLICATION);
} }
void SDL_UpdateTrays(void)
{
}
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{ {
if (!SDL_IsMainThread()) { if (!SDL_IsMainThread()) {