SDL: tray: improved error checking

From 7570ab106da5888803bb4269144c0a687f849614 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 19 Jan 2025 09:59:12 -0800
Subject: [PATCH] tray: improved error checking

Also clean up any existing trays when the program quits

Fixes https://github.com/libsdl-org/SDL/issues/11893
---
 src/SDL.c                   |   2 +
 src/SDL_utils.c             |  19 ++++++
 src/SDL_utils_c.h           |   2 +
 src/tray/SDL_tray_utils.c   |  57 +++++++++++-----
 src/tray/SDL_tray_utils.h   |   5 +-
 src/tray/cocoa/SDL_tray.m   | 130 ++++++++++++++++++++++++++++--------
 src/tray/dummy/SDL_tray.c   |  32 +++------
 src/tray/unix/SDL_tray.c    |  81 +++++++++++++++++++---
 src/tray/windows/SDL_tray.c |  89 +++++++++++++++++++++---
 9 files changed, 334 insertions(+), 83 deletions(-)

diff --git a/src/SDL.c b/src/SDL.c
index 90191d1b0c4d6..e9c9fb1a2353f 100644
--- a/src/SDL.c
+++ b/src/SDL.c
@@ -51,6 +51,7 @@
 #include "sensor/SDL_sensor_c.h"
 #include "stdlib/SDL_getenv_c.h"
 #include "thread/SDL_thread_c.h"
+#include "tray/SDL_tray_utils.h"
 #include "video/SDL_pixels_c.h"
 #include "video/SDL_surface_c.h"
 #include "video/SDL_video_c.h"
@@ -642,6 +643,7 @@ void SDL_Quit(void)
     SDL_HelperWindowDestroy();
 #endif
     SDL_QuitSubSystem(SDL_INIT_EVERYTHING);
+    SDL_CleanupTrays();
 
 #ifdef SDL_USE_LIBDBUS
     SDL_DBus_Quit();
diff --git a/src/SDL_utils.c b/src/SDL_utils.c
index d061becb4faea..a877360f6218d 100644
--- a/src/SDL_utils.c
+++ b/src/SDL_utils.c
@@ -178,6 +178,22 @@ bool SDL_ObjectValid(void *object, SDL_ObjectType type)
     return (((SDL_ObjectType)(uintptr_t)object_type) == type);
 }
 
+int SDL_GetObjects(SDL_ObjectType type, void **objects, int count)
+{
+    const void *object, *object_type;
+    void *iter = NULL;
+    int num_objects = 0;
+    while (SDL_IterateHashTable(SDL_objects, &object, &object_type, &iter)) {
+        if ((SDL_ObjectType)(uintptr_t)object_type == type) {
+            if (num_objects < count) {
+                objects[num_objects] = (void *)object;
+            }
+            ++num_objects;
+        }
+    }
+    return num_objects;
+}
+
 void SDL_SetObjectsInvalid(void)
 {
     if (SDL_ShouldQuit(&SDL_objects_init)) {
@@ -217,6 +233,9 @@ void SDL_SetObjectsInvalid(void)
             case SDL_OBJECT_TYPE_THREAD:
                 type = "thread";
                 break;
+            case SDL_OBJECT_TYPE_TRAY:
+                type = "SDL_Tray";
+                break;
             default:
                 type = "unknown object";
                 break;
diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h
index 68dfcec327cba..557dad49a59d0 100644
--- a/src/SDL_utils_c.h
+++ b/src/SDL_utils_c.h
@@ -61,12 +61,14 @@ typedef enum
     SDL_OBJECT_TYPE_HIDAPI_DEVICE,
     SDL_OBJECT_TYPE_HIDAPI_JOYSTICK,
     SDL_OBJECT_TYPE_THREAD,
+    SDL_OBJECT_TYPE_TRAY,
 
 } SDL_ObjectType;
 
 extern Uint32 SDL_GetNextObjectID(void);
 extern void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid);
 extern bool SDL_ObjectValid(void *object, SDL_ObjectType type);
