diff --git a/v2/internal/ffenestri/common.c b/v2/internal/ffenestri/common.c index f7f8cda2..dcd46083 100644 --- a/v2/internal/ffenestri/common.c +++ b/v2/internal/ffenestri/common.c @@ -45,6 +45,29 @@ const char* getJSONString(JsonNode *item, const char* key) { return result; } +void ABORT_JSON(JsonNode *node, const char* key) { + ABORT("Unable to read required key '%s' from JSON: %s\n", key, json_encode(node)); +} + +const char* mustJSONString(JsonNode *node, const char* key) { + const char* result = getJSONString(node, key); + if ( result == NULL ) { + ABORT_JSON(node, key); + } + return result; +} +JsonNode* mustJSONObject(JsonNode *node, const char* key) { + struct JsonNode* result = getJSONObject(node, key); + if ( result == NULL ) { + ABORT_JSON(node, key); + } + return result; +} + +JsonNode* getJSONObject(JsonNode* node, const char* key) { + return json_find_member(node, key); +} + bool getJSONBool(JsonNode *item, const char* key, bool *result) { JsonNode *node = json_find_member(item, key); if ( node != NULL && node->tag == JSON_BOOL) { diff --git a/v2/internal/ffenestri/common.h b/v2/internal/ffenestri/common.h index 6c008e00..b289555a 100644 --- a/v2/internal/ffenestri/common.h +++ b/v2/internal/ffenestri/common.h @@ -17,6 +17,7 @@ #include "json.h" #define STREQ(a,b) strcmp(a, b) == 0 +#define STREMPTY(string) strlen(string) == 0 #define STRCOPY(a) concat(a, "") #define STR_HAS_CHARS(input) input != NULL && strlen(input) > 0 #define MEMFREE(input) free((void*)input); input = NULL; @@ -27,6 +28,10 @@ char* concat(const char *string1, const char *string2); void ABORT(const char *message, ...); int freeHashmapItem(void *const context, struct hashmap_element_s *const e); const char* getJSONString(JsonNode *item, const char* key); +const char* mustJSONString(JsonNode *node, const char* key); +JsonNode* getJSONObject(JsonNode* node, const char* key); +JsonNode* mustJSONObject(JsonNode *node, const char* key); + bool getJSONBool(JsonNode *item, const char* key, bool *result); bool getJSONInt(JsonNode *item, const char* key, int *result); diff --git a/v2/internal/ffenestri/ffenestri_darwin.c b/v2/internal/ffenestri/ffenestri_darwin.c index 32e52314..19d6040e 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.c +++ b/v2/internal/ffenestri/ffenestri_darwin.c @@ -1999,6 +1999,9 @@ void Run(struct Application *app, int argc, char **argv) { msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu); } + // Setup initial trays + ShowTrayMenusInStore(app->trayMenuStore); + // If we have a tray menu, process it if( app->trayMenuAsJSON != NULL ) { parseTrayData(app); diff --git a/v2/internal/ffenestri/menu_darwin.c b/v2/internal/ffenestri/menu_darwin.c index 0ca8c866..a9b8b64a 100644 --- a/v2/internal/ffenestri/menu_darwin.c +++ b/v2/internal/ffenestri/menu_darwin.c @@ -149,12 +149,12 @@ void menuItemCallback(id self, SEL cmd, id sender) { ContextMenu* contextMenu = (ContextMenu*) callbackData->menu->parentData; data = contextMenu->contextMenuData; parentID = contextMenu->ID; + } else if ( menuType == TrayMenuType ) { + parentID = (const char*) callbackData->menu->parentData; } message = createMenuClickedMessage(menuID, data, menuType, parentID); - // TODO: Add other menu types here! - // Notify the backend messageFromWindowCallback(message); MEMFREE(message); diff --git a/v2/internal/ffenestri/traymenu_darwin.c b/v2/internal/ffenestri/traymenu_darwin.c index 79f3fec3..8cdf2834 100644 --- a/v2/internal/ffenestri/traymenu_darwin.c +++ b/v2/internal/ffenestri/traymenu_darwin.c @@ -5,17 +5,96 @@ #include "common.h" #include "traymenu_darwin.h" +extern 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); + } + // 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); + + // 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 }\n", trayMenu->ID, trayMenu, trayMenu->label, trayMenu->icon, trayMenu->menu ); +} + +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 + UpdateTrayMenuIcon(trayMenu); + + // Update the label if needed + UpdateTrayMenuLabel(trayMenu); + } + + id menu = GetMenu(trayMenu->menu); + msg(trayMenu->statusbaritem, s("setMenu:"), menu); +} + +void UpdateTrayMenuLabel(TrayMenu *trayMenu) { + + // Exit early if NULL + if( trayMenu->label == NULL ) { + return; + } + // We don't check for a + id statusBarButton = msg(trayMenu->statusbaritem, s("button")); + msg(statusBarButton, s("setTitle:"), str(trayMenu->label)); +} + +void UpdateTrayMenuIcon(TrayMenu *trayMenu) { + + // Exit early if NULL or emptystring + if( trayMenu->icon == NULL || STREMPTY(trayMenu->icon ) ) { + return; + } + id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon)); + id statusBarButton = msg(trayMenu->statusbaritem, s("button")); + msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition); + msg(statusBarButton, s("setImage:"), trayImage); +} + void DeleteTrayMenu(TrayMenu* trayMenu) { + +// printf("Freeing TrayMenu:\n"); +// DumpTrayMenu(trayMenu); + + // Delete the menu + DeleteMenu(trayMenu->menu); + + // Free JSON + json_delete(trayMenu->processedJSON); + // Free the tray menu memory MEMFREE(trayMenu); } \ No newline at end of file diff --git a/v2/internal/ffenestri/traymenu_darwin.h b/v2/internal/ffenestri/traymenu_darwin.h index 816752c1..f7aa9cd2 100644 --- a/v2/internal/ffenestri/traymenu_darwin.h +++ b/v2/internal/ffenestri/traymenu_darwin.h @@ -5,19 +5,30 @@ #ifndef TRAYMENU_DARWIN_H #define TRAYMENU_DARWIN_H +#include "common.h" #include "menu_darwin.h" typedef struct { const char *label; const char *icon; - const char *trayID; + const char *ID; Menu* menu; + id statusbaritem; + int trayIconPosition; + + JsonNode* processedJSON; + } TrayMenu; TrayMenu* NewTrayMenu(const char *trayJSON); +void DumpTrayMenu(TrayMenu* trayMenu); +void ShowTrayMenu(TrayMenu* trayMenu); +void UpdateTrayMenuIcon(TrayMenu *trayMenu); +void UpdateTrayMenuLabel(TrayMenu *trayMenu); + void DeleteTrayMenu(TrayMenu* trayMenu); #endif //TRAYMENU_DARWIN_H diff --git a/v2/internal/ffenestri/traymenustore_darwin.c b/v2/internal/ffenestri/traymenustore_darwin.c index e2d40861..b46fb2c4 100644 --- a/v2/internal/ffenestri/traymenustore_darwin.c +++ b/v2/internal/ffenestri/traymenustore_darwin.c @@ -21,12 +21,22 @@ TrayMenuStore* NewTrayMenuStore() { void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) { TrayMenu* newMenu = NewTrayMenu(menuJSON); - const char *ID = "TEST"; - - hashmap_put(&store->trayMenuMap, ID, strlen(ID), newMenu); + hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu); } +int showTrayMenu(void *const context, struct hashmap_element_s *const e) { + ShowTrayMenu(e->data); + return -1; +} + +void ShowTrayMenusInStore(TrayMenuStore* store) { + if( hashmap_num_entries(&store->trayMenuMap) > 0 ) { + hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL); + } +} + + int freeTrayMenu(void *const context, struct hashmap_element_s *const e) { DeleteTrayMenu(e->data); return -1; diff --git a/v2/internal/ffenestri/traymenustore_darwin.h b/v2/internal/ffenestri/traymenustore_darwin.h index 0daeac6d..3f3fc56b 100644 --- a/v2/internal/ffenestri/traymenustore_darwin.h +++ b/v2/internal/ffenestri/traymenustore_darwin.h @@ -19,6 +19,7 @@ typedef struct { TrayMenuStore* NewTrayMenuStore(); void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON); +void ShowTrayMenusInStore(TrayMenuStore* store); void DeleteTrayMenuStore(TrayMenuStore* store); #endif //TRAYMENUSTORE_DARWIN_H diff --git a/v2/internal/menumanager/menumanager.go b/v2/internal/menumanager/menumanager.go index 4c85d29a..d1cfc515 100644 --- a/v2/internal/menumanager/menumanager.go +++ b/v2/internal/menumanager/menumanager.go @@ -41,14 +41,17 @@ func (m *Manager) ProcessClick(menuID string, data string, menuType string, pare case "ApplicationMenu": menuItemMap = m.applicationMenuItemMap case "ContextMenu": - // TBD contextMenu := m.contextMenus[parentID] if contextMenu == nil { return fmt.Errorf("unknown context menu: %s", parentID) } menuItemMap = contextMenu.menuItemMap - //case "TrayMenu": - // // TBD + case "TrayMenu": + trayMenu := m.trayMenus[parentID] + if trayMenu == nil { + return fmt.Errorf("unknown tray menu: %s", parentID) + } + menuItemMap = trayMenu.menuItemMap default: return fmt.Errorf("unknown menutype: %s", menuType) } diff --git a/v2/test/kitchensink/main.go b/v2/test/kitchensink/main.go index 79c62fbf..adf6d6a6 100644 --- a/v2/test/kitchensink/main.go +++ b/v2/test/kitchensink/main.go @@ -46,6 +46,7 @@ func main() { app.Bind(&Dialog{}) app.Bind(&Window{}) app.Bind(Menu) + app.Bind(Tray) app.Bind(&ContextMenu{}) err = app.Run() diff --git a/v2/test/kitchensink/tray.go b/v2/test/kitchensink/tray.go index b1b2f62e..d6e60c96 100644 --- a/v2/test/kitchensink/tray.go +++ b/v2/test/kitchensink/tray.go @@ -14,7 +14,8 @@ type Tray struct { //dynamicMenuItems map[string]*menu.MenuItem //anotherDynamicMenuCounter int - trayMenu *menu.TrayMenu + trayMenu *menu.TrayMenu + secondTrayMenu *menu.TrayMenu done bool } @@ -53,6 +54,22 @@ func (t *Tray) WailsInit(runtime *wails.Runtime) error { return nil } +func (t *Tray) showWindow(_ *menu.CallbackData) { + t.runtime.Window.Show() +} + +func (t *Tray) hideWindow(_ *menu.CallbackData) { + t.runtime.Window.Hide() +} + +func (t *Tray) unminimiseWindow(_ *menu.CallbackData) { + t.runtime.Window.Unminimise() +} + +func (t *Tray) minimiseWindow(_ *menu.CallbackData) { + t.runtime.Window.Minimise() +} + func (t *Tray) WailsShutdown() { t.done = true } @@ -137,14 +154,24 @@ func (t *Tray) createTrayMenus() []*menu.TrayMenu { trayMenu := &menu.TrayMenu{} trayMenu.Label = "Test Tray Label" trayMenu.Menu = menu.NewMenuFromItems( - menu.Text("Show Window", "Show Window", nil, nil), - menu.Text("Hide Window", "Hide Window", nil, nil), - menu.Text("Minimise Window", "Minimise Window", nil, nil), - menu.Text("Unminimise Window", "Unminimise Window", nil, nil), + menu.Text("Show Window", "Show Window", nil, t.showWindow), + menu.Text("Hide Window", "Hide Window", nil, t.hideWindow), + menu.Text("Minimise Window", "Minimise Window", nil, t.minimiseWindow), + menu.Text("Unminimise Window", "Unminimise Window", nil, t.unminimiseWindow), ) t.trayMenu = trayMenu + secondTrayMenu := &menu.TrayMenu{} + secondTrayMenu.Label = "Another tray label" + secondTrayMenu.Menu = menu.NewMenuFromItems( + menu.Text("Show Window", "Show Window", nil, t.showWindow), + menu.Text("Hide Window", "Hide Window", nil, t.hideWindow), + menu.Text("Minimise Window", "Minimise Window", nil, t.minimiseWindow), + menu.Text("Unminimise Window", "Unminimise Window", nil, t.unminimiseWindow), + ) + t.secondTrayMenu = secondTrayMenu return []*menu.TrayMenu{ trayMenu, + secondTrayMenu, } }