From 01b9b0edb7fe67d333cc26744a525c9ee0fd578e Mon Sep 17 00:00:00 2001
From: Semphriss <[EMAIL REDACTED]>
Date: Tue, 24 Dec 2024 13:36:39 -0500
Subject: [PATCH] Add system tray support (#10873)
---
Android.mk | 1 +
CMakeLists.txt | 17 +
VisualC-GDK/SDL/SDL.vcxproj | 14 +-
VisualC-GDK/SDL/SDL.vcxproj.filters | 4 +
VisualC/SDL/SDL.vcxproj | 3 +
VisualC/SDL/SDL.vcxproj.filters | 9 +
include/SDL3/SDL.h | 1 +
include/SDL3/SDL_tray.h | 431 +++++++++++++++++
src/dynapi/SDL_dynapi.sym | 21 +
src/dynapi/SDL_dynapi_overrides.h | 21 +
src/dynapi/SDL_dynapi_procs.h | 21 +
src/tray/cocoa/SDL_tray.m | 458 ++++++++++++++++++
src/tray/dummy/SDL_tray.c | 139 ++++++
src/tray/unix/SDL_tray.c | 664 ++++++++++++++++++++++++++
src/tray/windows/SDL_tray.c | 589 +++++++++++++++++++++++
src/video/windows/SDL_surface_utils.c | 95 ++++
src/video/windows/SDL_surface_utils.h | 38 ++
test/CMakeLists.txt | 1 +
test/sdl-test_round.bmp | Bin 0 -> 147594 bytes
test/testtray.c | 599 +++++++++++++++++++++++
20 files changed, 3125 insertions(+), 1 deletion(-)
create mode 100644 include/SDL3/SDL_tray.h
create mode 100644 src/tray/cocoa/SDL_tray.m
create mode 100644 src/tray/dummy/SDL_tray.c
create mode 100644 src/tray/unix/SDL_tray.c
create mode 100644 src/tray/windows/SDL_tray.c
create mode 100644 src/video/windows/SDL_surface_utils.c
create mode 100644 src/video/windows/SDL_surface_utils.h
create mode 100644 test/sdl-test_round.bmp
create mode 100644 test/testtray.c
diff --git a/Android.mk b/Android.mk
index 56c817a2b62bf..3e584f9a5b0dc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -78,6 +78,7 @@ LOCAL_SRC_FILES := \
$(wildcard $(LOCAL_PATH)/src/time/unix/*.c) \
$(wildcard $(LOCAL_PATH)/src/timer/*.c) \
$(wildcard $(LOCAL_PATH)/src/timer/unix/*.c) \
+ $(wildcard $(LOCAL_PATH)/src/tray/dummy/*.c) \
$(wildcard $(LOCAL_PATH)/src/video/*.c) \
$(wildcard $(LOCAL_PATH)/src/video/android/*.c) \
$(wildcard $(LOCAL_PATH)/src/video/yuv2rgb/*.c))
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 695b6ed6654be..5a6cad028058d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1567,6 +1567,9 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU)
CheckVivante()
CheckVulkan()
CheckQNXScreen()
+
+ sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/unix/*.c")
+ set(HAVE_SDL_TRAY TRUE)
endif()
if(UNIX)
@@ -2075,6 +2078,9 @@ elseif(WINDOWS)
set(HAVE_RENDER_VULKAN TRUE)
endif()
endif()
+
+ sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/windows/*.c")
+ set(HAVE_SDL_TRAY TRUE)
endif()
if(SDL_HIDAPI)
@@ -2351,6 +2357,11 @@ elseif(APPLE)
endif()
endif()
endif()
+
+ if(MACOS)
+ sdl_glob_sources("${SDL3_SOURCE_DIR}/src/tray/cocoa/*.m")
+ set(HAVE_SDL_TRAY TRUE)
+ endif()
endif()
# Minimum version for $<LINK_LIBRARY:feature,library-list>
@@ -2973,6 +2984,8 @@ if(SDL_VIDEO)
endif()
endif()
+sdl_glob_sources(${SDL3_SOURCE_DIR}/src/tray/*.c)
+
if(SDL_GPU)
if(HAVE_D3D11_H)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/gpu/d3d11/*.c")
@@ -3055,6 +3068,10 @@ if(NOT HAVE_SDL_PROCESS)
set(SDL_PROCESS_DUMMY 1)
sdl_glob_sources(${SDL3_SOURCE_DIR}/src/process/dummy/*.c)
endif()
+if(NOT HAVE_SDL_TRAY)
+ set(SDL_TRAY_DUMMY 1)
+ sdl_glob_sources(${SDL3_SOURCE_DIR}/src/tray/dummy/*.c)
+endif()
if(NOT HAVE_CAMERA)
set(SDL_CAMERA_DRIVER_DUMMY 1)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c")
diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 008af3dff8026..2b6437fb7e055 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -592,6 +592,7 @@
<ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
<ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
<ClInclude Include="..\..\src\video\windows\SDL_msctf.h" />
+ <ClInclude Include="..\..\src\video\windows\SDL_surface_utils.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
@@ -827,6 +828,16 @@
<ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
<ClCompile Include="..\..\src\time\SDL_time.c" />
<ClCompile Include="..\..\src\time\windows\SDL_systime.c" />
+ <ClCompile Include="..\..\src\tray\dummy\SDL_tray.c">
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Gaming.Desktop.x64'">true</ExcludedFromBuild>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Desktop.x64'">true</ExcludedFromBuild>
+ </ClCompile>
+ <ClCompile Include="..\..\src\tray\windows\SDL_tray.c">
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Gaming.Xbox.Scarlett.x64'">true</ExcludedFromBuild>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Xbox.Scarlett.x64'">true</ExcludedFromBuild>
+ <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\video\dummy\SDL_nullevents.c" />
<ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
@@ -855,6 +866,7 @@
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c" />
<ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
<ClCompile Include="..\..\src\video\SDL_yuv.c" />
+ <ClCompile Include="..\..\src\video\windows\SDL_surface_utils.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
@@ -889,4 +901,4 @@
<ImportGroup Label="ExtensionTargets">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
</ImportGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 230c95d2c22c6..5ae2609c8042d 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -181,6 +181,7 @@
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c" />
<ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
<ClCompile Include="..\..\src\video\SDL_yuv.c" />
+ <ClCompile Include="..\..\src\video\windows\SDL_surface_utils.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
@@ -217,6 +218,8 @@
<ClCompile Include="..\..\src\storage\SDL_storage.c" />
<ClCompile Include="..\..\src\time\SDL_time.c" />
<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\video\yuv2rgb\yuv_rgb_lsx.c" />
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_sse.c" />
<ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_std.c" />
@@ -435,6 +438,7 @@
<ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
<ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
<ClInclude Include="..\..\src\video\windows\SDL_msctf.h" />
+ <ClInclude Include="..\..\src\video\windows\SDL_surface_utils.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 306a883d04d62..42d0c13daaec1 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -489,6 +489,7 @@
<ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
<ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
<ClInclude Include="..\..\src\video\windows\SDL_msctf.h" />
+ <ClInclude Include="..\..\src\video\windows\SDL_surface_utils.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
<ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
@@ -671,6 +672,7 @@
<ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
<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\video\dummy\SDL_nullevents.c" />
<ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
@@ -701,6 +703,7 @@
<ClCompile Include="..\..\src\video\SDL_video_unsupported.c" />
<ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
<ClCompile Include="..\..\src\video\SDL_yuv.c" />
+ <ClCompile Include="..\..\src\video\windows\SDL_surface_utils.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
<ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index aa86194fabc24..d1d24865f57bb 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -690,6 +690,9 @@
<ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb_std_func.h">
<Filter>video\yuv2rgb</Filter>
</ClInclude>
+ <ClInclude Include="..\..\src\video\windows\SDL_surface_utils.h">
+ <Filter>video\windows</Filter>
+ </ClInclude>
<ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h">
<Filter>video\windows</Filter>
</ClInclude>
@@ -1229,6 +1232,9 @@
<ClCompile Include="..\..\src\time\windows\SDL_systime.c">
<Filter>time\windows</Filter>
</ClCompile>
+ <ClCompile Include="..\..\src\tray\windows\SDL_tray.c">
+ <Filter>video</Filter>
+ </ClCompile>
<ClCompile Include="..\..\src\video\SDL_RLEaccel.c">
<Filter>video</Filter>
</ClCompile>
@@ -1301,6 +1307,9 @@
<ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c">
<Filter>video\dummy</Filter>
</ClCompile>
+ <ClCompile Include="..\..\src\video\windows\SDL_surface_utils.c">
+ <Filter>video\windows</Filter>
+ </ClCompile>
<ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c">
<Filter>video\windows</Filter>
</ClCompile>
diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h
index e36c67b6db163..0b4388eb5ccac 100644
--- a/include/SDL3/SDL.h
+++ b/include/SDL3/SDL.h
@@ -81,6 +81,7 @@
#include <SDL3/SDL_thread.h>
#include <SDL3/SDL_time.h>
#include <SDL3/SDL_timer.h>
+#include <SDL3/SDL_tray.h>
#include <SDL3/SDL_touch.h>
#include <SDL3/SDL_version.h>
#include <SDL3/SDL_video.h>
diff --git a/include/SDL3/SDL_tray.h b/include/SDL3/SDL_tray.h
new file mode 100644
index 0000000000000..b7c50207062b7
--- /dev/null
+++ b/include/SDL3/SDL_tray.h
@@ -0,0 +1,431 @@
+/*
+ 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.
+*/
+
+/**
+ * # CategoryTray
+ *
+ * System tray menu support.
+ */
+
+#ifndef SDL_tray_h_
+#define SDL_tray_h_
+
+#include <SDL3/SDL_error.h>
+
+#include <SDL3/SDL_video.h>
+
+#include <SDL3/SDL_begin_code.h>
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct SDL_Tray SDL_Tray;
+typedef struct SDL_TrayMenu SDL_TrayMenu;
+typedef struct SDL_TrayEntry SDL_TrayEntry;
+
+/**
+ * Flags that control the creation of system tray entries.
+ *
+ * Some of these flags are required; exactly one of them must be specified at
+ * the time a tray entry is created. Other flags are optional; zero or more of
+ * those can be OR'ed together with the required flag.
+ *
+ * \since This datatype is available since SDL 3.0.0.
+ *
+ * \sa SDL_InsertTrayEntryAt
+ */
+typedef Uint32 SDL_TrayEntryFlags;
+
+#define SDL_TRAYENTRY_BUTTON 0x00000001u /**< Make the entry a simple button. Required. */
+#define SDL_TRAYENTRY_CHECKBOX 0x00000002u /**< Make the entry a checkbox. Required. */
+#define SDL_TRAYENTRY_SUBMENU 0x00000004u /**< Prepare the entry to have a submenu. Required */
+#define SDL_TRAYENTRY_DISABLED 0x80000000u /**< Make the entry disabled. Optional. */
+#define SDL_TRAYENTRY_CHECKED 0x40000000u /**< Make the entry checked. This is valid only for checkboxes. Optional. */
+
+/**
+ * A callback that is invoked when a tray entry is selected.
+ *
+ * \param userdata an optional pointer to pass extra data to the callback when
+ * it will be invoked.
+ * \param entry the tray entry that was selected.
+ *
+ * \sa SDL_SetTrayEntryCallback
+ */
+typedef void (SDLCALL *SDL_TrayCallback)(void *userdata, SDL_TrayEntry *entry);
+
+/**
+ * Create an icon to be placed in the operating system's tray, or equivalent.
+ *
+ * Many platforms advise not using a system tray unless persistence is a
+ * necessary feature. Avoid needlessly creating a tray icon, as the user may
+ * feel like it clutters their interface.
+ *
+ * Using tray icons require the video subsystem.
+ *
+ * \param icon a surface to be used as icon. May be NULL.
+ * \param tooltip a tooltip to be displayed when the mouse hovers the icon. Not
+ * supported on all platforms. May be NULL.
+ * \returns The newly created system tray icon.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateTrayMenu
+ * \sa SDL_GetTrayMenu
+ * \sa SDL_DestroyTray
+ */
+extern SDL_DECLSPEC SDL_Tray *SDLCALL SDL_CreateTray(SDL_Surface *icon, const char *tooltip);
+
+/**
+ * Updates the system tray icon's icon.
+ *
+ * \param tray the tray icon to be updated.
+ * \param icon the new icon. May be NULL.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateTray
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon);
+
+/**
+ * Updates the system tray icon's tooltip.
+ *
+ * \param tray the tray icon to be updated.
+ * \param tooltip the new tooltip. May be NULL.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateTray
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip);
+
+/**
+ * Create a menu for a system tray.
+ *
+ * This should be called at most once per tray icon.
+ *
+ * This function does the same thing as SDL_CreateTraySubmenu(), except that it
+ * takes a SDL_Tray instead of a SDL_TrayEntry.
+ *
+ * A menu does not need to be destroyed; it will be destroyed with the tray.
+ *
+ * \param tray the tray to bind the menu to.
+ * \returns the newly created menu.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateTray
+ * \sa SDL_GetTrayMenu
+ * \sa SDL_GetTrayMenuParentTray
+ */
+extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_CreateTrayMenu(SDL_Tray *tray);
+
+/**
+ * Create a submenu for a system tray entry.
+ *
+ * This should be called at most once per tray entry.
+ *
+ * This function does the same thing as SDL_CreateTrayMenu, except that it
+ * takes a SDL_TrayEntry instead of a SDL_Tray.
+ *
+ * A menu does not need to be destroyed; it will be destroyed with the tray.
+ *
+ * \param entry the tray entry to bind the menu to.
+ * \returns the newly created menu.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_InsertTrayEntryAt
+ * \sa SDL_GetTraySubmenu
+ * \sa SDL_GetTrayMenuParentEntry
+ */
+extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_CreateTraySubmenu(SDL_TrayEntry *entry);
+
+/**
+ * Gets a previously created tray menu.
+ *
+ * You should have called SDL_CreateTrayMenu() on the tray object. This
+ * function allows you to fetch it again later.
+ *
+ * This function does the same thing as SDL_GetTraySubmenu(), except that it
+ * takes a SDL_Tray instead of a SDL_TrayEntry.
+ *
+ * A menu does not need to be destroyed; it will be destroyed with the tray.
+ *
+ * \param tray the tray entry to bind the menu to.
+ * \returns the newly created menu.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateTray
+ * \sa SDL_CreateTrayMenu
+ */
+extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_GetTrayMenu(SDL_Tray *tray);
+
+/**
+ * Gets a previously created tray entry submenu.
+ *
+ * You should have called SDL_CreateTraySubenu() on the entry object. This
+ * function allows you to fetch it again later.
+ *
+ * This function does the same thing as SDL_GetTrayMenu(), except that it
+ * takes a SDL_TrayEntry instead of a SDL_Tray.
+ *
+ * A menu does not need to be destroyed; it will be destroyed with the tray.
+ *
+ * \param entry the tray entry to bind the menu to.
+ * \returns the newly created menu.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_InsertTrayEntryAt
+ * \sa SDL_CreateTraySubmenu
+ */
+extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_GetTraySubmenu(SDL_TrayEntry *entry);
+
+/**
+ * Returns a list of entries in the menu, in order.
+ *
+ * \param menu The menu to get entries from.
+ * \param size An optional pointer to obtain the number of entries in the menu.
+ * \returns the entries within the given menu. The pointer becomes invalid when
+ * any function that inserts or deletes entries in the menu is called.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_RemoveTrayEntry
+ * \sa SDL_InsertTrayEntryAt
+ */
+extern SDL_DECLSPEC const SDL_TrayEntry **SDLCALL SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size);
+
+/**
+ * Removes a tray entry.
+ *
+ * \param entry The entry to be deleted.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetTrayEntries
+ * \sa SDL_InsertTrayEntryAt
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_RemoveTrayEntry(SDL_TrayEntry *entry);
+
+/**
+ * Insert a tray entry at a given position.
+ *
+ * If label is NULL, the entry will be a separator. Many functions won't work
+ * for an entry that is a separator.
+ *
+ * An entry does not need to be destroyed; it will be destroyed with the tray.
+ *
+ * \param menu the menu to append the entry to.
+ * \param pos the desired position for the new entry. Entries at or following
+ * this place will be moved. If pos is -1, the entry is appended.
+ * \param label the text to be displayed on the entry, or NULL for a separator.
+ * \param flags a combination of flags, some of which are mandatory.
+ * \returns the newly created entry, or NULL if pos is out of bounds.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_TrayEntryFlags
+ * \sa SDL_GetTrayEntries
+ * \sa SDL_RemoveTrayEntry
+ * \sa SDL_GetTrayEntryParent
+ */
+extern SDL_DECLSPEC SDL_TrayEntry *SDLCALL SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags);
+
+/**
+ * Sets the label of an entry.
+ *
+ * An entry cannot change between a separator and an ordinary entry; that is,
+ * it is not possible to set a non-NULL label on an entry that has a NULL label
+ * (separators), or to set a NULL label to an entry that has a non-NULL label.
+ * The function will silently fail if that happens.
+ *
+ * \param entry the entry to be updated.
+ * \param label the new label for the entry.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetTrayEntries
+ * \sa SDL_InsertTrayEntryAt
+ * \sa SDL_GetTrayEntryLabel
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label);
+
+/**
+ * Gets the label of an entry.
+ *
+ * If the returned value is NULL, the entry is a separator.
+ *
+ * \param entry the entry to be read.
+ * \returns the label of the entry.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetTrayEntries
+ * \sa SDL_InsertTrayEntryAt
+ * \sa SDL_SetTrayEntryLabel
+ */
+extern SDL_DECLSPEC const char *SDLCALL SDL_GetTrayEntryLabel(SDL_TrayEntry *entry);
+
+/**
+ * Sets whether or not an entry is checked.
+ *
+ * The entry must have been created with the SDL_TRAYENTRY_CHECKBOX flag.
+ *
+ * \param entry the entry to be updated.
+ * \param checked SDL_TRUE if the entry should be checked; SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetTrayEntries
+ * \sa SDL_InsertTrayEntryAt
+ * \sa SDL_GetTrayEntryChecked
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked);
+
+/**
+ * Gets whether or not an entry is checked.
+ *
+ * The entry must have been created with the SDL_TRAYENTRY_CHECKBOX flag.
+ *
+ * \param entry the entry to be read.
+ * \returns SDL_TRUE if the entry is checked; SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetTrayEntries
+ * \sa SDL_InsertTrayEntryAt
+ * \sa SDL_SetTrayEntryChecked
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_GetTrayEntryChecked(SDL_TrayEntry *entry);
+
+/**
+ * Sets whether or not an entry is enabled.
+ *
+ * \param entry the entry to be updated.
+ * \param enabled SDL_TRUE if the entry should be enabled; SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetTrayEntries
+ * \sa SDL_InsertTrayEntryAt
+ * \sa SDL_GetTrayEntryEnabled
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled);
+
+/**
+ * Gets whether or not an entry is enabled.
+ *
+ * \param entry the entry to be read.
+ * \returns SDL_TRUE if the entry is enabled; SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetTrayEntries
+ * \sa SDL_InsertTrayEntryAt
+ * \sa SDL_SetTrayEntryEnabled
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry);
+
+/**
+ * Sets a callback to be invoked when the entry is selected.
+ *
+ * \param entry the entry to be updated.
+ * \param callback a callback to be invoked when the entry is selected.
+ * \param userdata an optional pointer to pass extra data to the callback when
+ * it will be invoked.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetTrayEntries
+ * \sa SDL_InsertTrayEntryAt
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata);
+
+/**
+ * Destroys a tray object.
+ *
+ * This also destroys all associated menus and entries.
+ *
+ * \param tray the tray icon to be destroyed.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateTray
+ */
+extern SDL_DECLSPEC void SDLCALL SDL_DestroyTray(SDL_Tray *tray);
+
+/**
+ * Gets the menu contianing a certain tray entry.
+ *
+ * \param entry the entry for which to get the parent menu.
+ * \returns the parent menu.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_InsertTrayEntryAt
+ */
+extern SDL_DECLSPEC SDL_TrayMenu *SDLCALL SDL_GetTrayEntryParent(SDL_TrayEntry *entry);
+
+/**
+ * Gets the entry for which the menu is a submenu, if the current menu is a
+ * submenu.
+ *
+ * Either this function or SDL_GetTrayMenuParentTray() will return non-NULL for
+ * any given menu.
+ *
+ * \param menu the menu for which to get the parent entry.
+ * \returns the parent entry, or NULL if this menu is not a submenu.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateTraySubmenu
+ * \sa SDL_GetTrayMenuParentTray
+ */
+extern SDL_DECLSPEC SDL_TrayEntry *SDLCALL SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu);
+
+/**
+ * Gets the tray for which this menu is the first-level menu, if the current
+ * menu isn't a submenu.
+ *
+ * Either this function or SDL_GetTrayMenuParentEntry() will return non-NULL for
+ * any given menu.
+ *
+ * \param menu the menu for which to get the parent enttrayry.
+ * \returns the parent tray, or NULL if this menu is a submenu.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreateTrayMenu
+ * \sa SDL_GetTrayMenuParentEntry
+ */
+extern SDL_DECLSPEC SDL_Tray *SDLCALL SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu);
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include <SDL3/SDL_close_code.h>
+
+#endif /* SDL_tray_h_ */
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index f3fe1c33c2ba7..37a018e75f998 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1208,6 +1208,27 @@ SDL3_0.0.0 {
SDL_RenderTextureAffine;
SDL_WaitAndAcquireGPUSwapchainTexture;
SDL_RenderDebugTextFormat;
+ SDL_CreateTray;
+ SDL_SetTrayIcon;
+ SDL_SetTrayTooltip;
+ SDL_CreateTrayMenu;
+ SDL_CreateTraySubmenu;
+ SDL_GetTrayMenu;
+ SDL_GetTraySubmenu;
+ SDL_GetTrayEntries;
+ SDL_RemoveTrayEntry;
+ SDL_InsertTrayEntryAt;
+ SDL_SetTrayEntryLabel;
+ SDL_GetTrayEntryLabel;
+ SDL_SetTrayEntryChecked;
+ SDL_GetTrayEntryChecked;
+ SDL_SetTrayEntryEnabled;
+ SDL_GetTrayEntryEnabled;
+ SDL_SetTrayEntryCallback;
+ SDL_DestroyTray;
+ SDL_GetTrayEntryParent;
+ SDL_GetTrayMenuParentEntry;
+ SDL_GetTrayMenuParentTray;
# 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 12e77270ca11d..9fa340a26b9b8 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1233,3 +1233,24 @@
#define SDL_RenderTextureAffine SDL_RenderTextureAffine_REAL
#define SDL_WaitAndAcquireGPUSwapchainTexture SDL_WaitAndAcquireGPUSwapchainTexture_REAL
#define SDL_RenderDebugTextFormat SDL_RenderDebugTextFormat_REAL
+#define SDL_CreateTray SDL_CreateTray_REAL
+#define SDL_SetTrayIcon SDL_SetTrayIcon_REAL
+#define SDL_SetTrayTooltip SDL_SetTrayTooltip_REAL
+#define SDL_CreateTrayMenu SDL_CreateTrayMenu_REAL
+#define SDL_CreateTraySubmenu SDL_CreateTraySubmenu_REAL
+#define SDL_GetTrayMenu SDL_GetTrayMenu_REAL
+#define SDL_GetTraySubmenu SDL_GetTraySubmenu_REAL
+#define SDL_GetTrayEntries SDL_GetTrayEntries_REAL
+#define SDL_RemoveTrayEntry SDL_RemoveTrayEntry_REAL
+#define SDL_InsertTrayEntryAt SDL_InsertTrayEntryAt_REAL
+#define SDL_SetTrayEntryLabel SDL_SetTrayEntryLabel_REAL
+#define SDL_GetTrayEntryLabel SDL_GetTrayEntryLabel_REAL
+#define SDL_SetTrayEntryChecked SDL_SetTrayEntryChecked_REAL
+#define SDL_GetTrayEntryChecked SDL_GetTrayEntryChecked_REAL
+#define SDL_SetTrayEntryEnabled SDL_SetTrayEntryEnabled_REAL
+#define SDL_GetTrayEntryEnabled SDL_GetTrayEntryEnabled_REAL
+#define SDL_SetTrayEntryCallback SDL_SetTrayEntryCallback_REAL
+#define SDL_DestroyTray SDL_DestroyTray_REAL
+#define SDL_GetTrayEntryParent SDL_GetTrayEntryParent_REAL
+#define SDL_GetTrayMenuParentEntry SDL_GetTrayMenuParentEntry_REAL
+#define SDL_GetTrayMenuParentTray SDL_GetTrayMenuParentTray_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 189158744e795..eec6229fff699 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1241,3 +1241,24 @@ SDL_DYNAPI_PROC(bool,SDL_WaitAndAcquireGPUSwapchainTexture,(SDL_GPUCommandBuffer
#ifndef SDL_DYNAPI_PROC_NO_VARARGS
SDL_DYNAPI_PROC(bool,SDL_RenderDebugTextFormat,(SDL_Renderer *a,float b,float c,SDL_PRINTF_FORMAT_STRING const char *d,...),(a,b,c,d),return)
#endif
+SDL_DYNAPI_PROC(SDL_Tray*,SDL_CreateTray,(SDL_Surface *a,const char *b),(a,b),return)
+SDL_DYNAPI_PROC(void,SDL_SetTrayIcon,(SDL_Tray *a,SDL_Surface *b),(a,b),)
+SDL_DYNAPI_PROC(void,SDL_SetTrayTooltip,(SDL_Tray *a,const char *b),(a,b),)
+SDL_DYNAPI_PROC(SDL_TrayMenu*,SDL_CreateTrayMenu,(SDL_Tray *a),(a),return)
+SDL_DYNAPI_PROC(SDL_TrayMenu*,SDL_CreateTraySubmenu,(SDL_TrayEntry *a),(a),return)
+SDL_DYNAPI_PROC(SDL_TrayMenu*,SDL_GetTrayMenu,(SDL_Tray *a),(a),return)
+SDL_DYNAPI_PROC(SDL_TrayMenu*,SDL_GetTraySubmenu,(SDL_TrayEntry *a),(a),return)
+SDL_DYNAPI_PROC(const SDL_TrayEntry**,SDL_GetTrayEntries,(SDL_TrayMenu *a,int *b),(a,b),return)
+SDL_DYNAPI_PROC(void,SDL_RemoveTrayEntry,(SDL_TrayEntry *a),(a),)
+SDL_DYNAPI_PROC(SDL_TrayEntry*,SDL_InsertTrayEntryAt,(SDL_TrayMenu *a,int b,const char *c,SDL_TrayEntryFlags d),(a,b,c,d),return)
+SDL_DYNAPI_PROC(void,SDL_SetTrayEntryLabel,(SDL_TrayEntry *a,const char *b),(a,b),)
+SDL_DYNAPI_PROC(const char*,SDL_GetTrayEntryLabel,(SDL_TrayEntry *a),(a),return)
+SDL_DYNAPI_PROC(void,SDL_SetTrayEntryChecked,(SDL_TrayEntry *a,bool b),(a,b),)
+SDL_DYNAPI_PROC(bool,SDL_GetTrayEntryChecked,(SDL_TrayEntry *a),(a),return)
+SDL_DYNAPI_PROC(void,SDL_SetTrayEntryEnabled,(SDL_TrayEntry *a,bool b),(a,b),)
+SDL_DYNAPI_PROC(bool,SDL_GetTrayEntryEnabled,(SDL_TrayEntry *a),(a),return)
+SDL_DYNAPI_PROC(void,SDL_SetTrayEntryCallback,(SDL_TrayEntry *a,SDL_TrayCallback b,void *c),(a,b,c),)
+SDL_DYNAPI_PROC(void,SDL_DestroyTray,(SDL_Tray *a),(a),)
+SDL_DYNAPI_PROC(SDL_TrayMenu*,SDL_GetTrayEntryParent,(SDL_TrayEntry *a),(a),return)
+SDL_DYNAPI_PROC(SDL_TrayEntry*,SDL_GetTrayMenuParentEntry,(SDL_TrayMenu *a),(a),return)
+SDL_DYNAPI_PROC(SDL_Tray*,SDL_GetTrayMenuParentTray,(SDL_TrayMenu *a),(a),return)
diff --git a/src/tray/cocoa/SDL_tray.m b/src/tray/cocoa/SDL_tray.m
new file mode 100644
index 0000000000000..515ee6527cb5c
--- /dev/null
+++ b/src/tray/cocoa/SDL_tray.m
@@ -0,0 +1,458 @@
+/*
+ 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
+
(Patch may be truncated, please check the link at the top of this post.)