From 70e16a8d13f8b96a798840cbcce668f86c197dff Mon Sep 17 00:00:00 2001
From: Jesse Chounard <[EMAIL REDACTED]>
Date: Wed, 4 Feb 2026 14:50:00 -0600
Subject: [PATCH] tray: Add icon click callbacks for Windows and macOS (#14964)
---
include/SDL3/SDL_tray.h | 75 ++++++++++++++
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 | 158 +++++++++++++++++++++++++++++-
src/tray/dummy/SDL_tray.c | 6 ++
src/tray/unix/SDL_tray.c | 22 ++++-
src/tray/windows/SDL_tray.c | 90 +++++++++++++++--
8 files changed, 343 insertions(+), 11 deletions(-)
diff --git a/include/SDL3/SDL_tray.h b/include/SDL3/SDL_tray.h
index 688278a1d3225..71a85c201fc16 100644
--- a/include/SDL3/SDL_tray.h
+++ b/include/SDL3/SDL_tray.h
@@ -34,6 +34,7 @@
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_error.h>
+#include <SDL3/SDL_properties.h>
#include <SDL3/SDL_surface.h>
#include <SDL3/SDL_video.h>
@@ -96,6 +97,23 @@ typedef Uint32 SDL_TrayEntryFlags;
*/
typedef void (SDLCALL *SDL_TrayCallback)(void *userdata, SDL_TrayEntry *entry);
+/**
+ * A callback that is invoked when the tray icon is clicked.
+ *
+ * \param userdata an optional pointer to pass extra data to the callback when
+ * it will be invoked. May be NULL.
+ * \param tray the tray that was clicked.
+ * \returns true to show the tray menu after the callback returns, false to
+ * skip showing the menu. This return value is only used for left
+ * and right click callbacks; other mouse events ignore the return
+ * value.
+ *
+ * \since This datatype is available since SDL 3.6.0.
+ *
+ * \sa SDL_CreateTrayWithProperties
+ */
+typedef bool (SDLCALL *SDL_TrayClickCallback)(void *userdata, SDL_Tray *tray);
+
/**
* Create an icon to be placed in the operating system's tray, or equivalent.
*
@@ -114,12 +132,69 @@ typedef void (SDLCALL *SDL_TrayCallback)(void *userdata, SDL_TrayEntry *entry);
*
* \since This function is available since SDL 3.2.0.
*
+ * \sa SDL_CreateTrayWithProperties
* \sa SDL_CreateTrayMenu
* \sa SDL_GetTrayMenu
* \sa SDL_DestroyTray
*/
extern SDL_DECLSPEC SDL_Tray * SDLCALL SDL_CreateTray(SDL_Surface *icon, const char *tooltip);
+/**
+ * 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.
+ *
+ * These are the supported properties:
+ *
+ * - `SDL_PROP_TRAY_CREATE_ICON_POINTER`: an SDL_Surface to be used as the
+ * tray icon. May be NULL.
+ * - `SDL_PROP_TRAY_CREATE_TOOLTIP_STRING`: a tooltip to be displayed when
+ * the mouse hovers the icon in UTF-8 encoding. Not supported on all
+ * platforms. May be NULL.
+ * - `SDL_PROP_TRAY_CREATE_USERDATA_POINTER`: an optional pointer to
+ * associate with the tray, which will be passed to click callbacks.
+ * May be NULL.
+ * - `SDL_PROP_TRAY_CREATE_LEFTCLICK_CALLBACK_POINTER`: an SDL_TrayClickCallback
+ * to be invoked when the tray icon is left-clicked. Not supported on all
+ * platforms. The callback should return true to show the default menu, or
+ * false to skip showing it. May be NULL.
+ * - `SDL_PROP_TRAY_CREATE_RIGHTCLICK_CALLBACK_POINTER`: an SDL_TrayClickCallback
+ * to be invoked when the tray icon is right-clicked. Not supported on all
+ * platforms. The callback should return true to show the default menu, or
+ * false to skip showing it. May be NULL.
+ * - `SDL_PROP_TRAY_CREATE_MIDDLECLICK_CALLBACK_POINTER`: an SDL_TrayClickCallback
+ * to be invoked when the tray icon is middle-clicked. Not supported on all
+ * platforms. May be NULL.
+ * - `SDL_PROP_TRAY_CREATE_DOUBLECLICK_CALLBACK_POINTER`: an SDL_TrayClickCallback
+ * to be invoked when the tray icon is double-clicked. Not supported on all
+ * platforms. May be NULL.
+ *
+ * \param props the properties to use.
+ * \returns The newly created system tray icon.
+ *
+ * \threadsafety This function should only be called on the main thread.
+ *
+ * \since This function is available since SDL 3.6.0.
+ *
+ * \sa SDL_CreateTray
+ * \sa SDL_CreateTrayMenu
+ * \sa SDL_GetTrayMenu
+ * \sa SDL_DestroyTray
+ */
+extern SDL_DECLSPEC SDL_Tray * SDLCALL SDL_CreateTrayWithProperties(SDL_PropertiesID props);
+
+#define SDL_PROP_TRAY_CREATE_ICON_POINTER "SDL.tray.create.icon"
+#define SDL_PROP_TRAY_CREATE_TOOLTIP_STRING "SDL.tray.create.tooltip"
+#define SDL_PROP_TRAY_CREATE_USERDATA_POINTER "SDL.tray.create.userdata"
+#define SDL_PROP_TRAY_CREATE_LEFTCLICK_CALLBACK_POINTER "SDL.tray.create.leftclick_callback"
+#define SDL_PROP_TRAY_CREATE_RIGHTCLICK_CALLBACK_POINTER "SDL.tray.create.rightclick_callback"
+#define SDL_PROP_TRAY_CREATE_MIDDLECLICK_CALLBACK_POINTER "SDL.tray.create.middleclick_callback"
+#define SDL_PROP_TRAY_CREATE_DOUBLECLICK_CALLBACK_POINTER "SDL.tray.create.doubleclick_callback"
+
/**
* Updates the system tray icon's icon.
*
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 2f2ff3998bbc8..86ff0a684b276 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1279,6 +1279,7 @@ SDL3_0.0.0 {
SDL_OpenXR_LoadLibrary;
SDL_OpenXR_UnloadLibrary;
SDL_OpenXR_GetXrGetInstanceProcAddr;
+ SDL_CreateTrayWithProperties;
# 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 fd7627cf325da..acee86998e2ce 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1305,3 +1305,4 @@
#define SDL_OpenXR_LoadLibrary SDL_OpenXR_LoadLibrary_REAL
#define SDL_OpenXR_UnloadLibrary SDL_OpenXR_UnloadLibrary_REAL
#define SDL_OpenXR_GetXrGetInstanceProcAddr SDL_OpenXR_GetXrGetInstanceProcAddr_REAL
+#define SDL_CreateTrayWithProperties SDL_CreateTrayWithProperties_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index bd44244021075..68dca5a19233b 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1313,3 +1313,4 @@ SDL_DYNAPI_PROC(XrResult,SDL_DestroyGPUXRSwapchain,(SDL_GPUDevice *a,XrSwapchain
SDL_DYNAPI_PROC(bool,SDL_OpenXR_LoadLibrary,(void),(),return)
SDL_DYNAPI_PROC(void,SDL_OpenXR_UnloadLibrary,(void),(),)
SDL_DYNAPI_PROC(PFN_xrGetInstanceProcAddr,SDL_OpenXR_GetXrGetInstanceProcAddr,(void),(),return)
+SDL_DYNAPI_PROC(SDL_Tray*,SDL_CreateTrayWithProperties,(SDL_PropertiesID a),(a),return)
diff --git a/src/tray/cocoa/SDL_tray.m b/src/tray/cocoa/SDL_tray.m
index 39442e4faa102..860a4eb5a25fb 100644
--- a/src/tray/cocoa/SDL_tray.m
+++ b/src/tray/cocoa/SDL_tray.m
@@ -28,7 +28,18 @@
#include "../SDL_tray_utils.h"
#include "../../video/SDL_surface_c.h"
-/* applicationDockMenu */
+/* Forward declaration */
+struct SDL_Tray;
+
+/* Objective-C helper class to handle status item button clicks */
+@interface SDLTrayClickHandler : NSObject
+@property (nonatomic, assign) struct SDL_Tray *tray;
+@property (nonatomic, assign) NSTimeInterval lastLeftClickTime;
+@property (nonatomic, strong) id middleClickMonitor;
+- (void)handleClick:(id)sender;
+- (void)startMonitoringMiddleClicks;
+- (void)stopMonitoringMiddleClicks;
+@end
struct SDL_TrayMenu {
NSMenu *nsmenu;
@@ -56,8 +67,105 @@
NSStatusItem *statusItem;
SDL_TrayMenu *menu;
+ SDLTrayClickHandler *clickHandler;
+
+ void *userdata;
+ SDL_TrayClickCallback left_click_callback;
+ SDL_TrayClickCallback right_click_callback;
+ SDL_TrayClickCallback middle_click_callback;
+ SDL_TrayClickCallback double_click_callback;
};
+@implementation SDLTrayClickHandler
+
+- (void)handleClick:(id)sender
+{
+ if (!self.tray) {
+ return;
+ }
+
+ NSEvent *event = [NSApp currentEvent];
+ NSUInteger buttonNumber = [event buttonNumber];
+
+ bool show_menu = false;
+
+ if (buttonNumber == 0) {
+ /* Left click - check for double-click ourselves */
+ NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
+ NSTimeInterval doubleClickInterval = [NSEvent doubleClickInterval];
+
+ if (self.tray->double_click_callback && (now - self.lastLeftClickTime) <= doubleClickInterval) {
+ /* Double-click */
+ self.tray->double_click_callback(self.tray->userdata, self.tray);
+ self.lastLeftClickTime = 0; /* Reset to prevent triple-click from triggering another double */
+ } else {
+ /* Single left click */
+ self.lastLeftClickTime = now;
+ if (self.tray->left_click_callback) {
+ show_menu = self.tray->left_click_callback(self.tray->userdata, self.tray);
+ } else {
+ show_menu = true;
+ }
+ }
+ } else if (buttonNumber == 1) {
+ /* Right click */
+ if (self.tray->right_click_callback) {
+ show_menu = self.tray->right_click_callback(self.tray->userdata, self.tray);
+ } else {
+ show_menu = true;
+ }
+ } else if (buttonNumber == 2) {
+ /* Middle click */
+ if (self.tray->middle_click_callback) {
+ self.tray->middle_click_callback(self.tray->userdata, self.tray);
+ }
+ }
+
+ if (show_menu && self.tray->menu) {
+ [self.tray->statusItem popUpStatusItemMenu:self.tray->menu->nsmenu];
+ }
+}
+
+- (void)startMonitoringMiddleClicks
+{
+ if (self.middleClickMonitor) {
+ return;
+ }
+
+ __weak SDLTrayClickHandler *weakSelf = self;
+ self.middleClickMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskOtherMouseUp handler:^NSEvent *(NSEvent *event) {
+ SDLTrayClickHandler *strongSelf = weakSelf;
+ if (!strongSelf || !strongSelf.tray || [event buttonNumber] != 2) {
+ return event;
+ }
+
+ /* Check if the click is within the status item's button bounds */
+ NSPoint clickLocation = [event locationInWindow];
+ NSWindow *statusItemWindow = strongSelf.tray->statusItem.button.window;
+
+ if (statusItemWindow && event.window == statusItemWindow) {
+ NSPoint localPoint = [strongSelf.tray->statusItem.button convertPoint:clickLocation fromView:nil];
+ if (NSPointInRect(localPoint, strongSelf.tray->statusItem.button.bounds)) {
+ if (strongSelf.tray->middle_click_callback) {
+ strongSelf.tray->middle_click_callback(strongSelf.tray->userdata, strongSelf.tray);
+ }
+ }
+ }
+
+ return event;
+ }];
+}
+
+- (void)stopMonitoringMiddleClicks
+{
+ if (self.middleClickMonitor) {
+ [NSEvent removeMonitor:self.middleClickMonitor];
+ self.middleClickMonitor = nil;
+ }
+}
+
+@end
+
static void DestroySDLMenu(SDL_TrayMenu *menu)
{
for (int i = 0; i < menu->nEntries; i++) {
@@ -82,13 +190,16 @@ void SDL_UpdateTrays(void)
{
}
-SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
+SDL_Tray *SDL_CreateTrayWithProperties(SDL_PropertiesID props)
{
if (!SDL_IsMainThread()) {
SDL_SetError("This function should be called on the main thread");
return NULL;
}
+ SDL_Surface *icon = (SDL_Surface *)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, NULL);
+ const char *tooltip = SDL_GetStringProperty(props, SDL_PROP_TRAY_CREATE_TOOLTIP_STRING, NULL);
+
if (icon) {
icon = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
if (!icon) {
@@ -102,6 +213,12 @@ void SDL_UpdateTrays(void)
return NULL;
}
+ tray->userdata = SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_USERDATA_POINTER, NULL);
+ tray->left_click_callback = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_LEFTCLICK_CALLBACK_POINTER, NULL);
+ tray->right_click_callback = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_RIGHTCLICK_CALLBACK_POINTER, NULL);
+ tray->middle_click_callback = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_MIDDLECLICK_CALLBACK_POINTER, NULL);
+ tray->double_click_callback = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_DOUBLECLICK_CALLBACK_POINTER, NULL);
+
tray->statusItem = nil;
tray->statusBar = [NSStatusBar systemStatusBar];
tray->statusItem = [tray->statusBar statusItemWithLength:NSVariableStatusItemLength];
@@ -140,11 +257,40 @@ void SDL_UpdateTrays(void)
SDL_DestroySurface(icon);
}
+ /* Create click handler and set up button to receive clicks */
+ tray->clickHandler = [[SDLTrayClickHandler alloc] init];
+ tray->clickHandler.tray = tray;
+
+ [tray->statusItem.button setTarget:tray->clickHandler];
+ [tray->statusItem.button setAction:@selector(handleClick:)];
+ [tray->statusItem.button sendActionOn:(NSEventMaskLeftMouseUp | NSEventMaskRightMouseUp)];
+
+ /* Start monitoring for middle clicks since status items don't receive them via the normal action mechanism */
+ [tray->clickHandler startMonitoringMiddleClicks];
+
SDL_RegisterTray(tray);
return tray;
}
+SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
+{
+ SDL_Tray *tray;
+ SDL_PropertiesID props = SDL_CreateProperties();
+ if (!props) {
+ return NULL;
+ }
+ if (icon) {
+ SDL_SetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, icon);
+ }
+ if (tooltip) {
+ SDL_SetStringProperty(props, SDL_PROP_TRAY_CREATE_TOOLTIP_STRING, tooltip);
+ }
+ tray = SDL_CreateTrayWithProperties(props);
+ SDL_DestroyProperties(props);
+ return tray;
+}
+
void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
{
if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
@@ -216,7 +362,7 @@ void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
NSMenu *nsmenu = [[NSMenu alloc] init];
[nsmenu setAutoenablesItems:FALSE];
- [tray->statusItem setMenu:nsmenu];
+ /* Don't set menu on statusItem - we handle menu display manually in the click handler */
tray->menu = menu;
menu->nsmenu = nsmenu;
@@ -518,6 +664,12 @@ void SDL_DestroyTray(SDL_Tray *tray)
DestroySDLMenu(tray->menu);
}
+ if (tray->clickHandler) {
+ [tray->clickHandler stopMonitoringMiddleClicks];
+ tray->clickHandler.tray = NULL;
+ tray->clickHandler = nil;
+ }
+
SDL_free(tray);
}
diff --git a/src/tray/dummy/SDL_tray.c b/src/tray/dummy/SDL_tray.c
index 7bedbb3e91798..71ac2da9131a5 100644
--- a/src/tray/dummy/SDL_tray.c
+++ b/src/tray/dummy/SDL_tray.c
@@ -29,6 +29,12 @@ void SDL_UpdateTrays(void)
{
}
+SDL_Tray *SDL_CreateTrayWithProperties(SDL_PropertiesID props)
+{
+ SDL_Unsupported();
+ return NULL;
+}
+
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 719281fe1cace..d932dd3754abd 100644
--- a/src/tray/unix/SDL_tray.c
+++ b/src/tray/unix/SDL_tray.c
@@ -239,7 +239,7 @@ void SDL_UpdateTrays(void)
}
}
-SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
+SDL_Tray *SDL_CreateTrayWithProperties(SDL_PropertiesID props)
{
if (!SDL_IsMainThread()) {
SDL_SetError("This function should be called on the main thread");
@@ -250,6 +250,8 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
return NULL;
}
+ SDL_Surface *icon = (SDL_Surface *)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, NULL);
+
SDL_Tray *tray = NULL;
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
if (!gtk) {
@@ -327,6 +329,24 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
return NULL;
}
+SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
+{
+ SDL_Tray *tray;
+ SDL_PropertiesID props = SDL_CreateProperties();
+ if (!props) {
+ return NULL;
+ }
+ if (icon) {
+ SDL_SetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, icon);
+ }
+ if (tooltip) {
+ SDL_SetStringProperty(props, SDL_PROP_TRAY_CREATE_TOOLTIP_STRING, tooltip);
+ }
+ tray = SDL_CreateTrayWithProperties(props);
+ SDL_DestroyProperties(props);
+ return tray;
+}
+
void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
{
if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
diff --git a/src/tray/windows/SDL_tray.c b/src/tray/windows/SDL_tray.c
index b7f752f9587a0..170785238e07f 100644
--- a/src/tray/windows/SDL_tray.c
+++ b/src/tray/windows/SDL_tray.c
@@ -62,6 +62,13 @@ struct SDL_Tray {
HWND hwnd;
HICON icon;
SDL_TrayMenu *menu;
+
+ void *userdata;
+ SDL_TrayClickCallback left_click_callback;
+ SDL_TrayClickCallback right_click_callback;
+ SDL_TrayClickCallback middle_click_callback;
+ SDL_TrayClickCallback double_click_callback;
+ bool ignore_next_left_up;
};
static UINT_PTR get_next_id(void)
@@ -119,10 +126,47 @@ LRESULT CALLBACK TrayWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lPar
switch (uMsg) {
case WM_TRAYICON:
- if (LOWORD(lParam) == WM_CONTEXTMENU || LOWORD(lParam) == WM_LBUTTONUP) {
- SetForegroundWindow(hwnd);
+ {
+ bool show_menu = false;
+
+ switch (LOWORD(lParam)) {
+ case WM_LBUTTONUP:
+ if (tray->ignore_next_left_up) {
+ tray->ignore_next_left_up = false;
+ } else if (tray->left_click_callback) {
+ show_menu = tray->left_click_callback(tray->userdata, tray);
+ } else {
+ show_menu = true;
+ }
+ break;
+
+ case WM_CONTEXTMENU:
+ if (tray->right_click_callback) {
+ show_menu = tray->right_click_callback(tray->userdata, tray);
+ } else {
+ show_menu = true;
+ }
+ break;
+
+ case WM_MBUTTONUP:
+ if (tray->middle_click_callback) {
+ tray->middle_click_callback(tray->userdata, tray);
+ }
+ break;
+
+ case WM_LBUTTONDBLCLK:
+ if (tray->double_click_callback) {
+ tray->double_click_callback(tray->userdata, tray);
+ /* Suppress the WM_LBUTTONUP that follows a double-click, so we
+ don't fire both double-click and left-click callbacks. This
+ matches the behavior on other platforms. */
+ tray->ignore_next_left_up = true;
+ }
+ break;
+ }
- if (tray->menu) {
+ if (show_menu && tray->menu) {
+ SetForegroundWindow(hwnd);
TrackPopupMenu(tray->menu->hMenu, TPM_BOTTOMALIGN | TPM_RIGHTALIGN, GET_X_LPARAM(wParam), GET_Y_LPARAM(wParam), 0, hwnd, NULL);
}
}
@@ -267,7 +311,7 @@ static bool SDL_RegisterTrayClass(LPCWSTR className)
return true;
}
-SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
+SDL_Tray *SDL_CreateTrayWithProperties(SDL_PropertiesID props)
{
if (!SDL_IsMainThread()) {
SDL_SetError("This function should be called on the main thread");
@@ -280,9 +324,19 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
return NULL;
}
+ SDL_Surface *icon = (SDL_Surface *)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, NULL);
+ const char *tooltip = SDL_GetStringProperty(props, SDL_PROP_TRAY_CREATE_TOOLTIP_STRING, NULL);
+
+ tray->userdata = SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_USERDATA_POINTER, NULL);
+ tray->left_click_callback = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_LEFTCLICK_CALLBACK_POINTER, NULL);
+ tray->right_click_callback = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_RIGHTCLICK_CALLBACK_POINTER, NULL);
+ tray->middle_click_callback = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_MIDDLECLICK_CALLBACK_POINTER, NULL);
+ tray->double_click_callback = (SDL_TrayClickCallback)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_DOUBLECLICK_CALLBACK_POINTER, NULL);
+
tray->menu = NULL;
if (!SDL_RegisterTrayClass(TEXT("SDL_TRAY"))) {
SDL_SetError("Failed to register SDL_TRAY window class");
+ SDL_free(tray);
return NULL;
}
tray->hwnd = CreateWindowEx(0, TEXT("SDL_TRAY"), NULL, WS_OVERLAPPEDWINDOW,
@@ -297,9 +351,13 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
tray->nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
tray->nid.uCallbackMessage = WM_TRAYICON;
tray->nid.uVersion = NOTIFYICON_VERSION_4;
- wchar_t *tooltipw = WIN_UTF8ToStringW(tooltip);
- SDL_wcslcpy(tray->nid.szTip, tooltipw, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip));
- SDL_free(tooltipw);
+ if (tooltip) {
+ wchar_t *tooltipw = WIN_UTF8ToStringW(tooltip);
+ if(tooltipw) {
+ SDL_wcslcpy(tray->nid.szTip, tooltipw, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip));
+ SDL_free(tooltipw);
+ }
+ }
if (icon) {
tray->nid.hIcon = WIN_CreateIconFromSurface(icon);
@@ -324,6 +382,24 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
return tray;
}
+SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
+{
+ SDL_Tray *tray;
+ SDL_PropertiesID props = SDL_CreateProperties();
+ if (!props) {
+ return NULL;
+ }
+ if (icon) {
+ SDL_SetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, icon);
+ }
+ if (tooltip) {
+ SDL_SetStringProperty(props, SDL_PROP_TRAY_CREATE_TOOLTIP_STRING, tooltip);
+ }
+ tray = SDL_CreateTrayWithProperties(props);
+ SDL_DestroyProperties(props);
+ return tray;
+}
+
void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
{
if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {