mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 19:20:40 +08:00
Support custom protocols
This commit is contained in:
parent
2d1b2c0947
commit
b4c669ff86
@ -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 {
|
||||||
|
@ -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);
|
||||||
@ -113,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;
|
||||||
@ -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) {
|
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);
|
||||||
@ -1162,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@:@@");
|
||||||
|
|
||||||
@ -1831,6 +1861,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) {
|
||||||
@ -1904,6 +1938,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
|
@ -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])
|
||||||
|
}
|
||||||
|
}
|
94
v2/internal/subsystem/url.go
Normal file
94
v2/internal/subsystem/url.go
Normal 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()
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user