Compare commits

...

37 Commits

Author SHA1 Message Date
Lea Anthony
eb4bff89da v2.0.0-alpha.44 2021-03-04 06:18:31 +11:00
Lea Anthony
c66dc777f3 Remove debug logging 2021-03-04 06:18:11 +11:00
Lea Anthony
9003462457 v2.0.0-alpha.43 2021-03-04 06:09:17 +11:00
Lea Anthony
e124f0a220 Support Alternative menu items 2021-03-04 06:07:45 +11:00
Lea Anthony
c6d3f57712 v2.0.0-alpha.42 2021-03-01 08:49:31 +11:00
Lea Anthony
b4c669ff86 Support custom protocols 2021-02-28 22:08:23 +11:00
Lea Anthony
2d1b2c0947 Guard app signing 2021-02-28 15:29:15 +11:00
Lea Anthony
4a0c5aa785 v2.0.0-alpha.41 2021-02-27 20:33:42 +11:00
Lea Anthony
f48d7f8f60 Add support for -sign 2021-02-27 20:32:29 +11:00
Lea Anthony
651f24f641 update vanilla template 2021-02-27 20:06:49 +11:00
Lea Anthony
8fd77148ca update vanilla template 2021-02-27 16:59:16 +11:00
Lea Anthony
0dc0762fdf update vanilla template 2021-02-27 16:45:30 +11:00
Lea Anthony
1a92550709 update vanilla template 2021-02-27 16:08:53 +11:00
Lea Anthony
bffc15bc14 fix: terminate app on window close 2021-02-27 14:49:44 +11:00
Lea Anthony
198d206c46 Update basic template to new API 2021-02-27 14:07:27 +11:00
Lea Anthony
bb8e848ef6 Run go mod tidy before compilation 2021-02-27 14:03:54 +11:00
Lea Anthony
bac3e9e5c1 Fix asset dir perms 2021-02-27 13:54:39 +11:00
Lea Anthony
bc5eddeb66 v2.0.0-alpha.40 2021-02-26 15:31:37 +11:00
Lea Anthony
8e7258d812 Add locking for tray operations 2021-02-26 15:23:39 +11:00
Lea Anthony
7118762cec v2.0.0-alpha.39 2021-02-25 22:05:38 +11:00
Lea Anthony
6af92cf0a4 Delete Tray for bridge 2021-02-25 19:57:36 +11:00
matryer
1bb91634f7 avoid fmt in happy path 2021-02-25 07:39:42 +00:00
Lea Anthony
f71ce7913f v2.0.0-alpha.38 2021-02-24 21:50:39 +11:00
Lea Anthony
53db687a26 Support DeleteTrayMenuByID 2021-02-24 21:49:55 +11:00
Lea Anthony
13939d3d6b v2.0.0-alpha.37 2021-02-23 20:15:01 +11:00
Lea Anthony
552c6b8711 fix: modifiers 2021-02-23 18:57:59 +11:00
Lea Anthony
feee2b3db2 v2.0.0-alpha.36 2021-02-23 08:53:26 +11:00
Lea Anthony
9889c2bdbb Support Activation Policy 2021-02-23 08:52:56 +11:00
Lea Anthony
2432fccf71 v2.0.0-alpha.35 2021-02-22 20:17:22 +11:00
Lea Anthony
70510fd180 @wails/runtime v1.3.10 2021-02-22 20:16:14 +11:00
Lea Anthony
17c6201469 Menu off by default in dev. Toggle with backtick. 2021-02-22 20:14:44 +11:00
Lea Anthony
0f209c8900 v2.0.0-alpha.34 2021-02-22 19:39:07 +11:00
Lea Anthony
cbf043585c v2.0.0-alpha.33 2021-02-22 19:33:44 +11:00
Lea Anthony
5ae621ceaa Start signal handler a little later 2021-02-22 19:33:18 +11:00
Lea Anthony
1231b59443 better signal handling for shutdown 2021-02-22 17:39:33 +11:00
Lea Anthony
b18d4fbf41 v2.0.0-alpha.32 2021-02-22 09:00:47 +11:00
Lea Anthony
9ec5605e63 fix: loglevel duplication 2021-02-22 09:00:09 +11:00
43 changed files with 622 additions and 171 deletions

View File

@@ -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)

View File

