Atlas - SDL_tray.m

Home / ext / SDL / src / tray / cocoa Lines: 1 | Size: 14054 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#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 CHECK_PARAM(!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 CHECK_PARAM(!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 CHECK_PARAM(!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 CHECK_PARAM(!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 CHECK_PARAM(!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 CHECK_PARAM(!menu) { 341 SDL_InvalidParamError("menu"); 342 return NULL; 343 } 344 345 CHECK_PARAM(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 CHECK_PARAM(!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 CHECK_PARAM(!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 CHECK_PARAM(!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 CHECK_PARAM(!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 525
[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.