detached fd295b9

Merge pull request #5 from bnema/dev

fix: navigation gestures, WebView cleanup, and CLI help
commit
fd295b9079b301ad84b1522b995b8401ec25444c
author
bnema · · 2025-10-18 11:40 UTC
committer
GitHub · · 2025-10-18 11:40 UTC
merge of
fcefb9a + a123e25 · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
8 files · +179 -32
M CHANGELOG.md +5 -0
@@ -16,8 +16,13 @@ All notable changes to this project will be documented in this file.
1616 - Simplified omnibox favicon handling: uses database favicon_url only
1717
1818 ### Fixed
19+- Mouse button navigation: Added support for hardware mouse buttons 8/9 (back/forward)
20+- Touchpad gestures: Two-finger horizontal swipe for back/forward navigation
21+- Web app shortcuts: Ctrl+C, Ctrl+V, Ctrl+A now work in Gmail, VSCode web, etc.
1922 - Dark mode flash on startup: Set GTK dark theme preference and WebView background color before window/content creation
2023 - Cmd+Q and window close button now trigger graceful shutdown instead of killing the browser process
24+- WebView cleanup: Properly destroy WebViews to prevent memory leaks and duplicate handler registrations when closing stacked panes
25+- CLI help: Running `dumber` without arguments now shows available commands (use `--gui` or `browse` to launch GUI)
2126
2227 ## [0.12.0] - 2025-10-16
2328
@@ -16,8 +16,13 @@ All notable changes to this project will be documented in this file.
16 - Simplified omnibox favicon handling: uses database favicon_url only16 - Simplified omnibox favicon handling: uses database favicon_url only
17 17
18 ### Fixed18 ### Fixed
19+- Mouse button navigation: Added support for hardware mouse buttons 8/9 (back/forward)
20+- Touchpad gestures: Two-finger horizontal swipe for back/forward navigation
21+- Web app shortcuts: Ctrl+C, Ctrl+V, Ctrl+A now work in Gmail, VSCode web, etc.
19 - Dark mode flash on startup: Set GTK dark theme preference and WebView background color before window/content creation22 - Dark mode flash on startup: Set GTK dark theme preference and WebView background color before window/content creation
20 - Cmd+Q and window close button now trigger graceful shutdown instead of killing the browser process23 - Cmd+Q and window close button now trigger graceful shutdown instead of killing the browser process
24+- WebView cleanup: Properly destroy WebViews to prevent memory leaks and duplicate handler registrations when closing stacked panes
25+- CLI help: Running `dumber` without arguments now shows available commands (use `--gui` or `browse` to launch GUI)
21 26
22 ## [0.12.0] - 2025-10-1627 ## [0.12.0] - 2025-10-16
23 28
M README.md +2 -26
@@ -172,16 +172,6 @@ Dumber features a Zellij-inspired pane management system that allows you to spli
172172 3. **Navigate**: Use `Alt+Arrow Keys` to move focus between split panes, or `Alt+Up/Down` to navigate within stacked panes
173173 4. **Close**: Press `X` in pane mode to close the current pane
174174
175-### Features
176-- **Binary tree layout**: Panes are organized in a binary tree structure for optimal space usage
177-- **Stacked panes**: Zellij-style stacking with collapsed title bars showing "Page Title - domain" for inactive panes
178-- **Focus management**: Visual borders, hover-to-focus, and enhanced focus throttling (100ms) for intuitive navigation
179-- **Independent sessions**: Each pane maintains its own browsing session, history, and zoom level
180-- **Advanced popup handling**: Intelligent popup vs tab detection, OAuth auto-close, and popup deduplication with SHA256 fingerprinting
181-- **Cross-WebView communication**: localStorage-based parent-popup bridge enabling proper `window.opener` functionality
182-- **WebView identity tracking**: Unique WebView ID system for precise popup targeting and enhanced debugging
183-- **Keyboard-driven**: All operations accessible via keyboard shortcuts with modal pane mode
184-
185175 ### Configuration
186176 The pane system is fully configurable via `config.json`:
187177 ```json
@@ -373,22 +363,8 @@ WebKitGTK uses GStreamer for media playback. Dumber includes automatic hardware
373363 - `sudo apt install gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-pipewire`
374364 - Hardware accel: `gstreamer1.0-vaapi va-driver-all` (covers most GPUs)
375365
376-## Theme and Runtime Updates
377-- The homepage and pages respect your system’s light/dark preference.
378-- The app updates color‑scheme live when your GTK theme changes (no relaunch needed).
379-
380-## Project Structure
381-- `main.go`: Entry point with CLI/GUI routing
382-- `internal/app/cli`: CLI application logic and Cobra commands
383-- `internal/app/browser`: Browser application logic and WebKit integration
384-- `internal/cli`: Cobra commands (`browse`, `dmenu`, `version`)
385-- `internal/config`: Viper‑backed config, XDG helpers, defaults
386-- `internal/db`: SQLite schema init and sqlc‑generated queries
387-- `internal/logging`: Structured logging system with file rotation
388-- `services`: Application services (parser/history/config/browser)
389-- `pkg/webkit`: WebKit2GTK bindings (CGO and stub implementations)
390-- `pkg/clipboard`: Cross-platform clipboard operations
391-- `frontend`: TypeScript homepage UI bundled into the binary
366+## Contribute
367+I will gladly accept contributions, especially related to UI/UX enhancements. Feel free to open an issue and let's discuss it!
392368
393369 ## Security & Privacy
394370 - History and zoom settings are stored locally in SQLite under XDG state.
@@ -172,16 +172,6 @@ Dumber features a Zellij-inspired pane management system that allows you to spli
172 3. **Navigate**: Use `Alt+Arrow Keys` to move focus between split panes, or `Alt+Up/Down` to navigate within stacked panes172 3. **Navigate**: Use `Alt+Arrow Keys` to move focus between split panes, or `Alt+Up/Down` to navigate within stacked panes
173 4. **Close**: Press `X` in pane mode to close the current pane173 4. **Close**: Press `X` in pane mode to close the current pane
174 174
175-### Features
176-- **Binary tree layout**: Panes are organized in a binary tree structure for optimal space usage
177-- **Stacked panes**: Zellij-style stacking with collapsed title bars showing "Page Title - domain" for inactive panes
178-- **Focus management**: Visual borders, hover-to-focus, and enhanced focus throttling (100ms) for intuitive navigation
179-- **Independent sessions**: Each pane maintains its own browsing session, history, and zoom level
180-- **Advanced popup handling**: Intelligent popup vs tab detection, OAuth auto-close, and popup deduplication with SHA256 fingerprinting
181-- **Cross-WebView communication**: localStorage-based parent-popup bridge enabling proper `window.opener` functionality
182-- **WebView identity tracking**: Unique WebView ID system for precise popup targeting and enhanced debugging
183-- **Keyboard-driven**: All operations accessible via keyboard shortcuts with modal pane mode
184-
185 ### Configuration175 ### Configuration
186 The pane system is fully configurable via `config.json`:176 The pane system is fully configurable via `config.json`:
187 ```json177 ```json
@@ -373,22 +363,8 @@ WebKitGTK uses GStreamer for media playback. Dumber includes automatic hardware
373 - `sudo apt install gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-pipewire`363 - `sudo apt install gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-pipewire`
374 - Hardware accel: `gstreamer1.0-vaapi va-driver-all` (covers most GPUs)364 - Hardware accel: `gstreamer1.0-vaapi va-driver-all` (covers most GPUs)
375 365
376-## Theme and Runtime Updates366+## Contribute
377-- The homepage and pages respect your system’s light/dark preference.367+I will gladly accept contributions, especially related to UI/UX enhancements. Feel free to open an issue and let's discuss it!
378-- The app updates color‑scheme live when your GTK theme changes (no relaunch needed).
379-
380-## Project Structure
381-- `main.go`: Entry point with CLI/GUI routing
382-- `internal/app/cli`: CLI application logic and Cobra commands
383-- `internal/app/browser`: Browser application logic and WebKit integration
384-- `internal/cli`: Cobra commands (`browse`, `dmenu`, `version`)
385-- `internal/config`: Viper‑backed config, XDG helpers, defaults
386-- `internal/db`: SQLite schema init and sqlc‑generated queries
387-- `internal/logging`: Structured logging system with file rotation
388-- `services`: Application services (parser/history/config/browser)
389-- `pkg/webkit`: WebKit2GTK bindings (CGO and stub implementations)
390-- `pkg/clipboard`: Cross-platform clipboard operations
391-- `frontend`: TypeScript homepage UI bundled into the binary
392 368
393 ## Security & Privacy369 ## Security & Privacy
394 - History and zoom settings are stored locally in SQLite under XDG state.370 - History and zoom settings are stored locally in SQLite under XDG state.
M internal/app/browser/pane.go +9 -0
@@ -121,6 +121,15 @@ func (p *BrowserPane) Cleanup() {
121121 }
122122 }
123123
124+ // Destroy WebView to unregister from global registry and release resources
125+ if p.webView != nil && !p.webView.IsDestroyed() {
126+ if err := p.webView.Destroy(); err != nil {
127+ log.Printf("[pane-%s] failed to destroy webview: %v", p.id, err)
128+ } else {
129+ log.Printf("[pane-%s] successfully destroyed webview", p.id)
130+ }
131+ }
132+
124133 // Clear tracking
125134 p.guiComponents = nil
126135 p.hasGUI = false
@@ -121,6 +121,15 @@ func (p *BrowserPane) Cleanup() {
121 }121 }
122 }122 }
123 123
124+ // Destroy WebView to unregister from global registry and release resources
125+ if p.webView != nil && !p.webView.IsDestroyed() {
126+ if err := p.webView.Destroy(); err != nil {
127+ log.Printf("[pane-%s] failed to destroy webview: %v", p.id, err)
128+ } else {
129+ log.Printf("[pane-%s] successfully destroyed webview", p.id)
130+ }
131+ }
132+
124 // Clear tracking133 // Clear tracking
125 p.guiComponents = nil134 p.guiComponents = nil
126 p.hasGUI = false135 p.hasGUI = false
M main.go +2 -2
@@ -62,9 +62,9 @@ func main() {
6262
6363 // shouldRunCLI determines if we should run in CLI mode based on arguments
6464 func shouldRunCLI() bool {
65- // No args = GUI landing page mode
65+ // No args = show CLI help with available commands
6666 if len(os.Args) <= 1 {
67- return false
67+ return true
6868 }
6969
7070 // Check for GUI-specific flags
@@ -62,9 +62,9 @@ func main() {
62 62
63 // shouldRunCLI determines if we should run in CLI mode based on arguments63 // shouldRunCLI determines if we should run in CLI mode based on arguments
64 func shouldRunCLI() bool {64 func shouldRunCLI() bool {
65- // No args = GUI landing page mode65+ // No args = show CLI help with available commands
66 if len(os.Args) <= 1 {66 if len(os.Args) <= 1 {
67- return false67+ return true
68 }68 }
69 69
70 // Check for GUI-specific flags70 // Check for GUI-specific flags
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