mirror of
https://github.com/taigrr/wails.git
synced 2026-04-02 05:08:54 -07:00
Add IsDarkMode
Updated runtime test
This commit is contained in:
3
v2/.vscode/settings.json
vendored
3
v2/.vscode/settings.json
vendored
@@ -7,6 +7,7 @@
|
||||
"functional": "c",
|
||||
"__locale": "c",
|
||||
"locale": "c",
|
||||
"chrono": "c"
|
||||
"chrono": "c",
|
||||
"system_error": "c"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:]
|
||||
|
||||
@@ -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
|
||||
|
||||
42
v2/internal/messagedispatcher/message/system.go
Normal file
42
v2/internal/messagedispatcher/message/system.go
Normal 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
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
50
v2/internal/runtime/goruntime/system.go
Normal file
50
v2/internal/runtime/goruntime/system.go
Normal 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)
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user