From a65fd21e21bdd881977cb47c9570779f63624a7f Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Fri, 27 Feb 2026 15:27:53 -0600 Subject: [PATCH 01/18] feat(darwin): Add NSPanel support for Spotlight-like windows This adds support for creating NSPanel-based windows instead of NSWindow, enabling Spotlight-like auxiliary windows that can appear over fullscreen apps without yanking the user out of fullscreen. Key changes: - Add UsePanel and PanelOptions to MacWindow configuration - Implement WebviewPanel class (NSPanel subclass) with same functionality as WebviewWindow - Add panelNew() function to create panels with NonactivatingPanel style - Override sendEvent: on both WebviewWindow and WebviewPanel to ensure KeyBindings work regardless of WKWebView first responder state - Add collection behavior and window level configuration options The NSPanel with NSWindowStyleMaskNonactivatingPanel allows the panel to receive keyboard input without activating the owning application, which is the key feature for Spotlight/Alfred-like interfaces. Fixes #5023 Co-Authored-By: Claude Opus 4.5 --- v3/pkg/application/webview_window_darwin.go | 180 ++++++++++++++++- v3/pkg/application/webview_window_darwin.h | 15 ++ v3/pkg/application/webview_window_darwin.m | 198 +++++++++++++++++++ v3/pkg/application/webview_window_options.go | 37 ++++ 4 files changed, 421 insertions(+), 9 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 8fe0ed022..94e3f832b 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -27,7 +27,7 @@ extern void registerListener(unsigned int event); void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences) { NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; if (frameless) { - styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; + styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable; } WebviewWindow* window = [[WebviewWindow alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) styleMask:styleMask @@ -129,6 +129,114 @@ void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWa return window; } +// Create a new Panel (NSPanel variant for Spotlight-like windows) +void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences, + bool floatingPanel, bool nonactivatingPanel, bool becomesKeyOnlyIfNeeded, bool hidesOnDeactivate, bool worksWhenModal) { + NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; + if (frameless) { + styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable; + } + // Add NonactivatingPanel style for Spotlight-like behavior (key feature!) + if (nonactivatingPanel) { + styleMask |= NSWindowStyleMaskNonactivatingPanel; + } + + WebviewPanel* panel = [[WebviewPanel alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + + // Configure panel-specific properties + [panel setFloatingPanel:floatingPanel]; + [panel setBecomesKeyOnlyIfNeeded:becomesKeyOnlyIfNeeded]; + [panel setHidesOnDeactivate:hidesOnDeactivate]; + [panel setWorksWhenModal:worksWhenModal]; + + // Create delegate (same as window) + WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init]; + [delegate autorelease]; + + [panel setDelegate:delegate]; + delegate.windowId = id; + + // Add NSView to panel + NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; + [view autorelease]; + + [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + if( frameless ) { + [view setWantsLayer:YES]; + view.layer.cornerRadius = 8.0; + } + [panel setContentView:view]; + + // Embed wkwebview in panel (same as window) + NSRect frame = NSMakeRect(0, 0, width, height); + WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init]; + [config autorelease]; + + if (preferences.TabFocusesLinks != NULL) { + config.preferences.tabFocusesLinks = *preferences.TabFocusesLinks; + } + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110300 + if (@available(macOS 11.3, *)) { + if (preferences.TextInteractionEnabled != NULL) { + config.preferences.textInteractionEnabled = *preferences.TextInteractionEnabled; + } + } +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120300 + if (@available(macOS 12.3, *)) { + if (preferences.FullscreenEnabled != NULL) { + config.preferences.elementFullscreenEnabled = *preferences.FullscreenEnabled; + } + } +#endif + + config.suppressesIncrementalRendering = true; + config.applicationNameForUserAgent = @"wails.io"; + [config setURLSchemeHandler:delegate forURLScheme:@"wails"]; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 + if (@available(macOS 10.15, *)) { + config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled; + } +#endif + + WKUserContentController* userContentController = [WKUserContentController new]; + [userContentController autorelease]; + + [userContentController addScriptMessageHandler:delegate name:@"external"]; + config.userContentController = userContentController; + + WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config]; + [webView autorelease]; + + if (preferences.AllowsBackForwardNavigationGestures != NULL) { + webView.allowsBackForwardNavigationGestures = *preferences.AllowsBackForwardNavigationGestures; + } + + [view addSubview:webView]; + + [webView setNavigationDelegate:delegate]; + [webView setUIDelegate:delegate]; + + [webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + + if( enableDragAndDrop ) { + WebviewDrag* dragView = [[WebviewDrag alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; + [dragView autorelease]; + + [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [view addSubview:dragView]; + dragView.windowId = id; + } + + panel.webView = webView; + return panel; +} void printWindowStyle(void *window) { WebviewWindow* nsWindow = (WebviewWindow*)window; @@ -181,6 +289,38 @@ void printWindowStyle(void *window) { printf("\n"); } +void printWindowInfo(void *window) { + NSWindow* nsWindow = (NSWindow*)window; + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[nsWindow delegate]; + NSWindowStyleMask styleMask = [nsWindow styleMask]; + NSWindowCollectionBehavior behavior = [nsWindow collectionBehavior]; + NSInteger level = [nsWindow level]; + BOOL isKeyWindow = [nsWindow isKeyWindow]; + BOOL canBecomeKey = [nsWindow canBecomeKeyWindow]; + BOOL isPanel = [nsWindow isKindOfClass:[NSPanel class]]; + + printf("\n=== Window %d Info ===\n", delegate.windowId); + printf("Is NSPanel: %s\n", isPanel ? "YES" : "NO"); + printf("Window Level: %ld\n", (long)level); + printf("Is Key Window: %s\n", isKeyWindow ? "YES" : "NO"); + printf("Can Become Key: %s\n", canBecomeKey ? "YES" : "NO"); + + printf("Style Mask: "); + if (styleMask & NSWindowStyleMaskBorderless) printf("Borderless "); + if (styleMask & NSWindowStyleMaskTitled) printf("Titled "); + if (styleMask & NSWindowStyleMaskNonactivatingPanel) printf("NonactivatingPanel "); + if (styleMask & NSWindowStyleMaskResizable) printf("Resizable "); + printf("\n"); + + printf("Collection Behavior: "); + if (behavior & NSWindowCollectionBehaviorCanJoinAllSpaces) printf("CanJoinAllSpaces "); + if (behavior & NSWindowCollectionBehaviorFullScreenAuxiliary) printf("FullScreenAuxiliary "); + if (behavior & NSWindowCollectionBehaviorTransient) printf("Transient "); + if (behavior & NSWindowCollectionBehaviorIgnoresCycle) printf("IgnoresCycle "); + if (behavior & NSWindowCollectionBehaviorFullScreenPrimary) printf("FullScreenPrimary "); + printf("\n"); + printf("======================\n\n"); +} // setInvisibleTitleBarHeight sets the invisible title bar height void setInvisibleTitleBarHeight(void* window, unsigned int height) { @@ -959,6 +1099,10 @@ func (w *macosWebviewWindow) show() { C.windowShow(w.nsWindow) } +func (w *macosWebviewWindow) printInfo() { + C.printWindowInfo(w.nsWindow) +} + func (w *macosWebviewWindow) hide() { globalApplication.debug("Window hiding", "windowId", w.parent.id, "title", w.parent.options.Title) C.windowHide(w.nsWindow) @@ -1266,14 +1410,32 @@ func (w *macosWebviewWindow) run() { options := w.parent.options macOptions := options.Mac - w.nsWindow = C.windowNew(C.uint(w.parent.id), - C.int(options.Width), - C.int(options.Height), - C.bool(macOptions.EnableFraudulentWebsiteWarnings), - C.bool(options.Frameless), - C.bool(options.EnableFileDrop), - w.getWebviewPreferences(), - ) + // Create either NSPanel or NSWindow based on UsePanel option + if macOptions.UsePanel { + panelOpts := macOptions.PanelOptions + w.nsWindow = C.panelNew(C.uint(w.parent.id), + C.int(options.Width), + C.int(options.Height), + C.bool(macOptions.EnableFraudulentWebsiteWarnings), + C.bool(options.Frameless), + C.bool(options.EnableFileDrop), + w.getWebviewPreferences(), + C.bool(panelOpts.FloatingPanel), + C.bool(panelOpts.NonactivatingPanel), + C.bool(panelOpts.BecomesKeyOnlyIfNeeded), + C.bool(panelOpts.HidesOnDeactivate), + C.bool(panelOpts.WorksWhenModal), + ) + } else { + w.nsWindow = C.windowNew(C.uint(w.parent.id), + C.int(options.Width), + C.int(options.Height), + C.bool(macOptions.EnableFraudulentWebsiteWarnings), + C.bool(options.Frameless), + C.bool(options.EnableFileDrop), + w.getWebviewPreferences(), + ) + } w.setTitle(options.Title) w.setResizable(!options.DisableResize) if options.MinWidth != 0 || options.MinHeight != 0 { diff --git a/v3/pkg/application/webview_window_darwin.h b/v3/pkg/application/webview_window_darwin.h index 489ff582e..cab8216f3 100644 --- a/v3/pkg/application/webview_window_darwin.h +++ b/v3/pkg/application/webview_window_darwin.h @@ -18,6 +18,21 @@ @end +// WebviewPanel is an NSPanel variant for Spotlight-like auxiliary windows. +// NSPanel supports NSWindowStyleMaskNonactivatingPanel which allows the panel +// to receive keyboard input without activating the owning application. +@interface WebviewPanel : NSPanel +- (BOOL) canBecomeKeyWindow; +- (BOOL) canBecomeMainWindow; +- (BOOL) acceptsFirstResponder; +- (BOOL) becomeFirstResponder; +- (BOOL) resignFirstResponder; +- (WebviewPanel*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; + +@property (assign) WKWebView* webView; + +@end + @interface WebviewWindowDelegate : NSObject @property unsigned int windowId; diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index 48130a562..19d9d62e3 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -28,6 +28,17 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { [self setMovableByWindowBackground:YES]; return self; } +// Override sendEvent to intercept key events BEFORE WKWebView consumes them +// This ensures KeyBindings work regardless of first responder state +- (void)sendEvent:(NSEvent *)event { + if (event.type == NSEventTypeKeyDown) { + // Process through keybinding system first + [self keyDown:event]; + // Still pass to WKWebView for normal input handling + // (keybinding callbacks like Hide will take effect regardless) + } + [super sendEvent:event]; +} - (void)keyDown:(NSEvent *)event { NSUInteger modifierFlags = event.modifierFlags; // Create an array to hold the modifier strings @@ -243,6 +254,193 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { } } @end + +// WebviewPanel implementation - NSPanel variant for Spotlight-like windows +@implementation WebviewPanel +- (WebviewPanel*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; +{ + self = [super initWithContentRect:contentRect styleMask:windowStyle backing:bufferingType defer:deferCreation]; + [self setAlphaValue:1.0]; + [self setBackgroundColor:[NSColor clearColor]]; + [self setOpaque:NO]; + [self setMovableByWindowBackground:YES]; + return self; +} +// Override sendEvent to intercept key events BEFORE WKWebView consumes them +// This ensures KeyBindings work regardless of first responder state +- (void)sendEvent:(NSEvent *)event { + if (event.type == NSEventTypeKeyDown) { + // Process through keybinding system first + [self keyDown:event]; + // Still pass to WKWebView for normal input handling + // (keybinding callbacks like Hide will take effect regardless) + } + [super sendEvent:event]; +} +- (void)keyDown:(NSEvent *)event { + NSUInteger modifierFlags = event.modifierFlags; + NSMutableArray *modifierStrings = [NSMutableArray array]; + if (modifierFlags & NSEventModifierFlagShift) { + [modifierStrings addObject:@"shift"]; + } + if (modifierFlags & NSEventModifierFlagControl) { + [modifierStrings addObject:@"ctrl"]; + } + if (modifierFlags & NSEventModifierFlagOption) { + [modifierStrings addObject:@"option"]; + } + if (modifierFlags & NSEventModifierFlagCommand) { + [modifierStrings addObject:@"cmd"]; + } + NSString *keyString = [self keyStringFromEvent:event]; + if (keyString.length > 0) { + [modifierStrings addObject:keyString]; + } + NSString *keyEventString = [modifierStrings componentsJoinedByString:@"+"]; + const char* utf8String = [keyEventString UTF8String]; + WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)self.delegate; + processWindowKeyDownEvent(delegate.windowId, utf8String); +} +- (NSString *)keyStringFromEvent:(NSEvent *)event { + NSString *characters = [event characters]; + if (characters.length == 0) { + return @""; + } + if ([characters isEqualToString:@"\r"]) { + return @"enter"; + } + if ([characters isEqualToString:@"\b"]) { + return @"backspace"; + } + if ([characters isEqualToString:@"\e"]) { + return @"escape"; + } + if ([characters isEqualToString:@"\x0B"]) { + return @"page down"; + } + if ([characters isEqualToString:@"\x0E"]) { + return @"page up"; + } + if ([characters isEqualToString:@"\x01"]) { + return @"home"; + } + if ([characters isEqualToString:@"\x04"]) { + return @"end"; + } + if ([characters isEqualToString:@"\x0C"]) { + return @"clear"; + } + switch ([event keyCode]) { + case 122: return @"f1"; + case 120: return @"f2"; + case 99: return @"f3"; + case 118: return @"f4"; + case 96: return @"f5"; + case 97: return @"f6"; + case 98: return @"f7"; + case 100: return @"f8"; + case 101: return @"f9"; + case 109: return @"f10"; + case 103: return @"f11"; + case 111: return @"f12"; + case 105: return @"f13"; + case 107: return @"f14"; + case 113: return @"f15"; + case 106: return @"f16"; + case 64: return @"f17"; + case 79: return @"f18"; + case 80: return @"f19"; + case 90: return @"f20"; + case 0: return @"a"; + case 11: return @"b"; + case 8: return @"c"; + case 2: return @"d"; + case 14: return @"e"; + case 3: return @"f"; + case 5: return @"g"; + case 4: return @"h"; + case 34: return @"i"; + case 38: return @"j"; + case 40: return @"k"; + case 37: return @"l"; + case 46: return @"m"; + case 45: return @"n"; + case 31: return @"o"; + case 35: return @"p"; + case 12: return @"q"; + case 15: return @"r"; + case 1: return @"s"; + case 17: return @"t"; + case 32: return @"u"; + case 9: return @"v"; + case 13: return @"w"; + case 7: return @"x"; + case 16: return @"y"; + case 6: return @"z"; + case 29: return @"0"; + case 18: return @"1"; + case 19: return @"2"; + case 20: return @"3"; + case 21: return @"4"; + case 23: return @"5"; + case 22: return @"6"; + case 26: return @"7"; + case 28: return @"8"; + case 25: return @"9"; + case 51: return @"delete"; + case 117: return @"forward delete"; + case 123: return @"left"; + case 124: return @"right"; + case 126: return @"up"; + case 125: return @"down"; + case 48: return @"tab"; + case 53: return @"escape"; + case 49: return @"space"; + case 33: return @"["; + case 30: return @"]"; + case 43: return @","; + case 27: return @"-"; + case 39: return @"'"; + case 44: return @"/"; + case 47: return @"."; + case 41: return @";"; + case 24: return @"="; + case 50: return @"`"; + case 42: return @"\\"; + default: return @""; + } +} +- (BOOL)canBecomeKeyWindow { + return YES; +} +- (BOOL) canBecomeMainWindow { + return NO; // Panels typically don't become main window +} +- (BOOL) acceptsFirstResponder { + return YES; +} +- (BOOL) becomeFirstResponder { + return YES; +} +- (BOOL) resignFirstResponder { + return YES; +} +- (void) setDelegate:(id) delegate { + [delegate retain]; + [super setDelegate: delegate]; + if ([delegate isKindOfClass:[WebviewWindowDelegate class]]) { + [self registerForDraggedTypes:@[NSFilenamesPboardType]]; + } +} +- (void) dealloc { + [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"external"]; + if (self.delegate) { + [self.delegate release]; + } + [super dealloc]; +} +@end + @implementation WebviewWindowDelegate - (NSDragOperation)draggingEntered:(id)sender { NSPasteboard *pasteboard = [sender draggingPasteboard]; diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index c1b1bf164..20d5ed985 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -485,6 +485,43 @@ type MacWindow struct { // LiquidGlass contains configuration for the Liquid Glass effect LiquidGlass MacLiquidGlass + + // UsePanel creates an NSPanel instead of NSWindow. + // Panels are suitable for auxiliary windows like Spotlight-style interfaces. + // NSPanel supports NSWindowStyleMaskNonactivatingPanel which allows the panel + // to receive keyboard input without activating the owning application. + UsePanel bool + + // PanelOptions configures NSPanel-specific behavior (only used when UsePanel is true) + PanelOptions MacPanelOptions +} + +// MacPanelOptions contains NSPanel-specific configuration. +// These options only apply when MacWindow.UsePanel is true. +type MacPanelOptions struct { + // FloatingPanel makes the panel float above other windows. + // Equivalent to [panel setFloatingPanel:YES] + FloatingPanel bool + + // NonactivatingPanel uses NSWindowStyleMaskNonactivatingPanel style. + // This allows the panel to receive keyboard input without activating the app. + // This is the key setting for Spotlight-like behavior. + NonactivatingPanel bool + + // BecomesKeyOnlyIfNeeded controls when the panel becomes key window. + // If true, the panel only becomes key when a view that needs keyboard input is clicked. + // If false (default), the panel becomes key immediately when shown. + // For Spotlight-like behavior, set to false. + BecomesKeyOnlyIfNeeded bool + + // HidesOnDeactivate controls whether the panel hides when the app loses focus. + // NSPanel defaults to YES, unlike NSWindow which defaults to NO. + // Set to true for Spotlight-like behavior (hide when clicking outside). + HidesOnDeactivate bool + + // WorksWhenModal allows the panel to receive events during modal sessions. + // Typically not needed for Spotlight-like panels. + WorksWhenModal bool } type MacWindowLevel string From 87e4d7ca2d7bbb52adfb296a8a3bb87d0754a8a0 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Fri, 27 Feb 2026 15:33:24 -0600 Subject: [PATCH 02/18] fix(darwin): Add NSWindowStyleMaskMiniaturizable to frameless style mask Co-Authored-By: Claude Opus 4.5 --- v3/pkg/application/webview_window_darwin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 94e3f832b..106b59d4c 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -27,7 +27,7 @@ extern void registerListener(unsigned int event); void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences) { NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; if (frameless) { - styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable; + styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; } WebviewWindow* window = [[WebviewWindow alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) styleMask:styleMask @@ -134,7 +134,7 @@ void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWar bool floatingPanel, bool nonactivatingPanel, bool becomesKeyOnlyIfNeeded, bool hidesOnDeactivate, bool worksWhenModal) { NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; if (frameless) { - styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable; + styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; } // Add NonactivatingPanel style for Spotlight-like behavior (key feature!) if (nonactivatingPanel) { From 59473bd3982fe3cabe67c50e8cff3e4abb804f0a Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Fri, 27 Feb 2026 15:36:15 -0600 Subject: [PATCH 03/18] chore(darwin): Remove debug print functions Co-Authored-By: Claude Opus 4.5 --- v3/pkg/application/webview_window_darwin.go | 88 --------------------- 1 file changed, 88 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 106b59d4c..da8d89ce5 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -238,90 +238,6 @@ void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWar return panel; } -void printWindowStyle(void *window) { - WebviewWindow* nsWindow = (WebviewWindow*)window; - NSWindowStyleMask styleMask = [nsWindow styleMask]; - // Get delegate - WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate]; - - printf("Window %d style mask: ", windowDelegate.windowId); - - if (styleMask & NSWindowStyleMaskTitled) - { - printf("NSWindowStyleMaskTitled "); - } - - if (styleMask & NSWindowStyleMaskClosable) - { - printf("NSWindowStyleMaskClosable "); - } - - if (styleMask & NSWindowStyleMaskMiniaturizable) - { - printf("NSWindowStyleMaskMiniaturizable "); - } - - if (styleMask & NSWindowStyleMaskResizable) - { - printf("NSWindowStyleMaskResizable "); - } - - if (styleMask & NSWindowStyleMaskFullSizeContentView) - { - printf("NSWindowStyleMaskFullSizeContentView "); - } - - if (styleMask & NSWindowStyleMaskNonactivatingPanel) - { - printf("NSWindowStyleMaskNonactivatingPanel "); - } - - if (styleMask & NSWindowStyleMaskFullScreen) - { - printf("NSWindowStyleMaskFullScreen "); - } - - if (styleMask & NSWindowStyleMaskBorderless) - { - printf("MSWindowStyleMaskBorderless "); - } - - printf("\n"); -} - -void printWindowInfo(void *window) { - NSWindow* nsWindow = (NSWindow*)window; - WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[nsWindow delegate]; - NSWindowStyleMask styleMask = [nsWindow styleMask]; - NSWindowCollectionBehavior behavior = [nsWindow collectionBehavior]; - NSInteger level = [nsWindow level]; - BOOL isKeyWindow = [nsWindow isKeyWindow]; - BOOL canBecomeKey = [nsWindow canBecomeKeyWindow]; - BOOL isPanel = [nsWindow isKindOfClass:[NSPanel class]]; - - printf("\n=== Window %d Info ===\n", delegate.windowId); - printf("Is NSPanel: %s\n", isPanel ? "YES" : "NO"); - printf("Window Level: %ld\n", (long)level); - printf("Is Key Window: %s\n", isKeyWindow ? "YES" : "NO"); - printf("Can Become Key: %s\n", canBecomeKey ? "YES" : "NO"); - - printf("Style Mask: "); - if (styleMask & NSWindowStyleMaskBorderless) printf("Borderless "); - if (styleMask & NSWindowStyleMaskTitled) printf("Titled "); - if (styleMask & NSWindowStyleMaskNonactivatingPanel) printf("NonactivatingPanel "); - if (styleMask & NSWindowStyleMaskResizable) printf("Resizable "); - printf("\n"); - - printf("Collection Behavior: "); - if (behavior & NSWindowCollectionBehaviorCanJoinAllSpaces) printf("CanJoinAllSpaces "); - if (behavior & NSWindowCollectionBehaviorFullScreenAuxiliary) printf("FullScreenAuxiliary "); - if (behavior & NSWindowCollectionBehaviorTransient) printf("Transient "); - if (behavior & NSWindowCollectionBehaviorIgnoresCycle) printf("IgnoresCycle "); - if (behavior & NSWindowCollectionBehaviorFullScreenPrimary) printf("FullScreenPrimary "); - printf("\n"); - printf("======================\n\n"); -} - // setInvisibleTitleBarHeight sets the invisible title bar height void setInvisibleTitleBarHeight(void* window, unsigned int height) { WebviewWindow* nsWindow = (WebviewWindow*)window; @@ -1099,10 +1015,6 @@ func (w *macosWebviewWindow) show() { C.windowShow(w.nsWindow) } -func (w *macosWebviewWindow) printInfo() { - C.printWindowInfo(w.nsWindow) -} - func (w *macosWebviewWindow) hide() { globalApplication.debug("Window hiding", "windowId", w.parent.id, "title", w.parent.options.Title) C.windowHide(w.nsWindow) From 0c600ae76db5be55dbf9ae8bd8cb543448848ef5 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Fri, 27 Feb 2026 16:17:54 -0600 Subject: [PATCH 04/18] restore removed print function --- v3/pkg/application/webview_window_darwin.go | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index da8d89ce5..5dafeaf6d 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -129,6 +129,57 @@ void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWa return window; } +void printWindowStyle(void *window) { + WebviewWindow* nsWindow = (WebviewWindow*)window; + NSWindowStyleMask styleMask = [nsWindow styleMask]; + // Get delegate + WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate]; + + printf("Window %d style mask: ", windowDelegate.windowId); + + if (styleMask & NSWindowStyleMaskTitled) + { + printf("NSWindowStyleMaskTitled "); + } + + if (styleMask & NSWindowStyleMaskClosable) + { + printf("NSWindowStyleMaskClosable "); + } + + if (styleMask & NSWindowStyleMaskMiniaturizable) + { + printf("NSWindowStyleMaskMiniaturizable "); + } + + if (styleMask & NSWindowStyleMaskResizable) + { + printf("NSWindowStyleMaskResizable "); + } + + if (styleMask & NSWindowStyleMaskFullSizeContentView) + { + printf("NSWindowStyleMaskFullSizeContentView "); + } + + if (styleMask & NSWindowStyleMaskNonactivatingPanel) + { + printf("NSWindowStyleMaskNonactivatingPanel "); + } + + if (styleMask & NSWindowStyleMaskFullScreen) + { + printf("NSWindowStyleMaskFullScreen "); + } + + if (styleMask & NSWindowStyleMaskBorderless) + { + printf("MSWindowStyleMaskBorderless "); + } + + printf("\n"); +} + // Create a new Panel (NSPanel variant for Spotlight-like windows) void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences, bool floatingPanel, bool nonactivatingPanel, bool becomesKeyOnlyIfNeeded, bool hidesOnDeactivate, bool worksWhenModal) { From 95d3e75a8f498ca4d3db363578bf736a5bd6b461 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Fri, 27 Feb 2026 16:42:37 -0600 Subject: [PATCH 05/18] Add WindowClass option, remove references to spotlight in comments --- v3/pkg/application/webview_window_darwin.go | 8 +++--- v3/pkg/application/webview_window_darwin.h | 2 +- v3/pkg/application/webview_window_darwin.m | 2 +- v3/pkg/application/webview_window_options.go | 28 ++++++++++++-------- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 5dafeaf6d..b28cf085c 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -180,14 +180,14 @@ void printWindowStyle(void *window) { printf("\n"); } -// Create a new Panel (NSPanel variant for Spotlight-like windows) +// Create a new Panel (NSPanel variant for auxiliary windows) void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences, bool floatingPanel, bool nonactivatingPanel, bool becomesKeyOnlyIfNeeded, bool hidesOnDeactivate, bool worksWhenModal) { NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; if (frameless) { styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; } - // Add NonactivatingPanel style for Spotlight-like behavior (key feature!) + // Add NonactivatingPanel style to receive keyboard input without activating the app if (nonactivatingPanel) { styleMask |= NSWindowStyleMaskNonactivatingPanel; } @@ -1373,8 +1373,8 @@ func (w *macosWebviewWindow) run() { options := w.parent.options macOptions := options.Mac - // Create either NSPanel or NSWindow based on UsePanel option - if macOptions.UsePanel { + // Create either NSPanel or NSWindow based on WindowClass + if macOptions.WindowClass == NSPanel { panelOpts := macOptions.PanelOptions w.nsWindow = C.panelNew(C.uint(w.parent.id), C.int(options.Width), diff --git a/v3/pkg/application/webview_window_darwin.h b/v3/pkg/application/webview_window_darwin.h index cab8216f3..0129df337 100644 --- a/v3/pkg/application/webview_window_darwin.h +++ b/v3/pkg/application/webview_window_darwin.h @@ -18,7 +18,7 @@ @end -// WebviewPanel is an NSPanel variant for Spotlight-like auxiliary windows. +// WebviewPanel is an NSPanel variant for auxiliary windows. // NSPanel supports NSWindowStyleMaskNonactivatingPanel which allows the panel // to receive keyboard input without activating the owning application. @interface WebviewPanel : NSPanel diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index 19d9d62e3..12d0d46cf 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -255,7 +255,7 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { } @end -// WebviewPanel implementation - NSPanel variant for Spotlight-like windows +// WebviewPanel implementation - NSPanel variant for auxiliary windows @implementation WebviewPanel - (WebviewPanel*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; { diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index 20d5ed985..301325b5f 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -486,18 +486,28 @@ type MacWindow struct { // LiquidGlass contains configuration for the Liquid Glass effect LiquidGlass MacLiquidGlass - // UsePanel creates an NSPanel instead of NSWindow. - // Panels are suitable for auxiliary windows like Spotlight-style interfaces. - // NSPanel supports NSWindowStyleMaskNonactivatingPanel which allows the panel - // to receive keyboard input without activating the owning application. - UsePanel bool + // WindowClass specifies whether to create an NSWindow or NSPanel. + // Use NSPanel for auxiliary windows that can appear over fullscreen + // apps without activating the application. + // Default: NSWindow + WindowClass MacWindowClass - // PanelOptions configures NSPanel-specific behavior (only used when UsePanel is true) + // PanelOptions configures NSPanel-specific behavior (only used when WindowClass == NSPanel) PanelOptions MacPanelOptions } +// MacWindowClass specifies the underlying macOS window class to use +type MacWindowClass int + +const ( + // NSWindow creates a standard NSWindow (default) + NSWindow MacWindowClass = iota + // NSPanel creates an NSPanel for auxiliary windows + NSPanel +) + // MacPanelOptions contains NSPanel-specific configuration. -// These options only apply when MacWindow.UsePanel is true. +// These options only apply when MacWindow.WindowClass == NSPanel. type MacPanelOptions struct { // FloatingPanel makes the panel float above other windows. // Equivalent to [panel setFloatingPanel:YES] @@ -505,22 +515,18 @@ type MacPanelOptions struct { // NonactivatingPanel uses NSWindowStyleMaskNonactivatingPanel style. // This allows the panel to receive keyboard input without activating the app. - // This is the key setting for Spotlight-like behavior. NonactivatingPanel bool // BecomesKeyOnlyIfNeeded controls when the panel becomes key window. // If true, the panel only becomes key when a view that needs keyboard input is clicked. // If false (default), the panel becomes key immediately when shown. - // For Spotlight-like behavior, set to false. BecomesKeyOnlyIfNeeded bool // HidesOnDeactivate controls whether the panel hides when the app loses focus. // NSPanel defaults to YES, unlike NSWindow which defaults to NO. - // Set to true for Spotlight-like behavior (hide when clicking outside). HidesOnDeactivate bool // WorksWhenModal allows the panel to receive events during modal sessions. - // Typically not needed for Spotlight-like panels. WorksWhenModal bool } From 2fa3834d8509c68ecd65df34edd6463f61112079 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Fri, 27 Feb 2026 17:56:41 -0600 Subject: [PATCH 06/18] comments, remove unnecessary properties --- v3/pkg/application/webview_window_darwin.go | 16 ++++---- v3/pkg/application/webview_window_darwin.h | 3 -- v3/pkg/application/webview_window_darwin.m | 1 - v3/pkg/application/webview_window_options.go | 39 ++++++-------------- 4 files changed, 19 insertions(+), 40 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index b28cf085c..e1dee398b 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -180,28 +180,27 @@ void printWindowStyle(void *window) { printf("\n"); } -// Create a new Panel (NSPanel variant for auxiliary windows) +// Create a new Panel void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences, - bool floatingPanel, bool nonactivatingPanel, bool becomesKeyOnlyIfNeeded, bool hidesOnDeactivate, bool worksWhenModal) { + bool floatingPanel, bool becomesKeyOnlyIfNeeded, bool nonactivatingPanel, bool utilityWindow) { NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; if (frameless) { styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; } - // Add NonactivatingPanel style to receive keyboard input without activating the app if (nonactivatingPanel) { styleMask |= NSWindowStyleMaskNonactivatingPanel; } + if (utilityWindow) { + styleMask |= NSWindowStyleMaskUtilityWindow; + } WebviewPanel* panel = [[WebviewPanel alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; - // Configure panel-specific properties [panel setFloatingPanel:floatingPanel]; [panel setBecomesKeyOnlyIfNeeded:becomesKeyOnlyIfNeeded]; - [panel setHidesOnDeactivate:hidesOnDeactivate]; - [panel setWorksWhenModal:worksWhenModal]; // Create delegate (same as window) WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init]; @@ -1384,10 +1383,9 @@ func (w *macosWebviewWindow) run() { C.bool(options.EnableFileDrop), w.getWebviewPreferences(), C.bool(panelOpts.FloatingPanel), - C.bool(panelOpts.NonactivatingPanel), C.bool(panelOpts.BecomesKeyOnlyIfNeeded), - C.bool(panelOpts.HidesOnDeactivate), - C.bool(panelOpts.WorksWhenModal), + C.bool(panelOpts.NonactivatingPanel), + C.bool(panelOpts.UtilityWindow), ) } else { w.nsWindow = C.windowNew(C.uint(w.parent.id), diff --git a/v3/pkg/application/webview_window_darwin.h b/v3/pkg/application/webview_window_darwin.h index 0129df337..4e129f2fc 100644 --- a/v3/pkg/application/webview_window_darwin.h +++ b/v3/pkg/application/webview_window_darwin.h @@ -18,9 +18,6 @@ @end -// WebviewPanel is an NSPanel variant for auxiliary windows. -// NSPanel supports NSWindowStyleMaskNonactivatingPanel which allows the panel -// to receive keyboard input without activating the owning application. @interface WebviewPanel : NSPanel - (BOOL) canBecomeKeyWindow; - (BOOL) canBecomeMainWindow; diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index 12d0d46cf..bf3a0c74f 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -255,7 +255,6 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { } @end -// WebviewPanel implementation - NSPanel variant for auxiliary windows @implementation WebviewPanel - (WebviewPanel*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; { diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index 301325b5f..3c3fdaf11 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -486,48 +486,33 @@ type MacWindow struct { // LiquidGlass contains configuration for the Liquid Glass effect LiquidGlass MacLiquidGlass - // WindowClass specifies whether to create an NSWindow or NSPanel. - // Use NSPanel for auxiliary windows that can appear over fullscreen - // apps without activating the application. - // Default: NSWindow + // WindowClass is the window class for the window WindowClass MacWindowClass - // PanelOptions configures NSPanel-specific behavior (only used when WindowClass == NSPanel) + // PanelOptions contains options for NSPanel windows PanelOptions MacPanelOptions } -// MacWindowClass specifies the underlying macOS window class to use +// MacWindowClass is the window class for macOS type MacWindowClass int const ( - // NSWindow creates a standard NSWindow (default) + // NSWindow - The default value. A window that an app displays on the screen. NSWindow MacWindowClass = iota - // NSPanel creates an NSPanel for auxiliary windows + // NSPanel - The window will be an NSPanel, a special kind of window that typically performs a function that is auxiliary to the main window NSPanel ) -// MacPanelOptions contains NSPanel-specific configuration. -// These options only apply when MacWindow.WindowClass == NSPanel. +// MacPanelOptions contains options for NSPanel windows type MacPanelOptions struct { - // FloatingPanel makes the panel float above other windows. - // Equivalent to [panel setFloatingPanel:YES] + // FloatingPanel will make the panel float above other windows FloatingPanel bool - - // NonactivatingPanel uses NSWindowStyleMaskNonactivatingPanel style. - // This allows the panel to receive keyboard input without activating the app. - NonactivatingPanel bool - - // BecomesKeyOnlyIfNeeded controls when the panel becomes key window. - // If true, the panel only becomes key when a view that needs keyboard input is clicked. - // If false (default), the panel becomes key immediately when shown. + // BecomesKeyOnlyIfNeeded will make the panel become key only when needed BecomesKeyOnlyIfNeeded bool - - // HidesOnDeactivate controls whether the panel hides when the app loses focus. - // NSPanel defaults to YES, unlike NSWindow which defaults to NO. - HidesOnDeactivate bool - - // WorksWhenModal allows the panel to receive events during modal sessions. - WorksWhenModal bool + // NonactivatingPanel will apply the NSWindowStyleMaskNonactivatingPanel style + NonactivatingPanel bool + // UtilityWindow will apply the NSWindowStyleMaskUtilityWindow style + UtilityWindow bool } type MacWindowLevel string From ffe6e917449a22e10b6659af4549b6cd92dcd861 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Fri, 27 Feb 2026 18:17:35 -0600 Subject: [PATCH 07/18] docs: Add NSPanel support to changelog Co-Authored-By: Claude Opus 4.5 --- website/src/pages/changelog.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index c5f51220e..232d023d8 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- [macOS] Added NSPanel support for Spotlight-like auxiliary windows. Panels can appear over fullscreen apps without activating the application using `NSWindowStyleMaskNonactivatingPanel`. Configure via `Mac.WindowClass` and `Mac.PanelOptions` in window options. Added by [@Grantmartin2002](https://github.com/Grantmartin2002) in [PR](https://github.com/wailsapp/wails/pull/5024) + ### Fixed - Fixed locking issue on Windows when multiselect dialog returns an error. Fixed in [PR](https://github.com/wailsapp/wails/pull/4156) by @johannes-luebke From 4a839904619419b39b2f7078bc07de203ed34da1 Mon Sep 17 00:00:00 2001 From: Grant Martin <69131375+Grantmartin2002@users.noreply.github.com> Date: Fri, 27 Feb 2026 19:00:47 -0600 Subject: [PATCH 08/18] Update changelog for macOS NSPanel support --- website/src/pages/changelog.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index 232d023d8..01d1f07af 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- [macOS] Added NSPanel support for Spotlight-like auxiliary windows. Panels can appear over fullscreen apps without activating the application using `NSWindowStyleMaskNonactivatingPanel`. Configure via `Mac.WindowClass` and `Mac.PanelOptions` in window options. Added by [@Grantmartin2002](https://github.com/Grantmartin2002) in [PR](https://github.com/wailsapp/wails/pull/5024) +- Added NSPanel support for macOS Windows. Panels serve as auxillary windows and can appear over fullscreen apps without activating the application using `NSWindowStyleMaskNonactivatingPanel`. Configure via `Mac.WindowClass` and `Mac.PanelOptions` in window options. Added by [@Grantmartin2002](https://github.com/Grantmartin2002) in [PR](https://github.com/wailsapp/wails/pull/5024) ### Fixed - Fixed locking issue on Windows when multiselect dialog returns an error. Fixed in [PR](https://github.com/wailsapp/wails/pull/4156) by @johannes-luebke From cc5af7279ce98eecb8d106464d8246057e3add8d Mon Sep 17 00:00:00 2001 From: Grant Martin <69131375+Grantmartin2002@users.noreply.github.com> Date: Fri, 27 Feb 2026 19:01:15 -0600 Subject: [PATCH 09/18] Update changelog for NSPanel support on macOS --- website/src/pages/changelog.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index 01d1f07af..7ee827ba5 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Added NSPanel support for macOS Windows. Panels serve as auxillary windows and can appear over fullscreen apps without activating the application using `NSWindowStyleMaskNonactivatingPanel`. Configure via `Mac.WindowClass` and `Mac.PanelOptions` in window options. Added by [@Grantmartin2002](https://github.com/Grantmartin2002) in [PR](https://github.com/wailsapp/wails/pull/5024) +- Added NSPanel support for macOS. Panels serve as auxillary windows and can appear over fullscreen apps without activating the application using `NSWindowStyleMaskNonactivatingPanel`. Configure via `Mac.WindowClass` and `Mac.PanelOptions` in window options. Added by [@Grantmartin2002](https://github.com/Grantmartin2002) in [PR](https://github.com/wailsapp/wails/pull/5024) ### Fixed - Fixed locking issue on Windows when multiselect dialog returns an error. Fixed in [PR](https://github.com/wailsapp/wails/pull/4156) by @johannes-luebke From 388449825f81ac342e00f8df475edaf238183d60 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Fri, 27 Feb 2026 19:24:56 -0600 Subject: [PATCH 10/18] update documentation --- .../content/docs/features/windows/options.mdx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx index 9b38d5e2b..62b100567 100644 --- a/docs/src/content/docs/features/windows/options.mdx +++ b/docs/src/content/docs/features/windows/options.mdx @@ -828,8 +828,9 @@ Controls how the window behaves across macOS Spaces and fullscreen. These are bi - `MacWindowCollectionBehaviorFullScreenAllowsTiling` - Allows side-by-side tiling (macOS 10.11+) - `MacWindowCollectionBehaviorFullScreenDisallowsTiling` - Prevents tiling (macOS 10.11+) -**Example - Spotlight-like window:** +**Note:** NSWindow fullscreen overlay requires application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } } +**Example - Spotlight-like window:** ```go // Window that appears on all Spaces AND can overlay fullscreen apps Mac: application.MacWindow{ @@ -848,6 +849,32 @@ Mac: application.MacWindow{ }, ``` +**WindowClass** (`MacWindowClass`) +- `NSWindow` - Standard window (default) +- `NSPanel` - Auxiliary window that can float above other windows and receive input without activating the application + +**PanelOptions** (`MacPanelOptions`) + +Options for NSPanel windows (only applies when `WindowClass` is `NSPanel`): +- `FloatingPanel` - Panel floats above other windows +- `BecomesKeyOnlyIfNeeded` - Panel becomes key only when needed +- `NonactivatingPanel` - Panel receives input without activating the application +- `UtilityWindow` - Panel uses utility window style + +**Note:** Unlike NSWindow, NSPanel fullscreen overlay does NOT require application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } } + +**Example - Spotlight-like panel:** +```go +Mac: application.MacWindow{ + WindowClass: application.NSPanel, + PanelOptions: application.MacPanelOptions{ + NonactivatingPanel: true, + }, + CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces | + application.MacWindowCollectionBehaviorFullScreenAuxiliary, +}, +``` + ### Windows Options ```go From 6bb5a5709b4e36f29ef79186b17a31bcec6f2e17 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Mon, 2 Mar 2026 16:10:57 -0600 Subject: [PATCH 11/18] fix(darwin): preserve FloatingPanel level, clean up sendEvent - Fix FloatingPanel level override: skip setting default WindowLevel when NSPanel has FloatingPanel=true, preserving setFloatingPanel behavior - Clean up sendEvent override comments - Fix typo in changelog (auxillary -> auxiliary) Co-Authored-By: Claude Opus 4.5 --- v3/pkg/application/webview_window_darwin.go | 9 ++++++--- v3/pkg/application/webview_window_darwin.m | 6 ------ website/src/pages/changelog.mdx | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index e1dee398b..8b243dadc 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -1425,10 +1425,13 @@ func (w *macosWebviewWindow) run() { case MacBackdropNormal: } - if macOptions.WindowLevel == "" { - macOptions.WindowLevel = MacWindowLevelNormal + // Only set window level if explicitly specified, or if not a floating panel + // (setFloatingPanel:YES already sets NSFloatingWindowLevel, so don't override it) + if macOptions.WindowLevel != "" { + w.setWindowLevel(macOptions.WindowLevel) + } else if !(macOptions.WindowClass == NSPanel && macOptions.PanelOptions.FloatingPanel) { + w.setWindowLevel(MacWindowLevelNormal) } - w.setWindowLevel(macOptions.WindowLevel) // Set collection behavior (defaults to FullScreenPrimary for backwards compatibility) w.setCollectionBehavior(macOptions.CollectionBehavior) diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index bf3a0c74f..83ed6170c 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -32,10 +32,7 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { // This ensures KeyBindings work regardless of first responder state - (void)sendEvent:(NSEvent *)event { if (event.type == NSEventTypeKeyDown) { - // Process through keybinding system first [self keyDown:event]; - // Still pass to WKWebView for normal input handling - // (keybinding callbacks like Hide will take effect regardless) } [super sendEvent:event]; } @@ -269,10 +266,7 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { // This ensures KeyBindings work regardless of first responder state - (void)sendEvent:(NSEvent *)event { if (event.type == NSEventTypeKeyDown) { - // Process through keybinding system first [self keyDown:event]; - // Still pass to WKWebView for normal input handling - // (keybinding callbacks like Hide will take effect regardless) } [super sendEvent:event]; } diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index 7ee827ba5..d65c85432 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Added NSPanel support for macOS. Panels serve as auxillary windows and can appear over fullscreen apps without activating the application using `NSWindowStyleMaskNonactivatingPanel`. Configure via `Mac.WindowClass` and `Mac.PanelOptions` in window options. Added by [@Grantmartin2002](https://github.com/Grantmartin2002) in [PR](https://github.com/wailsapp/wails/pull/5024) +- Added NSPanel support for macOS. Panels serve as auxiliary windows and can appear over fullscreen apps without activating the application using `NSWindowStyleMaskNonactivatingPanel`. Configure via `Mac.WindowClass` and `Mac.PanelOptions` in window options. Added by [@Grantmartin2002](https://github.com/Grantmartin2002) in [PR](https://github.com/wailsapp/wails/pull/5024) ### Fixed - Fixed locking issue on Windows when multiselect dialog returns an error. Fixed in [PR](https://github.com/wailsapp/wails/pull/4156) by @johannes-luebke From 63759b29065fa2ef8b4832978b875ed0e71d59f7 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Mon, 2 Mar 2026 16:12:19 -0600 Subject: [PATCH 12/18] escape mdx --- docs/src/content/docs/features/windows/options.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx index 62b100567..7cdb927d6 100644 --- a/docs/src/content/docs/features/windows/options.mdx +++ b/docs/src/content/docs/features/windows/options.mdx @@ -828,7 +828,7 @@ Controls how the window behaves across macOS Spaces and fullscreen. These are bi - `MacWindowCollectionBehaviorFullScreenAllowsTiling` - Allows side-by-side tiling (macOS 10.11+) - `MacWindowCollectionBehaviorFullScreenDisallowsTiling` - Prevents tiling (macOS 10.11+) -**Note:** NSWindow fullscreen overlay requires application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } } +**Note:** NSWindow fullscreen overlay requires `application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } }` **Example - Spotlight-like window:** ```go @@ -861,7 +861,7 @@ Options for NSPanel windows (only applies when `WindowClass` is `NSPanel`): - `NonactivatingPanel` - Panel receives input without activating the application - `UtilityWindow` - Panel uses utility window style -**Note:** Unlike NSWindow, NSPanel fullscreen overlay does NOT require application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } } +**Note:** Unlike NSWindow, NSPanel fullscreen overlay does NOT require `application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } }` **Example - Spotlight-like panel:** ```go From 58299cc293eb5b311079dd4e62031c2d27d68b3e Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Mon, 2 Mar 2026 16:20:29 -0600 Subject: [PATCH 13/18] add helpers --- v3/pkg/application/webview_window_darwin.go | 207 ++++++---------- v3/pkg/application/webview_window_darwin.m | 262 ++++---------------- 2 files changed, 122 insertions(+), 347 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 8b243dadc..b7fe6abfc 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -23,6 +23,72 @@ struct WebviewPreferences { extern void registerListener(unsigned int event); +// Shared helper to configure webview for a window or panel +static WKWebView* configureWebviewForWindow(NSWindow* window, NSView* view, WebviewWindowDelegate* delegate, + int width, int height, bool fraudulentWebsiteWarningEnabled, + bool enableDragAndDrop, struct WebviewPreferences preferences) { + NSRect frame = NSMakeRect(0, 0, width, height); + WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init]; + [config autorelease]; + + if (preferences.TabFocusesLinks != NULL) { + config.preferences.tabFocusesLinks = *preferences.TabFocusesLinks; + } + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110300 + if (@available(macOS 11.3, *)) { + if (preferences.TextInteractionEnabled != NULL) { + config.preferences.textInteractionEnabled = *preferences.TextInteractionEnabled; + } + } +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120300 + if (@available(macOS 12.3, *)) { + if (preferences.FullscreenEnabled != NULL) { + config.preferences.elementFullscreenEnabled = *preferences.FullscreenEnabled; + } + } +#endif + + config.suppressesIncrementalRendering = true; + config.applicationNameForUserAgent = @"wails.io"; + [config setURLSchemeHandler:delegate forURLScheme:@"wails"]; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 + if (@available(macOS 10.15, *)) { + config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled; + } +#endif + + WKUserContentController* userContentController = [WKUserContentController new]; + [userContentController autorelease]; + [userContentController addScriptMessageHandler:delegate name:@"external"]; + config.userContentController = userContentController; + + WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config]; + [webView autorelease]; + + if (preferences.AllowsBackForwardNavigationGestures != NULL) { + webView.allowsBackForwardNavigationGestures = *preferences.AllowsBackForwardNavigationGestures; + } + + [view addSubview:webView]; + [webView setNavigationDelegate:delegate]; + [webView setUIDelegate:delegate]; + [webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + + if (enableDragAndDrop) { + WebviewDrag* dragView = [[WebviewDrag alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; + [dragView autorelease]; + [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [view addSubview:dragView]; + dragView.windowId = delegate.windowId; + } + + return webView; +} + // Create a new Window void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences) { NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; @@ -56,76 +122,9 @@ void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWa } [window setContentView:view]; - // Embed wkwebview in window - NSRect frame = NSMakeRect(0, 0, width, height); - WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init]; - [config autorelease]; - - // Set preferences - if (preferences.TabFocusesLinks != NULL) { - config.preferences.tabFocusesLinks = *preferences.TabFocusesLinks; - } - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110300 - if (@available(macOS 11.3, *)) { - if (preferences.TextInteractionEnabled != NULL) { - config.preferences.textInteractionEnabled = *preferences.TextInteractionEnabled; - } - } -#endif - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120300 - if (@available(macOS 12.3, *)) { - if (preferences.FullscreenEnabled != NULL) { - config.preferences.elementFullscreenEnabled = *preferences.FullscreenEnabled; - } - } -#endif - - config.suppressesIncrementalRendering = true; - config.applicationNameForUserAgent = @"wails.io"; - [config setURLSchemeHandler:delegate forURLScheme:@"wails"]; - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 - if (@available(macOS 10.15, *)) { - config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled; - } -#endif - - // Setup user content controller - WKUserContentController* userContentController = [WKUserContentController new]; - [userContentController autorelease]; - - [userContentController addScriptMessageHandler:delegate name:@"external"]; - config.userContentController = userContentController; - - WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config]; - [webView autorelease]; - - // Set allowsBackForwardNavigationGestures if specified - if (preferences.AllowsBackForwardNavigationGestures != NULL) { - webView.allowsBackForwardNavigationGestures = *preferences.AllowsBackForwardNavigationGestures; - } - - [view addSubview:webView]; - - // support webview events - [webView setNavigationDelegate:delegate]; - [webView setUIDelegate:delegate]; - - // Ensure webview resizes with the window - [webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - - if( enableDragAndDrop ) { - WebviewDrag* dragView = [[WebviewDrag alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; - [dragView autorelease]; - - [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - [view addSubview:dragView]; - dragView.windowId = id; - } - - window.webView = webView; + // Configure webview using shared helper + window.webView = configureWebviewForWindow(window, view, delegate, width, height, + fraudulentWebsiteWarningEnabled, enableDragAndDrop, preferences); return window; } @@ -220,71 +219,9 @@ void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWar } [panel setContentView:view]; - // Embed wkwebview in panel (same as window) - NSRect frame = NSMakeRect(0, 0, width, height); - WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init]; - [config autorelease]; - - if (preferences.TabFocusesLinks != NULL) { - config.preferences.tabFocusesLinks = *preferences.TabFocusesLinks; - } - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110300 - if (@available(macOS 11.3, *)) { - if (preferences.TextInteractionEnabled != NULL) { - config.preferences.textInteractionEnabled = *preferences.TextInteractionEnabled; - } - } -#endif - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120300 - if (@available(macOS 12.3, *)) { - if (preferences.FullscreenEnabled != NULL) { - config.preferences.elementFullscreenEnabled = *preferences.FullscreenEnabled; - } - } -#endif - - config.suppressesIncrementalRendering = true; - config.applicationNameForUserAgent = @"wails.io"; - [config setURLSchemeHandler:delegate forURLScheme:@"wails"]; - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 - if (@available(macOS 10.15, *)) { - config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled; - } -#endif - - WKUserContentController* userContentController = [WKUserContentController new]; - [userContentController autorelease]; - - [userContentController addScriptMessageHandler:delegate name:@"external"]; - config.userContentController = userContentController; - - WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config]; - [webView autorelease]; - - if (preferences.AllowsBackForwardNavigationGestures != NULL) { - webView.allowsBackForwardNavigationGestures = *preferences.AllowsBackForwardNavigationGestures; - } - - [view addSubview:webView]; - - [webView setNavigationDelegate:delegate]; - [webView setUIDelegate:delegate]; - - [webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - - if( enableDragAndDrop ) { - WebviewDrag* dragView = [[WebviewDrag alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; - [dragView autorelease]; - - [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - [view addSubview:dragView]; - dragView.windowId = id; - } - - panel.webView = webView; + // Configure webview using shared helper + panel.webView = configureWebviewForWindow(panel, view, delegate, width, height, + fraudulentWebsiteWarningEnabled, enableDragAndDrop, preferences); return panel; } diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index 83ed6170c..da2fd85fe 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -18,89 +18,22 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { LiquidGlassStyleDark = 2, LiquidGlassStyleVibrant = 3 }; -@implementation WebviewWindow -- (WebviewWindow*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; -{ - self = [super initWithContentRect:contentRect styleMask:windowStyle backing:bufferingType defer:deferCreation]; - [self setAlphaValue:1.0]; - [self setBackgroundColor:[NSColor clearColor]]; - [self setOpaque:NO]; - [self setMovableByWindowBackground:YES]; - return self; -} -// Override sendEvent to intercept key events BEFORE WKWebView consumes them -// This ensures KeyBindings work regardless of first responder state -- (void)sendEvent:(NSEvent *)event { - if (event.type == NSEventTypeKeyDown) { - [self keyDown:event]; - } - [super sendEvent:event]; -} -- (void)keyDown:(NSEvent *)event { - NSUInteger modifierFlags = event.modifierFlags; - // Create an array to hold the modifier strings - NSMutableArray *modifierStrings = [NSMutableArray array]; - // Check for modifier flags and add corresponding strings to the array - if (modifierFlags & NSEventModifierFlagShift) { - [modifierStrings addObject:@"shift"]; - } - if (modifierFlags & NSEventModifierFlagControl) { - [modifierStrings addObject:@"ctrl"]; - } - if (modifierFlags & NSEventModifierFlagOption) { - [modifierStrings addObject:@"option"]; - } - if (modifierFlags & NSEventModifierFlagCommand) { - [modifierStrings addObject:@"cmd"]; - } - NSString *keyString = [self keyStringFromEvent:event]; - if (keyString.length > 0) { - [modifierStrings addObject:keyString]; - } - // Combine the modifier strings with the key character - NSString *keyEventString = [modifierStrings componentsJoinedByString:@"+"]; - const char* utf8String = [keyEventString UTF8String]; - WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)self.delegate; - processWindowKeyDownEvent(delegate.windowId, utf8String); -} -- (NSString *)keyStringFromEvent:(NSEvent *)event { - // Get the pressed key - // Check for special keys like escape and tab + +// Shared key event handling functions +static NSString* keyStringFromKeyEvent(NSEvent *event) { NSString *characters = [event characters]; if (characters.length == 0) { return @""; } - if ([characters isEqualToString:@"\r"]) { - return @"enter"; - } - if ([characters isEqualToString:@"\b"]) { - return @"backspace"; - } - if ([characters isEqualToString:@"\e"]) { - return @"escape"; - } - // page down - if ([characters isEqualToString:@"\x0B"]) { - return @"page down"; - } - // page up - if ([characters isEqualToString:@"\x0E"]) { - return @"page up"; - } - // home - if ([characters isEqualToString:@"\x01"]) { - return @"home"; - } - // end - if ([characters isEqualToString:@"\x04"]) { - return @"end"; - } - // clear - if ([characters isEqualToString:@"\x0C"]) { - return @"clear"; - } + if ([characters isEqualToString:@"\r"]) return @"enter"; + if ([characters isEqualToString:@"\b"]) return @"backspace"; + if ([characters isEqualToString:@"\e"]) return @"escape"; + if ([characters isEqualToString:@"\x0B"]) return @"page down"; + if ([characters isEqualToString:@"\x0E"]) return @"page up"; + if ([characters isEqualToString:@"\x01"]) return @"home"; + if ([characters isEqualToString:@"\x04"]) return @"end"; + if ([characters isEqualToString:@"\x0C"]) return @"clear"; switch ([event keyCode]) { - // Function keys case 122: return @"f1"; case 120: return @"f2"; case 99: return @"f3"; @@ -121,7 +54,6 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { case 79: return @"f18"; case 80: return @"f19"; case 90: return @"f20"; - // Letter keys case 0: return @"a"; case 11: return @"b"; case 8: return @"c"; @@ -148,7 +80,6 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { case 7: return @"x"; case 16: return @"y"; case 6: return @"z"; - // Number keys case 29: return @"0"; case 18: return @"1"; case 19: return @"2"; @@ -159,7 +90,6 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { case 26: return @"7"; case 28: return @"8"; case 25: return @"9"; - // Other special keys case 51: return @"delete"; case 117: return @"forward delete"; case 123: return @"left"; @@ -169,7 +99,6 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { case 48: return @"tab"; case 53: return @"escape"; case 49: return @"space"; - // Punctuation and other keys (for a standard US layout) case 33: return @"["; case 30: return @"]"; case 43: return @","; @@ -184,6 +113,44 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { default: return @""; } } + +static void dispatchKeyDownEvent(NSEvent *event, unsigned int windowId) { + NSUInteger modifierFlags = event.modifierFlags; + NSMutableArray *modifierStrings = [NSMutableArray array]; + if (modifierFlags & NSEventModifierFlagShift) [modifierStrings addObject:@"shift"]; + if (modifierFlags & NSEventModifierFlagControl) [modifierStrings addObject:@"ctrl"]; + if (modifierFlags & NSEventModifierFlagOption) [modifierStrings addObject:@"option"]; + if (modifierFlags & NSEventModifierFlagCommand) [modifierStrings addObject:@"cmd"]; + NSString *keyString = keyStringFromKeyEvent(event); + if (keyString.length > 0) { + [modifierStrings addObject:keyString]; + } + NSString *keyEventString = [modifierStrings componentsJoinedByString:@"+"]; + processWindowKeyDownEvent(windowId, [keyEventString UTF8String]); +} + +@implementation WebviewWindow +- (WebviewWindow*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; +{ + self = [super initWithContentRect:contentRect styleMask:windowStyle backing:bufferingType defer:deferCreation]; + [self setAlphaValue:1.0]; + [self setBackgroundColor:[NSColor clearColor]]; + [self setOpaque:NO]; + [self setMovableByWindowBackground:YES]; + return self; +} +// Override sendEvent to intercept key events BEFORE WKWebView consumes them +// This ensures KeyBindings work regardless of first responder state +- (void)sendEvent:(NSEvent *)event { + if (event.type == NSEventTypeKeyDown) { + [self keyDown:event]; + } + [super sendEvent:event]; +} +- (void)keyDown:(NSEvent *)event { + WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)self.delegate; + dispatchKeyDownEvent(event, delegate.windowId); +} - (BOOL)canBecomeKeyWindow { return YES; } @@ -271,137 +238,8 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) { [super sendEvent:event]; } - (void)keyDown:(NSEvent *)event { - NSUInteger modifierFlags = event.modifierFlags; - NSMutableArray *modifierStrings = [NSMutableArray array]; - if (modifierFlags & NSEventModifierFlagShift) { - [modifierStrings addObject:@"shift"]; - } - if (modifierFlags & NSEventModifierFlagControl) { - [modifierStrings addObject:@"ctrl"]; - } - if (modifierFlags & NSEventModifierFlagOption) { - [modifierStrings addObject:@"option"]; - } - if (modifierFlags & NSEventModifierFlagCommand) { - [modifierStrings addObject:@"cmd"]; - } - NSString *keyString = [self keyStringFromEvent:event]; - if (keyString.length > 0) { - [modifierStrings addObject:keyString]; - } - NSString *keyEventString = [modifierStrings componentsJoinedByString:@"+"]; - const char* utf8String = [keyEventString UTF8String]; WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)self.delegate; - processWindowKeyDownEvent(delegate.windowId, utf8String); -} -- (NSString *)keyStringFromEvent:(NSEvent *)event { - NSString *characters = [event characters]; - if (characters.length == 0) { - return @""; - } - if ([characters isEqualToString:@"\r"]) { - return @"enter"; - } - if ([characters isEqualToString:@"\b"]) { - return @"backspace"; - } - if ([characters isEqualToString:@"\e"]) { - return @"escape"; - } - if ([characters isEqualToString:@"\x0B"]) { - return @"page down"; - } - if ([characters isEqualToString:@"\x0E"]) { - return @"page up"; - } - if ([characters isEqualToString:@"\x01"]) { - return @"home"; - } - if ([characters isEqualToString:@"\x04"]) { - return @"end"; - } - if ([characters isEqualToString:@"\x0C"]) { - return @"clear"; - } - switch ([event keyCode]) { - case 122: return @"f1"; - case 120: return @"f2"; - case 99: return @"f3"; - case 118: return @"f4"; - case 96: return @"f5"; - case 97: return @"f6"; - case 98: return @"f7"; - case 100: return @"f8"; - case 101: return @"f9"; - case 109: return @"f10"; - case 103: return @"f11"; - case 111: return @"f12"; - case 105: return @"f13"; - case 107: return @"f14"; - case 113: return @"f15"; - case 106: return @"f16"; - case 64: return @"f17"; - case 79: return @"f18"; - case 80: return @"f19"; - case 90: return @"f20"; - case 0: return @"a"; - case 11: return @"b"; - case 8: return @"c"; - case 2: return @"d"; - case 14: return @"e"; - case 3: return @"f"; - case 5: return @"g"; - case 4: return @"h"; - case 34: return @"i"; - case 38: return @"j"; - case 40: return @"k"; - case 37: return @"l"; - case 46: return @"m"; - case 45: return @"n"; - case 31: return @"o"; - case 35: return @"p"; - case 12: return @"q"; - case 15: return @"r"; - case 1: return @"s"; - case 17: return @"t"; - case 32: return @"u"; - case 9: return @"v"; - case 13: return @"w"; - case 7: return @"x"; - case 16: return @"y"; - case 6: return @"z"; - case 29: return @"0"; - case 18: return @"1"; - case 19: return @"2"; - case 20: return @"3"; - case 21: return @"4"; - case 23: return @"5"; - case 22: return @"6"; - case 26: return @"7"; - case 28: return @"8"; - case 25: return @"9"; - case 51: return @"delete"; - case 117: return @"forward delete"; - case 123: return @"left"; - case 124: return @"right"; - case 126: return @"up"; - case 125: return @"down"; - case 48: return @"tab"; - case 53: return @"escape"; - case 49: return @"space"; - case 33: return @"["; - case 30: return @"]"; - case 43: return @","; - case 27: return @"-"; - case 39: return @"'"; - case 44: return @"/"; - case 47: return @"."; - case 41: return @";"; - case 24: return @"="; - case 50: return @"`"; - case 42: return @"\\"; - default: return @""; - } + dispatchKeyDownEvent(event, delegate.windowId); } - (BOOL)canBecomeKeyWindow { return YES; From e82c1a76118c415b0d1af9988d1eb2c3fd6a64da Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Thu, 5 Mar 2026 12:53:07 -0600 Subject: [PATCH 14/18] panelPreferences, MacWindowClass refactor --- v3/pkg/application/webview_window_darwin.go | 82 +++++++++++++------- v3/pkg/application/webview_window_options.go | 20 ++--- 2 files changed, 65 insertions(+), 37 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index b7fe6abfc..fe1ba2e87 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -21,32 +21,39 @@ struct WebviewPreferences { bool *AllowsBackForwardNavigationGestures; }; +struct PanelPreferences { + bool *FloatingPanel; + bool *BecomesKeyOnlyIfNeeded; + bool *NonactivatingPanel; + bool *UtilityWindow; +}; + extern void registerListener(unsigned int event); // Shared helper to configure webview for a window or panel static WKWebView* configureWebviewForWindow(NSWindow* window, NSView* view, WebviewWindowDelegate* delegate, int width, int height, bool fraudulentWebsiteWarningEnabled, - bool enableDragAndDrop, struct WebviewPreferences preferences) { + bool enableDragAndDrop, struct WebviewPreferences webviewPreferences) { NSRect frame = NSMakeRect(0, 0, width, height); WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init]; [config autorelease]; - if (preferences.TabFocusesLinks != NULL) { - config.preferences.tabFocusesLinks = *preferences.TabFocusesLinks; + if (webviewPreferences.TabFocusesLinks != NULL) { + config.preferences.tabFocusesLinks = *webviewPreferences.TabFocusesLinks; } #if MAC_OS_X_VERSION_MAX_ALLOWED >= 110300 if (@available(macOS 11.3, *)) { - if (preferences.TextInteractionEnabled != NULL) { - config.preferences.textInteractionEnabled = *preferences.TextInteractionEnabled; + if (webviewPreferences.TextInteractionEnabled != NULL) { + config.preferences.textInteractionEnabled = *webviewPreferences.TextInteractionEnabled; } } #endif #if MAC_OS_X_VERSION_MAX_ALLOWED >= 120300 if (@available(macOS 12.3, *)) { - if (preferences.FullscreenEnabled != NULL) { - config.preferences.elementFullscreenEnabled = *preferences.FullscreenEnabled; + if (webviewPreferences.FullscreenEnabled != NULL) { + config.preferences.elementFullscreenEnabled = *webviewPreferences.FullscreenEnabled; } } #endif @@ -69,8 +76,8 @@ static WKWebView* configureWebviewForWindow(NSWindow* window, NSView* view, Webv WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config]; [webView autorelease]; - if (preferences.AllowsBackForwardNavigationGestures != NULL) { - webView.allowsBackForwardNavigationGestures = *preferences.AllowsBackForwardNavigationGestures; + if (webviewPreferences.AllowsBackForwardNavigationGestures != NULL) { + webView.allowsBackForwardNavigationGestures = *webviewPreferences.AllowsBackForwardNavigationGestures; } [view addSubview:webView]; @@ -90,7 +97,7 @@ static WKWebView* configureWebviewForWindow(NSWindow* window, NSView* view, Webv } // Create a new Window -void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences) { +void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences webviewPreferences) { NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; if (frameless) { styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; @@ -124,7 +131,7 @@ void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWa // Configure webview using shared helper window.webView = configureWebviewForWindow(window, view, delegate, width, height, - fraudulentWebsiteWarningEnabled, enableDragAndDrop, preferences); + fraudulentWebsiteWarningEnabled, enableDragAndDrop, webviewPreferences); return window; } @@ -180,26 +187,30 @@ void printWindowStyle(void *window) { } // Create a new Panel -void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences, - bool floatingPanel, bool becomesKeyOnlyIfNeeded, bool nonactivatingPanel, bool utilityWindow) { +void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, + struct WebviewPreferences webviewPreferences, struct PanelPreferences panelPreferences) { NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; if (frameless) { styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; } - if (nonactivatingPanel) { + if (panelPreferences.NonactivatingPanel != NULL && *panelPreferences.NonactivatingPanel) { styleMask |= NSWindowStyleMaskNonactivatingPanel; } - if (utilityWindow) { + if (panelPreferences.UtilityWindow != NULL && *panelPreferences.UtilityWindow) { styleMask |= NSWindowStyleMaskUtilityWindow; } - + // if panel, create panel add panelpreferneces, else create window WebviewPanel* panel = [[WebviewPanel alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; - [panel setFloatingPanel:floatingPanel]; - [panel setBecomesKeyOnlyIfNeeded:becomesKeyOnlyIfNeeded]; + if (panelPreferences.FloatingPanel != NULL) { + [panel setFloatingPanel:*panelPreferences.FloatingPanel]; + } + if (panelPreferences.BecomesKeyOnlyIfNeeded != NULL) { + [panel setBecomesKeyOnlyIfNeeded:*panelPreferences.BecomesKeyOnlyIfNeeded]; + } // Create delegate (same as window) WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init]; @@ -213,7 +224,7 @@ void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWar [view autorelease]; [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - if( frameless ) { + if (frameless) { [view setWantsLayer:YES]; view.layer.cornerRadius = 8.0; } @@ -221,7 +232,7 @@ void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWar // Configure webview using shared helper panel.webView = configureWebviewForWindow(panel, view, delegate, width, height, - fraudulentWebsiteWarningEnabled, enableDragAndDrop, preferences); + fraudulentWebsiteWarningEnabled, enableDragAndDrop, webviewPreferences); return panel; } @@ -1301,6 +1312,27 @@ func (w *macosWebviewWindow) getWebviewPreferences() C.struct_WebviewPreferences return result } +func (w *macosWebviewWindow) getPanelPreferences() C.struct_PanelPreferences { + panelPrefs := w.parent.options.Mac.PanelPreferences + + var result C.struct_PanelPreferences + + if panelPrefs.FloatingPanel.IsSet() { + result.FloatingPanel = bool2CboolPtr(panelPrefs.FloatingPanel.Get()) + } + if panelPrefs.BecomesKeyOnlyIfNeeded.IsSet() { + result.BecomesKeyOnlyIfNeeded = bool2CboolPtr(panelPrefs.BecomesKeyOnlyIfNeeded.Get()) + } + if panelPrefs.NonactivatingPanel.IsSet() { + result.NonactivatingPanel = bool2CboolPtr(panelPrefs.NonactivatingPanel.Get()) + } + if panelPrefs.UtilityWindow.IsSet() { + result.UtilityWindow = bool2CboolPtr(panelPrefs.UtilityWindow.Get()) + } + + return result +} + func (w *macosWebviewWindow) run() { for eventId := range w.parent.eventListeners { w.on(eventId) @@ -1310,8 +1342,7 @@ func (w *macosWebviewWindow) run() { macOptions := options.Mac // Create either NSPanel or NSWindow based on WindowClass - if macOptions.WindowClass == NSPanel { - panelOpts := macOptions.PanelOptions + if macOptions.WindowClass == MacWindowClassPanel { w.nsWindow = C.panelNew(C.uint(w.parent.id), C.int(options.Width), C.int(options.Height), @@ -1319,10 +1350,7 @@ func (w *macosWebviewWindow) run() { C.bool(options.Frameless), C.bool(options.EnableFileDrop), w.getWebviewPreferences(), - C.bool(panelOpts.FloatingPanel), - C.bool(panelOpts.BecomesKeyOnlyIfNeeded), - C.bool(panelOpts.NonactivatingPanel), - C.bool(panelOpts.UtilityWindow), + w.getPanelPreferences(), ) } else { w.nsWindow = C.windowNew(C.uint(w.parent.id), @@ -1366,7 +1394,7 @@ func (w *macosWebviewWindow) run() { // (setFloatingPanel:YES already sets NSFloatingWindowLevel, so don't override it) if macOptions.WindowLevel != "" { w.setWindowLevel(macOptions.WindowLevel) - } else if !(macOptions.WindowClass == NSPanel && macOptions.PanelOptions.FloatingPanel) { + } else if !(macOptions.WindowClass == MacWindowClassPanel && macOptions.PanelPreferences.FloatingPanel.IsSet() && macOptions.PanelPreferences.FloatingPanel.Get()) { w.setWindowLevel(MacWindowLevelNormal) } diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index 3c3fdaf11..ffbaec374 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -490,29 +490,29 @@ type MacWindow struct { WindowClass MacWindowClass // PanelOptions contains options for NSPanel windows - PanelOptions MacPanelOptions + PanelPreferences MacPanelPreferences } // MacWindowClass is the window class for macOS type MacWindowClass int const ( - // NSWindow - The default value. A window that an app displays on the screen. - NSWindow MacWindowClass = iota - // NSPanel - The window will be an NSPanel, a special kind of window that typically performs a function that is auxiliary to the main window - NSPanel + // MacWindowClassWindow - The default value. The window will be a NSWindow, a window that an app displays on the screen. + MacWindowClassWindow MacWindowClass = iota + // MacWindowClassPanel - The window will be a NSPanel, a special kind of window that typically performs a function that is auxiliary to the main window + MacWindowClassPanel ) // MacPanelOptions contains options for NSPanel windows -type MacPanelOptions struct { +type MacPanelPreferences struct { // FloatingPanel will make the panel float above other windows - FloatingPanel bool + FloatingPanel u.Bool // BecomesKeyOnlyIfNeeded will make the panel become key only when needed - BecomesKeyOnlyIfNeeded bool + BecomesKeyOnlyIfNeeded u.Bool // NonactivatingPanel will apply the NSWindowStyleMaskNonactivatingPanel style - NonactivatingPanel bool + NonactivatingPanel u.Bool // UtilityWindow will apply the NSWindowStyleMaskUtilityWindow style - UtilityWindow bool + UtilityWindow u.Bool } type MacWindowLevel string From 0ff5568cff9912b71c0ed52016e2132f45c5291d Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Thu, 5 Mar 2026 17:19:33 -0600 Subject: [PATCH 15/18] refactored comments and documentation --- .../content/docs/features/windows/options.mdx | 16 ++++++++-------- v3/pkg/application/webview_window_options.go | 8 ++++---- website/src/pages/changelog.mdx | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx index 7cdb927d6..1f22d52f2 100644 --- a/docs/src/content/docs/features/windows/options.mdx +++ b/docs/src/content/docs/features/windows/options.mdx @@ -828,7 +828,7 @@ Controls how the window behaves across macOS Spaces and fullscreen. These are bi - `MacWindowCollectionBehaviorFullScreenAllowsTiling` - Allows side-by-side tiling (macOS 10.11+) - `MacWindowCollectionBehaviorFullScreenDisallowsTiling` - Prevents tiling (macOS 10.11+) -**Note:** NSWindow fullscreen overlay requires `application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } }` +**Note:** MacWindowClassWindow fullscreen overlay requires `application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } }` **Example - Spotlight-like window:** ```go @@ -850,24 +850,24 @@ Mac: application.MacWindow{ ``` **WindowClass** (`MacWindowClass`) -- `NSWindow` - Standard window (default) -- `NSPanel` - Auxiliary window that can float above other windows and receive input without activating the application +- `MacWindowClassWindow` - Standard window (default) +- `MacWindowClassPanel` - Auxiliary window that can float above other windows and receive input without activating the application -**PanelOptions** (`MacPanelOptions`) +**PanelPreferences** (`MacPanelPreferences`) -Options for NSPanel windows (only applies when `WindowClass` is `NSPanel`): +Preferences for MacWindowClassPanel windows (only applies when `WindowClass` is `MacWindowClassPanel`): - `FloatingPanel` - Panel floats above other windows - `BecomesKeyOnlyIfNeeded` - Panel becomes key only when needed - `NonactivatingPanel` - Panel receives input without activating the application - `UtilityWindow` - Panel uses utility window style -**Note:** Unlike NSWindow, NSPanel fullscreen overlay does NOT require `application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } }` +**Note:** Unlike MacWindowClassWindow, MacWindowClassPanel fullscreen overlay does NOT require `application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } }` **Example - Spotlight-like panel:** ```go Mac: application.MacWindow{ - WindowClass: application.NSPanel, - PanelOptions: application.MacPanelOptions{ + WindowClass: application.MacWindowClassPanel, + PanelPreferences: application.MacPanelPreferences{ NonactivatingPanel: true, }, CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces | diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index ffbaec374..fdabae015 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -489,7 +489,7 @@ type MacWindow struct { // WindowClass is the window class for the window WindowClass MacWindowClass - // PanelOptions contains options for NSPanel windows + // PanelPreferences contains configuration for panel windows PanelPreferences MacPanelPreferences } @@ -497,13 +497,13 @@ type MacWindow struct { type MacWindowClass int const ( - // MacWindowClassWindow - The default value. The window will be a NSWindow, a window that an app displays on the screen. + // MacWindowClassWindow - The default value. A window that an app displays on the screen. MacWindowClassWindow MacWindowClass = iota - // MacWindowClassPanel - The window will be a NSPanel, a special kind of window that typically performs a function that is auxiliary to the main window + // MacWindowClassPanel - A special kind of window that typically performs a function that is auxiliary to the main window MacWindowClassPanel ) -// MacPanelOptions contains options for NSPanel windows +// MacPanelPreferences contains options for MacWindowClassPanel windows type MacPanelPreferences struct { // FloatingPanel will make the panel float above other windows FloatingPanel u.Bool diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index d65c85432..ac7e7804e 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Added NSPanel support for macOS. Panels serve as auxiliary windows and can appear over fullscreen apps without activating the application using `NSWindowStyleMaskNonactivatingPanel`. Configure via `Mac.WindowClass` and `Mac.PanelOptions` in window options. Added by [@Grantmartin2002](https://github.com/Grantmartin2002) in [PR](https://github.com/wailsapp/wails/pull/5024) +- Added Panel support for macOS. Panels serve as auxiliary windows and can appear over fullscreen apps without activating the application using `NSWindowStyleMaskNonactivatingPanel`. Configure via `Mac.WindowClass` and `Mac.PanelPreferences` in window options. Added by [@Grantmartin2002](https://github.com/Grantmartin2002) in [PR](https://github.com/wailsapp/wails/pull/5024) ### Fixed - Fixed locking issue on Windows when multiselect dialog returns an error. Fixed in [PR](https://github.com/wailsapp/wails/pull/4156) by @johannes-luebke From 267bed3e649c3fa11ea68633c64e1d330bd1e892 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Thu, 5 Mar 2026 17:25:13 -0600 Subject: [PATCH 16/18] revert back to basic boolean --- v3/pkg/application/webview_window_darwin.go | 42 +++++++------------- v3/pkg/application/webview_window_options.go | 8 ++-- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index fe1ba2e87..326640624 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -22,10 +22,10 @@ struct WebviewPreferences { }; struct PanelPreferences { - bool *FloatingPanel; - bool *BecomesKeyOnlyIfNeeded; - bool *NonactivatingPanel; - bool *UtilityWindow; + bool FloatingPanel; + bool BecomesKeyOnlyIfNeeded; + bool NonactivatingPanel; + bool UtilityWindow; }; extern void registerListener(unsigned int event); @@ -193,10 +193,10 @@ void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWar if (frameless) { styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; } - if (panelPreferences.NonactivatingPanel != NULL && *panelPreferences.NonactivatingPanel) { + if (panelPreferences.NonactivatingPanel) { styleMask |= NSWindowStyleMaskNonactivatingPanel; } - if (panelPreferences.UtilityWindow != NULL && *panelPreferences.UtilityWindow) { + if (panelPreferences.UtilityWindow) { styleMask |= NSWindowStyleMaskUtilityWindow; } // if panel, create panel add panelpreferneces, else create window @@ -205,12 +205,8 @@ void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWar backing:NSBackingStoreBuffered defer:NO]; - if (panelPreferences.FloatingPanel != NULL) { - [panel setFloatingPanel:*panelPreferences.FloatingPanel]; - } - if (panelPreferences.BecomesKeyOnlyIfNeeded != NULL) { - [panel setBecomesKeyOnlyIfNeeded:*panelPreferences.BecomesKeyOnlyIfNeeded]; - } + [panel setFloatingPanel:panelPreferences.FloatingPanel]; + [panel setBecomesKeyOnlyIfNeeded:panelPreferences.BecomesKeyOnlyIfNeeded]; // Create delegate (same as window) WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init]; @@ -1315,22 +1311,12 @@ func (w *macosWebviewWindow) getWebviewPreferences() C.struct_WebviewPreferences func (w *macosWebviewWindow) getPanelPreferences() C.struct_PanelPreferences { panelPrefs := w.parent.options.Mac.PanelPreferences - var result C.struct_PanelPreferences - - if panelPrefs.FloatingPanel.IsSet() { - result.FloatingPanel = bool2CboolPtr(panelPrefs.FloatingPanel.Get()) + return C.struct_PanelPreferences{ + FloatingPanel: C.bool(panelPrefs.FloatingPanel), + BecomesKeyOnlyIfNeeded: C.bool(panelPrefs.BecomesKeyOnlyIfNeeded), + NonactivatingPanel: C.bool(panelPrefs.NonactivatingPanel), + UtilityWindow: C.bool(panelPrefs.UtilityWindow), } - if panelPrefs.BecomesKeyOnlyIfNeeded.IsSet() { - result.BecomesKeyOnlyIfNeeded = bool2CboolPtr(panelPrefs.BecomesKeyOnlyIfNeeded.Get()) - } - if panelPrefs.NonactivatingPanel.IsSet() { - result.NonactivatingPanel = bool2CboolPtr(panelPrefs.NonactivatingPanel.Get()) - } - if panelPrefs.UtilityWindow.IsSet() { - result.UtilityWindow = bool2CboolPtr(panelPrefs.UtilityWindow.Get()) - } - - return result } func (w *macosWebviewWindow) run() { @@ -1394,7 +1380,7 @@ func (w *macosWebviewWindow) run() { // (setFloatingPanel:YES already sets NSFloatingWindowLevel, so don't override it) if macOptions.WindowLevel != "" { w.setWindowLevel(macOptions.WindowLevel) - } else if !(macOptions.WindowClass == MacWindowClassPanel && macOptions.PanelPreferences.FloatingPanel.IsSet() && macOptions.PanelPreferences.FloatingPanel.Get()) { + } else if !(macOptions.WindowClass == MacWindowClassPanel && macOptions.PanelPreferences.FloatingPanel) { w.setWindowLevel(MacWindowLevelNormal) } diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index fdabae015..10189c481 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -506,13 +506,13 @@ const ( // MacPanelPreferences contains options for MacWindowClassPanel windows type MacPanelPreferences struct { // FloatingPanel will make the panel float above other windows - FloatingPanel u.Bool + FloatingPanel bool // BecomesKeyOnlyIfNeeded will make the panel become key only when needed - BecomesKeyOnlyIfNeeded u.Bool + BecomesKeyOnlyIfNeeded bool // NonactivatingPanel will apply the NSWindowStyleMaskNonactivatingPanel style - NonactivatingPanel u.Bool + NonactivatingPanel bool // UtilityWindow will apply the NSWindowStyleMaskUtilityWindow style - UtilityWindow u.Bool + UtilityWindow bool } type MacWindowLevel string From 8229e080086d8caf441600574cbb314c023db161 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Fri, 6 Mar 2026 13:32:15 -0600 Subject: [PATCH 17/18] removed webviewwindow sendevent --- v3/pkg/application/webview_window_darwin.m | 9 --------- 1 file changed, 9 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index da2fd85fe..6662bdedf 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -139,14 +139,6 @@ static void dispatchKeyDownEvent(NSEvent *event, unsigned int windowId) { [self setMovableByWindowBackground:YES]; return self; } -// Override sendEvent to intercept key events BEFORE WKWebView consumes them -// This ensures KeyBindings work regardless of first responder state -- (void)sendEvent:(NSEvent *)event { - if (event.type == NSEventTypeKeyDown) { - [self keyDown:event]; - } - [super sendEvent:event]; -} - (void)keyDown:(NSEvent *)event { WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)self.delegate; dispatchKeyDownEvent(event, delegate.windowId); @@ -230,7 +222,6 @@ static void dispatchKeyDownEvent(NSEvent *event, unsigned int windowId) { return self; } // Override sendEvent to intercept key events BEFORE WKWebView consumes them -// This ensures KeyBindings work regardless of first responder state - (void)sendEvent:(NSEvent *)event { if (event.type == NSEventTypeKeyDown) { [self keyDown:event]; From 7304725e040a85e04c1c7530d929df1ce5fe394b Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Fri, 6 Mar 2026 15:53:10 -0600 Subject: [PATCH 18/18] consolidate windowNew and panelNew --- v3/pkg/application/webview_window_darwin.go | 123 +++++++------------- 1 file changed, 41 insertions(+), 82 deletions(-) diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 326640624..f141e7e60 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -97,41 +97,56 @@ static WKWebView* configureWebviewForWindow(NSWindow* window, NSView* view, Webv } // Create a new Window -void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences webviewPreferences) { +void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, + struct WebviewPreferences webviewPreferences, bool isPanel, struct PanelPreferences panelPreferences) { NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; if (frameless) { styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; } - WebviewWindow* window = [[WebviewWindow alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) - styleMask:styleMask - backing:NSBackingStoreBuffered - defer:NO]; - // Note: collectionBehavior is set later via windowSetCollectionBehavior() - // to allow user configuration of Space and fullscreen behavior + NSWindow* window; + if (isPanel) { + if (panelPreferences.NonactivatingPanel) { + styleMask |= NSWindowStyleMaskNonactivatingPanel; + } + if (panelPreferences.UtilityWindow) { + styleMask |= NSWindowStyleMaskUtilityWindow; + } + WebviewPanel* panel = [[WebviewPanel alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + [panel setFloatingPanel:panelPreferences.FloatingPanel]; + [panel setBecomesKeyOnlyIfNeeded:panelPreferences.BecomesKeyOnlyIfNeeded]; + window = panel; + } else { + window = [[WebviewWindow alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + } - // Create delegate WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init]; [delegate autorelease]; - - // Set delegate [window setDelegate:delegate]; delegate.windowId = id; - // Add NSView to window NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; [view autorelease]; - [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - if( frameless ) { + if (frameless) { [view setWantsLayer:YES]; view.layer.cornerRadius = 8.0; } [window setContentView:view]; - // Configure webview using shared helper - window.webView = configureWebviewForWindow(window, view, delegate, width, height, - fraudulentWebsiteWarningEnabled, enableDragAndDrop, webviewPreferences); + WKWebView* webView = configureWebviewForWindow(window, view, delegate, width, height, + fraudulentWebsiteWarningEnabled, enableDragAndDrop, webviewPreferences); + if (isPanel) { + ((WebviewPanel*)window).webView = webView; + } else { + ((WebviewWindow*)window).webView = webView; + } return window; } @@ -186,51 +201,6 @@ void printWindowStyle(void *window) { printf("\n"); } -// Create a new Panel -void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, - struct WebviewPreferences webviewPreferences, struct PanelPreferences panelPreferences) { - NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; - if (frameless) { - styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; - } - if (panelPreferences.NonactivatingPanel) { - styleMask |= NSWindowStyleMaskNonactivatingPanel; - } - if (panelPreferences.UtilityWindow) { - styleMask |= NSWindowStyleMaskUtilityWindow; - } - // if panel, create panel add panelpreferneces, else create window - WebviewPanel* panel = [[WebviewPanel alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1) - styleMask:styleMask - backing:NSBackingStoreBuffered - defer:NO]; - - [panel setFloatingPanel:panelPreferences.FloatingPanel]; - [panel setBecomesKeyOnlyIfNeeded:panelPreferences.BecomesKeyOnlyIfNeeded]; - - // Create delegate (same as window) - WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init]; - [delegate autorelease]; - - [panel setDelegate:delegate]; - delegate.windowId = id; - - // Add NSView to panel - NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)]; - [view autorelease]; - - [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - if (frameless) { - [view setWantsLayer:YES]; - view.layer.cornerRadius = 8.0; - } - [panel setContentView:view]; - - // Configure webview using shared helper - panel.webView = configureWebviewForWindow(panel, view, delegate, width, height, - fraudulentWebsiteWarningEnabled, enableDragAndDrop, webviewPreferences); - return panel; -} // setInvisibleTitleBarHeight sets the invisible title bar height void setInvisibleTitleBarHeight(void* window, unsigned int height) { @@ -1327,27 +1297,16 @@ func (w *macosWebviewWindow) run() { options := w.parent.options macOptions := options.Mac - // Create either NSPanel or NSWindow based on WindowClass - if macOptions.WindowClass == MacWindowClassPanel { - w.nsWindow = C.panelNew(C.uint(w.parent.id), - C.int(options.Width), - C.int(options.Height), - C.bool(macOptions.EnableFraudulentWebsiteWarnings), - C.bool(options.Frameless), - C.bool(options.EnableFileDrop), - w.getWebviewPreferences(), - w.getPanelPreferences(), - ) - } else { - w.nsWindow = C.windowNew(C.uint(w.parent.id), - C.int(options.Width), - C.int(options.Height), - C.bool(macOptions.EnableFraudulentWebsiteWarnings), - C.bool(options.Frameless), - C.bool(options.EnableFileDrop), - w.getWebviewPreferences(), - ) - } + w.nsWindow = C.windowNew(C.uint(w.parent.id), + C.int(options.Width), + C.int(options.Height), + C.bool(macOptions.EnableFraudulentWebsiteWarnings), + C.bool(options.Frameless), + C.bool(options.EnableFileDrop), + w.getWebviewPreferences(), + C.bool(macOptions.WindowClass == MacWindowClassPanel), + w.getPanelPreferences(), + ) w.setTitle(options.Title) w.setResizable(!options.DisableResize) if options.MinWidth != 0 || options.MinHeight != 0 {