diff --git a/v3/examples/keybindings/main.go b/v3/examples/keybindings/main.go index 0e7106a5b..028a14d71 100644 --- a/v3/examples/keybindings/main.go +++ b/v3/examples/keybindings/main.go @@ -15,7 +15,7 @@ func main() { ApplicationShouldTerminateAfterLastWindowClosed: true, }, KeyBindings: map[string]func(window *application.WebviewWindow){ - "CmdOrCtrl+C": func(window *application.WebviewWindow) { + "CmdOrCtrl+Shift+C": func(window *application.WebviewWindow) { window.Center() }, }, diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index f89410ea8..b4cd7fe2c 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -187,6 +187,13 @@ type webViewAssetRequest struct { windowName string } +var windowKeyEvents = make(chan *windowKeyEvent) + +type windowKeyEvent struct { + windowId uint + acceleratorString string +} + func (r *webViewAssetRequest) Header() (http.Header, error) { h, err := r.Request.Header() if err != nil { @@ -430,6 +437,12 @@ func (a *App) Run() error { a.handleWindowMessage(event) } }() + go func() { + for { + event := <-windowKeyEvents + a.handleWindowKeyEvent(event) + } + }() go func() { for { dragAndDropMessage := <-windowDragAndDropBuffer @@ -742,6 +755,19 @@ func (a *App) processKeyBinding(acceleratorString string, window *WebviewWindow) return true } +func (a *App) handleWindowKeyEvent(event *windowKeyEvent) { + // Get window from window map + a.windowsLock.Lock() + window, ok := a.windows[event.windowId] + a.windowsLock.Unlock() + if !ok { + log.Printf("WebviewWindow #%d not found", event.windowId) + return + } + // Get callback from window + window.handleKeyEvent(event.acceleratorString) +} + func invokeSync(fn func()) { var wg sync.WaitGroup wg.Add(1) diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go index 8b42004e7..75549583f 100644 --- a/v3/pkg/application/application_darwin.go +++ b/v3/pkg/application/application_darwin.go @@ -272,6 +272,14 @@ func processURLRequest(windowID C.uint, wkUrlSchemeTask unsafe.Pointer) { } } +//export processWindowKeyDownEvent +func processWindowKeyDownEvent(windowID C.uint, acceleratorString *C.char) { + windowKeyEvents <- &windowKeyEvent{ + windowId: uint(windowID), + acceleratorString: C.GoString(acceleratorString), + } +} + //export processDragItems func processDragItems(windowID C.uint, arr **C.char, length C.int) { var filenames []string diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index cbe22b0de..0ba7e2687 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -69,6 +69,7 @@ type ( absolutePosition() (int, int) setAbsolutePosition(x int, y int) flash(enabled bool) + handleKeyEvent(acceleratorString string) } ) @@ -1065,3 +1066,12 @@ func (w *WebviewWindow) processKeyBinding(acceleratorString string) bool { return true } + +func (w *WebviewWindow) handleKeyEvent(acceleratorString string) { + if w.impl == nil { + return + } + invokeSync(func() { + w.impl.handleKeyEvent(acceleratorString) + }) +} diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 3c8560c6d..7d481281c 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -720,6 +720,16 @@ type macosWebviewWindow struct { parent *WebviewWindow } +func (w *macosWebviewWindow) handleKeyEvent(acceleratorString string) { + // Parse acceleratorString + accelerator, err := parseAccelerator(acceleratorString) + if err != nil { + globalApplication.error("unable to parse accelerator: %s", err.Error()) + return + } + w.parent.processKeyBinding(accelerator.String()) +} + func (w *macosWebviewWindow) isFocused() bool { return bool(C.windowIsFocused(w.nsWindow)) } diff --git a/v3/pkg/application/webview_window_darwin.m b/v3/pkg/application/webview_window_darwin.m index bf45ebbde..fb4523a48 100644 --- a/v3/pkg/application/webview_window_darwin.m +++ b/v3/pkg/application/webview_window_darwin.m @@ -5,6 +5,7 @@ #import "../events/events.h" extern void processMessage(unsigned int, const char*); extern void processURLRequest(unsigned int, void *); +extern void processWindowKeyDownEvent(unsigned int, const char*); extern bool hasListeners(unsigned int); @implementation WebviewWindow - (WebviewWindow*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; @@ -16,8 +17,172 @@ extern bool hasListeners(unsigned int); [self setMovableByWindowBackground:YES]; return self; } + - (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 + 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"; + } + + switch ([event keyCode]) { + // Function keys + 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"; + + // Letter keys + 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"; + + // Number keys + 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"; + + // Other special keys + 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"; + + // Punctuation and other keys (for a standard US layout) + 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; } diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index a17647062..3026045cb 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -1550,7 +1550,7 @@ func (w *windowsWebviewWindow) processKeyBinding(vkey uint) bool { if len(w.parent.keyBindings) == 0 { return false } - // Get the keyboard state and convert to an accellerator + // Get the keyboard state and convert to an accelerator var keyState [256]byte if !w32.GetKeyboardState(keyState[:]) { globalApplication.error("Error getting keyboard state")