From 70e16a8d13f8b96a798840cbcce668f86c197dff Mon Sep 17 00:00:00 2001 From: Jesse Chounard 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 688278a1d3..71a85c201f 100644 --- a/include/SDL3/SDL_tray.h +++ b/include/SDL3/SDL_tray.h @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -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 2f2ff3998b..86ff0a684b 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 fd7627cf32..acee86998e 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 bd44244021..68dca5a192 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 39442e4faa..860a4eb5a2 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 @@ struct SDL_Tray { 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 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) 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 @@ SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) 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 @@ SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray) 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 7bedbb3e91..71ac2da913 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 719281fe1c..d932dd3754 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 @@ tray_error: 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 b7f752f958..170785238e 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; - if (tray->menu) { + 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 (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)) {