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 00000000..a46bbdd8 Binary files /dev/null and b/v2/test/kitchensink/trayicon.png differ