detached 2c08618

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
2c08618669a3b4e543da48dc1b0e62ff2fcb2dcb
author
bnema · · 2025-10-18 11:16 UTC
parent
76f2e7d
signature
unsigned
4 files · +161 -4
M pkg/webkit/keyboard_bridge.go +2 -4
@@ -180,10 +180,8 @@ func (w *WebView) AttachKeyboardBridge() {
180180 case '0':
181181 keyName = keyZero
182182 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.
187185 return false
188186 }
189187
@@ -180,10 +180,8 @@ func (w *WebView) AttachKeyboardBridge() {
180 case '0':180 case '0':
181 keyName = keyZero181 keyName = keyZero
182 default:182 default:
183- // Only forward shortcuts with modifiers183+ // 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 false185 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) {
145145 // Attach keyboard bridge to forward keyboard events to JavaScript
146146 wv.AttachKeyboardBridge()
147147
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+
148154 // Only create window if requested (standalone WebViews)
149155 // Workspace panes will set CreateWindow=false and manage the container themselves
150156 if cfg.CreateWindow {
@@ -922,6 +928,12 @@ func (w *WebView) InitializeFromBare(cfg *Config) error {
922928 // Attach keyboard bridge
923929 w.AttachKeyboardBridge()
924930
931+ // Attach mouse gesture controls
932+ w.AttachMouseGestures()
933+
934+ // Attach touchpad gesture controls
935+ w.AttachTouchpadGestures()
936+
925937 log.Printf("[webkit] Completed initialization for WebView ID %d", w.id)
926938
927939 return nil
@@ -145,6 +145,12 @@ func NewWebView(cfg *Config) (*WebView, error) {
145 // Attach keyboard bridge to forward keyboard events to JavaScript145 // 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 themselves155 // 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 bridge928 // 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 nil939 return nil

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close