Atlas - SDL_tray.c
Home / ext / SDL / src / tray / windows Lines: 1 | Size: 18230 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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 27#include <windowsx.h> 28#include <shellapi.h> 29 30#ifndef NOTIFYICON_VERSION_4 31#define NOTIFYICON_VERSION_4 4 32#endif 33#ifndef NIF_SHOWTIP 34#define NIF_SHOWTIP 0x00000080 35#endif 36 37#define WM_TRAYICON (WM_USER + 1) 38 39struct SDL_TrayMenu { 40 HMENU hMenu; 41 42 int nEntries; 43 SDL_TrayEntry **entries; 44 45 SDL_Tray *parent_tray; 46 SDL_TrayEntry *parent_entry; 47}; 48 49struct SDL_TrayEntry { 50 SDL_TrayMenu *parent; 51 UINT_PTR id; 52 53 char label_cache[4096]; 54 SDL_TrayEntryFlags flags; 55 SDL_TrayCallback callback; 56 void *userdata; 57 SDL_TrayMenu *submenu; 58}; 59 60struct SDL_Tray { 61 NOTIFYICONDATAW nid; 62 HWND hwnd; 63 HICON icon; 64 SDL_TrayMenu *menu; 65}; 66 67static UINT_PTR get_next_id(void) 68{ 69 static UINT_PTR next_id = 0; 70 return ++next_id; 71} 72 73static SDL_TrayEntry *find_entry_in_menu(SDL_TrayMenu *menu, UINT_PTR id) 74{ 75 for (int i = 0; i < menu->nEntries; i++) { 76 SDL_TrayEntry *entry = menu->entries[i]; 77 78 if (entry->id == id) { 79 return entry; 80 } 81 82 if (entry->submenu) { 83 SDL_TrayEntry *e = find_entry_in_menu(entry->submenu, id); 84 85 if (e) { 86 return e; 87 } 88 } 89 } 90 91 return NULL; 92} 93 94static SDL_TrayEntry *find_entry_with_id(SDL_Tray *tray, UINT_PTR id) 95{ 96 if (!tray->menu) { 97 return NULL; 98 } 99 100 return find_entry_in_menu(tray->menu, id); 101} 102 103LRESULT CALLBACK TrayWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 104 SDL_Tray *tray = (SDL_Tray *) GetWindowLongPtr(hwnd, GWLP_USERDATA); 105 SDL_TrayEntry *entry = NULL; 106 107 if (!tray) { 108 return DefWindowProc(hwnd, uMsg, wParam, lParam); 109 } 110 111 static UINT s_taskbarRestart = 0; 112 if (s_taskbarRestart == 0) { 113 s_taskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); 114 } 115 if (uMsg == s_taskbarRestart) { 116 Shell_NotifyIconW(NIM_ADD, &tray->nid); 117 Shell_NotifyIconW(NIM_SETVERSION, &tray->nid); 118 } 119 120 switch (uMsg) { 121 case WM_TRAYICON: 122 if (LOWORD(lParam) == WM_CONTEXTMENU || LOWORD(lParam) == WM_LBUTTONUP) { 123 SetForegroundWindow(hwnd); 124 125 if (tray->menu) { 126 TrackPopupMenu(tray->menu->hMenu, TPM_BOTTOMALIGN | TPM_RIGHTALIGN, GET_X_LPARAM(wParam), GET_Y_LPARAM(wParam), 0, hwnd, NULL); 127 } 128 } 129 break; 130 131 case WM_COMMAND: 132 entry = find_entry_with_id(tray, LOWORD(wParam)); 133 134 if (entry && (entry->flags & SDL_TRAYENTRY_CHECKBOX)) { 135 SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry)); 136 } 137 138 if (entry && entry->callback) { 139 entry->callback(entry->userdata, entry); 140 } 141 break; 142 143 case WM_SETTINGCHANGE: 144 if (wParam == 0 && lParam != 0 && SDL_wcscmp((wchar_t *)lParam, L"ImmersiveColorSet") == 0) { 145 WIN_UpdateDarkModeForHWND(hwnd); 146 } 147 break; 148 149 default: 150 return DefWindowProc(hwnd, uMsg, wParam, lParam); 151 } 152 return 0; 153} 154 155static void DestroySDLMenu(SDL_TrayMenu *menu) 156{ 157 for (int i = 0; i < menu->nEntries; i++) { 158 if (menu->entries[i] && menu->entries[i]->submenu) { 159 DestroySDLMenu(menu->entries[i]->submenu); 160 } 161 SDL_free(menu->entries[i]); 162 } 163 SDL_free(menu->entries); 164 DestroyMenu(menu->hMenu); 165 SDL_free(menu); 166} 167 168static wchar_t *escape_label(const char *in) 169{ 170 const char *c; 171 char *c2; 172 int len = 0; 173 174 for (c = in; *c; c++) { 175 len += (*c == '&') ? 2 : 1; 176 } 177 178 char *escaped = (char *)SDL_malloc(SDL_strlen(in) + len + 1); 179 if (!escaped) { 180 return NULL; 181 } 182 183 for (c = in, c2 = escaped; *c;) { 184 if (*c == '&') { 185 *c2++ = *c; 186 } 187 188 *c2++ = *c++; 189 } 190 191 *c2 = '\0'; 192 193 wchar_t *out = WIN_UTF8ToStringW(escaped); 194 SDL_free(escaped); 195 196 return out; 197} 198 199static HICON load_default_icon() 200{ 201 HINSTANCE hInstance = GetModuleHandle(NULL); 202 if (!hInstance) { 203 return LoadIcon(NULL, IDI_APPLICATION); 204 } 205 206 const char *hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL); 207 if (hint && *hint) { 208 HICON icon = LoadIcon(hInstance, MAKEINTRESOURCE(SDL_atoi(hint))); 209 return icon ? icon : LoadIcon(NULL, IDI_APPLICATION); 210 } 211 212 hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON); 213 if (hint && *hint) { 214 HICON icon = LoadIcon(hInstance, MAKEINTRESOURCE(SDL_atoi(hint))); 215 return icon ? icon : LoadIcon(NULL, IDI_APPLICATION); 216 } 217 218 return LoadIcon(NULL, IDI_APPLICATION); 219} 220 221void SDL_UpdateTrays(void) 222{ 223} 224 225static void SDL_PropTrayCleanupCb(void *userdata, void *cls) 226{ 227 WNDCLASSEX wcex; 228 229 wcex.hIcon = NULL; 230 wcex.hIconSm = NULL; 231 HINSTANCE h = GetModuleHandle(NULL); 232 if (GetClassInfoEx(h, cls, &wcex)) { 233 UnregisterClass(cls, h); 234 } 235} 236 237static bool SDL_RegisterTrayClass(LPCWSTR className) 238{ 239 SDL_PropertiesID props = SDL_GetGlobalProperties(); 240 if (!props) { 241 return false; 242 } 243 if (SDL_GetPointerProperty(props, SDL_PROP_TRAY_CLEANUP, NULL) != NULL) { 244 return true; 245 } 246 247 WNDCLASSEX wcex; 248 249 wcex.cbSize = sizeof(WNDCLASSEX); 250 wcex.hCursor = NULL; 251 wcex.hIcon = NULL; 252 wcex.hIconSm = NULL; 253 wcex.lpszMenuName = NULL; 254 wcex.lpszClassName = className; 255 wcex.style = 0; 256 wcex.hbrBackground = NULL; 257 wcex.lpfnWndProc = TrayWindowProc; 258 wcex.hInstance = NULL; 259 wcex.cbClsExtra = 0; 260 wcex.cbWndExtra = 0; 261 262 if (!RegisterClassEx(&wcex)) { 263 return SDL_SetError("Couldn't register tray class"); 264 } 265 266 SDL_SetPointerPropertyWithCleanup(props, SDL_PROP_TRAY_CLEANUP, (void *)wcex.lpszClassName, SDL_PropTrayCleanupCb, NULL); 267 return true; 268} 269 270SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) 271{ 272 if (!SDL_IsMainThread()) { 273 SDL_SetError("This function should be called on the main thread"); 274 return NULL; 275 } 276 277 SDL_Tray *tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray)); 278 279 if (!tray) { 280 return NULL; 281 } 282 283 tray->menu = NULL; 284 if (!SDL_RegisterTrayClass(TEXT("SDL_TRAY"))) { 285 SDL_SetError("Failed to register SDL_TRAY window class"); 286 return NULL; 287 } 288 tray->hwnd = CreateWindowEx(0, TEXT("SDL_TRAY"), NULL, WS_OVERLAPPEDWINDOW, 289 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, NULL, NULL); 290 291 WIN_UpdateDarkModeForHWND(tray->hwnd); 292 293 SDL_zero(tray->nid); 294 tray->nid.cbSize = sizeof(NOTIFYICONDATAW); 295 tray->nid.hWnd = tray->hwnd; 296 tray->nid.uID = (UINT) get_next_id(); 297 tray->nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP; 298 tray->nid.uCallbackMessage = WM_TRAYICON; 299 tray->nid.uVersion = NOTIFYICON_VERSION_4; 300 wchar_t *tooltipw = WIN_UTF8ToStringW(tooltip); 301 SDL_wcslcpy(tray->nid.szTip, tooltipw, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip)); 302 SDL_free(tooltipw); 303 304 if (icon) { 305 tray->nid.hIcon = WIN_CreateIconFromSurface(icon); 306 307 if (!tray->nid.hIcon) { 308 tray->nid.hIcon = load_default_icon(); 309 } 310 311 tray->icon = tray->nid.hIcon; 312 } else { 313 tray->nid.hIcon = load_default_icon(); 314 tray->icon = tray->nid.hIcon; 315 } 316 317 Shell_NotifyIconW(NIM_ADD, &tray->nid); 318 Shell_NotifyIconW(NIM_SETVERSION, &tray->nid); 319 320 SetWindowLongPtr(tray->hwnd, GWLP_USERDATA, (LONG_PTR) tray); 321 322 SDL_RegisterTray(tray); 323 324 return tray; 325} 326 327void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) 328{ 329 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 330 return; 331 } 332 333 if (tray->icon) { 334 DestroyIcon(tray->icon); 335 } 336 337 if (icon) { 338 tray->nid.hIcon = WIN_CreateIconFromSurface(icon); 339 340 if (!tray->nid.hIcon) { 341 tray->nid.hIcon = load_default_icon(); 342 } 343 344 tray->icon = tray->nid.hIcon; 345 } else { 346 tray->nid.hIcon = load_default_icon(); 347 tray->icon = tray->nid.hIcon; 348 } 349 350 Shell_NotifyIconW(NIM_MODIFY, &tray->nid); 351} 352 353void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip) 354{ 355 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 356 return; 357 } 358 359 if (tooltip) { 360 wchar_t *tooltipw = WIN_UTF8ToStringW(tooltip); 361 SDL_wcslcpy(tray->nid.szTip, tooltipw, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip)); 362 SDL_free(tooltipw); 363 } else { 364 tray->nid.szTip[0] = '\0'; 365 } 366 367 Shell_NotifyIconW(NIM_MODIFY, &tray->nid); 368} 369 370SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray) 371{ 372 CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 373 SDL_InvalidParamError("tray"); 374 return NULL; 375 } 376 377 tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu)); 378 379 if (!tray->menu) { 380 return NULL; 381 } 382 383 tray->menu->hMenu = CreatePopupMenu(); 384 tray->menu->parent_tray = tray; 385 tray->menu->parent_entry = NULL; 386 387 return tray->menu; 388} 389 390SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray) 391{ 392 CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 393 SDL_InvalidParamError("tray"); 394 return NULL; 395 } 396 397 return tray->menu; 398} 399 400SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) 401{ 402 CHECK_PARAM(!entry) { 403 SDL_InvalidParamError("entry"); 404 return NULL; 405 } 406 407 if (!entry->submenu) { 408 SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU"); 409 return NULL; 410 } 411 412 return entry->submenu; 413} 414 415SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry) 416{ 417 CHECK_PARAM(!entry) { 418 SDL_InvalidParamError("entry"); 419 return NULL; 420 } 421 422 return entry->submenu; 423} 424 425const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count) 426{ 427 CHECK_PARAM(!menu) { 428 SDL_InvalidParamError("menu"); 429 return NULL; 430 } 431 432 if (count) { 433 *count = menu->nEntries; 434 } 435 return (const SDL_TrayEntry **)menu->entries; 436} 437 438void SDL_RemoveTrayEntry(SDL_TrayEntry *entry) 439{ 440 if (!entry) { 441 return; 442 } 443 444 SDL_TrayMenu *menu = entry->parent; 445 446 bool found = false; 447 for (int i = 0; i < menu->nEntries - 1; i++) { 448 if (menu->entries[i] == entry) { 449 found = true; 450 } 451 452 if (found) { 453 menu->entries[i] = menu->entries[i + 1]; 454 } 455 } 456 457 if (entry->submenu) { 458 DestroySDLMenu(entry->submenu); 459 } 460 461 menu->nEntries--; 462 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries)); 463 464 /* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */ 465 if (new_entries) { 466 menu->entries = new_entries; 467 menu->entries[menu->nEntries] = NULL; 468 } 469 470 if (!DeleteMenu(menu->hMenu, (UINT) entry->id, MF_BYCOMMAND)) { 471 /* This is somewhat useless since we don't return anything, but might help with eventual bugs */ 472 SDL_SetError("Couldn't destroy tray entry"); 473 } 474 475 SDL_free(entry); 476} 477 478SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) 479{ 480 CHECK_PARAM(!menu) { 481 SDL_InvalidParamError("menu"); 482 return NULL; 483 } 484 485 CHECK_PARAM(pos < -1 || pos > menu->nEntries) { 486 SDL_InvalidParamError("pos"); 487 return NULL; 488 } 489 490 int windows_compatible_pos = pos; 491 492 if (pos == -1) { 493 pos = menu->nEntries; 494 } else if (pos == menu->nEntries) { 495 windows_compatible_pos = -1; 496 } 497 498 SDL_TrayEntry *entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry)); 499 if (!entry) { 500 return NULL; 501 } 502 503 wchar_t *label_w = NULL; 504 505 if (label && (label_w = escape_label(label)) == NULL) { 506 SDL_free(entry); 507 return NULL; 508 } 509 510 entry->parent = menu; 511 entry->flags = flags; 512 entry->callback = NULL; 513 entry->userdata = NULL; 514 entry->submenu = NULL; 515 SDL_snprintf(entry->label_cache, sizeof(entry->label_cache), "%s", label ? label : ""); 516 517 if (label != NULL && flags & SDL_TRAYENTRY_SUBMENU) { 518 entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu)); 519 if (!entry->submenu) { 520 SDL_free(entry); 521 SDL_free(label_w); 522 return NULL; 523 } 524 525 entry->submenu->hMenu = CreatePopupMenu(); 526 entry->submenu->nEntries = 0; 527 entry->submenu->entries = NULL; 528 entry->submenu->parent_entry = entry; 529 entry->submenu->parent_tray = NULL; 530 531 entry->id = (UINT_PTR) entry->submenu->hMenu; 532 } else { 533 entry->id = get_next_id(); 534 } 535 536 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries)); 537 if (!new_entries) { 538 SDL_free(entry); 539 SDL_free(label_w); 540 if (entry->submenu) { 541 DestroyMenu(entry->submenu->hMenu); 542 SDL_free(entry->submenu); 543 } 544 return NULL; 545 } 546 547 menu->entries = new_entries; 548 menu->nEntries++; 549 550 for (int i = menu->nEntries - 1; i > pos; i--) { 551 menu->entries[i] = menu->entries[i - 1]; 552 } 553 554 new_entries[pos] = entry; 555 new_entries[menu->nEntries] = NULL; 556 557 if (label == NULL) { 558 InsertMenuW(menu->hMenu, windows_compatible_pos, MF_SEPARATOR | MF_BYPOSITION, entry->id, NULL); 559 } else { 560 UINT mf = MF_STRING | MF_BYPOSITION; 561 if (flags & SDL_TRAYENTRY_SUBMENU) { 562 mf = MF_POPUP; 563 } 564 565 if (flags & SDL_TRAYENTRY_DISABLED) { 566 mf |= MF_DISABLED | MF_GRAYED; 567 } 568 569 if (flags & SDL_TRAYENTRY_CHECKED) { 570 mf |= MF_CHECKED; 571 } 572 573 InsertMenuW(menu->hMenu, windows_compatible_pos, mf, entry->id, label_w); 574 575 SDL_free(label_w); 576 } 577 578 return entry; 579} 580 581void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) 582{ 583 if (!entry) { 584 return; 585 } 586 587 SDL_snprintf(entry->label_cache, sizeof(entry->label_cache), "%s", label); 588 589 wchar_t *label_w = escape_label(label); 590 591 if (!label_w) { 592 return; 593 } 594 595 MENUITEMINFOW mii; 596 mii.cbSize = sizeof(MENUITEMINFOW); 597 mii.fMask = MIIM_STRING; 598 599 mii.dwTypeData = label_w; 600 mii.cch = (UINT) SDL_wcslen(label_w); 601 602 if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii)) { 603 SDL_SetError("Couldn't update tray entry label"); 604 } 605 606 SDL_free(label_w); 607} 608 609const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry) 610{ 611 CHECK_PARAM(!entry) { 612 SDL_InvalidParamError("entry"); 613 return NULL; 614 } 615 616 return entry->label_cache; 617} 618 619void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked) 620{ 621 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { 622 return; 623 } 624 625 CheckMenuItem(entry->parent->hMenu, (UINT) entry->id, checked ? MF_CHECKED : MF_UNCHECKED); 626} 627 628bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry) 629{ 630 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { 631 return false; 632 } 633 634 MENUITEMINFOW mii; 635 mii.cbSize = sizeof(MENUITEMINFOW); 636 mii.fMask = MIIM_STATE; 637 638 GetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii); 639 640 return ((mii.fState & MFS_CHECKED) != 0); 641} 642 643void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled) 644{ 645 if (!entry) { 646 return; 647 } 648 649 EnableMenuItem(entry->parent->hMenu, (UINT) entry->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : (MF_DISABLED | MF_GRAYED))); 650} 651 652bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry) 653{ 654 if (!entry) { 655 return false; 656 } 657 658 MENUITEMINFOW mii; 659 mii.cbSize = sizeof(MENUITEMINFOW); 660 mii.fMask = MIIM_STATE; 661 662 GetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii); 663 664 return ((mii.fState & MFS_ENABLED) != 0); 665} 666 667void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) 668{ 669 if (!entry) { 670 return; 671 } 672 673 entry->callback = callback; 674 entry->userdata = userdata; 675} 676 677void SDL_ClickTrayEntry(SDL_TrayEntry *entry) 678{ 679 if (!entry) { 680 return; 681 } 682 683 if (entry->flags & SDL_TRAYENTRY_CHECKBOX) { 684 SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry)); 685 } 686 687 if (entry->callback) { 688 entry->callback(entry->userdata, entry); 689 } 690} 691 692SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) 693{ 694 CHECK_PARAM(!entry) { 695 SDL_InvalidParamError("entry"); 696 return NULL; 697 } 698 699 return entry->parent; 700} 701 702SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu) 703{ 704 CHECK_PARAM(!menu) { 705 SDL_InvalidParamError("menu"); 706 return NULL; 707 } 708 709 return menu->parent_entry; 710} 711 712SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu) 713{ 714 CHECK_PARAM(!menu) { 715 SDL_InvalidParamError("menu"); 716 return NULL; 717 } 718 719 return menu->parent_tray; 720} 721 722void SDL_DestroyTray(SDL_Tray *tray) 723{ 724 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 725 return; 726 } 727 728 SDL_UnregisterTray(tray); 729 730 Shell_NotifyIconW(NIM_DELETE, &tray->nid); 731 732 if (tray->menu) { 733 DestroySDLMenu(tray->menu); 734 } 735 736 if (tray->icon) { 737 DestroyIcon(tray->icon); 738 } 739 740 if (tray->hwnd) { 741 DestroyWindow(tray->hwnd); 742 } 743 744 SDL_free(tray); 745} 746[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.