feat(shortcuts): add Ctrl+Shift+P print page shortcut
- commit
- 058f890e1111790c4b562ab33f365accc12961ed
- author
- bnema · · 2026-01-06 18:12 UTC
- committer
- bnema · · 2026-01-06 19:04 UTC
- parent
- 6a8b220
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +38 -0
M internal/infrastructure/webkit/webview.go +15 -0
| @@ -950,6 +950,21 @@ func (wv *WebView) ShowDevTools() error { | |||
| 950 | 950 | return nil | |
| 951 | 951 | } | |
| 952 | 952 | ||
| 953 | + | // Print opens the print dialog for the current page. | |
| 954 | + | func (wv *WebView) Print() error { | |
| 955 | + | if wv.destroyed.Load() { | |
| 956 | + | return fmt.Errorf("webview %d is destroyed", wv.id) | |
| 957 | + | } | |
| 958 | + | printOp := webkit.NewPrintOperation(wv.inner) | |
| 959 | + | if printOp == nil { | |
| 960 | + | return fmt.Errorf("failed to create print operation for webview %d", wv.id) | |
| 961 | + | } | |
| 962 | + | // RunDialog with nil parent window - GTK will use the widget's toplevel | |
| 963 | + | printOp.RunDialog(nil) | |
| 964 | + | wv.logger.Debug().Uint64("id", uint64(wv.id)).Msg("print dialog opened") | |
| 965 | + | return nil | |
| 966 | + | } | |
| 967 | + | ||
| 953 | 968 | // IsDestroyed returns true if the WebView has been destroyed. | |
| 954 | 969 | func (wv *WebView) IsDestroyed() bool { | |
| 955 | 970 | return wv.destroyed.Load() | |
| @@ -950,6 +950,21 @@ func (wv *WebView) ShowDevTools() error { | |||||
| 950 | return nil | 950 | return nil | ||
| 951 | } | 951 | } | ||
| 952 | 952 | ||||
| 953 | + | // Print opens the print dialog for the current page. | |||
| 954 | + | func (wv *WebView) Print() error { | |||
| 955 | + | if wv.destroyed.Load() { | |||
| 956 | + | return fmt.Errorf("webview %d is destroyed", wv.id) | |||
| 957 | + | } | |||
| 958 | + | printOp := webkit.NewPrintOperation(wv.inner) | |||
| 959 | + | if printOp == nil { | |||
| 960 | + | return fmt.Errorf("failed to create print operation for webview %d", wv.id) | |||
| 961 | + | } | |||
| 962 | + | // RunDialog with nil parent window - GTK will use the widget's toplevel | |||
| 963 | + | printOp.RunDialog(nil) | |||
| 964 | + | wv.logger.Debug().Uint64("id", uint64(wv.id)).Msg("print dialog opened") | |||
| 965 | + | return nil | |||
| 966 | + | } | |||
| 967 | + | ||||
| 953 | // IsDestroyed returns true if the WebView has been destroyed. | 968 | // IsDestroyed returns true if the WebView has been destroyed. | ||
| 954 | func (wv *WebView) IsDestroyed() bool { | 969 | func (wv *WebView) IsDestroyed() bool { | ||
| 955 | return wv.destroyed.Load() | 970 | return wv.destroyed.Load() | ||
M internal/ui/coordinator/navigation.go +20 -0
| @@ -200,6 +200,26 @@ func (c *NavigationCoordinator) OpenDevTools(ctx context.Context) error { | |||
| 200 | 200 | return fmt.Errorf("webview does not support devtools") | |
| 201 | 201 | } | |
| 202 | 202 | ||
| 203 | + | // PrintPage opens the print dialog for the active WebView. | |
| 204 | + | func (c *NavigationCoordinator) PrintPage(ctx context.Context) error { | |
| 205 | + | log := logging.FromContext(ctx) | |
| 206 | + | ||
| 207 | + | wv := c.contentCoord.ActiveWebView(ctx) | |
| 208 | + | if wv == nil { | |
| 209 | + | log.Warn().Msg("no active webview for print") | |
| 210 | + | return fmt.Errorf("no active webview") | |
| 211 | + | } | |
| 212 | + | ||
| 213 | + | log.Debug().Uint64("webview_id", uint64(wv.ID())).Msg("opening print dialog") | |
| 214 | + | ||
| 215 | + | // Type assert to access Print (not in port.WebView interface) | |
| 216 | + | if webkitWV, ok := interface{}(wv).(*webkit.WebView); ok { | |
| 217 | + | return webkitWV.Print() | |
| 218 | + | } | |
| 219 | + | ||
| 220 | + | return fmt.Errorf("webview does not support printing") | |
| 221 | + | } | |
| 222 | + | ||
| 203 | 223 | // UpdateHistoryTitle updates the title of a history entry after page load. | |
| 204 | 224 | func (c *NavigationCoordinator) UpdateHistoryTitle(ctx context.Context, paneID entity.PaneID, url, title string) { | |
| 205 | 225 | log := logging.FromContext(ctx) | |
| @@ -200,6 +200,26 @@ func (c *NavigationCoordinator) OpenDevTools(ctx context.Context) error { | |||||
| 200 | return fmt.Errorf("webview does not support devtools") | 200 | return fmt.Errorf("webview does not support devtools") | ||
| 201 | } | 201 | } | ||
| 202 | 202 | ||||
| 203 | + | // PrintPage opens the print dialog for the active WebView. | |||
| 204 | + | func (c *NavigationCoordinator) PrintPage(ctx context.Context) error { | |||
| 205 | + | log := logging.FromContext(ctx) | |||
| 206 | + | ||||
| 207 | + | wv := c.contentCoord.ActiveWebView(ctx) | |||
| 208 | + | if wv == nil { | |||
| 209 | + | log.Warn().Msg("no active webview for print") | |||
| 210 | + | return fmt.Errorf("no active webview") | |||
| 211 | + | } | |||
| 212 | + | ||||
| 213 | + | log.Debug().Uint64("webview_id", uint64(wv.ID())).Msg("opening print dialog") | |||
| 214 | + | ||||
| 215 | + | // Type assert to access Print (not in port.WebView interface) | |||
| 216 | + | if webkitWV, ok := interface{}(wv).(*webkit.WebView); ok { | |||
| 217 | + | return webkitWV.Print() | |||
| 218 | + | } | |||
| 219 | + | ||||
| 220 | + | return fmt.Errorf("webview does not support printing") | |||
| 221 | + | } | |||
| 222 | + | ||||
| 203 | // UpdateHistoryTitle updates the title of a history entry after page load. | 223 | // UpdateHistoryTitle updates the title of a history entry after page load. | ||
| 204 | func (c *NavigationCoordinator) UpdateHistoryTitle(ctx context.Context, paneID entity.PaneID, url, title string) { | 224 | func (c *NavigationCoordinator) UpdateHistoryTitle(ctx context.Context, paneID entity.PaneID, url, title string) { | ||
| 205 | log := logging.FromContext(ctx) | 225 | log := logging.FromContext(ctx) | ||
M internal/ui/dispatcher/keyboard.go +1 -0
| @@ -179,6 +179,7 @@ func (d *KeyboardDispatcher) initActionHandlers() { | |||
| 179 | 179 | input.ActionGoForward: d.navCoord.GoForward, | |
| 180 | 180 | input.ActionReload: d.navCoord.Reload, | |
| 181 | 181 | input.ActionHardReload: d.navCoord.HardReload, | |
| 182 | + | input.ActionPrintPage: d.navCoord.PrintPage, | |
| 182 | 183 | // Zoom actions | |
| 183 | 184 | input.ActionZoomIn: func(ctx context.Context) error { return d.handleZoom(ctx, "in") }, | |
| 184 | 185 | input.ActionZoomOut: func(ctx context.Context) error { return d.handleZoom(ctx, "out") }, | |
| @@ -179,6 +179,7 @@ func (d *KeyboardDispatcher) initActionHandlers() { | |||||
| 179 | input.ActionGoForward: d.navCoord.GoForward, | 179 | input.ActionGoForward: d.navCoord.GoForward, | ||
| 180 | input.ActionReload: d.navCoord.Reload, | 180 | input.ActionReload: d.navCoord.Reload, | ||
| 181 | input.ActionHardReload: d.navCoord.HardReload, | 181 | input.ActionHardReload: d.navCoord.HardReload, | ||
| 182 | + | input.ActionPrintPage: d.navCoord.PrintPage, | |||
| 182 | // Zoom actions | 183 | // Zoom actions | ||
| 183 | input.ActionZoomIn: func(ctx context.Context) error { return d.handleZoom(ctx, "in") }, | 184 | input.ActionZoomIn: func(ctx context.Context) error { return d.handleZoom(ctx, "in") }, | ||
| 184 | input.ActionZoomOut: func(ctx context.Context) error { return d.handleZoom(ctx, "out") }, | 185 | input.ActionZoomOut: func(ctx context.Context) error { return d.handleZoom(ctx, "out") }, | ||
M internal/ui/input/shortcuts.go +2 -0
| @@ -170,6 +170,7 @@ const ( | |||
| 170 | 170 | ActionReload Action = "reload" | |
| 171 | 171 | ActionHardReload Action = "hard_reload" | |
| 172 | 172 | ActionStop Action = "stop" | |
| 173 | + | ActionPrintPage Action = "print_page" | |
| 173 | 174 | ||
| 174 | 175 | // Zoom | |
| 175 | 176 | ActionZoomIn Action = "zoom_in" | |
| @@ -369,6 +370,7 @@ func (s *ShortcutSet) registerStandardShortcuts() { | |||
| 369 | 370 | s.Global[KeyBinding{uint(gdk.KEY_q), ModCtrl}] = ActionQuit | |
| 370 | 371 | s.Global[KeyBinding{uint(gdk.KEY_F11), ModNone}] = ActionToggleFullscreen | |
| 371 | 372 | s.Global[KeyBinding{uint('c'), ModCtrl | ModShift}] = ActionCopyURL | |
| 373 | + | s.Global[KeyBinding{uint('p'), ModCtrl | ModShift}] = ActionPrintPage | |
| 372 | 374 | // Session management - direct shortcut to open session manager | |
| 373 | 375 | s.Global[KeyBinding{uint(gdk.KEY_s), ModCtrl | ModShift}] = ActionOpenSessionManager | |
| 374 | 376 | } | |
| @@ -170,6 +170,7 @@ const ( | |||||
| 170 | ActionReload Action = "reload" | 170 | ActionReload Action = "reload" | ||
| 171 | ActionHardReload Action = "hard_reload" | 171 | ActionHardReload Action = "hard_reload" | ||
| 172 | ActionStop Action = "stop" | 172 | ActionStop Action = "stop" | ||
| 173 | + | ActionPrintPage Action = "print_page" | |||
| 173 | 174 | ||||
| 174 | // Zoom | 175 | // Zoom | ||
| 175 | ActionZoomIn Action = "zoom_in" | 176 | ActionZoomIn Action = "zoom_in" | ||
| @@ -369,6 +370,7 @@ func (s *ShortcutSet) registerStandardShortcuts() { | |||||
| 369 | s.Global[KeyBinding{uint(gdk.KEY_q), ModCtrl}] = ActionQuit | 370 | s.Global[KeyBinding{uint(gdk.KEY_q), ModCtrl}] = ActionQuit | ||
| 370 | s.Global[KeyBinding{uint(gdk.KEY_F11), ModNone}] = ActionToggleFullscreen | 371 | s.Global[KeyBinding{uint(gdk.KEY_F11), ModNone}] = ActionToggleFullscreen | ||
| 371 | s.Global[KeyBinding{uint('c'), ModCtrl | ModShift}] = ActionCopyURL | 372 | s.Global[KeyBinding{uint('c'), ModCtrl | ModShift}] = ActionCopyURL | ||
| 373 | + | s.Global[KeyBinding{uint('p'), ModCtrl | ModShift}] = ActionPrintPage | |||
| 372 | // Session management - direct shortcut to open session manager | 374 | // Session management - direct shortcut to open session manager | ||
| 373 | s.Global[KeyBinding{uint(gdk.KEY_s), ModCtrl | ModShift}] = ActionOpenSessionManager | 375 | s.Global[KeyBinding{uint(gdk.KEY_s), ModCtrl | ModShift}] = ActionOpenSessionManager | ||
| 374 | } | 376 | } | ||