From d8fdc96899f6f4802c7e518eb4d74ff52d199ff2 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Fri, 2 Oct 2020 11:41:46 +1000 Subject: [PATCH] Add IsDarkMode Updated runtime test --- v2/.vscode/settings.json | 3 +- v2/internal/ffenestri/ffenestri.h | 1 + v2/internal/ffenestri/ffenestri_client.go | 4 ++ v2/internal/ffenestri/ffenestri_darwin.c | 70 +++++++++++++------ .../messagedispatcher/dispatchclient.go | 1 + .../messagedispatcher/message/dialog.go | 2 +- .../message/messageparser.go | 1 + .../messagedispatcher/message/system.go | 42 +++++++++++ .../messagedispatcher/messagedispatcher.go | 31 ++++++++ v2/internal/runtime/goruntime/runtime.go | 2 + v2/internal/runtime/goruntime/system.go | 50 +++++++++++++ v2/test/runtime/main.go | 8 +-- v2/test/runtime/runtime.go | 5 ++ 13 files changed, 191 insertions(+), 29 deletions(-) create mode 100644 v2/internal/messagedispatcher/message/system.go create mode 100644 v2/internal/runtime/goruntime/system.go diff --git a/v2/.vscode/settings.json b/v2/.vscode/settings.json index c021e502..01d4ec2d 100644 --- a/v2/.vscode/settings.json +++ b/v2/.vscode/settings.json @@ -7,6 +7,7 @@ "functional": "c", "__locale": "c", "locale": "c", - "chrono": "c" + "chrono": "c", + "system_error": "c" } } \ No newline at end of file diff --git a/v2/internal/ffenestri/ffenestri.h b/v2/internal/ffenestri/ffenestri.h index c064a80a..52b3e83a 100644 --- a/v2/internal/ffenestri/ffenestri.h +++ b/v2/internal/ffenestri/ffenestri.h @@ -31,4 +31,5 @@ extern void ToggleFullscreen(void *app); extern void DisableFrame(void *app); extern void OpenDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolveAliases, int treatPackagesAsDirectories); extern void SaveDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories); +extern void DarkModeEnabled(void *appPointer, char *callbackID); #endif diff --git a/v2/internal/ffenestri/ffenestri_client.go b/v2/internal/ffenestri/ffenestri_client.go index 83ef7d5a..40acde18 100644 --- a/v2/internal/ffenestri/ffenestri_client.go +++ b/v2/internal/ffenestri/ffenestri_client.go @@ -150,3 +150,7 @@ func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories), ) } + +func (c *Client) DarkModeEnabled(callbackID string) { + C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID)) +} diff --git a/v2/internal/ffenestri/ffenestri_darwin.c b/v2/internal/ffenestri/ffenestri_darwin.c index bb60ece3..1725d4ff 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.c +++ b/v2/internal/ffenestri/ffenestri_darwin.c @@ -13,14 +13,15 @@ #define s(str) sel_registerName(str) #define u(str) sel_getUid(str) #define str(input) msg(c("NSString"), s("stringWithUTF8String:"), input) +#define cstr(input) (const char *)msg(input, s("UTF8String")) #define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input)) #define ALLOC(classname) msg(c(classname), s("alloc")) -#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame")); -#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds")); +#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame")) +#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds")) -#define ON_MAIN_THREAD(str) dispatch( ^{ str; } ); -#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str))); +#define ON_MAIN_THREAD(str) dispatch( ^{ str; } ) +#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str))) #define NSBackingStoreBuffered 2 @@ -212,7 +213,7 @@ void applyWindowColour(struct Application *app) { (float)app->blue / 255.0, (float)app->alpha / 255.0); msg(app->mainWindow, s("setBackgroundColor:"), colour); - ) + ); } } @@ -232,14 +233,14 @@ void FullSizeContent(struct Application *app) { void Hide(struct Application *app) { ON_MAIN_THREAD( msg(app->application, s("hide:")) - ) + ); } void Show(struct Application *app) { ON_MAIN_THREAD( msg(app->mainWindow, s("makeKeyAndOrderFront:"), NULL); msg(app->application, s("activateIgnoringOtherApps:"), YES); - ) + ); } void SetWindowBackgroundIsTranslucent(struct Application *app) { @@ -265,7 +266,8 @@ void messageHandler(id self, SEL cmd, id contentController, id message) { msg(app->mainWindow, s("performWindowDragWithEvent:"), app->mouseEvent); } } else { - const char *m = (const char *)msg(msg(message, s("body")), s("UTF8String")); + // const char *m = (const char *)msg(msg(message, s("body")), s("UTF8String")); + const char *m = cstr(msg(message, s("body"))); app->sendMessageToBackend(m); } } @@ -363,14 +365,14 @@ void SetTitle(struct Application *app, const char *title) { Debug("SetTitle Called"); ON_MAIN_THREAD( msg(app->mainWindow, s("setTitle:"), str(title)); - ) + ); } void ToggleFullscreen(struct Application *app) { ON_MAIN_THREAD( app->fullscreen = !app->fullscreen; - MAIN_WINDOW_CALL("toggleFullScreen:") - ) + MAIN_WINDOW_CALL("toggleFullScreen:"); + ); } // Fullscreen sets the main window to be fullscreen @@ -392,15 +394,15 @@ void UnFullscreen(struct Application *app) { void Center(struct Application *app) { Debug("Center Called"); ON_MAIN_THREAD( - MAIN_WINDOW_CALL("center") - ) + MAIN_WINDOW_CALL("center"); + ); } void ToggleMaximise(struct Application *app) { ON_MAIN_THREAD( app->maximised = !app->maximised; - MAIN_WINDOW_CALL("zoom:") - ) + MAIN_WINDOW_CALL("zoom:"); + ); } void Maximise(struct Application *app) { @@ -419,7 +421,7 @@ void ToggleMinimise(struct Application *app) { ON_MAIN_THREAD( MAIN_WINDOW_CALL(app->minimised ? "deminiaturize:" : "miniaturize:" ); app->minimised = !app->minimised; - ) + ); } void Minimise(struct Application *app) { @@ -463,7 +465,7 @@ void SetSize(struct Application *app, int width, int height) { frame.size.height = (float)height; msg(app->mainWindow, s("setFrame:display:animate:"), frame, 1, 0); - ) + ); } void SetPosition(struct Application *app, int x, int y) { @@ -475,7 +477,7 @@ void SetPosition(struct Application *app, int x, int y) { windowFrame.origin.x = screenFrame.origin.x + (float)x; windowFrame.origin.y = (screenFrame.origin.y + screenFrame.size.height) - windowFrame.size.height - (float)y; msg(app->mainWindow, s("setFrame:display:animate:"), windowFrame, 1, 0); - ) + ); } // OpenDialog opens a dialog to select files/directories @@ -563,7 +565,7 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi }); msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow); - ) + ); } // SaveDialog opens a dialog to select files/directories @@ -632,7 +634,7 @@ void SaveDialog(struct Application *app, char *callbackID, char *title, char *fi }); msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow); - ) + ); } const char *invoke = "window.external={invoke:function(x){window.webkit.messageHandlers.external.postMessage(x);}};"; @@ -664,7 +666,7 @@ void SetMinWindowSize(struct Application *app, int minWidth, int minHeight) if( app->mainWindow != NULL ) { ON_MAIN_THREAD( setMinMaxSize(app); - ) + ); } } @@ -677,7 +679,7 @@ void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight) if( app->mainWindow != NULL ) { ON_MAIN_THREAD( setMinMaxSize(app); - ) + ); } } @@ -687,7 +689,7 @@ void ExecJS(struct Application *app, const char *js) { s("evaluateJavaScript:completionHandler:"), str(js), NULL); - ) + ); } void SetDebug(void *applicationPointer, int flag) { @@ -756,6 +758,28 @@ void createApplication(struct Application *app) { msg(application, s("setActivationPolicy:"), 0); } +void DarkModeEnabled(struct Application *app, const char *callbackID) { + ON_MAIN_THREAD( + id userDefaults = msg(c("NSUserDefaults"), s("standardUserDefaults")); + const char *mode = cstr(msg(userDefaults, s("stringForKey:"), str("AppleInterfaceStyle"))); + const char *result = "F"; + if ( mode != NULL && strcmp(mode, "Dark") == 0 ) { + result = "T"; + } + // Construct callback message. Format "SD|" + const char *callback = concat("SD", callbackID); + const char *header = concat(callback, "|"); + const char *responseMessage = concat(header, result); + // Send message to backend + app->sendMessageToBackend(responseMessage); + + // Free memory + free((void*)header); + free((void*)callback); + free((void*)responseMessage); + ); +} + void createDelegate(struct Application *app) { // Define delegate Class delegateClass = objc_allocateClassPair((Class) c("NSResponder"), "AppDelegate", 0); diff --git a/v2/internal/messagedispatcher/dispatchclient.go b/v2/internal/messagedispatcher/dispatchclient.go index c08f0c8a..0635b522 100644 --- a/v2/internal/messagedispatcher/dispatchclient.go +++ b/v2/internal/messagedispatcher/dispatchclient.go @@ -29,6 +29,7 @@ type Client interface { WindowFullscreen() WindowUnFullscreen() WindowSetColour(colour int) + DarkModeEnabled(callbackID string) } // DispatchClient is what the frontends use to interface with the diff --git a/v2/internal/messagedispatcher/message/dialog.go b/v2/internal/messagedispatcher/message/dialog.go index 1f94d320..44caf565 100644 --- a/v2/internal/messagedispatcher/message/dialog.go +++ b/v2/internal/messagedispatcher/message/dialog.go @@ -19,7 +19,7 @@ func dialogMessageParser(message string) (*parsedMessage, error) { // Switch the event type (with or without data) switch message[0] { - // Format of Dialog response messages: D|<[]string as json encoded string> + // Format of Dialog response messages: D|<[]string as json encoded string> case 'D': dialogType := message[1] message = message[2:] diff --git a/v2/internal/messagedispatcher/message/messageparser.go b/v2/internal/messagedispatcher/message/messageparser.go index 240a2bb2..457b5d09 100644 --- a/v2/internal/messagedispatcher/message/messageparser.go +++ b/v2/internal/messagedispatcher/message/messageparser.go @@ -18,6 +18,7 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){ 'C': callMessageParser, 'W': windowMessageParser, 'D': dialogMessageParser, + 'S': systemMessageParser, } // Parse will attempt to parse the given message diff --git a/v2/internal/messagedispatcher/message/system.go b/v2/internal/messagedispatcher/message/system.go new file mode 100644 index 00000000..0e2ae97a --- /dev/null +++ b/v2/internal/messagedispatcher/message/system.go @@ -0,0 +1,42 @@ +package message + +import ( + "fmt" + "strings" +) + +// systemMessageParser does what it says on the tin! +func systemMessageParser(message string) (*parsedMessage, error) { + + // Sanity check: system messages must be at least 4 bytes + if len(message) < 4 { + return nil, fmt.Errorf("system message was an invalid length") + } + + var responseMessage *parsedMessage + + // Remove 'S' + message = message[1:] + + // Switch the event type (with or without data) + switch message[0] { + // Format of system response messages: S| + // DarkModeEnabled + case 'D': + message = message[1:] + idx := strings.IndexByte(message, '|') + if idx < 0 { + return nil, fmt.Errorf("Invalid system response message format") + } + callbackID := message[:idx] + payloadData := message[idx+1:] + + topic := "systemresponse:" + callbackID + responseMessage = &parsedMessage{Topic: topic, Data: payloadData == "T"} + + default: + return nil, fmt.Errorf("Invalid message to systemMessageParser()") + } + + return responseMessage, nil +} diff --git a/v2/internal/messagedispatcher/messagedispatcher.go b/v2/internal/messagedispatcher/messagedispatcher.go index 3be2b07c..744d3dbd 100644 --- a/v2/internal/messagedispatcher/messagedispatcher.go +++ b/v2/internal/messagedispatcher/messagedispatcher.go @@ -21,6 +21,7 @@ type Dispatcher struct { eventChannel <-chan *servicebus.Message windowChannel <-chan *servicebus.Message dialogChannel <-chan *servicebus.Message + systemChannel <-chan *servicebus.Message running bool servicebus *servicebus.ServiceBus @@ -63,6 +64,11 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher, return nil, err } + systemChannel, err := servicebus.Subscribe("system:") + if err != nil { + return nil, err + } + result := &Dispatcher{ servicebus: servicebus, eventChannel: eventChannel, @@ -72,6 +78,7 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher, quitChannel: quitChannel, windowChannel: windowChannel, dialogChannel: dialogChannel, + systemChannel: systemChannel, } return result, nil @@ -99,6 +106,8 @@ func (d *Dispatcher) Start() error { d.processWindowMessage(windowMessage) case dialogMessage := <-d.dialogChannel: d.processDialogMessage(dialogMessage) + case systemMessage := <-d.systemChannel: + d.processSystemMessage(systemMessage) } } @@ -174,6 +183,28 @@ func (d *Dispatcher) processCallResult(result *servicebus.Message) { client.frontend.CallResult(result.Data().(string)) } +// processSystem +func (d *Dispatcher) processSystemMessage(result *servicebus.Message) { + + d.logger.Trace("Got system in message dispatcher: %+v", result) + + splitTopic := strings.Split(result.Topic(), ":") + command := splitTopic[1] + callbackID := splitTopic[2] + switch command { + case "isdarkmode": + d.lock.RLock() + for _, client := range d.clients { + client.frontend.DarkModeEnabled(callbackID) + break + } + d.lock.RUnlock() + + default: + d.logger.Error("Unknown system command: %s", command) + } +} + // processEvent will func (d *Dispatcher) processEvent(result *servicebus.Message) { diff --git a/v2/internal/runtime/goruntime/runtime.go b/v2/internal/runtime/goruntime/runtime.go index 5b03ce4b..1d044ac9 100644 --- a/v2/internal/runtime/goruntime/runtime.go +++ b/v2/internal/runtime/goruntime/runtime.go @@ -8,6 +8,7 @@ type Runtime struct { Events Events Window Window Dialog Dialog + System System bus *servicebus.ServiceBus } @@ -18,6 +19,7 @@ func New(serviceBus *servicebus.ServiceBus) *Runtime { Events: newEvents(serviceBus), Window: newWindow(serviceBus), Dialog: newDialog(serviceBus), + System: newSystem(serviceBus), bus: serviceBus, } } diff --git a/v2/internal/runtime/goruntime/system.go b/v2/internal/runtime/goruntime/system.go new file mode 100644 index 00000000..6c53b1a6 --- /dev/null +++ b/v2/internal/runtime/goruntime/system.go @@ -0,0 +1,50 @@ +package goruntime + +import ( + "fmt" + + "github.com/wailsapp/wails/v2/internal/crypto" + "github.com/wailsapp/wails/v2/internal/servicebus" +) + +// System defines all System related operations +type System interface { + IsDarkMode() bool +} + +// system exposes the System interface +type system struct { + bus *servicebus.ServiceBus +} + +// newSystem creates a new System struct +func newSystem(bus *servicebus.ServiceBus) System { + return &system{ + bus: bus, + } +} + +// On pass through +func (r *system) IsDarkMode() bool { + + // Create unique system callback + uniqueCallback := crypto.RandomID() + + // Subscribe to the respose channel + responseTopic := "systemresponse:" + uniqueCallback + systemResponseChannel, err := r.bus.Subscribe(responseTopic) + if err != nil { + fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) + } + + message := "system:isdarkmode:" + uniqueCallback + r.bus.Publish(message, nil) + + // Wait for result + var result *servicebus.Message = <-systemResponseChannel + + // Delete subscription to response topic + r.bus.UnSubscribe(responseTopic) + + return result.Data().(bool) +} diff --git a/v2/test/runtime/main.go b/v2/test/runtime/main.go index 820d1c50..2b110a5e 100644 --- a/v2/test/runtime/main.go +++ b/v2/test/runtime/main.go @@ -22,14 +22,14 @@ func main() { Height: 620, DisableResize: false, Fullscreen: false, - RGBA: 0xFFFFFFFF, + RGBA: 0xFFFFFF00, Mac: &mac.Options{ // TitleBar: mac.TitleBarHidden(), // TitleBar: mac.TitleBarHiddenInset(), TitleBar: mac.TitleBarDefault(), - // Appearance: mac.NSAppearanceNameDarkAqua, - WebviewIsTransparent: true, - // WindowBackgroundIsTranslucent: true, + // Appearance: mac.NSAppearanceNameVibrantLight, + WebviewIsTransparent: true, + WindowBackgroundIsTranslucent: true, }, }) diff --git a/v2/test/runtime/runtime.go b/v2/test/runtime/runtime.go index cc6919d2..5d1bc73b 100644 --- a/v2/test/runtime/runtime.go +++ b/v2/test/runtime/runtime.go @@ -174,3 +174,8 @@ func (r *RuntimeTest) Minimise() { func (r *RuntimeTest) Unminimise() { r.runtime.Window.Unminimise() } + +// Check is system is running in dark mode +func (r *RuntimeTest) IsDarkMode() bool { + return r.runtime.System.IsDarkMode() +}