Merge commit '411251c6242b04119edc41ce83f09f0714e2d32b' as 'external/SDL'

This commit is contained in:
2026-05-13 17:47:57 +02:00
2245 changed files with 975842 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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.
*/
#include "SDL_internal.h"
#include "../events/SDL_events_c.h"
#include "../video/SDL_sysvideo.h"
#include "SDL_tray_utils.h"
static int active_trays = 0;
void SDL_RegisterTray(SDL_Tray *tray)
{
SDL_SetObjectValid(tray, SDL_OBJECT_TYPE_TRAY, true);
++active_trays;
}
void SDL_UnregisterTray(SDL_Tray *tray)
{
SDL_assert(SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY));
SDL_SetObjectValid(tray, SDL_OBJECT_TYPE_TRAY, false);
--active_trays;
if (active_trays > 0) {
return;
}
if (!SDL_GetHintBoolean(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, true)) {
return;
}
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 == 0) {
SDL_SendQuit();
}
}
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);
SDL_ClearProperty(SDL_GetGlobalProperties(), SDL_PROP_TRAY_CLEANUP);
}
bool SDL_HasActiveTrays(void)
{
return (active_trays > 0);
}
int SDL_GetActiveTrayCount(void)
{
return active_trays;
}
+29
View File
@@ -0,0 +1,29 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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.
*/
#include "SDL_internal.h"
#define SDL_PROP_TRAY_CLEANUP "SDL.internal.tray.cleanup"
extern void SDL_RegisterTray(SDL_Tray *tray);
extern void SDL_UnregisterTray(SDL_Tray *tray);
extern void SDL_CleanupTrays(void);
extern bool SDL_HasActiveTrays(void);
extern int SDL_GetActiveTrayCount(void);
+662
View File
@@ -0,0 +1,662 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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.
*/
#include "SDL_internal.h"
#ifdef SDL_PLATFORM_MACOS
#include <Cocoa/Cocoa.h>
#include "../SDL_tray_utils.h"
#include "../../video/SDL_surface_c.h"
/* 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, strong) id middleClickMonitor;
- (void)handleClick:(id)sender;
- (void)startMonitoringMiddleClicks;
- (void)stopMonitoringMiddleClicks;
@end
struct SDL_TrayMenu {
NSMenu *nsmenu;
int nEntries;
SDL_TrayEntry **entries;
SDL_Tray *parent_tray;
SDL_TrayEntry *parent_entry;
};
struct SDL_TrayEntry {
NSMenuItem *nsitem;
SDL_TrayEntryFlags flags;
SDL_TrayCallback callback;
void *userdata;
SDL_TrayMenu *submenu;
SDL_TrayMenu *parent;
};
struct SDL_Tray {
NSStatusBar *statusBar;
NSStatusItem *statusItem;
SDL_TrayMenu *menu;
SDLTrayClickHandler *clickHandler;
void *userdata;
SDL_TrayClickCallback left_click_callback;
SDL_TrayClickCallback right_click_callback;
SDL_TrayClickCallback middle_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 */
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++) {
if (menu->entries[i] && menu->entries[i]->submenu) {
DestroySDLMenu(menu->entries[i]->submenu);
}
SDL_free(menu->entries[i]);
}
SDL_free(menu->entries);
if (menu->parent_entry) {
[menu->parent_entry->parent->nsmenu setSubmenu:nil forItem:menu->parent_entry->nsitem];
} else if (menu->parent_tray) {
[menu->parent_tray->statusItem setMenu:nil];
}
SDL_free(menu);
}
void SDL_UpdateTrays(void)
{
}
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) {
return NULL;
}
}
SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
if (!tray) {
SDL_DestroySurface(icon);
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->statusItem = nil;
tray->statusBar = [NSStatusBar systemStatusBar];
tray->statusItem = [tray->statusBar statusItemWithLength:NSVariableStatusItemLength];
[[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE];
if (tooltip) {
tray->statusItem.button.toolTip = [NSString stringWithUTF8String:tooltip];
} else {
tray->statusItem.button.toolTip = nil;
}
if (icon) {
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:icon->pitch
bitsPerPixel:32];
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
may give oversized status bar buttons. */
NSImage *iconimg22 = [[NSImage alloc] initWithSize:NSMakeSize(22, 22)];
[iconimg22 lockFocus];
[iconimg setSize:NSMakeSize(22, 22)];
[iconimg drawInRect:NSMakeRect(0, 0, 22, 22)];
[iconimg22 unlockFocus];
tray->statusItem.button.image = iconimg22;
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)) {
return;
}
if (!icon) {
tray->statusItem.button.image = nil;
return;
}
icon = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
if (!icon) {
tray->statusItem.button.image = nil;
return;
}
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:icon->pitch
bitsPerPixel:32];
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
may give oversized status bar buttons. */
NSImage *iconimg22 = [[NSImage alloc] initWithSize:NSMakeSize(22, 22)];
[iconimg22 lockFocus];
[iconimg setSize:NSMakeSize(22, 22)];
[iconimg drawInRect:NSMakeRect(0, 0, 22, 22)];
[iconimg22 unlockFocus];
tray->statusItem.button.image = iconimg22;
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 {
tray->statusItem.button.toolTip = nil;
}
}
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
{
CHECK_PARAM(!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;
}
NSMenu *nsmenu = [[NSMenu alloc] init];
[nsmenu setAutoenablesItems:FALSE];
/* Don't set menu on statusItem - we handle menu display manually in the click handler */
tray->menu = menu;
menu->nsmenu = nsmenu;
menu->nEntries = 0;
menu->entries = NULL;
menu->parent_tray = tray;
menu->parent_entry = NULL;
return menu;
}
SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
{
CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
SDL_InvalidParamError("tray");
return NULL;
}
return tray->menu;
}
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
{
CHECK_PARAM(!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
if (entry->submenu) {
SDL_SetError("Tray entry submenu already exists");
return NULL;
}
if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) {
SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
return NULL;
}
SDL_TrayMenu *menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*menu));
if (!menu) {
return NULL;
}
NSMenu *nsmenu = [[NSMenu alloc] init];
[nsmenu setAutoenablesItems:FALSE];
entry->submenu = menu;
menu->nsmenu = nsmenu;
menu->nEntries = 0;
menu->entries = NULL;
menu->parent_tray = NULL;
menu->parent_entry = entry;
[entry->parent->nsmenu setSubmenu:nsmenu forItem:entry->nsitem];
return menu;
}
SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
{
CHECK_PARAM(!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->submenu;
}
const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)
{
CHECK_PARAM(!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
if (count) {
*count = menu->nEntries;
}
return (const SDL_TrayEntry **)menu->entries;
}
void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
{
if (!entry) {
return;
}
SDL_TrayMenu *menu = entry->parent;
bool found = false;
for (int i = 0; i < menu->nEntries - 1; i++) {
if (menu->entries[i] == entry) {
found = true;
}
if (found) {
menu->entries[i] = menu->entries[i + 1];
}
}
if (entry->submenu) {
DestroySDLMenu(entry->submenu);
}
menu->nEntries--;
SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries));
/* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
if (new_entries) {
menu->entries = new_entries;
menu->entries[menu->nEntries] = NULL;
}
[menu->nsmenu removeItem:entry->nsitem];
SDL_free(entry);
}
SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
{
CHECK_PARAM(!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
CHECK_PARAM(pos < -1 || pos > menu->nEntries) {
SDL_InvalidParamError("pos");
return NULL;
}
if (pos == -1) {
pos = menu->nEntries;
}
SDL_TrayEntry *entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry));
if (!entry) {
return NULL;
}
SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries));
if (!new_entries) {
SDL_free(entry);
return NULL;
}
menu->entries = new_entries;
menu->nEntries++;
for (int i = menu->nEntries - 1; i > pos; i--) {
menu->entries[i] = menu->entries[i - 1];
}
new_entries[pos] = entry;
new_entries[menu->nEntries] = NULL;
NSMenuItem *nsitem;
if (label == NULL) {
nsitem = [NSMenuItem separatorItem];
} else {
nsitem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label] action:@selector(menu:) keyEquivalent:@""];
[nsitem setEnabled:((flags & SDL_TRAYENTRY_DISABLED) ? FALSE : TRUE)];
[nsitem setState:((flags & SDL_TRAYENTRY_CHECKED) ? NSControlStateValueOn : NSControlStateValueOff)];
[nsitem setRepresentedObject:[NSValue valueWithPointer:entry]];
}
[menu->nsmenu insertItem:nsitem atIndex:pos];
entry->nsitem = nsitem;
entry->flags = flags;
entry->callback = NULL;
entry->userdata = NULL;
entry->submenu = NULL;
entry->parent = menu;
return 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)
{
CHECK_PARAM(!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 || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
return;
}
[entry->nsitem setEnabled:(enabled ? YES : NO)];
}
bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
{
if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
return false;
}
return entry->nsitem.enabled;
}
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
{
if (!entry) {
return;
}
entry->callback = callback;
entry->userdata = userdata;
}
void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
{
if (!entry) {
return;
}
if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
}
if (entry->callback) {
entry->callback(entry->userdata, entry);
}
}
SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
{
CHECK_PARAM(!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->parent;
}
SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
{
CHECK_PARAM(!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
return menu->parent_entry;
}
SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
{
CHECK_PARAM(!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
return menu->parent_tray;
}
void SDL_DestroyTray(SDL_Tray *tray)
{
if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
return;
}
SDL_UnregisterTray(tray);
[[NSStatusBar systemStatusBar] removeStatusItem:tray->statusItem];
if (tray->menu) {
DestroySDLMenu(tray->menu);
}
if (tray->clickHandler) {
[tray->clickHandler stopMonitoringMiddleClicks];
tray->clickHandler.tray = NULL;
tray->clickHandler = nil;
}
SDL_free(tray);
}
#endif // SDL_PLATFORM_MACOS
+149
View File
@@ -0,0 +1,149 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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.
*/
#include "SDL_internal.h"
#ifdef SDL_TRAY_DUMMY
#include "../SDL_tray_utils.h"
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();
return NULL;
}
void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
{
}
void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
{
}
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
{
SDL_InvalidParamError("tray");
return NULL;
}
SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
{
SDL_InvalidParamError("tray");
return NULL;
}
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
{
SDL_InvalidParamError("entry");
return NULL;
}
SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
{
return NULL;
}
const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)
{
SDL_InvalidParamError("menu");
return NULL;
}
void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
{
}
SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
{
SDL_InvalidParamError("menu");
return NULL;
}
void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
{
}
const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
{
SDL_InvalidParamError("entry");
return NULL;
}
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
{
}
bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
{
return SDL_InvalidParamError("entry");
}
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
{
}
bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
{
return SDL_InvalidParamError("entry");
}
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
{
}
void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
{
}
SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
{
SDL_InvalidParamError("entry");
return NULL;
}
SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
{
SDL_InvalidParamError("menu");
return NULL;
}
SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
{
SDL_InvalidParamError("menu");
return NULL;
}
void SDL_DestroyTray(SDL_Tray *tray)
{
}
#endif // SDL_TRAY_DUMMY
File diff suppressed because it is too large Load Diff
+334
View File
@@ -0,0 +1,334 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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.
*/
#include "SDL_internal.h"
#include "../SDL_tray_utils.h"
#include "SDL_unixtray.h"
static SDL_TrayDriver *driver = NULL;
void SDL_UpdateTrays(void)
{
SDL_Tray **trays;
int active_trays;
int count;
int i;
active_trays = SDL_GetActiveTrayCount();
if (!active_trays) {
return;
}
trays = SDL_calloc(active_trays, sizeof(SDL_Tray *));
if (!trays) {
return;
}
count = SDL_GetObjects(SDL_OBJECT_TYPE_TRAY, (void **)trays, active_trays);
SDL_assert(count == active_trays);
for (i = 0; i < count; i++) {
if (trays[i]) {
if (SDL_ObjectValid(trays[i], SDL_OBJECT_TYPE_TRAY)) {
trays[i]->driver->UpdateTray(trays[i]);
}
}
}
SDL_free(trays);
}
SDL_Tray *SDL_CreateTrayWithProperties(SDL_PropertiesID props)
{
SDL_Tray *tray;
if (!SDL_IsMainThread()) {
SDL_SetError("This function should be called on the main thread");
return NULL;
}
if (!driver) {
#ifdef SDL_USE_LIBDBUS
driver = SDL_Tray_CreateDBusDriver();
#endif
}
if (driver) {
tray = driver->CreateTray(driver, props);
if (tray) {
SDL_RegisterTray(tray);
}
} else {
tray = NULL;
}
return tray;
}
SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
{
SDL_Tray *tray;
SDL_PropertiesID props;
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)
{
CHECK_PARAM (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
SDL_InvalidParamError("tray");
return;
}
tray->driver->SetTrayIcon(tray, icon);
}
void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
{
CHECK_PARAM (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
SDL_InvalidParamError("tray");
return;
}
tray->driver->SetTrayTooltip(tray, tooltip);
}
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
{
CHECK_PARAM (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
SDL_InvalidParamError("tray");
return NULL;
}
return tray->driver->CreateTrayMenu(tray);
}
SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
{
CHECK_PARAM (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
SDL_InvalidParamError("tray");
return NULL;
}
return tray->menu;
}
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->parent->parent_tray->driver->CreateTraySubmenu(entry);
}
SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->parent->parent_tray->driver->GetTraySubmenu(entry);
}
const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)
{
CHECK_PARAM (!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
CHECK_PARAM (!count) {
SDL_InvalidParamError("count");
return NULL;
}
return (const SDL_TrayEntry **)menu->parent_tray->driver->GetTrayEntries(menu, count);
}
void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return;
}
entry->parent->parent_tray->driver->RemoveTrayEntry(entry);
}
SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
{
CHECK_PARAM (!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
return menu->parent_tray->driver->InsertTrayEntryAt(menu, pos, label, flags);
}
void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return;
}
entry->parent->parent_tray->driver->SetTrayEntryLabel(entry, label);
}
const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->parent->parent_tray->driver->GetTrayEntryLabel(entry);
}
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return;
}
entry->parent->parent_tray->driver->SetTrayEntryChecked(entry, checked);
}
bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->parent->parent_tray->driver->GetTrayEntryChecked(entry);
}
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return;
}
entry->parent->parent_tray->driver->SetTrayEntryEnabled(entry, enabled);
}
bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->parent->parent_tray->driver->GetTrayEntryEnabled(entry);
}
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return;
}
CHECK_PARAM (!callback) {
SDL_InvalidParamError("callback");
return;
}
entry->parent->parent_tray->driver->SetTrayEntryCallback(entry, callback, userdata);
}
void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return;
}
entry->parent->parent_tray->driver->GetTrayEntryEnabled(entry);
}
SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
{
CHECK_PARAM (!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->parent;
}
SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
{
CHECK_PARAM (!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
return menu->parent_entry;
}
SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
{
CHECK_PARAM (!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
return menu->parent_tray;
}
void SDL_DestroyTray(SDL_Tray *tray)
{
if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
return;
}
SDL_UnregisterTray(tray);
tray->driver->DestroyTray(tray);
if (!SDL_GetActiveTrayCount()) {
driver->DestroyDriver(driver);
driver = NULL;
}
}
+89
View File
@@ -0,0 +1,89 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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.
*/
#include "SDL_internal.h"
#ifndef SDL_unixtray_h_
#define SDL_unixtray_h_
#include "../../core/linux/SDL_dbus.h"
#include "../SDL_tray_utils.h"
typedef struct SDL_TrayDriver
{
const char *name;
unsigned int count;
SDL_Tray *(*CreateTray)(struct SDL_TrayDriver *, SDL_PropertiesID);
void (*DestroyTray)(SDL_Tray *);
void (*UpdateTray)(SDL_Tray *);
void (*SetTrayIcon)(SDL_Tray *, SDL_Surface *);
void (*SetTrayTooltip)(SDL_Tray *, const char *);
SDL_TrayMenu *(*CreateTrayMenu)(SDL_Tray *);
SDL_TrayEntry *(*InsertTrayEntryAt)(SDL_TrayMenu *, int, const char *, SDL_TrayEntryFlags);
SDL_TrayMenu *(*CreateTraySubmenu)(SDL_TrayEntry *);
SDL_TrayMenu *(*GetTraySubmenu)(SDL_TrayEntry *);
SDL_TrayEntry **(*GetTrayEntries)(SDL_TrayMenu *, int *);
void (*RemoveTrayEntry)(SDL_TrayEntry *);
void (*SetTrayEntryLabel)(SDL_TrayEntry *, const char *);
const char *(*GetTrayEntryLabel)(SDL_TrayEntry *);
void (*SetTrayEntryChecked)(SDL_TrayEntry *, bool);
void (*ClickTrayEntry)(SDL_TrayEntry *);
bool (*GetTrayEntryChecked)(SDL_TrayEntry *);
void (*SetTrayEntryEnabled)(SDL_TrayEntry *, bool);
bool (*GetTrayEntryEnabled)(SDL_TrayEntry *);
void (*SetTrayEntryCallback)(SDL_TrayEntry *, SDL_TrayCallback, void *);
void (*DestroyDriver)(struct SDL_TrayDriver *);
void *internal;
} SDL_TrayDriver;
struct SDL_TrayMenu
{
SDL_Tray *parent_tray;
SDL_TrayEntry *parent_entry;
void *internal;
};
struct SDL_TrayEntry
{
SDL_TrayMenu *parent;
void *internal;
};
struct SDL_Tray
{
SDL_TrayDriver *driver;
SDL_TrayMenu *menu;
void *internal;
};
#ifdef SDL_USE_LIBDBUS
extern SDL_TrayDriver *SDL_Tray_CreateDBusDriver(void);
#endif
#endif // SDL_unixtray_h_
+806
View File
@@ -0,0 +1,806 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 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.
*/
#include "SDL_internal.h"
#include "../SDL_tray_utils.h"
#include "../../core/windows/SDL_windows.h"
#include <windowsx.h>
#include <shellapi.h>
#ifndef NOTIFYICON_VERSION_4
#define NOTIFYICON_VERSION_4 4
#endif
#ifndef NIF_SHOWTIP
#define NIF_SHOWTIP 0x00000080
#endif
#define WM_TRAYICON (WM_USER + 1)
struct SDL_TrayMenu {
HMENU hMenu;
int nEntries;
SDL_TrayEntry **entries;
SDL_Tray *parent_tray;
SDL_TrayEntry *parent_entry;
};
struct SDL_TrayEntry {
SDL_TrayMenu *parent;
UINT_PTR id;
char label_cache[4096];
SDL_TrayEntryFlags flags;
SDL_TrayCallback callback;
void *userdata;
SDL_TrayMenu *submenu;
};
struct SDL_Tray {
NOTIFYICONDATAW nid;
HWND hwnd;
HICON icon;
SDL_TrayMenu *menu;
void *userdata;
SDL_TrayClickCallback left_click_callback;
SDL_TrayClickCallback right_click_callback;
SDL_TrayClickCallback middle_click_callback;
};
static UINT_PTR get_next_id(void)
{
static UINT_PTR next_id = 0;
return ++next_id;
}
static SDL_TrayEntry *find_entry_in_menu(SDL_TrayMenu *menu, UINT_PTR id)
{
for (int i = 0; i < menu->nEntries; i++) {
SDL_TrayEntry *entry = menu->entries[i];
if (entry->id == id) {
return entry;
}
if (entry->submenu) {
SDL_TrayEntry *e = find_entry_in_menu(entry->submenu, id);
if (e) {
return e;
}
}
}
return NULL;
}
static SDL_TrayEntry *find_entry_with_id(SDL_Tray *tray, UINT_PTR id)
{
if (!tray->menu) {
return NULL;
}
return find_entry_in_menu(tray->menu, id);
}
LRESULT CALLBACK TrayWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
SDL_Tray *tray = (SDL_Tray *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
SDL_TrayEntry *entry = NULL;
if (!tray) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
static UINT s_taskbarRestart = 0;
if (s_taskbarRestart == 0) {
s_taskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
}
if (uMsg == s_taskbarRestart) {
Shell_NotifyIconW(NIM_ADD, &tray->nid);
Shell_NotifyIconW(NIM_SETVERSION, &tray->nid);
}
switch (uMsg) {
case WM_TRAYICON:
{
bool show_menu = false;
switch (LOWORD(lParam)) {
case WM_LBUTTONUP:
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;
}
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);
}
}
break;
case WM_COMMAND:
entry = find_entry_with_id(tray, LOWORD(wParam));
if (entry && (entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
}
if (entry && entry->callback) {
entry->callback(entry->userdata, entry);
}
break;
case WM_SETTINGCHANGE:
if (wParam == 0 && lParam != 0 && SDL_wcscmp((wchar_t *)lParam, L"ImmersiveColorSet") == 0) {
WIN_UpdateDarkModeForHWND(hwnd);
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
static void DestroySDLMenu(SDL_TrayMenu *menu)
{
for (int i = 0; i < menu->nEntries; i++) {
if (menu->entries[i] && menu->entries[i]->submenu) {
DestroySDLMenu(menu->entries[i]->submenu);
}
SDL_free(menu->entries[i]);
}
SDL_free(menu->entries);
DestroyMenu(menu->hMenu);
SDL_free(menu);
}
static wchar_t *escape_label(const char *in)
{
const char *c;
char *c2;
int len = 0;
for (c = in; *c; c++) {
len += (*c == '&') ? 2 : 1;
}
char *escaped = (char *)SDL_malloc(SDL_strlen(in) + len + 1);
if (!escaped) {
return NULL;
}
for (c = in, c2 = escaped; *c;) {
if (*c == '&') {
*c2++ = *c;
}
*c2++ = *c++;
}
*c2 = '\0';
wchar_t *out = WIN_UTF8ToStringW(escaped);
SDL_free(escaped);
return out;
}
static HICON load_default_icon(void)
{
HINSTANCE hInstance = GetModuleHandle(NULL);
if (!hInstance) {
return LoadIcon(NULL, IDI_APPLICATION);
}
const char *hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL);
if (hint && *hint) {
HICON icon = LoadIcon(hInstance, MAKEINTRESOURCE(SDL_atoi(hint)));
return icon ? icon : LoadIcon(NULL, IDI_APPLICATION);
}
hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON);
if (hint && *hint) {
HICON icon = LoadIcon(hInstance, MAKEINTRESOURCE(SDL_atoi(hint)));
return icon ? icon : LoadIcon(NULL, IDI_APPLICATION);
}
return LoadIcon(NULL, IDI_APPLICATION);
}
void SDL_UpdateTrays(void)
{
}
static void SDL_PropTrayCleanupCb(void *userdata, void *cls)
{
WNDCLASSEX wcex;
wcex.hIcon = NULL;
wcex.hIconSm = NULL;
HINSTANCE h = GetModuleHandle(NULL);
if (GetClassInfoEx(h, cls, &wcex)) {
UnregisterClass(cls, h);
}
}
static bool SDL_RegisterTrayClass(LPCWSTR className)
{
SDL_PropertiesID props = SDL_GetGlobalProperties();
if (!props) {
return false;
}
if (SDL_GetPointerProperty(props, SDL_PROP_TRAY_CLEANUP, NULL) != NULL) {
return true;
}
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.hCursor = NULL;
wcex.hIcon = NULL;
wcex.hIconSm = NULL;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = className;
wcex.style = 0;
wcex.hbrBackground = NULL;
wcex.lpfnWndProc = TrayWindowProc;
wcex.hInstance = NULL;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
if (!RegisterClassEx(&wcex)) {
return SDL_SetError("Couldn't register tray class");
}
SDL_SetPointerPropertyWithCleanup(props, SDL_PROP_TRAY_CLEANUP, (void *)wcex.lpszClassName, SDL_PropTrayCleanupCb, NULL);
return true;
}
SDL_Tray *SDL_CreateTrayWithProperties(SDL_PropertiesID props)
{
if (!SDL_IsMainThread()) {
SDL_SetError("This function should be called on the main thread");
return NULL;
}
SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
if (!tray) {
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->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,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, NULL, NULL);
WIN_UpdateDarkModeForHWND(tray->hwnd);
SDL_zero(tray->nid);
tray->nid.cbSize = sizeof(NOTIFYICONDATAW);
tray->nid.hWnd = tray->hwnd;
tray->nid.uID = (UINT) get_next_id();
tray->nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
tray->nid.uCallbackMessage = WM_TRAYICON;
tray->nid.uVersion = NOTIFYICON_VERSION_4;
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);
if (!tray->nid.hIcon) {
tray->nid.hIcon = load_default_icon();
}
tray->icon = tray->nid.hIcon;
} else {
tray->nid.hIcon = load_default_icon();
tray->icon = tray->nid.hIcon;
}
Shell_NotifyIconW(NIM_ADD, &tray->nid);
Shell_NotifyIconW(NIM_SETVERSION, &tray->nid);
SetWindowLongPtr(tray->hwnd, GWLP_USERDATA, (LONG_PTR) tray);
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)) {
return;
}
if (tray->icon) {
DestroyIcon(tray->icon);
}
if (icon) {
tray->nid.hIcon = WIN_CreateIconFromSurface(icon);
if (!tray->nid.hIcon) {
tray->nid.hIcon = load_default_icon();
}
tray->icon = tray->nid.hIcon;
} else {
tray->nid.hIcon = load_default_icon();
tray->icon = tray->nid.hIcon;
}
Shell_NotifyIconW(NIM_MODIFY, &tray->nid);
}
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));
SDL_free(tooltipw);
} else {
tray->nid.szTip[0] = '\0';
}
Shell_NotifyIconW(NIM_MODIFY, &tray->nid);
}
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
{
CHECK_PARAM(!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;
}
tray->menu->hMenu = CreatePopupMenu();
tray->menu->parent_tray = tray;
tray->menu->parent_entry = NULL;
return tray->menu;
}
SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
{
CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
SDL_InvalidParamError("tray");
return NULL;
}
return tray->menu;
}
SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
{
CHECK_PARAM(!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;
}
SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
{
CHECK_PARAM(!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->submenu;
}
const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)
{
CHECK_PARAM(!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
if (count) {
*count = menu->nEntries;
}
return (const SDL_TrayEntry **)menu->entries;
}
void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
{
if (!entry) {
return;
}
SDL_TrayMenu *menu = entry->parent;
bool found = false;
for (int i = 0; i < menu->nEntries - 1; i++) {
if (menu->entries[i] == entry) {
found = true;
}
if (found) {
menu->entries[i] = menu->entries[i + 1];
}
}
if (entry->submenu) {
DestroySDLMenu(entry->submenu);
}
menu->nEntries--;
SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries));
/* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
if (new_entries) {
menu->entries = new_entries;
menu->entries[menu->nEntries] = NULL;
}
if (!DeleteMenu(menu->hMenu, (UINT) entry->id, MF_BYCOMMAND)) {
/* This is somewhat useless since we don't return anything, but might help with eventual bugs */
SDL_SetError("Couldn't destroy tray entry");
}
SDL_free(entry);
}
SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
{
CHECK_PARAM(!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
CHECK_PARAM(pos < -1 || pos > menu->nEntries) {
SDL_InvalidParamError("pos");
return NULL;
}
int windows_compatible_pos = pos;
if (pos == -1) {
pos = menu->nEntries;
} else if (pos == menu->nEntries) {
windows_compatible_pos = -1;
}
SDL_TrayEntry *entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry));
if (!entry) {
return NULL;
}
wchar_t *label_w = NULL;
if (label && (label_w = escape_label(label)) == NULL) {
SDL_free(entry);
return NULL;
}
entry->parent = menu;
entry->flags = flags;
entry->callback = NULL;
entry->userdata = NULL;
entry->submenu = NULL;
SDL_snprintf(entry->label_cache, sizeof(entry->label_cache), "%s", label ? label : "");
if (label != NULL && flags & SDL_TRAYENTRY_SUBMENU) {
entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu));
if (!entry->submenu) {
SDL_free(entry);
SDL_free(label_w);
return NULL;
}
entry->submenu->hMenu = CreatePopupMenu();
entry->submenu->nEntries = 0;
entry->submenu->entries = NULL;
entry->submenu->parent_entry = entry;
entry->submenu->parent_tray = NULL;
entry->id = (UINT_PTR) entry->submenu->hMenu;
} else {
entry->id = get_next_id();
}
SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries));
if (!new_entries) {
SDL_free(entry);
SDL_free(label_w);
if (entry->submenu) {
DestroyMenu(entry->submenu->hMenu);
SDL_free(entry->submenu);
}
return NULL;
}
menu->entries = new_entries;
menu->nEntries++;
for (int i = menu->nEntries - 1; i > pos; i--) {
menu->entries[i] = menu->entries[i - 1];
}
new_entries[pos] = entry;
new_entries[menu->nEntries] = NULL;
if (label == NULL) {
InsertMenuW(menu->hMenu, windows_compatible_pos, MF_SEPARATOR | MF_BYPOSITION, entry->id, NULL);
} else {
UINT mf = MF_STRING | MF_BYPOSITION;
if (flags & SDL_TRAYENTRY_SUBMENU) {
mf = MF_POPUP;
}
if (flags & SDL_TRAYENTRY_DISABLED) {
mf |= MF_DISABLED | MF_GRAYED;
}
if (flags & SDL_TRAYENTRY_CHECKED) {
mf |= MF_CHECKED;
}
InsertMenuW(menu->hMenu, windows_compatible_pos, mf, entry->id, label_w);
SDL_free(label_w);
}
return entry;
}
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);
if (!label_w) {
return;
}
MENUITEMINFOW mii;
mii.cbSize = sizeof(MENUITEMINFOW);
mii.fMask = MIIM_STRING;
mii.dwTypeData = label_w;
mii.cch = (UINT) SDL_wcslen(label_w);
if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii)) {
SDL_SetError("Couldn't update tray entry label");
}
SDL_free(label_w);
}
const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
{
CHECK_PARAM(!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->label_cache;
}
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
{
if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
return;
}
CheckMenuItem(entry->parent->hMenu, (UINT) entry->id, checked ? MF_CHECKED : MF_UNCHECKED);
}
bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
{
if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
return false;
}
MENUITEMINFOW mii;
mii.cbSize = sizeof(MENUITEMINFOW);
mii.fMask = MIIM_STATE;
GetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii);
return ((mii.fState & MFS_CHECKED) != 0);
}
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;
GetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii);
return ((mii.fState & MFS_ENABLED) != 0);
}
void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
{
if (!entry) {
return;
}
entry->callback = callback;
entry->userdata = userdata;
}
void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
{
if (!entry) {
return;
}
if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
}
if (entry->callback) {
entry->callback(entry->userdata, entry);
}
}
SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
{
CHECK_PARAM(!entry) {
SDL_InvalidParamError("entry");
return NULL;
}
return entry->parent;
}
SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
{
CHECK_PARAM(!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
return menu->parent_entry;
}
SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
{
CHECK_PARAM(!menu) {
SDL_InvalidParamError("menu");
return NULL;
}
return menu->parent_tray;
}
void SDL_DestroyTray(SDL_Tray *tray)
{
if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
return;
}
SDL_UnregisterTray(tray);
Shell_NotifyIconW(NIM_DELETE, &tray->nid);
if (tray->menu) {
DestroySDLMenu(tray->menu);
}
if (tray->icon) {
DestroyIcon(tray->icon);
}
if (tray->hwnd) {
DestroyWindow(tray->hwnd);
}
SDL_free(tray);
}