//go:build darwin package app /* #cgo CFLAGS: -x objective-c -fblocks #cgo LDFLAGS: -framework Cocoa #include #import #import static inline BOOL gonaviBoolYES() { return YES; } static inline BOOL gonaviBoolNO() { return NO; } static char *gonaviNativeLogPath = NULL; static BOOL gonaviNativeObserverInstalled = NO; static void gonaviWriteNativeWindowLogLine(NSString *line) { if (line == nil || gonaviNativeLogPath == NULL) { return; } NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:[NSString stringWithUTF8String:gonaviNativeLogPath]]; if (handle == nil) { return; } @try { [handle seekToEndOfFile]; NSData *data = [line dataUsingEncoding:NSUTF8StringEncoding]; [handle writeData:data]; } @catch (__unused NSException *exception) { } @finally { [handle closeFile]; } } static NSString *gonaviWindowDiagnosticLine(NSString *eventName, NSWindow *window) { NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"yyyy/MM/dd HH:mm:ss.SSSSSS"]; NSString *timestamp = [formatter stringFromDate:[NSDate date]]; [formatter release]; if (window == nil) { return [NSString stringWithFormat:@"%@ [WARN] 原生窗口诊断:event=%@ window=nil\n", timestamp, eventName ?: @"unknown"]; } NSUInteger occlusionState = 0; NSInteger windowNumber = [window windowNumber]; NSInteger level = [window level]; NSUInteger collectionBehavior = [window collectionBehavior]; NSString *className = NSStringFromClass([window class]); NSString *delegateClassName = [window delegate] ? NSStringFromClass([[window delegate] class]) : @"nil"; if (@available(macOS 10.9, *)) { occlusionState = [window occlusionState]; } return [NSString stringWithFormat:@"%@ [WARN] 原生窗口诊断:event=%@ ptr=%p number=%ld class=%@ delegate=%@ visible=%@ miniaturized=%@ key=%@ main=%@ canHide=%@ level=%ld occlusion=%lu styleMask=%lu collectionBehavior=%lu frame=%@ screen=%@ title=%@\n", timestamp, eventName ?: @"unknown", window, (long)windowNumber, className ?: @"nil", delegateClassName, [window isVisible] ? @"true" : @"false", [window isMiniaturized] ? @"true" : @"false", [window isKeyWindow] ? @"true" : @"false", [window isMainWindow] ? @"true" : @"false", [window canHide] ? @"true" : @"false", (long)level, (unsigned long)occlusionState, (unsigned long)[window styleMask], (unsigned long)collectionBehavior, NSStringFromRect([window frame]), [window screen] ? NSStringFromRect([[window screen] frame]) : @"nil", [window title] ?: @""]; } @interface GoNaviNativeWindowObserver : NSObject @end @implementation GoNaviNativeWindowObserver - (void)logNotification:(NSNotification *)notification { NSString *name = [notification name] ?: @"unknown"; NSWindow *window = nil; if ([[notification object] isKindOfClass:[NSWindow class]]) { window = (NSWindow *)[notification object]; } else { window = [NSApp keyWindow] ?: [NSApp mainWindow]; } gonaviWriteNativeWindowLogLine(gonaviWindowDiagnosticLine(name, window)); } @end static GoNaviNativeWindowObserver *gonaviNativeWindowObserver = nil; static void gonaviInstallNativeWindowObserver(void) { if (gonaviNativeObserverInstalled) { return; } gonaviNativeObserverInstalled = YES; gonaviNativeWindowObserver = [[GoNaviNativeWindowObserver alloc] init]; NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; NSArray *windowNotifications = @[ NSWindowDidBecomeKeyNotification, NSWindowDidResignKeyNotification, NSWindowDidBecomeMainNotification, NSWindowDidResignMainNotification, NSWindowDidMiniaturizeNotification, NSWindowDidDeminiaturizeNotification, NSWindowDidEnterFullScreenNotification, NSWindowDidExitFullScreenNotification, NSWindowDidMoveNotification, NSWindowDidResizeNotification, NSWindowDidChangeOcclusionStateNotification, ]; for (NSString *notificationName in windowNotifications) { [center addObserver:gonaviNativeWindowObserver selector:@selector(logNotification:) name:notificationName object:nil]; } NSArray *appNotifications = @[ NSApplicationDidHideNotification, NSApplicationDidUnhideNotification, NSApplicationDidBecomeActiveNotification, NSApplicationDidResignActiveNotification, ]; for (NSString *notificationName in appNotifications) { [center addObserver:gonaviNativeWindowObserver selector:@selector(logNotification:) name:notificationName object:nil]; } for (NSWindow *window in [NSApp windows]) { gonaviWriteNativeWindowLogLine(gonaviWindowDiagnosticLine(@"observer:snapshot", window)); } } static void gonaviConfigureNativeWindowDiagnostics(const char *logPath) { if (logPath == NULL || logPath[0] == '\0') { return; } if (gonaviNativeLogPath != NULL) { free(gonaviNativeLogPath); gonaviNativeLogPath = NULL; } gonaviNativeLogPath = strdup(logPath); dispatch_async(dispatch_get_main_queue(), ^{ gonaviInstallNativeWindowObserver(); }); } static void gonaviSetWindowButtonsVisible(NSWindow *window, BOOL visible) { if (window == nil) { return; } for (NSWindowButton buttonType = NSWindowCloseButton; buttonType <= NSWindowZoomButton; buttonType++) { NSButton *button = [window standardWindowButton:buttonType]; if (button != nil) { [button setHidden:!visible]; [button setEnabled:visible]; } } } static BOOL gonaviShouldApplyMacWindowStyle(NSWindow *window) { if (window == nil) { return NO; } NSString *className = NSStringFromClass([window class]) ?: @""; NSString *delegateClassName = [window delegate] ? NSStringFromClass([[window delegate] class]) : @""; NSString *title = [window title] ?: @""; // 仅对主 WailsWindow 套用原生标题栏/全屏样式,避免误伤输入法候选窗、全屏过渡窗。 if ([className isEqualToString:@"WailsWindow"] || [delegateClassName isEqualToString:@"WindowDelegate"]) { return YES; } return [title isEqualToString:@"GoNavi"]; } static void gonaviApplyMacWindowStyle(BOOL enabled) { dispatch_async(dispatch_get_main_queue(), ^{ for (NSWindow *window in [NSApp windows]) { if (window == nil) { continue; } if (!gonaviShouldApplyMacWindowStyle(window)) { gonaviWriteNativeWindowLogLine(gonaviWindowDiagnosticLine(@"style:skip-non-app-window", window)); continue; } NSUInteger styleMask = [window styleMask]; styleMask |= NSWindowStyleMaskClosable; styleMask |= NSWindowStyleMaskMiniaturizable; styleMask |= NSWindowStyleMaskResizable; if (enabled) { styleMask |= NSWindowStyleMaskTitled; styleMask |= NSWindowStyleMaskFullSizeContentView; [window setStyleMask:styleMask]; [window setTitleVisibility:NSWindowTitleHidden]; [window setTitlebarAppearsTransparent:YES]; [window setMovableByWindowBackground:YES]; [window setCollectionBehavior:[window collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary]; gonaviSetWindowButtonsVisible(window, YES); } else { styleMask &= ~NSWindowStyleMaskTitled; styleMask &= ~NSWindowStyleMaskFullSizeContentView; [window setStyleMask:styleMask]; [window setTitleVisibility:NSWindowTitleVisible]; [window setTitlebarAppearsTransparent:NO]; [window setMovableByWindowBackground:YES]; gonaviSetWindowButtonsVisible(window, NO); } [[window contentView] setNeedsDisplay:YES]; [window invalidateShadow]; gonaviWriteNativeWindowLogLine(gonaviWindowDiagnosticLine(enabled ? @"style:enable-native-controls" : @"style:disable-native-controls", window)); } }); } */ import "C" import "unsafe" func installMacNativeWindowDiagnostics(logPath string) { if logPath == "" { return } cLogPath := C.CString(logPath) defer C.free(unsafe.Pointer(cLogPath)) C.gonaviConfigureNativeWindowDiagnostics(cLogPath) } func setMacNativeWindowControls(enabled bool) { state := resolveMacNativeWindowControlState(enabled) if state.ShowNativeButtons { C.gonaviApplyMacWindowStyle(C.gonaviBoolYES()) } else { C.gonaviApplyMacWindowStyle(C.gonaviBoolNO()) } }