From b837b1e1313c6a6e834ca5b380d927f9688f8d4a Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Thu, 10 Dec 2020 07:16:10 +1100 Subject: [PATCH] Tray menu support Bugfix move after tray use Refactored options to handle default tray/menu --- v2/internal/app/desktop.go | 46 +++-------- v2/internal/ffenestri/ffenestri.h | 1 + v2/internal/ffenestri/ffenestri_client.go | 16 ++++ v2/internal/ffenestri/ffenestri_darwin.c | 76 ++++++++++++------ v2/internal/ffenestri/ffenestri_darwin.go | 12 +-- .../messagedispatcher/dispatchclient.go | 1 + .../messagedispatcher/messagedispatcher.go | 38 +++++++++ v2/internal/runtime/runtime.go | 4 +- v2/internal/subsystem/runtime.go | 4 +- v2/pkg/commands/build/desktop.go | 18 +++-- v2/pkg/commands/build/desktop_darwin.go | 62 ++++++++++++++ v2/pkg/commands/build/hybrid.go | 2 +- v2/pkg/commands/build/packager_darwin.go | 21 +++-- v2/pkg/options/default.go | 3 +- v2/pkg/options/options.go | 65 ++++++++++++++- v2/test/kitchensink/main.go | 2 + v2/test/kitchensink/menu.go | 7 ++ v2/test/kitchensink/tray.go | 18 ++++- v2/test/kitchensink/trayicon.png | Bin 0 -> 2459 bytes 19 files changed, 311 insertions(+), 85 deletions(-) create mode 100644 v2/pkg/commands/build/desktop_darwin.go create mode 100644 v2/test/kitchensink/trayicon.png diff --git a/v2/internal/app/desktop.go b/v2/internal/app/desktop.go index f152d113..abefe143 100644 --- a/v2/internal/app/desktop.go +++ b/v2/internal/app/desktop.go @@ -3,9 +3,6 @@ package app import ( - "fmt" - goruntime "runtime" - "github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/ffenestri" "github.com/wailsapp/wails/v2/internal/logger" @@ -14,7 +11,6 @@ import ( "github.com/wailsapp/wails/v2/internal/servicebus" "github.com/wailsapp/wails/v2/internal/signal" "github.com/wailsapp/wails/v2/internal/subsystem" - "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/options" ) @@ -48,16 +44,16 @@ type App struct { } // Create App -func CreateApp(options *options.App) (*App, error) { +func CreateApp(appoptions *options.App) (*App, error) { // Merge default options - options.MergeDefaults() + options.MergeDefaults(appoptions) // Set up logger - myLogger := logger.New(options.Logger) - myLogger.SetLogLevel(options.LogLevel) + myLogger := logger.New(appoptions.Logger) + myLogger.SetLogLevel(appoptions.LogLevel) - window := ffenestri.NewApplicationWithConfig(options, myLogger) + window := ffenestri.NewApplicationWithConfig(appoptions, myLogger) result := &App{ window: window, @@ -66,7 +62,7 @@ func CreateApp(options *options.App) (*App, error) { bindings: binding.NewBindings(myLogger), } - result.options = options + result.options = appoptions // Initialise the app err := result.Init() @@ -96,8 +92,10 @@ func (a *App) Run() error { } // Start the runtime - runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, - a.options.Mac.Menu) + applicationMenu := options.GetApplicationMenu(a.options) + trayMenu := options.GetTrayMenu(a.options) + + runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, applicationMenu, trayMenu) if err != nil { return err } @@ -112,8 +110,7 @@ func (a *App) Run() error { a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options) // Start the binding subsystem - bindingsubsystem, err := subsystem.NewBinding(a.servicebus, a.logger, - a.bindings, a.runtime.GoRuntime()) + bindingsubsystem, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, a.runtime.GoRuntime()) if err != nil { return err } @@ -156,25 +153,9 @@ func (a *App) Run() error { return err } - // Start the menu subsystem - var applicationMenu *menu.Menu - var trayMenu *menu.Menu - switch goruntime.GOOS { - case "darwin": - applicationMenu = a.options.Mac.Menu - trayMenu = a.options.Mac.Tray - // case "linux": - // applicationMenu = a.options.Linux.Menu - // case "windows": - // applicationMenu = a.options.Windows.Menu - default: - return fmt.Errorf("unsupported OS: %s", goruntime.GOOS) - } - // Optionally start the menu subsystem if applicationMenu != nil { - menusubsystem, err := subsystem.NewMenu(applicationMenu, a.servicebus, - a.logger) + menusubsystem, err := subsystem.NewMenu(applicationMenu, a.servicebus, a.logger) if err != nil { return err } @@ -187,8 +168,7 @@ func (a *App) Run() error { // Optionally start the tray subsystem if trayMenu != nil { - traysubsystem, err := subsystem.NewTray(trayMenu, a.servicebus, - a.logger) + traysubsystem, err := subsystem.NewTray(trayMenu, a.servicebus, a.logger) if err != nil { return err } diff --git a/v2/internal/ffenestri/ffenestri.h b/v2/internal/ffenestri/ffenestri.h index 4c914fc4..42c74ee6 100644 --- a/v2/internal/ffenestri/ffenestri.h +++ b/v2/internal/ffenestri/ffenestri.h @@ -33,4 +33,5 @@ extern void OpenDialog(void *appPointer, char *callbackID, char *title, char *fi extern void SaveDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories); extern void DarkModeEnabled(void *appPointer, char *callbackID); extern void UpdateMenu(void *app, char *menuAsJSON); +extern void UpdateTray(void *app, char *menuAsJSON); #endif diff --git a/v2/internal/ffenestri/ffenestri_client.go b/v2/internal/ffenestri/ffenestri_client.go index d5627d4e..a70c9988 100644 --- a/v2/internal/ffenestri/ffenestri_client.go +++ b/v2/internal/ffenestri/ffenestri_client.go @@ -172,3 +172,19 @@ func (c *Client) UpdateMenu(menu *menu.Menu) { } C.UpdateMenu(c.app.app, c.app.string2CString(string(menuJSON))) } + +func (c *Client) UpdateTray(menu *menu.Menu) { + + // Guard against nil menus + if menu == nil { + return + } + // Process the menu + processedMenu := NewProcessedMenu(menu) + trayMenuJSON, err := json.Marshal(processedMenu) + if err != nil { + c.app.logger.Error("Error processing updated Tray: %s", err.Error()) + return + } + C.UpdateTray(c.app.app, c.app.string2CString(string(trayMenuJSON))) +} diff --git a/v2/internal/ffenestri/ffenestri_darwin.c b/v2/internal/ffenestri/ffenestri_darwin.c index 1362902b..42686e9d 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.c +++ b/v2/internal/ffenestri/ffenestri_darwin.c @@ -71,6 +71,10 @@ extern const unsigned char *assets[]; extern const unsigned char runtime; extern const char *icon[]; +// Tray icon +extern const unsigned int trayIconLength; +extern const unsigned char *trayIcon[]; + // MAIN DEBUG FLAG int debug; @@ -176,7 +180,6 @@ struct Application { int webviewIsTranparent; const char *appearance; int decorations; - bool dragging; int logLevel; // Features @@ -200,6 +203,7 @@ struct Application { // Tray const char *trayMenuAsJSON; JsonNode *processedTrayMenu; + id statusItem; // User Data char *HTML; @@ -334,7 +338,6 @@ void messageHandler(id self, SEL cmd, id contentController, id message) { // Guard against null events if( app->mouseEvent != NULL ) { HideMouse(); - app->dragging = true; ON_MAIN_THREAD( msg(app->mainWindow, s("performWindowDragWithEvent:"), app->mouseEvent); ); @@ -569,8 +572,6 @@ void* NewApplication(const char *title, int width, int height, int resizable, in result->mouseDownMonitor = NULL; result->mouseUpMonitor = NULL; - result->dragging = false; - // Features result->frame = 1; result->hideTitle = 0; @@ -591,6 +592,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in // Tray result->trayMenuAsJSON = NULL; result->processedTrayMenu = NULL; + result->statusItem = NULL; // Window Appearance result->vibrancyLayer = NULL; @@ -1917,22 +1919,30 @@ void UpdateMenu(struct Application *app, const char *menuAsJSON) { void parseTrayData(struct Application *app) { -// msg(statusBarButton, s("setImage:"), -// msg(c("NSImage"), s("imageNamed:"), -// msg(c("NSString"), s("stringWithUTF8String:"), tray->icon))); - - // Allocate the hashmaps we need allocateTrayHashMaps(app); // Create a new menu id traymenu = createMenu(str("")); - // Create a new menu bar - id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") ); - id statusItem = msg(statusBar, s("statusItemWithLength:"), -1.0); - msg(statusItem, s("retain")); - id statusBarButton = msg(statusItem, s("button")); + id statusItem = app->statusItem; + + // Create a new menu bar if we need to + if ( statusItem == NULL ) { + id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") ); + statusItem = msg(statusBar, s("statusItemWithLength:"), -1.0); + app->statusItem = statusItem; + msg(statusItem, s("retain")); + id statusBarButton = msg(statusItem, s("button")); + + // If we have a tray icon + if ( trayIconLength > 0 ) { + id imageData = msg(c("NSData"), s("dataWithBytes:length:"), trayIcon, trayIconLength); + id trayImage = ALLOC("NSImage"); + msg(trayImage, s("initWithData:"), imageData); + msg(statusBarButton, s("setImage:"), trayImage); + } + } Debug(app, ">>>>>>>>>>> TRAY MENU: %s", app->trayMenuAsJSON); @@ -1973,8 +1983,29 @@ void parseTrayData(struct Application *app) { processRadioGroup(radioGroup, &menuItemMapForTrayMenu, &radioGroupMapForTrayMenu); } + +// msg(statusBarButton, s("setImage:"), +// msg(c("NSImage"), s("imageNamed:"), +// msg(c("NSString"), s("stringWithUTF8String:"), tray->icon))); + + msg(statusItem, s("setMenu:"), traymenu); - } + } + + +// UpdateTray replaces the current tray menu with the given one +void UpdateTray(struct Application *app, const char *trayMenuAsJSON) { + Debug(app, "tray is now: %s", trayMenuAsJSON); + ON_MAIN_THREAD ( + + // Free up memory + destroyTray(app); + + // Set the menu JSON + app->trayMenuAsJSON = trayMenuAsJSON; + parseTrayData(app); + ); +} void Run(struct Application *app, int argc, char **argv) { @@ -2039,15 +2070,16 @@ void Run(struct Application *app, int argc, char **argv) { msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("windowDrag")); // Add mouse event hooks app->mouseDownMonitor = msg(c("NSEvent"), u("addLocalMonitorForEventsMatchingMask:handler:"), NSEventMaskLeftMouseDown, ^(id incomingEvent) { - app->mouseEvent = incomingEvent; + // Make sure the mouse click was in the window, not the tray + id window = msg(incomingEvent, s("window")); + if (window == app->mainWindow) { + app->mouseEvent = incomingEvent; + } return incomingEvent; }); app->mouseUpMonitor = msg(c("NSEvent"), u("addLocalMonitorForEventsMatchingMask:handler:"), NSEventMaskLeftMouseUp, ^(id incomingEvent) { app->mouseEvent = NULL; - if ( app->dragging ) { - ShowMouse(); - app->dragging = false; - } + ShowMouse(); return incomingEvent; }); @@ -2143,13 +2175,9 @@ void Run(struct Application *app, int argc, char **argv) { } // If we have a tray menu, process it - printf - ("\n\n\n*****************************************************************************************************************************************************************************************************************\n\n\n"); if( app->trayMenuAsJSON != NULL ) { parseTrayData(app); } -printf - ("\n\n\n*****************************************************************************************************************************************************************************************************************\n\n\n"); // Finally call run Debug(app, "Run called"); diff --git a/v2/internal/ffenestri/ffenestri_darwin.go b/v2/internal/ffenestri/ffenestri_darwin.go index d70986e8..109d3447 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.go +++ b/v2/internal/ffenestri/ffenestri_darwin.go @@ -20,6 +20,7 @@ extern void SetTray(void *, const char *); import "C" import ( "encoding/json" + "github.com/wailsapp/wails/v2/pkg/options" ) func (a *Application) processPlatformSettings() error { @@ -71,7 +72,8 @@ func (a *Application) processPlatformSettings() error { } // Process menu - if mac.Menu != nil { + applicationMenu := options.GetApplicationMenu(a.config) + if applicationMenu != nil { /* As radio groups need to be manually managed on OSX, @@ -81,7 +83,7 @@ func (a *Application) processPlatformSettings() error { a list of all members of the group and the number of members in the group (this last one is for optimisation at the C layer). */ - processedMenu := NewProcessedMenu(mac.Menu) + processedMenu := NewProcessedMenu(applicationMenu) applicationMenuJSON, err := json.Marshal(processedMenu) if err != nil { return err @@ -90,7 +92,8 @@ func (a *Application) processPlatformSettings() error { } // Process tray - if mac.Tray != nil { + tray := options.GetTrayMenu(a.config) + if tray != nil { /* As radio groups need to be manually managed on OSX, @@ -100,13 +103,12 @@ func (a *Application) processPlatformSettings() error { a list of all members of the group and the number of members in the group (this last one is for optimisation at the C layer). */ - processedMenu := NewProcessedMenu(mac.Tray) + processedMenu := NewProcessedMenu(tray) trayMenuJSON, err := json.Marshal(processedMenu) if err != nil { return err } C.SetTray(a.app, a.string2CString(string(trayMenuJSON))) - println("******************** SET TRAY!!!!! &&&&&&&&&&&&&&&&&&&&&&&&&&") } return nil diff --git a/v2/internal/messagedispatcher/dispatchclient.go b/v2/internal/messagedispatcher/dispatchclient.go index f8131bfa..ff5d94cb 100644 --- a/v2/internal/messagedispatcher/dispatchclient.go +++ b/v2/internal/messagedispatcher/dispatchclient.go @@ -32,6 +32,7 @@ type Client interface { WindowSetColour(colour int) DarkModeEnabled(callbackID string) UpdateMenu(menu *menu.Menu) + UpdateTray(menu *menu.Menu) } // DispatchClient is what the frontends use to interface with the diff --git a/v2/internal/messagedispatcher/messagedispatcher.go b/v2/internal/messagedispatcher/messagedispatcher.go index e10d0550..6f6a977a 100644 --- a/v2/internal/messagedispatcher/messagedispatcher.go +++ b/v2/internal/messagedispatcher/messagedispatcher.go @@ -24,6 +24,7 @@ type Dispatcher struct { dialogChannel <-chan *servicebus.Message systemChannel <-chan *servicebus.Message menuChannel <-chan *servicebus.Message + trayChannel <-chan *servicebus.Message running bool servicebus *servicebus.ServiceBus @@ -76,6 +77,11 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher, return nil, err } + trayChannel, err := servicebus.Subscribe("trayfrontend:") + if err != nil { + return nil, err + } + result := &Dispatcher{ servicebus: servicebus, eventChannel: eventChannel, @@ -87,6 +93,7 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher, dialogChannel: dialogChannel, systemChannel: systemChannel, menuChannel: menuChannel, + trayChannel: trayChannel, } return result, nil @@ -118,6 +125,8 @@ func (d *Dispatcher) Start() error { d.processSystemMessage(systemMessage) case menuMessage := <-d.menuChannel: d.processMenuMessage(menuMessage) + case trayMessage := <-d.trayChannel: + d.processTrayMessage(trayMessage) } } @@ -437,3 +446,32 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) { d.logger.Error("Unknown menufrontend command: %s", command) } } + +func (d *Dispatcher) processTrayMessage(result *servicebus.Message) { + splitTopic := strings.Split(result.Topic(), ":") + if len(splitTopic) < 2 { + d.logger.Error("Invalid tray message : %#v", result.Data()) + return + } + + command := splitTopic[1] + switch command { + case "update": + + updatedMenu, ok := result.Data().(*menu.Menu) + if !ok { + d.logger.Error("Invalid data for 'trayfrontend:update' : %#v", + result.Data()) + return + } + + // TODO: Work out what we mean in a multi window environment... + // For now we will just pick the first one + for _, client := range d.clients { + client.frontend.UpdateTray(updatedMenu) + } + + default: + d.logger.Error("Unknown menufrontend command: %s", command) + } +} diff --git a/v2/internal/runtime/runtime.go b/v2/internal/runtime/runtime.go index 6b33214c..a652e80c 100644 --- a/v2/internal/runtime/runtime.go +++ b/v2/internal/runtime/runtime.go @@ -20,7 +20,7 @@ type Runtime struct { } // New creates a new runtime -func New(serviceBus *servicebus.ServiceBus, menu *menu.Menu) *Runtime { +func New(serviceBus *servicebus.ServiceBus, menu *menu.Menu, trayMenu *menu.Menu) *Runtime { result := &Runtime{ Browser: newBrowser(), Events: newEvents(serviceBus), @@ -28,7 +28,7 @@ func New(serviceBus *servicebus.ServiceBus, menu *menu.Menu) *Runtime { Dialog: newDialog(serviceBus), System: newSystem(serviceBus), Menu: newMenu(serviceBus, menu), - Tray: newTray(serviceBus, menu), + Tray: newTray(serviceBus, trayMenu), Log: newLog(serviceBus), bus: serviceBus, } diff --git a/v2/internal/subsystem/runtime.go b/v2/internal/subsystem/runtime.go index 0baa42e5..e4eb5e9a 100644 --- a/v2/internal/subsystem/runtime.go +++ b/v2/internal/subsystem/runtime.go @@ -24,7 +24,7 @@ type Runtime struct { } // NewRuntime creates a new runtime subsystem -func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, menu *menu.Menu) (*Runtime, error) { +func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, menu *menu.Menu, trayMenu *menu.Menu) (*Runtime, error) { // Register quit channel quitChannel, err := bus.Subscribe("quit") @@ -42,7 +42,7 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, menu *menu.Me quitChannel: quitChannel, runtimeChannel: runtimeChannel, logger: logger.CustomLogger("Runtime Subsystem"), - runtime: runtime.New(bus, menu), + runtime: runtime.New(bus, menu, trayMenu), } return result, nil diff --git a/v2/pkg/commands/build/desktop.go b/v2/pkg/commands/build/desktop.go index 3176f76c..2b98d413 100644 --- a/v2/pkg/commands/build/desktop.go +++ b/v2/pkg/commands/build/desktop.go @@ -7,7 +7,6 @@ import ( "github.com/wailsapp/wails/v2/internal/fs" "github.com/wailsapp/wails/v2/internal/html" - "github.com/wailsapp/wails/v2/pkg/clilogger" ) // DesktopBuilder builds applications for the desktop @@ -32,7 +31,7 @@ func (d *DesktopBuilder) BuildAssets(options *Options) error { } // Build base assets (HTML/JS/CSS/etc) - err = d.BuildBaseAssets(assets, options.Logger) + err = d.BuildBaseAssets(assets, options) if err != nil { return err } @@ -47,9 +46,10 @@ func (d *DesktopBuilder) BuildAssets(options *Options) error { } // BuildBaseAssets builds the assets for the desktop application -func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, outputLogger *clilogger.CLILogger) error { +func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Options) error { var err error + outputLogger := options.Logger outputLogger.Print(" - Embedding Assets...") // Get target asset directory @@ -63,7 +63,13 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, outputLogger d.addFileToDelete(assetsFile) // Process Icon - err = d.processIcon(assetDir) + err = d.processApplicationIcon(assetDir) + if err != nil { + return err + } + + // Process Tray Icons + err = d.processTrayIcons(assetDir, options) if err != nil { return err } @@ -73,9 +79,9 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, outputLogger return nil } -// processIcon will copy a default icon if one doesn't exist, then, if +// processApplicationIcon will copy a default icon if one doesn't exist, then, if // needed, will compile the icon -func (d *DesktopBuilder) processIcon(assetDir string) error { +func (d *DesktopBuilder) processApplicationIcon(assetDir string) error { // Copy default icon if one doesn't exist iconFile := filepath.Join(d.projectData.Path, "icon.png") diff --git a/v2/pkg/commands/build/desktop_darwin.go b/v2/pkg/commands/build/desktop_darwin.go new file mode 100644 index 00000000..2f7dab5c --- /dev/null +++ b/v2/pkg/commands/build/desktop_darwin.go @@ -0,0 +1,62 @@ +// +build darwin + +package build + +import ( + "fmt" + "github.com/wailsapp/wails/v2/internal/fs" + "io/ioutil" + "path/filepath" + "strings" +) + +// desktop_linux.go will compile the tray icon found at /trayicon.png into the application +func (d *DesktopBuilder) processTrayIcons(assetDir string, options *Options) error { + + // Determine icon file + iconFile := filepath.Join(options.ProjectData.Path, "trayicon.png") + + var err error + + // Setup target + targetFilename := "trayicon" + targetFile := filepath.Join(assetDir, targetFilename+".c") + //d.addFileToDelete(targetFile) + + var dataBytes []byte + + // If the icon file exists, load it up + if fs.FileExists(iconFile) { + // Load the tray icon + dataBytes, err = ioutil.ReadFile(iconFile) + if err != nil { + return err + } + } + + // Use a strings builder + var cdata strings.Builder + + // Write header + header := `// trayicon.c +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL. +// This file was auto-generated. DO NOT MODIFY. + +` + cdata.WriteString(header) + cdata.WriteString(fmt.Sprintf("const unsigned int trayIconLength = %d;\n", len(dataBytes))) + cdata.WriteString("const unsigned char trayIcon[] = { ") + + // Convert each byte to hex + for _, b := range dataBytes { + cdata.WriteString(fmt.Sprintf("0x%x, ", b)) + } + + cdata.WriteString("0x00 };\n") + + err = ioutil.WriteFile(targetFile, []byte(cdata.String()), 0600) + if err != nil { + return err + } + return nil +} diff --git a/v2/pkg/commands/build/hybrid.go b/v2/pkg/commands/build/hybrid.go index a7e2e0cb..5c1cc086 100644 --- a/v2/pkg/commands/build/hybrid.go +++ b/v2/pkg/commands/build/hybrid.go @@ -46,7 +46,7 @@ func (b *HybridBuilder) BuildBaseAssets(options *Options) error { return err } - err = b.desktop.BuildBaseAssets(assets, options.Logger) + err = b.desktop.BuildBaseAssets(assets, options) if err != nil { return err } diff --git a/v2/pkg/commands/build/packager_darwin.go b/v2/pkg/commands/build/packager_darwin.go index 0e21dcde..a3dfc597 100644 --- a/v2/pkg/commands/build/packager_darwin.go +++ b/v2/pkg/commands/build/packager_darwin.go @@ -24,10 +24,15 @@ func packageApplication(options *Options) error { contentsDirectory := filepath.Join(options.BuildDirectory, bundlename, "/Contents") exeDir := filepath.Join(contentsDirectory, "/MacOS") - fs.MkDirs(exeDir, 0755) + err = fs.MkDirs(exeDir, 0755) + if err != nil { + return err + } resourceDir := filepath.Join(contentsDirectory, "/Resources") - fs.MkDirs(resourceDir, 0755) - + err = fs.MkDirs(resourceDir, 0755) + if err != nil { + return err + } // Copy binary packedBinaryPath := filepath.Join(exeDir, options.ProjectData.Name) err = fs.MoveFile(options.CompiledBinary, packedBinaryPath) @@ -42,7 +47,7 @@ func packageApplication(options *Options) error { } // Generate Icons - err = processIcon(resourceDir) + err = processApplicationIcon(resourceDir) if err != nil { return err } @@ -84,8 +89,10 @@ func generateDefaultPlist(options *Options, targetPlistFile string) error { if err != nil { return errors.Wrap(err, "Cannot open plist template") } - tmpl.Parse(string(infoPlist)) - + _, err = tmpl.Parse(string(infoPlist)) + if err != nil { + return err + } // Write the template to a buffer var tpl bytes.Buffer err = tmpl.Execute(&tpl, plistData) @@ -122,7 +129,7 @@ func newPlistData(title, exe, packageID, version, author string) *plistData { } } -func processIcon(resourceDir string) error { +func processApplicationIcon(resourceDir string) error { appIcon, err := fs.RelativeToCwd("appicon.png") if err != nil { diff --git a/v2/pkg/options/default.go b/v2/pkg/options/default.go index 1f4ae25a..915007cd 100644 --- a/v2/pkg/options/default.go +++ b/v2/pkg/options/default.go @@ -2,7 +2,6 @@ package options import ( "github.com/wailsapp/wails/v2/pkg/logger" - "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/options/mac" ) @@ -18,7 +17,7 @@ var Default = &App{ Appearance: mac.DefaultAppearance, WebviewIsTransparent: false, WindowBackgroundIsTranslucent: false, - Menu: menu.DefaultMacMenu(), + //Menu: menu.DefaultMacMenu(), }, Logger: logger.NewDefaultLogger(), LogLevel: logger.INFO, diff --git a/v2/pkg/options/options.go b/v2/pkg/options/options.go index ab0c0ffa..d23d2184 100644 --- a/v2/pkg/options/options.go +++ b/v2/pkg/options/options.go @@ -1,7 +1,9 @@ package options import ( + "github.com/wailsapp/wails/v2/pkg/menu" "log" + "runtime" "github.com/imdario/mergo" "github.com/wailsapp/wails/v2/pkg/logger" @@ -22,15 +24,74 @@ type App struct { StartHidden bool DevTools bool RGBA int + Tray *menu.Menu + Menu *menu.Menu Mac *mac.Options Logger logger.Logger `json:"-"` LogLevel logger.LogLevel } // MergeDefaults will set the minimum default values for an application -func (a *App) MergeDefaults() { - err := mergo.Merge(a, Default) +func MergeDefaults(appoptions *App) { + err := mergo.Merge(appoptions, Default) if err != nil { log.Fatal(err) } + + // We need to ensure there's a default menu on Mac + switch runtime.GOOS { + case "darwin": + if GetApplicationMenu(appoptions) == nil { + appoptions.Menu = menu.NewMenuFromItems(menu.AppMenu()) + } + } + +} + +func GetTrayMenu(appoptions *App) *menu.Menu { + var result *menu.Menu + switch runtime.GOOS { + case "darwin": + if appoptions.Mac != nil { + result = appoptions.Mac.Tray + } + //case "linux": + // if appoptions.Linux != nil { + // result = appoptions.Linux.Tray + // } + //case "windows": + // if appoptions.Windows != nil { + // result = appoptions.Windows.Tray + // } + } + + if result == nil { + result = appoptions.Tray + } + + return result +} + +func GetApplicationMenu(appoptions *App) *menu.Menu { + var result *menu.Menu + switch runtime.GOOS { + case "darwin": + if appoptions.Mac != nil { + result = appoptions.Mac.Menu + } + //case "linux": + // if appoptions.Linux != nil { + // result = appoptions.Linux.Tray + // } + //case "windows": + // if appoptions.Windows != nil { + // result = appoptions.Windows.Tray + // } + } + + if result == nil { + result = appoptions.Menu + } + + return result } diff --git a/v2/test/kitchensink/main.go b/v2/test/kitchensink/main.go index 62af966c..77b57489 100644 --- a/v2/test/kitchensink/main.go +++ b/v2/test/kitchensink/main.go @@ -17,6 +17,8 @@ func main() { Height: 768, MinWidth: 800, MinHeight: 600, + //Tray: menu.NewMenuFromItems(menu.AppMenu()), + //Menu: menu.NewMenuFromItems(menu.AppMenu()), Mac: &mac.Options{ WebviewIsTransparent: true, WindowBackgroundIsTranslucent: true, diff --git a/v2/test/kitchensink/menu.go b/v2/test/kitchensink/menu.go index d8902b2b..f82be1a7 100644 --- a/v2/test/kitchensink/menu.go +++ b/v2/test/kitchensink/menu.go @@ -145,6 +145,10 @@ func (m *Menu) createDynamicMenuTwo() { // Insert this menu after Dynamic Menu Item 1 dm1 := m.runtime.Menu.GetByID("Dynamic Menus 1") + if dm1 == nil { + return + } + dm1.InsertAfter(dm2) m.runtime.Menu.Update() } @@ -172,6 +176,9 @@ func (m *Menu) insertBeforeRandom(_ *menu.MenuItem) { m.dynamicMenuItems[text] = newItem item := m.runtime.Menu.GetByID(randomItemID) + if item == nil { + return + } m.runtime.Log.Info(fmt.Sprintf( "Inserting menu item '%s' before menu item '%s'", newItem.Label, diff --git a/v2/test/kitchensink/tray.go b/v2/test/kitchensink/tray.go index 2f826048..68da2488 100644 --- a/v2/test/kitchensink/tray.go +++ b/v2/test/kitchensink/tray.go @@ -26,9 +26,20 @@ func (t *Tray) WailsInit(runtime *wails.Runtime) error { // Setup Menu Listeners t.runtime.Tray.On("Show Window", func(mi *menu.MenuItem) { t.runtime.Window.Show() + showWindow := t.runtime.Tray.GetByID("Show Window") + hideWindow := t.runtime.Tray.GetByID("Hide Window") + showWindow.Hidden = true + hideWindow.Hidden = false + t.runtime.Tray.Update() }) t.runtime.Tray.On("Hide Window", func(mi *menu.MenuItem) { t.runtime.Window.Hide() + showWindow := t.runtime.Tray.GetByID("Show Window") + hideWindow := t.runtime.Tray.GetByID("Hide Window") + showWindow.Hidden = false + hideWindow.Hidden = true + t.runtime.Tray.Update() + }) return nil @@ -108,7 +119,12 @@ func (t *Tray) removeMenu(_ *menu.MenuItem) { func createApplicationTray() *menu.Menu { trayMenu := &menu.Menu{} - trayMenu.Append(menu.Text("Show Window", "Show Window")) + trayMenu.Append(&menu.MenuItem{ + ID: "Show Window", + Label: "Show Window", + Type: menu.TextType, + Hidden: true, + }) trayMenu.Append(menu.Text("Hide Window", "Hide Window")) return trayMenu } diff --git a/v2/test/kitchensink/trayicon.png b/v2/test/kitchensink/trayicon.png new file mode 100644 index 0000000000000000000000000000000000000000..a46bbdd85d610ead14967874172003f4a622a718 GIT binary patch literal 2459 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s-wr7S!ltlRYSS9D@>LsS+ zC#C9D0Vi&D~Tl`=|73as??%gf94%8m8%i_-NCEiEne z4UF`SjC6}q(sYX}^GXscbn}XpK}JB#a7isrF3Kz@$;{7F0GXMXlwVq6tE9xGpr8OX zyd*OXVR~LM)ceW#x%wa(eM3D1ePrdS6`44cgJdw27i8p@;mZj#ED1$=+6fRbt zxryniKB;->B^kC#hGu9I;h8Du;vj=^N;&BVo4$)^>&~XYg3V1;OlGUky%`lUsMSe zf+X|c)Itak!p+G9sa8->0LQXbVlpVyZIud&@>5EaQ&Yg+g3A`?rp=z|LiP@!SRg(!_u^HOY;ij?f_Sk*Ec85kIwGM$|RFk*#)L1SX+ z#PixNfg;E4jh&U<7Dq%x_&BXx=-An7B;<8T;_3CYXEV(gKkL0-ZJfC&bX%8@^nS5F ztT|J+2Dx~JiCsI=_R_N4t@p^@yTx|3?`@Y??-##waZB0_QAGozNNpn_zB6aevF`Ez z@!`?J$=_zA&2FAN`SBm?d-;VcYwj+r`dk0*eBkC88%zsAIr%e|O%;_Bm5V<)t806U z^p=pEz#lFa87Em=^QXw^f86{irPE$C`r-+J9j{xMPs+>uKNxk&sO9O?EfO#1e5;lJ zDip6|Y^%C6GECS^(2YYlp(nBJ#nSG&&b=iUbooj<%j>VVcYPJs@m{NTQKkQ=kY`W| zXVJBHdcip}^V6yS_xIhZ@-JVxTc2lYf8Xa@T=#r6axBgrZ+tAmcknaA^O;3| zKiPfR+qp0BTGY1#``MO@r|RzGO@Do%BLCQ$h`>)Lie6jok#T;jb0yyKYt6~c?`!+s zDeTgUFXgn}eL?N{U+(y%|LIR;7hDo#U|`@Z@Q5sCVBi)8VMc~ob0ioTnErUWIEGmC z<_39ZONUGReV1*bocZM9jZIS0uY^`yautmcz3#bSOMt#=#M>UVinsk? zXcmvPSUthDV9`3JJP*YQIj$?NKAOeyYeUXTR^?t><4bed7i-_W?K-PT{{FWDzUMX1 z6_$#`l)Zla`q-a8f68b2Jc~YdW9s3<&IZ21$BrFy;Fwsl`!4%jzvp^;r|>cq7Z+b{ zORPC2A>F@-W7AEY8Ou0>ysp0fDrebOy!`UZHxEyrkd~G{vT4nE!(-P4omvE#cE<1p ztrYpOZ~yaM?+%yk{(Jk-A*cUA?3*`lPCaapXK?Hj1H-Ikj7}VFhZlA|{!zL5>aWzo zLc_?YC?|25HEY%^h|&9QxO7X)g>0E*9u7qTrz1`s`(J(C#mx|)H@#b&MNM73c-Ojm z2ad|uHp`~GO71!Aw%Bk**y`k^K`&2p3#O%|Y4vRUZg5QVdTC=Z-?rT5%vlbazS3=m zd$(;n=6iY1&DUT581rr0wyj`czzo%vJ<2T&QES=U-Q7Pd%Q_~pIrsPKj~_qYW=POl zI&WgoN|U|&_xqoITGU@0Cn+oYx6Sa|zM!rbyZD5Kg$<01m^c(4%u%2B{I6i!!^IYT z^51^{oxUP&eZGK`fZ`LE>tFp%e0O_tNCtW}PWkkooz3w9i{ppAuXp*^-2Z>OP$s{! zveLo#@}8p-)#AxE2PB>gI{iv8kZ=q9V$`+h#-=rs*1fJg|M_30*=+m#{QUQC-@M_u ze`J~0N=~oLhx=!_?7wgSbk6m|279J^EnTPBvd%;E^PJ)8C;6*%%ytcyN^B!y}ivowMX=jfse*gd(EYBp{qkH-4u8Z3n-S%FaF1)IHgeL z`S~2P*?J;e@foweB~DVA_`EHQiDtXaRJ zE^q0#Ise<1qlsNzUEN+zM#dyOJiJ|;C2H-pV6V*V@^bU|l_7be-AA*RFJI2SYn}bH rpnX<$c6Mv7X5D-CY?+Ne8^aHASD|$E#Lr%TKy4~dS3j3^P6