[WIP] Support tray in menu manager

This commit is contained in:
Lea Anthony
2021-01-12 15:55:28 +11:00
parent 4e58b7697a
commit db6dde3e50
16 changed files with 250 additions and 209 deletions

View File

@@ -71,6 +71,12 @@ func CreateApp(appoptions *options.App) (*App, error) {
}
}
// Process tray menus
trayMenus := options.GetTrayMenus(appoptions)
for _, trayMenu := range trayMenus {
menuManager.AddTrayMenu(trayMenu)
}
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
result := &App{
@@ -112,10 +118,9 @@ func (a *App) Run() error {
// Start the runtime
applicationMenu := options.GetApplicationMenu(a.options)
trayMenu := options.GetTray(a.options)
contextMenus := options.GetContextMenus(a.options)
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, applicationMenu, trayMenu, contextMenus)
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, contextMenus)
if err != nil {
return err
}
@@ -186,18 +191,18 @@ func (a *App) Run() error {
}
}
// Optionally start the tray subsystem
if trayMenu != nil {
traysubsystem, err := subsystem.NewTray(trayMenu, a.servicebus, a.logger)
if err != nil {
return err
}
a.tray = traysubsystem
err = a.tray.Start()
if err != nil {
return err
}
}
//// Optionally start the tray subsystem
//if trayMenu != nil {
// traysubsystem, err := subsystem.NewTray(trayMenu, a.servicebus, a.logger)
// if err != nil {
// return err
// }
// a.tray = traysubsystem
// err = a.tray.Start()
// if err != nil {
// return err
// }
//}
// Optionally start the context menu subsystem
if contextMenus != nil {

View File

@@ -200,7 +200,7 @@ func (c *Client) UpdateTray(menu *menu.Menu) {
processedMenu := NewProcessedMenu(menu)
trayMenuJSON, err := json.Marshal(processedMenu)
if err != nil {
c.app.logger.Error("Error processing updated Tray: %s", err.Error())
c.app.logger.Error("Error processing updated TrayMenu: %s", err.Error())
return
}
C.UpdateTray(c.app.app, c.app.string2CString(string(trayMenuJSON)))

View File

@@ -23,8 +23,8 @@ extern void SetContextMenus(void *, const char *);
*/
import "C"
import (
"encoding/json"
"github.com/wailsapp/wails/v2/pkg/options"
"os"
)
func (a *Application) processPlatformSettings() error {
@@ -83,24 +83,17 @@ func (a *Application) processPlatformSettings() error {
}
// Process tray
tray := options.GetTray(a.config)
if tray != nil {
/*
As radio groups need to be manually managed on OSX,
we preprocess the menu to determine the radio groups.
This is defined as any adjacent menu item of type "RadioType".
We keep a record of every radio group member we discover by saving
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(tray.Menu)
trayMenuJSON, err := json.Marshal(processedMenu)
if err != nil {
return err
}
C.SetTray(a.app, a.string2CString(string(trayMenuJSON)), a.string2CString(tray.Label), a.string2CString(tray.Icon))
trays, err := a.menuManager.GetTrayMenus()
if err != nil {
return err
}
if trays != nil {
for _, tray := range trays {
println("Adding tray menu: " + tray)
//C.AddTray(a.app, a.string2CString(tray))
}
}
os.Exit(1)
// Process context menus
contextMenus := options.GetContextMenus(a.config)

View File

@@ -16,12 +16,16 @@ type Manager struct {
// Context menus
contextMenus map[string]*ContextMenu
// Tray menus
trayMenus map[string]*TrayMenu
}
func NewManager() *Manager {
return &Manager{
applicationMenuItemMap: NewMenuItemMap(),
contextMenus: make(map[string]*ContextMenu),
trayMenus: make(map[string]*TrayMenu),
}
}

View File

@@ -0,0 +1,62 @@
package menumanager
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/pkg/menu"
"sync"
)
var trayMenuID int
var trayMenuIDMutex sync.Mutex
func generateTrayID() string {
trayMenuIDMutex.Lock()
result := fmt.Sprintf("%d", trayMenuID)
trayMenuID++
trayMenuIDMutex.Unlock()
return result
}
type TrayMenu struct {
ID string
Label string
Icon string
menuItemMap *MenuItemMap
menu *menu.Menu
ProcessedMenu *WailsMenu
}
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
result := &TrayMenu{
ID: generateTrayID(),
Label: trayMenu.Label,
Icon: trayMenu.Icon,
menu: trayMenu.Menu,
menuItemMap: NewMenuItemMap(),
}
result.menuItemMap.AddMenu(trayMenu.Menu)
result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
return result
}
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) {
newTrayMenu := NewTrayMenu(trayMenu)
m.trayMenus[newTrayMenu.ID] = newTrayMenu
}
func (m *Manager) GetTrayMenus() ([]string, error) {
result := []string{}
for _, trayMenu := range m.trayMenus {
data, err := json.Marshal(trayMenu)
if err != nil {
return nil, err
}
result = append(result, string(data))
}
return result, nil
}

View File

@@ -2,7 +2,6 @@ package runtime
import (
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/menu"
)
// Menu defines all Menu related operations
@@ -11,15 +10,13 @@ type Menu interface {
}
type menuRuntime struct {
bus *servicebus.ServiceBus
menu *menu.Menu
bus *servicebus.ServiceBus
}
// newMenu creates a new Menu struct
func newMenu(bus *servicebus.ServiceBus, menu *menu.Menu) Menu {
func newMenu(bus *servicebus.ServiceBus) Menu {
return &menuRuntime{
bus: bus,
menu: menu,
bus: bus,
}
}

View File

@@ -14,22 +14,20 @@ type Runtime struct {
System System
Menu Menu
ContextMenu ContextMenus
Tray Tray
Store *StoreProvider
Log Log
bus *servicebus.ServiceBus
}
// New creates a new runtime
func New(serviceBus *servicebus.ServiceBus, menu *menu.Menu, trayMenu *menu.Tray, contextMenus *menu.ContextMenus) *Runtime {
func New(serviceBus *servicebus.ServiceBus, contextMenus *menu.ContextMenus) *Runtime {
result := &Runtime{
Browser: newBrowser(),
Events: newEvents(serviceBus),
Window: newWindow(serviceBus),
Dialog: newDialog(serviceBus),
System: newSystem(serviceBus),
Menu: newMenu(serviceBus, menu),
Tray: newTray(serviceBus, trayMenu),
Menu: newMenu(serviceBus),
ContextMenu: newContextMenus(serviceBus, contextMenus),
Log: newLog(serviceBus),
bus: serviceBus,

View File

@@ -8,9 +8,8 @@ import (
// Tray defines all Tray related operations
type Tray interface {
NewTray(id string) *menu.Tray
On(menuID string, callback func(*menu.MenuItem))
Update(tray ...*menu.Tray)
Update(tray ...*menu.TrayMenu)
GetByID(menuID string) *menu.MenuItem
RemoveByID(id string) bool
SetLabel(label string)
@@ -19,11 +18,11 @@ type Tray interface {
type trayRuntime struct {
bus *servicebus.ServiceBus
trayMenu *menu.Tray
trayMenu *menu.TrayMenu
}
// newTray creates a new Menu struct
func newTray(bus *servicebus.ServiceBus, menu *menu.Tray) Tray {
func newTray(bus *servicebus.ServiceBus, menu *menu.TrayMenu) Tray {
return &trayRuntime{
bus: bus,
trayMenu: menu,
@@ -38,14 +37,7 @@ func (t *trayRuntime) On(menuID string, callback func(*menu.MenuItem)) {
})
}
// NewTray creates a new Tray item
func (t *trayRuntime) NewTray(trayID string) *menu.Tray {
return &menu.Tray{
ID: trayID,
}
}
func (t *trayRuntime) Update(tray ...*menu.Tray) {
func (t *trayRuntime) Update(tray ...*menu.TrayMenu) {
//trayToUpdate := t.trayMenu
t.bus.Publish("tray:update", t.trayMenu)

View File

@@ -2,13 +2,10 @@ package subsystem
import (
"encoding/json"
"github.com/wailsapp/wails/v2/internal/menumanager"
"strings"
"sync"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/menu"
"strings"
)
// Menu is the subsystem that handles the operation of menus. It manages all service bus messages
@@ -18,11 +15,6 @@ type Menu struct {
menuChannel <-chan *servicebus.Message
running bool
// Event listeners
listeners map[string][]func(*menu.MenuItem)
menuItems map[string]*menu.MenuItem
notifyLock sync.RWMutex
// logger
logger logger.CustomLogger
@@ -52,8 +44,6 @@ func NewMenu(bus *servicebus.ServiceBus, logger *logger.Logger, menuManager *men
quitChannel: quitChannel,
menuChannel: menuChannel,
logger: logger.CustomLogger("Menu Subsystem"),
listeners: make(map[string][]func(*menu.MenuItem)),
menuItems: make(map[string]*menu.MenuItem),
bus: bus,
menuManager: menuManager,
}

View File

@@ -24,7 +24,7 @@ type Runtime struct {
}
// NewRuntime creates a new runtime subsystem
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, menu *menu.Menu, trayMenu *menu.Tray, contextMenus *menu.ContextMenus) (*Runtime, error) {
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, contextMenus *menu.ContextMenus) (*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, trayMenu, contextMenus),
runtime: runtime.New(bus, contextMenus),
}
return result, nil

View File

@@ -26,14 +26,14 @@ type Tray struct {
logger logger.CustomLogger
// The tray menu
trayMenu *menu.Tray
trayMenu *menu.TrayMenu
// Service Bus
bus *servicebus.ServiceBus
}
// NewTray creates a new menu subsystem
func NewTray(trayMenu *menu.Tray, bus *servicebus.ServiceBus,
func NewTray(trayMenu *menu.TrayMenu, bus *servicebus.ServiceBus,
logger *logger.Logger) (*Tray, error) {
// Register quit channel
@@ -51,7 +51,7 @@ func NewTray(trayMenu *menu.Tray, bus *servicebus.ServiceBus,
result := &Tray{
quitChannel: quitChannel,
trayChannel: trayChannel,
logger: logger.CustomLogger("Tray Subsystem"),
logger: logger.CustomLogger("TrayMenu Subsystem"),
listeners: make(map[string][]func(*menu.MenuItem)),
menuItems: make(map[string]*menu.MenuItem),
trayMenu: trayMenu,
@@ -87,7 +87,7 @@ func (t *Tray) Start() error {
t.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
continue
}
t.logger.Trace("Got Tray Menu clicked Message: %s %+v", menuMessage.Topic(), menuMessage.Data())
t.logger.Trace("Got TrayMenu Menu clicked Message: %s %+v", menuMessage.Topic(), menuMessage.Data())
menuid := menuMessage.Data().(string)
// Get the menu item

View File

@@ -1,10 +1,7 @@
package menu
// Tray are the options
type Tray struct {
// The ID of this tray
ID string
// TrayMenu are the options
type TrayMenu struct {
// Label is the text we wish to display in the tray
Label string

View File

@@ -9,6 +9,6 @@ type Options struct {
WebviewIsTransparent bool
WindowBackgroundIsTranslucent bool
Menu *menu.Menu
Tray *menu.Tray
TrayMenus []*menu.TrayMenu
ContextMenus *menu.ContextMenus
}

View File

@@ -25,7 +25,7 @@ type App struct {
DevTools bool
RGBA int
ContextMenus *menu.ContextMenus
Tray *menu.Tray
TrayMenus []*menu.TrayMenu
Menu *menu.Menu
Mac *mac.Options
Logger logger.Logger `json:"-"`
@@ -41,25 +41,25 @@ func MergeDefaults(appoptions *App) {
}
func GetTray(appoptions *App) *menu.Tray {
var result *menu.Tray
func GetTrayMenus(appoptions *App) []*menu.TrayMenu {
var result []*menu.TrayMenu
switch runtime.GOOS {
case "darwin":
if appoptions.Mac != nil {
result = appoptions.Mac.Tray
result = appoptions.Mac.TrayMenus
}
//case "linux":
// if appoptions.Linux != nil {
// result = appoptions.Linux.Tray
// result = appoptions.Linux.TrayMenu
// }
//case "windows":
// if appoptions.Windows != nil {
// result = appoptions.Windows.Tray
// result = appoptions.Windows.TrayMenu
// }
}
if result == nil {
result = appoptions.Tray
result = appoptions.TrayMenus
}
return result
@@ -74,11 +74,11 @@ func GetApplicationMenu(appoptions *App) *menu.Menu {
}
//case "linux":
// if appoptions.Linux != nil {
// result = appoptions.Linux.Tray
// result = appoptions.Linux.TrayMenu
// }
//case "windows":
// if appoptions.Windows != nil {
// result = appoptions.Windows.Tray
// result = appoptions.Windows.TrayMenu
// }
}
@@ -101,11 +101,11 @@ func GetContextMenus(appoptions *App) *menu.ContextMenus {
}
//case "linux":
// if appoptions.Linux != nil {
// result = appoptions.Linux.Tray
// result = appoptions.Linux.TrayMenu
// }
//case "windows":
// if appoptions.Windows != nil {
// result = appoptions.Windows.Tray
// result = appoptions.Windows.TrayMenu
// }
}

View File

@@ -11,6 +11,7 @@ import (
func main() {
Menu := &Menu{}
Tray := &Tray{}
// Create application with options
app, err := wails.CreateAppWithOptions(&options.App{
@@ -27,12 +28,9 @@ func main() {
WebviewIsTransparent: true,
WindowBackgroundIsTranslucent: true,
// Comment out line below to see Window.SetTitle() work
TitleBar: mac.TitleBarHiddenInset(),
Menu: Menu.createApplicationMenu(),
//Tray: &menu.Tray{
// Icon: "light",
// Menu: createApplicationTray(),
//},
TitleBar: mac.TitleBarHiddenInset(),
Menu: Menu.createApplicationMenu(),
TrayMenus: Tray.createTrayMenus(),
},
LogLevel: logger.TRACE,
})
@@ -48,7 +46,6 @@ func main() {
app.Bind(&Dialog{})
app.Bind(&Window{})
app.Bind(Menu)
app.Bind(&Tray{})
app.Bind(&ContextMenu{})
err = app.Run()

View File

@@ -3,19 +3,18 @@ package main
import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
"strconv"
"sync"
)
// Tray struct
type Tray struct {
runtime *wails.Runtime
//
//dynamicMenuCounter int
//lock sync.Mutex
//dynamicMenuItems map[string]*menu.MenuItem
//anotherDynamicMenuCounter int
dynamicMenuCounter int
lock sync.Mutex
dynamicMenuItems map[string]*menu.MenuItem
anotherDynamicMenuCounter int
trayMenu *menu.TrayMenu
done bool
}
@@ -24,32 +23,32 @@ type Tray struct {
func (t *Tray) WailsInit(runtime *wails.Runtime) error {
// Perform your setup here
t.runtime = runtime
// Setup Menu Listeners
t.runtime.Tray.On("Show Window", func(mi *menu.MenuItem) {
t.runtime.Window.Show()
})
t.runtime.Tray.On("Hide Window", func(mi *menu.MenuItem) {
t.runtime.Window.Hide()
})
t.runtime.Tray.On("Minimise Window", func(mi *menu.MenuItem) {
t.runtime.Window.Minimise()
})
t.runtime.Tray.On("Unminimise Window", func(mi *menu.MenuItem) {
t.runtime.Window.Unminimise()
})
// Auto switch between light / dark tray icons
t.runtime.Events.OnThemeChange(func(darkMode bool) {
if darkMode {
t.runtime.Tray.SetIcon("light")
return
}
t.runtime.Tray.SetIcon("dark")
})
//
//// Setup Menu Listeners
//t.runtime.Tray.On("Show Window", func(mi *menu.MenuItem) {
// t.runtime.Window.Show()
//})
//t.runtime.Tray.On("Hide Window", func(mi *menu.MenuItem) {
// t.runtime.Window.Hide()
//})
//
//t.runtime.Tray.On("Minimise Window", func(mi *menu.MenuItem) {
// t.runtime.Window.Minimise()
//})
//
//t.runtime.Tray.On("Unminimise Window", func(mi *menu.MenuItem) {
// t.runtime.Window.Unminimise()
//})
//
//// Auto switch between light / dark tray icons
//t.runtime.Events.OnThemeChange(func(darkMode bool) {
// if darkMode {
// t.runtime.Tray.SetIcon("light")
// return
// }
//
// t.runtime.Tray.SetIcon("dark")
//})
return nil
}
@@ -58,87 +57,94 @@ func (t *Tray) WailsShutdown() {
t.done = true
}
func (t *Tray) incrementcounter() int {
t.dynamicMenuCounter++
return t.dynamicMenuCounter
}
//func (t *Tray) incrementcounter() int {
// t.dynamicMenuCounter++
// return t.dynamicMenuCounter
//}
//
//func (t *Tray) decrementcounter() int {
// t.dynamicMenuCounter--
// return t.dynamicMenuCounter
//}
//
//func (t *Tray) addMenu(mi *menu.MenuItem) {
//
// // Lock because this method will be called in a gorouting
// t.lock.Lock()
// defer t.lock.Unlock()
//
// // Get this menu's parent
// parent := mi.Parent()
// counter := t.incrementcounter()
// menuText := "Dynamic Menu Item " + strconv.Itoa(counter)
// parent.Append(menu.Text(menuText, menuText, nil, nil))
// // parent.Append(menu.Text(menuText, menuText, menu.Key("[")))
//
// // If this is the first dynamic menu added, let's add a remove menu item
// if counter == 1 {
// removeMenu := menu.Text("Remove "+menuText,
// "Remove Last Item", keys.CmdOrCtrl("-"), nil)
// parent.Prepend(removeMenu)
// t.runtime.Tray.On("Remove Last Item", t.removeMenu)
// } else {
// removeMenu := t.runtime.Tray.GetByID("Remove Last Item")
// // Test if the remove menu hasn't already been removed in another thread
// if removeMenu != nil {
// removeMenu.Label = "Remove " + menuText
// }
// }
// t.runtime.Tray.Update()
//}
//
//func (t *Tray) removeMenu(_ *menu.MenuItem) {
//
// // Lock because this method will be called in a goroutine
// t.lock.Lock()
// defer t.lock.Unlock()
//
// // Get the id of the last dynamic menu
// menuID := "Dynamic Menu Item " + strconv.Itoa(t.dynamicMenuCounter)
//
// // Remove the last menu item by ID
// t.runtime.Tray.RemoveByID(menuID)
//
// // Update the counter
// counter := t.decrementcounter()
//
// // If we deleted the last dynamic menu, remove the "Remove Last Item" menu
// if counter == 0 {
// t.runtime.Tray.RemoveByID("Remove Last Item")
// } else {
// // Update label
// menuText := "Dynamic Menu Item " + strconv.Itoa(counter)
// removeMenu := t.runtime.Tray.GetByID("Remove Last Item")
// // Test if the remove menu hasn't already been removed in another thread
// if removeMenu == nil {
// return
// }
// removeMenu.Label = "Remove " + menuText
// }
//
// // parent.Append(menu.Text(menuText, menuText, menu.Key("[")))
// t.runtime.Tray.Update()
//}
func (t *Tray) decrementcounter() int {
t.dynamicMenuCounter--
return t.dynamicMenuCounter
}
//func (t *Tray) SetIcon(trayIconID string) {
// t.runtime.Tray.SetIcon(trayIconID)
//}
func (t *Tray) addMenu(mi *menu.MenuItem) {
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),
)
t.trayMenu = trayMenu
// Lock because this method will be called in a gorouting
t.lock.Lock()
defer t.lock.Unlock()
// Get this menu's parent
parent := mi.Parent()
counter := t.incrementcounter()
menuText := "Dynamic Menu Item " + strconv.Itoa(counter)
parent.Append(menu.Text(menuText, menuText, nil, nil))
// parent.Append(menu.Text(menuText, menuText, menu.Key("[")))
// If this is the first dynamic menu added, let's add a remove menu item
if counter == 1 {
removeMenu := menu.Text("Remove "+menuText,
"Remove Last Item", keys.CmdOrCtrl("-"), nil)
parent.Prepend(removeMenu)
t.runtime.Tray.On("Remove Last Item", t.removeMenu)
} else {
removeMenu := t.runtime.Tray.GetByID("Remove Last Item")
// Test if the remove menu hasn't already been removed in another thread
if removeMenu != nil {
removeMenu.Label = "Remove " + menuText
}
return []*menu.TrayMenu{
trayMenu,
}
t.runtime.Tray.Update()
}
func (t *Tray) removeMenu(_ *menu.MenuItem) {
// Lock because this method will be called in a goroutine
t.lock.Lock()
defer t.lock.Unlock()
// Get the id of the last dynamic menu
menuID := "Dynamic Menu Item " + strconv.Itoa(t.dynamicMenuCounter)
// Remove the last menu item by ID
t.runtime.Tray.RemoveByID(menuID)
// Update the counter
counter := t.decrementcounter()
// If we deleted the last dynamic menu, remove the "Remove Last Item" menu
if counter == 0 {
t.runtime.Tray.RemoveByID("Remove Last Item")
} else {
// Update label
menuText := "Dynamic Menu Item " + strconv.Itoa(counter)
removeMenu := t.runtime.Tray.GetByID("Remove Last Item")
// Test if the remove menu hasn't already been removed in another thread
if removeMenu == nil {
return
}
removeMenu.Label = "Remove " + menuText
}
// parent.Append(menu.Text(menuText, menuText, menu.Key("[")))
t.runtime.Tray.Update()
}
func (t *Tray) SetIcon(trayIconID string) {
t.runtime.Tray.SetIcon(trayIconID)
}
func createApplicationTray() *menu.Menu {
trayMenu := &menu.Menu{}
trayMenu.Append(menu.Text("Show Window", "Show Window", nil, nil))
trayMenu.Append(menu.Text("Hide Window", "Hide Window", nil, nil))
trayMenu.Append(menu.Text("Minimise Window", "Minimise Window", nil, nil))
trayMenu.Append(menu.Text("Unminimise Window", "Unminimise Window", nil, nil))
return trayMenu
}