feat(print): add Ctrl+Shift+P native print dialog with WebKit Ctrl+P blocking
- commit
- 549d2cefcc3f20cc991c1cbd63e01a85f3daef3e
- author
- bamen · · 2025-09-22 21:25 UTC
- committer
- bamen · · 2025-09-22 19:53 UTC
- parent
- 7ebcaf5
- signature
- unsigned
11 files · +142 -20
M CHANGELOG.md +5 -0
| @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. | |||
| 4 | 4 | ||
| 5 | 5 | ## [Unreleased] | |
| 6 | 6 | ||
| 7 | + | ### Added | |
| 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 | + | ||
| 7 | 10 | ### Fixed | |
| 8 | 11 | - **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 | |
| 9 | 12 | - **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 | |
| @@ -12,6 +15,8 @@ All notable changes to this project will be documented in this file. | |||
| 12 | 15 | - **WebView ID propagation**: Added webview ID request/response mechanism for stub builds to ensure JavaScript side knows its webview ID for proper focus validation | |
| 13 | 16 | - **Workspace theme integration**: Fixed white borders on inactive panes by implementing dynamic border colors based on GTK theme preference (dark mode: #333333 borders, light mode: #dddddd borders) | |
| 14 | 17 | - **GTK window background**: Added theme-aware window and pane background colors to prevent white bleeding through transparent elements | |
| 18 | + | - **Print dialog window handling**: Fixed GTK window destroy errors during WebView cleanup by only destroying windows when CreateWindow was actually enabled | |
| 19 | + | - **Modifier key logging noise**: Filtered out Ctrl, Shift, Alt, and Super key presses from accelerator miss logging to reduce console spam | |
| 15 | 20 | ||
| 16 | 21 | ## [0.9.0] - 2025-09-22 | |
| 17 | 22 | ||
| @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. | |||||
| 4 | 4 | ||||
| 5 | ## [Unreleased] | 5 | ## [Unreleased] | ||
| 6 | 6 | ||||
| 7 | + | ### Added | |||
| 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 | + | ||||
| 7 | ### Fixed | 10 | ### Fixed | ||
| 8 | - **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 | 11 | - **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 | ||
| 9 | - **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 | 12 | - **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 | ||
| @@ -12,6 +15,8 @@ All notable changes to this project will be documented in this file. | |||||
| 12 | - **WebView ID propagation**: Added webview ID request/response mechanism for stub builds to ensure JavaScript side knows its webview ID for proper focus validation | 15 | - **WebView ID propagation**: Added webview ID request/response mechanism for stub builds to ensure JavaScript side knows its webview ID for proper focus validation | ||
| 13 | - **Workspace theme integration**: Fixed white borders on inactive panes by implementing dynamic border colors based on GTK theme preference (dark mode: #333333 borders, light mode: #dddddd borders) | 16 | - **Workspace theme integration**: Fixed white borders on inactive panes by implementing dynamic border colors based on GTK theme preference (dark mode: #333333 borders, light mode: #dddddd borders) | ||
| 14 | - **GTK window background**: Added theme-aware window and pane background colors to prevent white bleeding through transparent elements | 17 | - **GTK window background**: Added theme-aware window and pane background colors to prevent white bleeding through transparent elements | ||
| 18 | + | - **Print dialog window handling**: Fixed GTK window destroy errors during WebView cleanup by only destroying windows when CreateWindow was actually enabled | |||
| 19 | + | - **Modifier key logging noise**: Filtered out Ctrl, Shift, Alt, and Super key presses from accelerator miss logging to reduce console spam | |||
| 15 | 20 | ||||
| 16 | ## [0.9.0] - 2025-09-22 | 21 | ## [0.9.0] - 2025-09-22 | ||
| 17 | 22 | ||||
M README.md +1 -0
| @@ -46,6 +46,7 @@ Dumber is a minimalist browser and launcher companion focused on speed and simpl | |||
| 46 | 46 | | **Ctrl/Cmd+-** | Zoom Out | Works across keyboard layouts | | |
| 47 | 47 | | **Ctrl/Cmd+0** | Reset Zoom | Return to 100% zoom | | |
| 48 | 48 | | **Ctrl/Cmd+Shift+C** | Copy URL | Copy current URL to clipboard with toast | | |
| 49 | + | | **Ctrl/Cmd+Shift+P** | Print Page | Open native print dialog | | |
| 49 | 50 | | **Ctrl/Cmd+R** / **F5** | Reload Page | Refresh current page | | |
| 50 | 51 | | **Ctrl/Cmd+Shift+R** | Hard Reload | Refresh ignoring cache | | |
| 51 | 52 | | **Ctrl+←** / **Ctrl+→** | Navigate Back/Forward | Browser history navigation | | |
| @@ -46,6 +46,7 @@ Dumber is a minimalist browser and launcher companion focused on speed and simpl | |||||
| 46 | | **Ctrl/Cmd+-** | Zoom Out | Works across keyboard layouts | | 46 | | **Ctrl/Cmd+-** | Zoom Out | Works across keyboard layouts | | ||
| 47 | | **Ctrl/Cmd+0** | Reset Zoom | Return to 100% zoom | | 47 | | **Ctrl/Cmd+0** | Reset Zoom | Return to 100% zoom | | ||
| 48 | | **Ctrl/Cmd+Shift+C** | Copy URL | Copy current URL to clipboard with toast | | 48 | | **Ctrl/Cmd+Shift+C** | Copy URL | Copy current URL to clipboard with toast | | ||
| 49 | + | | **Ctrl/Cmd+Shift+P** | Print Page | Open native print dialog | | |||
| 49 | | **Ctrl/Cmd+R** / **F5** | Reload Page | Refresh current page | | 50 | | **Ctrl/Cmd+R** / **F5** | Reload Page | Refresh current page | | ||
| 50 | | **Ctrl/Cmd+Shift+R** | Hard Reload | Refresh ignoring cache | | 51 | | **Ctrl/Cmd+Shift+R** | Hard Reload | Refresh ignoring cache | | ||
| 51 | | **Ctrl+←** / **Ctrl+→** | Navigate Back/Forward | Browser history navigation | | 52 | | **Ctrl+←** / **Ctrl+→** | Navigate Back/Forward | Browser history navigation | | ||
M internal/app/browser/window_shortcuts.go +23 -0
| @@ -21,6 +21,7 @@ type WindowShortcutHandler struct { | |||
| 21 | 21 | lastFindToggle time.Time | |
| 22 | 22 | lastCopyURL time.Time | |
| 23 | 23 | lastDevTools time.Time | |
| 24 | + | lastPrint time.Time | |
| 24 | 25 | } | |
| 25 | 26 | ||
| 26 | 27 | // NewWindowShortcutHandler creates a new window-level shortcut handler | |
| @@ -57,6 +58,7 @@ func (h *WindowShortcutHandler) registerGlobalShortcuts() error { | |||
| 57 | 58 | {"ctrl+l", h.handleOmniboxToggle, "Omnibox toggle"}, | |
| 58 | 59 | {"ctrl+f", h.handleFindToggle, "Find in page"}, | |
| 59 | 60 | {"ctrl+shift+c", h.handleCopyURL, "Copy URL"}, | |
| 61 | + | {"ctrl+shift+p", h.handlePrint, "Print page"}, | |
| 60 | 62 | {"F12", h.handleDevTools, "Developer tools"}, | |
| 61 | 63 | // Zoom shortcuts - global level for proper active pane targeting | |
| 62 | 64 | {"ctrl+plus", h.handleZoomIn, "Zoom in"}, | |
| @@ -149,6 +151,27 @@ func (h *WindowShortcutHandler) handleDevTools() { | |||
| 149 | 151 | } | |
| 150 | 152 | } | |
| 151 | 153 | ||
| 154 | + | func (h *WindowShortcutHandler) handlePrint() { | |
| 155 | + | h.mu.Lock() | |
| 156 | + | defer h.mu.Unlock() | |
| 157 | + | ||
| 158 | + | if time.Since(h.lastPrint) < 200*time.Millisecond { | |
| 159 | + | return | |
| 160 | + | } | |
| 161 | + | h.lastPrint = time.Now() | |
| 162 | + | ||
| 163 | + | if h.app.activePane == nil || h.app.activePane.webView == nil { | |
| 164 | + | log.Printf("[window-shortcuts] No active pane for print") | |
| 165 | + | return | |
| 166 | + | } | |
| 167 | + | ||
| 168 | + | log.Printf("[window-shortcuts] Print -> pane %p", h.app.activePane.webView) | |
| 169 | + | ||
| 170 | + | if err := h.app.activePane.webView.ShowPrintDialog(); err != nil { | |
| 171 | + | log.Printf("[window-shortcuts] Failed to show print dialog: %v", err) | |
| 172 | + | } | |
| 173 | + | } | |
| 174 | + | ||
| 152 | 175 | // handleZoomIn increases zoom level by 10% | |
| 153 | 176 | func (h *WindowShortcutHandler) handleZoomIn() { | |
| 154 | 177 | h.handleZoom("in", 1.1) | |
| @@ -21,6 +21,7 @@ type WindowShortcutHandler struct { | |||||
| 21 | lastFindToggle time.Time | 21 | lastFindToggle time.Time | ||
| 22 | lastCopyURL time.Time | 22 | lastCopyURL time.Time | ||
| 23 | lastDevTools time.Time | 23 | lastDevTools time.Time | ||
| 24 | + | lastPrint time.Time | |||
| 24 | } | 25 | } | ||
| 25 | 26 | ||||
| 26 | // NewWindowShortcutHandler creates a new window-level shortcut handler | 27 | // NewWindowShortcutHandler creates a new window-level shortcut handler | ||
| @@ -57,6 +58,7 @@ func (h *WindowShortcutHandler) registerGlobalShortcuts() error { | |||||
| 57 | {"ctrl+l", h.handleOmniboxToggle, "Omnibox toggle"}, | 58 | {"ctrl+l", h.handleOmniboxToggle, "Omnibox toggle"}, | ||
| 58 | {"ctrl+f", h.handleFindToggle, "Find in page"}, | 59 | {"ctrl+f", h.handleFindToggle, "Find in page"}, | ||
| 59 | {"ctrl+shift+c", h.handleCopyURL, "Copy URL"}, | 60 | {"ctrl+shift+c", h.handleCopyURL, "Copy URL"}, | ||
| 61 | + | {"ctrl+shift+p", h.handlePrint, "Print page"}, | |||
| 60 | {"F12", h.handleDevTools, "Developer tools"}, | 62 | {"F12", h.handleDevTools, "Developer tools"}, | ||
| 61 | // Zoom shortcuts - global level for proper active pane targeting | 63 | // Zoom shortcuts - global level for proper active pane targeting | ||
| 62 | {"ctrl+plus", h.handleZoomIn, "Zoom in"}, | 64 | {"ctrl+plus", h.handleZoomIn, "Zoom in"}, | ||
| @@ -149,6 +151,27 @@ func (h *WindowShortcutHandler) handleDevTools() { | |||||
| 149 | } | 151 | } | ||
| 150 | } | 152 | } | ||
| 151 | 153 | ||||
| 154 | + | func (h *WindowShortcutHandler) handlePrint() { | |||
| 155 | + | h.mu.Lock() | |||
| 156 | + | defer h.mu.Unlock() | |||
| 157 | + | ||||
| 158 | + | if time.Since(h.lastPrint) < 200*time.Millisecond { | |||
| 159 | + | return | |||
| 160 | + | } | |||
| 161 | + | h.lastPrint = time.Now() | |||
| 162 | + | ||||
| 163 | + | if h.app.activePane == nil || h.app.activePane.webView == nil { | |||
| 164 | + | log.Printf("[window-shortcuts] No active pane for print") | |||
| 165 | + | return | |||
| 166 | + | } | |||
| 167 | + | ||||
| 168 | + | log.Printf("[window-shortcuts] Print -> pane %p", h.app.activePane.webView) | |||
| 169 | + | ||||
| 170 | + | if err := h.app.activePane.webView.ShowPrintDialog(); err != nil { | |||
| 171 | + | log.Printf("[window-shortcuts] Failed to show print dialog: %v", err) | |||
| 172 | + | } | |||
| 173 | + | } | |||
| 174 | + | ||||
| 152 | // handleZoomIn increases zoom level by 10% | 175 | // handleZoomIn increases zoom level by 10% | ||
| 153 | func (h *WindowShortcutHandler) handleZoomIn() { | 176 | func (h *WindowShortcutHandler) handleZoomIn() { | ||
| 154 | h.handleZoom("in", 1.1) | 177 | h.handleZoom("in", 1.1) | ||
M internal/app/browser/workspace_manager.go +2 -3
| @@ -394,7 +394,6 @@ func (wm *WorkspaceManager) DispatchPaneFocusEvent(node *paneNode, active bool) | |||
| 394 | 394 | } | |
| 395 | 395 | } | |
| 396 | 396 | ||
| 397 | - | ||
| 398 | 397 | func (wm *WorkspaceManager) focusNode(node *paneNode) { | |
| 399 | 398 | if node == nil || !node.isLeaf || node.pane == nil || node.pane.webView == nil { | |
| 400 | 399 | return | |
| @@ -508,10 +507,10 @@ func (wm *WorkspaceManager) generateActivePaneCSS() string { | |||
| 508 | 507 | var inactiveBorderColor, windowBackgroundColor string | |
| 509 | 508 | isDark := webkit.PrefersDarkTheme() | |
| 510 | 509 | if isDark { | |
| 511 | - | inactiveBorderColor = "#333333" // Dark border for dark theme | |
| 510 | + | inactiveBorderColor = "#333333" // Dark border for dark theme | |
| 512 | 511 | windowBackgroundColor = "#2b2b2b" // Dark window background | |
| 513 | 512 | } else { | |
| 514 | - | inactiveBorderColor = "#dddddd" // Light border for light theme | |
| 513 | + | inactiveBorderColor = "#dddddd" // Light border for light theme | |
| 515 | 514 | windowBackgroundColor = "#ffffff" // Light window background | |
| 516 | 515 | } | |
| 517 | 516 | ||
| @@ -394,7 +394,6 @@ func (wm *WorkspaceManager) DispatchPaneFocusEvent(node *paneNode, active bool) | |||||
| 394 | } | 394 | } | ||
| 395 | } | 395 | } | ||
| 396 | 396 | ||||
| 397 | - | ||||
| 398 | func (wm *WorkspaceManager) focusNode(node *paneNode) { | 397 | func (wm *WorkspaceManager) focusNode(node *paneNode) { | ||
| 399 | if node == nil || !node.isLeaf || node.pane == nil || node.pane.webView == nil { | 398 | if node == nil || !node.isLeaf || node.pane == nil || node.pane.webView == nil { | ||
| 400 | return | 399 | return | ||
| @@ -508,10 +507,10 @@ func (wm *WorkspaceManager) generateActivePaneCSS() string { | |||||
| 508 | var inactiveBorderColor, windowBackgroundColor string | 507 | var inactiveBorderColor, windowBackgroundColor string | ||
| 509 | isDark := webkit.PrefersDarkTheme() | 508 | isDark := webkit.PrefersDarkTheme() | ||
| 510 | if isDark { | 509 | if isDark { | ||
| 511 | - | inactiveBorderColor = "#333333" // Dark border for dark theme | 510 | + | inactiveBorderColor = "#333333" // Dark border for dark theme |
| 512 | windowBackgroundColor = "#2b2b2b" // Dark window background | 511 | windowBackgroundColor = "#2b2b2b" // Dark window background | ||
| 513 | } else { | 512 | } else { | ||
| 514 | - | inactiveBorderColor = "#dddddd" // Light border for light theme | 513 | + | inactiveBorderColor = "#dddddd" // Light border for light theme |
| 515 | windowBackgroundColor = "#ffffff" // Light window background | 514 | windowBackgroundColor = "#ffffff" // Light window background | ||
| 516 | } | 515 | } | ||
| 517 | 516 | ||||
M internal/app/browser/workspace_manager_tree_test.go +7 -7
| @@ -347,7 +347,7 @@ func TestTreeTraversal(t *testing.T) { | |||
| 347 | 347 | original := wm.root | |
| 348 | 348 | node1, _ := wm.splitNode(original, "right") | |
| 349 | 349 | _, _ = wm.splitNode(node1, "down") | |
| 350 | - | _, _ = wm.splitNode(original, "down") // Split the original leaf, not the new root branch | |
| 350 | + | _, _ = wm.splitNode(original, "down") // Split the original leaf, not the new root branch | |
| 351 | 351 | ||
| 352 | 352 | leaves := wm.collectLeaves() | |
| 353 | 353 | ||
| @@ -532,7 +532,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||
| 532 | 532 | // This is where the bug occurs: parent is root, but 2 panes remain (B and C) | |
| 533 | 533 | // The current code incorrectly thinks this is an error | |
| 534 | 534 | err = wm.closePane(A) | |
| 535 | - | ||
| 535 | + | ||
| 536 | 536 | // THIS IS THE BUG: The current implementation returns an error here | |
| 537 | 537 | // but it shouldn't - it's a valid operation | |
| 538 | 538 | if err != nil { | |
| @@ -541,7 +541,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||
| 541 | 541 | t.Logf("Bug reproduced: %v", err) | |
| 542 | 542 | t.Logf("This error should not occur - closing pane with branch sibling is valid") | |
| 543 | 543 | // Comment out the return to see what the final state should be | |
| 544 | - | // return | |
| 544 | + | // return | |
| 545 | 545 | } else { | |
| 546 | 546 | t.Fatalf("Unexpected error: %v", err) | |
| 547 | 547 | } | |
| @@ -550,7 +550,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||
| 550 | 550 | // What SHOULD happen after successful close: | |
| 551 | 551 | // The SubPaned (containing B and C) should become the new root | |
| 552 | 552 | validateTreeStructure(t, wm.root) | |
| 553 | - | ||
| 553 | + | ||
| 554 | 554 | // Verify final state (what we expect after fix) | |
| 555 | 555 | finalLeaves, finalBranches := countNodes(wm.root) | |
| 556 | 556 | if finalLeaves != 2 { | |
| @@ -564,7 +564,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||
| 564 | 564 | if wm.root.isLeaf { | |
| 565 | 565 | t.Error("Root should be a branch node after closing A") | |
| 566 | 566 | } | |
| 567 | - | ||
| 567 | + | ||
| 568 | 568 | // B and C should still be in the tree | |
| 569 | 569 | foundB := false | |
| 570 | 570 | foundC := false | |
| @@ -594,7 +594,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||
| 594 | 594 | if err != nil { | |
| 595 | 595 | t.Fatalf("Split right failed: %v", err) | |
| 596 | 596 | } | |
| 597 | - | ||
| 597 | + | ||
| 598 | 598 | C, err := wm.splitNode(B, "down") | |
| 599 | 599 | if err != nil { | |
| 600 | 600 | t.Fatalf("Split down failed: %v", err) | |
| @@ -608,7 +608,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||
| 608 | 608 | ||
| 609 | 609 | // Close A - the Paned(v) containing B and C should become root | |
| 610 | 610 | err = wm.closePane(A) | |
| 611 | - | ||
| 611 | + | ||
| 612 | 612 | // Bug should occur here too | |
| 613 | 613 | if err != nil { | |
| 614 | 614 | if err.Error() == "unexpected tree state during pane closure" { | |
| @@ -347,7 +347,7 @@ func TestTreeTraversal(t *testing.T) { | |||||
| 347 | original := wm.root | 347 | original := wm.root | ||
| 348 | node1, _ := wm.splitNode(original, "right") | 348 | node1, _ := wm.splitNode(original, "right") | ||
| 349 | _, _ = wm.splitNode(node1, "down") | 349 | _, _ = wm.splitNode(node1, "down") | ||
| 350 | - | _, _ = wm.splitNode(original, "down") // Split the original leaf, not the new root branch | 350 | + | _, _ = wm.splitNode(original, "down") // Split the original leaf, not the new root branch |
| 351 | 351 | ||||
| 352 | leaves := wm.collectLeaves() | 352 | leaves := wm.collectLeaves() | ||
| 353 | 353 | ||||
| @@ -532,7 +532,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||||
| 532 | // This is where the bug occurs: parent is root, but 2 panes remain (B and C) | 532 | // This is where the bug occurs: parent is root, but 2 panes remain (B and C) | ||
| 533 | // The current code incorrectly thinks this is an error | 533 | // The current code incorrectly thinks this is an error | ||
| 534 | err = wm.closePane(A) | 534 | err = wm.closePane(A) | ||
| 535 | - | 535 | + | ||
| 536 | // THIS IS THE BUG: The current implementation returns an error here | 536 | // THIS IS THE BUG: The current implementation returns an error here | ||
| 537 | // but it shouldn't - it's a valid operation | 537 | // but it shouldn't - it's a valid operation | ||
| 538 | if err != nil { | 538 | if err != nil { | ||
| @@ -541,7 +541,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||||
| 541 | t.Logf("Bug reproduced: %v", err) | 541 | t.Logf("Bug reproduced: %v", err) | ||
| 542 | t.Logf("This error should not occur - closing pane with branch sibling is valid") | 542 | t.Logf("This error should not occur - closing pane with branch sibling is valid") | ||
| 543 | // Comment out the return to see what the final state should be | 543 | // Comment out the return to see what the final state should be | ||
| 544 | - | // return | 544 | + | // return |
| 545 | } else { | 545 | } else { | ||
| 546 | t.Fatalf("Unexpected error: %v", err) | 546 | t.Fatalf("Unexpected error: %v", err) | ||
| 547 | } | 547 | } | ||
| @@ -550,7 +550,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||||
| 550 | // What SHOULD happen after successful close: | 550 | // What SHOULD happen after successful close: | ||
| 551 | // The SubPaned (containing B and C) should become the new root | 551 | // The SubPaned (containing B and C) should become the new root | ||
| 552 | validateTreeStructure(t, wm.root) | 552 | validateTreeStructure(t, wm.root) | ||
| 553 | - | 553 | + | ||
| 554 | // Verify final state (what we expect after fix) | 554 | // Verify final state (what we expect after fix) | ||
| 555 | finalLeaves, finalBranches := countNodes(wm.root) | 555 | finalLeaves, finalBranches := countNodes(wm.root) | ||
| 556 | if finalLeaves != 2 { | 556 | if finalLeaves != 2 { | ||
| @@ -564,7 +564,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||||
| 564 | if wm.root.isLeaf { | 564 | if wm.root.isLeaf { | ||
| 565 | t.Error("Root should be a branch node after closing A") | 565 | t.Error("Root should be a branch node after closing A") | ||
| 566 | } | 566 | } | ||
| 567 | - | 567 | + | ||
| 568 | // B and C should still be in the tree | 568 | // B and C should still be in the tree | ||
| 569 | foundB := false | 569 | foundB := false | ||
| 570 | foundC := false | 570 | foundC := false | ||
| @@ -594,7 +594,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||||
| 594 | if err != nil { | 594 | if err != nil { | ||
| 595 | t.Fatalf("Split right failed: %v", err) | 595 | t.Fatalf("Split right failed: %v", err) | ||
| 596 | } | 596 | } | ||
| 597 | - | 597 | + | ||
| 598 | C, err := wm.splitNode(B, "down") | 598 | C, err := wm.splitNode(B, "down") | ||
| 599 | if err != nil { | 599 | if err != nil { | ||
| 600 | t.Fatalf("Split down failed: %v", err) | 600 | t.Fatalf("Split down failed: %v", err) | ||
| @@ -608,7 +608,7 @@ func TestBugClosePaneWithBranchSibling(t *testing.T) { | |||||
| 608 | 608 | ||||
| 609 | // Close A - the Paned(v) containing B and C should become root | 609 | // Close A - the Paned(v) containing B and C should become root | ||
| 610 | err = wm.closePane(A) | 610 | err = wm.closePane(A) | ||
| 611 | - | 611 | + | ||
| 612 | // Bug should occur here too | 612 | // Bug should occur here too | ||
| 613 | if err != nil { | 613 | if err != nil { | ||
| 614 | if err.Error() == "unexpected tree state during pane closure" { | 614 | if err.Error() == "unexpected tree state during pane closure" { | ||
M internal/app/control/zoom.go +1 -1
| @@ -59,7 +59,7 @@ func (z *ZoomController) handleZoomChange(level float64) { | |||
| 59 | 59 | if url == "" { | |
| 60 | 60 | return | |
| 61 | 61 | } | |
| 62 | - | ||
| 62 | + | ||
| 63 | 63 | go func(url string, level float64) { | |
| 64 | 64 | ctx := context.Background() | |
| 65 | 65 | if err := z.browserService.SetZoomLevel(ctx, url, level); err != nil { | |
| @@ -59,7 +59,7 @@ func (z *ZoomController) handleZoomChange(level float64) { | |||||
| 59 | if url == "" { | 59 | if url == "" { | ||
| 60 | return | 60 | return | ||
| 61 | } | 61 | } | ||
| 62 | - | 62 | + | ||
| 63 | go func(url string, level float64) { | 63 | go func(url string, level float64) { | ||
| 64 | ctx := context.Background() | 64 | ctx := context.Background() | ||
| 65 | if err := z.browserService.SetZoomLevel(ctx, url, level); err != nil { | 65 | if err := z.browserService.SetZoomLevel(ctx, url, level); err != nil { | ||
M internal/app/messaging/deduplicator.go +2 -3
| @@ -102,13 +102,12 @@ func (d *PaneRequestDeduplicator) ClearRequestID(requestID string) { | |||
| 102 | 102 | if requestID == "" { | |
| 103 | 103 | return | |
| 104 | 104 | } | |
| 105 | - | ||
| 105 | + | ||
| 106 | 106 | d.mu.Lock() | |
| 107 | 107 | defer d.mu.Unlock() | |
| 108 | - | ||
| 108 | + | ||
| 109 | 109 | if d.requestIDs[requestID] { | |
| 110 | 110 | delete(d.requestIDs, requestID) | |
| 111 | 111 | log.Printf("[deduplicator] Cleared request ID: %s", requestID) | |
| 112 | 112 | } | |
| 113 | 113 | } | |
| 114 | - | ||
| @@ -102,13 +102,12 @@ func (d *PaneRequestDeduplicator) ClearRequestID(requestID string) { | |||||
| 102 | if requestID == "" { | 102 | if requestID == "" { | ||
| 103 | return | 103 | return | ||
| 104 | } | 104 | } | ||
| 105 | - | 105 | + | ||
| 106 | d.mu.Lock() | 106 | d.mu.Lock() | ||
| 107 | defer d.mu.Unlock() | 107 | defer d.mu.Unlock() | ||
| 108 | - | 108 | + | ||
| 109 | if d.requestIDs[requestID] { | 109 | if d.requestIDs[requestID] { | ||
| 110 | delete(d.requestIDs, requestID) | 110 | delete(d.requestIDs, requestID) | ||
| 111 | log.Printf("[deduplicator] Cleared request ID: %s", requestID) | 111 | log.Printf("[deduplicator] Cleared request ID: %s", requestID) | ||
| 112 | } | 112 | } | ||
| 113 | } | 113 | } | ||
| 114 | - | ||||
M pkg/webkit/keyboard_cgo.go +14 -1
| @@ -199,7 +199,20 @@ func dispatchAccelerator(uid uintptr, keyval uint, state uint) bool { | |||
| 199 | 199 | case uint(C.GDK_KEY_p), uint(C.GDK_KEY_P): | |
| 200 | 200 | keyName = "p" | |
| 201 | 201 | default: | |
| 202 | - | // If Control held, log unknown keyval for diagnostics | |
| 202 | + | // Filter out modifier keys themselves (like Ctrl, Alt, Shift) | |
| 203 | + | // These keyvals shouldn't be logged as misses | |
| 204 | + | switch keyval { | |
| 205 | + | case 0xffe1, 0xffe2: // GDK_KEY_Shift_L, GDK_KEY_Shift_R | |
| 206 | + | return false | |
| 207 | + | case 0xffe3, 0xffe4: // GDK_KEY_Control_L, GDK_KEY_Control_R | |
| 208 | + | return false | |
| 209 | + | case 0xffe9, 0xffea: // GDK_KEY_Alt_L, GDK_KEY_Alt_R | |
| 210 | + | return false | |
| 211 | + | case 0xffeb, 0xffec: // GDK_KEY_Super_L, GDK_KEY_Super_R | |
| 212 | + | return false | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | // If Control held, log unknown keyval for diagnostics (but not modifier keys) | |
| 203 | 216 | if ctrl { | |
| 204 | 217 | log.Printf("[accelerator-miss] ctrl keyval=0x%x", keyval) | |
| 205 | 218 | } | |
| @@ -199,7 +199,20 @@ func dispatchAccelerator(uid uintptr, keyval uint, state uint) bool { | |||||
| 199 | case uint(C.GDK_KEY_p), uint(C.GDK_KEY_P): | 199 | case uint(C.GDK_KEY_p), uint(C.GDK_KEY_P): | ||
| 200 | keyName = "p" | 200 | keyName = "p" | ||
| 201 | default: | 201 | default: | ||
| 202 | - | // If Control held, log unknown keyval for diagnostics | 202 | + | // Filter out modifier keys themselves (like Ctrl, Alt, Shift) |
| 203 | + | // These keyvals shouldn't be logged as misses | |||
| 204 | + | switch keyval { | |||
| 205 | + | case 0xffe1, 0xffe2: // GDK_KEY_Shift_L, GDK_KEY_Shift_R | |||
| 206 | + | return false | |||
| 207 | + | case 0xffe3, 0xffe4: // GDK_KEY_Control_L, GDK_KEY_Control_R | |||
| 208 | + | return false | |||
| 209 | + | case 0xffe9, 0xffea: // GDK_KEY_Alt_L, GDK_KEY_Alt_R | |||
| 210 | + | return false | |||
| 211 | + | case 0xffeb, 0xffec: // GDK_KEY_Super_L, GDK_KEY_Super_R | |||
| 212 | + | return false | |||
| 213 | + | } | |||
| 214 | + | ||||
| 215 | + | // If Control held, log unknown keyval for diagnostics (but not modifier keys) | |||
| 203 | if ctrl { | 216 | if ctrl { | ||
| 204 | log.Printf("[accelerator-miss] ctrl keyval=0x%x", keyval) | 217 | log.Printf("[accelerator-miss] ctrl keyval=0x%x", keyval) | ||
| 205 | } | 218 | } | ||
A pkg/webkit/print_cgo.go +46 -0
| @@ -0,0 +1,46 @@ | |||
| 1 | + | //go:build webkit_cgo | |
| 2 | + | ||
| 3 | + | package webkit | |
| 4 | + | ||
| 5 | + | /* | |
| 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 | |
| 7 | + | #cgo CFLAGS: -I/usr/include/webkitgtk-6.0 | |
| 8 | + | #include <webkit/webkit.h> | |
| 9 | + | #include <gtk/gtk.h> | |
| 10 | + | */ | |
| 11 | + | import "C" | |
| 12 | + | import "unsafe" | |
| 13 | + | ||
| 14 | + | // ShowPrintDialog shows the print dialog for the WebView | |
| 15 | + | func (w *WebView) ShowPrintDialog() error { | |
| 16 | + | if w == nil || w.native == nil || w.native.wv == nil { | |
| 17 | + | return ErrNotImplemented | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | // Create print operation | |
| 21 | + | printOp := C.webkit_print_operation_new(w.native.wv) | |
| 22 | + | if printOp == nil { | |
| 23 | + | return ErrNotImplemented | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | // Pass nil for parent window - print dialog will work without it | |
| 27 | + | var parentWindow *C.GtkWindow = nil | |
| 28 | + | ||
| 29 | + | // Show print dialog | |
| 30 | + | response := C.webkit_print_operation_run_dialog(printOp, parentWindow) | |
| 31 | + | ||
| 32 | + | // Clean up print operation | |
| 33 | + | C.g_object_unref(C.gpointer(unsafe.Pointer(printOp))) | |
| 34 | + | ||
| 35 | + | // Handle the response | |
| 36 | + | switch response { | |
| 37 | + | case C.WEBKIT_PRINT_OPERATION_RESPONSE_PRINT: | |
| 38 | + | // User clicked print - the operation will proceed | |
| 39 | + | return nil | |
| 40 | + | case C.WEBKIT_PRINT_OPERATION_RESPONSE_CANCEL: | |
| 41 | + | // User cancelled - not an error | |
| 42 | + | return nil | |
| 43 | + | default: | |
| 44 | + | return ErrNotImplemented | |
| 45 | + | } | |
| 46 | + | } | |
| @@ -0,0 +1,46 @@ | |||||
| 1 | + | //go:build webkit_cgo | |||
| 2 | + | ||||
| 3 | + | package webkit | |||
| 4 | + | ||||
| 5 | + | /* | |||
| 6 | + | #cgo pkg-config: webkitgtk-6.0 gtk4 | |||
| 7 | + | #cgo CFLAGS: -I/usr/include/webkitgtk-6.0 | |||
| 8 | + | #include <webkit/webkit.h> | |||
| 9 | + | #include <gtk/gtk.h> | |||
| 10 | + | */ | |||
| 11 | + | import "C" | |||
| 12 | + | import "unsafe" | |||
| 13 | + | ||||
| 14 | + | // ShowPrintDialog shows the print dialog for the WebView | |||
| 15 | + | func (w *WebView) ShowPrintDialog() error { | |||
| 16 | + | if w == nil || w.native == nil || w.native.wv == nil { | |||
| 17 | + | return ErrNotImplemented | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | // Create print operation | |||
| 21 | + | printOp := C.webkit_print_operation_new(w.native.wv) | |||
| 22 | + | if printOp == nil { | |||
| 23 | + | return ErrNotImplemented | |||
| 24 | + | } | |||
| 25 | + | ||||
| 26 | + | // Pass nil for parent window - print dialog will work without it | |||
| 27 | + | var parentWindow *C.GtkWindow = nil | |||
| 28 | + | ||||
| 29 | + | // Show print dialog | |||
| 30 | + | response := C.webkit_print_operation_run_dialog(printOp, parentWindow) | |||
| 31 | + | ||||
| 32 | + | // Clean up print operation | |||
| 33 | + | C.g_object_unref(C.gpointer(unsafe.Pointer(printOp))) | |||
| 34 | + | ||||
| 35 | + | // Handle the response | |||
| 36 | + | switch response { | |||
| 37 | + | case C.WEBKIT_PRINT_OPERATION_RESPONSE_PRINT: | |||
| 38 | + | // User clicked print - the operation will proceed | |||
| 39 | + | return nil | |||
| 40 | + | case C.WEBKIT_PRINT_OPERATION_RESPONSE_CANCEL: | |||
| 41 | + | // User cancelled - not an error | |||
| 42 | + | return nil | |||
| 43 | + | default: | |||
| 44 | + | return ErrNotImplemented | |||
| 45 | + | } | |||
| 46 | + | } | |||
M pkg/webkit/webview.go +3 -0
| @@ -136,6 +136,9 @@ func (w *WebView) ShowDevTools() error { return nil } | |||
| 136 | 136 | // CloseDevTools is a no-op in the non-CGO build. | |
| 137 | 137 | func (w *WebView) CloseDevTools() error { return nil } | |
| 138 | 138 | ||
| 139 | + | // ShowPrintDialog is a no-op in the non-CGO build. | |
| 140 | + | func (w *WebView) ShowPrintDialog() error { return nil } | |
| 141 | + | ||
| 139 | 142 | // RegisterScriptMessageHandler registers a callback invoked when the content script posts a message. | |
| 140 | 143 | func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgHandler = cb } | |
| 141 | 144 | ||
| @@ -136,6 +136,9 @@ func (w *WebView) ShowDevTools() error { return nil } | |||||
| 136 | // CloseDevTools is a no-op in the non-CGO build. | 136 | // CloseDevTools is a no-op in the non-CGO build. | ||
| 137 | func (w *WebView) CloseDevTools() error { return nil } | 137 | func (w *WebView) CloseDevTools() error { return nil } | ||
| 138 | 138 | ||||
| 139 | + | // ShowPrintDialog is a no-op in the non-CGO build. | |||
| 140 | + | func (w *WebView) ShowPrintDialog() error { return nil } | |||
| 141 | + | ||||
| 139 | // RegisterScriptMessageHandler registers a callback invoked when the content script posts a message. | 142 | // RegisterScriptMessageHandler registers a callback invoked when the content script posts a message. | ||
| 140 | func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgHandler = cb } | 143 | func (w *WebView) RegisterScriptMessageHandler(cb func(payload string)) { w.msgHandler = cb } | ||
| 141 | 144 | ||||
M pkg/webkit/webview_cgo.go +38 -5
| @@ -260,6 +260,26 @@ static void connect_close_signal(WebKitWebView* web_view, unsigned long id) { | |||
| 260 | 260 | printf("[webkit-connect] Connected close signal to WebView with id: %lu\n", id); | |
| 261 | 261 | } | |
| 262 | 262 | ||
| 263 | + | // Callback for print signal - intercepts Ctrl+P and can redirect behavior | |
| 264 | + | static gboolean on_print_request(WebKitWebView* web_view, WebKitPrintOperation* print_operation, gpointer user_data) { | |
| 265 | + | (void)web_view; | |
| 266 | + | (void)print_operation; | |
| 267 | + | (void)user_data; | |
| 268 | + | ||
| 269 | + | // Return TRUE to prevent default print dialog | |
| 270 | + | // Return FALSE to allow default print behavior | |
| 271 | + | // For now, we'll block the print dialog entirely since Ctrl+P should trigger pane mode | |
| 272 | + | printf("[webkit-print] Print request intercepted - blocking to prevent conflict with pane mode\n"); | |
| 273 | + | return TRUE; // Block the print dialog | |
| 274 | + | } | |
| 275 | + | ||
| 276 | + | // Connect print signal to a WebView to intercept Ctrl+P | |
| 277 | + | static void connect_print_signal(WebKitWebView* web_view, unsigned long id) { | |
| 278 | + | if (!web_view) return; | |
| 279 | + | g_signal_connect_data(G_OBJECT(web_view), "print", G_CALLBACK(on_print_request), (gpointer)id, NULL, 0); | |
| 280 | + | printf("[webkit-connect] Connected print signal to WebView with id: %lu\n", id); | |
| 281 | + | } | |
| 282 | + | ||
| 263 | 283 | static gboolean gtk_prefers_dark() { | |
| 264 | 284 | // Method 1: Check GNOME desktop interface color-scheme (primary method) | |
| 265 | 285 | GSettings* desktop_settings = g_settings_new("org.gnome.desktop.interface"); | |
| @@ -1112,6 +1132,9 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||
| 1112 | 1132 | // Attach GTK4 input controllers (keyboard, mouse) | |
| 1113 | 1133 | AttachKeyboardControllers(v) | |
| 1114 | 1134 | ||
| 1135 | + | // Enable print interception to prevent Ctrl+P conflicts with pane mode | |
| 1136 | + | v.EnablePrintInterception() | |
| 1137 | + | ||
| 1115 | 1138 | // Watch for GTK theme changes and propagate to page at runtime | |
| 1116 | 1139 | if settings := C.gtk_settings_get_default(); settings != nil { | |
| 1117 | 1140 | C.connect_theme_changed_with_id(settings, C.ulong(v.id)) | |
| @@ -1580,7 +1603,7 @@ func (w *WebView) Destroy() error { | |||
| 1580 | 1603 | } | |
| 1581 | 1604 | ||
| 1582 | 1605 | // GTK4: destroy windows via gtk_window_destroy if we created one | |
| 1583 | - | if w.native.win != nil { | |
| 1606 | + | if w.native.win != nil && w.config != nil && w.config.CreateWindow { | |
| 1584 | 1607 | C.gtk_window_destroy((*C.GtkWindow)(unsafe.Pointer(w.native.win))) | |
| 1585 | 1608 | w.native.win = nil | |
| 1586 | 1609 | if w.window != nil { | |
| @@ -1667,6 +1690,13 @@ func (w *WebView) RegisterCloseHandler(cb func()) { | |||
| 1667 | 1690 | } | |
| 1668 | 1691 | } | |
| 1669 | 1692 | ||
| 1693 | + | // EnablePrintInterception connects the print signal to intercept Ctrl+P | |
| 1694 | + | func (w *WebView) EnablePrintInterception() { | |
| 1695 | + | if w.native != nil && w.native.wv != nil { | |
| 1696 | + | C.connect_print_signal(w.native.wv, C.ulong(w.id)) | |
| 1697 | + | } | |
| 1698 | + | } | |
| 1699 | + | ||
| 1670 | 1700 | func (w *WebView) dispatchScriptMessage(payload string) { | |
| 1671 | 1701 | if w != nil && w.msgHandler != nil { | |
| 1672 | 1702 | w.msgHandler(payload) | |
| @@ -1785,10 +1815,13 @@ func (w *WebView) DestroyWindow() { | |||
| 1785 | 1815 | if w == nil || w.native == nil || w.native.win == nil { | |
| 1786 | 1816 | return | |
| 1787 | 1817 | } | |
| 1788 | - | C.gtk_window_destroy((*C.GtkWindow)(unsafe.Pointer(w.native.win))) | |
| 1789 | - | w.native.win = nil | |
| 1790 | - | if w.window != nil { | |
| 1791 | - | w.window.win = nil | |
| 1818 | + | // Only destroy if we actually created a window | |
| 1819 | + | if w.config != nil && w.config.CreateWindow { | |
| 1820 | + | C.gtk_window_destroy((*C.GtkWindow)(unsafe.Pointer(w.native.win))) | |
| 1821 | + | w.native.win = nil | |
| 1822 | + | if w.window != nil { | |
| 1823 | + | w.window.win = nil | |
| 1824 | + | } | |
| 1792 | 1825 | } | |
| 1793 | 1826 | } | |
| 1794 | 1827 | ||
| @@ -260,6 +260,26 @@ static void connect_close_signal(WebKitWebView* web_view, unsigned long id) { | |||||
| 260 | printf("[webkit-connect] Connected close signal to WebView with id: %lu\n", id); | 260 | printf("[webkit-connect] Connected close signal to WebView with id: %lu\n", id); | ||
| 261 | } | 261 | } | ||
| 262 | 262 | ||||
| 263 | + | // Callback for print signal - intercepts Ctrl+P and can redirect behavior | |||
| 264 | + | static gboolean on_print_request(WebKitWebView* web_view, WebKitPrintOperation* print_operation, gpointer user_data) { | |||
| 265 | + | (void)web_view; | |||
| 266 | + | (void)print_operation; | |||
| 267 | + | (void)user_data; | |||
| 268 | + | ||||
| 269 | + | // Return TRUE to prevent default print dialog | |||
| 270 | + | // Return FALSE to allow default print behavior | |||
| 271 | + | // For now, we'll block the print dialog entirely since Ctrl+P should trigger pane mode | |||
| 272 | + | printf("[webkit-print] Print request intercepted - blocking to prevent conflict with pane mode\n"); | |||
| 273 | + | return TRUE; // Block the print dialog | |||
| 274 | + | } | |||
| 275 | + | ||||
| 276 | + | // Connect print signal to a WebView to intercept Ctrl+P | |||
| 277 | + | static void connect_print_signal(WebKitWebView* web_view, unsigned long id) { | |||
| 278 | + | if (!web_view) return; | |||
| 279 | + | g_signal_connect_data(G_OBJECT(web_view), "print", G_CALLBACK(on_print_request), (gpointer)id, NULL, 0); | |||
| 280 | + | printf("[webkit-connect] Connected print signal to WebView with id: %lu\n", id); | |||
| 281 | + | } | |||
| 282 | + | ||||
| 263 | static gboolean gtk_prefers_dark() { | 283 | static gboolean gtk_prefers_dark() { | ||
| 264 | // Method 1: Check GNOME desktop interface color-scheme (primary method) | 284 | // Method 1: Check GNOME desktop interface color-scheme (primary method) | ||
| 265 | GSettings* desktop_settings = g_settings_new("org.gnome.desktop.interface"); | 285 | GSettings* desktop_settings = g_settings_new("org.gnome.desktop.interface"); | ||
| @@ -1112,6 +1132,9 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||||
| 1112 | // Attach GTK4 input controllers (keyboard, mouse) | 1132 | // Attach GTK4 input controllers (keyboard, mouse) | ||
| 1113 | AttachKeyboardControllers(v) | 1133 | AttachKeyboardControllers(v) | ||
| 1114 | 1134 | ||||
| 1135 | + | // Enable print interception to prevent Ctrl+P conflicts with pane mode | |||
| 1136 | + | v.EnablePrintInterception() | |||
| 1137 | + | ||||
| 1115 | // Watch for GTK theme changes and propagate to page at runtime | 1138 | // Watch for GTK theme changes and propagate to page at runtime | ||
| 1116 | if settings := C.gtk_settings_get_default(); settings != nil { | 1139 | if settings := C.gtk_settings_get_default(); settings != nil { | ||
| 1117 | C.connect_theme_changed_with_id(settings, C.ulong(v.id)) | 1140 | C.connect_theme_changed_with_id(settings, C.ulong(v.id)) | ||
| @@ -1580,7 +1603,7 @@ func (w *WebView) Destroy() error { | |||||
| 1580 | } | 1603 | } | ||
| 1581 | 1604 | ||||
| 1582 | // GTK4: destroy windows via gtk_window_destroy if we created one | 1605 | // GTK4: destroy windows via gtk_window_destroy if we created one | ||
| 1583 | - | if w.native.win != nil { | 1606 | + | if w.native.win != nil && w.config != nil && w.config.CreateWindow { |
| 1584 | C.gtk_window_destroy((*C.GtkWindow)(unsafe.Pointer(w.native.win))) | 1607 | C.gtk_window_destroy((*C.GtkWindow)(unsafe.Pointer(w.native.win))) | ||
| 1585 | w.native.win = nil | 1608 | w.native.win = nil | ||
| 1586 | if w.window != nil { | 1609 | if w.window != nil { | ||
| @@ -1667,6 +1690,13 @@ func (w *WebView) RegisterCloseHandler(cb func()) { | |||||
| 1667 | } | 1690 | } | ||
| 1668 | } | 1691 | } | ||
| 1669 | 1692 | ||||
| 1693 | + | // EnablePrintInterception connects the print signal to intercept Ctrl+P | |||
| 1694 | + | func (w *WebView) EnablePrintInterception() { | |||
| 1695 | + | if w.native != nil && w.native.wv != nil { | |||
| 1696 | + | C.connect_print_signal(w.native.wv, C.ulong(w.id)) | |||
| 1697 | + | } | |||
| 1698 | + | } | |||
| 1699 | + | ||||
| 1670 | func (w *WebView) dispatchScriptMessage(payload string) { | 1700 | func (w *WebView) dispatchScriptMessage(payload string) { | ||
| 1671 | if w != nil && w.msgHandler != nil { | 1701 | if w != nil && w.msgHandler != nil { | ||
| 1672 | w.msgHandler(payload) | 1702 | w.msgHandler(payload) | ||
| @@ -1785,10 +1815,13 @@ func (w *WebView) DestroyWindow() { | |||||
| 1785 | if w == nil || w.native == nil || w.native.win == nil { | 1815 | if w == nil || w.native == nil || w.native.win == nil { | ||
| 1786 | return | 1816 | return | ||
| 1787 | } | 1817 | } | ||
| 1788 | - | C.gtk_window_destroy((*C.GtkWindow)(unsafe.Pointer(w.native.win))) | 1818 | + | // Only destroy if we actually created a window |
| 1789 | - | w.native.win = nil | 1819 | + | if w.config != nil && w.config.CreateWindow { |
| 1790 | - | if w.window != nil { | 1820 | + | C.gtk_window_destroy((*C.GtkWindow)(unsafe.Pointer(w.native.win))) |
| 1791 | - | w.window.win = nil | 1821 | + | w.native.win = nil |
| 1822 | + | if w.window != nil { | |||
| 1823 | + | w.window.win = nil | |||
| 1824 | + | } | |||
| 1792 | } | 1825 | } | ||
| 1793 | } | 1826 | } | ||
| 1794 | 1827 | ||||