@@ -1,3 +1,3 @@
package main package main
var version = "v2.0.0-alpha.31" var version = "v2.0.0-alpha.44"

View File

@@ -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
@@ -117,14 +118,6 @@ func (a *App) Run() error {
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup) parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext) ctx, cancel := context.WithCancel(parentContext)
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
// Start the service bus // Start the service bus
a.servicebus.Debug() a.servicebus.Debug()
err = a.servicebus.Start() err = a.servicebus.Start()
@@ -132,7 +125,7 @@ func (a *App) Run() error {
return err return err
} }
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback) runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
if err != nil { if err != nil {
return err return err
} }
@@ -168,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 {
@@ -207,6 +213,14 @@ func (a *App) Run() error {
return err return err
} }
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
err = a.window.Run(dispatcher, bindingDump, a.debug) err = a.window.Run(dispatcher, bindingDump, a.debug)
a.logger.Trace("Ffenestri.Run() exited") a.logger.Trace("Ffenestri.Run() exited")
if err != nil { if err != nil {
@@ -231,5 +245,10 @@ func (a *App) Run() error {
return err return err
} }
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil return nil
} }

View File

@@ -4,14 +4,11 @@ package app
import ( import (
"context" "context"
"flag"
"strings"
"sync" "sync"
"github.com/wailsapp/wails/v2/internal/bridge" "github.com/wailsapp/wails/v2/internal/bridge"
"github.com/wailsapp/wails/v2/internal/menumanager" "github.com/wailsapp/wails/v2/internal/menumanager"
clilogger "github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/binding"
@@ -70,23 +67,6 @@ func CreateApp(appoptions *options.App) (*App, error) {
// Set up logger // Set up logger
myLogger := logger.New(appoptions.Logger) myLogger := logger.New(appoptions.Logger)
loglevel := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
flag.Parse()
if len(*loglevel) > 0 {
switch strings.ToLower(*loglevel) {
case "trace":
myLogger.SetLogLevel(clilogger.TRACE)
case "info":
myLogger.SetLogLevel(clilogger.INFO)
case "warning":
myLogger.SetLogLevel(clilogger.WARNING)
case "error":
myLogger.SetLogLevel(clilogger.ERROR)
default:
myLogger.SetLogLevel(appoptions.LogLevel)
}
}
// Create the menu manager // Create the menu manager
menuManager := menumanager.NewManager() menuManager := menumanager.NewManager()
@@ -138,14 +118,6 @@ func (a *App) Run() error {
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup) parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext) ctx, cancel := context.WithCancel(parentContext)
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
// Start the service bus // Start the service bus
a.servicebus.Debug() a.servicebus.Debug()
err = a.servicebus.Start() err = a.servicebus.Start()
@@ -153,7 +125,7 @@ func (a *App) Run() error {
return err return err
} }
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback) runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
if err != nil { if err != nil {
return err return err
} }
@@ -231,6 +203,14 @@ func (a *App) Run() error {
// Generate backend.js // Generate backend.js
a.bindings.GenerateBackendJS() a.bindings.GenerateBackendJS()
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
err = a.bridge.Run(dispatcher, a.menuManager, bindingDump, a.debug) err = a.bridge.Run(dispatcher, a.menuManager, bindingDump, a.debug)
a.logger.Trace("Bridge.Run() exited") a.logger.Trace("Bridge.Run() exited")
if err != nil { if err != nil {
@@ -255,6 +235,10 @@ func (a *App) Run() error {
return err return err
} }
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil return nil
} }

View File

@@ -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),

View File

@@ -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,

View File

@@ -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);

View File

@@ -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))
}

View File

