mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 23:21:58 +08:00
Preliminary Tray support
This commit is contained in:
parent
65bea04080
commit
11bf564b73
@ -33,6 +33,7 @@ type App struct {
|
|||||||
binding *subsystem.Binding
|
binding *subsystem.Binding
|
||||||
call *subsystem.Call
|
call *subsystem.Call
|
||||||
menu *subsystem.Menu
|
menu *subsystem.Menu
|
||||||
|
tray *subsystem.Tray
|
||||||
dispatcher *messagedispatcher.Dispatcher
|
dispatcher *messagedispatcher.Dispatcher
|
||||||
|
|
||||||
// Indicates if the app is in debug mode
|
// Indicates if the app is in debug mode
|
||||||
@ -47,7 +48,7 @@ type App struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create App
|
// Create App
|
||||||
func CreateApp(options *options.App) *App {
|
func CreateApp(options *options.App) (*App, error) {
|
||||||
|
|
||||||
// Merge default options
|
// Merge default options
|
||||||
options.MergeDefaults()
|
options.MergeDefaults()
|
||||||
@ -68,15 +69,17 @@ func CreateApp(options *options.App) *App {
|
|||||||
result.options = options
|
result.options = options
|
||||||
|
|
||||||
// Initialise the app
|
// Initialise the app
|
||||||
result.Init()
|
err := result.Init()
|
||||||
|
|
||||||
return result
|
return result, err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the application
|
// Run the application
|
||||||
func (a *App) Run() error {
|
func (a *App) Run() error {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
// Setup signal handler
|
// Setup signal handler
|
||||||
signalsubsystem, err := signal.NewManager(a.servicebus, a.logger)
|
signalsubsystem, err := signal.NewManager(a.servicebus, a.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -87,7 +90,10 @@ func (a *App) Run() error {
|
|||||||
|
|
||||||
// Start the service bus
|
// Start the service bus
|
||||||
a.servicebus.Debug()
|
a.servicebus.Debug()
|
||||||
a.servicebus.Start()
|
err = a.servicebus.Start()
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Start the runtime
|
// Start the runtime
|
||||||
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger,
|
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger,
|
||||||
@ -96,7 +102,10 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.runtime = runtimesubsystem
|
a.runtime = runtimesubsystem
|
||||||
a.runtime.Start()
|
err = a.runtime.Start()
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Application Stores
|
// Application Stores
|
||||||
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
|
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
|
||||||
@ -109,7 +118,10 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.binding = bindingsubsystem
|
a.binding = bindingsubsystem
|
||||||
a.binding.Start()
|
err = a.binding.Start()
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Start the logging subsystem
|
// Start the logging subsystem
|
||||||
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
|
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
|
||||||
@ -117,7 +129,10 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.log = log
|
a.log = log
|
||||||
a.log.Start()
|
err = a.log.Start()
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// create the dispatcher
|
// create the dispatcher
|
||||||
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
|
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
|
||||||
@ -125,7 +140,10 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.dispatcher = dispatcher
|
a.dispatcher = dispatcher
|
||||||
dispatcher.Start()
|
err = dispatcher.Start()
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Start the eventing subsystem
|
// Start the eventing subsystem
|
||||||
event, err := subsystem.NewEvent(a.servicebus, a.logger)
|
event, err := subsystem.NewEvent(a.servicebus, a.logger)
|
||||||
@ -133,26 +151,53 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.event = event
|
a.event = event
|
||||||
a.event.Start()
|
err = a.event.Start()
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Start the menu subsystem
|
// Start the menu subsystem
|
||||||
var platformMenu *menu.Menu
|
var applicationMenu *menu.Menu
|
||||||
|
var trayMenu *menu.Menu
|
||||||
switch goruntime.GOOS {
|
switch goruntime.GOOS {
|
||||||
case "darwin":
|
case "darwin":
|
||||||
platformMenu = a.options.Mac.Menu
|
applicationMenu = a.options.Mac.Menu
|
||||||
|
trayMenu = a.options.Mac.Tray
|
||||||
// case "linux":
|
// case "linux":
|
||||||
// platformMenu = a.options.Linux.Menu
|
// applicationMenu = a.options.Linux.Menu
|
||||||
// case "windows":
|
// case "windows":
|
||||||
// platformMenu = a.options.Windows.Menu
|
// applicationMenu = a.options.Windows.Menu
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported OS: %s", goruntime.GOOS)
|
return fmt.Errorf("unsupported OS: %s", goruntime.GOOS)
|
||||||
}
|
}
|
||||||
menusubsystem, err := subsystem.NewMenu(platformMenu, a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
// Optionally start the menu subsystem
|
||||||
return err
|
if applicationMenu != nil {
|
||||||
|
menusubsystem, err := subsystem.NewMenu(applicationMenu, a.servicebus,
|
||||||
|
a.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.menu = menusubsystem
|
||||||
|
err = a.menu.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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a.menu = menusubsystem
|
|
||||||
a.menu.Start()
|
|
||||||
|
|
||||||
// Start the call subsystem
|
// Start the call subsystem
|
||||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
||||||
@ -160,7 +205,10 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.call = call
|
a.call = call
|
||||||
a.call.Start()
|
err = a.call.Start()
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Dump bindings as a debug
|
// Dump bindings as a debug
|
||||||
bindingDump, err := a.bindings.ToJSON()
|
bindingDump, err := a.bindings.ToJSON()
|
||||||
@ -170,7 +218,10 @@ func (a *App) Run() error {
|
|||||||
|
|
||||||
result := a.window.Run(dispatcher, bindingDump, a.debug)
|
result := a.window.Run(dispatcher, bindingDump, a.debug)
|
||||||
a.logger.Trace("Ffenestri.Run() exited")
|
a.logger.Trace("Ffenestri.Run() exited")
|
||||||
a.servicebus.Stop()
|
err = a.servicebus.Stop()
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -74,12 +74,18 @@ extern const char *icon[];
|
|||||||
// MAIN DEBUG FLAG
|
// MAIN DEBUG FLAG
|
||||||
int debug;
|
int debug;
|
||||||
|
|
||||||
// MenuItem map
|
// MenuItem map for the application menu
|
||||||
struct hashmap_s menuItemMapForApplicationMenu;
|
struct hashmap_s menuItemMapForApplicationMenu;
|
||||||
|
|
||||||
// RadioGroup map. Maps a menuitem id with its associated radio group items
|
// RadioGroup map for the application menu. Maps a menuitem id with its associated radio group items
|
||||||
struct hashmap_s radioGroupMapForApplicationMenu;
|
struct hashmap_s radioGroupMapForApplicationMenu;
|
||||||
|
|
||||||
|
// MenuItem map for the tray menu
|
||||||
|
struct hashmap_s menuItemMapForTrayMenu;
|
||||||
|
|
||||||
|
// RadioGroup map for the tray menu. Maps a menuitem id with its associated radio group items
|
||||||
|
struct hashmap_s radioGroupMapForTrayMenu;
|
||||||
|
|
||||||
// Dispatch Method
|
// Dispatch Method
|
||||||
typedef void (^dispatchMethod)(void);
|
typedef void (^dispatchMethod)(void);
|
||||||
|
|
||||||
@ -341,7 +347,7 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Callback for menu items
|
// Callback for menu items
|
||||||
void menuItemPressed(id self, SEL cmd, id sender) {
|
void menuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
|
||||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||||
// Notify the backend
|
// Notify the backend
|
||||||
const char *message = concat("MC", menuID);
|
const char *message = concat("MC", menuID);
|
||||||
@ -349,10 +355,17 @@ void menuItemPressed(id self, SEL cmd, id sender) {
|
|||||||
free((void*)message);
|
free((void*)message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Callback for tray items
|
||||||
|
void menuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
||||||
|
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||||
|
// Notify the backend
|
||||||
|
const char *message = concat("TC", menuID);
|
||||||
|
messageFromWindowCallback(message);
|
||||||
|
free((void*)message);
|
||||||
|
}
|
||||||
|
|
||||||
// Callback for menu items
|
// Callback for menu items
|
||||||
void checkboxMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender, struct
|
void checkboxMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
||||||
hashmap_s
|
|
||||||
*menuItemMap) {
|
|
||||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||||
|
|
||||||
// Get the menu item from the menu item map
|
// Get the menu item from the menu item map
|
||||||
@ -370,7 +383,26 @@ hashmap_s
|
|||||||
free((void*)message);
|
free((void*)message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// radioMenuItemPressed
|
// Callback for tray menu items
|
||||||
|
void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
||||||
|
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||||
|
|
||||||
|
// Get the menu item from the menu item map
|
||||||
|
id menuItem = (id)hashmap_get(&menuItemMapForTrayMenu, (char*)menuID, strlen(menuID));
|
||||||
|
|
||||||
|
// Get the current state
|
||||||
|
bool state = msg(menuItem, s("state"));
|
||||||
|
|
||||||
|
// Toggle the state
|
||||||
|
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||||
|
|
||||||
|
// Notify the backend
|
||||||
|
const char *message = concat("TC", menuID);
|
||||||
|
messageFromWindowCallback(message);
|
||||||
|
free((void*)message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// radioMenuItemPressedForApplicationMenu
|
||||||
void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
|
void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
|
||||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||||
|
|
||||||
@ -406,6 +438,43 @@ void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
|
|||||||
free((void*)message);
|
free((void*)message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// radioMenuItemPressedForTrayMenu
|
||||||
|
void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
||||||
|
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||||
|
|
||||||
|
// Get the menu item from the menu item map
|
||||||
|
id menuItem = (id)hashmap_get(&menuItemMapForTrayMenu, (char*)menuID, strlen(menuID));
|
||||||
|
|
||||||
|
// Check the menu items' current state
|
||||||
|
bool selected = msg(menuItem, s("state"));
|
||||||
|
|
||||||
|
// If it's already selected, exit early
|
||||||
|
if (selected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get this item's radio group members and turn them off
|
||||||
|
id *members = (id*)hashmap_get(&radioGroupMapForTrayMenu, (char*)menuID, strlen(menuID));
|
||||||
|
|
||||||
|
// Uncheck all members of the group
|
||||||
|
id thisMember = members[0];
|
||||||
|
int count = 0;
|
||||||
|
while(thisMember != NULL) {
|
||||||
|
msg(thisMember, s("setState:"), NSControlStateValueOff);
|
||||||
|
count = count + 1;
|
||||||
|
thisMember = members[count];
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the selected menu item
|
||||||
|
msg(menuItem, s("setState:"), NSControlStateValueOn);
|
||||||
|
|
||||||
|
// Notify the backend
|
||||||
|
const char *message = concat("TC", menuID);
|
||||||
|
messageFromWindowCallback(message);
|
||||||
|
free((void*)message);
|
||||||
|
}
|
||||||
|
|
||||||
// closeWindow is called when the close button is pressed
|
// closeWindow is called when the close button is pressed
|
||||||
void closeWindow(id self, SEL cmd, id sender) {
|
void closeWindow(id self, SEL cmd, id sender) {
|
||||||
struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
|
struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
|
||||||
@ -459,6 +528,22 @@ void allocateMenuHashMaps(struct Application *app) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void allocateTrayHashMaps(struct Application *app) {
|
||||||
|
// Allocate new menuItem map
|
||||||
|
if( 0 != hashmap_create((const unsigned)16, &menuItemMapForTrayMenu)) {
|
||||||
|
// Couldn't allocate map
|
||||||
|
Fatal(app, "Not enough memory to allocate menuItemMapForTrayMenu!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the Radio Group Cache
|
||||||
|
if( 0 != hashmap_create((const unsigned)4, &radioGroupMapForTrayMenu)) {
|
||||||
|
// Couldn't allocate map
|
||||||
|
Fatal(app, "Not enough memory to allocate radioGroupMapForTrayMenu!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
|
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
|
||||||
// Setup main application struct
|
// Setup main application struct
|
||||||
struct Application *result = malloc(sizeof(struct Application));
|
struct Application *result = malloc(sizeof(struct Application));
|
||||||
@ -548,21 +633,45 @@ void destroyMenu(struct Application *app) {
|
|||||||
json_delete(app->processedMenu);
|
json_delete(app->processedMenu);
|
||||||
app->processedMenu = NULL;
|
app->processedMenu = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the tray menu json if we have it
|
|
||||||
if ( app->trayMenuAsJSON != NULL ) {
|
|
||||||
free((void*)app->trayMenuAsJSON);
|
|
||||||
app->trayMenuAsJSON = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release processed menu
|
|
||||||
if( app->processedTrayMenu != NULL) {
|
|
||||||
json_delete(app->processedTrayMenu);
|
|
||||||
app->processedTrayMenu = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void destroyTray(struct Application *app) {
|
||||||
|
|
||||||
|
// If we don't have a tray, exit!
|
||||||
|
if( app->trayMenuAsJSON == NULL ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free menu item hashmap
|
||||||
|
hashmap_destroy(&menuItemMapForTrayMenu);
|
||||||
|
|
||||||
|
// Free radio group members
|
||||||
|
if( hashmap_num_entries(&radioGroupMapForTrayMenu) > 0 ) {
|
||||||
|
if (0!=hashmap_iterate_pairs(&radioGroupMapForTrayMenu, freeHashmapItem, NULL)) {
|
||||||
|
Fatal(app, "failed to deallocate hashmap entries!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Free radio groups hashmap
|
||||||
|
hashmap_destroy(&radioGroupMapForTrayMenu);
|
||||||
|
|
||||||
|
// Release the menu json if we have it
|
||||||
|
if ( app->trayMenuAsJSON != NULL ) {
|
||||||
|
free((void*)app->trayMenuAsJSON);
|
||||||
|
app->trayMenuAsJSON = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release processed tray
|
||||||
|
if( app->processedTrayMenu != NULL) {
|
||||||
|
json_delete(app->processedTrayMenu);
|
||||||
|
app->processedTrayMenu = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void DestroyApplication(struct Application *app) {
|
void DestroyApplication(struct Application *app) {
|
||||||
Debug(app, "Destroying Application");
|
Debug(app, "Destroying Application");
|
||||||
|
|
||||||
@ -582,8 +691,12 @@ void DestroyApplication(struct Application *app) {
|
|||||||
msg( c("NSEvent"), s("removeMonitor:"), app->mouseUpMonitor);
|
msg( c("NSEvent"), s("removeMonitor:"), app->mouseUpMonitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroy the menu
|
||||||
destroyMenu(app);
|
destroyMenu(app);
|
||||||
|
|
||||||
|
// Destroy the tray
|
||||||
|
destroyTray(app);
|
||||||
|
|
||||||
// Remove script handlers
|
// Remove script handlers
|
||||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
|
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
|
||||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
|
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
|
||||||
@ -1028,11 +1141,12 @@ void createDelegate(struct Application *app) {
|
|||||||
class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
|
class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
|
||||||
|
|
||||||
// Menu Callbacks
|
// Menu Callbacks
|
||||||
class_addMethod(delegateClass, s("menuCallback:"), (IMP)menuItemPressed, "v@:@");
|
class_addMethod(delegateClass, s("menuCallbackForApplicationMenu:"), (IMP)menuItemPressedForApplicationMenu, "v@:@");
|
||||||
class_addMethod(delegateClass, s("checkboxMenuCallbackForApplicationMenu:"), (IMP)checkboxMenuItemPressedForApplicationMenu, "v@:@");
|
class_addMethod(delegateClass, s("checkboxMenuCallbackForApplicationMenu:"), (IMP) checkboxMenuItemPressedForApplicationMenu, "v@:@");
|
||||||
class_addMethod(delegateClass, s("radioMenuCallbackForApplicationMenu:"),
|
class_addMethod(delegateClass, s("radioMenuCallbackForApplicationMenu:"), (IMP) radioMenuItemPressedForApplicationMenu, "v@:@");
|
||||||
(IMP)
|
class_addMethod(delegateClass, s("menuCallbackForTrayMenu:"), (IMP)menuItemPressedForTrayMenu, "v@:@");
|
||||||
radioMenuItemPressedForApplicationMenu, "v@:@");
|
class_addMethod(delegateClass, s("checkboxMenuCallbackForTrayMenu:"), (IMP) checkboxMenuItemPressedForTrayMenu, "v@:@");
|
||||||
|
class_addMethod(delegateClass, s("radioMenuCallbackForTrayMenu:"), (IMP) radioMenuItemPressedForTrayMenu, "v@:@");
|
||||||
|
|
||||||
|
|
||||||
// Script handler
|
// Script handler
|
||||||
@ -1476,14 +1590,14 @@ id processAcceleratorKey(const char *key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
id parseTextMenuItem(struct Application *app, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers) {
|
id parseTextMenuItem(struct Application *app, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char *menuCallback) {
|
||||||
id item = ALLOC("NSMenuItem");
|
id item = ALLOC("NSMenuItem");
|
||||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
|
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
|
||||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||||
|
|
||||||
id key = processAcceleratorKey(acceleratorkey);
|
id key = processAcceleratorKey(acceleratorkey);
|
||||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||||
s("menuCallback:"), key);
|
s(menuCallback), key);
|
||||||
|
|
||||||
msg(item, s("setEnabled:"), !disabled);
|
msg(item, s("setEnabled:"), !disabled);
|
||||||
msg(item, s("autorelease"));
|
msg(item, s("autorelease"));
|
||||||
@ -1542,7 +1656,7 @@ id parseRadioMenuItem(struct Application *app, id parentmenu, const char *title,
|
|||||||
|
|
||||||
void parseMenuItem(struct Application *app, id parentMenu, JsonNode *item,
|
void parseMenuItem(struct Application *app, id parentMenu, JsonNode *item,
|
||||||
struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
|
struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
|
||||||
*radioCallbackFunction) {
|
*radioCallbackFunction, const char *menuCallbackFunction) {
|
||||||
|
|
||||||
// Check if this item is hidden and if so, exit early!
|
// Check if this item is hidden and if so, exit early!
|
||||||
bool hidden = false;
|
bool hidden = false;
|
||||||
@ -1578,8 +1692,7 @@ struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
|
|||||||
JsonNode *item;
|
JsonNode *item;
|
||||||
json_foreach(item, submenu) {
|
json_foreach(item, submenu) {
|
||||||
// Get item label
|
// Get item label
|
||||||
parseMenuItem(app, thisMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction
|
parseMenuItem(app, thisMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -1634,7 +1747,7 @@ struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
|
|||||||
if( type != NULL ) {
|
if( type != NULL ) {
|
||||||
|
|
||||||
if( STREQ(type->string_, "Text")) {
|
if( STREQ(type->string_, "Text")) {
|
||||||
parseTextMenuItem(app, parentMenu, label, menuid, disabled, acceleratorkey, modifiers);
|
parseTextMenuItem(app, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, menuCallbackFunction);
|
||||||
}
|
}
|
||||||
else if ( STREQ(type->string_, "Separator")) {
|
else if ( STREQ(type->string_, "Separator")) {
|
||||||
addSeparator(parentMenu);
|
addSeparator(parentMenu);
|
||||||
@ -1644,16 +1757,14 @@ struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
|
|||||||
bool checked = false;
|
bool checked = false;
|
||||||
getJSONBool(item, "Checked", &checked);
|
getJSONBool(item, "Checked", &checked);
|
||||||
|
|
||||||
parseCheckboxMenuItem(app, parentMenu, label, menuid, disabled, checked,
|
parseCheckboxMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, checkboxCallbackFunction);
|
||||||
"", menuItemMap, checkboxCallbackFunction);
|
|
||||||
}
|
}
|
||||||
else if ( STREQ(type->string_, "Radio")) {
|
else if ( STREQ(type->string_, "Radio")) {
|
||||||
// Get checked state
|
// Get checked state
|
||||||
bool checked = false;
|
bool checked = false;
|
||||||
getJSONBool(item, "Checked", &checked);
|
getJSONBool(item, "Checked", &checked);
|
||||||
|
|
||||||
parseRadioMenuItem(app, parentMenu, label, menuid, disabled, checked, "",
|
parseRadioMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, radioCallbackFunction);
|
||||||
menuItemMap, radioCallbackFunction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( modifiers != NULL ) {
|
if ( modifiers != NULL ) {
|
||||||
@ -1664,8 +1775,7 @@ struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void parseMenu(struct Application *app, id parentMenu, JsonNode *menu, struct
|
void parseMenu(struct Application *app, id parentMenu, JsonNode *menu, struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char *radioCallbackFunction, const char *menuCallbackFunction) {
|
||||||
hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char *radioCallbackFunction) {
|
|
||||||
JsonNode *items = json_find_member(menu, "Items");
|
JsonNode *items = json_find_member(menu, "Items");
|
||||||
if( items == NULL ) {
|
if( items == NULL ) {
|
||||||
// Parse error!
|
// Parse error!
|
||||||
@ -1677,8 +1787,7 @@ hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char *radioC
|
|||||||
JsonNode *item;
|
JsonNode *item;
|
||||||
json_foreach(item, items) {
|
json_foreach(item, items) {
|
||||||
// Get item label
|
// Get item label
|
||||||
parseMenuItem(app, parentMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction
|
parseMenuItem(app, parentMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1764,7 +1873,7 @@ void parseMenuData(struct Application *app) {
|
|||||||
|
|
||||||
|
|
||||||
parseMenu(app, menubar, menuData, &menuItemMapForApplicationMenu,
|
parseMenu(app, menubar, menuData, &menuItemMapForApplicationMenu,
|
||||||
"checkboxMenuCallbackForApplicationMenu:", "radioMenuCallbackForApplicationMenu:");
|
"checkboxMenuCallbackForApplicationMenu:", "radioMenuCallbackForApplicationMenu:", "menuCallbackForApplicationMenu:");
|
||||||
|
|
||||||
// Create the radiogroup cache
|
// Create the radiogroup cache
|
||||||
JsonNode *radioGroups = json_find_member(app->processedMenu, "RadioGroups");
|
JsonNode *radioGroups = json_find_member(app->processedMenu, "RadioGroups");
|
||||||
@ -1807,17 +1916,64 @@ void UpdateMenu(struct Application *app, const char *menuAsJSON) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void parseTrayData(struct Application *app) {
|
void parseTrayData(struct Application *app) {
|
||||||
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"));
|
|
||||||
|
|
||||||
// msg(statusBarButton, s("setImage:"),
|
// msg(statusBarButton, s("setImage:"),
|
||||||
// msg(c("NSImage"), s("imageNamed:"),
|
// msg(c("NSImage"), s("imageNamed:"),
|
||||||
// msg(c("NSString"), s("stringWithUTF8String:"), tray->icon)));
|
// msg(c("NSString"), s("stringWithUTF8String:"), tray->icon)));
|
||||||
|
|
||||||
|
|
||||||
msg(statusItem, s("setMenu:"), NULL);
|
// 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"));
|
||||||
|
|
||||||
|
Debug(app, ">>>>>>>>>>> TRAY MENU: %s", app->trayMenuAsJSON);
|
||||||
|
|
||||||
|
// Parse the processed menu json
|
||||||
|
app->processedTrayMenu = json_decode(app->trayMenuAsJSON);
|
||||||
|
|
||||||
|
if( app->processedTrayMenu == NULL ) {
|
||||||
|
// Parse error!
|
||||||
|
Fatal(app, "Unable to parse Tray JSON: %s", app->trayMenuAsJSON);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pull out the Menu
|
||||||
|
JsonNode *trayMenuData = json_find_member(app->processedTrayMenu, "Menu");
|
||||||
|
if( trayMenuData == NULL ) {
|
||||||
|
// Parse error!
|
||||||
|
Fatal(app, "Unable to find Menu data: %s", app->processedTrayMenu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
parseMenu(app, traymenu, trayMenuData, &menuItemMapForTrayMenu,
|
||||||
|
"checkboxMenuCallbackForTrayMenu:", "radioMenuCallbackForTrayMenu:", "menuCallbackForTrayMenu:");
|
||||||
|
|
||||||
|
// Create the radiogroup cache
|
||||||
|
JsonNode *radioGroups = json_find_member(app->processedTrayMenu, "RadioGroups");
|
||||||
|
if( radioGroups == NULL ) {
|
||||||
|
// Parse error!
|
||||||
|
Fatal(app, "Unable to find RadioGroups data: %s", app->processedTrayMenu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate radio groups
|
||||||
|
JsonNode *radioGroup;
|
||||||
|
json_foreach(radioGroup, radioGroups) {
|
||||||
|
// Get item label
|
||||||
|
processRadioGroup(radioGroup, &menuItemMapForTrayMenu, &radioGroupMapForTrayMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg(statusItem, s("setMenu:"), traymenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1987,9 +2143,13 @@ void Run(struct Application *app, int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we have a tray menu, process it
|
// If we have a tray menu, process it
|
||||||
|
printf
|
||||||
|
("\n\n\n*****************************************************************************************************************************************************************************************************************\n\n\n");
|
||||||
if( app->trayMenuAsJSON != NULL ) {
|
if( app->trayMenuAsJSON != NULL ) {
|
||||||
parseTrayData(app);
|
parseTrayData(app);
|
||||||
}
|
}
|
||||||
|
printf
|
||||||
|
("\n\n\n*****************************************************************************************************************************************************************************************************************\n\n\n");
|
||||||
|
|
||||||
// Finally call run
|
// Finally call run
|
||||||
Debug(app, "Run called");
|
Debug(app, "Run called");
|
||||||
|
@ -15,6 +15,7 @@ extern void SetAppearance(void *, const char *);
|
|||||||
extern void WebviewIsTransparent(void *);
|
extern void WebviewIsTransparent(void *);
|
||||||
extern void SetWindowBackgroundIsTranslucent(void *);
|
extern void SetWindowBackgroundIsTranslucent(void *);
|
||||||
extern void SetMenu(void *, const char *);
|
extern void SetMenu(void *, const char *);
|
||||||
|
extern void SetTray(void *, const char *);
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
@ -79,27 +80,33 @@ func (a *Application) processPlatformSettings() error {
|
|||||||
We keep a record of every radio group member we discover by saving
|
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
|
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).
|
in the group (this last one is for optimisation at the C layer).
|
||||||
|
|
||||||
Example:
|
|
||||||
{
|
|
||||||
"RadioGroups": [
|
|
||||||
{
|
|
||||||
"Members": [
|
|
||||||
"option-1",
|
|
||||||
"option-2",
|
|
||||||
"option-3"
|
|
||||||
],
|
|
||||||
"Length": 3
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
processedMenu := NewProcessedMenu(mac.Menu)
|
processedMenu := NewProcessedMenu(mac.Menu)
|
||||||
menuJSON, err := json.Marshal(processedMenu)
|
applicationMenuJSON, err := json.Marshal(processedMenu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
C.SetMenu(a.app, a.string2CString(string(menuJSON)))
|
C.SetMenu(a.app, a.string2CString(string(applicationMenuJSON)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process tray
|
||||||
|
if mac.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(mac.Tray)
|
||||||
|
trayMenuJSON, err := json.Marshal(processedMenu)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
C.SetTray(a.app, a.string2CString(string(trayMenuJSON)))
|
||||||
|
println("******************** SET TRAY!!!!! &&&&&&&&&&&&&&&&&&&&&&&&&&")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
186
v2/internal/subsystem/tray.go
Normal file
186
v2/internal/subsystem/tray.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package subsystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tray is the subsystem that handles the operation of the tray menu.
|
||||||
|
// It manages all service bus messages starting with "tray".
|
||||||
|
type Tray struct {
|
||||||
|
quitChannel <-chan *servicebus.Message
|
||||||
|
trayChannel <-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
|
||||||
|
|
||||||
|
// The tray menu
|
||||||
|
trayMenu *menu.Menu
|
||||||
|
|
||||||
|
// Service Bus
|
||||||
|
bus *servicebus.ServiceBus
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTray creates a new menu subsystem
|
||||||
|
func NewTray(trayMenu *menu.Menu, bus *servicebus.ServiceBus,
|
||||||
|
logger *logger.Logger) (*Tray, error) {
|
||||||
|
|
||||||
|
// Register quit channel
|
||||||
|
quitChannel, err := bus.Subscribe("quit")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to menu messages
|
||||||
|
trayChannel, err := bus.Subscribe("tray:")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &Tray{
|
||||||
|
quitChannel: quitChannel,
|
||||||
|
trayChannel: trayChannel,
|
||||||
|
logger: logger.CustomLogger("Tray Subsystem"),
|
||||||
|
listeners: make(map[string][]func(*menu.MenuItem)),
|
||||||
|
menuItems: make(map[string]*menu.MenuItem),
|
||||||
|
trayMenu: trayMenu,
|
||||||
|
bus: bus,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up list of item/id pairs
|
||||||
|
result.processMenu(trayMenu)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the subsystem
|
||||||
|
func (t *Tray) Start() error {
|
||||||
|
|
||||||
|
t.logger.Trace("Starting")
|
||||||
|
|
||||||
|
t.running = true
|
||||||
|
|
||||||
|
// Spin off a go routine
|
||||||
|
go func() {
|
||||||
|
for t.running {
|
||||||
|
select {
|
||||||
|
case <-t.quitChannel:
|
||||||
|
t.running = false
|
||||||
|
break
|
||||||
|
case menuMessage := <-t.trayChannel:
|
||||||
|
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
||||||
|
menuMessageType := splitTopic[1]
|
||||||
|
switch menuMessageType {
|
||||||
|
case "clicked":
|
||||||
|
if len(splitTopic) != 2 {
|
||||||
|
t.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.logger.Trace("Got Menu clicked Message: %s %+v", menuMessage.Topic(), menuMessage.Data())
|
||||||
|
menuid := menuMessage.Data().(string)
|
||||||
|
|
||||||
|
// Get the menu item
|
||||||
|
menuItem := t.menuItems[menuid]
|
||||||
|
if menuItem == nil {
|
||||||
|
t.logger.Trace("Cannot process menuid %s - unknown", menuid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is the menu item a checkbox?
|
||||||
|
if menuItem.Type == menu.CheckboxType {
|
||||||
|
// Toggle state
|
||||||
|
menuItem.Checked = !menuItem.Checked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify listeners
|
||||||
|
t.notifyListeners(menuid, menuItem)
|
||||||
|
case "on":
|
||||||
|
listenerDetails := menuMessage.Data().(*message.MenuOnMessage)
|
||||||
|
id := listenerDetails.MenuID
|
||||||
|
t.listeners[id] = append(t.listeners[id], listenerDetails.Callback)
|
||||||
|
|
||||||
|
// Make sure we catch any menu updates
|
||||||
|
case "update":
|
||||||
|
updatedMenu := menuMessage.Data().(*menu.Menu)
|
||||||
|
t.processMenu(updatedMenu)
|
||||||
|
|
||||||
|
// Notify frontend of menu change
|
||||||
|
t.bus.Publish("trayfrontend:update", updatedMenu)
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.logger.Error("unknown tray message: %+v", menuMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call shutdown
|
||||||
|
t.shutdown()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tray) processMenu(trayMenu *menu.Menu) {
|
||||||
|
// Initialise the variables
|
||||||
|
t.menuItems = make(map[string]*menu.MenuItem)
|
||||||
|
t.trayMenu = trayMenu
|
||||||
|
|
||||||
|
for _, item := range trayMenu.Items {
|
||||||
|
t.processMenuItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tray) processMenuItem(item *menu.MenuItem) {
|
||||||
|
|
||||||
|
if item.SubMenu != nil {
|
||||||
|
for _, submenuitem := range item.SubMenu {
|
||||||
|
t.processMenuItem(submenuitem)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.ID != "" {
|
||||||
|
if t.menuItems[item.ID] != nil {
|
||||||
|
t.logger.Error("Menu id '%s' is used by multiple menu items: %s %s", t.menuItems[item.ID].Label, item.Label)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.menuItems[item.ID] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifies listeners that the given menu was clicked
|
||||||
|
func (t *Tray) notifyListeners(menuid string, menuItem *menu.MenuItem) {
|
||||||
|
|
||||||
|
// Get list of menu listeners
|
||||||
|
listeners := t.listeners[menuid]
|
||||||
|
if listeners == nil {
|
||||||
|
t.logger.Trace("No listeners for MenuItem with ID '%s'", menuid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock the listeners
|
||||||
|
t.notifyLock.Lock()
|
||||||
|
|
||||||
|
// Callback in goroutine
|
||||||
|
for _, listener := range listeners {
|
||||||
|
go listener(menuItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock
|
||||||
|
t.notifyLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tray) shutdown() {
|
||||||
|
t.logger.Trace("Shutdown")
|
||||||
|
}
|
@ -9,4 +9,5 @@ type Options struct {
|
|||||||
WebviewIsTransparent bool
|
WebviewIsTransparent bool
|
||||||
WindowBackgroundIsTranslucent bool
|
WindowBackgroundIsTranslucent bool
|
||||||
Menu *menu.Menu
|
Menu *menu.Menu
|
||||||
|
Tray *menu.Menu
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
wails "github.com/wailsapp/wails/v2"
|
"github.com/wailsapp/wails/v2"
|
||||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||||
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -22,6 +23,7 @@ func main() {
|
|||||||
// Comment out line below to see Window.SetTitle() work
|
// Comment out line below to see Window.SetTitle() work
|
||||||
TitleBar: mac.TitleBarHiddenInset(),
|
TitleBar: mac.TitleBarHiddenInset(),
|
||||||
Menu: createApplicationMenu(),
|
Menu: createApplicationMenu(),
|
||||||
|
Tray: createApplicationTray(),
|
||||||
},
|
},
|
||||||
LogLevel: logger.TRACE,
|
LogLevel: logger.TRACE,
|
||||||
})
|
})
|
||||||
@ -34,5 +36,8 @@ func main() {
|
|||||||
app.Bind(&Window{})
|
app.Bind(&Window{})
|
||||||
app.Bind(&Menu{})
|
app.Bind(&Menu{})
|
||||||
|
|
||||||
app.Run()
|
err := app.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
wails "github.com/wailsapp/wails/v2"
|
"github.com/wailsapp/wails/v2"
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -213,6 +213,12 @@ func (m *Menu) insertAfterRandom(_ *menu.MenuItem) {
|
|||||||
m.runtime.Menu.Update()
|
m.runtime.Menu.Update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createApplicationTray() *menu.Menu {
|
||||||
|
trayMenu := &menu.Menu{}
|
||||||
|
trayMenu.Append(menu.Text("Hello from the tray!", "hi"))
|
||||||
|
return trayMenu
|
||||||
|
}
|
||||||
|
|
||||||
func createApplicationMenu() *menu.Menu {
|
func createApplicationMenu() *menu.Menu {
|
||||||
|
|
||||||
// Create menu
|
// Create menu
|
||||||
|
Loading…
Reference in New Issue
Block a user