diff --git a/Android.mk b/Android.mk index 3e584f9a5b..9e79ee0bc9 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 2b6437fb7e..2422201d23 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -838,6 +838,7 @@ true true + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index 5ae2609c80..7efaf3abe3 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -220,6 +220,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index 42d0c13daa..e860035ae6 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -673,6 +673,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index d1d24865f5..111db56c20 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -1235,6 +1235,9 @@ video + + video + video diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 1e03207d7d..028b6e70c1 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 5535ec5059..5e8d13c6b4 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 0000000000..aa4b69179b --- /dev/null +++ b/src/tray/SDL_tray_utils.c @@ -0,0 +1,62 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + 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 0000000000..588752c854 --- /dev/null +++ b/src/tray/SDL_tray_utils.h @@ -0,0 +1,25 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + 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 5197b7dab8..843c6c9574 100644 --- a/src/tray/cocoa/SDL_tray.m +++ b/src/tray/cocoa/SDL_tray.m @@ -25,6 +25,7 @@ #include +#include "../SDL_tray_utils.h" #include "../../video/SDL_surface_c.h" /* applicationDockMenu */ @@ -158,6 +159,8 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) } 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 e8eaebd326..7adaed80a1 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 b2e81640d9..7a5ed34ee9 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 /* 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 2a623db22e..ba1a560e49 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 #include #include /* 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 df3a5488f6..71533bac7a 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();