// // Created by Lea Anthony on 12/1/21. // #include "common.h" #include "traymenu_darwin.h" #include "trayicons.h" // A cache for all our tray menu icons // Global because it's a singleton struct hashmap_s trayIconCache; TrayMenu* NewTrayMenu(const char* menuJSON) { TrayMenu* result = malloc(sizeof(TrayMenu)); /* {"ID":"0","Label":"Test Tray Label","Icon":"","ProcessedMenu":{"Menu":{"Items":[{"ID":"0","Label":"Show Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0},{"ID":"1","Label":"Hide Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0},{"ID":"2","Label":"Minimise Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0},{"ID":"3","Label":"Unminimise Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0}]},"RadioGroups":null}} */ JsonNode* processedJSON = json_decode(menuJSON); if( processedJSON == NULL ) { ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", menuJSON); } // Save reference to this json result->processedJSON = processedJSON; // TODO: Make this configurable result->trayIconPosition = NSImageLeft; result->ID = mustJSONString(processedJSON, "ID"); result->label = mustJSONString(processedJSON, "Label"); result->icon = mustJSONString(processedJSON, "Icon"); JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu"); // Create the menu result->menu = NewMenu(processedMenu); // Init tray status bar item result->statusbaritem = NULL; // Set the menu type and store the tray ID in the parent data result->menu->menuType = TrayMenuType; result->menu->parentData = (void*) result->ID; return result; } void DumpTrayMenu(TrayMenu* trayMenu) { printf(" ['%s':%p] = { label: '%s', icon: '%s', menu: %p, statusbar: %p }\n", trayMenu->ID, trayMenu, trayMenu->label, trayMenu->icon, trayMenu->menu, trayMenu->statusbaritem ); } void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) { // Exit early if NULL if( trayMenu->label == NULL ) { return; } // Update button label id statusBarButton = msg(trayMenu->statusbaritem, s("button")); msg(statusBarButton, s("setTitle:"), str(label)); } void UpdateTrayIcon(TrayMenu *trayMenu) { // Exit early if NULL if( trayMenu->icon == NULL ) { return; } id statusBarButton = msg(trayMenu->statusbaritem, s("button")); // Empty icon means remove it if( STREMPTY(trayMenu->icon) ) { // Remove image msg(statusBarButton, s("setImage:"), NULL); return; } id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon)); msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition); msg(statusBarButton, s("setImage:"), trayImage); } void ShowTrayMenu(TrayMenu* trayMenu) { // Create a status bar item if we don't have one if( trayMenu->statusbaritem == NULL ) { id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") ); trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength); msg(trayMenu->statusbaritem, s("retain")); } id statusBarButton = msg(trayMenu->statusbaritem, s("button")); msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition); // Update the icon if needed UpdateTrayIcon(trayMenu); // Update the label if needed UpdateTrayLabel(trayMenu, trayMenu->label); // Update the menu id menu = GetMenu(trayMenu->menu); msg(trayMenu->statusbaritem, s("setMenu:"), menu); } // UpdateTrayMenuInPlace receives 2 menus. The current menu gets // updated with the data from the new menu. void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) { // Delete the old menu DeleteMenu(currentMenu->menu); // Set the new one currentMenu->menu = newMenu->menu; // Delete the old JSON json_delete(currentMenu->processedJSON); // Set the new JSON currentMenu->processedJSON = newMenu->processedJSON; // Copy the other data currentMenu->ID = newMenu->ID; currentMenu->label = newMenu->label; currentMenu->trayIconPosition = newMenu->trayIconPosition; currentMenu->icon = newMenu->icon; } void DeleteTrayMenu(TrayMenu* trayMenu) { // printf("Freeing TrayMenu:\n"); // DumpTrayMenu(trayMenu); // Delete the menu DeleteMenu(trayMenu->menu); // Free JSON if (trayMenu->processedJSON != NULL ) { json_delete(trayMenu->processedJSON); } // Free the status item if ( trayMenu->statusbaritem != NULL ) { id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") ); msg(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem); msg(trayMenu->statusbaritem, s("release")); trayMenu->statusbaritem = NULL; } // Free the tray menu memory MEMFREE(trayMenu); } void LoadTrayIcons() { // Allocate the Tray Icons if( 0 != hashmap_create((const unsigned)4, &trayIconCache)) { // Couldn't allocate map ABORT("Not enough memory to allocate trayIconCache!"); } unsigned int count = 0; while( 1 ) { const unsigned char *name = trayIcons[count++]; if( name == 0x00 ) { break; } const unsigned char *lengthAsString = trayIcons[count++]; if( name == 0x00 ) { break; } const unsigned char *data = trayIcons[count++]; if( data == 0x00 ) { break; } int length = atoi((const char *)lengthAsString); // Create the icon and add to the hashmap id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length); id trayImage = ALLOC("NSImage"); msg(trayImage, s("initWithData:"), imageData); hashmap_put(&trayIconCache, (const char *)name, strlen((const char *)name), trayImage); } } void UnloadTrayIcons() { // Release the tray cache images if( hashmap_num_entries(&trayIconCache) > 0 ) { if (0!=hashmap_iterate_pairs(&trayIconCache, releaseNSObject, NULL)) { ABORT("failed to release hashmap entries!"); } } //Free radio groups hashmap hashmap_destroy(&trayIconCache); }