mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-04 07:29:56 +08:00
393 lines
9.9 KiB
Go
393 lines
9.9 KiB
Go
//go:build darwin
|
|
|
|
package application
|
|
|
|
/*
|
|
|
|
#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c
|
|
#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13
|
|
|
|
#include "application_darwin.h"
|
|
#include "application_darwin_delegate.h"
|
|
#include "webview_window_darwin.h"
|
|
#include <stdlib.h>
|
|
|
|
extern void registerListener(unsigned int event);
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
#import <Foundation/Foundation.h>
|
|
|
|
static AppDelegate *appDelegate = nil;
|
|
|
|
static void init(void) {
|
|
[NSApplication sharedApplication];
|
|
appDelegate = [[AppDelegate alloc] init];
|
|
[NSApp setDelegate:appDelegate];
|
|
|
|
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
|
|
NSWindow* eventWindow = [event window];
|
|
if (eventWindow == nil ) {
|
|
return event;
|
|
}
|
|
WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[eventWindow delegate];
|
|
if (windowDelegate == nil) {
|
|
return event;
|
|
}
|
|
if ([windowDelegate respondsToSelector:@selector(handleLeftMouseDown:)]) {
|
|
[windowDelegate handleLeftMouseDown:event];
|
|
}
|
|
return event;
|
|
}];
|
|
|
|
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
|
|
NSWindow* eventWindow = [event window];
|
|
if (eventWindow == nil ) {
|
|
return event;
|
|
}
|
|
WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[eventWindow delegate];
|
|
if (windowDelegate == nil) {
|
|
return event;
|
|
}
|
|
if ([windowDelegate respondsToSelector:@selector(handleLeftMouseUp:)]) {
|
|
[windowDelegate handleLeftMouseUp:eventWindow];
|
|
}
|
|
return event;
|
|
}];
|
|
|
|
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
|
|
[center addObserver:appDelegate selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil];
|
|
|
|
}
|
|
|
|
static bool isDarkMode(void) {
|
|
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
|
|
if (userDefaults == nil) {
|
|
return false;
|
|
}
|
|
|
|
NSString *interfaceStyle = [userDefaults stringForKey:@"AppleInterfaceStyle"];
|
|
if (interfaceStyle == nil) {
|
|
return false;
|
|
}
|
|
|
|
return [interfaceStyle isEqualToString:@"Dark"];
|
|
}
|
|
|
|
static void setApplicationShouldTerminateAfterLastWindowClosed(bool shouldTerminate) {
|
|
// Get the NSApp delegate
|
|
AppDelegate *appDelegate = (AppDelegate*)[NSApp delegate];
|
|
// Set the applicationShouldTerminateAfterLastWindowClosed boolean
|
|
appDelegate.shouldTerminateWhenLastWindowClosed = shouldTerminate;
|
|
}
|
|
|
|
static void setActivationPolicy(int policy) {
|
|
[NSApp setActivationPolicy:policy];
|
|
}
|
|
|
|
static void activateIgnoringOtherApps() {
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
}
|
|
|
|
static void run(void) {
|
|
@autoreleasepool {
|
|
[NSApp run];
|
|
[appDelegate release];
|
|
[NSApp abortModal];
|
|
}
|
|
}
|
|
|
|
// destroyApp destroys the application
|
|
static void destroyApp(void) {
|
|
[NSApp terminate:nil];
|
|
}
|
|
|
|
// Set the application menu
|
|
static void setApplicationMenu(void *menu) {
|
|
NSMenu *nsMenu = (__bridge NSMenu *)menu;
|
|
[NSApp setMainMenu:menu];
|
|
}
|
|
|
|
// Get the application name
|
|
static char* getAppName(void) {
|
|
NSString *appName = [NSRunningApplication currentApplication].localizedName;
|
|
if( appName == nil ) {
|
|
appName = [[NSProcessInfo processInfo] processName];
|
|
}
|
|
return strdup([appName UTF8String]);
|
|
}
|
|
|
|
// get the current window ID
|
|
static unsigned int getCurrentWindowID(void) {
|
|
NSWindow *window = [NSApp keyWindow];
|
|
// Get the window delegate
|
|
WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)[window delegate];
|
|
return delegate.windowId;
|
|
}
|
|
|
|
// Set the application icon
|
|
static void setApplicationIcon(void *icon, int length) {
|
|
// On main thread
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]];
|
|
[NSApp setApplicationIconImage:image];
|
|
});
|
|
}
|
|
|
|
// Hide the application
|
|
static void hide(void) {
|
|
[NSApp hide:nil];
|
|
}
|
|
|
|
// Show the application
|
|
static void show(void) {
|
|
[NSApp unhide:nil];
|
|
}
|
|
|
|
static const char* serializationNSDictionary(void *dict) {
|
|
@autoreleasepool {
|
|
NSDictionary *nsDict = (__bridge NSDictionary *)dict;
|
|
|
|
if ([NSJSONSerialization isValidJSONObject:nsDict]) {
|
|
NSError *error;
|
|
NSData *data = [NSJSONSerialization dataWithJSONObject:nsDict options:kNilOptions error:&error];
|
|
NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
|
|
|
|
return strdup([result UTF8String]);
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
static void startSingleInstanceListener(const char *uniqueID) {
|
|
// Convert to NSString
|
|
NSString *uid = [NSString stringWithUTF8String:uniqueID];
|
|
[[NSDistributedNotificationCenter defaultCenter] addObserver:appDelegate
|
|
selector:@selector(handleSecondInstanceNotification:) name:uid object:nil];
|
|
}
|
|
*/
|
|
import "C"
|
|
import (
|
|
"encoding/json"
|
|
"unsafe"
|
|
|
|
"github.com/wailsapp/wails/v3/internal/operatingsystem"
|
|
|
|
"github.com/wailsapp/wails/v3/internal/assetserver/webview"
|
|
"github.com/wailsapp/wails/v3/pkg/events"
|
|
)
|
|
|
|
type macosApp struct {
|
|
applicationMenu unsafe.Pointer
|
|
parent *App
|
|
}
|
|
|
|
func (m *macosApp) isDarkMode() bool {
|
|
return bool(C.isDarkMode())
|
|
}
|
|
|
|
func getNativeApplication() *macosApp {
|
|
return globalApplication.impl.(*macosApp)
|
|
}
|
|
|
|
func (m *macosApp) hide() {
|
|
C.hide()
|
|
}
|
|
|
|
func (m *macosApp) show() {
|
|
C.show()
|
|
}
|
|
|
|
func (m *macosApp) on(eventID uint) {
|
|
C.registerListener(C.uint(eventID))
|
|
}
|
|
|
|
func (m *macosApp) setIcon(icon []byte) {
|
|
C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon)))
|
|
}
|
|
|
|
func (m *macosApp) name() string {
|
|
appName := C.getAppName()
|
|
defer C.free(unsafe.Pointer(appName))
|
|
return C.GoString(appName)
|
|
}
|
|
|
|
func (m *macosApp) getCurrentWindowID() uint {
|
|
return uint(C.getCurrentWindowID())
|
|
}
|
|
|
|
func (m *macosApp) setApplicationMenu(menu *Menu) {
|
|
if menu == nil {
|
|
// Create a default menu for mac
|
|
menu = DefaultApplicationMenu()
|
|
}
|
|
menu.Update()
|
|
|
|
// Convert impl to macosMenu object
|
|
m.applicationMenu = (menu.impl).(*macosMenu).nsMenu
|
|
C.setApplicationMenu(m.applicationMenu)
|
|
}
|
|
|
|
func (m *macosApp) run() error {
|
|
if m.parent.options.SingleInstance != nil {
|
|
cUniqueID := C.CString(m.parent.options.SingleInstance.UniqueID)
|
|
defer C.free(unsafe.Pointer(cUniqueID))
|
|
C.startSingleInstanceListener(cUniqueID)
|
|
}
|
|
// Add a hook to the ApplicationDidFinishLaunching event
|
|
m.parent.OnApplicationEvent(events.Mac.ApplicationDidFinishLaunching, func(*ApplicationEvent) {
|
|
C.setApplicationShouldTerminateAfterLastWindowClosed(C.bool(m.parent.options.Mac.ApplicationShouldTerminateAfterLastWindowClosed))
|
|
C.setActivationPolicy(C.int(m.parent.options.Mac.ActivationPolicy))
|
|
C.activateIgnoringOtherApps()
|
|
})
|
|
m.setupCommonEvents()
|
|
// setup event listeners
|
|
for eventID := range m.parent.applicationEventListeners {
|
|
m.on(eventID)
|
|
}
|
|
C.run()
|
|
return nil
|
|
}
|
|
|
|
func (m *macosApp) destroy() {
|
|
C.destroyApp()
|
|
}
|
|
|
|
func (m *macosApp) GetFlags(options Options) map[string]any {
|
|
if options.Flags == nil {
|
|
options.Flags = make(map[string]any)
|
|
}
|
|
return options.Flags
|
|
}
|
|
|
|
func newPlatformApp(app *App) *macosApp {
|
|
C.init()
|
|
return &macosApp{
|
|
parent: app,
|
|
}
|
|
}
|
|
|
|
//export processApplicationEvent
|
|
func processApplicationEvent(eventID C.uint, data unsafe.Pointer) {
|
|
event := newApplicationEvent(events.ApplicationEventType(eventID))
|
|
|
|
if data != nil {
|
|
dataCStrJSON := C.serializationNSDictionary(data)
|
|
if dataCStrJSON != nil {
|
|
defer C.free(unsafe.Pointer(dataCStrJSON))
|
|
|
|
dataJSON := C.GoString(dataCStrJSON)
|
|
var result map[string]any
|
|
err := json.Unmarshal([]byte(dataJSON), &result)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
event.Context().setData(result)
|
|
}
|
|
}
|
|
|
|
switch event.Id {
|
|
case uint(events.Mac.ApplicationDidChangeTheme):
|
|
isDark := globalApplication.IsDarkMode()
|
|
event.Context().setIsDarkMode(isDark)
|
|
}
|
|
applicationEvents <- event
|
|
}
|
|
|
|
//export processWindowEvent
|
|
func processWindowEvent(windowID C.uint, eventID C.uint) {
|
|
windowEvents <- &windowEvent{
|
|
WindowID: uint(windowID),
|
|
EventID: uint(eventID),
|
|
}
|
|
}
|
|
|
|
//export processMessage
|
|
func processMessage(windowID C.uint, message *C.char) {
|
|
windowMessageBuffer <- &windowMessage{
|
|
windowId: uint(windowID),
|
|
message: C.GoString(message),
|
|
}
|
|
}
|
|
|
|
//export processURLRequest
|
|
func processURLRequest(windowID C.uint, wkUrlSchemeTask unsafe.Pointer) {
|
|
webviewRequests <- &webViewAssetRequest{
|
|
Request: webview.NewRequest(wkUrlSchemeTask),
|
|
windowId: uint(windowID),
|
|
windowName: globalApplication.getWindowForID(uint(windowID)).Name(),
|
|
}
|
|
}
|
|
|
|
//export processWindowKeyDownEvent
|
|
func processWindowKeyDownEvent(windowID C.uint, acceleratorString *C.char) {
|
|
windowKeyEvents <- &windowKeyEvent{
|
|
windowId: uint(windowID),
|
|
acceleratorString: C.GoString(acceleratorString),
|
|
}
|
|
}
|
|
|
|
//export processDragItems
|
|
func processDragItems(windowID C.uint, arr **C.char, length C.int) {
|
|
var filenames []string
|
|
// Convert the C array to a Go slice
|
|
goSlice := (*[1 << 30]*C.char)(unsafe.Pointer(arr))[:length:length]
|
|
for _, str := range goSlice {
|
|
filenames = append(filenames, C.GoString(str))
|
|
}
|
|
windowDragAndDropBuffer <- &dragAndDropMessage{
|
|
windowId: uint(windowID),
|
|
filenames: filenames,
|
|
}
|
|
}
|
|
|
|
//export processMenuItemClick
|
|
func processMenuItemClick(menuID C.uint) {
|
|
menuItemClicked <- uint(menuID)
|
|
}
|
|
|
|
//export shouldQuitApplication
|
|
func shouldQuitApplication() C.bool {
|
|
// TODO: This should be configurable
|
|
return C.bool(globalApplication.shouldQuit())
|
|
}
|
|
|
|
//export cleanup
|
|
func cleanup() {
|
|
globalApplication.cleanup()
|
|
}
|
|
|
|
func (a *App) logPlatformInfo() {
|
|
info, err := operatingsystem.Info()
|
|
if err != nil {
|
|
a.error("Error getting OS info: %s", err.Error())
|
|
return
|
|
}
|
|
|
|
a.info("Platform Info:", info.AsLogSlice()...)
|
|
|
|
}
|
|
|
|
func (a *App) platformEnvironment() map[string]any {
|
|
return map[string]any{}
|
|
}
|
|
|
|
func fatalHandler(errFunc func(error)) {
|
|
return
|
|
}
|
|
|
|
//export HandleOpenFile
|
|
func HandleOpenFile(filePath *C.char) {
|
|
goFilepath := C.GoString(filePath)
|
|
// Create new application event context
|
|
eventContext := newApplicationEventContext()
|
|
eventContext.setOpenedWithFile(goFilepath)
|
|
// EmitEvent application started event
|
|
applicationEvents <- &ApplicationEvent{
|
|
Id: uint(events.Common.ApplicationOpenedWithFile),
|
|
ctx: eventContext,
|
|
}
|
|
}
|