+extern int SDL_GetObjects(SDL_ObjectType type, void **objects, int count);
 extern void SDL_SetObjectsInvalid(void);
 
 extern const char *SDL_GetPersistentString(const char *string);
diff --git a/src/tray/SDL_tray_utils.c b/src/tray/SDL_tray_utils.c
index b21d40cd652ea..ce792adc479a7 100644
--- a/src/tray/SDL_tray_utils.c
+++ b/src/tray/SDL_tray_utils.c
@@ -27,38 +27,65 @@
 
 static int active_trays = 0;
 
-extern void SDL_IncrementTrayCount(void)
+void SDL_RegisterTray(SDL_Tray *tray)
 {
-    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);
-    }
+    SDL_SetObjectValid(tray, SDL_OBJECT_TYPE_TRAY, true);
+
+    ++active_trays;
 }
 
-extern void SDL_DecrementTrayCount(void)
+void SDL_UnregisterTray(SDL_Tray *tray)
 {
-    int toplevel_count = 0;
-    SDL_Window *n;
+    SDL_assert(SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY));
+
+    SDL_SetObjectValid(tray, SDL_OBJECT_TYPE_TRAY, false);
 
-    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);
+    --active_trays;
+    if (active_trays > 0) {
+        return;
     }
 
     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;
+    int toplevel_count = 0;
+    SDL_Window **windows = SDL_GetWindows(NULL);
+    if (windows) {
+        for (int i = 0; windows[i]; ++i) {
+            SDL_Window *window = windows[i];
+            if (!window->parent && !(window->flags & SDL_WINDOW_HIDDEN)) {
+                ++toplevel_count;
+            }
         }
+        SDL_free(windows);
     }
 
-    if (toplevel_count < 1) {
+    if (toplevel_count == 0) {
         SDL_SendQuit();
     }
 }
 
-extern bool SDL_HasNoActiveTrays(void)
+void SDL_CleanupTrays(void)
+{
+    if (active_trays == 0) {
+        return;
+    }
+
+    void **trays = (void **)SDL_malloc(active_trays * sizeof(*trays));
+    if (!trays) {
+        return;
+    }
+
+    int count = SDL_GetObjects(SDL_OBJECT_TYPE_TRAY, trays, active_trays);
+    SDL_assert(count == active_trays);
+    for (int i = 0; i < count; ++i) {
+        SDL_DestroyTray((SDL_Tray *)trays[i]);
+    }
+    SDL_free(trays);
+}
+
+bool SDL_HasNoActiveTrays(void)
 {
-    return active_trays < 1;
+    return active_trays == 0;
 }
diff --git a/src/tray/SDL_tray_utils.h b/src/tray/SDL_tray_utils.h
index f8f7a7058b963..8dc2249d2231a 100644
--- a/src/tray/SDL_tray_utils.h
+++ b/src/tray/SDL_tray_utils.h
@@ -20,6 +20,7 @@
 */
 #include "SDL_internal.h"
 
-extern void SDL_IncrementTrayCount(void);
-extern void SDL_DecrementTrayCount(void);
+extern void SDL_RegisterTray(SDL_Tray *tray);
+extern void SDL_UnregisterTray(SDL_Tray *tray);
+extern void SDL_CleanupTrays(void);
 extern bool SDL_HasNoActiveTrays(void);
