Add IsDarkMode

Updated runtime test
This commit is contained in:
Lea Anthony
2020-10-02 11:41:46 +10:00
parent c3280e8b60
commit d8fdc96899
13 changed files with 191 additions and 29 deletions

View File

@@ -7,6 +7,7 @@
"functional": "c",
"__locale": "c",
"locale": "c",
"chrono": "c"
"chrono": "c",
"system_error": "c"
}
}

View File

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

View File

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

View File

@@ -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<callbackID>|<json array of strings>"
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);

View File

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

View File

@@ -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<callbackID>|<[]string as json encoded string>
// Format of Dialog response messages: D<dialog type><callbackID>|<[]string as json encoded string>
case 'D':
dialogType := message[1]
message = message[2:]

View File

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

View File

@@ -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<command><callbackID>|<payload>
// 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
}

View File

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

View File

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

View File

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

View File

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

View File

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