Atlas - SDL_tray.c
Home / ext / SDL / src / tray / unix Lines: 1 | Size: 19749 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 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 "../../video/SDL_stb_c.h" 26 27#include <dlfcn.h> 28#include <errno.h> 29 30/* getpid() */ 31#include <unistd.h> 32 33/* APPINDICATOR_HEADER is not exposed as a build setting, but the code has been 34 written nevertheless to make future maintenance easier. */ 35#ifdef APPINDICATOR_HEADER 36#include APPINDICATOR_HEADER 37#else 38#include "../../core/unix/SDL_gtk.h" 39 40/* ------------------------------------------------------------------------- */ 41/* BEGIN THIRD-PARTY HEADER CONTENT */ 42/* ------------------------------------------------------------------------- */ 43/* AppIndicator */ 44 45typedef enum { 46 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, 47 APP_INDICATOR_CATEGORY_COMMUNICATIONS, 48 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES, 49 APP_INDICATOR_CATEGORY_HARDWARE, 50 APP_INDICATOR_CATEGORY_OTHER 51} AppIndicatorCategory; 52 53typedef enum { 54 APP_INDICATOR_STATUS_PASSIVE, 55 APP_INDICATOR_STATUS_ACTIVE, 56 APP_INDICATOR_STATUS_ATTENTION 57} AppIndicatorStatus; 58 59typedef struct _AppIndicator AppIndicator; 60 61static AppIndicator *(*app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category); 62static void (*app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status); 63static void (*app_indicator_set_icon)(AppIndicator *self, const gchar *icon_name); 64static void (*app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu); 65 66/* ------------------------------------------------------------------------- */ 67/* END THIRD-PARTY HEADER CONTENT */ 68/* ------------------------------------------------------------------------- */ 69#endif 70 71static void *libappindicator = NULL; 72 73static void quit_appindicator(void) 74{ 75 if (libappindicator) { 76 dlclose(libappindicator); 77 libappindicator = NULL; 78 } 79} 80 81const char *appindicator_names[] = { 82#ifdef SDL_PLATFORM_OPENBSD 83 "libayatana-appindicator3.so", 84 "libappindicator3.so", 85#else 86 "libayatana-appindicator3.so.1", 87 "libappindicator3.so.1", 88#endif 89 NULL 90}; 91 92static void *find_lib(const char **names) 93{ 94 const char **name_ptr = names; 95 void *handle = NULL; 96 97 do { 98 handle = dlopen(*name_ptr, RTLD_LAZY); 99 } while (*++name_ptr && !handle); 100 101 return handle; 102} 103 104static bool init_appindicator(void) 105{ 106 if (libappindicator) { 107 return true; 108 } 109 110 libappindicator = find_lib(appindicator_names); 111 112 if (!libappindicator) { 113 quit_appindicator(); 114 return SDL_SetError("Could not load AppIndicator libraries"); 115 } 116 117 app_indicator_new = dlsym(libappindicator, "app_indicator_new"); 118 app_indicator_set_status = dlsym(libappindicator, "app_indicator_set_status"); 119 app_indicator_set_icon = dlsym(libappindicator, "app_indicator_set_icon"); 120 app_indicator_set_menu = dlsym(libappindicator, "app_indicator_set_menu"); 121 122 if (!app_indicator_new || 123 !app_indicator_set_status || 124 !app_indicator_set_icon || 125 !app_indicator_set_menu) { 126 quit_appindicator(); 127 return SDL_SetError("Could not load AppIndicator functions"); 128 } 129 130 return true; 131} 132 133struct SDL_TrayMenu { 134 GtkMenuShell *menu; 135 136 int nEntries; 137 SDL_TrayEntry **entries; 138 139 SDL_Tray *parent_tray; 140 SDL_TrayEntry *parent_entry; 141}; 142 143struct SDL_TrayEntry { 144 SDL_TrayMenu *parent; 145 GtkWidget *item; 146 147 /* Checkboxes are "activated" when programmatically checked/unchecked; this 148 is a workaround. */ 149 bool ignore_signal; 150 151 SDL_TrayEntryFlags flags; 152 SDL_TrayCallback callback; 153 void *userdata; 154 SDL_TrayMenu *submenu; 155}; 156 157struct SDL_Tray { 158 AppIndicator *indicator; 159 SDL_TrayMenu *menu; 160 char *icon_dir; 161 char *icon_path; 162 163 GtkMenuShell *menu_cached; 164}; 165 166static void call_callback(GtkMenuItem *item, gpointer ptr) 167{ 168 SDL_TrayEntry *entry = ptr; 169 170 /* Not needed with AppIndicator, may be needed with other frameworks */ 171 /* if (entry->flags & SDL_TRAYENTRY_CHECKBOX) { 172 SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry)); 173 } */ 174 175 if (entry->ignore_signal) { 176 return; 177 } 178 179 if (entry->callback) { 180 entry->callback(entry->userdata, entry); 181 } 182} 183 184static bool new_tmp_filename(SDL_Tray *tray) 185{ 186 static int count = 0; 187 188 int would_have_written = SDL_asprintf(&tray->icon_path, "%s/%d.png", tray->icon_dir, count++); 189 190 if (would_have_written >= 0) { 191 return true; 192 } 193 194 tray->icon_path = NULL; 195 SDL_SetError("Failed to format new temporary filename"); 196 return false; 197} 198 199static const char *get_appindicator_id(void) 200{ 201 static int count = 0; 202 static char buffer[256]; 203 204 int would_have_written = SDL_snprintf(buffer, sizeof(buffer), "sdl-appindicator-%d-%d", getpid(), count++); 205 206 if (would_have_written <= 0 || would_have_written >= sizeof(buffer) - 1) { 207 SDL_SetError("Couldn't fit %d bytes in buffer of size %d", would_have_written, (int) sizeof(buffer)); 208 return NULL; 209 } 210 211 return buffer; 212} 213 214static void DestroySDLMenu(SDL_TrayMenu *menu) 215{ 216 for (int i = 0; i < menu->nEntries; i++) { 217 if (menu->entries[i] && menu->entries[i]->submenu) { 218 DestroySDLMenu(menu->entries[i]->submenu); 219 } 220 SDL_free(menu->entries[i]); 221 } 222 223 if (menu->menu) { 224 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 225 if (gtk) { 226 gtk->g.object_unref(menu->menu); 227 SDL_Gtk_ExitContext(gtk); 228 } 229 } 230 231 SDL_free(menu->entries); 232 SDL_free(menu); 233} 234 235void SDL_UpdateTrays(void) 236{ 237 if (SDL_HasActiveTrays()) { 238 SDL_UpdateGtk(); 239 } 240} 241 242SDL_Tray *SDL_CreateTrayWithProperties(SDL_PropertiesID props) 243{ 244 if (!SDL_IsMainThread()) { 245 SDL_SetError("This function should be called on the main thread"); 246 return NULL; 247 } 248 249 if (!init_appindicator()) { 250 return NULL; 251 } 252 253 SDL_Surface *icon = (SDL_Surface *)SDL_GetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, NULL); 254 255 SDL_Tray *tray = NULL; 256 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 257 if (!gtk) { 258 goto tray_error; 259 } 260 261 tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray)); 262 if (!tray) { 263 goto tray_error; 264 } 265 266 const gchar *cache_dir = gtk->g.get_user_cache_dir(); 267 if (!cache_dir) { 268 SDL_SetError("Cannot get user cache directory: %s", strerror(errno)); 269 goto tray_error; 270 } 271 272 char *sdl_dir; 273 SDL_asprintf(&sdl_dir, "%s/SDL", cache_dir); 274 if (!SDL_GetPathInfo(sdl_dir, NULL)) { 275 if (!SDL_CreateDirectory(sdl_dir)) { 276 SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno)); 277 goto sdl_dir_error; 278 } 279 } 280 281 /* On success, g_mkdtemp edits its argument in-place to replace the Xs 282 * with a random directory name, which it creates safely and atomically. 283 * On failure, it sets errno. */ 284 SDL_asprintf(&tray->icon_dir, "%s/tray-XXXXXX", sdl_dir); 285 if (!gtk->g.mkdtemp(tray->icon_dir)) { 286 SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno)); 287 goto icon_dir_error; 288 } 289 290 if (icon) { 291 if (!new_tmp_filename(tray)) { 292 goto icon_dir_error; 293 } 294 295 SDL_SavePNG(icon, tray->icon_path); 296 } else { 297 // allocate a dummy icon path 298 SDL_asprintf(&tray->icon_path, " "); 299 } 300 301 tray->indicator = app_indicator_new(get_appindicator_id(), tray->icon_path, 302 APP_INDICATOR_CATEGORY_APPLICATION_STATUS); 303 304 app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE); 305 306 // The tray icon isn't shown before a menu is created; create one early. 307 tray->menu_cached = (GtkMenuShell *)gtk->g.object_ref_sink(gtk->gtk.menu_new()); 308 app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu_cached)); 309 310 SDL_RegisterTray(tray); 311 SDL_Gtk_ExitContext(gtk); 312 SDL_free(sdl_dir); 313 314 return tray; 315 316icon_dir_error: 317 SDL_free(tray->icon_dir); 318 319sdl_dir_error: 320 SDL_free(sdl_dir); 321 322tray_error: 323 SDL_free(tray); 324 325 if (gtk) { 326 SDL_Gtk_ExitContext(gtk); 327 } 328 329 return NULL; 330} 331 332SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip) 333{ 334 SDL_Tray *tray; 335 SDL_PropertiesID props = SDL_CreateProperties(); 336 if (!props) { 337 return NULL; 338 } 339 if (icon) { 340 SDL_SetPointerProperty(props, SDL_PROP_TRAY_CREATE_ICON_POINTER, icon); 341 } 342 if (tooltip) { 343 SDL_SetStringProperty(props, SDL_PROP_TRAY_CREATE_TOOLTIP_STRING, tooltip); 344 } 345 tray = SDL_CreateTrayWithProperties(props); 346 SDL_DestroyProperties(props); 347 return tray; 348} 349 350void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) 351{ 352 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 353 return; 354 } 355 356 if (tray->icon_path) { 357 SDL_RemovePath(tray->icon_path); 358 SDL_free(tray->icon_path); 359 tray->icon_path = NULL; 360 } 361 362 /* AppIndicator caches the icon files; always change filename to avoid caching */ 363 364 if (icon && new_tmp_filename(tray)) { 365 SDL_SavePNG(icon, tray->icon_path); 366 app_indicator_set_icon(tray->indicator, tray->icon_path); 367 } else { 368 SDL_free(tray->icon_path); 369 tray->icon_path = NULL; 370 app_indicator_set_icon(tray->indicator, NULL); 371 } 372} 373 374void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip) 375{ 376 /* AppIndicator provides no tooltip support. */ 377} 378 379SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray) 380{ 381 CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 382 SDL_InvalidParamError("tray"); 383 return NULL; 384 } 385 386 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 387 if (!gtk) { 388 return NULL; 389 } 390 391 tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu)); 392 if (!tray->menu) { 393 SDL_Gtk_ExitContext(gtk); 394 return NULL; 395 } 396 397 tray->menu->menu = gtk->g.object_ref(tray->menu_cached); 398 tray->menu->parent_tray = tray; 399 tray->menu->parent_entry = NULL; 400 tray->menu->nEntries = 0; 401 tray->menu->entries = NULL; 402 403 SDL_Gtk_ExitContext(gtk); 404 405 return tray->menu; 406} 407 408SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray) 409{ 410 CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 411 SDL_InvalidParamError("tray"); 412 return NULL; 413 } 414 415 return tray->menu; 416} 417 418SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) 419{ 420 CHECK_PARAM(!entry) { 421 SDL_InvalidParamError("entry"); 422 return NULL; 423 } 424 425 if (entry->submenu) { 426 SDL_SetError("Tray entry submenu already exists"); 427 return NULL; 428 } 429 430 if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) { 431 SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU"); 432 return NULL; 433 } 434 435 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 436 if (!gtk) { 437 return NULL; 438 } 439 440 entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu)); 441 if (!entry->submenu) { 442 SDL_Gtk_ExitContext(gtk); 443 return NULL; 444 } 445 446 entry->submenu->menu = gtk->g.object_ref_sink(gtk->gtk.menu_new()); 447 entry->submenu->parent_tray = NULL; 448 entry->submenu->parent_entry = entry; 449 entry->submenu->nEntries = 0; 450 entry->submenu->entries = NULL; 451 452 gtk->gtk.menu_item_set_submenu(GTK_MENU_ITEM(entry->item), GTK_WIDGET(entry->submenu->menu)); 453 454 SDL_Gtk_ExitContext(gtk); 455 456 return entry->submenu; 457} 458 459SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry) 460{ 461 CHECK_PARAM(!entry) { 462 SDL_InvalidParamError("entry"); 463 return NULL; 464 } 465 466 return entry->submenu; 467} 468 469const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count) 470{ 471 CHECK_PARAM(!menu) { 472 SDL_InvalidParamError("menu"); 473 return NULL; 474 } 475 476 if (count) { 477 *count = menu->nEntries; 478 } 479 return (const SDL_TrayEntry **)menu->entries; 480} 481 482void SDL_RemoveTrayEntry(SDL_TrayEntry *entry) 483{ 484 if (!entry) { 485 return; 486 } 487 488 SDL_TrayMenu *menu = entry->parent; 489 490 bool found = false; 491 for (int i = 0; i < menu->nEntries - 1; i++) { 492 if (menu->entries[i] == entry) { 493 found = true; 494 } 495 496 if (found) { 497 menu->entries[i] = menu->entries[i + 1]; 498 } 499 } 500 501 if (entry->submenu) { 502 DestroySDLMenu(entry->submenu); 503 } 504 505 menu->nEntries--; 506 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries)); 507 508 /* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */ 509 if (new_entries) { 510 menu->entries = new_entries; 511 menu->entries[menu->nEntries] = NULL; 512 } 513 514 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 515 if (gtk) { 516 gtk->gtk.widget_destroy(entry->item); 517 SDL_Gtk_ExitContext(gtk); 518 } 519 SDL_free(entry); 520} 521 522SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) 523{ 524 CHECK_PARAM(!menu) { 525 SDL_InvalidParamError("menu"); 526 return NULL; 527 } 528 529 CHECK_PARAM(pos < -1 || pos > menu->nEntries) { 530 SDL_InvalidParamError("pos"); 531 return NULL; 532 } 533 534 if (pos == -1) { 535 pos = menu->nEntries; 536 } 537 538 SDL_TrayEntry *entry = NULL; 539 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 540 if (!gtk) { 541 goto error; 542 } 543 544 entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry)); 545 if (!entry) { 546 goto error; 547 } 548 549 entry->parent = menu; 550 entry->item = NULL; 551 entry->ignore_signal = false; 552 entry->flags = flags; 553 entry->callback = NULL; 554 entry->userdata = NULL; 555 entry->submenu = NULL; 556 557 if (label == NULL) { 558 entry->item = gtk->gtk.separator_menu_item_new(); 559 } else if (flags & SDL_TRAYENTRY_CHECKBOX) { 560 entry->item = gtk->gtk.check_menu_item_new_with_label(label); 561 gboolean active = ((flags & SDL_TRAYENTRY_CHECKED) != 0); 562 gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), active); 563 } else { 564 entry->item = gtk->gtk.menu_item_new_with_label(label); 565 } 566 567 gboolean sensitive = ((flags & SDL_TRAYENTRY_DISABLED) == 0); 568 gtk->gtk.widget_set_sensitive(entry->item, sensitive); 569 570 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries)); 571 572 if (!new_entries) { 573 goto error; 574 } 575 576 menu->entries = new_entries; 577 menu->nEntries++; 578 579 for (int i = menu->nEntries - 1; i > pos; i--) { 580 menu->entries[i] = menu->entries[i - 1]; 581 } 582 583 new_entries[pos] = entry; 584 new_entries[menu->nEntries] = NULL; 585 586 gtk->gtk.widget_show(entry->item); 587 gtk->gtk.menu_shell_insert(menu->menu, entry->item, (pos == menu->nEntries) ? -1 : pos); 588 589 gtk->g.signal_connect(entry->item, "activate", call_callback, entry); 590 591 SDL_Gtk_ExitContext(gtk); 592 593 return entry; 594 595error: 596 if (entry) { 597 SDL_free(entry); 598 } 599 600 if (gtk) { 601 SDL_Gtk_ExitContext(gtk); 602 } 603 604 return NULL; 605} 606 607void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) 608{ 609 if (!entry) { 610 return; 611 } 612 613 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 614 if (gtk) { 615 gtk->gtk.menu_item_set_label(GTK_MENU_ITEM(entry->item), label); 616 SDL_Gtk_ExitContext(gtk); 617 } 618} 619 620const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry) 621{ 622 CHECK_PARAM(!entry) { 623 SDL_InvalidParamError("entry"); 624 return NULL; 625 } 626 627 const char *label = NULL; 628 629 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 630 if (gtk) { 631 label = gtk->gtk.menu_item_get_label(GTK_MENU_ITEM(entry->item)); 632 SDL_Gtk_ExitContext(gtk); 633 } 634 635 return label; 636} 637 638void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked) 639{ 640 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { 641 return; 642 } 643 644 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 645 if (gtk) { 646 entry->ignore_signal = true; 647 gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), checked); 648 entry->ignore_signal = false; 649 SDL_Gtk_ExitContext(gtk); 650 } 651} 652 653bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry) 654{ 655 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { 656 return false; 657 } 658 659 bool checked = false; 660 661 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 662 if (gtk) { 663 checked = gtk->gtk.check_menu_item_get_active(GTK_CHECK_MENU_ITEM(entry->item)); 664 SDL_Gtk_ExitContext(gtk); 665 } 666 667 return checked; 668} 669 670void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled) 671{ 672 if (!entry) { 673 return; 674 } 675 676 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 677 if (gtk) { 678 gtk->gtk.widget_set_sensitive(entry->item, enabled); 679 SDL_Gtk_ExitContext(gtk); 680 } 681} 682 683bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry) 684{ 685 if (!entry) { 686 return false; 687 } 688 689 bool enabled = false; 690 691 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 692 if (gtk) { 693 enabled = gtk->gtk.widget_get_sensitive(entry->item); 694 SDL_Gtk_ExitContext(gtk); 695 } 696 697 return enabled; 698} 699 700void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) 701{ 702 if (!entry) { 703 return; 704 } 705 706 entry->callback = callback; 707 entry->userdata = userdata; 708} 709 710void SDL_ClickTrayEntry(SDL_TrayEntry *entry) 711{ 712 if (!entry) { 713 return; 714 } 715 716 if (entry->flags & SDL_TRAYENTRY_CHECKBOX) { 717 SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry)); 718 } 719 720 if (entry->callback) { 721 entry->callback(entry->userdata, entry); 722 } 723} 724 725SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) 726{ 727 CHECK_PARAM(!entry) { 728 SDL_InvalidParamError("entry"); 729 return NULL; 730 } 731 732 return entry->parent; 733} 734 735SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu) 736{ 737 return menu->parent_entry; 738} 739 740SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu) 741{ 742 CHECK_PARAM(!menu) { 743 SDL_InvalidParamError("menu"); 744 return NULL; 745 } 746 747 return menu->parent_tray; 748} 749 750void SDL_DestroyTray(SDL_Tray *tray) 751{ 752 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 753 return; 754 } 755 756 SDL_UnregisterTray(tray); 757 758 if (tray->menu) { 759 DestroySDLMenu(tray->menu); 760 } 761 762 if (tray->icon_path) { 763 SDL_RemovePath(tray->icon_path); 764 SDL_free(tray->icon_path); 765 } 766 767 if (tray->icon_dir) { 768 SDL_RemovePath(tray->icon_dir); 769 SDL_free(tray->icon_dir); 770 } 771 772 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 773 if (gtk) { 774 if (tray->menu_cached) { 775 gtk->g.object_unref(tray->menu_cached); 776 } 777 778 if (tray->indicator) { 779 gtk->g.object_unref(tray->indicator); 780 } 781 782 SDL_Gtk_ExitContext(gtk); 783 } 784 785 SDL_free(tray); 786} 787[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.