SDL: Added SDL_ClickTrayEntry()

https://github.com/libsdl-org/SDL/commit/042898995c8fcc88ad821c177b4c4e6566755bb5

From 042898995c8fcc88ad821c177b4c4e6566755bb5 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 10 Jan 2025 12:20:37 -0800
Subject: [PATCH] Added SDL_ClickTrayEntry()

Also removed the app delegate from the tray code on Cocoa and folded that into SDL3AppDelegate.

Fixes https://github.com/libsdl-org/SDL/issues/11906
---
 include/SDL3/SDL_tray.h           |  9 ++++++
 src/dynapi/SDL_dynapi.sym         |  1 +
 src/dynapi/SDL_dynapi_overrides.h |  1 +
 src/dynapi/SDL_dynapi_procs.h     |  1 +
 src/tray/cocoa/SDL_tray.m         | 47 +++++++++++--------------------
 src/tray/dummy/SDL_tray.c         |  4 +++
 src/tray/unix/SDL_tray.c          | 15 ++++++++++
 src/tray/windows/SDL_tray.c       | 15 ++++++++++
 src/video/cocoa/SDL_cocoaevents.m |  8 ++++++
 9 files changed, 70 insertions(+), 31 deletions(-)

diff --git a/include/SDL3/SDL_tray.h b/include/SDL3/SDL_tray.h
index d6d6042658992..a3448cd2f9729 100644
--- a/include/SDL3/SDL_tray.h
+++ b/include/SDL3/SDL_tray.h
@@ -388,6 +388,15 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry);
  */
 extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata);
 