diff --git a/src/tray/cocoa/SDL_tray.m b/src/tray/cocoa/SDL_tray.m
index 66f99a2b28a5a..15de54b24e9a6 100644
--- a/src/tray/cocoa/SDL_tray.m
+++ b/src/tray/cocoa/SDL_tray.m
@@ -80,8 +80,16 @@ static void DestroySDLMenu(SDL_TrayMenu *menu)
 
 SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
 {
+    if (icon) {
+        icon = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
+        if (!icon) {
+            return NULL;
+        }
+    }
+
     SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
     if (!tray) {
+        SDL_DestroySurface(icon);
         return NULL;
     }
 
@@ -97,22 +105,17 @@ static void DestroySDLMenu(SDL_TrayMenu *menu)
     }
 
     if (icon) {
-        SDL_Surface *iconfmt = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
-        if (!iconfmt) {
-            goto skip_putting_an_icon;
-        }
-
-        NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(unsigned char **)&iconfmt->pixels
-                                                                           pixelsWide:iconfmt->w
-                                                                           pixelsHigh:iconfmt->h
+        NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(unsigned char **)&icon->pixels
+                                                                           pixelsWide:icon->w
+                                                                           pixelsHigh:icon->h
                                                                         bitsPerSample:8
                                                                       samplesPerPixel:4
                                                                              hasAlpha:YES
                                                                              isPlanar:NO
                                                                        colorSpaceName:NSCalibratedRGBColorSpace
-                                                                          bytesPerRow:iconfmt->pitch
+                                                                          bytesPerRow:icon->pitch
                                                                          bitsPerPixel:32];
