summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/tray
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/tray
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/tray')
-rw-r--r--contrib/SDL-3.2.8/src/tray/SDL_tray_utils.c91
-rw-r--r--contrib/SDL-3.2.8/src/tray/SDL_tray_utils.h26
-rw-r--r--contrib/SDL-3.2.8/src/tray/cocoa/SDL_tray.m524
-rw-r--r--contrib/SDL-3.2.8/src/tray/dummy/SDL_tray.c143
-rw-r--r--contrib/SDL-3.2.8/src/tray/unix/SDL_tray.c817
-rw-r--r--contrib/SDL-3.2.8/src/tray/windows/SDL_tray.c690
6 files changed, 2291 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/tray/SDL_tray_utils.c b/contrib/SDL-3.2.8/src/tray/SDL_tray_utils.c
new file mode 100644
index 0000000..35cf559
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/tray/SDL_tray_utils.c
@@ -0,0 +1,91 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../video/SDL_sysvideo.h"
24#include "../events/SDL_events_c.h"
25#include "SDL_tray_utils.h"
26
27
28static int active_trays = 0;
29
30void SDL_RegisterTray(SDL_Tray *tray)
31{
32 SDL_SetObjectValid(tray, SDL_OBJECT_TYPE_TRAY, true);
33
34 ++active_trays;
35}
36
37void SDL_UnregisterTray(SDL_Tray *tray)
38{
39 SDL_assert(SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY));
40
41 SDL_SetObjectValid(tray, SDL_OBJECT_TYPE_TRAY, false);
42
43 --active_trays;
44 if (active_trays > 0) {
45 return;
46 }
47
48 if (!SDL_GetHintBoolean(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, true)) {
49 return;
50 }
51
52 int toplevel_count = 0;
53 SDL_Window **windows = SDL_GetWindows(NULL);
54 if (windows) {
55 for (int i = 0; windows[i]; ++i) {
56 SDL_Window *window = windows[i];
57 if (!window->parent && !(window->flags & SDL_WINDOW_HIDDEN)) {
58 ++toplevel_count;
59 }
60 }
61 SDL_free(windows);
62 }
63
64 if (toplevel_count == 0) {
65 SDL_SendQuit();
66 }
67}
68
69void SDL_CleanupTrays(void)
70{
71 if (active_trays == 0) {
72 return;
73 }
74
75 void **trays = (void **)SDL_malloc(active_trays * sizeof(*trays));
76 if (!trays) {
77 return;
78 }
79
80 int count = SDL_GetObjects(SDL_OBJECT_TYPE_TRAY, trays, active_trays);
81 SDL_assert(count == active_trays);
82 for (int i = 0; i < count; ++i) {
83 SDL_DestroyTray((SDL_Tray *)trays[i]);
84 }
85 SDL_free(trays);
86}
87
88bool SDL_HasActiveTrays(void)
89{
90 return (active_trays > 0);
91}
diff --git a/contrib/SDL-3.2.8/src/tray/SDL_tray_utils.h b/contrib/SDL-3.2.8/src/tray/SDL_tray_utils.h
new file mode 100644
index 0000000..e9e64fc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/tray/SDL_tray_utils.h
@@ -0,0 +1,26 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23extern void SDL_RegisterTray(SDL_Tray *tray);
24extern void SDL_UnregisterTray(SDL_Tray *tray);
25extern void SDL_CleanupTrays(void);
26extern bool SDL_HasActiveTrays(void);
diff --git a/contrib/SDL-3.2.8/src/tray/cocoa/SDL_tray.m b/contrib/SDL-3.2.8/src/tray/cocoa/SDL_tray.m
new file mode 100644
index 0000000..fd7f955
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/tray/cocoa/SDL_tray.m
@@ -0,0 +1,524 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_PLATFORM_MACOS
25
26#include <Cocoa/Cocoa.h>
27
28#include "../SDL_tray_utils.h"
29#include "../../video/SDL_surface_c.h"
30
31/* applicationDockMenu */
32
33struct SDL_TrayMenu {
34 NSMenu *nsmenu;
35
36 int nEntries;
37 SDL_TrayEntry **entries;
38
39 SDL_Tray *parent_tray;
40 SDL_TrayEntry *parent_entry;
41};
42
43struct SDL_TrayEntry {
44 NSMenuItem *nsitem;
45
46 SDL_TrayEntryFlags flags;
47 SDL_TrayCallback callback;
48 void *userdata;
49 SDL_TrayMenu *submenu;
50
51 SDL_TrayMenu *parent;
52};
53
54struct SDL_Tray {
55 NSStatusBar *statusBar;
56 NSStatusItem *statusItem;
57
58 SDL_TrayMenu *menu;
59};
60
61static void DestroySDLMenu(SDL_TrayMenu *menu)
62{
63 for (int i = 0; i < menu->nEntries; i++) {
64 if (menu->entries[i] && menu->entries[i]->submenu) {
65 DestroySDLMenu(menu->entries[i]->submenu);
66 }
67 SDL_free(menu->entries[i]);
68 }
69
70 SDL_free(menu->entries);
71
72 if (menu->parent_entry) {
73 [menu->parent_entry->parent->nsmenu setSubmenu:nil forItem:menu->parent_entry->nsitem];
74 } else if (menu->parent_tray) {
75 [menu->parent_tray->statusItem setMenu:nil];
76 }
77
78 SDL_free(menu);
79}
80
81void SDL_UpdateTrays(void)
82{
83}
84
85SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
86{
87 if (!SDL_IsMainThread()) {
88 SDL_SetError("This function should be called on the main thread");
89 return NULL;
90 }
91
92 if (icon) {
93 icon = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
94 if (!icon) {
95 return NULL;
96 }
97 }
98
99 SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
100 if (!tray) {
101 SDL_DestroySurface(icon);
102 return NULL;
103 }
104
105 tray->statusItem = nil;
106 tray->statusBar = [NSStatusBar systemStatusBar];
107 tray->statusItem = [tray->statusBar statusItemWithLength:NSVariableStatusItemLength];
108 [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE];
109
110 if (tooltip) {
111 tray->statusItem.button.toolTip = [NSString stringWithUTF8String:tooltip];
112 } else {
113 tray->statusItem.button.toolTip = nil;
114 }
115
116 if (icon) {
117 NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(unsigned char **)&icon->pixels
118 pixelsWide:icon->w
119 pixelsHigh:icon->h
120 bitsPerSample:8
121 samplesPerPixel:4
122 hasAlpha:YES
123 isPlanar:NO
124 colorSpaceName:NSCalibratedRGBColorSpace
125 bytesPerRow:icon->pitch
126 bitsPerPixel:32];
127 NSImage *iconimg = [[NSImage alloc] initWithSize:NSMakeSize(icon->w, icon->h)];
128 [iconimg addRepresentation:bitmap];
129
130 /* A typical icon size is 22x22 on macOS. Failing to resize the icon
131 may give oversized status bar buttons. */
132 NSImage *iconimg22 = [[NSImage alloc] initWithSize:NSMakeSize(22, 22)];
133 [iconimg22 lockFocus];
134 [iconimg setSize:NSMakeSize(22, 22)];
135 [iconimg drawInRect:NSMakeRect(0, 0, 22, 22)];
136 [iconimg22 unlockFocus];
137
138 tray->statusItem.button.image = iconimg22;
139
140 SDL_DestroySurface(icon);
141 }
142
143 SDL_RegisterTray(tray);
144
145 return tray;
146}
147
148void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
149{
150 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
151 return;
152 }
153
154 if (!icon) {
155 tray->statusItem.button.image = nil;
156 return;
157 }
158
159 icon = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_RGBA32);
160 if (!icon) {
161 tray->statusItem.button.image = nil;
162 return;
163 }
164
165 NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(unsigned char **)&icon->pixels
166 pixelsWide:icon->w
167 pixelsHigh:icon->h
168 bitsPerSample:8
169 samplesPerPixel:4
170 hasAlpha:YES
171 isPlanar:NO
172 colorSpaceName:NSCalibratedRGBColorSpace
173 bytesPerRow:icon->pitch
174 bitsPerPixel:32];
175 NSImage *iconimg = [[NSImage alloc] initWithSize:NSMakeSize(icon->w, icon->h)];
176 [iconimg addRepresentation:bitmap];
177
178 /* A typical icon size is 22x22 on macOS. Failing to resize the icon
179 may give oversized status bar buttons. */
180 NSImage *iconimg22 = [[NSImage alloc] initWithSize:NSMakeSize(22, 22)];
181 [iconimg22 lockFocus];
182 [iconimg setSize:NSMakeSize(22, 22)];
183 [iconimg drawInRect:NSMakeRect(0, 0, 22, 22)];
184 [iconimg22 unlockFocus];
185
186 tray->statusItem.button.image = iconimg22;
187
188 SDL_DestroySurface(icon);
189}
190
191void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
192{
193 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
194 return;
195 }
196
197 if (tooltip) {
198 tray->statusItem.button.toolTip = [NSString stringWithUTF8String:tooltip];
199 } else {
200 tray->statusItem.button.toolTip = nil;
201 }
202}
203
204SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
205{
206 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
207 SDL_InvalidParamError("tray");
208 return NULL;
209 }
210
211 SDL_TrayMenu *menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*menu));
212 if (!menu) {
213 return NULL;
214 }
215
216 NSMenu *nsmenu = [[NSMenu alloc] init];
217 [nsmenu setAutoenablesItems:FALSE];
218
219 [tray->statusItem setMenu:nsmenu];
220
221 tray->menu = menu;
222 menu->nsmenu = nsmenu;
223 menu->nEntries = 0;
224 menu->entries = NULL;
225 menu->parent_tray = tray;
226 menu->parent_entry = NULL;
227
228 return menu;
229}
230
231SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
232{
233 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
234 SDL_InvalidParamError("tray");
235 return NULL;
236 }
237
238 return tray->menu;
239}
240
241SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
242{
243 if (!entry) {
244 SDL_InvalidParamError("entry");
245 return NULL;
246 }
247
248 if (entry->submenu) {
249 SDL_SetError("Tray entry submenu already exists");
250 return NULL;
251 }
252
253 if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) {
254 SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
255 return NULL;
256 }
257
258 SDL_TrayMenu *menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*menu));
259 if (!menu) {
260 return NULL;
261 }
262
263 NSMenu *nsmenu = [[NSMenu alloc] init];
264 [nsmenu setAutoenablesItems:FALSE];
265
266 entry->submenu = menu;
267 menu->nsmenu = nsmenu;
268 menu->nEntries = 0;
269 menu->entries = NULL;
270 menu->parent_tray = NULL;
271 menu->parent_entry = entry;
272
273 [entry->parent->nsmenu setSubmenu:nsmenu forItem:entry->nsitem];
274
275 return menu;
276}
277
278SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
279{
280 if (!entry) {
281 SDL_InvalidParamError("entry");
282 return NULL;
283 }
284
285 return entry->submenu;
286}
287
288const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)
289{
290 if (!menu) {
291 SDL_InvalidParamError("menu");
292 return NULL;
293 }
294
295 if (count) {
296 *count = menu->nEntries;
297 }
298 return (const SDL_TrayEntry **)menu->entries;
299}
300
301void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
302{
303 if (!entry) {
304 return;
305 }
306
307 SDL_TrayMenu *menu = entry->parent;
308
309 bool found = false;
310 for (int i = 0; i < menu->nEntries - 1; i++) {
311 if (menu->entries[i] == entry) {
312 found = true;
313 }
314
315 if (found) {
316 menu->entries[i] = menu->entries[i + 1];
317 }
318 }
319
320 if (entry->submenu) {
321 DestroySDLMenu(entry->submenu);
322 }
323
324 menu->nEntries--;
325 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries));
326
327 /* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
328 if (new_entries) {
329 menu->entries = new_entries;
330 menu->entries[menu->nEntries] = NULL;
331 }
332
333 [menu->nsmenu removeItem:entry->nsitem];
334
335 SDL_free(entry);
336}
337
338SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
339{
340 if (!menu) {
341 SDL_InvalidParamError("menu");
342 return NULL;
343 }
344
345 if (pos < -1 || pos > menu->nEntries) {
346 SDL_InvalidParamError("pos");
347 return NULL;
348 }
349
350 if (pos == -1) {
351 pos = menu->nEntries;
352 }
353
354 SDL_TrayEntry *entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry));
355 if (!entry) {
356 return NULL;
357 }
358
359 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries));
360 if (!new_entries) {
361 SDL_free(entry);
362 return NULL;
363 }
364
365 menu->entries = new_entries;
366 menu->nEntries++;
367
368 for (int i = menu->nEntries - 1; i > pos; i--) {
369 menu->entries[i] = menu->entries[i - 1];
370 }
371
372 new_entries[pos] = entry;
373 new_entries[menu->nEntries] = NULL;
374
375 NSMenuItem *nsitem;
376 if (label == NULL) {
377 nsitem = [NSMenuItem separatorItem];
378 } else {
379 nsitem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label] action:@selector(menu:) keyEquivalent:@""];
380 [nsitem setEnabled:((flags & SDL_TRAYENTRY_DISABLED) ? FALSE : TRUE)];
381 [nsitem setState:((flags & SDL_TRAYENTRY_CHECKED) ? NSControlStateValueOn : NSControlStateValueOff)];
382 [nsitem setRepresentedObject:[NSValue valueWithPointer:entry]];
383 }
384
385 [menu->nsmenu insertItem:nsitem atIndex:pos];
386
387 entry->nsitem = nsitem;
388 entry->flags = flags;
389 entry->callback = NULL;
390 entry->userdata = NULL;
391 entry->submenu = NULL;
392 entry->parent = menu;
393
394 return entry;
395}
396
397void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
398{
399 if (!entry) {
400 return;
401 }
402
403 [entry->nsitem setTitle:[NSString stringWithUTF8String:label]];
404}
405
406const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
407{
408 if (!entry) {
409 SDL_InvalidParamError("entry");
410 return NULL;
411 }
412
413 return [[entry->nsitem title] UTF8String];
414}
415
416void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
417{
418 if (!entry) {
419 return;
420 }
421
422 [entry->nsitem setState:(checked ? NSControlStateValueOn : NSControlStateValueOff)];
423}
424
425bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
426{
427 if (!entry) {
428 return false;
429 }
430
431 return entry->nsitem.state == NSControlStateValueOn;
432}
433
434void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
435{
436 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
437 return;
438 }
439
440 [entry->nsitem setEnabled:(enabled ? YES : NO)];
441}
442
443bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
444{
445 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
446 return false;
447 }
448
449 return entry->nsitem.enabled;
450}
451
452void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
453{
454 if (!entry) {
455 return;
456 }
457
458 entry->callback = callback;
459 entry->userdata = userdata;
460}
461
462void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
463{
464 if (!entry) {
465 return;
466 }
467
468 if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
469 SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
470 }
471
472 if (entry->callback) {
473 entry->callback(entry->userdata, entry);
474 }
475}
476
477SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
478{
479 if (!entry) {
480 SDL_InvalidParamError("entry");
481 return NULL;
482 }
483
484 return entry->parent;
485}
486
487SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
488{
489 if (!menu) {
490 SDL_InvalidParamError("menu");
491 return NULL;
492 }
493
494 return menu->parent_entry;
495}
496
497SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
498{
499 if (!menu) {
500 SDL_InvalidParamError("menu");
501 return NULL;
502 }
503
504 return menu->parent_tray;
505}
506
507void SDL_DestroyTray(SDL_Tray *tray)
508{
509 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
510 return;
511 }
512
513 SDL_UnregisterTray(tray);
514
515 [[NSStatusBar systemStatusBar] removeStatusItem:tray->statusItem];
516
517 if (tray->menu) {
518 DestroySDLMenu(tray->menu);
519 }
520
521 SDL_free(tray);
522}
523
524#endif // SDL_PLATFORM_MACOS
diff --git a/contrib/SDL-3.2.8/src/tray/dummy/SDL_tray.c b/contrib/SDL-3.2.8/src/tray/dummy/SDL_tray.c
new file mode 100644
index 0000000..766fb92
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/tray/dummy/SDL_tray.c
@@ -0,0 +1,143 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifndef SDL_PLATFORM_MACOS
25
26#include "../SDL_tray_utils.h"
27
28void SDL_UpdateTrays(void)
29{
30}
31
32SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
33{
34 SDL_Unsupported();
35 return NULL;
36}
37
38void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
39{
40}
41
42void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
43{
44}
45
46SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
47{
48 SDL_InvalidParamError("tray");
49 return NULL;
50}
51
52SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
53{
54 SDL_InvalidParamError("tray");
55 return NULL;
56}
57
58SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
59{
60 SDL_InvalidParamError("entry");
61 return NULL;
62}
63
64SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
65{
66 return NULL;
67}
68
69const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)
70{
71 SDL_InvalidParamError("menu");
72 return NULL;
73}
74
75void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
76{
77}
78
79SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
80{
81 SDL_InvalidParamError("menu");
82 return NULL;
83}
84
85void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
86{
87}
88
89const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
90{
91 SDL_InvalidParamError("entry");
92 return NULL;
93}
94
95void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
96{
97}
98
99bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
100{
101 return SDL_InvalidParamError("entry");
102}
103
104void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
105{
106}
107
108bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
109{
110 return SDL_InvalidParamError("entry");
111}
112
113void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
114{
115}
116
117void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
118{
119}
120
121SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
122{
123 SDL_InvalidParamError("entry");
124 return NULL;
125}
126
127SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
128{
129 SDL_InvalidParamError("menu");
130 return NULL;
131}
132
133SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
134{
135 SDL_InvalidParamError("menu");
136 return NULL;
137}
138
139void SDL_DestroyTray(SDL_Tray *tray)
140{
141}
142
143#endif // !SDL_PLATFORM_MACOS
diff --git a/contrib/SDL-3.2.8/src/tray/unix/SDL_tray.c b/contrib/SDL-3.2.8/src/tray/unix/SDL_tray.c
new file mode 100644
index 0000000..dc2d0ca
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/tray/unix/SDL_tray.c
@@ -0,0 +1,817 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#include "../SDL_tray_utils.h"
25
26#include <dlfcn.h>
27#include <errno.h>
28
29/* getpid() */
30#include <unistd.h>
31
32/* APPINDICATOR_HEADER is not exposed as a build setting, but the code has been
33 written nevertheless to make future maintenance easier. */
34#ifdef APPINDICATOR_HEADER
35#include APPINDICATOR_HEADER
36#else
37/* ------------------------------------------------------------------------- */
38/* BEGIN THIRD-PARTY HEADER CONTENT */
39/* ------------------------------------------------------------------------- */
40/* Glib 2.0 */
41
42typedef unsigned long gulong;
43typedef void* gpointer;
44typedef char gchar;
45typedef int gint;
46typedef unsigned int guint;
47typedef gint gboolean;
48typedef void (*GCallback)(void);
49typedef struct _GClosure GClosure;
50typedef void (*GClosureNotify) (gpointer data, GClosure *closure);
51typedef gboolean (*GSourceFunc) (gpointer user_data);
52typedef enum
53{
54 G_CONNECT_AFTER = 1 << 0,
55 G_CONNECT_SWAPPED = 1 << 1
56} GConnectFlags;
57
58static gulong (*g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags);
59static void (*g_object_unref)(gpointer object);
60static gchar *(*g_mkdtemp)(gchar *template);
61gpointer (*g_object_ref_sink)(gpointer object);
62gpointer (*g_object_ref)(gpointer object);
63
64// glib_typeof requires compiler-specific code and includes that are too complex
65// to be worth copy-pasting here
66//#define g_object_ref(Obj) ((glib_typeof (Obj)) (g_object_ref) (Obj))
67//#define g_object_ref_sink(Obj) ((glib_typeof (Obj)) (g_object_ref_sink) (Obj))
68
69#define g_signal_connect(instance, detailed_signal, c_handler, data) \
70 g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0)
71
72#define _G_TYPE_CIC(ip, gt, ct) ((ct*) ip)
73
74#define G_TYPE_CHECK_INSTANCE_CAST(instance, g_type, c_type) (_G_TYPE_CIC ((instance), (g_type), c_type))
75
76#define G_CALLBACK(f) ((GCallback) (f))
77
78#define FALSE 0
79#define TRUE 1
80
81/* GTK 3.0 */
82
83typedef struct _GtkMenu GtkMenu;
84typedef struct _GtkMenuItem GtkMenuItem;
85typedef struct _GtkMenuShell GtkMenuShell;
86typedef struct _GtkWidget GtkWidget;
87typedef struct _GtkCheckMenuItem GtkCheckMenuItem;
88
89static gboolean (*gtk_init_check)(int *argc, char ***argv);
90static gboolean (*gtk_main_iteration_do)(gboolean blocking);
91static GtkWidget* (*gtk_menu_new)(void);
92static GtkWidget* (*gtk_separator_menu_item_new)(void);
93static GtkWidget* (*gtk_menu_item_new_with_label)(const gchar *label);
94static void (*gtk_menu_item_set_submenu)(GtkMenuItem *menu_item, GtkWidget *submenu);
95static GtkWidget* (*gtk_check_menu_item_new_with_label)(const gchar *label);
96static void (*gtk_check_menu_item_set_active)(GtkCheckMenuItem *check_menu_item, gboolean is_active);
97static void (*gtk_widget_set_sensitive)(GtkWidget *widget, gboolean sensitive);
98static void (*gtk_widget_show)(GtkWidget *widget);
99static void (*gtk_menu_shell_append)(GtkMenuShell *menu_shell, GtkWidget *child);
100static void (*gtk_menu_shell_insert)(GtkMenuShell *menu_shell, GtkWidget *child, gint position);
101static void (*gtk_widget_destroy)(GtkWidget *widget);
102static const gchar *(*gtk_menu_item_get_label)(GtkMenuItem *menu_item);
103static void (*gtk_menu_item_set_label)(GtkMenuItem *menu_item, const gchar *label);
104static gboolean (*gtk_check_menu_item_get_active)(GtkCheckMenuItem *check_menu_item);
105static gboolean (*gtk_widget_get_sensitive)(GtkWidget *widget);
106
107#define GTK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU_ITEM, GtkMenuItem))
108#define GTK_WIDGET(widget) (G_TYPE_CHECK_INSTANCE_CAST ((widget), GTK_TYPE_WIDGET, GtkWidget))
109#define GTK_CHECK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CHECK_MENU_ITEM, GtkCheckMenuItem))
110#define GTK_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU, GtkMenu))
111
112/* AppIndicator */
113
114typedef enum {
115 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
116 APP_INDICATOR_CATEGORY_COMMUNICATIONS,
117 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
118 APP_INDICATOR_CATEGORY_HARDWARE,
119 APP_INDICATOR_CATEGORY_OTHER
120} AppIndicatorCategory;
121
122typedef enum {
123 APP_INDICATOR_STATUS_PASSIVE,
124 APP_INDICATOR_STATUS_ACTIVE,
125 APP_INDICATOR_STATUS_ATTENTION
126} AppIndicatorStatus;
127
128typedef struct _AppIndicator AppIndicator;
129
130static AppIndicator *(*app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category);
131static void (*app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status);
132static void (*app_indicator_set_icon)(AppIndicator *self, const gchar *icon_name);
133static void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu);
134
135/* ------------------------------------------------------------------------- */
136/* END THIRD-PARTY HEADER CONTENT */
137/* ------------------------------------------------------------------------- */
138#endif
139
140#ifdef APPINDICATOR_HEADER
141
142static void quit_gtk(void)
143{
144}
145
146static bool init_gtk(void)
147{
148 return true;
149}
150
151#else
152
153static bool gtk_is_init = false;
154
155static void *libappindicator = NULL;
156static void *libgtk = NULL;
157static void *libgdk = NULL;
158
159static void quit_gtk(void)
160{
161 if (libappindicator) {
162 dlclose(libappindicator);
163 libappindicator = NULL;
164 }
165
166 if (libgtk) {
167 dlclose(libgtk);
168 libgtk = NULL;
169 }
170
171 if (libgdk) {
172 dlclose(libgdk);
173 libgdk = NULL;
174 }
175
176 gtk_is_init = false;
177}
178
179const char *appindicator_names[] = {
180#ifdef SDL_PLATFORM_OPENBSD
181 "libayatana-appindicator3.so",
182 "libappindicator3.so",
183#else
184 "libayatana-appindicator3.so.1",
185 "libappindicator3.so.1",
186#endif
187 NULL
188};
189
190const char *gtk_names[] = {
191#ifdef SDL_PLATFORM_OPENBSD
192 "libgtk-3.so",
193#else
194 "libgtk-3.so.0",
195#endif
196 NULL
197};
198
199const char *gdk_names[] = {
200#ifdef SDL_PLATFORM_OPENBSD
201 "libgdk-3.so",
202#else
203 "libgdk-3.so.0",
204#endif
205 NULL
206};
207
208static void *find_lib(const char **names)
209{
210 const char **name_ptr = names;
211 void *handle = NULL;
212
213 do {
214 handle = dlopen(*name_ptr, RTLD_LAZY);
215 } while (*++name_ptr && !handle);
216
217 return handle;
218}
219
220static bool init_gtk(void)
221{
222 if (gtk_is_init) {
223 return true;
224 }
225
226 libappindicator = find_lib(appindicator_names);
227 libgtk = find_lib(gtk_names);
228 libgdk = find_lib(gdk_names);
229
230 if (!libappindicator || !libgtk || !libgdk) {
231 quit_gtk();
232 return SDL_SetError("Could not load GTK/AppIndicator libraries");
233 }
234
235 gtk_init_check = dlsym(libgtk, "gtk_init_check");
236 gtk_main_iteration_do = dlsym(libgtk, "gtk_main_iteration_do");
237 gtk_menu_new = dlsym(libgtk, "gtk_menu_new");
238 gtk_separator_menu_item_new = dlsym(libgtk, "gtk_separator_menu_item_new");
239 gtk_menu_item_new_with_label = dlsym(libgtk, "gtk_menu_item_new_with_label");
240 gtk_menu_item_set_submenu = dlsym(libgtk, "gtk_menu_item_set_submenu");
241 gtk_check_menu_item_new_with_label = dlsym(libgtk, "gtk_check_menu_item_new_with_label");
242 gtk_check_menu_item_set_active = dlsym(libgtk, "gtk_check_menu_item_set_active");
243 gtk_widget_set_sensitive = dlsym(libgtk, "gtk_widget_set_sensitive");
244 gtk_widget_show = dlsym(libgtk, "gtk_widget_show");
245 gtk_menu_shell_append = dlsym(libgtk, "gtk_menu_shell_append");
246 gtk_menu_shell_insert = dlsym(libgtk, "gtk_menu_shell_insert");
247 gtk_widget_destroy = dlsym(libgtk, "gtk_widget_destroy");
248 gtk_menu_item_get_label = dlsym(libgtk, "gtk_menu_item_get_label");
249 gtk_menu_item_set_label = dlsym(libgtk, "gtk_menu_item_set_label");
250 gtk_check_menu_item_get_active = dlsym(libgtk, "gtk_check_menu_item_get_active");
251 gtk_widget_get_sensitive = dlsym(libgtk, "gtk_widget_get_sensitive");
252
253 /* Technically these are GLib or GObject functions, but we can find
254 * them via GDK */
255 g_mkdtemp = dlsym(libgdk, "g_mkdtemp");
256 g_signal_connect_data = dlsym(libgdk, "g_signal_connect_data");
257 g_object_unref = dlsym(libgdk, "g_object_unref");
258 g_object_ref_sink = dlsym(libgdk, "g_object_ref_sink");
259 g_object_ref = dlsym(libgdk, "g_object_ref");
260
261 app_indicator_new = dlsym(libappindicator, "app_indicator_new");
262 app_indicator_set_status = dlsym(libappindicator, "app_indicator_set_status");
263 app_indicator_set_icon = dlsym(libappindicator, "app_indicator_set_icon");
264 app_indicator_set_menu = dlsym(libappindicator, "app_indicator_set_menu");
265
266 if (!gtk_init_check ||
267 !gtk_main_iteration_do ||
268 !gtk_menu_new ||
269 !gtk_separator_menu_item_new ||
270 !gtk_menu_item_new_with_label ||
271 !gtk_menu_item_set_submenu ||
272 !gtk_check_menu_item_new_with_label ||
273 !gtk_check_menu_item_set_active ||
274 !gtk_widget_set_sensitive ||
275 !gtk_widget_show ||
276 !gtk_menu_shell_append ||
277 !gtk_menu_shell_insert ||
278 !gtk_widget_destroy ||
279 !g_mkdtemp ||
280 !g_object_ref_sink ||
281 !g_object_ref ||
282 !g_signal_connect_data ||
283 !g_object_unref ||
284 !app_indicator_new ||
285 !app_indicator_set_status ||
286 !app_indicator_set_icon ||
287 !app_indicator_set_menu ||
288 !gtk_menu_item_get_label ||
289 !gtk_menu_item_set_label ||
290 !gtk_check_menu_item_get_active ||
291 !gtk_widget_get_sensitive) {
292 quit_gtk();
293 return SDL_SetError("Could not load GTK/AppIndicator functions");
294 }
295
296 if (gtk_init_check(0, NULL) == FALSE) {
297 quit_gtk();
298 return SDL_SetError("Could not init GTK");
299 }
300
301 gtk_is_init = true;
302
303 return true;
304}
305#endif
306
307struct SDL_TrayMenu {
308 GtkMenuShell *menu;
309
310 int nEntries;
311 SDL_TrayEntry **entries;
312
313 SDL_Tray *parent_tray;
314 SDL_TrayEntry *parent_entry;
315};
316
317struct SDL_TrayEntry {
318 SDL_TrayMenu *parent;
319 GtkWidget *item;
320
321 /* Checkboxes are "activated" when programmatically checked/unchecked; this
322 is a workaround. */
323 bool ignore_signal;
324
325 SDL_TrayEntryFlags flags;
326 SDL_TrayCallback callback;
327 void *userdata;
328 SDL_TrayMenu *submenu;
329};
330
331/* Template for g_mkdtemp(). The Xs will get replaced with a random
332 * directory name, which is created safely and atomically. */
333#define ICON_DIR_TEMPLATE "/tmp/SDL-tray-XXXXXX"
334
335struct SDL_Tray {
336 AppIndicator *indicator;
337 SDL_TrayMenu *menu;
338 char icon_dir[sizeof(ICON_DIR_TEMPLATE)];
339 char icon_path[256];
340
341 GtkMenuShell *menu_cached;
342};
343
344static void call_callback(GtkMenuItem *item, gpointer ptr)
345{
346 SDL_TrayEntry *entry = ptr;
347
348 /* Not needed with AppIndicator, may be needed with other frameworks */
349 /* if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
350 SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
351 } */
352
353 if (entry->ignore_signal) {
354 return;
355 }
356
357 if (entry->callback) {
358 entry->callback(entry->userdata, entry);
359 }
360}
361
362static bool new_tmp_filename(SDL_Tray *tray)
363{
364 static int count = 0;
365
366 int would_have_written = SDL_snprintf(tray->icon_path, sizeof(tray->icon_path), "%s/%d.bmp", tray->icon_dir, count++);
367
368 if (would_have_written > 0 && ((unsigned) would_have_written) < sizeof(tray->icon_path) - 1) {
369 return true;
370 }
371
372 tray->icon_path[0] = '\0';
373 SDL_SetError("Failed to format new temporary filename");
374 return false;
375}
376
377static const char *get_appindicator_id(void)
378{
379 static int count = 0;
380 static char buffer[256];
381
382 int would_have_written = SDL_snprintf(buffer, sizeof(buffer), "sdl-appindicator-%d-%d", getpid(), count++);
383
384 if (would_have_written <= 0 || would_have_written >= sizeof(buffer) - 1) {
385 SDL_SetError("Couldn't fit %d bytes in buffer of size %d", would_have_written, (int) sizeof(buffer));
386 return NULL;
387 }
388
389 return buffer;
390}
391
392static void DestroySDLMenu(SDL_TrayMenu *menu)
393{
394 for (int i = 0; i < menu->nEntries; i++) {
395 if (menu->entries[i] && menu->entries[i]->submenu) {
396 DestroySDLMenu(menu->entries[i]->submenu);
397 }
398 SDL_free(menu->entries[i]);
399 }
400
401 if (menu->menu) {
402 g_object_unref(menu->menu);
403 }
404
405 SDL_free(menu->entries);
406 SDL_free(menu);
407}
408
409void SDL_UpdateTrays(void)
410{
411 if (SDL_HasActiveTrays()) {
412 gtk_main_iteration_do(FALSE);
413 }
414}
415
416SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
417{
418 if (!SDL_IsMainThread()) {
419 SDL_SetError("This function should be called on the main thread");
420 return NULL;
421 }
422
423 if (init_gtk() != true) {
424 return NULL;
425 }
426
427 SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
428 if (!tray) {
429 return NULL;
430 }
431
432 /* On success, g_mkdtemp edits its argument in-place to replace the Xs
433 * with a random directory name, which it creates safely and atomically.
434 * On failure, it sets errno. */
435 SDL_strlcpy(tray->icon_dir, ICON_DIR_TEMPLATE, sizeof(tray->icon_dir));
436 if (!g_mkdtemp(tray->icon_dir)) {
437 SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno));
438 SDL_free(tray);
439 return NULL;
440 }
441
442 if (icon) {
443 if (!new_tmp_filename(tray)) {
444 SDL_free(tray);
445 return NULL;
446 }
447
448 SDL_SaveBMP(icon, tray->icon_path);
449 }
450
451 tray->indicator = app_indicator_new(get_appindicator_id(), tray->icon_path,
452 APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
453
454 app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE);
455
456 // The tray icon isn't shown before a menu is created; create one early.
457 tray->menu_cached = (GtkMenuShell *) g_object_ref_sink(gtk_menu_new());
458 app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu_cached));
459
460 SDL_RegisterTray(tray);
461
462 return tray;
463}
464
465void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
466{
467 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
468 return;
469 }
470
471 if (*tray->icon_path) {
472 SDL_RemovePath(tray->icon_path);
473 }
474
475 /* AppIndicator caches the icon files; always change filename to avoid caching */
476
477 if (icon && new_tmp_filename(tray)) {
478 SDL_SaveBMP(icon, tray->icon_path);
479 app_indicator_set_icon(tray->indicator, tray->icon_path);
480 } else {
481 *tray->icon_path = '\0';
482 app_indicator_set_icon(tray->indicator, NULL);
483 }
484}
485
486void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
487{
488 /* AppIndicator provides no tooltip support. */
489}
490
491SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
492{
493 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
494 SDL_InvalidParamError("tray");
495 return NULL;
496 }
497
498 tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu));
499 if (!tray->menu) {
500 return NULL;
501 }
502
503 tray->menu->menu = g_object_ref(tray->menu_cached);
504 tray->menu->parent_tray = tray;
505 tray->menu->parent_entry = NULL;
506 tray->menu->nEntries = 0;
507 tray->menu->entries = NULL;
508
509 return tray->menu;
510}
511
512SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
513{
514 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
515 SDL_InvalidParamError("tray");
516 return NULL;
517 }
518
519 return tray->menu;
520}
521
522SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
523{
524 if (!entry) {
525 SDL_InvalidParamError("entry");
526 return NULL;
527 }
528
529 if (entry->submenu) {
530 SDL_SetError("Tray entry submenu already exists");
531 return NULL;
532 }
533
534 if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) {
535 SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
536 return NULL;
537 }
538
539 entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu));
540 if (!entry->submenu) {
541 return NULL;
542 }
543
544 entry->submenu->menu = (GtkMenuShell *)gtk_menu_new();
545 entry->submenu->parent_tray = NULL;
546 entry->submenu->parent_entry = entry;
547 entry->submenu->nEntries = 0;
548 entry->submenu->entries = NULL;
549
550 gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry->item), GTK_WIDGET(entry->submenu->menu));
551
552 return entry->submenu;
553}
554
555SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
556{
557 if (!entry) {
558 SDL_InvalidParamError("entry");
559 return NULL;
560 }
561
562 return entry->submenu;
563}
564
565const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)
566{
567 if (!menu) {
568 SDL_InvalidParamError("menu");
569 return NULL;
570 }
571
572 if (count) {
573 *count = menu->nEntries;
574 }
575 return (const SDL_TrayEntry **)menu->entries;
576}
577
578void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
579{
580 if (!entry) {
581 return;
582 }
583
584 SDL_TrayMenu *menu = entry->parent;
585
586 bool found = false;
587 for (int i = 0; i < menu->nEntries - 1; i++) {
588 if (menu->entries[i] == entry) {
589 found = true;
590 }
591
592 if (found) {
593 menu->entries[i] = menu->entries[i + 1];
594 }
595 }
596
597 if (entry->submenu) {
598 DestroySDLMenu(entry->submenu);
599 }
600
601 menu->nEntries--;
602 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries));
603
604 /* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
605 if (new_entries) {
606 menu->entries = new_entries;
607 menu->entries[menu->nEntries] = NULL;
608 }
609
610 gtk_widget_destroy(entry->item);
611 SDL_free(entry);
612}
613
614SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
615{
616 if (!menu) {
617 SDL_InvalidParamError("menu");
618 return NULL;
619 }
620
621 if (pos < -1 || pos > menu->nEntries) {
622 SDL_InvalidParamError("pos");
623 return NULL;
624 }
625
626 if (pos == -1) {
627 pos = menu->nEntries;
628 }
629
630 SDL_TrayEntry *entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry));
631 if (!entry) {
632 return NULL;
633 }
634
635 entry->parent = menu;
636 entry->item = NULL;
637 entry->ignore_signal = false;
638 entry->flags = flags;
639 entry->callback = NULL;
640 entry->userdata = NULL;
641 entry->submenu = NULL;
642
643 if (label == NULL) {
644 entry->item = gtk_separator_menu_item_new();
645 } else if (flags & SDL_TRAYENTRY_CHECKBOX) {
646 entry->item = gtk_check_menu_item_new_with_label(label);
647 gboolean active = ((flags & SDL_TRAYENTRY_CHECKED) != 0);
648 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), active);
649 } else {
650 entry->item = gtk_menu_item_new_with_label(label);
651 }
652
653 gboolean sensitive = ((flags & SDL_TRAYENTRY_DISABLED) == 0);
654 gtk_widget_set_sensitive(entry->item, sensitive);
655
656 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries));
657
658 if (!new_entries) {
659 SDL_free(entry);
660 return NULL;
661 }
662
663 menu->entries = new_entries;
664 menu->nEntries++;
665
666 for (int i = menu->nEntries - 1; i > pos; i--) {
667 menu->entries[i] = menu->entries[i - 1];
668 }
669
670 new_entries[pos] = entry;
671 new_entries[menu->nEntries] = NULL;
672
673 gtk_widget_show(entry->item);
674 gtk_menu_shell_insert(menu->menu, entry->item, (pos == menu->nEntries) ? -1 : pos);
675
676 g_signal_connect(entry->item, "activate", G_CALLBACK(call_callback), entry);
677
678 return entry;
679}
680
681void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
682{
683 if (!entry) {
684 return;
685 }
686
687 gtk_menu_item_set_label(GTK_MENU_ITEM(entry->item), label);
688}
689
690const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
691{
692 if (!entry) {
693 SDL_InvalidParamError("entry");
694 return NULL;
695 }
696
697 return gtk_menu_item_get_label(GTK_MENU_ITEM(entry->item));
698}
699
700void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
701{
702 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
703 return;
704 }
705
706 entry->ignore_signal = true;
707 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), checked);
708 entry->ignore_signal = false;
709}
710
711bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
712{
713 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
714 return false;
715 }
716
717 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(entry->item));
718}
719
720void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
721{
722 if (!entry) {
723 return;
724 }
725
726 gtk_widget_set_sensitive(entry->item, enabled);
727}
728
729bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
730{
731 if (!entry) {
732 return false;
733 }
734
735 return gtk_widget_get_sensitive(entry->item);
736}
737
738void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
739{
740 if (!entry) {
741 return;
742 }
743
744 entry->callback = callback;
745 entry->userdata = userdata;
746}
747
748void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
749{
750 if (!entry) {
751 return;
752 }
753
754 if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
755 SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
756 }
757
758 if (entry->callback) {
759 entry->callback(entry->userdata, entry);
760 }
761}
762
763SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
764{
765 if (!entry) {
766 SDL_InvalidParamError("entry");
767 return NULL;
768 }
769
770 return entry->parent;
771}
772
773SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
774{
775 return menu->parent_entry;
776}
777
778SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
779{
780 if (!menu) {
781 SDL_InvalidParamError("menu");
782 return NULL;
783 }
784
785 return menu->parent_tray;
786}
787
788void SDL_DestroyTray(SDL_Tray *tray)
789{
790 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
791 return;
792 }
793
794 SDL_UnregisterTray(tray);
795
796 if (tray->menu) {
797 DestroySDLMenu(tray->menu);
798 }
799
800 if (*tray->icon_path) {
801 SDL_RemovePath(tray->icon_path);
802 }
803
804 if (*tray->icon_dir) {
805 SDL_RemovePath(tray->icon_dir);
806 }
807
808 if (tray->menu_cached) {
809 g_object_unref(tray->menu_cached);
810 }
811
812 if (tray->indicator) {
813 g_object_unref(tray->indicator);
814 }
815
816 SDL_free(tray);
817}
diff --git a/contrib/SDL-3.2.8/src/tray/windows/SDL_tray.c b/contrib/SDL-3.2.8/src/tray/windows/SDL_tray.c
new file mode 100644
index 0000000..18008ee
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/tray/windows/SDL_tray.c
@@ -0,0 +1,690 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#include "../SDL_tray_utils.h"
25#include "../../core/windows/SDL_windows.h"
26#include "../../video/windows/SDL_windowswindow.h"
27
28#include <windowsx.h>
29#include <shellapi.h>
30
31#include "../../video/windows/SDL_surface_utils.h"
32
33#ifndef NOTIFYICON_VERSION_4
34#define NOTIFYICON_VERSION_4 4
35#endif
36#ifndef NIF_SHOWTIP
37#define NIF_SHOWTIP 0x00000080
38#endif
39
40#define WM_TRAYICON (WM_USER + 1)
41
42struct SDL_TrayMenu {
43 HMENU hMenu;
44
45 int nEntries;
46 SDL_TrayEntry **entries;
47
48 SDL_Tray *parent_tray;
49 SDL_TrayEntry *parent_entry;
50};
51
52struct SDL_TrayEntry {
53 SDL_TrayMenu *parent;
54 UINT_PTR id;
55
56 char label_cache[4096];
57 SDL_TrayEntryFlags flags;
58 SDL_TrayCallback callback;
59 void *userdata;
60 SDL_TrayMenu *submenu;
61};
62
63struct SDL_Tray {
64 NOTIFYICONDATAW nid;
65 HWND hwnd;
66 HICON icon;
67 SDL_TrayMenu *menu;
68};
69
70static UINT_PTR get_next_id(void)
71{
72 static UINT_PTR next_id = 0;
73 return ++next_id;
74}
75
76static SDL_TrayEntry *find_entry_in_menu(SDL_TrayMenu *menu, UINT_PTR id)
77{
78 for (int i = 0; i < menu->nEntries; i++) {
79 SDL_TrayEntry *entry = menu->entries[i];
80
81 if (entry->id == id) {
82 return entry;
83 }
84
85 if (entry->submenu) {
86 SDL_TrayEntry *e = find_entry_in_menu(entry->submenu, id);
87
88 if (e) {
89 return e;
90 }
91 }
92 }
93
94 return NULL;
95}
96
97static SDL_TrayEntry *find_entry_with_id(SDL_Tray *tray, UINT_PTR id)
98{
99 if (!tray->menu) {
100 return NULL;
101 }
102
103 return find_entry_in_menu(tray->menu, id);
104}
105
106LRESULT CALLBACK TrayWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
107 SDL_Tray *tray = (SDL_Tray *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
108 SDL_TrayEntry *entry = NULL;
109
110 if (!tray) {
111 return DefWindowProc(hwnd, uMsg, wParam, lParam);
112 }
113
114 switch (uMsg) {
115 case WM_TRAYICON:
116 if (LOWORD(lParam) == WM_CONTEXTMENU || LOWORD(lParam) == WM_LBUTTONUP) {
117 SetForegroundWindow(hwnd);
118
119 if (tray->menu) {
120 TrackPopupMenu(tray->menu->hMenu, TPM_BOTTOMALIGN | TPM_RIGHTALIGN, GET_X_LPARAM(wParam), GET_Y_LPARAM(wParam), 0, hwnd, NULL);
121 }
122 }
123 break;
124
125 case WM_COMMAND:
126 entry = find_entry_with_id(tray, LOWORD(wParam));
127
128 if (entry && (entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
129 SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
130 }
131
132 if (entry && entry->callback) {
133 entry->callback(entry->userdata, entry);
134 }
135 break;
136
137 case WM_SETTINGCHANGE:
138 if (wParam == 0 && lParam != 0 && SDL_wcscmp((wchar_t *)lParam, L"ImmersiveColorSet") == 0) {
139 WIN_UpdateDarkModeForHWND(hwnd);
140 }
141 break;
142
143 default:
144 return DefWindowProc(hwnd, uMsg, wParam, lParam);
145 }
146 return 0;
147}
148
149static void DestroySDLMenu(SDL_TrayMenu *menu)
150{
151 for (int i = 0; i < menu->nEntries; i++) {
152 if (menu->entries[i] && menu->entries[i]->submenu) {
153 DestroySDLMenu(menu->entries[i]->submenu);
154 }
155 SDL_free(menu->entries[i]);
156 }
157 SDL_free(menu->entries);
158 DestroyMenu(menu->hMenu);
159 SDL_free(menu);
160}
161
162static wchar_t *escape_label(const char *in)
163{
164 const char *c;
165 char *c2;
166 int len = 0;
167
168 for (c = in; *c; c++) {
169 len += (*c == '&') ? 2 : 1;
170 }
171
172 char *escaped = (char *)SDL_malloc(SDL_strlen(in) + len + 1);
173 if (!escaped) {
174 return NULL;
175 }
176
177 for (c = in, c2 = escaped; *c;) {
178 if (*c == '&') {
179 *c2++ = *c;
180 }
181
182 *c2++ = *c++;
183 }
184
185 *c2 = '\0';
186
187 wchar_t *out = WIN_UTF8ToStringW(escaped);
188 SDL_free(escaped);
189
190 return out;
191}
192
193static HICON load_default_icon()
194{
195 HINSTANCE hInstance = GetModuleHandle(NULL);
196 if (!hInstance) {
197 return LoadIcon(NULL, IDI_APPLICATION);
198 }
199
200 const char *hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL);
201 if (hint && *hint) {
202 HICON icon = LoadIcon(hInstance, MAKEINTRESOURCE(SDL_atoi(hint)));
203 return icon ? icon : LoadIcon(NULL, IDI_APPLICATION);
204 }
205
206 hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON);
207 if (hint && *hint) {
208 HICON icon = LoadIcon(hInstance, MAKEINTRESOURCE(SDL_atoi(hint)));
209 return icon ? icon : LoadIcon(NULL, IDI_APPLICATION);
210 }
211
212 return LoadIcon(NULL, IDI_APPLICATION);
213}
214
215void SDL_UpdateTrays(void)
216{
217}
218
219SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
220{
221 if (!SDL_IsMainThread()) {
222 SDL_SetError("This function should be called on the main thread");
223 return NULL;
224 }
225
226 SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray));
227
228 if (!tray) {
229 return NULL;
230 }
231
232 tray->menu = NULL;
233 tray->hwnd = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
234 SetWindowLongPtr(tray->hwnd, GWLP_WNDPROC, (LONG_PTR) TrayWindowProc);
235
236 WIN_UpdateDarkModeForHWND(tray->hwnd);
237
238 SDL_zero(tray->nid);
239 tray->nid.cbSize = sizeof(NOTIFYICONDATAW);
240 tray->nid.hWnd = tray->hwnd;
241 tray->nid.uID = (UINT) get_next_id();
242 tray->nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
243 tray->nid.uCallbackMessage = WM_TRAYICON;
244 tray->nid.uVersion = NOTIFYICON_VERSION_4;
245 wchar_t *tooltipw = WIN_UTF8ToStringW(tooltip);
246 SDL_wcslcpy(tray->nid.szTip, tooltipw, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip));
247 SDL_free(tooltipw);
248
249 if (icon) {
250 tray->nid.hIcon = CreateIconFromSurface(icon);
251
252 if (!tray->nid.hIcon) {
253 tray->nid.hIcon = load_default_icon();
254 }
255
256 tray->icon = tray->nid.hIcon;
257 } else {
258 tray->nid.hIcon = load_default_icon();
259 tray->icon = tray->nid.hIcon;
260 }
261
262 Shell_NotifyIconW(NIM_ADD, &tray->nid);
263 Shell_NotifyIconW(NIM_SETVERSION, &tray->nid);
264
265 SetWindowLongPtr(tray->hwnd, GWLP_USERDATA, (LONG_PTR) tray);
266
267 SDL_RegisterTray(tray);
268
269 return tray;
270}
271
272void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
273{
274 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
275 return;
276 }
277
278 if (tray->icon) {
279 DestroyIcon(tray->icon);
280 }
281
282 if (icon) {
283 tray->nid.hIcon = CreateIconFromSurface(icon);
284
285 if (!tray->nid.hIcon) {
286 tray->nid.hIcon = load_default_icon();
287 }
288
289 tray->icon = tray->nid.hIcon;
290 } else {
291 tray->nid.hIcon = load_default_icon();
292 tray->icon = tray->nid.hIcon;
293 }
294
295 Shell_NotifyIconW(NIM_MODIFY, &tray->nid);
296}
297
298void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
299{
300 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
301 return;
302 }
303
304 if (tooltip) {
305 wchar_t *tooltipw = WIN_UTF8ToStringW(tooltip);
306 SDL_wcslcpy(tray->nid.szTip, tooltipw, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip));
307 SDL_free(tooltipw);
308 } else {
309 tray->nid.szTip[0] = '\0';
310 }
311
312 Shell_NotifyIconW(NIM_MODIFY, &tray->nid);
313}
314
315SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
316{
317 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
318 SDL_InvalidParamError("tray");
319 return NULL;
320 }
321
322 tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu));
323
324 if (!tray->menu) {
325 return NULL;
326 }
327
328 tray->menu->hMenu = CreatePopupMenu();
329 tray->menu->parent_tray = tray;
330 tray->menu->parent_entry = NULL;
331
332 return tray->menu;
333}
334
335SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
336{
337 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
338 SDL_InvalidParamError("tray");
339 return NULL;
340 }
341
342 return tray->menu;
343}
344
345SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
346{
347 if (!entry) {
348 SDL_InvalidParamError("entry");
349 return NULL;
350 }
351
352 if (!entry->submenu) {
353 SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
354 return NULL;
355 }
356
357 return entry->submenu;
358}
359
360SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
361{
362 if (!entry) {
363 SDL_InvalidParamError("entry");
364 return NULL;
365 }
366
367 return entry->submenu;
368}
369
370const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)
371{
372 if (!menu) {
373 SDL_InvalidParamError("menu");
374 return NULL;
375 }
376
377 if (count) {
378 *count = menu->nEntries;
379 }
380 return (const SDL_TrayEntry **)menu->entries;
381}
382
383void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
384{
385 if (!entry) {
386 return;
387 }
388
389 SDL_TrayMenu *menu = entry->parent;
390
391 bool found = false;
392 for (int i = 0; i < menu->nEntries - 1; i++) {
393 if (menu->entries[i] == entry) {
394 found = true;
395 }
396
397 if (found) {
398 menu->entries[i] = menu->entries[i + 1];
399 }
400 }
401
402 if (entry->submenu) {
403 DestroySDLMenu(entry->submenu);
404 }
405
406 menu->nEntries--;
407 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries));
408
409 /* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
410 if (new_entries) {
411 menu->entries = new_entries;
412 menu->entries[menu->nEntries] = NULL;
413 }
414
415 if (!DeleteMenu(menu->hMenu, (UINT) entry->id, MF_BYCOMMAND)) {
416 /* This is somewhat useless since we don't return anything, but might help with eventual bugs */
417 SDL_SetError("Couldn't destroy tray entry");
418 }
419
420 SDL_free(entry);
421}
422
423SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
424{
425 if (!menu) {
426 SDL_InvalidParamError("menu");
427 return NULL;
428 }
429
430 if (pos < -1 || pos > menu->nEntries) {
431 SDL_InvalidParamError("pos");
432 return NULL;
433 }
434
435 int windows_compatible_pos = pos;
436
437 if (pos == -1) {
438 pos = menu->nEntries;
439 } else if (pos == menu->nEntries) {
440 windows_compatible_pos = -1;
441 }
442
443 SDL_TrayEntry *entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry));
444 if (!entry) {
445 return NULL;
446 }
447
448 wchar_t *label_w = NULL;
449
450 if (label && (label_w = escape_label(label)) == NULL) {
451 SDL_free(entry);
452 return NULL;
453 }
454
455 entry->parent = menu;
456 entry->flags = flags;
457 entry->callback = NULL;
458 entry->userdata = NULL;
459 entry->submenu = NULL;
460 SDL_snprintf(entry->label_cache, sizeof(entry->label_cache), "%s", label ? label : "");
461
462 if (label != NULL && flags & SDL_TRAYENTRY_SUBMENU) {
463 entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu));
464 if (!entry->submenu) {
465 SDL_free(entry);
466 SDL_free(label_w);
467 return NULL;
468 }
469
470 entry->submenu->hMenu = CreatePopupMenu();
471 entry->submenu->nEntries = 0;
472 entry->submenu->entries = NULL;
473 entry->submenu->parent_entry = entry;
474 entry->submenu->parent_tray = NULL;
475
476 entry->id = (UINT_PTR) entry->submenu->hMenu;
477 } else {
478 entry->id = get_next_id();
479 }
480
481 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries));
482 if (!new_entries) {
483 SDL_free(entry);
484 SDL_free(label_w);
485 if (entry->submenu) {
486 DestroyMenu(entry->submenu->hMenu);
487 SDL_free(entry->submenu);
488 }
489 return NULL;
490 }
491
492 menu->entries = new_entries;
493 menu->nEntries++;
494
495 for (int i = menu->nEntries - 1; i > pos; i--) {
496 menu->entries[i] = menu->entries[i - 1];
497 }
498
499 new_entries[pos] = entry;
500 new_entries[menu->nEntries] = NULL;
501
502 if (label == NULL) {
503 InsertMenuW(menu->hMenu, windows_compatible_pos, MF_SEPARATOR | MF_BYPOSITION, entry->id, NULL);
504 } else {
505 UINT mf = MF_STRING | MF_BYPOSITION;
506 if (flags & SDL_TRAYENTRY_SUBMENU) {
507 mf = MF_POPUP;
508 }
509
510 if (flags & SDL_TRAYENTRY_DISABLED) {
511 mf |= MF_DISABLED | MF_GRAYED;
512 }
513
514 if (flags & SDL_TRAYENTRY_CHECKED) {
515 mf |= MF_CHECKED;
516 }
517
518 InsertMenuW(menu->hMenu, windows_compatible_pos, mf, entry->id, label_w);
519
520 SDL_free(label_w);
521 }
522
523 return entry;
524}
525
526void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
527{
528 if (!entry) {
529 return;
530 }
531
532 SDL_snprintf(entry->label_cache, sizeof(entry->label_cache), "%s", label);
533
534 wchar_t *label_w = escape_label(label);
535
536 if (!label_w) {
537 return;
538 }
539
540 MENUITEMINFOW mii;
541 mii.cbSize = sizeof(MENUITEMINFOW);
542 mii.fMask = MIIM_STRING;
543
544 mii.dwTypeData = label_w;
545 mii.cch = (UINT) SDL_wcslen(label_w);
546
547 if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, TRUE, &mii)) {
548 SDL_SetError("Couldn't update tray entry label");
549 }
550
551 SDL_free(label_w);
552}
553
554const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
555{
556 if (!entry) {
557 SDL_InvalidParamError("entry");
558 return NULL;
559 }
560
561 return entry->label_cache;
562}
563
564void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
565{
566 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
567 return;
568 }
569
570 CheckMenuItem(entry->parent->hMenu, (UINT) entry->id, checked ? MF_CHECKED : MF_UNCHECKED);
571}
572
573bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
574{
575 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
576 return false;
577 }
578
579 MENUITEMINFOW mii;
580 mii.cbSize = sizeof(MENUITEMINFOW);
581 mii.fMask = MIIM_STATE;
582
583 GetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii);
584
585 return ((mii.fState & MFS_CHECKED) != 0);
586}
587
588void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
589{
590 if (!entry) {
591 return;
592 }
593
594 EnableMenuItem(entry->parent->hMenu, (UINT) entry->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
595}
596
597bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
598{
599 if (!entry) {
600 return false;
601 }
602
603 MENUITEMINFOW mii;
604 mii.cbSize = sizeof(MENUITEMINFOW);
605 mii.fMask = MIIM_STATE;
606
607 GetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii);
608
609 return ((mii.fState & MFS_ENABLED) != 0);
610}
611
612void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
613{
614 if (!entry) {
615 return;
616 }
617
618 entry->callback = callback;
619 entry->userdata = userdata;
620}
621
622void SDL_ClickTrayEntry(SDL_TrayEntry *entry)
623{
624 if (!entry) {
625 return;
626 }
627
628 if (entry->flags & SDL_TRAYENTRY_CHECKBOX) {
629 SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
630 }
631
632 if (entry->callback) {
633 entry->callback(entry->userdata, entry);
634 }
635}
636
637SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
638{
639 if (!entry) {
640 SDL_InvalidParamError("entry");
641 return NULL;
642 }
643
644 return entry->parent;
645}
646
647SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
648{
649 if (!menu) {
650 SDL_InvalidParamError("menu");
651 return NULL;
652 }
653
654 return menu->parent_entry;
655}
656
657SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
658{
659 if (!menu) {
660 SDL_InvalidParamError("menu");
661 return NULL;
662 }
663
664 return menu->parent_tray;
665}
666
667void SDL_DestroyTray(SDL_Tray *tray)
668{
669 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) {
670 return;
671 }
672
673 SDL_UnregisterTray(tray);
674
675 Shell_NotifyIconW(NIM_DELETE, &tray->nid);
676
677 if (tray->menu) {
678 DestroySDLMenu(tray->menu);
679 }
680
681 if (tray->icon) {
682 DestroyIcon(tray->icon);
683 }
684
685 if (tray->hwnd) {
686 DestroyWindow(tray->hwnd);
687 }
688
689 SDL_free(tray);
690}