SDL: Add SDL_IsTraySupported

From 47d8bdd1c3e9c799ecaa7cb10d84ae8d88165b5c Mon Sep 17 00:00:00 2001
From: Semphris <[EMAIL REDACTED]>
Date: Sun, 25 May 2025 17:49:53 -0400
Subject: [PATCH] Add SDL_IsTraySupported

---
 include/SDL3/SDL_tray.h           | 19 +++++++++++++++++++
 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         | 10 ++++++++++
 src/tray/dummy/SDL_tray.c         |  5 +++++
 src/tray/unix/SDL_tray.c          | 18 ++++++++++++++++++
 src/tray/windows/SDL_tray.c       | 10 ++++++++++
 8 files changed, 65 insertions(+)

diff --git a/include/SDL3/SDL_tray.h b/include/SDL3/SDL_tray.h
index 1780b0ba52bb7..8d2c7b1bb1bf9 100644
--- a/include/SDL3/SDL_tray.h
+++ b/include/SDL3/SDL_tray.h
@@ -96,6 +96,25 @@ typedef Uint32 SDL_TrayEntryFlags;
  */
 typedef void (SDLCALL *SDL_TrayCallback)(void *userdata, SDL_TrayEntry *entry);
 
+/**
+ * Check whether or not tray icons can be created.
+ *
+ * Note that this function does not guarantee that SDL_CreateTray() will or will
+ * not work; you should still check SDL_CreateTray() for errors.
+ *
+ * Using tray icons require the video subsystem.
+ *
+ * \returns true if trays are available, false otherwise.
+ *
+ * \threadsafety This function should only be called on the main thread. It will
+ *               return false if not called on the main thread.
+ *
+ * \since This function is available since SDL 3.4.0.
+ *
+ * \sa SDL_CreateTray
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_IsTraySupported(void);
+
 /**
  * Create an icon to be placed in the operating system's tray, or equivalent.
  *
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 91bd4300c732f..86abbbd12fc86 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1254,6 +1254,7 @@ SDL3_0.0.0 {
     SDL_SetAudioIterationCallbacks;
     SDL_GetEventDescription;
     SDL_PutAudioStreamDataNoCopy;
+    SDL_IsTraySupported;
     # 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 fcc0bad7b25ff..a2f02b270826d 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1279,3 +1279,4 @@
 #define SDL_SetAudioIterationCallbacks SDL_SetAudioIterationCallbacks_REAL
 #define SDL_GetEventDescription SDL_GetEventDescription_REAL
 #define SDL_PutAudioStreamDataNoCopy SDL_PutAudioStreamDataNoCopy_REAL
+#define SDL_IsTraySupported SDL_IsTraySupported_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 60e0dfea5728f..08ba54c2cbf8c 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1287,3 +1287,4 @@ SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamPlanarData,(SDL_AudioStream *a,const void
 SDL_DYNAPI_PROC(bool,SDL_SetAudioIterationCallbacks,(SDL_AudioDeviceID a,SDL_AudioIterationCallback b,SDL_AudioIterationCallback c,void *d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_GetEventDescription,(const SDL_Event *a,char *b,int c),(a,b,c),return)
 SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamDataNoCopy,(SDL_AudioStream *a,const void *b,int c,SDL_AudioStreamDataCompleteCallback d,void *e),(a,b,c,d,e),return)
+SDL_DYNAPI_PROC(bool,SDL_IsTraySupported,(void),(),return)
diff --git a/src/tray/cocoa/SDL_tray.m b/src/tray/cocoa/SDL_tray.m
index fd7f95517c2da..d093972a2d2c6 100644
--- a/src/tray/cocoa/SDL_tray.m
+++ b/src/tray/cocoa/SDL_tray.m
@@ -82,6 +82,16 @@ void SDL_UpdateTrays(void)
 {
 }
 
+bool SDL_IsTraySupported(void)
+{
+    if (!SDL_IsMainThread()) {
+        SDL_SetError("This function should be called on the main thread");
+        return false;
+    }
+
+    return true;
+}
+
 SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
 {
     if (!SDL_IsMainThread()) {
diff --git a/src/tray/dummy/SDL_tray.c b/src/tray/dummy/SDL_tray.c
index 766fb92584ef9..3a95c6575bd89 100644
--- a/src/tray/dummy/SDL_tray.c
+++ b/src/tray/dummy/SDL_tray.c
@@ -29,6 +29,11 @@ void SDL_UpdateTrays(void)
 {
 }
 
+bool SDL_IsTraySupported(void)
+{
+    return false;
+}
+
 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 4e010ec89ee73..0cc4390b72221 100644
--- a/src/tray/unix/SDL_tray.c
+++ b/src/tray/unix/SDL_tray.c
@@ -413,6 +413,24 @@ void SDL_UpdateTrays(void)
     }
 }
 
+bool SDL_IsTraySupported(void)
+{
+    if (!SDL_IsMainThread()) {
+        SDL_SetError("This function should be called on the main thread");
+        return false;
+    }
+
+    static bool has_trays = false;
+    static bool has_been_detected_once = false;
+
+    if (!has_been_detected_once) {
+        has_trays = init_gtk();
+        has_been_detected_once = true;
+    }
+
+    return has_trays;
+}
+
 SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
 {
     if (!SDL_IsMainThread()) {
diff --git a/src/tray/windows/SDL_tray.c b/src/tray/windows/SDL_tray.c
index 15021ac79845c..a3bd81ff10f03 100644
--- a/src/tray/windows/SDL_tray.c
+++ b/src/tray/windows/SDL_tray.c
@@ -216,6 +216,16 @@ void SDL_UpdateTrays(void)
 {
 }
 
+bool SDL_IsTraySupported(void)
+{
+    if (!SDL_IsMainThread()) {
+        SDL_SetError("This function should be called on the main thread");
+        return false;
+    }
+
+    return true;
+}
+
 SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
 {
     if (!SDL_IsMainThread()) {