fix: add mouse/touchpad navigation and fix web app keyboard shortcuts
- Add mouse button 8/9 (back/forward) navigation support - Add two-finger touchpad swipe navigation (horizontal) - Fix Ctrl key propagation to allow web app shortcuts
- commit
- 7bc2e6a92d6f0aca3dcf6d9a8d71b781852592ca
- author
- bnema · · 2025-10-18 11:16 UTC
- parent
- fcefb9a
- signature
- GPG Unverified
2B82207D370127A0The key is unknown
4 files · +161 -4
M pkg/webkit/keyboard_bridge.go +2 -4
| @@ -180,10 +180,8 @@ func (w *WebView) AttachKeyboardBridge() { | |||
| 180 | 180 | case '0': | |
| 181 | 181 | keyName = keyZero | |
| 182 | 182 | default: | |
| 183 | - | // Only forward shortcuts with modifiers | |
| 184 | - | if !ctrl && !alt && !shift { | |
| 185 | - | return false | |
| 186 | - | } | |
| 183 | + | // For unknown keys, let WebView handle them | |
| 184 | + | // This allows web apps to receive Ctrl+C, Ctrl+V, Ctrl+A, etc. | |
| 187 | 185 | return false | |
| 188 | 186 | } | |
| 189 | 187 | ||
| @@ -180,10 +180,8 @@ func (w *WebView) AttachKeyboardBridge() { | |||||
| 180 | case '0': | 180 | case '0': | ||
| 181 | keyName = keyZero | 181 | keyName = keyZero | ||
| 182 | default: | 182 | default: | ||
| 183 | - | // Only forward shortcuts with modifiers | 183 | + | // For unknown keys, let WebView handle them |
| 184 | - | if !ctrl && !alt && !shift { | 184 | + | // This allows web apps to receive Ctrl+C, Ctrl+V, Ctrl+A, etc. |
| 185 | - | return false | |||
| 186 | - | } | |||
| 187 | return false | 185 | return false | ||
| 188 | } | 186 | } | ||
| 189 | 187 | ||||
A pkg/webkit/mouse_gestures.go +71 -0
| @@ -0,0 +1,71 @@ | |||
| 1 | + | package webkit | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "log" | |
| 5 | + | ||
| 6 | + | "github.com/diamondburned/gotk4/pkg/gtk/v4" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // Mouse button constants for navigation | |
| 10 | + | // Standard button numbers (consistent across Wayland and GTK) | |
| 11 | + | const ( | |
| 12 | + | mouseButtonBack = 8 // Side button (back) | |
| 13 | + | mouseButtonForward = 9 // Side button (forward) | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | // AttachMouseGestures attaches mouse button gesture handling to the WebView | |
| 17 | + | // This enables hardware mouse button navigation (back/forward buttons) | |
| 18 | + | func (w *WebView) AttachMouseGestures() { | |
| 19 | + | if w.view == nil { | |
| 20 | + | log.Printf("[mouse-gestures] Cannot attach: WebView is nil") | |
| 21 | + | return | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | // Create gesture controller for mouse clicks | |
| 25 | + | gestureClick := gtk.NewGestureClick() | |
| 26 | + | ||
| 27 | + | // Listen to all mouse buttons (0 = any button) | |
| 28 | + | gestureClick.SetButton(0) | |
| 29 | + | ||
| 30 | + | // Set to capture phase to intercept before WebView processes | |
| 31 | + | gestureClick.SetPropagationPhase(gtk.PhaseCapture) | |
| 32 | + | ||
| 33 | + | // Connect to pressed signal | |
| 34 | + | gestureClick.ConnectPressed(func(nPress int, x, y float64) { | |
| 35 | + | // Get which button was pressed | |
| 36 | + | button := gestureClick.CurrentButton() | |
| 37 | + | ||
| 38 | + | // Only handle single clicks (not double/triple clicks) | |
| 39 | + | if nPress != 1 { | |
| 40 | + | return | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | switch button { | |
| 44 | + | case mouseButtonBack: | |
| 45 | + | // Mouse back button - navigate backward | |
| 46 | + | if w.view.CanGoBack() { | |
| 47 | + | log.Printf("[mouse-gestures] Back button clicked - navigating backward") | |
| 48 | + | w.view.GoBack() | |
| 49 | + | } else { | |
| 50 | + | log.Printf("[mouse-gestures] Back button clicked but cannot go back") | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | case mouseButtonForward: | |
| 54 | + | // Mouse forward button - navigate forward | |
| 55 | + | if w.view.CanGoForward() { | |
| 56 | + | log.Printf("[mouse-gestures] Forward button clicked - navigating forward") | |
| 57 | + | w.view.GoForward() | |
| 58 | + | } else { | |
| 59 | + | log.Printf("[mouse-gestures] Forward button clicked but cannot go forward") | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | default: | |
| 63 | + | // Don't handle other buttons (let WebView handle normally) | |
| 64 | + | return | |
| 65 | + | } | |
| 66 | + | }) | |
| 67 | + | ||
| 68 | + | // Attach controller to WebView | |
| 69 | + | w.view.AddController(gestureClick) | |
| 70 | + | log.Printf("[mouse-gestures] Mouse gesture controller attached to WebView ID %d", w.id) | |
| 71 | + | } | |
| @@ -0,0 +1,71 @@ | |||||
| 1 | + | package webkit | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "log" | |||
| 5 | + | ||||
| 6 | + | "github.com/diamondburned/gotk4/pkg/gtk/v4" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | // Mouse button constants for navigation | |||
| 10 | + | // Standard button numbers (consistent across Wayland and GTK) | |||
| 11 | + | const ( | |||
| 12 | + | mouseButtonBack = 8 // Side button (back) | |||
| 13 | + | mouseButtonForward = 9 // Side button (forward) | |||
| 14 | + | ) | |||
| 15 | + | ||||
| 16 | + | // AttachMouseGestures attaches mouse button gesture handling to the WebView | |||
| 17 | + | // This enables hardware mouse button navigation (back/forward buttons) | |||
| 18 | + | func (w *WebView) AttachMouseGestures() { | |||
| 19 | + | if w.view == nil { | |||
| 20 | + | log.Printf("[mouse-gestures] Cannot attach: WebView is nil") | |||
| 21 | + | return | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | // Create gesture controller for mouse clicks | |||
| 25 | + | gestureClick := gtk.NewGestureClick() | |||
| 26 | + | ||||
| 27 | + | // Listen to all mouse buttons (0 = any button) | |||
| 28 | + | gestureClick.SetButton(0) | |||
| 29 | + | ||||
| 30 | + | // Set to capture phase to intercept before WebView processes | |||
| 31 | + | gestureClick.SetPropagationPhase(gtk.PhaseCapture) | |||
| 32 | + | ||||
| 33 | + | // Connect to pressed signal | |||
| 34 | + | gestureClick.ConnectPressed(func(nPress int, x, y float64) { | |||
| 35 | + | // Get which button was pressed | |||
| 36 | + | button := gestureClick.CurrentButton() | |||
| 37 | + | ||||
| 38 | + | // Only handle single clicks (not double/triple clicks) | |||
| 39 | + | if nPress != 1 { | |||
| 40 | + | return | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | switch button { | |||
| 44 | + | case mouseButtonBack: | |||
| 45 | + | // Mouse back button - navigate backward | |||
| 46 | + | if w.view.CanGoBack() { | |||
| 47 | + | log.Printf("[mouse-gestures] Back button clicked - navigating backward") | |||
| 48 | + | w.view.GoBack() | |||
| 49 | + | } else { | |||
| 50 | + | log.Printf("[mouse-gestures] Back button clicked but cannot go back") | |||
| 51 | + | } | |||
| 52 | + | ||||
| 53 | + | case mouseButtonForward: | |||
| 54 | + | // Mouse forward button - navigate forward | |||
| 55 | + | if w.view.CanGoForward() { | |||
| 56 | + | log.Printf("[mouse-gestures] Forward button clicked - navigating forward") | |||
| 57 | + | w.view.GoForward() | |||
| 58 | + | } else { | |||
| 59 | + | log.Printf("[mouse-gestures] Forward button clicked but cannot go forward") | |||
| 60 | + | } | |||
| 61 | + | ||||
| 62 | + | default: | |||
| 63 | + | // Don't handle other buttons (let WebView handle normally) | |||
| 64 | + | return | |||
| 65 | + | } | |||
| 66 | + | }) | |||
| 67 | + | ||||
| 68 | + | // Attach controller to WebView | |||
| 69 | + | w.view.AddController(gestureClick) | |||
| 70 | + | log.Printf("[mouse-gestures] Mouse gesture controller attached to WebView ID %d", w.id) | |||
| 71 | + | } | |||
A pkg/webkit/touchpad_gestures.go +76 -0
| @@ -0,0 +1,76 @@ | |||
| 1 | + | package webkit | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "log" | |
| 5 | + | "math" | |
| 6 | + | ||
| 7 | + | "github.com/diamondburned/gotk4/pkg/gtk/v4" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | // Touchpad swipe velocity threshold (pixels per second) | |
| 11 | + | // Lower values make gestures more sensitive, higher values require faster swipes | |
| 12 | + | const ( | |
| 13 | + | swipeVelocityThreshold = 500.0 // Reasonable threshold for intentional swipes | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | // AttachTouchpadGestures attaches touchpad swipe gesture handling to the WebView | |
| 17 | + | // This enables two-finger swipe navigation (left/right for back/forward) | |
| 18 | + | func (w *WebView) AttachTouchpadGestures() { | |
| 19 | + | if w.view == nil { | |
| 20 | + | log.Printf("[touchpad-gestures] Cannot attach: WebView is nil") | |
| 21 | + | return | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | // Create gesture controller for swipe detection | |
| 25 | + | gestureSwipe := gtk.NewGestureSwipe() | |
| 26 | + | ||
| 27 | + | // Set to capture phase to intercept before WebView processes | |
| 28 | + | gestureSwipe.SetPropagationPhase(gtk.PhaseCapture) | |
| 29 | + | ||
| 30 | + | // Set to require touchpad (2 fingers minimum for touchpad gestures) | |
| 31 | + | // This prevents single-finger drags from triggering navigation | |
| 32 | + | gestureSwipe.SetTouchOnly(false) // Allow both touch and touchpad | |
| 33 | + | ||
| 34 | + | // Connect to swipe signal | |
| 35 | + | gestureSwipe.ConnectSwipe(func(velocityX, velocityY float64) { | |
| 36 | + | // Only process horizontal swipes (ignore mostly vertical swipes) | |
| 37 | + | // Use absolute values to compare magnitudes | |
| 38 | + | absVelocityX := math.Abs(velocityX) | |
| 39 | + | absVelocityY := math.Abs(velocityY) | |
| 40 | + | ||
| 41 | + | // Require horizontal velocity to be dominant | |
| 42 | + | if absVelocityY > absVelocityX { | |
| 43 | + | log.Printf("[touchpad-gestures] Swipe is more vertical (X:%.1f Y:%.1f), ignoring", velocityX, velocityY) | |
| 44 | + | return | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | // Check if swipe velocity meets threshold | |
| 48 | + | if absVelocityX < swipeVelocityThreshold { | |
| 49 | + | log.Printf("[touchpad-gestures] Swipe velocity too low (%.1f < %.1f), ignoring", absVelocityX, swipeVelocityThreshold) | |
| 50 | + | return | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | // Determine swipe direction and navigate | |
| 54 | + | if velocityX > 0 { | |
| 55 | + | // Swipe right (positive velocity) - go back | |
| 56 | + | if w.view.CanGoBack() { | |
| 57 | + | log.Printf("[touchpad-gestures] Swipe right detected (velocity: %.1f) - navigating backward", velocityX) | |
| 58 | + | w.view.GoBack() | |
| 59 | + | } else { | |
| 60 | + | log.Printf("[touchpad-gestures] Swipe right detected but cannot go back") | |
| 61 | + | } | |
| 62 | + | } else { | |
| 63 | + | // Swipe left (negative velocity) - go forward | |
| 64 | + | if w.view.CanGoForward() { | |
| 65 | + | log.Printf("[touchpad-gestures] Swipe left detected (velocity: %.1f) - navigating forward", velocityX) | |
| 66 | + | w.view.GoForward() | |
| 67 | + | } else { | |
| 68 | + | log.Printf("[touchpad-gestures] Swipe left detected but cannot go forward") | |
| 69 | + | } | |
| 70 | + | } | |
| 71 | + | }) | |
| 72 | + | ||
| 73 | + | // Attach controller to WebView | |
| 74 | + | w.view.AddController(gestureSwipe) | |
| 75 | + | log.Printf("[touchpad-gestures] Touchpad gesture controller attached to WebView ID %d", w.id) | |
| 76 | + | } | |
| @@ -0,0 +1,76 @@ | |||||
| 1 | + | package webkit | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "log" | |||
| 5 | + | "math" | |||
| 6 | + | ||||
| 7 | + | "github.com/diamondburned/gotk4/pkg/gtk/v4" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | // Touchpad swipe velocity threshold (pixels per second) | |||
| 11 | + | // Lower values make gestures more sensitive, higher values require faster swipes | |||
| 12 | + | const ( | |||
| 13 | + | swipeVelocityThreshold = 500.0 // Reasonable threshold for intentional swipes | |||
| 14 | + | ) | |||
| 15 | + | ||||
| 16 | + | // AttachTouchpadGestures attaches touchpad swipe gesture handling to the WebView | |||
| 17 | + | // This enables two-finger swipe navigation (left/right for back/forward) | |||
| 18 | + | func (w *WebView) AttachTouchpadGestures() { | |||
| 19 | + | if w.view == nil { | |||
| 20 | + | log.Printf("[touchpad-gestures] Cannot attach: WebView is nil") | |||
| 21 | + | return | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | // Create gesture controller for swipe detection | |||
| 25 | + | gestureSwipe := gtk.NewGestureSwipe() | |||
| 26 | + | ||||
| 27 | + | // Set to capture phase to intercept before WebView processes | |||
| 28 | + | gestureSwipe.SetPropagationPhase(gtk.PhaseCapture) | |||
| 29 | + | ||||
| 30 | + | // Set to require touchpad (2 fingers minimum for touchpad gestures) | |||
| 31 | + | // This prevents single-finger drags from triggering navigation | |||
| 32 | + | gestureSwipe.SetTouchOnly(false) // Allow both touch and touchpad | |||
| 33 | + | ||||
| 34 | + | // Connect to swipe signal | |||
| 35 | + | gestureSwipe.ConnectSwipe(func(velocityX, velocityY float64) { | |||
| 36 | + | // Only process horizontal swipes (ignore mostly vertical swipes) | |||
| 37 | + | // Use absolute values to compare magnitudes | |||
| 38 | + | absVelocityX := math.Abs(velocityX) | |||
| 39 | + | absVelocityY := math.Abs(velocityY) | |||
| 40 | + | ||||
| 41 | + | // Require horizontal velocity to be dominant | |||
| 42 | + | if absVelocityY > absVelocityX { | |||
| 43 | + | log.Printf("[touchpad-gestures] Swipe is more vertical (X:%.1f Y:%.1f), ignoring", velocityX, velocityY) | |||
| 44 | + | return | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | // Check if swipe velocity meets threshold | |||
| 48 | + | if absVelocityX < swipeVelocityThreshold { | |||
| 49 | + | log.Printf("[touchpad-gestures] Swipe velocity too low (%.1f < %.1f), ignoring", absVelocityX, swipeVelocityThreshold) | |||
| 50 | + | return | |||
| 51 | + | } | |||
| 52 | + | ||||
| 53 | + | // Determine swipe direction and navigate | |||
| 54 | + | if velocityX > 0 { | |||
| 55 | + | // Swipe right (positive velocity) - go back | |||
| 56 | + | if w.view.CanGoBack() { | |||
| 57 | + | log.Printf("[touchpad-gestures] Swipe right detected (velocity: %.1f) - navigating backward", velocityX) | |||
| 58 | + | w.view.GoBack() | |||
| 59 | + | } else { | |||
| 60 | + | log.Printf("[touchpad-gestures] Swipe right detected but cannot go back") | |||
| 61 | + | } | |||
| 62 | + | } else { | |||
| 63 | + | // Swipe left (negative velocity) - go forward | |||
| 64 | + | if w.view.CanGoForward() { | |||
| 65 | + | log.Printf("[touchpad-gestures] Swipe left detected (velocity: %.1f) - navigating forward", velocityX) | |||
| 66 | + | w.view.GoForward() | |||
| 67 | + | } else { | |||
| 68 | + | log.Printf("[touchpad-gestures] Swipe left detected but cannot go forward") | |||
| 69 | + | } | |||
| 70 | + | } | |||
| 71 | + | }) | |||
| 72 | + | ||||
| 73 | + | // Attach controller to WebView | |||
| 74 | + | w.view.AddController(gestureSwipe) | |||
| 75 | + | log.Printf("[touchpad-gestures] Touchpad gesture controller attached to WebView ID %d", w.id) | |||
| 76 | + | } | |||
M pkg/webkit/webview.go +12 -0
| @@ -145,6 +145,12 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||
| 145 | 145 | // Attach keyboard bridge to forward keyboard events to JavaScript | |
| 146 | 146 | wv.AttachKeyboardBridge() | |
| 147 | 147 | ||
| 148 | + | // Attach mouse gesture controls for hardware navigation buttons | |
| 149 | + | wv.AttachMouseGestures() | |
| 150 | + | ||
| 151 | + | // Attach touchpad gesture controls for swipe navigation | |
| 152 | + | wv.AttachTouchpadGestures() | |
| 153 | + | ||
| 148 | 154 | // Only create window if requested (standalone WebViews) | |
| 149 | 155 | // Workspace panes will set CreateWindow=false and manage the container themselves | |
| 150 | 156 | if cfg.CreateWindow { | |
| @@ -922,6 +928,12 @@ func (w *WebView) InitializeFromBare(cfg *Config) error { | |||
| 922 | 928 | // Attach keyboard bridge | |
| 923 | 929 | w.AttachKeyboardBridge() | |
| 924 | 930 | ||
| 931 | + | // Attach mouse gesture controls | |
| 932 | + | w.AttachMouseGestures() | |
| 933 | + | ||
| 934 | + | // Attach touchpad gesture controls | |
| 935 | + | w.AttachTouchpadGestures() | |
| 936 | + | ||
| 925 | 937 | log.Printf("[webkit] Completed initialization for WebView ID %d", w.id) | |
| 926 | 938 | ||
| 927 | 939 | return nil | |
| @@ -145,6 +145,12 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||||
| 145 | // Attach keyboard bridge to forward keyboard events to JavaScript | 145 | // Attach keyboard bridge to forward keyboard events to JavaScript | ||
| 146 | wv.AttachKeyboardBridge() | 146 | wv.AttachKeyboardBridge() | ||
| 147 | 147 | ||||
| 148 | + | // Attach mouse gesture controls for hardware navigation buttons | |||
| 149 | + | wv.AttachMouseGestures() | |||
| 150 | + | ||||
| 151 | + | // Attach touchpad gesture controls for swipe navigation | |||
| 152 | + | wv.AttachTouchpadGestures() | |||
| 153 | + | ||||
| 148 | // Only create window if requested (standalone WebViews) | 154 | // Only create window if requested (standalone WebViews) | ||
| 149 | // Workspace panes will set CreateWindow=false and manage the container themselves | 155 | // Workspace panes will set CreateWindow=false and manage the container themselves | ||
| 150 | if cfg.CreateWindow { | 156 | if cfg.CreateWindow { | ||
| @@ -922,6 +928,12 @@ func (w *WebView) InitializeFromBare(cfg *Config) error { | |||||
| 922 | // Attach keyboard bridge | 928 | // Attach keyboard bridge | ||
| 923 | w.AttachKeyboardBridge() | 929 | w.AttachKeyboardBridge() | ||
| 924 | 930 | ||||
| 931 | + | // Attach mouse gesture controls | |||
| 932 | + | w.AttachMouseGestures() | |||
| 933 | + | ||||
| 934 | + | // Attach touchpad gesture controls | |||
| 935 | + | w.AttachTouchpadGestures() | |||
| 936 | + | ||||
| 925 | log.Printf("[webkit] Completed initialization for WebView ID %d", w.id) | 937 | log.Printf("[webkit] Completed initialization for WebView ID %d", w.id) | ||
| 926 | 938 | ||||
| 927 | return nil | 939 | return nil | ||