mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-04 21:00:31 +08:00

This commit adds a robust teardown process for windows on application shutdown. It introduces a field to track the destruction state of each window and checks such before performing window operations. Also, it enhances the destroy functions within application for thorough clean up. Finally, redundant event handlers related to application termination were removed while fixing file generating challenge in go tasks.
352 lines
8.7 KiB
Go
352 lines
8.7 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>
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
// Destroy 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;
|
|
}
|
|
*/
|
|
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 {
|
|
// Add a hook to the ApplicationDidFinishLaunching event
|
|
m.parent.On(events.Mac.ApplicationDidFinishLaunching, func(*Event) {
|
|
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 quitApplication
|
|
func quitApplication() {
|
|
globalApplication.Quit()
|
|
}
|
|
|
|
func (a *App) logPlatformInfo() {
|
|
info, err := operatingsystem.Info()
|
|
if err != nil {
|
|
a.error("Error getting OS info", "error", err.Error())
|
|
return
|
|
}
|
|
|
|
a.info("Platform Info:", info.AsLogSlice()...)
|
|
|
|
}
|