diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go index 466514c9ea4..542fcc0f28e 100644 --- a/v3/pkg/application/application_darwin.go +++ b/v3/pkg/application/application_darwin.go @@ -432,6 +432,36 @@ func processWindowKeyDownEvent(windowID C.uint, acceleratorString *C.char) { } } +// processWindowKeyEquivalent is the synchronous counterpart to +// processWindowKeyDownEvent. Called from -[WebviewWindow performKeyEquivalent:] +// before the responder chain runs, so the caller can decide whether to +// consume a modifier-key combo (returning true) or let the WKWebView see it +// (returning false). Required for accelerators the webview would otherwise +// swallow before NSWindow's keyDown: is ever reached — Ctrl+Tab is the +// canonical example. +// +//export processWindowKeyEquivalent +func processWindowKeyEquivalent(windowID C.uint, acceleratorString *C.char) C.bool { + if globalApplication == nil { + return C.bool(false) + } + globalApplication.windowsLock.RLock() + window, ok := globalApplication.windows[uint(windowID)] + globalApplication.windowsLock.RUnlock() + if !ok { + return C.bool(false) + } + webviewWindow, ok := window.(*WebviewWindow) + if !ok { + return C.bool(false) + } + accelerator, err := parseAccelerator(C.GoString(acceleratorString)) + if err != nil { + return C.bool(false) + } + return C.bool(webviewWindow.processKeyBinding(accelerator.String())) +} + //export processDragItems func processDragItems(windowID C.uint, arr **C.char, length C.int, x C.int, y C.int) { var filenames []string diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index cee15bf8133..c674c703807 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -8,6 +8,7 @@ extern void processURLRequest(unsigned int, void *); extern void processDragItems(unsigned int windowId, char** arr, int length, int x, int y); extern void processWindowKeyDownEvent(unsigned int, const char*); +extern bool processWindowKeyEquivalent(unsigned int, const char*); extern bool hasListeners(unsigned int); extern bool windowShouldUnconditionallyClose(unsigned int); extern bool windowIsHidden(unsigned int); @@ -46,11 +47,9 @@ - (WebviewWindow*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger [self setMovableByWindowBackground:YES]; return self; } -- (void)keyDown:(NSEvent *)event { +- (NSString *)acceleratorStringFromEvent:(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"]; } @@ -67,11 +66,29 @@ - (void)keyDown:(NSEvent *)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]; + return [modifierStrings componentsJoinedByString:@"+"]; +} +- (void)keyDown:(NSEvent *)event { + NSString *keyEventString = [self acceleratorStringFromEvent:event]; WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)self.delegate; - processWindowKeyDownEvent(delegate.windowId, utf8String); + processWindowKeyDownEvent(delegate.windowId, [keyEventString UTF8String]); +} +// performKeyEquivalent is invoked by Cocoa for every modifier-key combo +// BEFORE the responder chain runs, giving the window a chance to claim +// accelerators (e.g. Ctrl+Tab) that the WKWebView would otherwise consume +// silently. Returns YES only when there is an actual KeyBinding match; +// otherwise falls through to super so normal Cocoa handling — including +// main-menu key equivalents — continues unchanged. +- (BOOL)performKeyEquivalent:(NSEvent *)event { + WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)self.delegate; + if (delegate != nil) { + NSString *keyEventString = [self acceleratorStringFromEvent:event]; + if (keyEventString.length > 0 && + processWindowKeyEquivalent(delegate.windowId, [keyEventString UTF8String])) { + return YES; + } + } + return [super performKeyEquivalent:event]; } - (NSString *)keyStringFromEvent:(NSEvent *)event { // Get the pressed key