Atlas - SDL_tray.c

Home / ext / SDL / src / tray / unix Lines: 1 | Size: 19141 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 "../../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_CreateTray(SDL_Surface *icon, const char *tooltip) 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_Tray *tray = NULL; 254 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 255 if (!gtk) { 256 goto tray_error; 257 } 258 259 tray = (SDL_Tray *)SDL_calloc(1, sizeof(*tray)); 260 if (!tray) { 261 goto tray_error; 262 } 263 264 const gchar *cache_dir = gtk->g.get_user_cache_dir(); 265 if (!cache_dir) { 266 SDL_SetError("Cannot get user cache directory: %s", strerror(errno)); 267 goto tray_error; 268 } 269 270 char *sdl_dir; 271 SDL_asprintf(&sdl_dir, "%s/SDL", cache_dir); 272 if (!SDL_GetPathInfo(sdl_dir, NULL)) { 273 if (!SDL_CreateDirectory(sdl_dir)) { 274 SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno)); 275 goto sdl_dir_error; 276 } 277 } 278 279 /* On success, g_mkdtemp edits its argument in-place to replace the Xs 280 * with a random directory name, which it creates safely and atomically. 281 * On failure, it sets errno. */ 282 SDL_asprintf(&tray->icon_dir, "%s/tray-XXXXXX", sdl_dir); 283 if (!gtk->g.mkdtemp(tray->icon_dir)) { 284 SDL_SetError("Cannot create directory for tray icon: %s", strerror(errno)); 285 goto icon_dir_error; 286 } 287 288 if (icon) { 289 if (!new_tmp_filename(tray)) { 290 goto icon_dir_error; 291 } 292 293 SDL_SavePNG(icon, tray->icon_path); 294 } else { 295 // allocate a dummy icon path 296 SDL_asprintf(&tray->icon_path, " "); 297 } 298 299 tray->indicator = app_indicator_new(get_appindicator_id(), tray->icon_path, 300 APP_INDICATOR_CATEGORY_APPLICATION_STATUS); 301 302 app_indicator_set_status(tray->indicator, APP_INDICATOR_STATUS_ACTIVE); 303 304 // The tray icon isn't shown before a menu is created; create one early. 305 tray->menu_cached = (GtkMenuShell *)gtk->g.object_ref_sink(gtk->gtk.menu_new()); 306 app_indicator_set_menu(tray->indicator, GTK_MENU(tray->menu_cached)); 307 308 SDL_RegisterTray(tray); 309 SDL_Gtk_ExitContext(gtk); 310 SDL_free(sdl_dir); 311 312 return tray; 313 314icon_dir_error: 315 SDL_free(tray->icon_dir); 316 317sdl_dir_error: 318 SDL_free(sdl_dir); 319 320tray_error: 321 SDL_free(tray); 322 323 if (gtk) { 324 SDL_Gtk_ExitContext(gtk); 325 } 326 327 return NULL; 328} 329 330void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon) 331{ 332 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 333 return; 334 } 335 336 if (tray->icon_path) { 337 SDL_RemovePath(tray->icon_path); 338 SDL_free(tray->icon_path); 339 tray->icon_path = NULL; 340 } 341 342 /* AppIndicator caches the icon files; always change filename to avoid caching */ 343 344 if (icon && new_tmp_filename(tray)) { 345 SDL_SavePNG(icon, tray->icon_path); 346 app_indicator_set_icon(tray->indicator, tray->icon_path); 347 } else { 348 SDL_free(tray->icon_path); 349 tray->icon_path = NULL; 350 app_indicator_set_icon(tray->indicator, NULL); 351 } 352} 353 354void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip) 355{ 356 /* AppIndicator provides no tooltip support. */ 357} 358 359SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray) 360{ 361 CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 362 SDL_InvalidParamError("tray"); 363 return NULL; 364 } 365 366 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 367 if (!gtk) { 368 return NULL; 369 } 370 371 tray->menu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*tray->menu)); 372 if (!tray->menu) { 373 SDL_Gtk_ExitContext(gtk); 374 return NULL; 375 } 376 377 tray->menu->menu = gtk->g.object_ref(tray->menu_cached); 378 tray->menu->parent_tray = tray; 379 tray->menu->parent_entry = NULL; 380 tray->menu->nEntries = 0; 381 tray->menu->entries = NULL; 382 383 SDL_Gtk_ExitContext(gtk); 384 385 return tray->menu; 386} 387 388SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray) 389{ 390 CHECK_PARAM(!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 391 SDL_InvalidParamError("tray"); 392 return NULL; 393 } 394 395 return tray->menu; 396} 397 398SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry) 399{ 400 CHECK_PARAM(!entry) { 401 SDL_InvalidParamError("entry"); 402 return NULL; 403 } 404 405 if (entry->submenu) { 406 SDL_SetError("Tray entry submenu already exists"); 407 return NULL; 408 } 409 410 if (!(entry->flags & SDL_TRAYENTRY_SUBMENU)) { 411 SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU"); 412 return NULL; 413 } 414 415 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 416 if (!gtk) { 417 return NULL; 418 } 419 420 entry->submenu = (SDL_TrayMenu *)SDL_calloc(1, sizeof(*entry->submenu)); 421 if (!entry->submenu) { 422 SDL_Gtk_ExitContext(gtk); 423 return NULL; 424 } 425 426 entry->submenu->menu = gtk->g.object_ref_sink(gtk->gtk.menu_new()); 427 entry->submenu->parent_tray = NULL; 428 entry->submenu->parent_entry = entry; 429 entry->submenu->nEntries = 0; 430 entry->submenu->entries = NULL; 431 432 gtk->gtk.menu_item_set_submenu(GTK_MENU_ITEM(entry->item), GTK_WIDGET(entry->submenu->menu)); 433 434 SDL_Gtk_ExitContext(gtk); 435 436 return entry->submenu; 437} 438 439SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry) 440{ 441 CHECK_PARAM(!entry) { 442 SDL_InvalidParamError("entry"); 443 return NULL; 444 } 445 446 return entry->submenu; 447} 448 449const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count) 450{ 451 CHECK_PARAM(!menu) { 452 SDL_InvalidParamError("menu"); 453 return NULL; 454 } 455 456 if (count) { 457 *count = menu->nEntries; 458 } 459 return (const SDL_TrayEntry **)menu->entries; 460} 461 462void SDL_RemoveTrayEntry(SDL_TrayEntry *entry) 463{ 464 if (!entry) { 465 return; 466 } 467 468 SDL_TrayMenu *menu = entry->parent; 469 470 bool found = false; 471 for (int i = 0; i < menu->nEntries - 1; i++) { 472 if (menu->entries[i] == entry) { 473 found = true; 474 } 475 476 if (found) { 477 menu->entries[i] = menu->entries[i + 1]; 478 } 479 } 480 481 if (entry->submenu) { 482 DestroySDLMenu(entry->submenu); 483 } 484 485 menu->nEntries--; 486 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(*new_entries)); 487 488 /* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */ 489 if (new_entries) { 490 menu->entries = new_entries; 491 menu->entries[menu->nEntries] = NULL; 492 } 493 494 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 495 if (gtk) { 496 gtk->gtk.widget_destroy(entry->item); 497 SDL_Gtk_ExitContext(gtk); 498 } 499 SDL_free(entry); 500} 501 502SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags) 503{ 504 CHECK_PARAM(!menu) { 505 SDL_InvalidParamError("menu"); 506 return NULL; 507 } 508 509 CHECK_PARAM(pos < -1 || pos > menu->nEntries) { 510 SDL_InvalidParamError("pos"); 511 return NULL; 512 } 513 514 if (pos == -1) { 515 pos = menu->nEntries; 516 } 517 518 SDL_TrayEntry *entry = NULL; 519 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 520 if (!gtk) { 521 goto error; 522 } 523 524 entry = (SDL_TrayEntry *)SDL_calloc(1, sizeof(*entry)); 525 if (!entry) { 526 goto error; 527 } 528 529 entry->parent = menu; 530 entry->item = NULL; 531 entry->ignore_signal = false; 532 entry->flags = flags; 533 entry->callback = NULL; 534 entry->userdata = NULL; 535 entry->submenu = NULL; 536 537 if (label == NULL) { 538 entry->item = gtk->gtk.separator_menu_item_new(); 539 } else if (flags & SDL_TRAYENTRY_CHECKBOX) { 540 entry->item = gtk->gtk.check_menu_item_new_with_label(label); 541 gboolean active = ((flags & SDL_TRAYENTRY_CHECKED) != 0); 542 gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), active); 543 } else { 544 entry->item = gtk->gtk.menu_item_new_with_label(label); 545 } 546 547 gboolean sensitive = ((flags & SDL_TRAYENTRY_DISABLED) == 0); 548 gtk->gtk.widget_set_sensitive(entry->item, sensitive); 549 550 SDL_TrayEntry **new_entries = (SDL_TrayEntry **)SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(*new_entries)); 551 552 if (!new_entries) { 553 goto error; 554 } 555 556 menu->entries = new_entries; 557 menu->nEntries++; 558 559 for (int i = menu->nEntries - 1; i > pos; i--) { 560 menu->entries[i] = menu->entries[i - 1]; 561 } 562 563 new_entries[pos] = entry; 564 new_entries[menu->nEntries] = NULL; 565 566 gtk->gtk.widget_show(entry->item); 567 gtk->gtk.menu_shell_insert(menu->menu, entry->item, (pos == menu->nEntries) ? -1 : pos); 568 569 gtk->g.signal_connect(entry->item, "activate", call_callback, entry); 570 571 SDL_Gtk_ExitContext(gtk); 572 573 return entry; 574 575error: 576 if (entry) { 577 SDL_free(entry); 578 } 579 580 if (gtk) { 581 SDL_Gtk_ExitContext(gtk); 582 } 583 584 return NULL; 585} 586 587void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label) 588{ 589 if (!entry) { 590 return; 591 } 592 593 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 594 if (gtk) { 595 gtk->gtk.menu_item_set_label(GTK_MENU_ITEM(entry->item), label); 596 SDL_Gtk_ExitContext(gtk); 597 } 598} 599 600const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry) 601{ 602 CHECK_PARAM(!entry) { 603 SDL_InvalidParamError("entry"); 604 return NULL; 605 } 606 607 const char *label = NULL; 608 609 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 610 if (gtk) { 611 label = gtk->gtk.menu_item_get_label(GTK_MENU_ITEM(entry->item)); 612 SDL_Gtk_ExitContext(gtk); 613 } 614 615 return label; 616} 617 618void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked) 619{ 620 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { 621 return; 622 } 623 624 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 625 if (gtk) { 626 entry->ignore_signal = true; 627 gtk->gtk.check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry->item), checked); 628 entry->ignore_signal = false; 629 SDL_Gtk_ExitContext(gtk); 630 } 631} 632 633bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry) 634{ 635 if (!entry || !(entry->flags & SDL_TRAYENTRY_CHECKBOX)) { 636 return false; 637 } 638 639 bool checked = false; 640 641 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 642 if (gtk) { 643 checked = gtk->gtk.check_menu_item_get_active(GTK_CHECK_MENU_ITEM(entry->item)); 644 SDL_Gtk_ExitContext(gtk); 645 } 646 647 return checked; 648} 649 650void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled) 651{ 652 if (!entry) { 653 return; 654 } 655 656 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 657 if (gtk) { 658 gtk->gtk.widget_set_sensitive(entry->item, enabled); 659 SDL_Gtk_ExitContext(gtk); 660 } 661} 662 663bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry) 664{ 665 if (!entry) { 666 return false; 667 } 668 669 bool enabled = false; 670 671 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 672 if (gtk) { 673 enabled = gtk->gtk.widget_get_sensitive(entry->item); 674 SDL_Gtk_ExitContext(gtk); 675 } 676 677 return enabled; 678} 679 680void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata) 681{ 682 if (!entry) { 683 return; 684 } 685 686 entry->callback = callback; 687 entry->userdata = userdata; 688} 689 690void SDL_ClickTrayEntry(SDL_TrayEntry *entry) 691{ 692 if (!entry) { 693 return; 694 } 695 696 if (entry->flags & SDL_TRAYENTRY_CHECKBOX) { 697 SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry)); 698 } 699 700 if (entry->callback) { 701 entry->callback(entry->userdata, entry); 702 } 703} 704 705SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry) 706{ 707 CHECK_PARAM(!entry) { 708 SDL_InvalidParamError("entry"); 709 return NULL; 710 } 711 712 return entry->parent; 713} 714 715SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu) 716{ 717 return menu->parent_entry; 718} 719 720SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu) 721{ 722 CHECK_PARAM(!menu) { 723 SDL_InvalidParamError("menu"); 724 return NULL; 725 } 726 727 return menu->parent_tray; 728} 729 730void SDL_DestroyTray(SDL_Tray *tray) 731{ 732 if (!SDL_ObjectValid(tray, SDL_OBJECT_TYPE_TRAY)) { 733 return; 734 } 735 736 SDL_UnregisterTray(tray); 737 738 if (tray->menu) { 739 DestroySDLMenu(tray->menu); 740 } 741 742 if (tray->icon_path) { 743 SDL_RemovePath(tray->icon_path); 744 SDL_free(tray->icon_path); 745 } 746 747 if (tray->icon_dir) { 748 SDL_RemovePath(tray->icon_dir); 749 SDL_free(tray->icon_dir); 750 } 751 752 SDL_GtkContext *gtk = SDL_Gtk_EnterContext(); 753 if (gtk) { 754 if (tray->menu_cached) { 755 gtk->g.object_unref(tray->menu_cached); 756 } 757 758 if (tray->indicator) { 759 gtk->g.object_unref(tray->indicator); 760 } 761 762 SDL_Gtk_ExitContext(gtk); 763 } 764 765 SDL_free(tray); 766} 767
[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.