From 18adac20d4af85fa401569ecb4136811e36694b6 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 6 Mar 2021 00:25:34 +1100 Subject: [PATCH] Tray menu open/close events --- v2/internal/ffenestri/ffenestri_darwin.c | 56 +++++++++++++++---- v2/internal/ffenestri/traymenu_darwin.c | 21 ++++++- v2/internal/ffenestri/traymenu_darwin.h | 2 + v2/internal/ffenestri/traymenustore_darwin.h | 4 ++ v2/internal/menumanager/traymenu.go | 24 ++++++++ v2/internal/messagedispatcher/message/menu.go | 8 +++ v2/internal/subsystem/menu.go | 6 ++ v2/pkg/menu/tray.go | 6 ++ 8 files changed, 113 insertions(+), 14 deletions(-) diff --git a/v2/internal/ffenestri/ffenestri_darwin.c b/v2/internal/ffenestri/ffenestri_darwin.c index 68155f163..a3a8cd7bf 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.c +++ b/v2/internal/ffenestri/ffenestri_darwin.c @@ -24,6 +24,8 @@ struct hashmap_s dialogIconCache; // Dispatch Method typedef void (^dispatchMethod)(void); +TrayMenuStore *TrayMenuStoreSingleton; + // dispatch will execute the given `func` pointer void dispatch(dispatchMethod func) { dispatch_async(dispatch_get_main_queue(), func); @@ -55,6 +57,9 @@ void filelog(const char *message) { } } +// The delegate class for tray menus +Class trayMenuDelegateClass; + // Utility function to visualise a hashmap void dumpHashmap(const char *name, struct hashmap_s *hashmap) { printf("%s = { ", name); @@ -127,9 +132,6 @@ struct Application { // Menu Menu *applicationMenu; - // Tray - TrayMenuStore* trayMenuStore; - // Context Menus ContextMenuStore *contextMenuStore; @@ -477,7 +479,7 @@ void DestroyApplication(struct Application *app) { } // Delete the tray menu store - DeleteTrayMenuStore(app->trayMenuStore); + DeleteTrayMenuStore(TrayMenuStoreSingleton); // Delete the context menu store DeleteContextMenuStore(app->contextMenuStore); @@ -1044,7 +1046,7 @@ void AddTrayMenu(struct Application *app, const char *trayMenuJSON) { // Guard against calling during shutdown if( app->shuttingDown ) return; - AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON); + AddTrayMenuToStore(TrayMenuStoreSingleton, trayMenuJSON); } void SetTrayMenu(struct Application *app, const char* trayMenuJSON) { @@ -1053,13 +1055,13 @@ void SetTrayMenu(struct Application *app, const char* trayMenuJSON) { if( app->shuttingDown ) return; ON_MAIN_THREAD( - UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON); + UpdateTrayMenuInStore(TrayMenuStoreSingleton, trayMenuJSON); ); } void DeleteTrayMenuByID(struct Application *app, const char *id) { ON_MAIN_THREAD( - DeleteTrayMenuInStore(app->trayMenuStore, id); + DeleteTrayMenuInStore(TrayMenuStoreSingleton, id); ); } @@ -1068,7 +1070,7 @@ void UpdateTrayMenuLabel(struct Application* app, const char* JSON) { if( app->shuttingDown ) return; ON_MAIN_THREAD( - UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON); + UpdateTrayMenuLabelInStore(TrayMenuStoreSingleton, JSON); ); } @@ -1164,7 +1166,7 @@ void getURL(id self, SEL selector, id event, id replyEvent) { void createDelegate(struct Application *app) { - // Define delegate + // Define delegate Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0); bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate")); class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@"); @@ -1661,6 +1663,35 @@ void processUserDialogIcons(struct Application *app) { } +void TrayMenuWillOpen(id self, SEL selector, id menu) { + // Extract tray menu id from menu + id trayMenuIDStr = objc_getAssociatedObject(menu, "trayMenuID"); + const char* trayMenuID = cstr(trayMenuIDStr); + const char *message = concat("Mo", trayMenuID); + messageFromWindowCallback(message); + MEMFREE(message); +} + +void TrayMenuDidClose(id self, SEL selector, id menu) { + // Extract tray menu id from menu + id trayMenuIDStr = objc_getAssociatedObject(menu, "trayMenuID"); + const char* trayMenuID = cstr(trayMenuIDStr); + const char *message = concat("Mc", trayMenuID); + messageFromWindowCallback(message); + MEMFREE(message); +} + +void createTrayMenuDelegate() { + // Define delegate + trayMenuDelegateClass = objc_allocateClassPair((Class) c("NSObject"), "MenuDelegate", 0); + class_addProtocol(trayMenuDelegateClass, objc_getProtocol("NSMenuDelegate")); + class_addMethod(trayMenuDelegateClass, s("menuWillOpen:"), (IMP) TrayMenuWillOpen, "v@:@"); + class_addMethod(trayMenuDelegateClass, s("menuDidClose:"), (IMP) TrayMenuDidClose, "v@:@"); + + // Script handler + objc_registerClassPair(trayMenuDelegateClass); +} + void Run(struct Application *app, int argc, char **argv) { @@ -1673,6 +1704,9 @@ void Run(struct Application *app, int argc, char **argv) { // Define delegate createDelegate(app); + // Define tray delegate + createTrayMenuDelegate(); + // Create the main window createMainWindow(app); @@ -1843,7 +1877,7 @@ void Run(struct Application *app, int argc, char **argv) { } // Setup initial trays - ShowTrayMenusInStore(app->trayMenuStore); + ShowTrayMenusInStore(TrayMenuStoreSingleton); // Process dialog icons processUserDialogIcons(app); @@ -1920,7 +1954,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in result->applicationMenu = NULL; // Tray - result->trayMenuStore = NewTrayMenuStore(); + TrayMenuStoreSingleton = NewTrayMenuStore(); // Context Menus result->contextMenuStore = NewContextMenuStore(); diff --git a/v2/internal/ffenestri/traymenu_darwin.c b/v2/internal/ffenestri/traymenu_darwin.c index 5919a615f..7b78b3b63 100644 --- a/v2/internal/ffenestri/traymenu_darwin.c +++ b/v2/internal/ffenestri/traymenu_darwin.c @@ -6,6 +6,8 @@ #include "traymenu_darwin.h" #include "trayicons.h" +extern Class trayMenuDelegateClass; + // A cache for all our tray menu icons // Global because it's a singleton struct hashmap_s trayIconCache; @@ -35,6 +37,8 @@ TrayMenu* NewTrayMenu(const char* menuJSON) { // Create the menu result->menu = NewMenu(processedMenu); + result->delegate = NULL; + // Init tray status bar item result->statusbaritem = NULL; @@ -82,8 +86,6 @@ void UpdateTrayIcon(TrayMenu *trayMenu) { msg(statusBarButton, s("setImage:"), trayImage); } - - void ShowTrayMenu(TrayMenu* trayMenu) { // Create a status bar item if we don't have one @@ -91,7 +93,6 @@ void ShowTrayMenu(TrayMenu* trayMenu) { 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")); @@ -105,6 +106,16 @@ void ShowTrayMenu(TrayMenu* trayMenu) { // Update the menu id menu = GetMenu(trayMenu->menu); + objc_setAssociatedObject(menu, "trayMenuID", str(trayMenu->ID), OBJC_ASSOCIATION_ASSIGN); + + // Create delegate + id trayMenuDelegate = msg((id)trayMenuDelegateClass, s("new")); + msg(menu, s("setDelegate:"), trayMenuDelegate); + objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN); + + // Create menu delegate + trayMenu->delegate = trayMenuDelegate; + msg(trayMenu->statusbaritem, s("setMenu:"), menu); } @@ -153,6 +164,10 @@ void DeleteTrayMenu(TrayMenu* trayMenu) { trayMenu->statusbaritem = NULL; } + if ( trayMenu->delegate != NULL ) { + msg(trayMenu->delegate, s("release")); + } + // Free the tray menu memory MEMFREE(trayMenu); } diff --git a/v2/internal/ffenestri/traymenu_darwin.h b/v2/internal/ffenestri/traymenu_darwin.h index 6b69f4b1a..ce2e3b8a1 100644 --- a/v2/internal/ffenestri/traymenu_darwin.h +++ b/v2/internal/ffenestri/traymenu_darwin.h @@ -21,6 +21,8 @@ typedef struct { JsonNode* processedJSON; + id delegate; + } TrayMenu; TrayMenu* NewTrayMenu(const char *trayJSON); diff --git a/v2/internal/ffenestri/traymenustore_darwin.h b/v2/internal/ffenestri/traymenustore_darwin.h index e669a6cf8..a09a9e004 100644 --- a/v2/internal/ffenestri/traymenustore_darwin.h +++ b/v2/internal/ffenestri/traymenustore_darwin.h @@ -5,6 +5,8 @@ #ifndef TRAYMENUSTORE_DARWIN_H #define TRAYMENUSTORE_DARWIN_H +#include "traymenu_darwin.h" + #include typedef struct { @@ -26,6 +28,8 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON); void ShowTrayMenusInStore(TrayMenuStore* store); void DeleteTrayMenuStore(TrayMenuStore* store); +TrayMenu* GetTrayMenuByID(TrayMenuStore* store, const char* menuID); + void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON); void DeleteTrayMenuInStore(TrayMenuStore* store, const char* id); diff --git a/v2/internal/menumanager/traymenu.go b/v2/internal/menumanager/traymenu.go index fab065d58..29447de8a 100644 --- a/v2/internal/menumanager/traymenu.go +++ b/v2/internal/menumanager/traymenu.go @@ -29,6 +29,7 @@ type TrayMenu struct { menuItemMap *MenuItemMap menu *menu.Menu ProcessedMenu *WailsMenu + trayMenu *menu.TrayMenu } func (t *TrayMenu) AsJSON() (string, error) { @@ -46,6 +47,7 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu { Icon: trayMenu.Icon, menu: trayMenu.Menu, menuItemMap: NewMenuItemMap(), + trayMenu: trayMenu, } result.menuItemMap.AddMenu(trayMenu.Menu) @@ -54,6 +56,28 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu { return result } +func (m *Manager) OnTrayMenuOpen(id string) { + trayMenu, ok := m.trayMenus[id] + if !ok { + return + } + if trayMenu.trayMenu.OnOpen == nil { + return + } + go trayMenu.trayMenu.OnOpen() +} + +func (m *Manager) OnTrayMenuClose(id string) { + trayMenu, ok := m.trayMenus[id] + if !ok { + return + } + if trayMenu.trayMenu.OnClose == nil { + return + } + go trayMenu.trayMenu.OnClose() +} + func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) { newTrayMenu := NewTrayMenu(trayMenu) diff --git a/v2/internal/messagedispatcher/message/menu.go b/v2/internal/messagedispatcher/message/menu.go index 106c5ce47..483865fb9 100644 --- a/v2/internal/messagedispatcher/message/menu.go +++ b/v2/internal/messagedispatcher/message/menu.go @@ -32,6 +32,14 @@ func menuMessageParser(message string) (*parsedMessage, error) { callbackid := message[2:] topic = "menu:clicked" data = callbackid + case 'o': + callbackid := message[2:] + topic = "menu:ontrayopen" + data = callbackid + case 'c': + callbackid := message[2:] + topic = "menu:ontrayclose" + data = callbackid default: return nil, fmt.Errorf("invalid menu message: %s", message) } diff --git a/v2/internal/subsystem/menu.go b/v2/internal/subsystem/menu.go index b77907938..2a6552769 100644 --- a/v2/internal/subsystem/menu.go +++ b/v2/internal/subsystem/menu.go @@ -77,6 +77,12 @@ func (m *Menu) Start() error { splitTopic := strings.Split(menuMessage.Topic(), ":") menuMessageType := splitTopic[1] switch menuMessageType { + case "ontrayopen": + trayID := menuMessage.Data().(string) + m.menuManager.OnTrayMenuOpen(trayID) + case "ontrayclose": + trayID := menuMessage.Data().(string) + m.menuManager.OnTrayMenuClose(trayID) case "clicked": if len(splitTopic) != 2 { m.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic) diff --git a/v2/pkg/menu/tray.go b/v2/pkg/menu/tray.go index 989df3830..d3212136b 100644 --- a/v2/pkg/menu/tray.go +++ b/v2/pkg/menu/tray.go @@ -14,4 +14,10 @@ type TrayMenu struct { // Menu is the initial menu we wish to use for the tray Menu *Menu + + // OnOpen is called when the Menu is opened + OnOpen func() + + // OnClose is called when the Menu is closed + OnClose func() }