@@ -46,6 +46,15 @@ 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);
}
}
// 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);
@@ -79,6 +88,7 @@ struct Application {
id mouseEvent; id mouseEvent;
id mouseDownMonitor; id mouseDownMonitor;
id mouseUpMonitor; id mouseUpMonitor;
int activationPolicy;
// Window Data // Window Data
const char *title; const char *title;
@@ -112,6 +122,7 @@ struct Application {
int useToolBar; int useToolBar;
int hideToolbarSeparator; int hideToolbarSeparator;
int windowBackgroundIsTranslucent; int windowBackgroundIsTranslucent;
int hasURLHandlers;
// Menu // Menu
Menu *applicationMenu; Menu *applicationMenu;
@@ -253,7 +264,7 @@ void Hide(struct Application *app) {
if( app->shuttingDown ) return; if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
msg(app->application, s("hide:")) msg(app->application, s("hide:"));
); );
} }
@@ -1046,6 +1057,12 @@ void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
); );
} }
void DeleteTrayMenuByID(struct Application *app, const char *id) {
ON_MAIN_THREAD(
DeleteTrayMenuInStore(app->trayMenuStore, id);
);
}
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) { void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
// Guard against calling during shutdown // Guard against calling during shutdown
if( app->shuttingDown ) return; if( app->shuttingDown ) return;
@@ -1112,7 +1129,7 @@ void processDecorations(struct Application *app) {
void createApplication(struct Application *app) { void createApplication(struct Application *app) {
id application = msg(c("NSApplication"), s("sharedApplication")); id application = msg(c("NSApplication"), s("sharedApplication"));
app->application = application; app->application = application;
msg(application, s("setActivationPolicy:"), 0); msg(application, s("setActivationPolicy:"), app->activationPolicy);
} }
void DarkModeEnabled(struct Application *app, const char *callbackID) { void DarkModeEnabled(struct Application *app, const char *callbackID) {
@@ -1136,17 +1153,31 @@ 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 // Define delegate
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0); Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0);
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate")); bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@"); class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
// class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@"); class_addMethod(delegateClass, 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(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
// If there are URL Handlers, register the callback method
if( app->hasURLHandlers ) {
class_addMethod(delegateClass, s("getUrl:withReplyEvent:"), (IMP) getURL, "i@:@@");
}
// Script handler // Script handler
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@"); class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
objc_registerClassPair(delegateClass); objc_registerClassPair(delegateClass);
@@ -1155,6 +1186,12 @@ void createDelegate(struct Application *app) {
id delegate = msg((id)delegateClass, s("new")); id delegate = msg((id)delegateClass, 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(delegateClass, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
@@ -1170,13 +1207,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
@@ -1190,14 +1233,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;
} }
@@ -1812,15 +1857,22 @@ void Run(struct Application *app, int argc, char **argv) {
MEMFREE(internalCode); MEMFREE(internalCode);
} }
void SetActivationPolicy(struct Application* app, int 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) {
Debug(app, "Quit Called"); Debug(app, "Quit Called");
msg(app->application, s("stop:"), NULL); msg(app->application, s("stop:"), NULL);
ON_MAIN_THREAD ( SetSize(app, 0, 0);
// Terminate app by triggering a UI event Show(app);
SetSize(app, 0, 0); Hide(app);
);
} }
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) { void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) {
@@ -1884,6 +1936,10 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->shuttingDown = false; result->shuttingDown = false;
result->activationPolicy = NSApplicationActivationPolicyRegular;
result->hasURLHandlers = 0;
return (void*) result; return (void*) result;
} }

View File

@@ -48,6 +48,9 @@ func (a *Application) processPlatformSettings() error {
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance))) C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
} }
// Set activation policy
C.SetActivationPolicy(a.app, C.int(mac.ActivationPolicy))
// Check if the webview should be transparent // Check if the webview should be transparent
if mac.WebviewIsTransparent { if mac.WebviewIsTransparent {
C.WebviewIsTransparent(a.app) C.WebviewIsTransparent(a.app)
@@ -87,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
} }

View File

@@ -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)
@@ -66,6 +70,10 @@
#define NSControlStateValueOff 0 #define NSControlStateValueOff 0
#define NSControlStateValueOn 1 #define NSControlStateValueOn 1
#define NSApplicationActivationPolicyRegular 0
#define NSApplicationActivationPolicyAccessory 1
#define NSApplicationActivationPolicyProhibited 2
// Unbelievably, if the user swaps their button preference // Unbelievably, if the user swaps their button preference
// then right buttons are reported as left buttons // then right buttons are reported as left buttons
#define NSEventMaskLeftMouseDown 1 << 1 #define NSEventMaskLeftMouseDown 1 << 1
@@ -110,6 +118,10 @@ void SetTray(struct Application* app, const char *, const char *, const char *);
//void SetContextMenus(struct Application* app, const char *); //void SetContextMenus(struct Application* app, const char *);
void AddTrayMenu(struct Application* app, const char *); void AddTrayMenu(struct Application* app, const char *);
void SetActivationPolicy(struct Application* app, int policy);
void* lookupStringConstant(id constantName); void* lookupStringConstant(id constantName);
void HasURLHandlers(struct Application* app);
#endif #endif

