mirror of
https://github.com/taigrr/wails.git
synced 2026-04-17 12:15:02 -07:00
Compare commits
40 Commits
v2.0.0-alp
...
v2.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96d8509da3 | ||
|
|
598445ab0f | ||
|
|
24788e2fd3 | ||
|
|
bbf4dde43f | ||
|
|
0afd27ab45 | ||
|
|
d498423ec2 | ||
|
|
66ce84973c | ||
|
|
55e6a0f312 | ||
|
|
81e83fdf18 | ||
|
|
f9b79d24f8 | ||
|
|
0599a47bfe | ||
|
|
817c55d318 | ||
|
|
14146c8c0c | ||
|
|
18adac20d4 | ||
|
|
eb4bff89da | ||
|
|
c66dc777f3 | ||
|
|
9003462457 | ||
|
|
e124f0a220 | ||
|
|
c6d3f57712 | ||
|
|
b4c669ff86 | ||
|
|
2d1b2c0947 | ||
|
|
4a0c5aa785 | ||
|
|
f48d7f8f60 | ||
|
|
651f24f641 | ||
|
|
8fd77148ca | ||
|
|
0dc0762fdf | ||
|
|
1a92550709 | ||
|
|
bffc15bc14 | ||
|
|
198d206c46 | ||
|
|
bb8e848ef6 | ||
|
|
bac3e9e5c1 | ||
|
|
bc5eddeb66 | ||
|
|
8e7258d812 | ||
|
|
7118762cec | ||
|
|
6af92cf0a4 | ||
|
|
1bb91634f7 | ||
|
|
f71ce7913f | ||
|
|
53db687a26 | ||
|
|
13939d3d6b | ||
|
|
552c6b8711 |
@@ -57,6 +57,11 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||||||
keepAssets := false
|
keepAssets := false
|
||||||
command.BoolFlag("k", "Keep generated assets", &keepAssets)
|
command.BoolFlag("k", "Keep generated assets", &keepAssets)
|
||||||
|
|
||||||
|
appleIdentity := ""
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
command.StringFlag("sign", "Signs your app with the given identity.", &appleIdentity)
|
||||||
|
}
|
||||||
|
|
||||||
command.Action(func() error {
|
command.Action(func() error {
|
||||||
|
|
||||||
// Create logger
|
// Create logger
|
||||||
@@ -72,6 +77,11 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||||||
app.PrintBanner()
|
app.PrintBanner()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure package is used with apple identity
|
||||||
|
if appleIdentity != "" && pack == false {
|
||||||
|
return fmt.Errorf("must use `-package` flag when using `-sign`")
|
||||||
|
}
|
||||||
|
|
||||||
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
|
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
|
||||||
logger.Println(task)
|
logger.Println(task)
|
||||||
logger.Println(strings.Repeat("-", len(task)))
|
logger.Println(strings.Repeat("-", len(task)))
|
||||||
@@ -84,14 +94,15 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||||||
|
|
||||||
// Create BuildOptions
|
// Create BuildOptions
|
||||||
buildOptions := &build.Options{
|
buildOptions := &build.Options{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
OutputType: outputType,
|
OutputType: outputType,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Pack: pack,
|
Pack: pack,
|
||||||
Platform: platform,
|
Platform: platform,
|
||||||
LDFlags: ldflags,
|
LDFlags: ldflags,
|
||||||
Compiler: compilerCommand,
|
Compiler: compilerCommand,
|
||||||
KeepAssets: keepAssets,
|
KeepAssets: keepAssets,
|
||||||
|
AppleIdentity: appleIdentity,
|
||||||
}
|
}
|
||||||
|
|
||||||
return doBuild(buildOptions)
|
return doBuild(buildOptions)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
var version = "v2.0.0-alpha.36"
|
var version = "v2.0.0-alpha.50"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type App struct {
|
|||||||
//binding *subsystem.Binding
|
//binding *subsystem.Binding
|
||||||
call *subsystem.Call
|
call *subsystem.Call
|
||||||
menu *subsystem.Menu
|
menu *subsystem.Menu
|
||||||
|
url *subsystem.URL
|
||||||
dispatcher *messagedispatcher.Dispatcher
|
dispatcher *messagedispatcher.Dispatcher
|
||||||
|
|
||||||
menuManager *menumanager.Manager
|
menuManager *menumanager.Manager
|
||||||
@@ -160,6 +161,19 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.options.Mac.URLHandlers != nil {
|
||||||
|
// Start the url handler subsystem
|
||||||
|
url, err := subsystem.NewURL(a.servicebus, a.logger, a.options.Mac.URLHandlers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.url = url
|
||||||
|
err = a.url.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Start the eventing subsystem
|
// Start the eventing subsystem
|
||||||
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
|
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ type BridgeClient struct {
|
|||||||
messageCache chan string
|
messageCache chan string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b BridgeClient) DeleteTrayMenuByID(id string) {
|
||||||
|
b.session.sendMessage("TD" + id)
|
||||||
|
}
|
||||||
|
|
||||||
func NewBridgeClient() *BridgeClient {
|
func NewBridgeClient() *BridgeClient {
|
||||||
return &BridgeClient{
|
return &BridgeClient{
|
||||||
messageCache: make(chan string, 100),
|
messageCache: make(chan string, 100),
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ type DialogClient struct {
|
|||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DialogClient) DeleteTrayMenuByID(id string) {
|
||||||
|
}
|
||||||
|
|
||||||
func NewDialogClient(log *logger.Logger) *DialogClient {
|
func NewDialogClient(log *logger.Logger) *DialogClient {
|
||||||
return &DialogClient{
|
return &DialogClient{
|
||||||
log: log,
|
log: log,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ extern void DarkModeEnabled(struct Application*, char *callbackID);
|
|||||||
extern void SetApplicationMenu(struct Application*, const char *);
|
extern void SetApplicationMenu(struct Application*, const char *);
|
||||||
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||||
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||||
|
extern void DeleteTrayMenuByID(struct Application*, const char *id);
|
||||||
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
||||||
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
||||||
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
||||||
|
|||||||
@@ -208,3 +208,7 @@ func (c *Client) UpdateTrayMenuLabel(JSON string) {
|
|||||||
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
|
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
|
||||||
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
|
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteTrayMenuByID(id string) {
|
||||||
|
C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id))
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ struct hashmap_s dialogIconCache;
|
|||||||
// Dispatch Method
|
// Dispatch Method
|
||||||
typedef void (^dispatchMethod)(void);
|
typedef void (^dispatchMethod)(void);
|
||||||
|
|
||||||
|
TrayMenuStore *TrayMenuStoreSingleton;
|
||||||
|
|
||||||
// dispatch will execute the given `func` pointer
|
// dispatch will execute the given `func` pointer
|
||||||
void dispatch(dispatchMethod func) {
|
void dispatch(dispatchMethod func) {
|
||||||
dispatch_async(dispatch_get_main_queue(), func);
|
dispatch_async(dispatch_get_main_queue(), func);
|
||||||
@@ -46,6 +48,18 @@ int hashmap_log(void *const context, struct hashmap_element_s *const e) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void filelog(const char *message) {
|
||||||
|
FILE *fp = fopen("/tmp/wailslog.txt", "ab");
|
||||||
|
if (fp != NULL)
|
||||||
|
{
|
||||||
|
fputs(message, fp);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The delegate class for tray menus
|
||||||
|
Class trayMenuDelegateClass;
|
||||||
|
|
||||||
// Utility function to visualise a hashmap
|
// Utility function to visualise a hashmap
|
||||||
void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
|
void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
|
||||||
printf("%s = { ", name);
|
printf("%s = { ", name);
|
||||||
@@ -113,13 +127,11 @@ struct Application {
|
|||||||
int useToolBar;
|
int useToolBar;
|
||||||
int hideToolbarSeparator;
|
int hideToolbarSeparator;
|
||||||
int windowBackgroundIsTranslucent;
|
int windowBackgroundIsTranslucent;
|
||||||
|
int hasURLHandlers;
|
||||||
|
|
||||||
// Menu
|
// Menu
|
||||||
Menu *applicationMenu;
|
Menu *applicationMenu;
|
||||||
|
|
||||||
// Tray
|
|
||||||
TrayMenuStore* trayMenuStore;
|
|
||||||
|
|
||||||
// Context Menus
|
// Context Menus
|
||||||
ContextMenuStore *contextMenuStore;
|
ContextMenuStore *contextMenuStore;
|
||||||
|
|
||||||
@@ -467,7 +479,7 @@ void DestroyApplication(struct Application *app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete the tray menu store
|
// Delete the tray menu store
|
||||||
DeleteTrayMenuStore(app->trayMenuStore);
|
DeleteTrayMenuStore(TrayMenuStoreSingleton);
|
||||||
|
|
||||||
// Delete the context menu store
|
// Delete the context menu store
|
||||||
DeleteContextMenuStore(app->contextMenuStore);
|
DeleteContextMenuStore(app->contextMenuStore);
|
||||||
@@ -1034,7 +1046,7 @@ void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
|
|||||||
// Guard against calling during shutdown
|
// Guard against calling during shutdown
|
||||||
if( app->shuttingDown ) return;
|
if( app->shuttingDown ) return;
|
||||||
|
|
||||||
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
|
AddTrayMenuToStore(TrayMenuStoreSingleton, trayMenuJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
||||||
@@ -1043,7 +1055,13 @@ void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
|||||||
if( app->shuttingDown ) return;
|
if( app->shuttingDown ) return;
|
||||||
|
|
||||||
ON_MAIN_THREAD(
|
ON_MAIN_THREAD(
|
||||||
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
|
UpdateTrayMenuInStore(TrayMenuStoreSingleton, trayMenuJSON);
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteTrayMenuByID(struct Application *app, const char *id) {
|
||||||
|
ON_MAIN_THREAD(
|
||||||
|
DeleteTrayMenuInStore(TrayMenuStoreSingleton, id);
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1052,7 +1070,7 @@ void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
|||||||
if( app->shuttingDown ) return;
|
if( app->shuttingDown ) return;
|
||||||
|
|
||||||
ON_MAIN_THREAD(
|
ON_MAIN_THREAD(
|
||||||
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
|
UpdateTrayMenuLabelInStore(TrayMenuStoreSingleton, JSON);
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1137,27 +1155,49 @@ void DarkModeEnabled(struct Application *app, const char *callbackID) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getURL(id self, SEL selector, id event, id replyEvent) {
|
||||||
|
id desc = msg(event, s("paramDescriptorForKeyword:"), keyDirectObject);
|
||||||
|
id url = msg(desc, s("stringValue"));
|
||||||
|
const char* curl = cstr(url);
|
||||||
|
const char* message = concat("UC", curl);
|
||||||
|
messageFromWindowCallback(message);
|
||||||
|
MEMFREE(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void createDelegate(struct Application *app) {
|
void createDelegate(struct Application *app) {
|
||||||
// Define delegate
|
|
||||||
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0);
|
// Define delegate
|
||||||
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
|
Class appDelegate = objc_allocateClassPair((Class) c("NSResponder"), "AppDelegate", 0);
|
||||||
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
|
class_addProtocol(appDelegate, objc_getProtocol("NSTouchBarProvider"));
|
||||||
// class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
|
|
||||||
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
class_addMethod(appDelegate, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
|
||||||
|
class_addMethod(appDelegate, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||||
|
|
||||||
// All Menu Items use a common callback
|
// All Menu Items use a common callback
|
||||||
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
|
class_addMethod(appDelegate, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
|
||||||
|
|
||||||
|
// If there are URL Handlers, register the callback method
|
||||||
|
if( app->hasURLHandlers ) {
|
||||||
|
class_addMethod(appDelegate, s("getUrl:withReplyEvent:"), (IMP) getURL, "i@:@@");
|
||||||
|
}
|
||||||
|
|
||||||
// Script handler
|
// Script handler
|
||||||
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
class_addMethod(appDelegate, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
||||||
objc_registerClassPair(delegateClass);
|
objc_registerClassPair(appDelegate);
|
||||||
|
|
||||||
// Create delegate
|
// Create delegate
|
||||||
id delegate = msg((id)delegateClass, s("new"));
|
id delegate = msg((id)appDelegate, s("new"));
|
||||||
objc_setAssociatedObject(delegate, "application", (id)app, OBJC_ASSOCIATION_ASSIGN);
|
objc_setAssociatedObject(delegate, "application", (id)app, OBJC_ASSOCIATION_ASSIGN);
|
||||||
|
|
||||||
|
// If there are URL Handlers, register a listener for them
|
||||||
|
if( app->hasURLHandlers ) {
|
||||||
|
id eventManager = msg(c("NSAppleEventManager"), s("sharedAppleEventManager"));
|
||||||
|
msg(eventManager, s("setEventHandler:andSelector:forEventClass:andEventID:"), delegate, s("getUrl:withReplyEvent:"), kInternetEventClass, kAEGetURL);
|
||||||
|
}
|
||||||
|
|
||||||
// Theme change listener
|
// Theme change listener
|
||||||
class_addMethod(delegateClass, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
|
class_addMethod(appDelegate, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
|
||||||
|
|
||||||
// Get defaultCenter
|
// Get defaultCenter
|
||||||
id defaultCenter = msg(c("NSDistributedNotificationCenter"), s("defaultCenter"));
|
id defaultCenter = msg(c("NSDistributedNotificationCenter"), s("defaultCenter"));
|
||||||
@@ -1171,13 +1211,19 @@ void createDelegate(struct Application *app) {
|
|||||||
bool windowShouldClose(id self, SEL cmd, id sender) {
|
bool windowShouldClose(id self, SEL cmd, id sender) {
|
||||||
msg(sender, s("orderBack:"));
|
msg(sender, s("orderBack:"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool windowShouldExit(id self, SEL cmd, id sender) {
|
||||||
|
msg(sender, s("orderBack:"));
|
||||||
|
messageFromWindowCallback("WC");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void createMainWindow(struct Application *app) {
|
void createMainWindow(struct Application *app) {
|
||||||
// Create main window
|
// Create main window
|
||||||
id mainWindow = ALLOC("NSWindow");
|
id mainWindow = ALLOC("NSWindow");
|
||||||
mainWindow = msg(mainWindow, s("initWithContentRect:styleMask:backing:defer:"),
|
mainWindow = msg(mainWindow, s("initWithContentRect:styleMask:backing:defer:"),
|
||||||
CGRectMake(0, 0, app->width, app->height), app->decorations, NSBackingStoreBuffered, NO);
|
CGRectMake(0, 0, app->width, app->height), app->decorations, NSBackingStoreBuffered, NO);
|
||||||
msg(mainWindow, s("autorelease"));
|
msg(mainWindow, s("autorelease"));
|
||||||
|
|
||||||
// Set Appearance
|
// Set Appearance
|
||||||
@@ -1191,14 +1237,16 @@ void createMainWindow(struct Application *app) {
|
|||||||
msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO);
|
msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO);
|
||||||
msg(mainWindow, s("setTitleVisibility:"), app->hideTitle);
|
msg(mainWindow, s("setTitleVisibility:"), app->hideTitle);
|
||||||
|
|
||||||
if( app->hideWindowOnClose ) {
|
// Create window delegate to override windowShouldClose
|
||||||
// Create window delegate to override windowShouldClose
|
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "WindowDelegate", 0);
|
||||||
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "WindowDelegate", 0);
|
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSWindowDelegate"));
|
||||||
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSWindowDelegate"));
|
if( app->hideWindowOnClose ) {
|
||||||
class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldClose, "v@:@");
|
class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldClose, "v@:@");
|
||||||
app->windowDelegate = msg((id)delegateClass, s("new"));
|
} else {
|
||||||
msg(mainWindow, s("setDelegate:"), app->windowDelegate);
|
class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldExit, "v@:@");
|
||||||
}
|
}
|
||||||
|
app->windowDelegate = msg((id)delegateClass, s("new"));
|
||||||
|
msg(mainWindow, s("setDelegate:"), app->windowDelegate);
|
||||||
|
|
||||||
app->mainWindow = mainWindow;
|
app->mainWindow = mainWindow;
|
||||||
}
|
}
|
||||||
@@ -1617,6 +1665,35 @@ void processUserDialogIcons(struct Application *app) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TrayMenuWillOpen(id self, SEL selector, id menu) {
|
||||||
|
// Extract tray menu id from menu
|
||||||
|
id trayMenuIDStr = objc_getAssociatedObject(menu, "trayMenuID");
|
||||||
|
const char* trayMenuID = cstr(trayMenuIDStr);
|
||||||
|
const char *message = concat("Mo", trayMenuID);
|
||||||
|
messageFromWindowCallback(message);
|
||||||
|
MEMFREE(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayMenuDidClose(id self, SEL selector, id menu) {
|
||||||
|
// Extract tray menu id from menu
|
||||||
|
id trayMenuIDStr = objc_getAssociatedObject(menu, "trayMenuID");
|
||||||
|
const char* trayMenuID = cstr(trayMenuIDStr);
|
||||||
|
const char *message = concat("Mc", trayMenuID);
|
||||||
|
messageFromWindowCallback(message);
|
||||||
|
MEMFREE(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void createTrayMenuDelegate() {
|
||||||
|
// Define delegate
|
||||||
|
trayMenuDelegateClass = objc_allocateClassPair((Class) c("NSObject"), "MenuDelegate", 0);
|
||||||
|
class_addProtocol(trayMenuDelegateClass, objc_getProtocol("NSMenuDelegate"));
|
||||||
|
class_addMethod(trayMenuDelegateClass, s("menuWillOpen:"), (IMP) TrayMenuWillOpen, "v@:@");
|
||||||
|
class_addMethod(trayMenuDelegateClass, s("menuDidClose:"), (IMP) TrayMenuDidClose, "v@:@");
|
||||||
|
|
||||||
|
// Script handler
|
||||||
|
objc_registerClassPair(trayMenuDelegateClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Run(struct Application *app, int argc, char **argv) {
|
void Run(struct Application *app, int argc, char **argv) {
|
||||||
|
|
||||||
@@ -1629,6 +1706,9 @@ void Run(struct Application *app, int argc, char **argv) {
|
|||||||
// Define delegate
|
// Define delegate
|
||||||
createDelegate(app);
|
createDelegate(app);
|
||||||
|
|
||||||
|
// Define tray delegate
|
||||||
|
createTrayMenuDelegate();
|
||||||
|
|
||||||
// Create the main window
|
// Create the main window
|
||||||
createMainWindow(app);
|
createMainWindow(app);
|
||||||
|
|
||||||
@@ -1799,7 +1879,7 @@ void Run(struct Application *app, int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup initial trays
|
// Setup initial trays
|
||||||
ShowTrayMenusInStore(app->trayMenuStore);
|
ShowTrayMenusInStore(TrayMenuStoreSingleton);
|
||||||
|
|
||||||
// Process dialog icons
|
// Process dialog icons
|
||||||
processUserDialogIcons(app);
|
processUserDialogIcons(app);
|
||||||
@@ -1817,6 +1897,10 @@ void SetActivationPolicy(struct Application* app, int policy) {
|
|||||||
app->activationPolicy = policy;
|
app->activationPolicy = policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HasURLHandlers(struct Application* app) {
|
||||||
|
app->hasURLHandlers = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Quit will stop the cocoa application and free up all the memory
|
// Quit will stop the cocoa application and free up all the memory
|
||||||
// used by the application
|
// used by the application
|
||||||
void Quit(struct Application *app) {
|
void Quit(struct Application *app) {
|
||||||
@@ -1872,7 +1956,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
|||||||
result->applicationMenu = NULL;
|
result->applicationMenu = NULL;
|
||||||
|
|
||||||
// Tray
|
// Tray
|
||||||
result->trayMenuStore = NewTrayMenuStore();
|
TrayMenuStoreSingleton = NewTrayMenuStore();
|
||||||
|
|
||||||
// Context Menus
|
// Context Menus
|
||||||
result->contextMenuStore = NewContextMenuStore();
|
result->contextMenuStore = NewContextMenuStore();
|
||||||
@@ -1890,6 +1974,8 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
|||||||
|
|
||||||
result->activationPolicy = NSApplicationActivationPolicyRegular;
|
result->activationPolicy = NSApplicationActivationPolicyRegular;
|
||||||
|
|
||||||
|
result->hasURLHandlers = 0;
|
||||||
|
|
||||||
return (void*) result;
|
return (void*) result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,5 +90,10 @@ func (a *Application) processPlatformSettings() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process URL Handlers
|
||||||
|
if a.config.Mac.URLHandlers != nil {
|
||||||
|
C.HasURLHandlers(a.app)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@
|
|||||||
// Macros to make it slightly more sane
|
// Macros to make it slightly more sane
|
||||||
#define msg objc_msgSend
|
#define msg objc_msgSend
|
||||||
|
|
||||||
|
#define kInternetEventClass 'GURL'
|
||||||
|
#define kAEGetURL 'GURL'
|
||||||
|
#define keyDirectObject '----'
|
||||||
|
|
||||||
#define c(str) (id)objc_getClass(str)
|
#define c(str) (id)objc_getClass(str)
|
||||||
#define s(str) sel_registerName(str)
|
#define s(str) sel_registerName(str)
|
||||||
#define u(str) sel_getUid(str)
|
#define u(str) sel_getUid(str)
|
||||||
@@ -118,4 +122,6 @@ void SetActivationPolicy(struct Application* app, int policy);
|
|||||||
|
|
||||||
void* lookupStringConstant(id constantName);
|
void* lookupStringConstant(id constantName);
|
||||||
|
|
||||||
|
void HasURLHandlers(struct Application* app);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -508,6 +508,7 @@ unsigned long parseModifiers(const char **modifiers) {
|
|||||||
const char *thisModifier = modifiers[0];
|
const char *thisModifier = modifiers[0];
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while( thisModifier != NULL ) {
|
while( thisModifier != NULL ) {
|
||||||
|
|
||||||
// Determine flags
|
// Determine flags
|
||||||
if( STREQ(thisModifier, "cmdorctrl") ) {
|
if( STREQ(thisModifier, "cmdorctrl") ) {
|
||||||
result |= NSEventModifierFlagCommand;
|
result |= NSEventModifierFlagCommand;
|
||||||
@@ -521,7 +522,7 @@ unsigned long parseModifiers(const char **modifiers) {
|
|||||||
if( STREQ(thisModifier, "super") ) {
|
if( STREQ(thisModifier, "super") ) {
|
||||||
result |= NSEventModifierFlagCommand;
|
result |= NSEventModifierFlagCommand;
|
||||||
}
|
}
|
||||||
if( STREQ(thisModifier, "control") ) {
|
if( STREQ(thisModifier, "ctrl") ) {
|
||||||
result |= NSEventModifierFlagControl;
|
result |= NSEventModifierFlagControl;
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
@@ -575,7 +576,7 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage) {
|
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate) {
|
||||||
id item = ALLOC("NSMenuItem");
|
id item = ALLOC("NSMenuItem");
|
||||||
|
|
||||||
// Create a MenuItemCallbackData
|
// Create a MenuItemCallbackData
|
||||||
@@ -584,9 +585,13 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
|
|||||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||||
|
|
||||||
id key = processAcceleratorKey(acceleratorkey);
|
if( !alternate ) {
|
||||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
id key = processAcceleratorKey(acceleratorkey);
|
||||||
s("menuItemCallback:"), key);
|
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||||
|
s("menuItemCallback:"), key);
|
||||||
|
} else {
|
||||||
|
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
|
||||||
|
}
|
||||||
|
|
||||||
if( tooltip != NULL ) {
|
if( tooltip != NULL ) {
|
||||||
msg(item, s("setToolTip:"), str(tooltip));
|
msg(item, s("setToolTip:"), str(tooltip));
|
||||||
@@ -599,7 +604,7 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
|
|||||||
id nsimage = ALLOC("NSImage");
|
id nsimage = ALLOC("NSImage");
|
||||||
msg(nsimage, s("initWithData:"), imageData);
|
msg(nsimage, s("initWithData:"), imageData);
|
||||||
if( templateImage ) {
|
if( templateImage ) {
|
||||||
msg(nsimage, s("template"), YES);
|
msg(nsimage, s("setTemplate:"), YES);
|
||||||
}
|
}
|
||||||
msg(item, s("setImage:"), nsimage);
|
msg(item, s("setImage:"), nsimage);
|
||||||
}
|
}
|
||||||
@@ -662,10 +667,16 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
|
|||||||
msg(item, s("autorelease"));
|
msg(item, s("autorelease"));
|
||||||
|
|
||||||
// Process modifiers
|
// Process modifiers
|
||||||
if( modifiers != NULL ) {
|
if( modifiers != NULL && !alternate) {
|
||||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// alternate
|
||||||
|
if( alternate ) {
|
||||||
|
msg(item, s("setAlternate:"), true);
|
||||||
|
msg(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
|
||||||
|
}
|
||||||
msg(parentMenu, s("addItem:"), item);
|
msg(parentMenu, s("addItem:"), item);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
@@ -726,6 +737,11 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
label = "(empty)";
|
label = "(empty)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Is this an alternate menu item?
|
||||||
|
bool alternate = false;
|
||||||
|
getJSONBool(item, "MacAlternate", &alternate);
|
||||||
|
|
||||||
const char *menuid = getJSONString(item, "ID");
|
const char *menuid = getJSONString(item, "ID");
|
||||||
if ( menuid == NULL) {
|
if ( menuid == NULL) {
|
||||||
menuid = "";
|
menuid = "";
|
||||||
@@ -781,7 +797,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
if( type != NULL ) {
|
if( type != NULL ) {
|
||||||
|
|
||||||
if( STREQ(type->string_, "Text")) {
|
if( STREQ(type->string_, "Text")) {
|
||||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage);
|
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate);
|
||||||
}
|
}
|
||||||
else if ( STREQ(type->string_, "Separator")) {
|
else if ( STREQ(type->string_, "Separator")) {
|
||||||
addSeparator(parentMenu);
|
addSeparator(parentMenu);
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
|
|||||||
|
|
||||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
|
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
|
||||||
|
|
||||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage);
|
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate);
|
||||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
||||||
void processMenuData(Menu *menu, JsonNode *menuData);
|
void processMenuData(Menu *menu, JsonNode *menuData);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include "traymenu_darwin.h"
|
#include "traymenu_darwin.h"
|
||||||
#include "trayicons.h"
|
#include "trayicons.h"
|
||||||
|
|
||||||
|
extern Class trayMenuDelegateClass;
|
||||||
|
|
||||||
// A cache for all our tray menu icons
|
// A cache for all our tray menu icons
|
||||||
// Global because it's a singleton
|
// Global because it's a singleton
|
||||||
struct hashmap_s trayIconCache;
|
struct hashmap_s trayIconCache;
|
||||||
@@ -35,6 +37,8 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
|
|||||||
// Create the menu
|
// Create the menu
|
||||||
result->menu = NewMenu(processedMenu);
|
result->menu = NewMenu(processedMenu);
|
||||||
|
|
||||||
|
result->delegate = NULL;
|
||||||
|
|
||||||
// Init tray status bar item
|
// Init tray status bar item
|
||||||
result->statusbaritem = NULL;
|
result->statusbaritem = NULL;
|
||||||
|
|
||||||
@@ -78,12 +82,20 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
|
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
|
||||||
|
|
||||||
|
// If we don't have the image in the icon cache then assume it's base64 encoded image data
|
||||||
|
if (trayImage == NULL) {
|
||||||
|
id data = ALLOC("NSData");
|
||||||
|
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(trayMenu->icon), 0);
|
||||||
|
trayImage = ALLOC("NSImage");
|
||||||
|
msg(trayImage, s("initWithData:"), imageData);
|
||||||
|
}
|
||||||
|
|
||||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||||
msg(statusBarButton, s("setImage:"), trayImage);
|
msg(statusBarButton, s("setImage:"), trayImage);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||||
|
|
||||||
// Create a status bar item if we don't have one
|
// Create a status bar item if we don't have one
|
||||||
@@ -91,7 +103,6 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
|||||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||||
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||||
msg(trayMenu->statusbaritem, s("retain"));
|
msg(trayMenu->statusbaritem, s("retain"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||||
@@ -105,6 +116,16 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
|||||||
|
|
||||||
// Update the menu
|
// Update the menu
|
||||||
id menu = GetMenu(trayMenu->menu);
|
id menu = GetMenu(trayMenu->menu);
|
||||||
|
objc_setAssociatedObject(menu, "trayMenuID", str(trayMenu->ID), OBJC_ASSOCIATION_ASSIGN);
|
||||||
|
|
||||||
|
// Create delegate
|
||||||
|
id trayMenuDelegate = msg((id)trayMenuDelegateClass, s("new"));
|
||||||
|
msg(menu, s("setDelegate:"), trayMenuDelegate);
|
||||||
|
objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN);
|
||||||
|
|
||||||
|
// Create menu delegate
|
||||||
|
trayMenu->delegate = trayMenuDelegate;
|
||||||
|
|
||||||
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +174,10 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
|
|||||||
trayMenu->statusbaritem = NULL;
|
trayMenu->statusbaritem = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( trayMenu->delegate != NULL ) {
|
||||||
|
msg(trayMenu->delegate, s("release"));
|
||||||
|
}
|
||||||
|
|
||||||
// Free the tray menu memory
|
// Free the tray menu memory
|
||||||
MEMFREE(trayMenu);
|
MEMFREE(trayMenu);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ typedef struct {
|
|||||||
|
|
||||||
JsonNode* processedJSON;
|
JsonNode* processedJSON;
|
||||||
|
|
||||||
|
id delegate;
|
||||||
|
|
||||||
} TrayMenu;
|
} TrayMenu;
|
||||||
|
|
||||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
TrayMenu* NewTrayMenu(const char *trayJSON);
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ TrayMenuStore* NewTrayMenuStore() {
|
|||||||
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
|
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&result->lock, NULL) != 0) {
|
||||||
|
printf("\n mutex init has failed\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,15 +30,19 @@ int dumpTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DumpTrayMenuStore(TrayMenuStore* store) {
|
void DumpTrayMenuStore(TrayMenuStore* store) {
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
|
hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
|
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
|
||||||
|
|
||||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
//TODO: check if there is already an entry for this menu
|
//TODO: check if there is already an entry for this menu
|
||||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||||
@@ -43,12 +52,13 @@ int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ShowTrayMenusInStore(TrayMenuStore* store) {
|
void ShowTrayMenusInStore(TrayMenuStore* store) {
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
|
if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
|
||||||
hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
|
hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||||
DeleteTrayMenu(e->data);
|
DeleteTrayMenu(e->data);
|
||||||
return -1;
|
return -1;
|
||||||
@@ -65,22 +75,39 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
|
|||||||
|
|
||||||
// Destroy tray menu map
|
// Destroy tray menu map
|
||||||
hashmap_destroy(&store->trayMenuMap);
|
hashmap_destroy(&store->trayMenuMap);
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&store->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||||
// Get the current menu
|
// Get the current menu
|
||||||
return hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
pthread_mutex_lock(&store->lock);
|
||||||
|
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||||
// Get the current menu
|
// Get the current menu
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
|
||||||
if (result == NULL ) {
|
if (result == NULL ) {
|
||||||
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
|
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* ID) {
|
||||||
|
|
||||||
|
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
|
hashmap_remove(&store->trayMenuMap, ID, strlen(ID));
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
DeleteTrayMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||||
// Parse the JSON
|
// Parse the JSON
|
||||||
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
||||||
@@ -105,7 +132,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
|||||||
// If we don't have a menu, we create one
|
// If we don't have a menu, we create one
|
||||||
if ( currentMenu == NULL ) {
|
if ( currentMenu == NULL ) {
|
||||||
// Store the new menu
|
// Store the new menu
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
|
||||||
// Show it
|
// Show it
|
||||||
ShowTrayMenu(newMenu);
|
ShowTrayMenu(newMenu);
|
||||||
@@ -116,7 +145,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
|||||||
// Save the status bar reference
|
// Save the status bar reference
|
||||||
newMenu->statusbaritem = currentMenu->statusbaritem;
|
newMenu->statusbaritem = currentMenu->statusbaritem;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
|
hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
|
||||||
// Delete the current menu
|
// Delete the current menu
|
||||||
DeleteMenu(currentMenu->menu);
|
DeleteMenu(currentMenu->menu);
|
||||||
@@ -125,9 +156,10 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
|||||||
// Free the tray menu memory
|
// Free the tray menu memory
|
||||||
MEMFREE(currentMenu);
|
MEMFREE(currentMenu);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
|
||||||
// Show the updated menu
|
// Show the updated menu
|
||||||
ShowTrayMenu(newMenu);
|
ShowTrayMenu(newMenu);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
#ifndef TRAYMENUSTORE_DARWIN_H
|
#ifndef TRAYMENUSTORE_DARWIN_H
|
||||||
#define TRAYMENUSTORE_DARWIN_H
|
#define TRAYMENUSTORE_DARWIN_H
|
||||||
|
|
||||||
|
#include "traymenu_darwin.h"
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
||||||
int dummy;
|
int dummy;
|
||||||
@@ -13,6 +17,8 @@ typedef struct {
|
|||||||
// It maps tray IDs to TrayMenu*
|
// It maps tray IDs to TrayMenu*
|
||||||
struct hashmap_s trayMenuMap;
|
struct hashmap_s trayMenuMap;
|
||||||
|
|
||||||
|
pthread_mutex_t lock;
|
||||||
|
|
||||||
} TrayMenuStore;
|
} TrayMenuStore;
|
||||||
|
|
||||||
TrayMenuStore* NewTrayMenuStore();
|
TrayMenuStore* NewTrayMenuStore();
|
||||||
@@ -22,6 +28,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
|
|||||||
void ShowTrayMenusInStore(TrayMenuStore* store);
|
void ShowTrayMenusInStore(TrayMenuStore* store);
|
||||||
void DeleteTrayMenuStore(TrayMenuStore* store);
|
void DeleteTrayMenuStore(TrayMenuStore* store);
|
||||||
|
|
||||||
|
TrayMenu* GetTrayMenuByID(TrayMenuStore* store, const char* menuID);
|
||||||
|
|
||||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
||||||
|
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* id);
|
||||||
|
|
||||||
#endif //TRAYMENUSTORE_DARWIN_H
|
#endif //TRAYMENUSTORE_DARWIN_H
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func Mkdir(dirname string) error {
|
|||||||
// Returns error on failure
|
// Returns error on failure
|
||||||
func MkDirs(fullPath string, mode ...os.FileMode) error {
|
func MkDirs(fullPath string, mode ...os.FileMode) error {
|
||||||
var perms os.FileMode
|
var perms os.FileMode
|
||||||
perms = 0700
|
perms = 0755
|
||||||
if len(mode) == 1 {
|
if len(mode) == 1 {
|
||||||
perms = mode[0]
|
perms = mode[0]
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ func CopyDir(src string, dst string) (err error) {
|
|||||||
return fmt.Errorf("destination already exists")
|
return fmt.Errorf("destination already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.MkdirAll(dst, si.Mode())
|
err = MkDirs(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type ProcessedMenuItem struct {
|
|||||||
// Image - base64 image data
|
// Image - base64 image data
|
||||||
Image string `json:",omitempty"`
|
Image string `json:",omitempty"`
|
||||||
MacTemplateImage bool `json:", omitempty"`
|
MacTemplateImage bool `json:", omitempty"`
|
||||||
|
MacAlternate bool `json:", omitempty"`
|
||||||
|
|
||||||
// Tooltip
|
// Tooltip
|
||||||
Tooltip string `json:",omitempty"`
|
Tooltip string `json:",omitempty"`
|
||||||
@@ -60,6 +61,7 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
|
|||||||
FontName: menuItem.FontName,
|
FontName: menuItem.FontName,
|
||||||
Image: menuItem.Image,
|
Image: menuItem.Image,
|
||||||
MacTemplateImage: menuItem.MacTemplateImage,
|
MacTemplateImage: menuItem.MacTemplateImage,
|
||||||
|
MacAlternate: menuItem.MacAlternate,
|
||||||
Tooltip: menuItem.Tooltip,
|
Tooltip: menuItem.Tooltip,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,23 @@ package menumanager
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var trayMenuID int
|
var trayMenuID int
|
||||||
var trayMenuIDMutex sync.Mutex
|
var trayMenuIDMutex sync.Mutex
|
||||||
|
|
||||||
func generateTrayID() string {
|
func generateTrayID() string {
|
||||||
|
var idStr string
|
||||||
trayMenuIDMutex.Lock()
|
trayMenuIDMutex.Lock()
|
||||||
result := fmt.Sprintf("%d", trayMenuID)
|
idStr = strconv.Itoa(trayMenuID)
|
||||||
trayMenuID++
|
trayMenuID++
|
||||||
trayMenuIDMutex.Unlock()
|
trayMenuIDMutex.Unlock()
|
||||||
return result
|
return idStr
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrayMenu struct {
|
type TrayMenu struct {
|
||||||
@@ -26,6 +29,7 @@ type TrayMenu struct {
|
|||||||
menuItemMap *MenuItemMap
|
menuItemMap *MenuItemMap
|
||||||
menu *menu.Menu
|
menu *menu.Menu
|
||||||
ProcessedMenu *WailsMenu
|
ProcessedMenu *WailsMenu
|
||||||
|
trayMenu *menu.TrayMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TrayMenu) AsJSON() (string, error) {
|
func (t *TrayMenu) AsJSON() (string, error) {
|
||||||
@@ -43,6 +47,7 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
|||||||
Icon: trayMenu.Icon,
|
Icon: trayMenu.Icon,
|
||||||
menu: trayMenu.Menu,
|
menu: trayMenu.Menu,
|
||||||
menuItemMap: NewMenuItemMap(),
|
menuItemMap: NewMenuItemMap(),
|
||||||
|
trayMenu: trayMenu,
|
||||||
}
|
}
|
||||||
|
|
||||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||||
@@ -51,6 +56,28 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) OnTrayMenuOpen(id string) {
|
||||||
|
trayMenu, ok := m.trayMenus[id]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if trayMenu.trayMenu.OnOpen == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go trayMenu.trayMenu.OnOpen()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) OnTrayMenuClose(id string) {
|
||||||
|
trayMenu, ok := m.trayMenus[id]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if trayMenu.trayMenu.OnClose == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go trayMenu.trayMenu.OnClose()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||||
newTrayMenu := NewTrayMenu(trayMenu)
|
newTrayMenu := NewTrayMenu(trayMenu)
|
||||||
|
|
||||||
@@ -65,6 +92,14 @@ func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
|||||||
return newTrayMenu.AsJSON()
|
return newTrayMenu.AsJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) GetTrayID(trayMenu *menu.TrayMenu) (string, error) {
|
||||||
|
trayID, exists := m.trayMenuPointers[trayMenu]
|
||||||
|
if !exists {
|
||||||
|
return "", fmt.Errorf("Unable to find menu ID for tray menu!")
|
||||||
|
}
|
||||||
|
return trayID, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetTrayMenu updates or creates a menu
|
// SetTrayMenu updates or creates a menu
|
||||||
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type Client interface {
|
|||||||
SetTrayMenu(trayMenuJSON string)
|
SetTrayMenu(trayMenuJSON string)
|
||||||
UpdateTrayMenuLabel(JSON string)
|
UpdateTrayMenuLabel(JSON string)
|
||||||
UpdateContextMenu(contextMenuJSON string)
|
UpdateContextMenu(contextMenuJSON string)
|
||||||
|
DeleteTrayMenuByID(id string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchClient is what the frontends use to interface with the
|
// DispatchClient is what the frontends use to interface with the
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ func menuMessageParser(message string) (*parsedMessage, error) {
|
|||||||
callbackid := message[2:]
|
callbackid := message[2:]
|
||||||
topic = "menu:clicked"
|
topic = "menu:clicked"
|
||||||
data = callbackid
|
data = callbackid
|
||||||
|
case 'o':
|
||||||
|
callbackid := message[2:]
|
||||||
|
topic = "menu:ontrayopen"
|
||||||
|
data = callbackid
|
||||||
|
case 'c':
|
||||||
|
callbackid := message[2:]
|
||||||
|
topic = "menu:ontrayclose"
|
||||||
|
data = callbackid
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid menu message: %s", message)
|
return nil, fmt.Errorf("invalid menu message: %s", message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
|
|||||||
'M': menuMessageParser,
|
'M': menuMessageParser,
|
||||||
'T': trayMessageParser,
|
'T': trayMessageParser,
|
||||||
'X': contextMenusMessageParser,
|
'X': contextMenusMessageParser,
|
||||||
|
'U': urlMessageParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse will attempt to parse the given message
|
// Parse will attempt to parse the given message
|
||||||
func Parse(message string) (*parsedMessage, error) {
|
func Parse(message string) (*parsedMessage, error) {
|
||||||
|
|
||||||
if len(message) == 0 {
|
if len(message) == 0 {
|
||||||
return nil, fmt.Errorf("MessageParser received blank message");
|
return nil, fmt.Errorf("MessageParser received blank message")
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMethod := messageParsers[message[0]]
|
parseMethod := messageParsers[message[0]]
|
||||||
|
|||||||
20
v2/internal/messagedispatcher/message/url.go
Normal file
20
v2/internal/messagedispatcher/message/url.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// urlMessageParser does what it says on the tin!
|
||||||
|
func urlMessageParser(message string) (*parsedMessage, error) {
|
||||||
|
|
||||||
|
// Sanity check: URL messages must be at least 2 bytes
|
||||||
|
if len(message) < 2 {
|
||||||
|
return nil, fmt.Errorf("log message was an invalid length")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch on the log type
|
||||||
|
switch message[1] {
|
||||||
|
case 'C':
|
||||||
|
return &parsedMessage{Topic: "url:handler", Data: message[2:]}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("url message type '%c' invalid", message[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -527,6 +527,17 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
|||||||
for _, client := range d.clients {
|
for _, client := range d.clients {
|
||||||
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
|
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
|
||||||
}
|
}
|
||||||
|
case "deletetraymenu":
|
||||||
|
traymenuid, ok := result.Data().(string)
|
||||||
|
if !ok {
|
||||||
|
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
|
||||||
|
result.Data())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, client := range d.clients {
|
||||||
|
client.frontend.DeleteTrayMenuByID(traymenuid)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
d.logger.Error("Unknown menufrontend command: %s", command)
|
d.logger.Error("Unknown menufrontend command: %s", command)
|
||||||
|
|||||||
@@ -649,6 +649,18 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteTrayMenu(id) {
|
||||||
|
trays.update((current) => {
|
||||||
|
// Remove existing if it exists, else add
|
||||||
|
const index = current.findIndex(item => item.ID === id);
|
||||||
|
if ( index === -1 ) {
|
||||||
|
return log("ERROR: Attempted to delete tray index ")
|
||||||
|
}
|
||||||
|
current.splice(index, 1);
|
||||||
|
return current;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let selectedMenu = writable(null);
|
let selectedMenu = writable(null);
|
||||||
|
|
||||||
function fade(node, { delay = 0, duration = 400, easing = identity } = {}) {
|
function fade(node, { delay = 0, duration = 400, easing = identity } = {}) {
|
||||||
@@ -1666,6 +1678,11 @@
|
|||||||
let trayLabelData = JSON.parse(updateTrayLabelJSON);
|
let trayLabelData = JSON.parse(updateTrayLabelJSON);
|
||||||
updateTrayLabel(trayLabelData);
|
updateTrayLabel(trayLabelData);
|
||||||
break
|
break
|
||||||
|
case 'D':
|
||||||
|
// Delete Tray Menu
|
||||||
|
const id = trayMessage.slice(1);
|
||||||
|
deleteTrayMenu(id);
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
log('Unknown tray message: ' + message.data);
|
log('Unknown tray message: ' + message.data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wails/runtime",
|
"name": "@wails/runtime",
|
||||||
"version": "1.3.10",
|
"version": "1.3.12",
|
||||||
"description": "Wails V2 Javascript runtime library",
|
"description": "Wails V2 Javascript runtime library",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"types": "runtime.d.ts",
|
"types": "runtime.d.ts",
|
||||||
|
|||||||
@@ -49,4 +49,16 @@ export function updateTrayLabel(tray) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteTrayMenu(id) {
|
||||||
|
trays.update((current) => {
|
||||||
|
// Remove existing if it exists, else add
|
||||||
|
const index = current.findIndex(item => item.ID === id);
|
||||||
|
if ( index === -1 ) {
|
||||||
|
return log("ERROR: Attempted to delete tray index ", id, "but it doesn't exist")
|
||||||
|
}
|
||||||
|
current.splice(index, 1);
|
||||||
|
return current;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export let selectedMenu = writable(null);
|
export let selectedMenu = writable(null);
|
||||||
@@ -10,7 +10,7 @@ The lightweight framework for web-like apps
|
|||||||
/* jshint esversion: 6 */
|
/* jshint esversion: 6 */
|
||||||
|
|
||||||
|
|
||||||
import {setTray, hideOverlay, showOverlay, updateTrayLabel} from "./store";
|
import {setTray, hideOverlay, showOverlay, updateTrayLabel, deleteTrayMenu} from "./store";
|
||||||
import {log} from "./log";
|
import {log} from "./log";
|
||||||
|
|
||||||
let websocket = null;
|
let websocket = null;
|
||||||
@@ -154,6 +154,11 @@ function handleMessage(message) {
|
|||||||
let trayLabelData = JSON.parse(updateTrayLabelJSON)
|
let trayLabelData = JSON.parse(updateTrayLabelJSON)
|
||||||
updateTrayLabel(trayLabelData)
|
updateTrayLabel(trayLabelData)
|
||||||
break
|
break
|
||||||
|
case 'D':
|
||||||
|
// Delete Tray Menu
|
||||||
|
const id = trayMessage.slice(1);
|
||||||
|
deleteTrayMenu(id)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
log('Unknown tray message: ' + message.data);
|
log('Unknown tray message: ' + message.data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type Menu interface {
|
|||||||
UpdateApplicationMenu()
|
UpdateApplicationMenu()
|
||||||
UpdateContextMenu(contextMenu *menu.ContextMenu)
|
UpdateContextMenu(contextMenu *menu.ContextMenu)
|
||||||
SetTrayMenu(trayMenu *menu.TrayMenu)
|
SetTrayMenu(trayMenu *menu.TrayMenu)
|
||||||
|
DeleteTrayMenu(trayMenu *menu.TrayMenu)
|
||||||
UpdateTrayMenuLabel(trayMenu *menu.TrayMenu)
|
UpdateTrayMenuLabel(trayMenu *menu.TrayMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,3 +40,7 @@ func (m *menuRuntime) SetTrayMenu(trayMenu *menu.TrayMenu) {
|
|||||||
func (m *menuRuntime) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) {
|
func (m *menuRuntime) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) {
|
||||||
m.bus.Publish("menu:updatetraymenulabel", trayMenu)
|
m.bus.Publish("menu:updatetraymenulabel", trayMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *menuRuntime) DeleteTrayMenu(trayMenu *menu.TrayMenu) {
|
||||||
|
m.bus.Publish("menu:deletetraymenu", trayMenu)
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ func (m *Menu) Start() error {
|
|||||||
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
||||||
menuMessageType := splitTopic[1]
|
menuMessageType := splitTopic[1]
|
||||||
switch menuMessageType {
|
switch menuMessageType {
|
||||||
|
case "ontrayopen":
|
||||||
|
trayID := menuMessage.Data().(string)
|
||||||
|
m.menuManager.OnTrayMenuOpen(trayID)
|
||||||
|
case "ontrayclose":
|
||||||
|
trayID := menuMessage.Data().(string)
|
||||||
|
m.menuManager.OnTrayMenuClose(trayID)
|
||||||
case "clicked":
|
case "clicked":
|
||||||
if len(splitTopic) != 2 {
|
if len(splitTopic) != 2 {
|
||||||
m.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
m.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
||||||
@@ -137,6 +143,17 @@ func (m *Menu) Start() error {
|
|||||||
// Notify frontend of menu change
|
// Notify frontend of menu change
|
||||||
m.bus.Publish("menufrontend:settraymenu", updatedMenu)
|
m.bus.Publish("menufrontend:settraymenu", updatedMenu)
|
||||||
|
|
||||||
|
case "deletetraymenu":
|
||||||
|
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
||||||
|
trayID, err := m.menuManager.GetTrayID(trayMenu)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Trace("%s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify frontend of menu change
|
||||||
|
m.bus.Publish("menufrontend:deletetraymenu", trayID)
|
||||||
|
|
||||||
case "updatetraymenulabel":
|
case "updatetraymenulabel":
|
||||||
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
||||||
updatedLabel, err := m.menuManager.UpdateTrayMenuLabel(trayMenu)
|
updatedLabel, err := m.menuManager.UpdateTrayMenuLabel(trayMenu)
|
||||||
|
|||||||
98
v2/internal/subsystem/url.go
Normal file
98
v2/internal/subsystem/url.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package subsystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// URL is the URL Handler subsystem. It handles messages with topics starting
|
||||||
|
// with "url:"
|
||||||
|
type URL struct {
|
||||||
|
urlChannel <-chan *servicebus.Message
|
||||||
|
|
||||||
|
// quit flag
|
||||||
|
shouldQuit bool
|
||||||
|
|
||||||
|
// Logger!
|
||||||
|
logger *logger.Logger
|
||||||
|
|
||||||
|
// Context for shutdown
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
|
||||||
|
// internal waitgroup
|
||||||
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
handlers map[string]func(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewURL creates a new log subsystem
|
||||||
|
func NewURL(bus *servicebus.ServiceBus, logger *logger.Logger, handlers map[string]func(string)) (*URL, error) {
|
||||||
|
|
||||||
|
// Subscribe to log messages
|
||||||
|
urlChannel, err := bus.Subscribe("url")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
result := &URL{
|
||||||
|
urlChannel: urlChannel,
|
||||||
|
logger: logger,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
handlers: handlers,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the subsystem
|
||||||
|
func (u *URL) Start() error {
|
||||||
|
|
||||||
|
u.wg.Add(1)
|
||||||
|
|
||||||
|
// Spin off a go routine
|
||||||
|
go func() {
|
||||||
|
defer u.logger.Trace("URL Shutdown")
|
||||||
|
|
||||||
|
for u.shouldQuit == false {
|
||||||
|
select {
|
||||||
|
case <-u.ctx.Done():
|
||||||
|
u.wg.Done()
|
||||||
|
return
|
||||||
|
case urlMessage := <-u.urlChannel:
|
||||||
|
// Guard against nil messages
|
||||||
|
if urlMessage == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
messageType := strings.TrimPrefix(urlMessage.Topic(), "url:")
|
||||||
|
switch messageType {
|
||||||
|
case "handler":
|
||||||
|
url := urlMessage.Data().(string)
|
||||||
|
splitURL := strings.Split(url, ":")
|
||||||
|
protocol := splitURL[0]
|
||||||
|
callback, ok := u.handlers[protocol]
|
||||||
|
if ok {
|
||||||
|
go callback(url)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
u.logger.Error("unknown url message: %+v", urlMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *URL) Close() {
|
||||||
|
u.cancel()
|
||||||
|
u.wg.Wait()
|
||||||
|
}
|
||||||
@@ -12,20 +12,19 @@ type Basic struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newBasic creates a new Basic application struct
|
// newBasic creates a new Basic application struct
|
||||||
func newBasic() *Basic {
|
func NewBasic() *Basic {
|
||||||
return &Basic{}
|
return &Basic{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WailsInit is called at application startup
|
// startup is called at application startup
|
||||||
func (b *Basic) WailsInit(runtime *wails.Runtime) error {
|
func (b *Basic) startup(runtime *wails.Runtime) {
|
||||||
// Perform your setup here
|
// Perform your setup here
|
||||||
b.runtime = runtime
|
b.runtime = runtime
|
||||||
runtime.Window.SetTitle("{{.ProjectName}}")
|
runtime.Window.SetTitle("{{.ProjectName}}")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WailsShutdown is called at application termination
|
// shutdown is called at application termination
|
||||||
func (b *Basic) WailsShutdown() {
|
func (b *Basic) shutdown() {
|
||||||
// Perform your teardown here
|
// Perform your teardown here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<link rel="stylesheet" href="/main.css">
|
<link rel="stylesheet" href="/main.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body data-wails-drag>
|
||||||
<div id="logo"></div>
|
<div id="logo"></div>
|
||||||
<div id="input">
|
<div id="input">
|
||||||
<input id="name" type="text"></input>
|
<input id="name" type="text"></input>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,21 +1,38 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/wailsapp/wails/v2"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2"
|
||||||
|
"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/mac"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
// Create application with options
|
// Create application with options
|
||||||
app, err := wails.CreateApp("{{.ProjectName}}", 1024, 768)
|
app := NewBasic()
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Bind(newBasic())
|
err := wails.Run(&options.App{
|
||||||
|
Title: "{{.ProjectName}}",
|
||||||
err = app.Run()
|
Width: 800,
|
||||||
|
Height: 600,
|
||||||
|
DisableResize: true,
|
||||||
|
Mac: &mac.Options{
|
||||||
|
WebviewIsTransparent: true,
|
||||||
|
WindowBackgroundIsTranslucent: true,
|
||||||
|
TitleBar: mac.TitleBarHiddenInset(),
|
||||||
|
Menu: menu.DefaultMacMenu(),
|
||||||
|
},
|
||||||
|
LogLevel: logger.DEBUG,
|
||||||
|
Startup: app.startup,
|
||||||
|
Shutdown: app.shutdown,
|
||||||
|
Bind: []interface{}{
|
||||||
|
app,
|
||||||
|
},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,13 @@ func (b *BaseBuilder) CleanUp() {
|
|||||||
// CompileProject compiles the project
|
// CompileProject compiles the project
|
||||||
func (b *BaseBuilder) CompileProject(options *Options) error {
|
func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||||
|
|
||||||
|
// Run go mod tidy first
|
||||||
|
cmd := exec.Command(options.Compiler, "mod", "tidy")
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Default go build command
|
// Default go build command
|
||||||
commands := slicer.String([]string{"build"})
|
commands := slicer.String([]string{"build"})
|
||||||
|
|
||||||
@@ -188,7 +195,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
|
|
||||||
// Get application build directory
|
// Get application build directory
|
||||||
appDir := options.BuildDirectory
|
appDir := options.BuildDirectory
|
||||||
err := cleanBuildDirectory(options)
|
err = cleanBuildDirectory(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -211,14 +218,11 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
options.CompiledBinary = compiledBinary
|
options.CompiledBinary = compiledBinary
|
||||||
|
|
||||||
// Create the command
|
// Create the command
|
||||||
cmd := exec.Command(options.Compiler, commands.AsSlice()...)
|
cmd = exec.Command(options.Compiler, commands.AsSlice()...)
|
||||||
|
|
||||||
// Set the directory
|
// Set the directory
|
||||||
cmd.Dir = b.projectData.Path
|
cmd.Dir = b.projectData.Path
|
||||||
|
|
||||||
// Set GO111MODULE environment variable
|
|
||||||
cmd.Env = append(os.Environ(), "GO111MODULE=on")
|
|
||||||
|
|
||||||
// Add CGO flags
|
// Add CGO flags
|
||||||
// We use the project/build dir as a temporary place for our generated c headers
|
// We use the project/build dir as a temporary place for our generated c headers
|
||||||
buildBaseDir, err := fs.RelativeToCwd("build")
|
buildBaseDir, err := fs.RelativeToCwd("build")
|
||||||
@@ -226,7 +230,16 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Env = append(os.Environ(), fmt.Sprintf("CGO_CFLAGS=-I%s", buildBaseDir))
|
cmd.Env = os.Environ() // inherit env
|
||||||
|
|
||||||
|
// Use upsertEnv so we don't overwrite user's CGO_CFLAGS
|
||||||
|
cmd.Env = upsertEnv(cmd.Env, "CGO_CFLAGS", func(v string) string {
|
||||||
|
if v != "" {
|
||||||
|
v += " "
|
||||||
|
}
|
||||||
|
v += "-I" + buildBaseDir
|
||||||
|
return v
|
||||||
|
})
|
||||||
|
|
||||||
// Setup buffers
|
// Setup buffers
|
||||||
var stdo, stde bytes.Buffer
|
var stdo, stde bytes.Buffer
|
||||||
@@ -384,3 +397,22 @@ func (b *BaseBuilder) ExtractAssets() (*html.AssetBundle, error) {
|
|||||||
// Read in html
|
// Read in html
|
||||||
return html.NewAssetBundle(b.projectData.HTML)
|
return html.NewAssetBundle(b.projectData.HTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func upsertEnv(env []string, key string, update func(v string) string) []string {
|
||||||
|
newEnv := make([]string, len(env), len(env)+1)
|
||||||
|
found := false
|
||||||
|
for i := range env {
|
||||||
|
if strings.HasPrefix(env[i], key+"=") {
|
||||||
|
eqIndex := strings.Index(env[i], "=")
|
||||||
|
val := env[i][eqIndex+1:]
|
||||||
|
newEnv[i] = fmt.Sprintf("%s=%v", key, update(val))
|
||||||
|
found = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newEnv[i] = env[i]
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
newEnv = append(newEnv, fmt.Sprintf("%s=%v", key, update("")))
|
||||||
|
}
|
||||||
|
return newEnv
|
||||||
|
}
|
||||||
|
|||||||
31
v2/pkg/commands/build/base_test.go
Normal file
31
v2/pkg/commands/build/base_test.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestUpdateEnv(t *testing.T) {
|
||||||
|
|
||||||
|
env := []string{"one=1", "two=a=b", "three="}
|
||||||
|
newEnv := upsertEnv(env, "two", func(v string) string {
|
||||||
|
return v + "+added"
|
||||||
|
})
|
||||||
|
newEnv = upsertEnv(newEnv, "newVar", func(v string) string {
|
||||||
|
return "added"
|
||||||
|
})
|
||||||
|
newEnv = upsertEnv(newEnv, "three", func(v string) string {
|
||||||
|
return "3"
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(newEnv) != 4 {
|
||||||
|
t.Errorf("expected: 4, got: %d", len(newEnv))
|
||||||
|
}
|
||||||
|
if newEnv[1] != "two=a=b+added" {
|
||||||
|
t.Errorf("expected: \"two=a=b+added\", got: %q", newEnv[1])
|
||||||
|
}
|
||||||
|
if newEnv[2] != "three=3" {
|
||||||
|
t.Errorf("expected: \"three=3\", got: %q", newEnv[2])
|
||||||
|
}
|
||||||
|
if newEnv[3] != "newVar=added" {
|
||||||
|
t.Errorf("expected: \"newVar=added\", got: %q", newEnv[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ type Options struct {
|
|||||||
BuildDirectory string // Directory to use for building the application
|
BuildDirectory string // Directory to use for building the application
|
||||||
CompiledBinary string // Fully qualified path to the compiled binary
|
CompiledBinary string // Fully qualified path to the compiled binary
|
||||||
KeepAssets bool // /Keep the generated assets/files
|
KeepAssets bool // /Keep the generated assets/files
|
||||||
|
AppleIdentity string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetModeAsString returns the current mode as a string
|
// GetModeAsString returns the current mode as a string
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package build
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -52,6 +54,14 @@ func packageApplication(options *Options) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sign app if needed
|
||||||
|
if options.AppleIdentity != "" {
|
||||||
|
err = signApplication(options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,3 +186,21 @@ func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
|
|||||||
}()
|
}()
|
||||||
return icns.Encode(dest, srcImg)
|
return icns.Encode(dest, srcImg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func signApplication(options *Options) error {
|
||||||
|
bundlename := filepath.Join(options.BuildDirectory, options.ProjectData.Name+".app")
|
||||||
|
identity := fmt.Sprintf(`"%s"`, options.AppleIdentity)
|
||||||
|
cmd := exec.Command("codesign", "--sign", identity, "--deep", "--force", "--verbose", "--timestamp", "--options", "runtime", bundlename)
|
||||||
|
var stdo, stde bytes.Buffer
|
||||||
|
cmd.Stdout = &stdo
|
||||||
|
cmd.Stderr = &stde
|
||||||
|
|
||||||
|
// Run command
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
// Format error if we have one
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s\n%s", err, string(stde.Bytes()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
66
v2/pkg/logger/filelogger.go
Normal file
66
v2/pkg/logger/filelogger.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileLogger is a utility to log messages to a number of destinations
|
||||||
|
type FileLogger struct {
|
||||||
|
filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileLogger creates a new Logger.
|
||||||
|
func NewFileLogger(filename string) Logger {
|
||||||
|
return &FileLogger{
|
||||||
|
filename: filename,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print works like Sprintf.
|
||||||
|
func (l *FileLogger) Print(message string) {
|
||||||
|
f, err := os.OpenFile(l.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := f.WriteString(message); err != nil {
|
||||||
|
f.Close()
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *FileLogger) Println(message string) {
|
||||||
|
l.Print(message + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Trace(message string) {
|
||||||
|
l.Println("TRACE | " + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Debug(message string) {
|
||||||
|
l.Println("DEBUG | " + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Info(message string) {
|
||||||
|
l.Println("INFO | " + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Warning(message string) {
|
||||||
|
l.Println("WARN | " + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Error(message string) {
|
||||||
|
l.Println("ERROR | " + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Fatal(message string) {
|
||||||
|
l.Println("FATAL | " + message)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
@@ -10,15 +10,15 @@ type Modifier string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// CmdOrCtrlKey represents Command on Mac and Control on other platforms
|
// CmdOrCtrlKey represents Command on Mac and Control on other platforms
|
||||||
CmdOrCtrlKey Modifier = "CmdOrCtrl"
|
CmdOrCtrlKey Modifier = "cmdorctrl"
|
||||||
// OptionOrAltKey represents Option on Mac and Alt on other platforms
|
// OptionOrAltKey represents Option on Mac and Alt on other platforms
|
||||||
OptionOrAltKey Modifier = "OptionOrAlt"
|
OptionOrAltKey Modifier = "optionoralt"
|
||||||
// ShiftKey represents the shift key on all systems
|
// ShiftKey represents the shift key on all systems
|
||||||
ShiftKey Modifier = "Shift"
|
ShiftKey Modifier = "shift"
|
||||||
// SuperKey represents Command on Mac and the Windows key on the other platforms
|
// SuperKey represents Command on Mac and the Windows key on the other platforms
|
||||||
SuperKey Modifier = "Super"
|
SuperKey Modifier = "super"
|
||||||
// ControlKey represents the control key on all systems
|
// ControlKey represents the control key on all systems
|
||||||
ControlKey Modifier = "Control"
|
ControlKey Modifier = "ctrl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var modifierMap = map[string]Modifier{
|
var modifierMap = map[string]Modifier{
|
||||||
@@ -30,7 +30,8 @@ var modifierMap = map[string]Modifier{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseModifier(text string) (*Modifier, error) {
|
func parseModifier(text string) (*Modifier, error) {
|
||||||
result, valid := modifierMap[text]
|
lowertext := strings.ToLower(text)
|
||||||
|
result, valid := modifierMap[lowertext]
|
||||||
if !valid {
|
if !valid {
|
||||||
return nil, fmt.Errorf("'%s' is not a valid modifier", text)
|
return nil, fmt.Errorf("'%s' is not a valid modifier", text)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,12 @@ type MenuItem struct {
|
|||||||
// Image - base64 image data
|
// Image - base64 image data
|
||||||
Image string
|
Image string
|
||||||
|
|
||||||
// MacTemplateImage indicates that on a mac, this image is a template image
|
// MacTemplateImage indicates that on a Mac, this image is a template image
|
||||||
MacTemplateImage bool
|
MacTemplateImage bool
|
||||||
|
|
||||||
|
// MacAlternate indicates that this item is an alternative to the previous menu item
|
||||||
|
MacAlternate bool
|
||||||
|
|
||||||
// Tooltip
|
// Tooltip
|
||||||
Tooltip string
|
Tooltip string
|
||||||
|
|
||||||
|
|||||||
@@ -14,4 +14,10 @@ type TrayMenu struct {
|
|||||||
|
|
||||||
// Menu is the initial menu we wish to use for the tray
|
// Menu is the initial menu we wish to use for the tray
|
||||||
Menu *Menu
|
Menu *Menu
|
||||||
|
|
||||||
|
// OnOpen is called when the Menu is opened
|
||||||
|
OnOpen func()
|
||||||
|
|
||||||
|
// OnClose is called when the Menu is closed
|
||||||
|
OnClose func()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ type Options struct {
|
|||||||
TrayMenus []*menu.TrayMenu
|
TrayMenus []*menu.TrayMenu
|
||||||
ContextMenus []*menu.ContextMenu
|
ContextMenus []*menu.ContextMenu
|
||||||
ActivationPolicy ActivationPolicy
|
ActivationPolicy ActivationPolicy
|
||||||
|
URLHandlers map[string]func(string)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user