Compare commits

...

5 Commits

Author SHA1 Message Date
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
14 changed files with 216 additions and 15 deletions

View File

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

View File

@@ -35,6 +35,7 @@ type App struct {
//binding *subsystem.Binding
call *subsystem.Call
menu *subsystem.Menu
url *subsystem.URL
dispatcher *messagedispatcher.Dispatcher
menuManager *menumanager.Manager
@@ -160,6 +161,19 @@ func (a *App) Run() error {
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
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
if err != nil {

View File

@@ -46,6 +46,15 @@ int hashmap_log(void *const context, struct hashmap_element_s *const e) {
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
void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
printf("%s = { ", name);
@@ -113,6 +122,7 @@ struct Application {
int useToolBar;
int hideToolbarSeparator;
int windowBackgroundIsTranslucent;
int hasURLHandlers;
// Menu
Menu *applicationMenu;
@@ -1143,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) {
// Define delegate
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0);
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
// class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
// All Menu Items use a common callback
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
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
objc_registerClassPair(delegateClass);
@@ -1162,6 +1186,12 @@ void createDelegate(struct Application *app) {
id delegate = msg((id)delegateClass, s("new"));
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
class_addMethod(delegateClass, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
@@ -1831,6 +1861,10 @@ 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
// used by the application
void Quit(struct Application *app) {
@@ -1904,6 +1938,8 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->activationPolicy = NSApplicationActivationPolicyRegular;
result->hasURLHandlers = 0;
return (void*) result;
}

View File

@@ -90,5 +90,10 @@ func (a *Application) processPlatformSettings() error {
}
}
// Process URL Handlers
if a.config.Mac.URLHandlers != nil {
C.HasURLHandlers(a.app)
}
return nil
}

View File

@@ -14,6 +14,10 @@
// Macros to make it slightly more sane
#define msg objc_msgSend
#define kInternetEventClass 'GURL'
#define kAEGetURL 'GURL'
#define keyDirectObject '----'
#define c(str) (id)objc_getClass(str)
#define s(str) sel_registerName(str)
#define u(str) sel_getUid(str)
@@ -118,4 +122,6 @@ void SetActivationPolicy(struct Application* app, int policy);
void* lookupStringConstant(id constantName);
void HasURLHandlers(struct Application* app);
#endif

View File

@@ -576,7 +576,7 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
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");
// Create a MenuItemCallbackData
@@ -585,9 +585,15 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
if( !alternate ) {
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
printf("Menuitem title: %s\n", title);
} else {
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
printf("ALT Menuitem title: %s\n", title);
}
if( tooltip != NULL ) {
msg(item, s("setToolTip:"), str(tooltip));
@@ -663,10 +669,16 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
msg(item, s("autorelease"));
// Process modifiers
if( modifiers != NULL ) {
if( modifiers != NULL && !alternate) {
unsigned long modifierFlags = parseModifiers(modifiers);
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
}
// alternate
if( alternate ) {
msg(item, s("setAlternate:"), true);
msg(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
}
msg(parentMenu, s("addItem:"), item);
return item;
@@ -727,6 +739,11 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
label = "(empty)";
}
// Is this an alternate menu item?
bool alternate = false;
getJSONBool(item, "MacAlternate", &alternate);
const char *menuid = getJSONString(item, "ID");
if ( menuid == NULL) {
menuid = "";
@@ -782,7 +799,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
if( type != NULL ) {
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")) {
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 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 processMenuData(Menu *menu, JsonNode *menuData);

View File

@@ -37,6 +37,7 @@ type ProcessedMenuItem struct {
// Image - base64 image data
Image string `json:",omitempty"`
MacTemplateImage bool `json:", omitempty"`
MacAlternate bool `json:", omitempty"`
// Tooltip
Tooltip string `json:",omitempty"`
@@ -60,6 +61,7 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
FontName: menuItem.FontName,
Image: menuItem.Image,
MacTemplateImage: menuItem.MacTemplateImage,
MacAlternate: menuItem.MacAlternate,
Tooltip: menuItem.Tooltip,
}

View File

@@ -21,13 +21,14 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
'M': menuMessageParser,
'T': trayMessageParser,
'X': contextMenusMessageParser,
'U': urlMessageParser,
}
// Parse will attempt to parse the given message
func Parse(message string) (*parsedMessage, error) {
if len(message) == 0 {
return nil, fmt.Errorf("MessageParser received blank message");
return nil, fmt.Errorf("MessageParser received blank message")
}
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

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

@@ -55,9 +55,11 @@ func packageApplication(options *Options) error {
}
// Sign app if needed
err = signApplication(options)
if err != nil {
return err
if options.AppleIdentity != "" {
err = signApplication(options)
if err != nil {
return err
}
}
return nil
@@ -188,7 +190,7 @@ func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
func signApplication(options *Options) error {
bundlename := filepath.Join(options.BuildDirectory, options.ProjectData.Name+".app")
identity := fmt.Sprintf(`"%s"`, options.AppleIdentity)
cmd := exec.Command("codesign", "--deep", "--force", "--verbose", "--sign", identity, bundlename)
cmd := exec.Command("codesign", "--sign", identity, "--deep", "--force", "--verbose", "--timestamp", "--options", "runtime", bundlename)
var stdo, stde bytes.Buffer
cmd.Stdout = &stdo
cmd.Stderr = &stde

View File

@@ -39,9 +39,12 @@ type MenuItem struct {
// Image - base64 image data
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
// MacAlternate indicates that this item is an alternative to the previous menu item
MacAlternate bool
// Tooltip
Tooltip string

View File

@@ -20,4 +20,5 @@ type Options struct {
TrayMenus []*menu.TrayMenu
ContextMenus []*menu.ContextMenu
ActivationPolicy ActivationPolicy
URLHandlers map[string]func(string)
}