-        NSImage *iconimg = [[NSImage alloc] initWithSize:NSMakeSize(iconfmt->w, iconfmt->h)];
+        NSImage *iconimg = [[NSImage alloc] initWithSize:NSMakeSize(icon->w, icon->h)];
         [iconimg addRepresentation:bitmap];
 
         /* A typical icon size is 22x22 on macOS. Failing to resize the icon
@@ -125,39 +128,42 @@ static void DestroySDLMenu(SDL_TrayMenu *menu)
 
         tray->statusItem.button.image = iconimg22;
 
-        SDL_DestroySurface(iconfmt);
+        SDL_DestroySurface(icon);
     }
 
-skip_putting_an_icon:
-    SDL_IncrementTrayCount();
+    SDL_RegisterTray(tray);
 
     return tray;
 }
 
 void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        return;
+    }
+
     if (!icon) {
         tray->statusItem.button.image = nil;
         return;
     }
 
-    SDL_Surface *iconfmt = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
-    if (!iconfmt) {
+    icon = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
+    if (!icon) {
         tray->statusItem.button.image = nil;
         return;
     }
 
-    NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(unsigned char **)&iconfmt->pixels
-                                                                       pixelsWide:iconfmt->w
-                                                                       pixelsHigh:iconfmt->h
+    NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(unsigned char **)&icon->pixels
+                                                                       pixelsWide:icon->w
+                                                                       pixelsHigh:icon->h
                                                                     bitsPerSample:8
                                                                   samplesPerPixel:4
                                                                          hasAlpha:YES
                                                                          isPlanar:NO
                                                                    colorSpaceName:NSCalibratedRGBColorSpace
-                                                                      bytesPerRow:iconfmt->pitch
+                                                                      bytesPerRow:icon->pitch
                                                                      bitsPerPixel:32];
-    NSImage *iconimg = [[NSImage alloc] initWithSize:NSMakeSize(iconfmt->w, iconfmt->h)];
+    NSImage *iconimg = [[NSImage alloc] initWithSize:NSMakeSize(icon->w, icon->h)];
     [iconimg addRepresentation:bitmap];
 
     /* A typical icon size is 22x22 on macOS. Failing to resize the icon
@@ -170,11 +176,15 @@ void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
 
     tray->statusItem.button.image = iconimg22;
 
-    SDL_DestroySurface(iconfmt);
+    SDL_DestroySurface(icon);
 }
 
 void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        return;
+    }
+
     if (tooltip) {
         tray->statusItem.button.toolTip = [NSString stringWithUTF8String:tooltip];
     } else {
@@ -184,6 +194,11 @@ void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
 
 SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        SDL_InvalidParamError("tray");
+        return NULL;
+    }
+
     SDL_TrayMenu *menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*menu));
     if (!menu) {
         return NULL;
@@ -206,11 +221,21 @@ void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
 
 SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        SDL_InvalidParamError("tray");
+        return NULL;
+    }
+
     return tray->menu;
 }
 
 SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     if (entry->submenu) {
         SDL_SetError("Tray entry submenu already exists");
         return NULL;
@@ -243,11 +268,21 @@ void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
 
 SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     return entry->submenu;
 }
 
 const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     if (size) {
         *size = menu->nEntries;
     }
@@ -293,6 +328,11 @@ void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
 
 SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     if (pos < -1 || pos > menu->nEntries) {
         SDL_InvalidParamError("pos");
         return NULL;
@@ -347,28 +387,44 @@ void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
 
 void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
 {
+    if (!entry) {
+        return;
+    }
+
     [entry->nsitem setTitle:[NSString stringWithUTF8String:label]];
 }
 
 const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     return [[entry->nsitem title] UTF8String];
 }
 
 void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
 {
+    if (!entry) {
+        return;
+    }
+
     [entry->nsitem setState:(checked ? NSControlStateValueOn : NSControlStateValueOff)];
 }
 
 bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        return false;
+    }
+
     return entry->nsitem.state == NSControlStateValueOn;
 }
 
 void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
 {
-    if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
-        SDL_SetError("Cannot update check for entry not created with SDL_TRAYENTRY_CHECKBOX");
+    if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
         return;
     }
 
@@ -377,8 +433,7 @@ void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
 
 bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
 {
-    if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
-        SDL_SetError("Cannot fetch check for entry not created with SDL_TRAYENTRY_CHECKBOX");
+    if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
         return false;
     }
 
@@ -387,6 +442,10 @@ bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
 
 void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
 {
+    if (!entry) {
+        return;
+    }
+
     entry->callback = callback;
     entry->userdata = userdata;
 }
@@ -408,25 +467,42 @@ void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
 
 SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     return entry->parent;
 }
 
 SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     return menu->parent_entry;
 }
 
 SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     return menu->parent_tray;
 }
 
 void SDL_DestroyTray(SDL_Tray *tray)
 {
-    if (!tray) {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
         return;
     }
 
+    SDL_UnregisterTray(tray);
+
     [[NSStatusBar systemStatusBar] removeStatusItem:tray->statusItem];
 
     if (tray->menu) {
@@ -434,8 +510,6 @@ 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 59d7e8a7bb0cf..55a1e645586f4 100644
--- a/src/tray/dummy/SDL_tray.c
+++ b/src/tray/dummy/SDL_tray.c
@@ -33,29 +33,27 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
 
 void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
 {
-    SDL_Unsupported();
 }
 
 void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
 {
-    SDL_Unsupported();
 }
 
 SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
 {
-    SDL_Unsupported();
+    SDL_InvalidParamError("tray");
     return NULL;
 }
 
 SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
 {
-    SDL_Unsupported();
+    SDL_InvalidParamError("tray");
     return NULL;
 }
 
 SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
 {
-    SDL_Unsupported();
+    SDL_InvalidParamError("entry");
     return NULL;
 }
 
@@ -66,57 +64,50 @@ SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
 
 const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size)
 {
-    SDL_Unsupported();
+    SDL_InvalidParamError("menu");
     return NULL;
 }
 
 void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
 {
-    SDL_Unsupported();
 }
 
 SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
 {
-    SDL_Unsupported();
+    SDL_InvalidParamError("menu");
     return NULL;
 }
 
 void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
 {
-    SDL_Unsupported();
 }
 
 const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
 {
-    SDL_Unsupported();
+    SDL_InvalidParamError("entry");
     return NULL;
 }
 
 void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
 {
-    SDL_Unsupported();
 }
 
 bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
 {
-    SDL_Unsupported();
-    return false;
+    return SDL_InvalidParamError("entry");
 }
 
 void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
 {
-    SDL_Unsupported();
 }
 
 bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
 {
-    SDL_Unsupported();
-    return false;
+    return SDL_InvalidParamError("entry");
 }
 
 void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
 {
-    SDL_Unsupported();
 }
 
 void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
@@ -125,25 +116,24 @@ void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
 
 SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
 {
-    SDL_Unsupported();
+    SDL_InvalidParamError("entry");
     return NULL;
 }
 
 SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
 {
-    SDL_Unsupported();
+    SDL_InvalidParamError("menu");
     return NULL;
 }
 
 SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
 {
-    SDL_Unsupported();
+    SDL_InvalidParamError("menu");
     return NULL;
 }
 
 void SDL_DestroyTray(SDL_Tray *tray)
 {
-    SDL_Unsupported();
 }
 
 #endif // !SDL_PLATFORM_MACOS
diff --git a/src/tray/unix/SDL_tray.c b/src/tray/unix/SDL_tray.c
index e1bd27c095a02..3d5fafab0c5c5 100644
--- a/src/tray/unix/SDL_tray.c
+++ b/src/tray/unix/SDL_tray.c
@@ -144,7 +144,7 @@ static void quit_gtk(void)
 
 static bool init_gtk(void)
 {
-
+    return true;
 }
 
 #else
@@ -434,13 +434,17 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
 
     app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE);
 
-    SDL_IncrementTrayCount();
+    SDL_RegisterTray(tray);
 
     return tray;
 }
 
 void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        return;
+    }
+
     if (*tray->icon_path) {
         SDL_RemovePath(tray->icon_path);
     }
@@ -463,6 +467,11 @@ void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
 
 SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        SDL_InvalidParamError("tray");
+        return NULL;
+    }
+
     tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu));
     if (!tray->menu) {
         return NULL;
@@ -481,11 +490,21 @@ SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
 
 SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        SDL_InvalidParamError("tray");
+        return NULL;
+    }
+
     return tray->menu;
 }
 
 SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     if (entry->submenu) {
         SDL_SetError("Tray entry submenu already exists");
         return NULL;
@@ -514,11 +533,21 @@ SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
 
 SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     return entry->submenu;
 }
 
 const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     if (size) {
         *size = menu->nEntries;
     }
@@ -563,6 +592,11 @@ void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
 
 SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     if (pos < -1 || pos > menu->nEntries) {
         SDL_InvalidParamError("pos");
         return NULL;
@@ -625,18 +659,26 @@ SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *la
 
 void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
 {
+    if (!entry) {
+        return;
+    }
+
     gtk_menu_item_set_label(GTK_MENU_ITEM(entry->item), label);
 }
 
 const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     return gtk_menu_item_get_label(GTK_MENU_ITEM(entry->item));
 }
 
 void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
 {
-    if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
-        SDL_SetError("Cannot update check for entry not created with SDL_TRAYENTRY_CHECKBOX");
+    if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
         return;
     }
 
@@ -647,8 +689,7 @@ void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
 
 bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
 {
-    if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
-        SDL_SetError("Cannot fetch check for entry not created with SDL_TRAYENTRY_CHECKBOX");
+    if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
         return false;
     }
 
@@ -657,16 +698,28 @@ bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
 
 void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
 {
+    if (!entry) {
+        return;
+    }
+
     gtk_widget_set_sensitive(entry->item, enabled);
 }
 
 bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        return false;
+    }
+
     return gtk_widget_get_sensitive(entry->item);
 }
 
 void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
 {
+    if (!entry) {
+        return;
+    }
+
     entry->callback = callback;
     entry->userdata = userdata;
 }
@@ -688,6 +741,11 @@ void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
 
 SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     return entry->parent;
 }
 
@@ -698,15 +756,22 @@ SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
 
 SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     return menu->parent_tray;
 }
 
 void SDL_DestroyTray(SDL_Tray *tray)
 {
-    if (!tray) {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
         return;
     }
 
+    SDL_UnregisterTray(tray);
+
     if (tray->menu) {
         DestroySDLMenu(tray->menu);
     }
@@ -725,8 +790,6 @@ void SDL_DestroyTray(SDL_Tray *tray)
 
     SDL_free(tray);
 
-    SDL_DecrementTrayCount();
-
     if (SDL_HasNoActiveTrays()) {
         gtk_main_quit();
         gtk_thread_active = false;
diff --git a/src/tray/windows/SDL_tray.c b/src/tray/windows/SDL_tray.c
index fe072eb017c0f..dbfbabafa27a6 100644
--- a/src/tray/windows/SDL_tray.c
+++ b/src/tray/windows/SDL_tray.c
@@ -252,13 +252,17 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
 
     SetWindowLongPtr(tray->hwnd, GWLP_USERDATA, (LONG_PTR) tray);
 
-    SDL_IncrementTrayCount();
+    SDL_RegisterTray(tray);
 
     return tray;
 }
 
 void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        return;
+    }
+
     if (tray->icon) {
         DestroyIcon(tray->icon);
     }
@@ -281,6 +285,10 @@ void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
 
 void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        return;
+    }
+
     if (tooltip) {
         wchar_t *tooltipw = WIN_UTF8ToStringW(tooltip);
         SDL_wcslcpy(tray->nid.szTip, tooltipw, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip));
@@ -294,6 +302,11 @@ void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
 
 SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        SDL_InvalidParamError("tray");
+        return NULL;
+    }
+
     tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu));
 
     if (!tray->menu) {
@@ -309,13 +322,24 @@ SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
 
 SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
 {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
+        SDL_InvalidParamError("tray");
+        return NULL;
+    }
+
     return tray->menu;
 }
 
 SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     if (!entry->submenu) {
         SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
+        return NULL;
     }
 
     return entry->submenu;
@@ -323,11 +347,21 @@ SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
 
 SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     return entry->submenu;
 }
 
 const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     if (size) {
         *size = menu->nEntries;
     }
@@ -376,6 +410,11 @@ void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
 
 SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     if (pos < -1 || pos > menu->nEntries) {
         SDL_InvalidParamError("pos");
         return NULL;
@@ -474,6 +513,10 @@ SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *la
 
 void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
 {
+    if (!entry) {
+        return;
+    }
+
     SDL_snprintf(entry->label_cache, sizeof(entry->label_cache), "%s", label);
 
     wchar_t *label_w = escape_label(label);
@@ -498,13 +541,17 @@ void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
 
 const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     return entry->label_cache;
 }
 
 void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
 {
-    if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
-        SDL_SetError("Can't check/uncheck tray entry not created with SDL_TRAYENTRY_CHECKBOX");
+    if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
         return;
     }
 
@@ -513,8 +560,7 @@ void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
 
 bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
 {
-    if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
-        SDL_SetError("Can't get check status of tray entry not created with SDL_TRAYENTRY_CHECKBOX");
+    if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
         return false;
     }
 
@@ -529,11 +575,19 @@ bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
 
 void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
 {
+    if (!entry) {
+        return;
+    }
+
     EnableMenuItem(entry->parent->hMenu, (UINT) entry->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
 }
 
 bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        return false;
+    }
+
     MENUITEMINFOW mii;
     mii.cbSize = sizeof(MENUITEMINFOW);
     mii.fMask = MIIM_STATE;
@@ -545,6 +599,10 @@ bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
 
 void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
 {
+    if (!entry) {
+        return;
+    }
+
     entry->callback = callback;
     entry->userdata = userdata;
 }
@@ -566,25 +624,42 @@ void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
 
 SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
 {
+    if (!entry) {
+        SDL_InvalidParamError("entry");
+        return NULL;
+    }
+
     return entry->parent;
 }
 
 SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     return menu->parent_entry;
 }
 
 SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
 {
+    if (!menu) {
+        SDL_InvalidParamError("menu");
+        return NULL;
+    }
+
     return menu->parent_tray;
 }
 
 void SDL_DestroyTray(SDL_Tray *tray)
 {
-    if (!tray) {
+    if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
         return;
     }
 
+    SDL_UnregisterTray(tray);
+
     Shell_NotifyIconW(NIM_DELETE, &tray->nid);
 
     if (tray->menu) {
@@ -600,6 +675,4 @@ void SDL_DestroyTray(SDL_Tray *tray)
     }
 
     SDL_free(tray);
-
-    SDL_DecrementTrayCount();
 }