SDL: tray: unix: Shutdown the GTK thread when not in use

From 0ae57f1d90d770a5d294e544815e4498f6f47109 Mon Sep 17 00:00:00 2001
From: David Gow <[EMAIL REDACTED]>
Date: Sun, 5 Jan 2025 17:54:55 +0800
Subject: [PATCH] tray: unix: Shutdown the GTK thread when not in use

When using the libappindicator/gtk/unix Tray backend, the background
thread which calls gtk_main() is never destroyed. This means that we
detect a leaked thread as SDL_Quit().

Instead, tell gtk to shut down its main loop when no tray icons are
active. This fixes the issue here: SDL notices no leak, and repeatedly
creating / destroying tray icons seems to work fine.

Signed-off-by: David Gow <david@ingeniumdigital.com>
---
 src/tray/unix/SDL_tray.c | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/src/tray/unix/SDL_tray.c b/src/tray/unix/SDL_tray.c
index 994fb5de95756..e4f0208f4ab57 100644
--- a/src/tray/unix/SDL_tray.c
+++ b/src/tray/unix/SDL_tray.c
@@ -78,7 +78,7 @@ 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);
@@ -132,6 +132,8 @@ static int main_gtk_thread(void *data)
     return 0;
 }
 
+static bool gtk_thread_active = false;
+
 #ifdef APPINDICATOR_HEADER
 
 static void quit_gtk(void)
@@ -140,7 +142,7 @@ static void quit_gtk(void)
 
 static bool init_gtk(void)
 {
-    SDL_DetachThread(SDL_CreateThread(main_gtk_thread, "tray gtk", NULL));
+
 }
 
 #else
@@ -223,6 +225,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_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");
@@ -249,6 +252,7 @@ static bool init_gtk(void)
 
     if (!gtk_init_check ||
         !gtk_main ||
+        !gtk_main_quit ||
         !gtk_menu_new ||
         !gtk_separator_menu_item_new ||
         !gtk_menu_item_new_with_label ||
@@ -281,8 +285,6 @@ static bool init_gtk(void)
 
     gtk_is_init = true;
 
-    SDL_DetachThread(SDL_CreateThread(main_gtk_thread, "tray gtk", NULL));
-
     return true;
 }
 #endif
@@ -383,6 +385,11 @@ 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_malloc(sizeof(SDL_Tray));
 
     if (!tray) {
@@ -674,4 +681,9 @@ void SDL_DestroyTray(SDL_Tray *tray)
     SDL_free(tray);
 
     SDL_DecrementTrayCount();
+
+    if (SDL_HasNoActiveTrays()) {
+        gtk_main_quit();
+        gtk_thread_active = false;
+    }
 }