fix(shortcuts): implement Ctrl+W pane closing with active WebView detection and segfault prevention
- commit
- 4f572297a546c974a5663b30443bf261c602cfba
- author
- bamen · · 2025-09-22 21:38 UTC
- committer
- bamen · · 2025-09-22 17:45 UTC
- parent
- 681ce9a
- signature
- unsigned
5 files · +92 -23
M CHANGELOG.md +2 -0
| @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file. | |||
| 8 | 8 | - **Print functionality**: Added Ctrl+Shift+P shortcut to open native print dialogs from any pane. Ctrl+P is blocked at WebKit level to prevent conflicts with pane mode shortcuts | |
| 9 | 9 | ||
| 10 | 10 | ### Fixed | |
| 11 | + | - **Ctrl+W pane closing**: Fixed Ctrl+W shortcut to properly close active panes including popups. Implemented active WebView detection and proper popup vs regular pane handling via OnWorkspaceMessage for consistent cleanup | |
| 12 | + | - **WebView script injection segfault**: Fixed segmentation violation when injecting JavaScript into destroyed WebViews during message handling. Added IsDestroyed() safety check to prevent GTK widget corruption | |
| 11 | 13 | - **Alt+Arrow workspace navigation**: Fixed Alt+Arrow keys not working for pane navigation after WebView focus changes. Implemented C-level to GTK4 shortcut bridge that ensures workspace navigation works consistently across all panes | |
| 12 | 14 | - **Global shortcuts protection**: Fixed window shortcuts (Ctrl+L, Ctrl+F, F12) not working in new panes due to overly aggressive webpage protection. Window shortcuts now properly bubble up to GTK while pane shortcuts remain blocked from webpages. Resolves omnibox and developer tools not responding to keyboard shortcuts in newly created panes | |
| 13 | 15 | - **Omnibox keyboard event isolation**: Fixed keyboard event leakage from omnibox to underlying webpages that caused unintended page actions (e.g., typing 's' in omnibox triggering GitHub search). Implemented WebKit-level main-world event blocking that activates when omnibox opens, preventing page JavaScript from receiving keyboard events while preserving native GTK shortcuts (Ctrl+L, Ctrl+F, etc.) and omnibox functionality | |
| @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file. | |||||
| 8 | - **Print functionality**: Added Ctrl+Shift+P shortcut to open native print dialogs from any pane. Ctrl+P is blocked at WebKit level to prevent conflicts with pane mode shortcuts | 8 | - **Print functionality**: Added Ctrl+Shift+P shortcut to open native print dialogs from any pane. Ctrl+P is blocked at WebKit level to prevent conflicts with pane mode shortcuts | ||
| 9 | 9 | ||||
| 10 | ### Fixed | 10 | ### Fixed | ||
| 11 | + | - **Ctrl+W pane closing**: Fixed Ctrl+W shortcut to properly close active panes including popups. Implemented active WebView detection and proper popup vs regular pane handling via OnWorkspaceMessage for consistent cleanup | |||
| 12 | + | - **WebView script injection segfault**: Fixed segmentation violation when injecting JavaScript into destroyed WebViews during message handling. Added IsDestroyed() safety check to prevent GTK widget corruption | |||
| 11 | - **Alt+Arrow workspace navigation**: Fixed Alt+Arrow keys not working for pane navigation after WebView focus changes. Implemented C-level to GTK4 shortcut bridge that ensures workspace navigation works consistently across all panes | 13 | - **Alt+Arrow workspace navigation**: Fixed Alt+Arrow keys not working for pane navigation after WebView focus changes. Implemented C-level to GTK4 shortcut bridge that ensures workspace navigation works consistently across all panes | ||
| 12 | - **Global shortcuts protection**: Fixed window shortcuts (Ctrl+L, Ctrl+F, F12) not working in new panes due to overly aggressive webpage protection. Window shortcuts now properly bubble up to GTK while pane shortcuts remain blocked from webpages. Resolves omnibox and developer tools not responding to keyboard shortcuts in newly created panes | 14 | - **Global shortcuts protection**: Fixed window shortcuts (Ctrl+L, Ctrl+F, F12) not working in new panes due to overly aggressive webpage protection. Window shortcuts now properly bubble up to GTK while pane shortcuts remain blocked from webpages. Resolves omnibox and developer tools not responding to keyboard shortcuts in newly created panes | ||
| 13 | - **Omnibox keyboard event isolation**: Fixed keyboard event leakage from omnibox to underlying webpages that caused unintended page actions (e.g., typing 's' in omnibox triggering GitHub search). Implemented WebKit-level main-world event blocking that activates when omnibox opens, preventing page JavaScript from receiving keyboard events while preserving native GTK shortcuts (Ctrl+L, Ctrl+F, etc.) and omnibox functionality | 15 | - **Omnibox keyboard event isolation**: Fixed keyboard event leakage from omnibox to underlying webpages that caused unintended page actions (e.g., typing 's' in omnibox triggering GitHub search). Implemented WebKit-level main-world event blocking that activates when omnibox opens, preventing page JavaScript from receiving keyboard events while preserving native GTK shortcuts (Ctrl+L, Ctrl+F, etc.) and omnibox functionality | ||
M internal/app/browser/window_shortcuts.go +35 -6
| @@ -6,6 +6,7 @@ import ( | |||
| 6 | 6 | "sync" | |
| 7 | 7 | "time" | |
| 8 | 8 | ||
| 9 | + | "github.com/bnema/dumber/internal/app/messaging" | |
| 9 | 10 | "github.com/bnema/dumber/pkg/webkit" | |
| 10 | 11 | ) | |
| 11 | 12 | ||
| @@ -313,15 +314,43 @@ func (h *WindowShortcutHandler) handleClosePane() { | |||
| 313 | 314 | return | |
| 314 | 315 | } | |
| 315 | 316 | ||
| 316 | - | if h.app.activePane == nil { | |
| 317 | - | log.Printf("[window-shortcuts] No active pane to close") | |
| 318 | - | return | |
| 317 | + | // Find the currently active WebView | |
| 318 | + | var activeWebView *webkit.WebView | |
| 319 | + | log.Printf("[window-shortcuts] Searching for active WebView among %d WebViews", len(h.app.workspace.viewToNode)) | |
| 320 | + | for webView := range h.app.workspace.viewToNode { | |
| 321 | + | if webView != nil { | |
| 322 | + | isActive := webView.IsActive() | |
| 323 | + | log.Printf("[window-shortcuts] WebView %s: IsActive=%t", webView.ID(), isActive) | |
| 324 | + | if isActive { | |
| 325 | + | activeWebView = webView | |
| 326 | + | log.Printf("[window-shortcuts] Found active WebView: %s", webView.ID()) | |
| 327 | + | break | |
| 328 | + | } | |
| 329 | + | } | |
| 319 | 330 | } | |
| 320 | 331 | ||
| 321 | - | log.Printf("[window-shortcuts] Closing current pane") | |
| 332 | + | if activeWebView == nil { | |
| 333 | + | log.Printf("[window-shortcuts] No active WebView found, using workspace closeCurrentPane") | |
| 334 | + | h.app.workspace.closeCurrentPane() | |
| 335 | + | return | |
| 336 | + | } | |
| 322 | 337 | ||
| 323 | - | // Use the existing closeCurrentPane method from workspace manager | |
| 324 | - | h.app.workspace.closeCurrentPane() | |
| 338 | + | // Check if the active WebView is a popup | |
| 339 | + | node := h.app.workspace.viewToNode[activeWebView] | |
| 340 | + | if node != nil && node.isPopup { | |
| 341 | + | log.Printf("[window-shortcuts] Closing popup via OnWorkspaceMessage") | |
| 342 | + | // Use the proper close-popup message for popups | |
| 343 | + | msg := messaging.Message{ | |
| 344 | + | Event: "close-popup", | |
| 345 | + | WebViewID: activeWebView.ID(), | |
| 346 | + | Reason: "user-ctrl-w", | |
| 347 | + | } | |
| 348 | + | h.app.workspace.OnWorkspaceMessage(activeWebView, msg) | |
| 349 | + | } else { | |
| 350 | + | log.Printf("[window-shortcuts] Closing regular pane via workspace closeCurrentPane") | |
| 351 | + | // Use regular close for non-popup panes | |
| 352 | + | h.app.workspace.closeCurrentPane() | |
| 353 | + | } | |
| 325 | 354 | } | |
| 326 | 355 | ||
| 327 | 356 | // Cleanup releases resources | |
| @@ -6,6 +6,7 @@ import ( | |||||
| 6 | "sync" | 6 | "sync" | ||
| 7 | "time" | 7 | "time" | ||
| 8 | 8 | ||||
| 9 | + | "github.com/bnema/dumber/internal/app/messaging" | |||
| 9 | "github.com/bnema/dumber/pkg/webkit" | 10 | "github.com/bnema/dumber/pkg/webkit" | ||
| 10 | ) | 11 | ) | ||
| 11 | 12 | ||||
| @@ -313,15 +314,43 @@ func (h *WindowShortcutHandler) handleClosePane() { | |||||
| 313 | return | 314 | return | ||
| 314 | } | 315 | } | ||
| 315 | 316 | ||||
| 316 | - | if h.app.activePane == nil { | 317 | + | // Find the currently active WebView |
| 317 | - | log.Printf("[window-shortcuts] No active pane to close") | 318 | + | var activeWebView *webkit.WebView |
| 318 | - | return | 319 | + | log.Printf("[window-shortcuts] Searching for active WebView among %d WebViews", len(h.app.workspace.viewToNode)) |
| 320 | + | for webView := range h.app.workspace.viewToNode { | |||
| 321 | + | if webView != nil { | |||
| 322 | + | isActive := webView.IsActive() | |||
| 323 | + | log.Printf("[window-shortcuts] WebView %s: IsActive=%t", webView.ID(), isActive) | |||
| 324 | + | if isActive { | |||
| 325 | + | activeWebView = webView | |||
| 326 | + | log.Printf("[window-shortcuts] Found active WebView: %s", webView.ID()) | |||
| 327 | + | break | |||
| 328 | + | } | |||
| 329 | + | } | |||
| 319 | } | 330 | } | ||
| 320 | 331 | ||||
| 321 | - | log.Printf("[window-shortcuts] Closing current pane") | 332 | + | if activeWebView == nil { |
| 333 | + | log.Printf("[window-shortcuts] No active WebView found, using workspace closeCurrentPane") | |||
| 334 | + | h.app.workspace.closeCurrentPane() | |||
| 335 | + | return | |||
| 336 | + | } | |||
| 322 | 337 | ||||
| 323 | - | // Use the existing closeCurrentPane method from workspace manager | 338 | + | // Check if the active WebView is a popup |
| 324 | - | h.app.workspace.closeCurrentPane() | 339 | + | node := h.app.workspace.viewToNode[activeWebView] |
| 340 | + | if node != nil && node.isPopup { | |||
| 341 | + | log.Printf("[window-shortcuts] Closing popup via OnWorkspaceMessage") | |||
| 342 | + | // Use the proper close-popup message for popups | |||
| 343 | + | msg := messaging.Message{ | |||
| 344 | + | Event: "close-popup", | |||
| 345 | + | WebViewID: activeWebView.ID(), | |||
| 346 | + | Reason: "user-ctrl-w", | |||
| 347 | + | } | |||
| 348 | + | h.app.workspace.OnWorkspaceMessage(activeWebView, msg) | |||
| 349 | + | } else { | |||
| 350 | + | log.Printf("[window-shortcuts] Closing regular pane via workspace closeCurrentPane") | |||
| 351 | + | // Use regular close for non-popup panes | |||
| 352 | + | h.app.workspace.closeCurrentPane() | |||
| 353 | + | } | |||
| 325 | } | 354 | } | ||
| 326 | 355 | ||||
| 327 | // Cleanup releases resources | 356 | // Cleanup releases resources | ||
M internal/app/messaging/handler.go +6 -2
| @@ -95,13 +95,11 @@ func (h *Handler) SetWorkspaceObserver(observer WorkspaceObserver) { | |||
| 95 | 95 | ||
| 96 | 96 | // Handle processes incoming script messages | |
| 97 | 97 | func (h *Handler) Handle(payload string) { | |
| 98 | - | log.Printf("[DEBUG] Received message: %s", payload) | |
| 99 | 98 | var msg Message | |
| 100 | 99 | if err := json.Unmarshal([]byte(payload), &msg); err != nil { | |
| 101 | 100 | log.Printf("[ERROR] Failed to unmarshal message: %v", err) | |
| 102 | 101 | return | |
| 103 | 102 | } | |
| 104 | - | log.Printf("[DEBUG] Parsed message type: %s", msg.Type) | |
| 105 | 103 | ||
| 106 | 104 | switch msg.Type { | |
| 107 | 105 | case "navigate": | |
| @@ -612,6 +610,12 @@ func (h *Handler) handleWebViewIDRequest(msg Message) { | |||
| 612 | 610 | return | |
| 613 | 611 | } | |
| 614 | 612 | ||
| 613 | + | // Check if WebView is destroyed before attempting any operations | |
| 614 | + | if h.webView.IsDestroyed() { | |
| 615 | + | log.Printf("[messaging] Cannot provide webview ID - webview is destroyed") | |
| 616 | + | return | |
| 617 | + | } | |
| 618 | + | ||
| 615 | 619 | webViewID := h.webView.ID() | |
| 616 | 620 | log.Printf("[messaging] Sending webview ID %s to JavaScript", webViewID) | |
| 617 | 621 | ||
| @@ -95,13 +95,11 @@ func (h *Handler) SetWorkspaceObserver(observer WorkspaceObserver) { | |||||
| 95 | 95 | ||||
| 96 | // Handle processes incoming script messages | 96 | // Handle processes incoming script messages | ||
| 97 | func (h *Handler) Handle(payload string) { | 97 | func (h *Handler) Handle(payload string) { | ||
| 98 | - | log.Printf("[DEBUG] Received message: %s", payload) | |||
| 99 | var msg Message | 98 | var msg Message | ||
| 100 | if err := json.Unmarshal([]byte(payload), &msg); err != nil { | 99 | if err := json.Unmarshal([]byte(payload), &msg); err != nil { | ||
| 101 | log.Printf("[ERROR] Failed to unmarshal message: %v", err) | 100 | log.Printf("[ERROR] Failed to unmarshal message: %v", err) | ||
| 102 | return | 101 | return | ||
| 103 | } | 102 | } | ||
| 104 | - | log.Printf("[DEBUG] Parsed message type: %s", msg.Type) | |||
| 105 | 103 | ||||
| 106 | switch msg.Type { | 104 | switch msg.Type { | ||
| 107 | case "navigate": | 105 | case "navigate": | ||
| @@ -612,6 +610,12 @@ func (h *Handler) handleWebViewIDRequest(msg Message) { | |||||
| 612 | return | 610 | return | ||
| 613 | } | 611 | } | ||
| 614 | 612 | ||||
| 613 | + | // Check if WebView is destroyed before attempting any operations | |||
| 614 | + | if h.webView.IsDestroyed() { | |||
| 615 | + | log.Printf("[messaging] Cannot provide webview ID - webview is destroyed") | |||
| 616 | + | return | |||
| 617 | + | } | |||
| 618 | + | ||||
| 615 | webViewID := h.webView.ID() | 619 | webViewID := h.webView.ID() | ||
| 616 | log.Printf("[messaging] Sending webview ID %s to JavaScript", webViewID) | 620 | log.Printf("[messaging] Sending webview ID %s to JavaScript", webViewID) | ||
| 617 | 621 | ||||
M pkg/webkit/keyboard_cgo.go +44 -15
| @@ -153,6 +153,36 @@ func goOnKeyPress(id C.ulong, keyval C.guint, state C.GdkModifierType) C.gboolea | |||
| 153 | 153 | return C.gboolean(0) | |
| 154 | 154 | } | |
| 155 | 155 | ||
| 156 | + | // getCLevelBridgeShortcut returns the shortcut string for shortcuts that need C-level bridge handling. | |
| 157 | + | // These shortcuts are blocked at the webpage level and have their callbacks triggered directly from C code. | |
| 158 | + | // Returns empty string if the shortcut doesn't need C-level handling. | |
| 159 | + | func getCLevelBridgeShortcut(ctrl, alt, shift bool, keyName string) string { | |
| 160 | + | // Alt+Arrow shortcuts - for workspace pane navigation | |
| 161 | + | if alt && !ctrl && !shift { | |
| 162 | + | switch keyName { | |
| 163 | + | case "ArrowLeft": | |
| 164 | + | return "alt+arrowleft" | |
| 165 | + | case "ArrowRight": | |
| 166 | + | return "alt+arrowright" | |
| 167 | + | case "ArrowUp": | |
| 168 | + | return "alt+arrowup" | |
| 169 | + | case "ArrowDown": | |
| 170 | + | return "alt+arrowdown" | |
| 171 | + | } | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | // Ctrl+W shortcut - for closing current pane via workspace manager | |
| 175 | + | if ctrl && !alt && !shift && keyName == "w" { | |
| 176 | + | return "ctrl+w" | |
| 177 | + | } | |
| 178 | + | ||
| 179 | + | // Add other C-level bridge shortcuts here as needed | |
| 180 | + | // Note: Most shortcuts (Ctrl+L, Ctrl+F, zoom) should NOT be here | |
| 181 | + | // They work fine through normal GTK4 handling without C-level intervention | |
| 182 | + | ||
| 183 | + | return "" | |
| 184 | + | } | |
| 185 | + | ||
| 156 | 186 | func dispatchAccelerator(uid uintptr, keyval uint, state uint) bool { | |
| 157 | 187 | ctrl := (state & uint(C.GDK_CONTROL_MASK)) != 0 | |
| 158 | 188 | alt := (state & uint(C.GDK_ALT_MASK)) != 0 | |
| @@ -199,6 +229,8 @@ func dispatchAccelerator(uid uintptr, keyval uint, state uint) bool { | |||
| 199 | 229 | keyName = "f" | |
| 200 | 230 | case uint(C.GDK_KEY_p), uint(C.GDK_KEY_P): | |
| 201 | 231 | keyName = "p" | |
| 232 | + | case uint(C.GDK_KEY_w), uint(C.GDK_KEY_W): | |
| 233 | + | keyName = "w" | |
| 202 | 234 | default: | |
| 203 | 235 | // Filter out modifier keys themselves (like Ctrl, Alt, Shift) | |
| 204 | 236 | // These keyvals shouldn't be logged as misses | |
| @@ -220,21 +252,18 @@ func dispatchAccelerator(uid uintptr, keyval uint, state uint) bool { | |||
| 220 | 252 | return false | |
| 221 | 253 | } | |
| 222 | 254 | ||
| 223 | - | // Check for Alt+Arrow shortcuts that need C-level handling | |
| 224 | - | if alt && !ctrl && !shift { | |
| 225 | - | if keyName == "ArrowLeft" || keyName == "ArrowRight" || keyName == "ArrowUp" || keyName == "ArrowDown" { | |
| 226 | - | // Build shortcut string for Alt+Arrow | |
| 227 | - | shortcutStr := "alt+arrow" + strings.ToLower(strings.TrimPrefix(keyName, "Arrow")) | |
| 228 | - | ||
| 229 | - | // Look up callback handle and trigger if found | |
| 230 | - | shortcutsRegistryMu.RLock() | |
| 231 | - | handle, exists := globalShortcutHandles[shortcutStr] | |
| 232 | - | shortcutsRegistryMu.RUnlock() | |
| 233 | - | ||
| 234 | - | if exists && handle != 0 { | |
| 235 | - | invokeWindowShortcutCallback(handle) | |
| 236 | - | return true | |
| 237 | - | } | |
| 255 | + | // Check for shortcuts that need C-level handling and trigger if found | |
| 256 | + | shortcut := getCLevelBridgeShortcut(ctrl, alt, shift, keyName) | |
| 257 | + | if shortcut != "" { | |
| 258 | + | // For all C-level bridge shortcuts (Alt+Arrow and Ctrl+W), use global window shortcut handler | |
| 259 | + | // The window shortcut handler now properly handles both popup and regular panes | |
| 260 | + | shortcutsRegistryMu.RLock() | |
| 261 | + | handle, exists := globalShortcutHandles[shortcut] | |
| 262 | + | shortcutsRegistryMu.RUnlock() | |
| 263 | + | ||
| 264 | + | if exists && handle != 0 { | |
| 265 | + | invokeWindowShortcutCallback(handle) | |
| 266 | + | return true | |
| 238 | 267 | } | |
| 239 | 268 | } | |
| 240 | 269 | ||
| @@ -153,6 +153,36 @@ func goOnKeyPress(id C.ulong, keyval C.guint, state C.GdkModifierType) C.gboolea | |||||
| 153 | return C.gboolean(0) | 153 | return C.gboolean(0) | ||
| 154 | } | 154 | } | ||
| 155 | 155 | ||||
| 156 | + | // getCLevelBridgeShortcut returns the shortcut string for shortcuts that need C-level bridge handling. | |||
| 157 | + | // These shortcuts are blocked at the webpage level and have their callbacks triggered directly from C code. | |||
| 158 | + | // Returns empty string if the shortcut doesn't need C-level handling. | |||
| 159 | + | func getCLevelBridgeShortcut(ctrl, alt, shift bool, keyName string) string { | |||
| 160 | + | // Alt+Arrow shortcuts - for workspace pane navigation | |||
| 161 | + | if alt && !ctrl && !shift { | |||
| 162 | + | switch keyName { | |||
| 163 | + | case "ArrowLeft": | |||
| 164 | + | return "alt+arrowleft" | |||
| 165 | + | case "ArrowRight": | |||
| 166 | + | return "alt+arrowright" | |||
| 167 | + | case "ArrowUp": | |||
| 168 | + | return "alt+arrowup" | |||
| 169 | + | case "ArrowDown": | |||
| 170 | + | return "alt+arrowdown" | |||
| 171 | + | } | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | // Ctrl+W shortcut - for closing current pane via workspace manager | |||
| 175 | + | if ctrl && !alt && !shift && keyName == "w" { | |||
| 176 | + | return "ctrl+w" | |||
| 177 | + | } | |||
| 178 | + | ||||
| 179 | + | // Add other C-level bridge shortcuts here as needed | |||
| 180 | + | // Note: Most shortcuts (Ctrl+L, Ctrl+F, zoom) should NOT be here | |||
| 181 | + | // They work fine through normal GTK4 handling without C-level intervention | |||
| 182 | + | ||||
| 183 | + | return "" | |||
| 184 | + | } | |||
| 185 | + | ||||
| 156 | func dispatchAccelerator(uid uintptr, keyval uint, state uint) bool { | 186 | func dispatchAccelerator(uid uintptr, keyval uint, state uint) bool { | ||
| 157 | ctrl := (state & uint(C.GDK_CONTROL_MASK)) != 0 | 187 | ctrl := (state & uint(C.GDK_CONTROL_MASK)) != 0 | ||
| 158 | alt := (state & uint(C.GDK_ALT_MASK)) != 0 | 188 | alt := (state & uint(C.GDK_ALT_MASK)) != 0 | ||
| @@ -199,6 +229,8 @@ func dispatchAccelerator(uid uintptr, keyval uint, state uint) bool { | |||||
| 199 | keyName = "f" | 229 | keyName = "f" | ||
| 200 | case uint(C.GDK_KEY_p), uint(C.GDK_KEY_P): | 230 | case uint(C.GDK_KEY_p), uint(C.GDK_KEY_P): | ||
| 201 | keyName = "p" | 231 | keyName = "p" | ||
| 232 | + | case uint(C.GDK_KEY_w), uint(C.GDK_KEY_W): | |||
| 233 | + | keyName = "w" | |||
| 202 | default: | 234 | default: | ||
| 203 | // Filter out modifier keys themselves (like Ctrl, Alt, Shift) | 235 | // Filter out modifier keys themselves (like Ctrl, Alt, Shift) | ||
| 204 | // These keyvals shouldn't be logged as misses | 236 | // These keyvals shouldn't be logged as misses | ||
| @@ -220,21 +252,18 @@ func dispatchAccelerator(uid uintptr, keyval uint, state uint) bool { | |||||
| 220 | return false | 252 | return false | ||
| 221 | } | 253 | } | ||
| 222 | 254 | ||||
| 223 | - | // Check for Alt+Arrow shortcuts that need C-level handling | 255 | + | // Check for shortcuts that need C-level handling and trigger if found |
| 224 | - | if alt && !ctrl && !shift { | 256 | + | shortcut := getCLevelBridgeShortcut(ctrl, alt, shift, keyName) |
| 225 | - | if keyName == "ArrowLeft" || keyName == "ArrowRight" || keyName == "ArrowUp" || keyName == "ArrowDown" { | 257 | + | if shortcut != "" { |
| 226 | - | // Build shortcut string for Alt+Arrow | 258 | + | // For all C-level bridge shortcuts (Alt+Arrow and Ctrl+W), use global window shortcut handler |
| 227 | - | shortcutStr := "alt+arrow" + strings.ToLower(strings.TrimPrefix(keyName, "Arrow")) | 259 | + | // The window shortcut handler now properly handles both popup and regular panes |
| 228 | - | 260 | + | shortcutsRegistryMu.RLock() | |
| 229 | - | // Look up callback handle and trigger if found | 261 | + | handle, exists := globalShortcutHandles[shortcut] |
| 230 | - | shortcutsRegistryMu.RLock() | 262 | + | shortcutsRegistryMu.RUnlock() |
| 231 | - | handle, exists := globalShortcutHandles[shortcutStr] | 263 | + | |
| 232 | - | shortcutsRegistryMu.RUnlock() | 264 | + | if exists && handle != 0 { |
| 233 | - | 265 | + | invokeWindowShortcutCallback(handle) | |
| 234 | - | if exists && handle != 0 { | 266 | + | return true |
| 235 | - | invokeWindowShortcutCallback(handle) | |||
| 236 | - | return true | |||
| 237 | - | } | |||
| 238 | } | 267 | } | ||
| 239 | } | 268 | } | ||
| 240 | 269 | ||||
M pkg/webkit/webview_cgo.go +5 -0
| @@ -2617,6 +2617,11 @@ func (w *WebView) IsActive() bool { | |||
| 2617 | 2617 | return w != nil && w.isActive | |
| 2618 | 2618 | } | |
| 2619 | 2619 | ||
| 2620 | + | // IsDestroyed returns whether this WebView has been destroyed | |
| 2621 | + | func (w *WebView) IsDestroyed() bool { | |
| 2622 | + | return w == nil || w.destroyed | |
| 2623 | + | } | |
| 2624 | + | ||
| 2620 | 2625 | // ID returns the unique identifier for this WebView as a string | |
| 2621 | 2626 | func (w *WebView) ID() string { | |
| 2622 | 2627 | if w == nil { | |
| @@ -2617,6 +2617,11 @@ func (w *WebView) IsActive() bool { | |||||
| 2617 | return w != nil && w.isActive | 2617 | return w != nil && w.isActive | ||
| 2618 | } | 2618 | } | ||
| 2619 | 2619 | ||||
| 2620 | + | // IsDestroyed returns whether this WebView has been destroyed | |||
| 2621 | + | func (w *WebView) IsDestroyed() bool { | |||
| 2622 | + | return w == nil || w.destroyed | |||
| 2623 | + | } | |||
| 2624 | + | ||||
| 2620 | // ID returns the unique identifier for this WebView as a string | 2625 | // ID returns the unique identifier for this WebView as a string | ||
| 2621 | func (w *WebView) ID() string { | 2626 | func (w *WebView) ID() string { | ||
| 2622 | if w == nil { | 2627 | if w == nil { | ||