View File

@@ -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));
@@ -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);

View File

@@ -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);

View File

@@ -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);
} }

View File

@@ -5,6 +5,8 @@
#ifndef TRAYMENUSTORE_DARWIN_H #ifndef TRAYMENUSTORE_DARWIN_H
#define TRAYMENUSTORE_DARWIN_H #define TRAYMENUSTORE_DARWIN_H
#include <pthread.h>
typedef struct { typedef struct {
int dummy; int dummy;
@@ -13,6 +15,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();
@@ -23,5 +27,6 @@ void ShowTrayMenusInStore(TrayMenuStore* store);
void DeleteTrayMenuStore(TrayMenuStore* store); void DeleteTrayMenuStore(TrayMenuStore* store);
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

View File

@@ -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
} }

View File

@@ -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,
} }

View File

@@ -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 {
@@ -65,6 +68,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]

View File

@@ -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

View File

@@ -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]]

View 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])
}
}

View File

@@ -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)

View File

@@ -620,7 +620,7 @@
} }
/** Menubar **/ /** Menubar **/
const menuVisible = writable(true); const menuVisible = writable(false);
/** Trays **/ /** Trays **/
@@ -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 } = {}) {
@@ -1220,11 +1232,11 @@
function get_each_context$1(ctx, list, i) { function get_each_context$1(ctx, list, i) {
const child_ctx = ctx.slice(); const child_ctx = ctx.slice();
child_ctx[8] = list[i]; child_ctx[9] = list[i];
return child_ctx; return child_ctx;
} }
// (29:0) {#if $menuVisible } // (38:0) {#if $menuVisible }
function create_if_block$3(ctx) { function create_if_block$3(ctx) {
let div; let div;
let span1; let span1;
@@ -1336,11 +1348,11 @@
}; };
} }
// (32:4) {#each $trays as tray} // (41:4) {#each $trays as tray}
function create_each_block$1(ctx) { function create_each_block$1(ctx) {
let traymenu; let traymenu;
let current; let current;
traymenu = new TrayMenu({ props: { tray: /*tray*/ ctx[8] } }); traymenu = new TrayMenu({ props: { tray: /*tray*/ ctx[9] } });
return { return {
c() { c() {
@@ -1352,7 +1364,7 @@
}, },
p(ctx, dirty) { p(ctx, dirty) {
const traymenu_changes = {}; const traymenu_changes = {};
if (dirty & /*$trays*/ 4) traymenu_changes.tray = /*tray*/ ctx[8]; if (dirty & /*$trays*/ 4) traymenu_changes.tray = /*tray*/ ctx[9];
traymenu.$set(traymenu_changes); traymenu.$set(traymenu_changes);
}, },
i(local) { i(local) {
@@ -1373,6 +1385,8 @@
function create_fragment$3(ctx) { function create_fragment$3(ctx) {
let if_block_anchor; let if_block_anchor;
let current; let current;
let mounted;
let dispose;
let if_block = /*$menuVisible*/ ctx[1] && create_if_block$3(ctx); let if_block = /*$menuVisible*/ ctx[1] && create_if_block$3(ctx);
return { return {
@@ -1384,6 +1398,11 @@
if (if_block) if_block.m(target, anchor); if (if_block) if_block.m(target, anchor);
insert(target, if_block_anchor, anchor); insert(target, if_block_anchor, anchor);
current = true; current = true;
if (!mounted) {
dispose = listen(window, "keydown", /*handleKeydown*/ ctx[3]);
mounted = true;
}
}, },
p(ctx, [dirty]) { p(ctx, [dirty]) {
if (/*$menuVisible*/ ctx[1]) { if (/*$menuVisible*/ ctx[1]) {
@@ -1421,6 +1440,8 @@
d(detaching) { d(detaching) {
if (if_block) if_block.d(detaching); if (if_block) if_block.d(detaching);
if (detaching) detach(if_block_anchor); if (detaching) detach(if_block_anchor);
mounted = false;
dispose();
} }
}; };
} }
@@ -1440,7 +1461,7 @@
onMount(() => { onMount(() => {
const interval = setInterval( const interval = setInterval(
() => { () => {
$$invalidate(3, time = new Date()); $$invalidate(4, time = new Date());
}, },
1000 1000
); );
@@ -1450,33 +1471,52 @@
}; };
}); });
function handleKeydown(e) {
// Backtick toggle
if (e.keyCode == 192) {
menuVisible.update(current => {
return !current;
});
}
}
$$self.$$.update = () => { $$self.$$.update = () => {
if ($$self.$$.dirty & /*time*/ 8) { if ($$self.$$.dirty & /*time*/ 16) {
$$invalidate(4, day = time.toLocaleString("default", { weekday: "short" })); $$invalidate(5, day = time.toLocaleString("default", { weekday: "short" }));
} }
if ($$self.$$.dirty & /*time*/ 8) { if ($$self.$$.dirty & /*time*/ 16) {
$$invalidate(5, dom = time.getDate()); $$invalidate(6, dom = time.getDate());
} }
if ($$self.$$.dirty & /*time*/ 8) { if ($$self.$$.dirty & /*time*/ 16) {
$$invalidate(6, mon = time.toLocaleString("default", { month: "short" })); $$invalidate(7, mon = time.toLocaleString("default", { month: "short" }));
} }
if ($$self.$$.dirty & /*time*/ 8) { if ($$self.$$.dirty & /*time*/ 16) {
$$invalidate(7, currentTime = time.toLocaleString("en-US", { $$invalidate(8, currentTime = time.toLocaleString("en-US", {
hour: "numeric", hour: "numeric",
minute: "numeric", minute: "numeric",
hour12: true hour12: true
}).toLowerCase()); }).toLowerCase());
} }
if ($$self.$$.dirty & /*day, dom, mon, currentTime*/ 240) { if ($$self.$$.dirty & /*day, dom, mon, currentTime*/ 480) {
$$invalidate(0, dateTimeString = `${day} ${dom} ${mon} ${currentTime}`); $$invalidate(0, dateTimeString = `${day} ${dom} ${mon} ${currentTime}`);
} }
}; };
return [dateTimeString, $menuVisible, $trays, time, day, dom, mon, currentTime]; return [
dateTimeString,
$menuVisible,
$trays,
handleKeydown,
time,
day,
dom,
mon,
currentTime
];
} }
class Menubar extends SvelteComponent { class Menubar extends SvelteComponent {
@@ -1638,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);
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@wails/runtime", "name": "@wails/runtime",
"version": "1.3.9", "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",

View File

@@ -24,6 +24,15 @@
}; };
}); });
function handleKeydown(e) {
// Backtick toggle
if( e.keyCode == 192 ) {
menuVisible.update( (current) => {
return !current;
});
}
}
</script> </script>
{#if $menuVisible } {#if $menuVisible }
@@ -37,6 +46,8 @@
</div> </div>
{/if} {/if}
<svelte:window on:keydown={handleKeydown}/>
<style> <style>
.tray-menus { .tray-menus {

View File

@@ -13,7 +13,7 @@ export function hideOverlay() {
} }
/** Menubar **/ /** Menubar **/
export const menuVisible = writable(true); export const menuVisible = writable(false);
export function showMenuBar() { export function showMenuBar() {
menuVisible.set(true); menuVisible.set(true);
@@ -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);

View File

@@ -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);
} }

