From 0461180e25b86affeb82e9ff1f97bc22335baa4a Mon Sep 17 00:00:00 2001
From: Semphris <[EMAIL REDACTED]>
Date: Thu, 26 Dec 2024 20:50:46 -0500
Subject: [PATCH] SDL_EVENT_QUIT when no window nor tray
SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE will not fire if there are active tray icons. This impacts only applications that create tray icons, and that at least one icon outlives the last visible top-level window. SDL_EVENT_QUIT will fire when the last active tray is destroyed if there are no active windows.
---
Android.mk | 1 +
VisualC-GDK/SDL/SDL.vcxproj | 1 +
VisualC-GDK/SDL/SDL.vcxproj.filters | 1 +
VisualC/SDL/SDL.vcxproj | 1 +
VisualC/SDL/SDL.vcxproj.filters | 3 ++
include/SDL3/SDL_hints.h | 4 ++
src/events/SDL_windowevents.c | 4 +-
src/tray/SDL_tray_utils.c | 62 +++++++++++++++++++++++++++++
src/tray/SDL_tray_utils.h | 25 ++++++++++++
src/tray/cocoa/SDL_tray.m | 5 +++
src/tray/dummy/SDL_tray.c | 2 +
src/tray/unix/SDL_tray.c | 11 +++++
src/tray/windows/SDL_tray.c | 6 +++
test/testtray.c | 52 ++++++++++++++++++++++--
14 files changed, 173 insertions(+), 5 deletions(-)
create mode 100644 src/tray/SDL_tray_utils.c
create mode 100644 src/tray/SDL_tray_utils.h
diff --git a/Android.mk b/Android.mk
index 3e584f9a5b0dc..9e79ee0bc9492 100644
--- a/Android.mk
+++ b/Android.mk
@@ -79,6 +79,7 @@ LOCAL_SRC_FILES := \
$(wildcard $(LOCAL_PATH)/src/timer/*.c) \
$(wildcard $(LOCAL_PATH)/src/timer/unix/*.c) \
$(wildcard $(LOCAL_PATH)/src/tray/dummy/*.c) \
+ $(wildcard $(LOCAL_PATH)/src/tray/*.c) \
$(wildcard $(LOCAL_PATH)/src/video/*.c) \
$(wildcard $(LOCAL_PATH)/src/video/android/*.c) \
$(wildcard $(LOCAL_PATH)/src/video/yuv2rgb/*.c))
diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 2b6437fb7e055..2422201d23f56 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -838,6 +838,7 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Gaming.Xbox.XboxOne.x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Xbox.XboxOne.x64'">true</ExcludedFromBuild>
</ClCompile>
+ <ClCompile Include="..\..\src\tray\SDL_tray_utils.c" />
<ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
<ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 5ae2609c8042d..7efaf3abe3fef 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -220,6 +220,7 @@
<ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
<ClCompile Include="..\..\src\tray\dummy\SDL_tray.c" />
<ClCompile Include="..\..\src\tray\windows\SDL_tray.c" />
+ <ClCompile Include="..\..\src\tray\SDL_tray_utils.c" />
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_lsx.c" />
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_sse.c" />
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_std.c" />
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 42d0c13daaec1..e860035ae625a 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -673,6 +673,7 @@
<ClCompile Include="..\..\src\time\SDL_time.c" />
<ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
<ClCompile Include="..\..\src\tray\windows\SDL_tray.c" />
+ <ClCompile Include="..\..\src\tray\SDL_tray_utils.c" />
<ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
<ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index d1d24865f57bb..111db56c2079e 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -1235,6 +1235,9 @@
<ClCompile Include="..\..\src\tray\windows\SDL_tray.c">
<Filter>video</Filter>
</ClCompile>
+ <ClCompile Include="..\..\src\tray\SDL_tray_utils.c">
+ <Filter>video</Filter>
+ </ClCompile>
<ClCompile Include="..\..\src\video\SDL_RLEaccel.c">
<Filter>video</Filter>
</ClCompile>
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 1e03207d7d753..028b6e70c1d50 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -2775,6 +2775,10 @@ extern "C" {
* - "1": SDL will send a quit event when the last window is requesting to
* close. (default)
*
+ * If there is at least one active system tray icon, SDL_EVENT_QUIT will instead
+ * be sent when both the last window will be closed and the last tray icon will
+ * be destroyed.
+ *
* This hint can be set anytime.
*
* \since This hint is available since SDL 3.1.3.
diff --git a/src/events/SDL_windowevents.c b/src/events/SDL_windowevents.c
index 5535ec5059f9b..5e8d13c6b43e1 100644
--- a/src/events/SDL_windowevents.c
+++ b/src/events/SDL_windowevents.c
@@ -24,7 +24,7 @@
#include "SDL_events_c.h"
#include "SDL_mouse_c.h"
-
+#include "../tray/SDL_tray_utils.h"
static bool SDLCALL RemoveSupercededWindowEvents(void *userdata, SDL_Event *event)
{
@@ -247,7 +247,7 @@ bool SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, int data
break;
}
- if (windowevent == SDL_EVENT_WINDOW_CLOSE_REQUESTED && !window->parent) {
+ if (windowevent == SDL_EVENT_WINDOW_CLOSE_REQUESTED && !window->parent && SDL_HasNoActiveTrays()) {
int toplevel_count = 0;
SDL_Window *n;
for (n = SDL_GetVideoDevice()->windows; n; n = n->next) {
diff --git a/src/tray/SDL_tray_utils.c b/src/tray/SDL_tray_utils.c
new file mode 100644
index 0000000000000..aa4b69179b32f
--- /dev/null
+++ b/src/tray/SDL_tray_utils.c
@@ -0,0 +1,62 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 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.
+*/
+#include "SDL_internal.h"
+
+#include "../video/SDL_sysvideo.h"
+#include "../events/SDL_events_c.h"
+
+static int active_trays = 0;
+
+extern void SDL_IncrementTrayCount(void)
+{
+ if (++active_trays < 1) {
+ SDL_Log("Active tray count corrupted (%d < 1), this is a bug. The app may close or fail to close unexpectedly.", active_trays);
+ }
+}
+
+extern void SDL_DecrementTrayCount(void)
+{
+ int toplevel_count = 0;
+ SDL_Window *n;
+
+ if (--active_trays < 0) {
+ SDL_Log("Active tray count corrupted (%d < 0), this is a bug. The app may close or fail to close unexpectedly.", active_trays);
+ }
+
+ if (!SDL_GetHintBoolean(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, true)) {
+ return;
+ }
+
+ for (n = SDL_GetVideoDevice()->windows; n; n = n->next) {
+ if (!n->parent && !(n->flags & SDL_WINDOW_HIDDEN)) {
+ ++toplevel_count;
+ }
+ }
+
+ if (toplevel_count < 1) {
+ SDL_SendQuit();
+ }
+}
+
+extern bool SDL_HasNoActiveTrays(void)
+{
+ return active_trays < 1;
+}
diff --git a/src/tray/SDL_tray_utils.h b/src/tray/SDL_tray_utils.h
new file mode 100644
index 0000000000000..588752c854db1
--- /dev/null
+++ b/src/tray/SDL_tray_utils.h
@@ -0,0 +1,25 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2024 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.
+*/
+#include "SDL_internal.h"
+
+extern void SDL_IncrementTrayCount(void);
+extern void SDL_DecrementTrayCount(void);
+extern bool SDL_HasNoActiveTrays(void);
diff --git a/src/tray/cocoa/SDL_tray.m b/src/tray/cocoa/SDL_tray.m
index 5197b7dab893c..843c6c9574377 100644
--- a/src/tray/cocoa/SDL_tray.m
+++ b/src/tray/cocoa/SDL_tray.m
@@ -25,6 +25,7 @@
#include <Cocoa/Cocoa.h>
+#include "../SDL_tray_utils.h"
#include "../../video/SDL_surface_c.h"
/* applicationDockMenu */
@@ -158,6 +159,8 @@ static void DestroySDLMenu(SDL_TrayMenu *menu)
}
skip_putting_an_icon:
+ SDL_IncrementTrayCount();
+
return tray;
}
@@ -445,6 +448,8 @@ void SDL_DestroyTray(SDL_Tray *tray)
}
SDL_free(tray);
+
+ SDL_DecrementTrayCount();
}
#endif // SDL_PLATFORM_MACOS
diff --git a/src/tray/dummy/SDL_tray.c b/src/tray/dummy/SDL_tray.c
index e8eaebd32640d..7adaed80a1311 100644
--- a/src/tray/dummy/SDL_tray.c
+++ b/src/tray/dummy/SDL_tray.c
@@ -23,6 +23,8 @@
#ifndef SDL_PLATFORM_MACOS
+#include "../SDL_tray_utils.h"
+
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 b2e81640d9930..7a5ed34ee9b9e 100644
--- a/src/tray/unix/SDL_tray.c
+++ b/src/tray/unix/SDL_tray.c
@@ -21,6 +21,8 @@
#include "SDL_internal.h"
+#include "../SDL_tray_utils.h"
+
#include <dlfcn.h>
/* getpid() */
@@ -52,6 +54,7 @@ typedef enum
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);
#define g_signal_connect(instance, detailed_signal, c_handler, data) \
g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0)
@@ -237,6 +240,7 @@ static bool init_gtk(void)
gtk_widget_get_sensitive = dlsym(libgtk, "gtk_widget_get_sensitive");
g_signal_connect_data = dlsym(libgdk, "g_signal_connect_data");
+ g_object_unref = dlsym(libgdk, "g_object_unref");
app_indicator_new = dlsym(libappindicator, "app_indicator_new");
app_indicator_set_status = dlsym(libappindicator, "app_indicator_set_status");
@@ -257,6 +261,7 @@ static bool init_gtk(void)
!gtk_menu_shell_insert ||
!gtk_widget_destroy ||
!g_signal_connect_data ||
+ !g_object_unref ||
!app_indicator_new ||
!app_indicator_set_status ||
!app_indicator_set_icon ||
@@ -394,6 +399,8 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE);
+ SDL_IncrementTrayCount();
+
return tray;
}
@@ -660,5 +667,9 @@ void SDL_DestroyTray(SDL_Tray *tray)
SDL_RemovePath(tray->icon_path);
}
+ g_object_unref(tray->indicator);
+
SDL_free(tray);
+
+ SDL_DecrementTrayCount();
}
diff --git a/src/tray/windows/SDL_tray.c b/src/tray/windows/SDL_tray.c
index 2a623db22e180..ba1a560e4972d 100644
--- a/src/tray/windows/SDL_tray.c
+++ b/src/tray/windows/SDL_tray.c
@@ -21,7 +21,9 @@
#include "SDL_internal.h"
+#include "../SDL_tray_utils.h"
#include "../../core/windows/SDL_windows.h"
+
#include <windowsx.h>
#include <shellapi.h>
#include <stdlib.h> /* FIXME: for mbstowcs_s, wcslen */
@@ -228,6 +230,8 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
SetWindowLongPtr(tray->hwnd, GWLP_USERDATA, (LONG_PTR) tray);
+ SDL_IncrementTrayCount();
+
return tray;
}
@@ -568,4 +572,6 @@ void SDL_DestroyTray(SDL_Tray *tray)
}
SDL_free(tray);
+
+ SDL_DecrementTrayCount();
}
diff --git a/test/testtray.c b/test/testtray.c
index df3a5488f6b4a..71533bac7a651 100644
--- a/test/testtray.c
+++ b/test/testtray.c
@@ -9,6 +9,20 @@ static void SDLCALL tray_quit(void *ptr, SDL_TrayEntry *entry)
SDL_PushEvent(&e);
}
+static bool trays_destroyed = false;
+
+static void SDLCALL tray_close(void *ptr, SDL_TrayEntry *entry)
+{
+ SDL_Tray **trays = (SDL_Tray **) ptr;
+
+ trays_destroyed = true;
+
+ SDL_DestroyTray(trays[0]);
+ SDL_DestroyTray(trays[1]);
+
+ SDL_free(trays);
+}
+
static void SDLCALL apply_icon(void *ptr, const char * const *filelist, int filter)
{
if (!*filelist) {
@@ -500,6 +514,13 @@ int main(int argc, char **argv)
return 1;
}
+ SDL_Window *w = SDL_CreateWindow("", 640, 480, 0);
+
+ if (!w) {
+ SDL_Log("Couldn't create window: %s", SDL_GetError());
+ goto quit;
+ }
+
/* TODO: Resource paths? */
SDL_Surface *icon = SDL_LoadBMP("../test/sdl-test_round.bmp");
@@ -517,7 +538,7 @@ int main(int argc, char **argv)
if (!tray) {
SDL_Log("Couldn't create control tray: %s", SDL_GetError());
- goto quit;
+ goto clean_window;
}
SDL_Tray *tray2 = SDL_CreateTray(icon2, "SDL Tray example");
@@ -545,7 +566,20 @@ int main(int argc, char **argv)
SDL_TrayEntry *entry_quit = SDL_InsertTrayEntryAt(menu, -1, "Quit", SDL_TRAYENTRY_BUTTON);
CHECK(entry_quit);
+ SDL_TrayEntry *entry_close = SDL_InsertTrayEntryAt(menu, -1, "Close", SDL_TRAYENTRY_BUTTON);
+ CHECK(entry_close);
+
+ /* TODO: Track memory! */
+ SDL_Tray **trays = SDL_malloc(sizeof(SDL_Tray *) * 2);
+ if (!trays) {
+ goto clean_all;
+ }
+
+ trays[0] = tray;
+ trays[1] = tray2;
+
SDL_SetTrayEntryCallback(entry_quit, tray_quit, NULL);
+ SDL_SetTrayEntryCallback(entry_close, tray_close, trays);
SDL_InsertTrayEntryAt(menu, -1, NULL, 0);
@@ -582,14 +616,26 @@ int main(int argc, char **argv)
while (SDL_WaitEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) {
break;
+ } else if (e.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
+ SDL_DestroyWindow(w);
+ w = NULL;
}
}
clean_all:
- SDL_DestroyTray(tray2);
+ if (!trays_destroyed) {
+ SDL_DestroyTray(tray2);
+ }
clean_tray1:
- SDL_DestroyTray(tray);
+ if (!trays_destroyed) {
+ SDL_DestroyTray(tray);
+ }
+
+clean_window:
+ if (w) {
+ SDL_DestroyWindow(w);
+ }
quit:
SDL_Quit();