+/**
+ * Simulate a click on a tray entry.
+ *
+ * \param entry The entry to activate.
+ *
+ * \since This function is available since SDL 3.1.9.
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_ClickTrayEntry(SDL_TrayEntry *entry);
+
 /**
  * Destroys a tray object.
  *
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index db3718e694b33..f0e66fc8348d8 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1231,6 +1231,7 @@ SDL3_0.0.0 {
     SDL_GetTrayMenuParentTray;
     SDL_GetThreadState;
     SDL_AudioStreamDevicePaused;
+    SDL_ClickTrayEntry;
     # 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 bf42c0f7b2f90..e23fe2ed2841b 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1256,3 +1256,4 @@
 #define SDL_GetTrayMenuParentTray SDL_GetTrayMenuParentTray_REAL
 #define SDL_GetThreadState SDL_GetThreadState_REAL
 #define SDL_AudioStreamDevicePaused SDL_AudioStreamDevicePaused_REAL
+#define SDL_ClickTrayEntry SDL_ClickTrayEntry_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 74da2a73c289d..d74f9597c694a 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1264,3 +1264,4 @@ SDL_DYNAPI_PROC(SDL_TrayEntry*,SDL_GetTrayMenuParentEntry,(SDL_TrayMenu *a),(a),
 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),)
diff --git a/src/tray/cocoa/SDL_tray.m b/src/tray/cocoa/SDL_tray.m
index 0bb6fe80df9b5..66f99a2b28a5a 100644
--- a/src/tray/cocoa/SDL_tray.m
+++ b/src/tray/cocoa/SDL_tray.m
@@ -58,31 +58,6 @@
     SDL_TrayMenu *menu;
 };
 
-static NSApplication *app = NULL;
-
-@interface AppDelegate: NSObject <NSApplicationDelegate>
-    - (IBAction)menu:(id)sender;
-@end
-
-@implementation AppDelegate{}
-    - (IBAction)menu:(id)sender
-    {
-        SDL_TrayEntry *entry = [[sender representedObject] pointerValue];
-
-        if (!entry) {
-            return;
-        }
-
-        if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
-            SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
-        }
-
-        if (entry->callback) {
-            entry->callback(entry->userdata, entry);
-        }
-    }
-@end
-
 static void DestroySDLMenu(SDL_TrayMenu *menu)
 {
     for (int i = 0; i < menu->nEntries; i++) {
@@ -106,11 +81,6 @@ static void DestroySDLMenu(SDL_TrayMenu *menu)
 SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
 {
     SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
-
-    AppDelegate *delegate = [[AppDelegate alloc] init];
-    app = [NSApplication sharedApplication];
-    [app setDelegate:delegate];
-
     if (!tray) {
         return NULL;
     }
@@ -118,7 +88,7 @@ static void DestroySDLMenu(SDL_TrayMenu *menu)
     tray->statusItem = nil;
     tray->statusBar = [NSStatusBar systemStatusBar];
     tray->statusItem = [tray->statusBar statusItemWithLength:NSVariableStatusItemLength];
-    [app activateIgnoringOtherApps:TRUE];
+    [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE];
 
     if (tooltip) {
         tray->statusItem.button.toolTip = [NSString stringWithUTF8String:tooltip];
@@ -421,6 +391,21 @@ void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, v
     entry->userdata = userdata;
 }
 
+void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
+{
+	if (!entry) {
+		return;
+	}
+
+	if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
+		SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
+	}
+
+	if (entry->callback) {
+		entry->callback(entry->userdata, entry);
+	}
+}
+
 SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
 {
     return entry->parent;
diff --git a/src/tray/dummy/SDL_tray.c b/src/tray/dummy/SDL_tray.c
index 00d73960f4bfc..59d7e8a7bb0cf 100644
--- a/src/tray/dummy/SDL_tray.c
+++ b/src/tray/dummy/SDL_tray.c
@@ -119,6 +119,10 @@ void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, v
     SDL_Unsupported();
 }
 
+void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
+{
+}
+
 SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
 {
     SDL_Unsupported();
diff --git a/src/tray/unix/SDL_tray.c b/src/tray/unix/SDL_tray.c
index 74a72ffddce40..cc57fd78114d2 100644
--- a/src/tray/unix/SDL_tray.c
+++ b/src/tray/unix/SDL_tray.c
@@ -671,6 +671,21 @@ void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, v
     entry->userdata = userdata;
 }
 
+void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
+{
+	if (!entry) {
+		return;
+	}
+
+	if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
+		SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
+	}
+
+	if (entry->callback) {
+		entry->callback(entry->userdata, entry);
+	}
+}
+
 SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
 {
     return entry->parent;
diff --git a/src/tray/windows/SDL_tray.c b/src/tray/windows/SDL_tray.c
index 11273d4490353..c73a1d5510fd1 100644
--- a/src/tray/windows/SDL_tray.c
+++ b/src/tray/windows/SDL_tray.c
@@ -536,6 +536,21 @@ void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, v
     entry->userdata = userdata;
 }
 
+void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
+{
+	if (!entry) {
+		return;
+	}
+
+	if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
+		SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
+	}
+
+	if (entry->callback) {
+		entry->callback(entry->userdata, entry);
+	}
+}
+
 SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
 {
     return entry->parent;
diff --git a/src/video/cocoa/SDL_cocoaevents.m b/src/video/cocoa/SDL_cocoaevents.m
index b377237b0a0ba..89f72b2bd20d1 100644
--- a/src/video/cocoa/SDL_cocoaevents.m
+++ b/src/video/cocoa/SDL_cocoaevents.m
@@ -136,6 +136,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath
                         change:(NSDictionary *)change
                        context:(void *)context;
 - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app;
+- (IBAction)menu:(id)sender;
 @end
 
 @implementation SDL3AppDelegate : NSObject
@@ -358,6 +359,13 @@ - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app
     return YES;
 }
 
+- (IBAction)menu:(id)sender
+{
+	SDL_TrayEntry *entry = [[sender representedObject] pointerValue];
+
+	SDL_ClickTrayEntry(entry);
+}
+
 @end
 
 static SDL3AppDelegate *appDelegate = nil;