mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-04 03:29:03 +08:00
Support context menu data
Support StartHidden
This commit is contained in:
parent
34ac62e4ac
commit
a8995c5377
@ -25,7 +25,7 @@ type App struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateApp returns a null application
|
// CreateApp returns a null application
|
||||||
func CreateApp(options *options.App) *App {
|
func CreateApp(_ *options.App) *App {
|
||||||
return &App{}
|
return &App{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +37,5 @@ func (a *App) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bind the dummy interface
|
// Bind the dummy interface
|
||||||
func (a *App) Bind(dummy interface{}) error {
|
func (a *App) Bind(_ interface{}) {
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ type App struct {
|
|||||||
call *subsystem.Call
|
call *subsystem.Call
|
||||||
menu *subsystem.Menu
|
menu *subsystem.Menu
|
||||||
tray *subsystem.Tray
|
tray *subsystem.Tray
|
||||||
|
contextmenus *subsystem.ContextMenus
|
||||||
dispatcher *messagedispatcher.Dispatcher
|
dispatcher *messagedispatcher.Dispatcher
|
||||||
|
|
||||||
// Indicates if the app is in debug mode
|
// Indicates if the app is in debug mode
|
||||||
@ -94,8 +95,9 @@ func (a *App) Run() error {
|
|||||||
// Start the runtime
|
// Start the runtime
|
||||||
applicationMenu := options.GetApplicationMenu(a.options)
|
applicationMenu := options.GetApplicationMenu(a.options)
|
||||||
trayMenu := options.GetTrayMenu(a.options)
|
trayMenu := options.GetTrayMenu(a.options)
|
||||||
|
contextMenus := options.GetContextMenus(a.options)
|
||||||
|
|
||||||
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, applicationMenu, trayMenu)
|
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, applicationMenu, trayMenu, contextMenus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -179,6 +181,19 @@ func (a *App) Run() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optionally start the context menu subsystem
|
||||||
|
if contextMenus != nil {
|
||||||
|
contextmenussubsystem, err := subsystem.NewContextMenus(contextMenus, a.servicebus, a.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.contextmenus = contextmenussubsystem
|
||||||
|
err = a.contextmenus.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -34,4 +34,6 @@ extern void SaveDialog(void *appPointer, char *callbackID, char *title, char *fi
|
|||||||
extern void DarkModeEnabled(void *appPointer, char *callbackID);
|
extern void DarkModeEnabled(void *appPointer, char *callbackID);
|
||||||
extern void UpdateMenu(void *app, char *menuAsJSON);
|
extern void UpdateMenu(void *app, char *menuAsJSON);
|
||||||
extern void UpdateTray(void *app, char *menuAsJSON);
|
extern void UpdateTray(void *app, char *menuAsJSON);
|
||||||
|
extern void UpdateContextMenus(void *app, char *contextMenusAsJSON);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -13,6 +13,7 @@ import "C"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@ -188,3 +189,19 @@ func (c *Client) UpdateTray(menu *menu.Menu) {
|
|||||||
}
|
}
|
||||||
C.UpdateTray(c.app.app, c.app.string2CString(string(trayMenuJSON)))
|
C.UpdateTray(c.app.app, c.app.string2CString(string(trayMenuJSON)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateContextMenus(contextMenus *menu.ContextMenus) {
|
||||||
|
|
||||||
|
// Guard against nil contextMenus
|
||||||
|
if contextMenus == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Process the menu
|
||||||
|
contextMenusJSON, err := json.Marshal(contextMenus)
|
||||||
|
fmt.Printf("\n\nUPDATED CONTEXT MENUS:\n %+v\n\n", string(contextMenusJSON))
|
||||||
|
if err != nil {
|
||||||
|
c.app.logger.Error("Error processing updated Context Menus: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
C.UpdateContextMenus(c.app.app, c.app.string2CString(string(contextMenusJSON)))
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#define STREQ(a,b) strcmp(a, b) == 0
|
#define STREQ(a,b) strcmp(a, b) == 0
|
||||||
#define STRCOPY(a) concat(a, "")
|
#define STRCOPY(a) concat(a, "")
|
||||||
|
#define MEMFREE(input) free((void*)input); input = NULL;
|
||||||
|
|
||||||
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
|
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
|
||||||
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
|
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
|
||||||
@ -107,6 +108,10 @@ struct hashmap_s menuItemMapForContextMenus;
|
|||||||
// RadioGroup map for the context menus. Maps a menuitem id with its associated radio group items
|
// RadioGroup map for the context menus. Maps a menuitem id with its associated radio group items
|
||||||
struct hashmap_s radioGroupMapForContextMenus;
|
struct hashmap_s radioGroupMapForContextMenus;
|
||||||
|
|
||||||
|
// Context menu data is given by the frontend when clicking a context menu.
|
||||||
|
// We send this to the backend when an item is selected;
|
||||||
|
const char *contextMenuData;
|
||||||
|
|
||||||
// Dispatch Method
|
// Dispatch Method
|
||||||
typedef void (^dispatchMethod)(void);
|
typedef void (^dispatchMethod)(void);
|
||||||
|
|
||||||
@ -253,7 +258,7 @@ void Debug(struct Application *app, const char *message, ... ) {
|
|||||||
va_start(args, message);
|
va_start(args, message);
|
||||||
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
|
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
|
||||||
app->sendMessageToBackend(&logbuffer[0]);
|
app->sendMessageToBackend(&logbuffer[0]);
|
||||||
free((void*)temp);
|
MEMFREE(temp);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,7 +269,7 @@ void Fatal(struct Application *app, const char *message, ... ) {
|
|||||||
va_start(args, message);
|
va_start(args, message);
|
||||||
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
|
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
|
||||||
app->sendMessageToBackend(&logbuffer[0]);
|
app->sendMessageToBackend(&logbuffer[0]);
|
||||||
free((void*)temp);
|
MEMFREE(temp);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,21 +328,33 @@ void showContextMenu(struct Application *app, const char *contextMenuID) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf("contextMenuID = %s\n", contextMenuID);
|
||||||
ON_MAIN_THREAD (
|
|
||||||
|
|
||||||
// Look for the context menu for this ID
|
// Look for the context menu for this ID
|
||||||
id contextMenu = (id)hashmap_get(&contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
|
id contextMenu = (id)hashmap_get(&contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
|
||||||
|
|
||||||
|
printf("CONTEXT MENU = %p\n", contextMenu);
|
||||||
|
|
||||||
|
// Free menu id
|
||||||
|
MEMFREE(contextMenuID);
|
||||||
|
|
||||||
|
if( contextMenu == NULL ) {
|
||||||
|
printf("\n\n\n\n\n\n\n");
|
||||||
|
dumpHashmap("contextMenuMap", &contextMenuMap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Grab the content view and show the menu
|
// Grab the content view and show the menu
|
||||||
id contentView = msg(app->mainWindow, s("contentView"));
|
id contentView = msg(app->mainWindow, s("contentView"));
|
||||||
|
printf("contentView = %p\n", contentView);
|
||||||
|
|
||||||
// Get the triggering event
|
// Get the triggering event
|
||||||
id menuEvent = msg(app->mainWindow, s("currentEvent"));
|
id menuEvent = msg(app->mainWindow, s("currentEvent"));
|
||||||
|
printf("menuEvent = %p\n", menuEvent);
|
||||||
|
|
||||||
// Show popup
|
// Show popup
|
||||||
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu, menuEvent, contentView);
|
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu, menuEvent, contentView);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetColour(struct Application *app, int red, int green, int blue, int alpha) {
|
void SetColour(struct Application *app, int red, int green, int blue, int alpha) {
|
||||||
@ -378,7 +395,9 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
|||||||
if( strcmp(name, "completed") == 0) {
|
if( strcmp(name, "completed") == 0) {
|
||||||
// Delete handler
|
// Delete handler
|
||||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
|
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
|
||||||
|
if (app->startHidden == 0) {
|
||||||
Show(app);
|
Show(app);
|
||||||
|
}
|
||||||
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
|
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
|
||||||
} else if( strcmp(name, "windowDrag") == 0 ) {
|
} else if( strcmp(name, "windowDrag") == 0 ) {
|
||||||
// Guard against null events
|
// Guard against null events
|
||||||
@ -397,6 +416,11 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
|||||||
|
|
||||||
const char *contextMenuMessage = cstr(msg(message, s("body")));
|
const char *contextMenuMessage = cstr(msg(message, s("body")));
|
||||||
|
|
||||||
|
if( contextMenuMessage == NULL ) {
|
||||||
|
printf("EMPTY CONTEXT MENU MESSAGE!!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the message
|
// Parse the message
|
||||||
JsonNode *contextMenuMessageJSON = json_decode(contextMenuMessage);
|
JsonNode *contextMenuMessageJSON = json_decode(contextMenuMessage);
|
||||||
if( contextMenuMessageJSON == NULL ) {
|
if( contextMenuMessageJSON == NULL ) {
|
||||||
@ -414,31 +438,23 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
|||||||
Debug(app, "Error decoding context menu ID (Not a string): %s", contextMenuMessage);
|
Debug(app, "Error decoding context menu ID (Not a string): %s", contextMenuMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: Use the X & Y coordinates of the menu to programmatically open
|
|
||||||
// the context menu at that point rather than relying on the current NSEvent.
|
|
||||||
// I got it mostly working (IE not crashing) but the menu was invisible...
|
|
||||||
// Revisit later
|
|
||||||
|
|
||||||
// // Get menu X
|
// Get menu Data
|
||||||
// JsonNode *contextMenuXNode = json_find_member(contextMenuMessageJSON, "x");
|
JsonNode *contextMenuDataNode = json_find_member(contextMenuMessageJSON, "data");
|
||||||
// if( contextMenuXNode == NULL ) {
|
if( contextMenuDataNode == NULL ) {
|
||||||
// Debug(app, "Error decoding context menu X: %s", contextMenuMessage);
|
Debug(app, "Error decoding context menu data: %s", contextMenuMessage);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// if( contextMenuXNode->tag != JSON_NUMBER ) {
|
if( contextMenuDataNode->tag != JSON_STRING ) {
|
||||||
// Debug(app, "Error decoding context menu X (Not a number): %s", contextMenuMessage);
|
Debug(app, "Error decoding context menu data (Not a string): %s", contextMenuMessage);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// // Get menu Y
|
|
||||||
// JsonNode *contextMenuYNode = json_find_member(contextMenuMessageJSON, "y");
|
// Save a copy of the context menu data
|
||||||
// if( contextMenuYNode == NULL ) {
|
if ( contextMenuData != NULL ) {
|
||||||
// Debug(app, "Error decoding context menu Y: %s", contextMenuMessage);
|
MEMFREE(contextMenuData);
|
||||||
// return;
|
}
|
||||||
// }
|
contextMenuData = STRCOPY(contextMenuDataNode->string_);
|
||||||
// if( contextMenuYNode->tag != JSON_NUMBER ) {
|
|
||||||
// Debug(app, "Error decoding context menu Y (Not a number): %s", contextMenuMessage);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ON_MAIN_THREAD(
|
ON_MAIN_THREAD(
|
||||||
showContextMenu(app, contextMenuIDNode->string_);
|
showContextMenu(app, contextMenuIDNode->string_);
|
||||||
@ -451,40 +467,52 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a JSON message for the given menuItemID and data
|
||||||
|
const char* createContextMenuMessage(const char *menuItemID, const char *contextMenuData) {
|
||||||
|
JsonNode *jsonObject = json_mkobject();
|
||||||
|
json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID));
|
||||||
|
json_append_member(jsonObject, "data", json_mkstring(contextMenuData));
|
||||||
|
const char *result = json_encode(jsonObject);
|
||||||
|
json_delete(jsonObject);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Callback for menu items
|
// Callback for menu items
|
||||||
void menuItemPressedForApplicationMenu(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 *menuItemID = (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", menuItemID);
|
||||||
messageFromWindowCallback(message);
|
messageFromWindowCallback(message);
|
||||||
free((void*)message);
|
MEMFREE(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback for tray items
|
// Callback for tray items
|
||||||
void menuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
void menuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
||||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||||
// Notify the backend
|
// Notify the backend
|
||||||
const char *message = concat("TC", menuID);
|
const char *message = concat("TC", menuItemID);
|
||||||
messageFromWindowCallback(message);
|
messageFromWindowCallback(message);
|
||||||
free((void*)message);
|
MEMFREE(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Callback for context menu items
|
// Callback for context menu items
|
||||||
void menuItemPressedForContextMenus(id self, SEL cmd, id sender) {
|
void menuItemPressedForContextMenus(id self, SEL cmd, id sender) {
|
||||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
const char *menuItemID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||||
// Notify the backend
|
// Notify the backend
|
||||||
const char *message = concat("XC", menuID);
|
const char *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData);
|
||||||
|
const char *message = concat("XC", contextMenuMessage);
|
||||||
messageFromWindowCallback(message);
|
messageFromWindowCallback(message);
|
||||||
free((void*)message);
|
MEMFREE(message);
|
||||||
|
MEMFREE(contextMenuMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback for menu items
|
// Callback for menu items
|
||||||
void checkboxMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
void checkboxMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
||||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
const char *menuItemID = (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
|
||||||
id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuID, strlen(menuID));
|
id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID));
|
||||||
|
|
||||||
// Get the current state
|
// Get the current state
|
||||||
bool state = msg(menuItem, s("state"));
|
bool state = msg(menuItem, s("state"));
|
||||||
@ -493,17 +521,17 @@ void checkboxMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender, stru
|
|||||||
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||||
|
|
||||||
// Notify the backend
|
// Notify the backend
|
||||||
const char *message = concat("MC", menuID);
|
const char *message = concat("MC", menuItemID);
|
||||||
messageFromWindowCallback(message);
|
messageFromWindowCallback(message);
|
||||||
free((void*)message);
|
MEMFREE(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback for tray menu items
|
// Callback for tray menu items
|
||||||
void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
||||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
const char *menuItemID = (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
|
||||||
id menuItem = (id)hashmap_get(&menuItemMapForTrayMenu, (char*)menuID, strlen(menuID));
|
id menuItem = (id)hashmap_get(&menuItemMapForTrayMenu, (char*)menuItemID, strlen(menuItemID));
|
||||||
|
|
||||||
// Get the current state
|
// Get the current state
|
||||||
bool state = msg(menuItem, s("state"));
|
bool state = msg(menuItem, s("state"));
|
||||||
@ -512,17 +540,17 @@ void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hash
|
|||||||
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||||
|
|
||||||
// Notify the backend
|
// Notify the backend
|
||||||
const char *message = concat("TC", menuID);
|
const char *message = concat("TC", menuItemID);
|
||||||
messageFromWindowCallback(message);
|
messageFromWindowCallback(message);
|
||||||
free((void*)message);
|
MEMFREE(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback for context menu items
|
// Callback for context menu items
|
||||||
void checkboxMenuItemPressedForContextMenus(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
void checkboxMenuItemPressedForContextMenus(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
|
||||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
const char *menuItemID = (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
|
||||||
id menuItem = (id)hashmap_get(&menuItemMapForContextMenus, (char*)menuID, strlen(menuID));
|
id menuItem = (id)hashmap_get(&menuItemMapForContextMenus, (char*)menuItemID, strlen(menuItemID));
|
||||||
|
|
||||||
// Get the current state
|
// Get the current state
|
||||||
bool state = msg(menuItem, s("state"));
|
bool state = msg(menuItem, s("state"));
|
||||||
@ -531,17 +559,19 @@ void checkboxMenuItemPressedForContextMenus(id self, SEL cmd, id sender, struct
|
|||||||
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||||
|
|
||||||
// Notify the backend
|
// Notify the backend
|
||||||
const char *message = concat("XC", menuID);
|
const char *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData);
|
||||||
|
const char *message = concat("XC", contextMenuMessage);
|
||||||
messageFromWindowCallback(message);
|
messageFromWindowCallback(message);
|
||||||
free((void*)message);
|
MEMFREE(message);
|
||||||
|
MEMFREE(contextMenuMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// radioMenuItemPressedForApplicationMenu
|
// 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 *menuItemID = (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
|
||||||
id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuID, strlen(menuID));
|
id menuItem = (id)hashmap_get(&menuItemMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID));
|
||||||
|
|
||||||
// Check the menu items' current state
|
// Check the menu items' current state
|
||||||
bool selected = msg(menuItem, s("state"));
|
bool selected = msg(menuItem, s("state"));
|
||||||
@ -552,7 +582,7 @@ void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get this item's radio group members and turn them off
|
// Get this item's radio group members and turn them off
|
||||||
id *members = (id*)hashmap_get(&radioGroupMapForApplicationMenu, (char*)menuID, strlen(menuID));
|
id *members = (id*)hashmap_get(&radioGroupMapForApplicationMenu, (char*)menuItemID, strlen(menuItemID));
|
||||||
|
|
||||||
// Uncheck all members of the group
|
// Uncheck all members of the group
|
||||||
id thisMember = members[0];
|
id thisMember = members[0];
|
||||||
@ -567,18 +597,18 @@ void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
|
|||||||
msg(menuItem, s("setState:"), NSControlStateValueOn);
|
msg(menuItem, s("setState:"), NSControlStateValueOn);
|
||||||
|
|
||||||
// Notify the backend
|
// Notify the backend
|
||||||
const char *message = concat("MC", menuID);
|
const char *message = concat("MC", menuItemID);
|
||||||
messageFromWindowCallback(message);
|
messageFromWindowCallback(message);
|
||||||
free((void*)message);
|
MEMFREE(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// radioMenuItemPressedForTrayMenu
|
// radioMenuItemPressedForTrayMenu
|
||||||
void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
||||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
const char *menuItemID = (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
|
||||||
id menuItem = (id)hashmap_get(&menuItemMapForTrayMenu, (char*)menuID, strlen(menuID));
|
id menuItem = (id)hashmap_get(&menuItemMapForTrayMenu, (char*)menuItemID, strlen(menuItemID));
|
||||||
|
|
||||||
// Check the menu items' current state
|
// Check the menu items' current state
|
||||||
bool selected = msg(menuItem, s("state"));
|
bool selected = msg(menuItem, s("state"));
|
||||||
@ -589,7 +619,7 @@ void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get this item's radio group members and turn them off
|
// Get this item's radio group members and turn them off
|
||||||
id *members = (id*)hashmap_get(&radioGroupMapForTrayMenu, (char*)menuID, strlen(menuID));
|
id *members = (id*)hashmap_get(&radioGroupMapForTrayMenu, (char*)menuItemID, strlen(menuItemID));
|
||||||
|
|
||||||
// Uncheck all members of the group
|
// Uncheck all members of the group
|
||||||
id thisMember = members[0];
|
id thisMember = members[0];
|
||||||
@ -604,17 +634,17 @@ void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
|
|||||||
msg(menuItem, s("setState:"), NSControlStateValueOn);
|
msg(menuItem, s("setState:"), NSControlStateValueOn);
|
||||||
|
|
||||||
// Notify the backend
|
// Notify the backend
|
||||||
const char *message = concat("TC", menuID);
|
const char *message = concat("TC", menuItemID);
|
||||||
messageFromWindowCallback(message);
|
messageFromWindowCallback(message);
|
||||||
free((void*)message);
|
MEMFREE(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// radioMenuItemPressedForContextMenus
|
// radioMenuItemPressedForContextMenus
|
||||||
void radioMenuItemPressedForContextMenus(id self, SEL cmd, id sender) {
|
void radioMenuItemPressedForContextMenus(id self, SEL cmd, id sender) {
|
||||||
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
const char *menuItemID = (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
|
||||||
id menuItem = (id)hashmap_get(&menuItemMapForContextMenus, (char*)menuID, strlen(menuID));
|
id menuItem = (id)hashmap_get(&menuItemMapForContextMenus, (char*)menuItemID, strlen(menuItemID));
|
||||||
|
|
||||||
// Check the menu items' current state
|
// Check the menu items' current state
|
||||||
bool selected = msg(menuItem, s("state"));
|
bool selected = msg(menuItem, s("state"));
|
||||||
@ -625,7 +655,7 @@ void radioMenuItemPressedForContextMenus(id self, SEL cmd, id sender) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get this item's radio group members and turn them off
|
// Get this item's radio group members and turn them off
|
||||||
id *members = (id*)hashmap_get(&radioGroupMapForContextMenus, (char*)menuID, strlen(menuID));
|
id *members = (id*)hashmap_get(&radioGroupMapForContextMenus, (char*)menuItemID, strlen(menuItemID));
|
||||||
|
|
||||||
// Uncheck all members of the group
|
// Uncheck all members of the group
|
||||||
id thisMember = members[0];
|
id thisMember = members[0];
|
||||||
@ -640,9 +670,11 @@ void radioMenuItemPressedForContextMenus(id self, SEL cmd, id sender) {
|
|||||||
msg(menuItem, s("setState:"), NSControlStateValueOn);
|
msg(menuItem, s("setState:"), NSControlStateValueOn);
|
||||||
|
|
||||||
// Notify the backend
|
// Notify the backend
|
||||||
const char *message = concat("XC", menuID);
|
const char *contextMenuMessage = createContextMenuMessage(menuItemID, contextMenuData);
|
||||||
|
const char *message = concat("XC", contextMenuMessage);
|
||||||
messageFromWindowCallback(message);
|
messageFromWindowCallback(message);
|
||||||
free((void*)message);
|
MEMFREE(message);
|
||||||
|
MEMFREE(contextMenuMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeWindow is called when the close button is pressed
|
// closeWindow is called when the close button is pressed
|
||||||
@ -651,6 +683,11 @@ void closeWindow(id self, SEL cmd, id sender) {
|
|||||||
app->sendMessageToBackend("WC");
|
app->sendMessageToBackend("WC");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void willFinishLaunching(id self, SEL cmd, id sender) {
|
||||||
|
struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
|
||||||
|
printf("\n\n\n\n\n\n\n\n\n\n\n\nI AM HERE!!!!!!!\n\n\n\n\n\n\n\n\n\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
bool isDarkMode(struct Application *app) {
|
bool isDarkMode(struct Application *app) {
|
||||||
id userDefaults = msg(c("NSUserDefaults"), s("standardUserDefaults"));
|
id userDefaults = msg(c("NSUserDefaults"), s("standardUserDefaults"));
|
||||||
const char *mode = cstr(msg(userDefaults, s("stringForKey:"), str("AppleInterfaceStyle")));
|
const char *mode = cstr(msg(userDefaults, s("stringForKey:"), str("AppleInterfaceStyle")));
|
||||||
@ -785,6 +822,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
|||||||
|
|
||||||
// Context Menus
|
// Context Menus
|
||||||
result->contextMenusAsJSON = NULL;
|
result->contextMenusAsJSON = NULL;
|
||||||
|
contextMenuData = NULL;
|
||||||
|
|
||||||
// Window Appearance
|
// Window Appearance
|
||||||
result->vibrancyLayer = NULL;
|
result->vibrancyLayer = NULL;
|
||||||
@ -818,8 +856,7 @@ void destroyMenu(struct Application *app) {
|
|||||||
|
|
||||||
// Release the menu json if we have it
|
// Release the menu json if we have it
|
||||||
if ( app->menuAsJSON != NULL ) {
|
if ( app->menuAsJSON != NULL ) {
|
||||||
free((void*)app->menuAsJSON);
|
MEMFREE(app->menuAsJSON);
|
||||||
app->menuAsJSON = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release processed menu
|
// Release processed menu
|
||||||
@ -852,9 +889,15 @@ void destroyContextMenus(struct Application *app) {
|
|||||||
//Free context menu map
|
//Free context menu map
|
||||||
hashmap_destroy(&contextMenuMap);
|
hashmap_destroy(&contextMenuMap);
|
||||||
|
|
||||||
// Destroy context menu JSON
|
// Destroy processed Context Menus
|
||||||
free((void*)app->contextMenusAsJSON);
|
if( app->processedContextMenus != NULL) {
|
||||||
app->contextMenusAsJSON = NULL;
|
json_delete(app->processedContextMenus);
|
||||||
|
app->processedContextMenus = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the menu json
|
||||||
|
MEMFREE(app->contextMenusAsJSON);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -878,12 +921,8 @@ void destroyTray(struct Application *app) {
|
|||||||
//Free radio groups hashmap
|
//Free radio groups hashmap
|
||||||
hashmap_destroy(&radioGroupMapForTrayMenu);
|
hashmap_destroy(&radioGroupMapForTrayMenu);
|
||||||
|
|
||||||
// Free up the context menu map
|
|
||||||
hashmap_destroy(&contextMenuMap);
|
|
||||||
|
|
||||||
// Release the menu json
|
// Release the menu json
|
||||||
free((void*)app->trayMenuAsJSON);
|
MEMFREE(app->trayMenuAsJSON);
|
||||||
app->trayMenuAsJSON = NULL;
|
|
||||||
|
|
||||||
|
|
||||||
// Release processed tray
|
// Release processed tray
|
||||||
@ -893,16 +932,12 @@ void destroyTray(struct Application *app) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void DestroyApplication(struct Application *app) {
|
void DestroyApplication(struct Application *app) {
|
||||||
Debug(app, "Destroying Application");
|
Debug(app, "Destroying Application");
|
||||||
|
|
||||||
// Free the bindings
|
// Free the bindings
|
||||||
if (app->bindings != NULL) {
|
if (app->bindings != NULL) {
|
||||||
free((void*)app->bindings);
|
MEMFREE(app->bindings);
|
||||||
app->bindings = NULL;
|
|
||||||
} else {
|
} else {
|
||||||
Debug(app, "Almost a double free for app->bindings");
|
Debug(app, "Almost a double free for app->bindings");
|
||||||
}
|
}
|
||||||
@ -924,6 +959,11 @@ void DestroyApplication(struct Application *app) {
|
|||||||
// Destroy the context menus
|
// Destroy the context menus
|
||||||
destroyContextMenus(app);
|
destroyContextMenus(app);
|
||||||
|
|
||||||
|
// Clear context menu data if we have it
|
||||||
|
if( contextMenuData != NULL ) {
|
||||||
|
MEMFREE(contextMenuData);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove script handlers
|
// Remove script handlers
|
||||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
|
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
|
||||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
|
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
|
||||||
@ -1142,9 +1182,9 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
|
|||||||
app->sendMessageToBackend(responseMessage);
|
app->sendMessageToBackend(responseMessage);
|
||||||
|
|
||||||
// Free memory
|
// Free memory
|
||||||
free((void*)header);
|
MEMFREE(header);
|
||||||
free((void*)callback);
|
MEMFREE(callback);
|
||||||
free((void*)responseMessage);
|
MEMFREE(responseMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow);
|
msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow);
|
||||||
@ -1211,9 +1251,9 @@ void SaveDialog(struct Application *app, char *callbackID, char *title, char *fi
|
|||||||
app->sendMessageToBackend(responseMessage);
|
app->sendMessageToBackend(responseMessage);
|
||||||
|
|
||||||
// Free memory
|
// Free memory
|
||||||
free((void*)header);
|
MEMFREE(header);
|
||||||
free((void*)callback);
|
MEMFREE(callback);
|
||||||
free((void*)responseMessage);
|
MEMFREE(responseMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow);
|
msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow);
|
||||||
@ -1289,7 +1329,7 @@ void SetContextMenus(struct Application *app, const char *contextMenusAsJSON) {
|
|||||||
void SetBindings(struct Application *app, const char *bindings) {
|
void SetBindings(struct Application *app, const char *bindings) {
|
||||||
const char* temp = concat("window.wailsbindings = \"", bindings);
|
const char* temp = concat("window.wailsbindings = \"", bindings);
|
||||||
const char* jscall = concat(temp, "\";");
|
const char* jscall = concat(temp, "\";");
|
||||||
free((void*)temp);
|
MEMFREE(temp);
|
||||||
app->bindings = jscall;
|
app->bindings = jscall;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1360,9 +1400,9 @@ void DarkModeEnabled(struct Application *app, const char *callbackID) {
|
|||||||
app->sendMessageToBackend(responseMessage);
|
app->sendMessageToBackend(responseMessage);
|
||||||
|
|
||||||
// Free memory
|
// Free memory
|
||||||
free((void*)header);
|
MEMFREE(header);
|
||||||
free((void*)callback);
|
MEMFREE(callback);
|
||||||
free((void*)responseMessage);
|
MEMFREE(responseMessage);
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1372,6 +1412,7 @@ void createDelegate(struct Application *app) {
|
|||||||
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
|
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
|
||||||
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) yes, "c@:@");
|
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) yes, "c@:@");
|
||||||
class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
|
class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
|
||||||
|
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||||
|
|
||||||
// Menu Callbacks
|
// Menu Callbacks
|
||||||
class_addMethod(delegateClass, s("menuCallbackForApplicationMenu:"), (IMP)menuItemPressedForApplicationMenu, "v@:@");
|
class_addMethod(delegateClass, s("menuCallbackForApplicationMenu:"), (IMP)menuItemPressedForApplicationMenu, "v@:@");
|
||||||
@ -2150,6 +2191,19 @@ void UpdateMenu(struct Application *app, const char *menuAsJSON) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dumpContextMenus(struct Application *app) {
|
||||||
|
dumpHashmap("menuItemMapForContextMenus", &menuItemMapForContextMenus);
|
||||||
|
printf("&menuItemMapForContextMenus = %p\n", &menuItemMapForContextMenus);
|
||||||
|
|
||||||
|
//Free radio groups hashmap
|
||||||
|
dumpHashmap("radioGroupMapForContextMenus", &radioGroupMapForContextMenus);
|
||||||
|
printf("&radioGroupMapForContextMenus = %p\n", &radioGroupMapForContextMenus);
|
||||||
|
|
||||||
|
//Free context menu map
|
||||||
|
dumpHashmap("contextMenuMap", &contextMenuMap);
|
||||||
|
printf("&contextMenuMap = %p\n", &contextMenuMap);
|
||||||
|
}
|
||||||
|
|
||||||
void parseContextMenus(struct Application *app) {
|
void parseContextMenus(struct Application *app) {
|
||||||
|
|
||||||
// Allocation the hashmaps we need
|
// Allocation the hashmaps we need
|
||||||
@ -2164,20 +2218,29 @@ void parseContextMenus(struct Application *app) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JsonNode *contextMenuItems = json_find_member(app->processedContextMenus, "Items");
|
||||||
|
if( contextMenuItems == NULL ) {
|
||||||
|
// Parse error!
|
||||||
|
Fatal(app, "Unable to find Items:", app->processedContextMenus);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Iterate context menus
|
// Iterate context menus
|
||||||
JsonNode *contextMenu;
|
JsonNode *contextMenu;
|
||||||
json_foreach(contextMenu, app->processedContextMenus) {
|
json_foreach(contextMenu, contextMenuItems) {
|
||||||
// Create a new menu
|
// Create a new menu
|
||||||
id menu = createMenu(str(""));
|
id menu = createMenu(str(""));
|
||||||
|
printf("Context menu NSMenu pointer = %p\n", menu);
|
||||||
|
|
||||||
// parse the menu
|
// parse the menu
|
||||||
parseMenu(app, menu, contextMenu, &menuItemMapForContextMenus,
|
parseMenu(app, menu, contextMenu, &menuItemMapForContextMenus,
|
||||||
"checkboxMenuCallbackForContextMenus:", "radioMenuCallbackForContextMenus:", "menuCallbackForContextMenus:");
|
"checkboxMenuCallbackForContextMenus:", "radioMenuCallbackForContextMenus:", "menuCallbackForContextMenus:");
|
||||||
|
|
||||||
// Store the item in the context menu map
|
// Store the item in the context menu map
|
||||||
|
printf("Putting context menu %p with key '%s' in contextMenuMap %p\n", menu, contextMenu->key, &contextMenuMap);
|
||||||
hashmap_put(&contextMenuMap, (char*)contextMenu->key, strlen(contextMenu->key), menu);
|
hashmap_put(&contextMenuMap, (char*)contextMenu->key, strlen(contextMenu->key), menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dumpContextMenus(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
void parseTrayData(struct Application *app) {
|
void parseTrayData(struct Application *app) {
|
||||||
@ -2266,6 +2329,20 @@ void UpdateTray(struct Application *app, const char *trayMenuAsJSON) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateContextMenus(struct Application *app, const char *contextMenusAsJSON) {
|
||||||
|
ON_MAIN_THREAD (
|
||||||
|
|
||||||
|
dumpContextMenus(app);
|
||||||
|
|
||||||
|
// Free up memory
|
||||||
|
destroyContextMenus(app);
|
||||||
|
|
||||||
|
// Set the context menu JSON
|
||||||
|
app->contextMenusAsJSON = contextMenusAsJSON;
|
||||||
|
parseContextMenus(app);
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Run(struct Application *app, int argc, char **argv) {
|
void Run(struct Application *app, int argc, char **argv) {
|
||||||
|
|
||||||
@ -2378,11 +2455,11 @@ void Run(struct Application *app, int argc, char **argv) {
|
|||||||
// We want to evaluate the internal code plus runtime before the assets
|
// We want to evaluate the internal code plus runtime before the assets
|
||||||
const char *temp = concat(invoke, app->bindings);
|
const char *temp = concat(invoke, app->bindings);
|
||||||
const char *internalCode = concat(temp, (const char*)&runtime);
|
const char *internalCode = concat(temp, (const char*)&runtime);
|
||||||
free((void*)temp);
|
MEMFREE(temp);
|
||||||
|
|
||||||
// Add code that sets up the initial state, EG: State Stores.
|
// Add code that sets up the initial state, EG: State Stores.
|
||||||
temp = concat(internalCode, getInitialState(app));
|
temp = concat(internalCode, getInitialState(app));
|
||||||
free((void*)internalCode);
|
MEMFREE(internalCode);
|
||||||
internalCode = temp;
|
internalCode = temp;
|
||||||
|
|
||||||
// Loop over assets and build up one giant Mother Of All Evals
|
// Loop over assets and build up one giant Mother Of All Evals
|
||||||
@ -2397,7 +2474,7 @@ void Run(struct Application *app, int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
temp = concat(internalCode, (const char *)asset);
|
temp = concat(internalCode, (const char *)asset);
|
||||||
free((void*)internalCode);
|
MEMFREE(internalCode);
|
||||||
internalCode = temp;
|
internalCode = temp;
|
||||||
index++;
|
index++;
|
||||||
};
|
};
|
||||||
@ -2405,14 +2482,14 @@ void Run(struct Application *app, int argc, char **argv) {
|
|||||||
// Disable context menu if not in debug mode
|
// Disable context menu if not in debug mode
|
||||||
if( debug != 1 ) {
|
if( debug != 1 ) {
|
||||||
temp = concat(internalCode, "wails._.DisableDefaultContextMenu();");
|
temp = concat(internalCode, "wails._.DisableDefaultContextMenu();");
|
||||||
free((void*)internalCode);
|
MEMFREE(internalCode);
|
||||||
internalCode = temp;
|
internalCode = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "@@:@");
|
// class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "@@:@");
|
||||||
// Include callback after evaluation
|
// Include callback after evaluation
|
||||||
temp = concat(internalCode, "webkit.messageHandlers.completed.postMessage(true);");
|
temp = concat(internalCode, "webkit.messageHandlers.completed.postMessage(true);");
|
||||||
free((void*)internalCode);
|
MEMFREE(internalCode);
|
||||||
internalCode = temp;
|
internalCode = temp;
|
||||||
|
|
||||||
// const char *viewportScriptString = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); meta.setAttribute('initial-scale', '1.0'); meta.setAttribute('maximum-scale', '1.0'); meta.setAttribute('minimum-scale', '1.0'); meta.setAttribute('user-scalable', 'no'); document.getElementsByTagName('head')[0].appendChild(meta);";
|
// const char *viewportScriptString = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); meta.setAttribute('initial-scale', '1.0'); meta.setAttribute('maximum-scale', '1.0'); meta.setAttribute('minimum-scale', '1.0'); meta.setAttribute('user-scalable', 'no'); document.getElementsByTagName('head')[0].appendChild(meta);";
|
||||||
@ -2449,11 +2526,14 @@ void Run(struct Application *app, int argc, char **argv) {
|
|||||||
parseContextMenus(app);
|
parseContextMenus(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We set it to be invisible by default. It will become visible when everything has initialised
|
||||||
|
msg(app->mainWindow, s("setIsVisible:"), NO);
|
||||||
|
|
||||||
// Finally call run
|
// Finally call run
|
||||||
Debug(app, "Run called");
|
Debug(app, "Run called");
|
||||||
msg(app->application, s("run"));
|
msg(app->application, s("run"));
|
||||||
|
|
||||||
free((void*)internalCode);
|
MEMFREE(internalCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -21,6 +21,7 @@ extern void SetContextMenus(void *, const char *);
|
|||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -116,6 +117,7 @@ func (a *Application) processPlatformSettings() error {
|
|||||||
contextMenus := options.GetContextMenus(a.config)
|
contextMenus := options.GetContextMenus(a.config)
|
||||||
if contextMenus != nil {
|
if contextMenus != nil {
|
||||||
contextMenusJSON, err := json.Marshal(contextMenus)
|
contextMenusJSON, err := json.Marshal(contextMenus)
|
||||||
|
fmt.Printf("\n\nCONTEXT MENUS:\n %+v\n\n", string(contextMenusJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ type Client interface {
|
|||||||
DarkModeEnabled(callbackID string)
|
DarkModeEnabled(callbackID string)
|
||||||
UpdateMenu(menu *menu.Menu)
|
UpdateMenu(menu *menu.Menu)
|
||||||
UpdateTray(menu *menu.Menu)
|
UpdateTray(menu *menu.Menu)
|
||||||
|
UpdateContextMenus(contextMenus *menu.ContextMenus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchClient is what the frontends use to interface with the
|
// DispatchClient is what the frontends use to interface with the
|
||||||
|
43
v2/internal/messagedispatcher/message/contextmenus.go
Normal file
43
v2/internal/messagedispatcher/message/contextmenus.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContextMenusOnMessage is used to emit listener registration requests
|
||||||
|
// on the service bus
|
||||||
|
type ContextMenusOnMessage struct {
|
||||||
|
// MenuID is the id of the menu item we are interested in
|
||||||
|
MenuID string
|
||||||
|
// Callback is called when the menu is clicked
|
||||||
|
Callback func(*menu.MenuItem, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// contextMenusMessageParser does what it says on the tin!
|
||||||
|
func contextMenusMessageParser(message string) (*parsedMessage, error) {
|
||||||
|
|
||||||
|
// Sanity check: Menu messages must be at least 2 bytes
|
||||||
|
if len(message) < 3 {
|
||||||
|
return nil, fmt.Errorf("context menus message was an invalid length")
|
||||||
|
}
|
||||||
|
|
||||||
|
var topic string
|
||||||
|
var data interface{}
|
||||||
|
|
||||||
|
// Switch the message type
|
||||||
|
switch message[1] {
|
||||||
|
case 'C':
|
||||||
|
contextMenuData := message[2:]
|
||||||
|
topic = "contextmenus:clicked"
|
||||||
|
data = contextMenuData
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid menu message: %s", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new parsed message struct
|
||||||
|
parsedMessage := &parsedMessage{Topic: topic, Data: data}
|
||||||
|
|
||||||
|
return parsedMessage, nil
|
||||||
|
}
|
@ -20,6 +20,7 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
|
|||||||
'S': systemMessageParser,
|
'S': systemMessageParser,
|
||||||
'M': menuMessageParser,
|
'M': menuMessageParser,
|
||||||
'T': trayMessageParser,
|
'T': trayMessageParser,
|
||||||
|
'X': contextMenusMessageParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse will attempt to parse the given message
|
// Parse will attempt to parse the given message
|
||||||
|
@ -20,7 +20,7 @@ func trayMessageParser(message string) (*parsedMessage, error) {
|
|||||||
|
|
||||||
// Sanity check: Menu messages must be at least 2 bytes
|
// Sanity check: Menu messages must be at least 2 bytes
|
||||||
if len(message) < 3 {
|
if len(message) < 3 {
|
||||||
return nil, fmt.Errorf("event message was an invalid length")
|
return nil, fmt.Errorf("tray message was an invalid length")
|
||||||
}
|
}
|
||||||
|
|
||||||
var topic string
|
var topic string
|
||||||
|
@ -24,6 +24,7 @@ type Dispatcher struct {
|
|||||||
dialogChannel <-chan *servicebus.Message
|
dialogChannel <-chan *servicebus.Message
|
||||||
systemChannel <-chan *servicebus.Message
|
systemChannel <-chan *servicebus.Message
|
||||||
menuChannel <-chan *servicebus.Message
|
menuChannel <-chan *servicebus.Message
|
||||||
|
contextMenuChannel <-chan *servicebus.Message
|
||||||
trayChannel <-chan *servicebus.Message
|
trayChannel <-chan *servicebus.Message
|
||||||
running bool
|
running bool
|
||||||
|
|
||||||
@ -77,6 +78,11 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contextMenuChannel, err := servicebus.Subscribe("contextmenufrontend:")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
trayChannel, err := servicebus.Subscribe("trayfrontend:")
|
trayChannel, err := servicebus.Subscribe("trayfrontend:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -94,6 +100,7 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
|
|||||||
systemChannel: systemChannel,
|
systemChannel: systemChannel,
|
||||||
menuChannel: menuChannel,
|
menuChannel: menuChannel,
|
||||||
trayChannel: trayChannel,
|
trayChannel: trayChannel,
|
||||||
|
contextMenuChannel: contextMenuChannel,
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@ -125,6 +132,8 @@ func (d *Dispatcher) Start() error {
|
|||||||
d.processSystemMessage(systemMessage)
|
d.processSystemMessage(systemMessage)
|
||||||
case menuMessage := <-d.menuChannel:
|
case menuMessage := <-d.menuChannel:
|
||||||
d.processMenuMessage(menuMessage)
|
d.processMenuMessage(menuMessage)
|
||||||
|
case contextMenuMessage := <-d.contextMenuChannel:
|
||||||
|
d.processContextMenuMessage(contextMenuMessage)
|
||||||
case trayMessage := <-d.trayChannel:
|
case trayMessage := <-d.trayChannel:
|
||||||
d.processTrayMessage(trayMessage)
|
d.processTrayMessage(trayMessage)
|
||||||
}
|
}
|
||||||
@ -446,6 +455,34 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
|||||||
d.logger.Error("Unknown menufrontend command: %s", command)
|
d.logger.Error("Unknown menufrontend command: %s", command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func (d *Dispatcher) processContextMenuMessage(result *servicebus.Message) {
|
||||||
|
splitTopic := strings.Split(result.Topic(), ":")
|
||||||
|
if len(splitTopic) < 2 {
|
||||||
|
d.logger.Error("Invalid contextmenu message : %#v", result.Data())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
command := splitTopic[1]
|
||||||
|
switch command {
|
||||||
|
case "update":
|
||||||
|
|
||||||
|
updatedContextMenus, ok := result.Data().(*menu.ContextMenus)
|
||||||
|
if !ok {
|
||||||
|
d.logger.Error("Invalid data for 'contextmenufrontend: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.UpdateContextMenus(updatedContextMenus)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
d.logger.Error("Unknown contextmenufrontend command: %s", command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Dispatcher) processTrayMessage(result *servicebus.Message) {
|
func (d *Dispatcher) processTrayMessage(result *servicebus.Message) {
|
||||||
splitTopic := strings.Split(result.Topic(), ":")
|
splitTopic := strings.Split(result.Topic(), ":")
|
||||||
|
48
v2/internal/runtime/contextmenus.go
Normal file
48
v2/internal/runtime/contextmenus.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContextMenus defines all ContextMenu related operations
|
||||||
|
type ContextMenus interface {
|
||||||
|
On(menuID string, callback func(*menu.MenuItem, string))
|
||||||
|
Update()
|
||||||
|
GetByID(menuID string) *menu.MenuItem
|
||||||
|
RemoveByID(id string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextMenus struct {
|
||||||
|
bus *servicebus.ServiceBus
|
||||||
|
contextmenus *menu.ContextMenus
|
||||||
|
}
|
||||||
|
|
||||||
|
// newContextMenus creates a new ContextMenu struct
|
||||||
|
func newContextMenus(bus *servicebus.ServiceBus, contextmenus *menu.ContextMenus) ContextMenus {
|
||||||
|
return &contextMenus{
|
||||||
|
bus: bus,
|
||||||
|
contextmenus: contextmenus,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On registers a listener for a particular event
|
||||||
|
func (t *contextMenus) On(menuID string, callback func(*menu.MenuItem, string)) {
|
||||||
|
t.bus.Publish("contextmenus:on", &message.ContextMenusOnMessage{
|
||||||
|
MenuID: menuID,
|
||||||
|
Callback: callback,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *contextMenus) Update() {
|
||||||
|
t.bus.Publish("contextmenus:update", t.contextmenus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *contextMenus) GetByID(menuItemID string) *menu.MenuItem {
|
||||||
|
return t.contextmenus.GetByID(menuItemID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *contextMenus) RemoveByID(menuItemID string) bool {
|
||||||
|
return t.contextmenus.RemoveByID(menuItemID)
|
||||||
|
}
|
@ -57,10 +57,10 @@ export function Init() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
if( contextMenuId != null ) {
|
if( contextMenuId != null ) {
|
||||||
|
let contextData = currentElement.dataset['wails-context-menu-data'];
|
||||||
let message = {
|
let message = {
|
||||||
id: contextMenuId,
|
id: contextMenuId,
|
||||||
x: e.clientX,
|
data: contextData || "",
|
||||||
y: e.clientY,
|
|
||||||
};
|
};
|
||||||
window.webkit.messageHandlers.contextMenu.postMessage(JSON.stringify(message));
|
window.webkit.messageHandlers.contextMenu.postMessage(JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ type Runtime struct {
|
|||||||
Dialog Dialog
|
Dialog Dialog
|
||||||
System System
|
System System
|
||||||
Menu Menu
|
Menu Menu
|
||||||
|
ContextMenu ContextMenus
|
||||||
Tray Tray
|
Tray Tray
|
||||||
Store *StoreProvider
|
Store *StoreProvider
|
||||||
Log Log
|
Log Log
|
||||||
@ -20,7 +21,7 @@ type Runtime struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new runtime
|
// New creates a new runtime
|
||||||
func New(serviceBus *servicebus.ServiceBus, menu *menu.Menu, trayMenu *menu.Menu) *Runtime {
|
func New(serviceBus *servicebus.ServiceBus, menu *menu.Menu, trayMenu *menu.Menu, contextMenus *menu.ContextMenus) *Runtime {
|
||||||
result := &Runtime{
|
result := &Runtime{
|
||||||
Browser: newBrowser(),
|
Browser: newBrowser(),
|
||||||
Events: newEvents(serviceBus),
|
Events: newEvents(serviceBus),
|
||||||
@ -29,6 +30,7 @@ func New(serviceBus *servicebus.ServiceBus, menu *menu.Menu, trayMenu *menu.Menu
|
|||||||
System: newSystem(serviceBus),
|
System: newSystem(serviceBus),
|
||||||
Menu: newMenu(serviceBus, menu),
|
Menu: newMenu(serviceBus, menu),
|
||||||
Tray: newTray(serviceBus, trayMenu),
|
Tray: newTray(serviceBus, trayMenu),
|
||||||
|
ContextMenu: newContextMenus(serviceBus, contextMenus),
|
||||||
Log: newLog(serviceBus),
|
Log: newLog(serviceBus),
|
||||||
bus: serviceBus,
|
bus: serviceBus,
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ type trayRuntime struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newTray creates a new Menu struct
|
// newTray creates a new Menu struct
|
||||||
func newTray(bus *servicebus.ServiceBus, menu *menu.Menu) Menu {
|
func newTray(bus *servicebus.ServiceBus, menu *menu.Menu) Tray {
|
||||||
return &trayRuntime{
|
return &trayRuntime{
|
||||||
bus: bus,
|
bus: bus,
|
||||||
trayMenu: menu,
|
trayMenu: menu,
|
||||||
|
200
v2/internal/subsystem/contextmenus.go
Normal file
200
v2/internal/subsystem/contextmenus.go
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
package subsystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContextMenus is the subsystem that handles the operation of context menus. It manages all service bus messages
|
||||||
|
// starting with "contextmenus".
|
||||||
|
type ContextMenus struct {
|
||||||
|
quitChannel <-chan *servicebus.Message
|
||||||
|
menuChannel <-chan *servicebus.Message
|
||||||
|
running bool
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
listeners map[string][]func(*menu.MenuItem, string)
|
||||||
|
menuItems map[string]*menu.MenuItem
|
||||||
|
notifyLock sync.RWMutex
|
||||||
|
|
||||||
|
// logger
|
||||||
|
logger logger.CustomLogger
|
||||||
|
|
||||||
|
// The context menus
|
||||||
|
contextMenus *menu.ContextMenus
|
||||||
|
|
||||||
|
// Service Bus
|
||||||
|
bus *servicebus.ServiceBus
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContextMenus creates a new context menu subsystem
|
||||||
|
func NewContextMenus(contextMenus *menu.ContextMenus, bus *servicebus.ServiceBus, logger *logger.Logger) (*ContextMenus, error) {
|
||||||
|
|
||||||
|
// Register quit channel
|
||||||
|
quitChannel, err := bus.Subscribe("quit")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to menu messages
|
||||||
|
menuChannel, err := bus.Subscribe("contextmenus:")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &ContextMenus{
|
||||||
|
quitChannel: quitChannel,
|
||||||
|
menuChannel: menuChannel,
|
||||||
|
logger: logger.CustomLogger("Context Menu Subsystem"),
|
||||||
|
listeners: make(map[string][]func(*menu.MenuItem, string)),
|
||||||
|
menuItems: make(map[string]*menu.MenuItem),
|
||||||
|
contextMenus: contextMenus,
|
||||||
|
bus: bus,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up list of item/id pairs
|
||||||
|
result.processContextMenus(contextMenus)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextMenuData struct {
|
||||||
|
MenuItemID string `json:"menuItemID"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the subsystem
|
||||||
|
func (c *ContextMenus) Start() error {
|
||||||
|
|
||||||
|
c.logger.Trace("Starting")
|
||||||
|
|
||||||
|
c.running = true
|
||||||
|
|
||||||
|
// Spin off a go routine
|
||||||
|
go func() {
|
||||||
|
for c.running {
|
||||||
|
select {
|
||||||
|
case <-c.quitChannel:
|
||||||
|
c.running = false
|
||||||
|
break
|
||||||
|
case menuMessage := <-c.menuChannel:
|
||||||
|
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
||||||
|
menuMessageType := splitTopic[1]
|
||||||
|
switch menuMessageType {
|
||||||
|
case "clicked":
|
||||||
|
if len(splitTopic) != 2 {
|
||||||
|
c.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.logger.Trace("Got Context Menu clicked Message: %s %+v", menuMessage.Topic(), menuMessage.Data())
|
||||||
|
contextMenuDataJSON := menuMessage.Data().(string)
|
||||||
|
|
||||||
|
var data contextMenuData
|
||||||
|
err := json.Unmarshal([]byte(contextMenuDataJSON), &data)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Trace("Cannot process contextMenuDataJSON %s", string(contextMenuDataJSON))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the menu item
|
||||||
|
menuItem := c.menuItems[data.MenuItemID]
|
||||||
|
if menuItem == nil {
|
||||||
|
c.logger.Trace("Cannot process menuitem id %s - unknown", data.MenuItemID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is the menu item a checkbox?
|
||||||
|
if menuItem.Type == menu.CheckboxType {
|
||||||
|
// Toggle state
|
||||||
|
menuItem.Checked = !menuItem.Checked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify listeners
|
||||||
|
c.notifyListeners(data, menuItem)
|
||||||
|
case "on":
|
||||||
|
listenerDetails := menuMessage.Data().(*message.ContextMenusOnMessage)
|
||||||
|
id := listenerDetails.MenuID
|
||||||
|
c.listeners[id] = append(c.listeners[id], listenerDetails.Callback)
|
||||||
|
|
||||||
|
// Make sure we catch any menu updates
|
||||||
|
case "update":
|
||||||
|
updatedMenu := menuMessage.Data().(*menu.ContextMenus)
|
||||||
|
c.processContextMenus(updatedMenu)
|
||||||
|
|
||||||
|
// Notify frontend of menu change
|
||||||
|
c.bus.Publish("contextmenufrontend:update", updatedMenu)
|
||||||
|
|
||||||
|
default:
|
||||||
|
c.logger.Error("unknown context menu message: %+v", menuMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call shutdown
|
||||||
|
c.shutdown()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextMenus) processContextMenus(contextMenus *menu.ContextMenus) {
|
||||||
|
// Initialise the variables
|
||||||
|
c.menuItems = make(map[string]*menu.MenuItem)
|
||||||
|
c.contextMenus = contextMenus
|
||||||
|
|
||||||
|
for _, contextMenu := range contextMenus.Items {
|
||||||
|
for _, item := range contextMenu.Items {
|
||||||
|
c.processMenuItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextMenus) processMenuItem(item *menu.MenuItem) {
|
||||||
|
|
||||||
|
if item.SubMenu != nil {
|
||||||
|
for _, submenuitem := range item.SubMenu {
|
||||||
|
c.processMenuItem(submenuitem)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.ID != "" {
|
||||||
|
if c.menuItems[item.ID] != nil {
|
||||||
|
c.logger.Error("Context Menu id '%s' is used by multiple menu items: %s %s", c.menuItems[item.ID].Label, item.Label)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.menuItems[item.ID] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifies listeners that the given menu was clicked
|
||||||
|
func (c *ContextMenus) notifyListeners(contextData contextMenuData, menuItem *menu.MenuItem) {
|
||||||
|
|
||||||
|
// Get list of menu listeners
|
||||||
|
listeners := c.listeners[contextData.MenuItemID]
|
||||||
|
if listeners == nil {
|
||||||
|
c.logger.Trace("No listeners for MenuItem with ID '%s'", contextData.MenuItemID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock the listeners
|
||||||
|
c.notifyLock.Lock()
|
||||||
|
|
||||||
|
// Callback in goroutine
|
||||||
|
for _, listener := range listeners {
|
||||||
|
go listener(menuItem, contextData.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock
|
||||||
|
c.notifyLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextMenus) shutdown() {
|
||||||
|
c.logger.Trace("Shutdown")
|
||||||
|
}
|
@ -24,7 +24,7 @@ type Runtime struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRuntime creates a new runtime subsystem
|
// NewRuntime creates a new runtime subsystem
|
||||||
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, menu *menu.Menu, trayMenu *menu.Menu) (*Runtime, error) {
|
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, menu *menu.Menu, trayMenu *menu.Menu, contextMenus *menu.ContextMenus) (*Runtime, error) {
|
||||||
|
|
||||||
// Register quit channel
|
// Register quit channel
|
||||||
quitChannel, err := bus.Subscribe("quit")
|
quitChannel, err := bus.Subscribe("quit")
|
||||||
@ -42,7 +42,7 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, menu *menu.Me
|
|||||||
quitChannel: quitChannel,
|
quitChannel: quitChannel,
|
||||||
runtimeChannel: runtimeChannel,
|
runtimeChannel: runtimeChannel,
|
||||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||||
runtime: runtime.New(bus, menu, trayMenu),
|
runtime: runtime.New(bus, menu, trayMenu, contextMenus),
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
38
v2/pkg/menu/contextmenu.go
Normal file
38
v2/pkg/menu/contextmenu.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package menu
|
||||||
|
|
||||||
|
type ContextMenus struct {
|
||||||
|
Items map[string]*Menu
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContextMenus() *ContextMenus {
|
||||||
|
return &ContextMenus{
|
||||||
|
Items: make(map[string]*Menu),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextMenus) AddMenu(ID string, menu *Menu) {
|
||||||
|
c.Items[ID] = menu
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextMenus) GetByID(menuID string) *MenuItem {
|
||||||
|
|
||||||
|
// Loop over menu items
|
||||||
|
for _, item := range c.Items {
|
||||||
|
result := item.GetByID(menuID)
|
||||||
|
if result != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ContextMenus) RemoveByID(id string) bool {
|
||||||
|
// Loop over menu items
|
||||||
|
for _, item := range c.Items {
|
||||||
|
result := item.RemoveByID(id)
|
||||||
|
if result == true {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -10,5 +10,5 @@ type Options struct {
|
|||||||
WindowBackgroundIsTranslucent bool
|
WindowBackgroundIsTranslucent bool
|
||||||
Menu *menu.Menu
|
Menu *menu.Menu
|
||||||
Tray *menu.Menu
|
Tray *menu.Menu
|
||||||
ContextMenus map[string]*menu.Menu
|
ContextMenus *menu.ContextMenus
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ type App struct {
|
|||||||
StartHidden bool
|
StartHidden bool
|
||||||
DevTools bool
|
DevTools bool
|
||||||
RGBA int
|
RGBA int
|
||||||
ContextMenus map[string]*menu.Menu
|
ContextMenus *menu.ContextMenus
|
||||||
Tray *menu.Menu
|
Tray *menu.Menu
|
||||||
Menu *menu.Menu
|
Menu *menu.Menu
|
||||||
Mac *mac.Options
|
Mac *mac.Options
|
||||||
@ -97,11 +97,11 @@ func GetApplicationMenu(appoptions *App) *menu.Menu {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetContextMenus(appoptions *App) map[string]*menu.Menu {
|
func GetContextMenus(appoptions *App) *menu.ContextMenus {
|
||||||
var result map[string]*menu.Menu
|
var result *menu.ContextMenus
|
||||||
|
|
||||||
result = appoptions.ContextMenus
|
result = appoptions.ContextMenus
|
||||||
var contextMenuOverrides map[string]*menu.Menu
|
var contextMenuOverrides *menu.ContextMenus
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "darwin":
|
case "darwin":
|
||||||
if appoptions.Mac != nil {
|
if appoptions.Mac != nil {
|
||||||
@ -118,8 +118,10 @@ func GetContextMenus(appoptions *App) map[string]*menu.Menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Overwrite defaults with OS Specific context menus
|
// Overwrite defaults with OS Specific context menus
|
||||||
for id, contextMenu := range contextMenuOverrides {
|
if contextMenuOverrides != nil {
|
||||||
result[id] = contextMenu
|
for id, contextMenu := range contextMenuOverrides.Items {
|
||||||
|
result.AddMenu(id, contextMenu)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
40
v2/test/kitchensink/contextmenus.go
Normal file
40
v2/test/kitchensink/contextmenus.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContextMenu struct
|
||||||
|
type ContextMenu struct {
|
||||||
|
runtime *wails.Runtime
|
||||||
|
counter int
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// WailsInit is called at application startup
|
||||||
|
func (c *ContextMenu) WailsInit(runtime *wails.Runtime) error {
|
||||||
|
// Perform your setup here
|
||||||
|
c.runtime = runtime
|
||||||
|
|
||||||
|
// Setup Menu Listeners
|
||||||
|
c.runtime.ContextMenu.On("Test Context Menu", func(mi *menu.MenuItem, contextData string) {
|
||||||
|
fmt.Printf("\n\nContext Data = '%s'\n\n", contextData)
|
||||||
|
c.lock.Lock()
|
||||||
|
c.counter++
|
||||||
|
mi.Label = fmt.Sprintf("Clicked %d times", c.counter)
|
||||||
|
c.lock.Unlock()
|
||||||
|
c.runtime.ContextMenu.Update()
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createContextMenus() *menu.ContextMenus {
|
||||||
|
result := menu.NewContextMenus()
|
||||||
|
result.AddMenu("test", menu.NewMenuFromItems(menu.Text("Clicked 0 times", "Test Context Menu")))
|
||||||
|
return result
|
||||||
|
}
|
@ -43,7 +43,7 @@
|
|||||||
<!-- Sticky alerts (toasts), empty container -->
|
<!-- Sticky alerts (toasts), empty container -->
|
||||||
<div class="sticky-alerts"></div>
|
<div class="sticky-alerts"></div>
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<div class="sidebar noselect" data-wails-context-menu-id="test">
|
<div class="sidebar noselect" data-wails-context-menu-id="test" data-wails-context-menu-data="hello!">
|
||||||
<div data-wails-no-drag class="sidebar-menu">
|
<div data-wails-no-drag class="sidebar-menu">
|
||||||
<!-- Sidebar brand -->
|
<!-- Sidebar brand -->
|
||||||
<div on:click="{ homepageClicked }" class="sidebar-brand">
|
<div on:click="{ homepageClicked }" class="sidebar-brand">
|
||||||
|
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"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/menu"
|
|
||||||
"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"
|
"log"
|
||||||
@ -20,9 +19,8 @@ func main() {
|
|||||||
MinHeight: 600,
|
MinHeight: 600,
|
||||||
//Tray: menu.NewMenuFromItems(menu.AppMenu()),
|
//Tray: menu.NewMenuFromItems(menu.AppMenu()),
|
||||||
//Menu: menu.NewMenuFromItems(menu.AppMenu()),
|
//Menu: menu.NewMenuFromItems(menu.AppMenu()),
|
||||||
ContextMenus: map[string]*menu.Menu{
|
StartHidden: true,
|
||||||
"test": menu.NewMenuFromItems(menu.Text("Test Menu", "Test Context Menu")),
|
ContextMenus: createContextMenus(),
|
||||||
},
|
|
||||||
Mac: &mac.Options{
|
Mac: &mac.Options{
|
||||||
WebviewIsTransparent: true,
|
WebviewIsTransparent: true,
|
||||||
WindowBackgroundIsTranslucent: true,
|
WindowBackgroundIsTranslucent: true,
|
||||||
@ -46,6 +44,7 @@ func main() {
|
|||||||
app.Bind(&Window{})
|
app.Bind(&Window{})
|
||||||
app.Bind(&Menu{})
|
app.Bind(&Menu{})
|
||||||
app.Bind(&Tray{})
|
app.Bind(&Tray{})
|
||||||
|
app.Bind(&ContextMenu{})
|
||||||
|
|
||||||
err = app.Run()
|
err = app.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user