View File

@@ -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)
}

View File

@@ -6,20 +6,19 @@ import (
// Runtime is a means for the user to interact with the application at runtime // Runtime is a means for the user to interact with the application at runtime
type Runtime struct { type Runtime struct {
Browser Browser Browser Browser
Events Events Events Events
Window Window Window Window
Dialog Dialog Dialog Dialog
System System System System
Menu Menu Menu Menu
Store *StoreProvider Store *StoreProvider
Log Log Log Log
bus *servicebus.ServiceBus bus *servicebus.ServiceBus
shutdownCallback func()
} }
// New creates a new runtime // New creates a new runtime
func New(serviceBus *servicebus.ServiceBus, shutdownCallback func()) *Runtime { func New(serviceBus *servicebus.ServiceBus) *Runtime {
result := &Runtime{ result := &Runtime{
Browser: newBrowser(), Browser: newBrowser(),
Events: newEvents(serviceBus), Events: newEvents(serviceBus),
@@ -36,11 +35,6 @@ func New(serviceBus *servicebus.ServiceBus, shutdownCallback func()) *Runtime {
// Quit the application // Quit the application
func (r *Runtime) Quit() { func (r *Runtime) Quit() {
// Call back to user's shutdown method if defined
if r.shutdownCallback != nil {
r.shutdownCallback()
}
// Start shutdown of Wails // Start shutdown of Wails
r.bus.Publish("quit", "runtime.Quit()") r.bus.Publish("quit", "runtime.Quit()")
} }

View File

@@ -26,25 +26,20 @@ type Manager struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
// The shutdown callback to notify the user's app that a shutdown
// has started
shutdownCallback func()
// Parent waitgroup // Parent waitgroup
wg *sync.WaitGroup wg *sync.WaitGroup
} }
// NewManager creates a new signal manager // NewManager creates a new signal manager
func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.ServiceBus, logger *logger.Logger, shutdownCallback func()) (*Manager, error) { func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.ServiceBus, logger *logger.Logger) (*Manager, error) {
result := &Manager{ result := &Manager{
bus: bus, bus: bus,
logger: logger.CustomLogger("Event Manager"), logger: logger.CustomLogger("Event Manager"),
signalchannel: make(chan os.Signal, 2), signalchannel: make(chan os.Signal, 2),
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
shutdownCallback: shutdownCallback, wg: ctx.Value("waitgroup").(*sync.WaitGroup),
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
} }
return result, nil return result, nil
@@ -67,11 +62,6 @@ func (m *Manager) Start() {
m.logger.Trace("Ctrl+C detected. Shutting down...") m.logger.Trace("Ctrl+C detected. Shutting down...")
m.bus.Publish("quit", "ctrl-c pressed") m.bus.Publish("quit", "ctrl-c pressed")
// Shutdown app first
if m.shutdownCallback != nil {
m.shutdownCallback()
}
// Start shutdown of Wails // Start shutdown of Wails
m.cancel() m.cancel()

View File

@@ -137,6 +137,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)

View File

@@ -37,7 +37,7 @@ type Runtime struct {
} }
// NewRuntime creates a new runtime subsystem // NewRuntime creates a new runtime subsystem
func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime), shutdownCallback func()) (*Runtime, error) { func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime)) (*Runtime, error) {
// Subscribe to log messages // Subscribe to log messages
runtimeChannel, err := bus.Subscribe("runtime:") runtimeChannel, err := bus.Subscribe("runtime:")
@@ -52,13 +52,12 @@ func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.
} }
result := &Runtime{ result := &Runtime{
runtimeChannel: runtimeChannel, runtimeChannel: runtimeChannel,
hooksChannel: hooksChannel, hooksChannel: hooksChannel,
logger: logger.CustomLogger("Runtime Subsystem"), logger: logger.CustomLogger("Runtime Subsystem"),
runtime: runtime.New(bus, shutdownCallback), runtime: runtime.New(bus),
startupCallback: startupCallback, startupCallback: startupCallback,
shutdownCallback: shutdownCallback, ctx: ctx,
ctx: ctx,
} }
return result, nil return result, nil

View File

@@ -0,0 +1,94 @@
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:
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()
}

View File

@@ -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
} }

View File

@@ -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

View File

@@ -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)
} }

View File

@@ -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,7 +218,7 @@ 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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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{

View File

@@ -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

View File

@@ -2,6 +2,14 @@ package mac
import "github.com/wailsapp/wails/v2/pkg/menu" import "github.com/wailsapp/wails/v2/pkg/menu"
type ActivationPolicy int
const (
NSApplicationActivationPolicyRegular ActivationPolicy = 0
NSApplicationActivationPolicyAccessory ActivationPolicy = 1
NSApplicationActivationPolicyProhibited ActivationPolicy = 2
)
// Options are options specific to Mac // Options are options specific to Mac
type Options struct { type Options struct {
TitleBar *TitleBar TitleBar *TitleBar
@@ -11,4 +19,6 @@ type Options struct {
Menu *menu.Menu Menu *menu.Menu
TrayMenus []*menu.TrayMenu TrayMenus []*menu.TrayMenu
ContextMenus []*menu.ContextMenu ContextMenus []*menu.ContextMenu
ActivationPolicy ActivationPolicy
URLHandlers map[string]func(string)
} }