feat(webkit): add workspace navigation callback system
Add RegisterWorkspaceNavigationHandler to allow workspace manager to handle alt+arrow navigation. This prevents page scrolling while enabling pane navigation. Also register handler for root webview after workspace init.
- commit
- 26523d5b691f2aba648a078b3cea51d3c926af65
- author
- bamen · · 2025-10-09 18:04 UTC
- committer
- bamen · · 2025-10-09 16:57 UTC
- parent
- fdb86d5
- signature
- unsigned
3 files · +45 -5
M internal/app/browser/webview.go +3 -0
| @@ -249,6 +249,9 @@ func (app *BrowserApp) createWebView() error { | |||
| 249 | 249 | ||
| 250 | 250 | if app.workspace == nil { | |
| 251 | 251 | app.workspace = NewWorkspaceManager(app, pane) | |
| 252 | + | // CRITICAL: Register navigation handler for the root webview | |
| 253 | + | // This is needed because buildPane() couldn't register it when workspace was nil | |
| 254 | + | app.workspace.RegisterNavigationHandler(view) | |
| 252 | 255 | } | |
| 253 | 256 | ||
| 254 | 257 | // Initialize window-level global shortcuts AFTER workspace is set up | |
| @@ -249,6 +249,9 @@ func (app *BrowserApp) createWebView() error { | |||||
| 249 | 249 | ||||
| 250 | if app.workspace == nil { | 250 | if app.workspace == nil { | ||
| 251 | app.workspace = NewWorkspaceManager(app, pane) | 251 | app.workspace = NewWorkspaceManager(app, pane) | ||
| 252 | + | // CRITICAL: Register navigation handler for the root webview | |||
| 253 | + | // This is needed because buildPane() couldn't register it when workspace was nil | |||
| 254 | + | app.workspace.RegisterNavigationHandler(view) | |||
| 252 | } | 255 | } | ||
| 253 | 256 | ||||
| 254 | // Initialize window-level global shortcuts AFTER workspace is set up | 257 | // Initialize window-level global shortcuts AFTER workspace is set up | ||
M pkg/webkit/keyboard_bridge.go +33 -5
| @@ -55,11 +55,12 @@ type ShortcutRouting struct { | |||
| 55 | 55 | // shortcutRoutingTable maps normalized shortcuts to their routing behavior | |
| 56 | 56 | // This centralizes all routing decisions in one place, avoiding magic strings and scattered logic | |
| 57 | 57 | var shortcutRoutingTable = map[string]ShortcutRouting{ | |
| 58 | - | // Workspace pane navigation - handled by GTK window-level shortcuts only | |
| 59 | - | "alt+arrowleft": {forwardToJS: false, blockWebKit: true, description: "Navigate to left pane (GTK handler)"}, | |
| 60 | - | "alt+arrowright": {forwardToJS: false, blockWebKit: true, description: "Navigate to right pane (GTK handler)"}, | |
| 61 | - | "alt+arrowup": {forwardToJS: false, blockWebKit: true, description: "Navigate to upper pane (GTK handler)"}, | |
| 62 | - | "alt+arrowdown": {forwardToJS: false, blockWebKit: true, description: "Navigate to lower pane (GTK handler)"}, | |
| 58 | + | // Workspace pane navigation - block WebKit to prevent page scrolling | |
| 59 | + | // Navigation is triggered via workspace navigation callback | |
| 60 | + | "alt+arrowleft": {forwardToJS: false, blockWebKit: true, description: "Navigate to left pane (callback handler)"}, | |
| 61 | + | "alt+arrowright": {forwardToJS: false, blockWebKit: true, description: "Navigate to right pane (callback handler)"}, | |
| 62 | + | "alt+arrowup": {forwardToJS: false, blockWebKit: true, description: "Navigate to upper pane (callback handler)"}, | |
| 63 | + | "alt+arrowdown": {forwardToJS: false, blockWebKit: true, description: "Navigate to lower pane (callback handler)"}, | |
| 63 | 64 | ||
| 64 | 65 | // Pane mode - forward to JavaScript but block WebKit's print dialog | |
| 65 | 66 | "cmdorctrl+p": {forwardToJS: true, blockWebKit: true, description: "Enter pane mode (JS handler + block print dialog)"}, | |
| @@ -167,6 +168,33 @@ func (w *WebView) AttachKeyboardBridge() { | |||
| 167 | 168 | routing = defaultRouting | |
| 168 | 169 | } | |
| 169 | 170 | ||
| 171 | + | // Handle workspace navigation shortcuts via callback | |
| 172 | + | if shortcut == "alt+arrowup" || shortcut == "alt+arrowdown" || shortcut == "alt+arrowleft" || shortcut == "alt+arrowright" { | |
| 173 | + | w.mu.RLock() | |
| 174 | + | handler := w.onWorkspaceNavigation | |
| 175 | + | w.mu.RUnlock() | |
| 176 | + | ||
| 177 | + | if handler != nil { | |
| 178 | + | var direction string | |
| 179 | + | switch shortcut { | |
| 180 | + | case "alt+arrowup": | |
| 181 | + | direction = "up" | |
| 182 | + | case "alt+arrowdown": | |
| 183 | + | direction = "down" | |
| 184 | + | case "alt+arrowleft": | |
| 185 | + | direction = "left" | |
| 186 | + | case "alt+arrowright": | |
| 187 | + | direction = "right" | |
| 188 | + | } | |
| 189 | + | if handler(direction) { | |
| 190 | + | log.Printf("[keyboard-bridge] Workspace navigation: %s (handled)", direction) | |
| 191 | + | return true // Block WebKit | |
| 192 | + | } | |
| 193 | + | } | |
| 194 | + | log.Printf("[keyboard-bridge] Shortcut %s: %s (no handler or not handled)", shortcut, routing.description) | |
| 195 | + | return true // Block WebKit anyway to prevent scrolling | |
| 196 | + | } | |
| 197 | + | ||
| 170 | 198 | // Dispatch to JavaScript if routing says to | |
| 171 | 199 | if routing.forwardToJS { | |
| 172 | 200 | script := fmt.Sprintf(`document.dispatchEvent(new CustomEvent('dumber:key',{detail:{shortcut:'%s'}}));`, shortcut) | |
| @@ -55,11 +55,12 @@ type ShortcutRouting struct { | |||||
| 55 | // shortcutRoutingTable maps normalized shortcuts to their routing behavior | 55 | // shortcutRoutingTable maps normalized shortcuts to their routing behavior | ||
| 56 | // This centralizes all routing decisions in one place, avoiding magic strings and scattered logic | 56 | // This centralizes all routing decisions in one place, avoiding magic strings and scattered logic | ||
| 57 | var shortcutRoutingTable = map[string]ShortcutRouting{ | 57 | var shortcutRoutingTable = map[string]ShortcutRouting{ | ||
| 58 | - | // Workspace pane navigation - handled by GTK window-level shortcuts only | 58 | + | // Workspace pane navigation - block WebKit to prevent page scrolling |
| 59 | - | "alt+arrowleft": {forwardToJS: false, blockWebKit: true, description: "Navigate to left pane (GTK handler)"}, | 59 | + | // Navigation is triggered via workspace navigation callback |
| 60 | - | "alt+arrowright": {forwardToJS: false, blockWebKit: true, description: "Navigate to right pane (GTK handler)"}, | 60 | + | "alt+arrowleft": {forwardToJS: false, blockWebKit: true, description: "Navigate to left pane (callback handler)"}, |
| 61 | - | "alt+arrowup": {forwardToJS: false, blockWebKit: true, description: "Navigate to upper pane (GTK handler)"}, | 61 | + | "alt+arrowright": {forwardToJS: false, blockWebKit: true, description: "Navigate to right pane (callback handler)"}, |
| 62 | - | "alt+arrowdown": {forwardToJS: false, blockWebKit: true, description: "Navigate to lower pane (GTK handler)"}, | 62 | + | "alt+arrowup": {forwardToJS: false, blockWebKit: true, description: "Navigate to upper pane (callback handler)"}, |
| 63 | + | "alt+arrowdown": {forwardToJS: false, blockWebKit: true, description: "Navigate to lower pane (callback handler)"}, | |||
| 63 | 64 | ||||
| 64 | // Pane mode - forward to JavaScript but block WebKit's print dialog | 65 | // Pane mode - forward to JavaScript but block WebKit's print dialog | ||
| 65 | "cmdorctrl+p": {forwardToJS: true, blockWebKit: true, description: "Enter pane mode (JS handler + block print dialog)"}, | 66 | "cmdorctrl+p": {forwardToJS: true, blockWebKit: true, description: "Enter pane mode (JS handler + block print dialog)"}, | ||
| @@ -167,6 +168,33 @@ func (w *WebView) AttachKeyboardBridge() { | |||||
| 167 | routing = defaultRouting | 168 | routing = defaultRouting | ||
| 168 | } | 169 | } | ||
| 169 | 170 | ||||
| 171 | + | // Handle workspace navigation shortcuts via callback | |||
| 172 | + | if shortcut == "alt+arrowup" || shortcut == "alt+arrowdown" || shortcut == "alt+arrowleft" || shortcut == "alt+arrowright" { | |||
| 173 | + | w.mu.RLock() | |||
| 174 | + | handler := w.onWorkspaceNavigation | |||
| 175 | + | w.mu.RUnlock() | |||
| 176 | + | ||||
| 177 | + | if handler != nil { | |||
| 178 | + | var direction string | |||
| 179 | + | switch shortcut { | |||
| 180 | + | case "alt+arrowup": | |||
| 181 | + | direction = "up" | |||
| 182 | + | case "alt+arrowdown": | |||
| 183 | + | direction = "down" | |||
| 184 | + | case "alt+arrowleft": | |||
| 185 | + | direction = "left" | |||
| 186 | + | case "alt+arrowright": | |||
| 187 | + | direction = "right" | |||
| 188 | + | } | |||
| 189 | + | if handler(direction) { | |||
| 190 | + | log.Printf("[keyboard-bridge] Workspace navigation: %s (handled)", direction) | |||
| 191 | + | return true // Block WebKit | |||
| 192 | + | } | |||
| 193 | + | } | |||
| 194 | + | log.Printf("[keyboard-bridge] Shortcut %s: %s (no handler or not handled)", shortcut, routing.description) | |||
| 195 | + | return true // Block WebKit anyway to prevent scrolling | |||
| 196 | + | } | |||
| 197 | + | ||||
| 170 | // Dispatch to JavaScript if routing says to | 198 | // Dispatch to JavaScript if routing says to | ||
| 171 | if routing.forwardToJS { | 199 | if routing.forwardToJS { | ||
| 172 | script := fmt.Sprintf(`document.dispatchEvent(new CustomEvent('dumber:key',{detail:{shortcut:'%s'}}));`, shortcut) | 200 | script := fmt.Sprintf(`document.dispatchEvent(new CustomEvent('dumber:key',{detail:{shortcut:'%s'}}));`, shortcut) | ||
M pkg/webkit/webview.go +9 -0
| @@ -41,6 +41,7 @@ type WebView struct { | |||
| 41 | 41 | onClose func() | |
| 42 | 42 | onNavigationPolicy func(url string, isUserGesture bool) bool | |
| 43 | 43 | onWindowTypeDetected func(WindowType, *WindowFeatures) | |
| 44 | + | onWorkspaceNavigation func(direction string) bool // Workspace pane navigation | |
| 44 | 45 | } | |
| 45 | 46 | ||
| 46 | 47 | // NewWebView creates a new WebView with the given configuration | |
| @@ -703,3 +704,11 @@ func (w *WebView) OnNavigate(handler func(url string)) { | |||
| 703 | 704 | // This wraps the URI changed handler | |
| 704 | 705 | w.onURIChanged = handler | |
| 705 | 706 | } | |
| 707 | + | ||
| 708 | + | // RegisterWorkspaceNavigationHandler registers a handler for workspace pane navigation | |
| 709 | + | // The handler receives the direction ("up", "down", "left", "right") and returns true if handled | |
| 710 | + | func (w *WebView) RegisterWorkspaceNavigationHandler(handler func(direction string) bool) { | |
| 711 | + | w.mu.Lock() | |
| 712 | + | defer w.mu.Unlock() | |
| 713 | + | w.onWorkspaceNavigation = handler | |
| 714 | + | } | |
| @@ -41,6 +41,7 @@ type WebView struct { | |||||
| 41 | onClose func() | 41 | onClose func() | ||
| 42 | onNavigationPolicy func(url string, isUserGesture bool) bool | 42 | onNavigationPolicy func(url string, isUserGesture bool) bool | ||
| 43 | onWindowTypeDetected func(WindowType, *WindowFeatures) | 43 | onWindowTypeDetected func(WindowType, *WindowFeatures) | ||
| 44 | + | onWorkspaceNavigation func(direction string) bool // Workspace pane navigation | |||
| 44 | } | 45 | } | ||
| 45 | 46 | ||||
| 46 | // NewWebView creates a new WebView with the given configuration | 47 | // NewWebView creates a new WebView with the given configuration | ||
| @@ -703,3 +704,11 @@ func (w *WebView) OnNavigate(handler func(url string)) { | |||||
| 703 | // This wraps the URI changed handler | 704 | // This wraps the URI changed handler | ||
| 704 | w.onURIChanged = handler | 705 | w.onURIChanged = handler | ||
| 705 | } | 706 | } | ||
| 707 | + | ||||
| 708 | + | // RegisterWorkspaceNavigationHandler registers a handler for workspace pane navigation | |||
| 709 | + | // The handler receives the direction ("up", "down", "left", "right") and returns true if handled | |||
| 710 | + | func (w *WebView) RegisterWorkspaceNavigationHandler(handler func(direction string) bool) { | |||
| 711 | + | w.mu.Lock() | |||
| 712 | + | defer w.mu.Unlock() | |||
| 713 | + | w.onWorkspaceNavigation = handler | |||
| 714 | + | } | |||