Merge commit '206dcdedf195fb320913584180edb12c7731e396' as 'external/SDL'
This commit is contained in:
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
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 "../video/SDL_sysvideo.h"
|
||||
#include "../events/SDL_events_c.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);
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
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);
|
||||
+662
@@ -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
@@ -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
|
||||
+786
@@ -0,0 +1,786 @@
|
||||
/*
|
||||
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 "../../video/SDL_stb_c.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* getpid() */
|
||||
#include <unistd.h>
|
||||
|
||||
/* APPINDICATOR_HEADER is not exposed as a build setting, but the code has been
|
||||
written nevertheless to make future maintenance easier. */
|
||||
#ifdef APPINDICATOR_HEADER
|
||||
#include APPINDICATOR_HEADER
|
||||
#else
|
||||
#include "../../core/unix/SDL_gtk.h"
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* BEGIN THIRD-PARTY HEADER CONTENT */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* AppIndicator */
|
||||
|
||||
typedef enum {
|
||||
APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
|
||||
APP_INDICATOR_CATEGORY_COMMUNICATIONS,
|
||||
APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
|
||||
APP_INDICATOR_CATEGORY_HARDWARE,
|
||||
APP_INDICATOR_CATEGORY_OTHER
|
||||
} AppIndicatorCategory;
|
||||
|
||||
typedef enum {
|
||||
APP_INDICATOR_STATUS_PASSIVE,
|
||||
APP_INDICATOR_STATUS_ACTIVE,
|
||||
APP_INDICATOR_STATUS_ATTENTION
|
||||
} AppIndicatorStatus;
|
||||
|
||||
typedef struct _AppIndicator AppIndicator;
|
||||
|
||||
static AppIndicator *(*app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category);
|
||||
static void (*app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status);
|
||||
static void (*app_indicator_set_icon)(AppIndicator *self, const gchar *icon_name);
|
||||
static void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* END THIRD-PARTY HEADER CONTENT */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
#endif
|
||||
|
||||
static void *libappindicator = NULL;
|
||||
|
||||
static void quit_appindicator(void)
|
||||
{
|
||||
if (libappindicator) {
|
||||
dlclose(libappindicator);
|
||||
libappindicator = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char *appindicator_names[] = {
|
||||
#ifdef SDL_PLATFORM_OPENBSD
|
||||
"libayatana-appindicator3.so",
|
||||
"libappindicator3.so",
|
||||
#else
|
||||
"libayatana-appindicator3.so.1",
|
||||
"libappindicator3.so.1",
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
||||
static void *find_lib(const char **names)
|
||||
{
|
||||
const char **name_ptr = names;
|
||||
void *handle = NULL;
|
||||
|
||||
do {
|
||||
handle = dlopen(*name_ptr, RTLD_LAZY);
|
||||
} while (*++name_ptr && !handle);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
static bool init_appindicator(void)
|
||||
{
|
||||
if (libappindicator) {
|
||||
return true;
|
||||
}
|
||||
|
||||
libappindicator = find_lib(appindicator_names);
|
||||
|
||||
if (!libappindicator) {
|
||||
quit_appindicator();
|
||||
return SDL_SetError("Could not load AppIndicator libraries");
|
||||
}
|
||||
|
||||
app_indicator_new = dlsym(libappindicator, "app_indicator_new");
|
||||
app_indicator_set_status = dlsym(libappindicator, "app_indicator_set_status");
|
||||
app_indicator_set_icon = dlsym(libappindicator, "app_indicator_set_icon");
|
||||
app_indicator_set_menu = dlsym(libappindicator, "app_indicator_set_menu");
|
||||
|
||||
if (!app_indicator_new ||
|
||||
!app_indicator_set_status ||
|
||||
!app_indicator_set_icon ||
|
||||
!app_indicator_set_menu) {
|
||||
quit_appindicator();
|
||||
return SDL_SetError("Could not load AppIndicator functions");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct SDL_TrayMenu {
|
||||
GtkMenuShell *menu;
|
||||
|
||||
int nEntries;
|
||||
SDL_TrayEntry **entries;
|
||||
|
||||
SDL_Tray *parent_tray;
|
||||
SDL_TrayEntry *parent_entry;
|
||||
};
|
||||
|
||||
struct SDL_TrayEntry {
|
||||
SDL_TrayMenu *parent;
|
||||
GtkWidget *item;
|
||||
|
||||
/* Checkboxes are "activated" when programmatically checked/unchecked; this
|
||||
is a workaround. */
|
||||
bool ignore_signal;
|
||||
|
||||
SDL_TrayEntryFlags flags;
|
||||
SDL_TrayCallback callback;
|
||||
void *userdata;
|
||||
SDL_TrayMenu *submenu;
|
||||
};
|
||||
|
||||
struct SDL_Tray {
|
||||
AppIndicator *indicator;
|
||||
SDL_TrayMenu *menu;
|
||||
char *icon_dir;
|
||||
char *icon_path;
|
||||
|
||||
GtkMenuShell *menu_cached;
|
||||
};
|
||||
|
||||
static void call_callback(GtkMenuItem *item, gpointer ptr)
|
||||
{
|
||||
SDL_TrayEntry *entry = ptr;
|
||||
|
||||
/* Not needed with AppIndicator, may be needed with other frameworks */
|
||||
/* if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
|
||||
SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
|
||||
} */
|
||||
|
||||
if (entry->ignore_signal) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry->callback) {
|
||||
entry->callback(entry->userdata, entry);
|
||||
}
|
||||
}
|
||||
|
||||
static bool new_tmp_filename(SDL_Tray *tray)
|
||||
{
|
||||
static int count = 0;
|
||||
|
||||
int would_have_written = SDL_asprintf(&tray->icon_path, "%s/%d.png", tray->icon_dir, count++);
|
||||
|
||||
if (would_have_written >= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
tray->icon_path = NULL;
|
||||
SDL_SetError("Failed to format new temporary filename");
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *get_appindicator_id(void)
|
||||
{
|
||||
static int count = 0;
|
||||
static char buffer[256];
|
||||
|
||||
int would_have_written = SDL_snprintf(buffer, sizeof(buffer), "sdl-appindicator-%d-%d", getpid(), count++);
|
||||
|
||||
if (would_have_written <= 0 || would_have_written >= sizeof(buffer) - 1) {
|
||||
SDL_SetError("Couldn't fit %d bytes in buffer of size %d", would_have_written, (int) sizeof(buffer));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
if (menu->menu) {
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (gtk) {
|
||||
gtk->g.object_unref(menu->menu);
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(menu->entries);
|
||||
SDL_free(menu);
|
||||
}
|
||||
|
||||
void SDL_UpdateTrays(void)
|
||||
{
|
||||
if (SDL_HasActiveTrays()) {
|
||||
SDL_UpdateGtk();
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Tray *SDL_CreateTrayWithProperties(SDL_PropertiesID props)
|
||||
{
|
||||
if (!SDL_IsMainThread()) {
|
||||
SDL_SetError("This function should be called on the main thread");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!init_appindicator()) {
|
||||
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) {
|
||||
goto tray_error;
|
||||
}
|
||||
|
||||
tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
|
||||
if (!tray) {
|
||||
goto tray_error;
|
||||
}
|
||||
|
||||
const gchar *cache_dir = gtk->g.get_user_cache_dir();
|
||||
if (!cache_dir) {
|
||||
SDL_SetError("Cannot get user cache directory: %s", strerror(errno));
|
||||
goto tray_error;
|
||||
}
|
||||
|
||||
char *sdl_dir;
|
||||
SDL_asprintf(&sdl_dir, "%s/SDL", cache_dir);
|
||||
if (!SDL_GetPathInfo(sdl_dir, NULL)) {
|
||||
if (!SDL_CreateDirectory(sdl_dir)) {
|
||||
SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno));
|
||||
goto sdl_dir_error;
|
||||
}
|
||||
}
|
||||
|
||||
/* On success, g_mkdtemp edits its argument in-place to replace the Xs
|
||||
* with a random directory name, which it creates safely and atomically.
|
||||
* On failure, it sets errno. */
|
||||
SDL_asprintf(&tray->icon_dir, "%s/tray-XXXXXX", sdl_dir);
|
||||
if (!gtk->g.mkdtemp(tray->icon_dir)) {
|
||||
SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno));
|
||||
goto icon_dir_error;
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
if (!new_tmp_filename(tray)) {
|
||||
goto icon_dir_error;
|
||||
}
|
||||
|
||||
SDL_SavePNG(icon, tray->icon_path);
|
||||
} else {
|
||||
// allocate a dummy icon path
|
||||
SDL_asprintf(&tray->icon_path, " ");
|
||||
}
|
||||
|
||||
tray->indicator = app_indicator_new(get_appindicator_id(), tray->icon_path,
|
||||
APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
|
||||
|
||||
app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE);
|
||||
|
||||
// The tray icon isn't shown before a menu is created; create one early.
|
||||
tray->menu_cached = (GtkMenuShell *)gtk->g.object_ref_sink(gtk->gtk.menu_new());
|
||||
app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu_cached));
|
||||
|
||||
SDL_RegisterTray(tray);
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
SDL_free(sdl_dir);
|
||||
|
||||
return tray;
|
||||
|
||||
icon_dir_error:
|
||||
SDL_free(tray->icon_dir);
|
||||
|
||||
sdl_dir_error:
|
||||
SDL_free(sdl_dir);
|
||||
|
||||
tray_error:
|
||||
SDL_free(tray);
|
||||
|
||||
if (gtk) {
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tray->icon_path) {
|
||||
SDL_RemovePath(tray->icon_path);
|
||||
SDL_free(tray->icon_path);
|
||||
tray->icon_path = NULL;
|
||||
}
|
||||
|
||||
/* AppIndicator caches the icon files; always change filename to avoid caching */
|
||||
|
||||
if (icon && new_tmp_filename(tray)) {
|
||||
SDL_SavePNG(icon, tray->icon_path);
|
||||
app_indicator_set_icon(tray->indicator, tray->icon_path);
|
||||
} else {
|
||||
SDL_free(tray->icon_path);
|
||||
tray->icon_path = NULL;
|
||||
app_indicator_set_icon(tray->indicator, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
|
||||
{
|
||||
/* AppIndicator provides no tooltip support. */
|
||||
}
|
||||
|
||||
SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
|
||||
{
|
||||
CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
|
||||
SDL_InvalidParamError("tray");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (!gtk) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu));
|
||||
if (!tray->menu) {
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tray->menu->menu = gtk->g.object_ref(tray->menu_cached);
|
||||
tray->menu->parent_tray = tray;
|
||||
tray->menu->parent_entry = NULL;
|
||||
tray->menu->nEntries = 0;
|
||||
tray->menu->entries = NULL;
|
||||
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
|
||||
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("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_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (!gtk) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu));
|
||||
if (!entry->submenu) {
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entry->submenu->menu = gtk->g.object_ref_sink(gtk->gtk.menu_new());
|
||||
entry->submenu->parent_tray = NULL;
|
||||
entry->submenu->parent_entry = entry;
|
||||
entry->submenu->nEntries = 0;
|
||||
entry->submenu->entries = NULL;
|
||||
|
||||
gtk->gtk.menu_item_set_submenu(GTK_MENU_ITEM(entry->item), GTK_WIDGET(entry->submenu->menu));
|
||||
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (gtk) {
|
||||
gtk->gtk.widget_destroy(entry->item);
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
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 = NULL;
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (!gtk) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry));
|
||||
if (!entry) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
entry->parent = menu;
|
||||
entry->item = NULL;
|
||||
entry->ignore_signal = false;
|
||||
entry->flags = flags;
|
||||
entry->callback = NULL;
|
||||
entry->userdata = NULL;
|
||||
entry->submenu = NULL;
|
||||
|
||||
if (label == NULL) {
|
||||
entry->item = gtk->gtk.separator_menu_item_new();
|
||||
} else if (flags & SDL_TRAYENTRY_CHECKBOX) {
|
||||
entry->item = gtk->gtk.check_menu_item_new_with_label(label);
|
||||
gboolean active = ((flags & SDL_TRAYENTRY_CHECKED) != 0);
|
||||
gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), active);
|
||||
} else {
|
||||
entry->item = gtk->gtk.menu_item_new_with_label(label);
|
||||
}
|
||||
|
||||
gboolean sensitive = ((flags & SDL_TRAYENTRY_DISABLED) == 0);
|
||||
gtk->gtk.widget_set_sensitive(entry->item, sensitive);
|
||||
|
||||
SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries));
|
||||
|
||||
if (!new_entries) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
gtk->gtk.widget_show(entry->item);
|
||||
gtk->gtk.menu_shell_insert(menu->menu, entry->item, (pos == menu->nEntries) ? -1 : pos);
|
||||
|
||||
gtk->g.signal_connect(entry->item, "activate", call_callback, entry);
|
||||
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
|
||||
return entry;
|
||||
|
||||
error:
|
||||
if (entry) {
|
||||
SDL_free(entry);
|
||||
}
|
||||
|
||||
if (gtk) {
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
|
||||
{
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (gtk) {
|
||||
gtk->gtk.menu_item_set_label(GTK_MENU_ITEM(entry->item), label);
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
}
|
||||
|
||||
const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
|
||||
{
|
||||
CHECK_PARAM(!entry) {
|
||||
SDL_InvalidParamError("entry");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *label = NULL;
|
||||
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (gtk) {
|
||||
label = gtk->gtk.menu_item_get_label(GTK_MENU_ITEM(entry->item));
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
|
||||
{
|
||||
if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (gtk) {
|
||||
entry->ignore_signal = true;
|
||||
gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), checked);
|
||||
entry->ignore_signal = false;
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
|
||||
{
|
||||
if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool checked = false;
|
||||
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (gtk) {
|
||||
checked = gtk->gtk.check_menu_item_get_active(GTK_CHECK_MENU_ITEM(entry->item));
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
|
||||
return checked;
|
||||
}
|
||||
|
||||
void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
|
||||
{
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (gtk) {
|
||||
gtk->gtk.widget_set_sensitive(entry->item, enabled);
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
|
||||
{
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool enabled = false;
|
||||
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (gtk) {
|
||||
enabled = gtk->gtk.widget_get_sensitive(entry->item);
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
|
||||
return 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)
|
||||
{
|
||||
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);
|
||||
|
||||
if (tray->menu) {
|
||||
DestroySDLMenu(tray->menu);
|
||||
}
|
||||
|
||||
if (tray->icon_path) {
|
||||
SDL_RemovePath(tray->icon_path);
|
||||
SDL_free(tray->icon_path);
|
||||
}
|
||||
|
||||
if (tray->icon_dir) {
|
||||
SDL_RemovePath(tray->icon_dir);
|
||||
SDL_free(tray->icon_dir);
|
||||
}
|
||||
|
||||
SDL_GtkContext *gtk = SDL_Gtk_EnterContext();
|
||||
if (gtk) {
|
||||
if (tray->menu_cached) {
|
||||
gtk->g.object_unref(tray->menu_cached);
|
||||
}
|
||||
|
||||
if (tray->indicator) {
|
||||
gtk->g.object_unref(tray->indicator);
|
||||
}
|
||||
|
||||
SDL_Gtk_ExitContext(gtk);
|
||||
}
|
||||
|
||||
SDL_free(tray);
|
||||
}
|
||||
+806
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user