feat(clipboard): implement Ctrl+Shift+C shortcut to copy current URL
- commit
- 8a86fff6eed2da36d43459b2ea2d7360709e69d3
- author
- brice · · 2025-12-17 18:51 UTC
- committer
- brice · · 2025-12-17 21:09 UTC
- parent
- 717efa6
- signature
- unsigned
4 files · +83 -20
M internal/ui/component/omnibox.go +7 -10
| @@ -7,7 +7,6 @@ import ( | |||
| 7 | 7 | "sync" | |
| 8 | 8 | "time" | |
| 9 | 9 | ||
| 10 | - | "github.com/bnema/dumber/internal/application/port" | |
| 11 | 10 | "github.com/bnema/dumber/internal/application/usecase" | |
| 12 | 11 | "github.com/bnema/dumber/internal/domain/entity" | |
| 13 | 12 | "github.com/bnema/dumber/internal/domain/url" | |
| @@ -81,7 +80,7 @@ type Omnibox struct { | |||
| 81 | 80 | historyUC *usecase.SearchHistoryUseCase | |
| 82 | 81 | favoritesUC *usecase.ManageFavoritesUseCase | |
| 83 | 82 | faviconCache *cache.FaviconCache | |
| 84 | - | clipboard port.Clipboard | |
| 83 | + | copyURLUC *usecase.CopyURLUseCase | |
| 85 | 84 | shortcuts map[string]config.SearchShortcut | |
| 86 | 85 | defaultSearch string | |
| 87 | 86 | initialBehavior string | |
| @@ -114,7 +113,7 @@ type OmniboxConfig struct { | |||
| 114 | 113 | HistoryUC *usecase.SearchHistoryUseCase | |
| 115 | 114 | FavoritesUC *usecase.ManageFavoritesUseCase | |
| 116 | 115 | FaviconCache *cache.FaviconCache | |
| 117 | - | Clipboard port.Clipboard | |
| 116 | + | CopyURLUC *usecase.CopyURLUseCase | |
| 118 | 117 | Shortcuts map[string]config.SearchShortcut | |
| 119 | 118 | DefaultSearch string | |
| 120 | 119 | InitialBehavior string | |
| @@ -139,7 +138,7 @@ func NewOmnibox(ctx context.Context, cfg OmniboxConfig) *Omnibox { | |||
| 139 | 138 | historyUC: cfg.HistoryUC, | |
| 140 | 139 | favoritesUC: cfg.FavoritesUC, | |
| 141 | 140 | faviconCache: cfg.FaviconCache, | |
| 142 | - | clipboard: cfg.Clipboard, | |
| 141 | + | copyURLUC: cfg.CopyURLUC, | |
| 143 | 142 | shortcuts: cfg.Shortcuts, | |
| 144 | 143 | defaultSearch: cfg.DefaultSearch, | |
| 145 | 144 | initialBehavior: cfg.InitialBehavior, | |
| @@ -1071,8 +1070,8 @@ func (o *Omnibox) toggleFavorite() { | |||
| 1071 | 1070 | func (o *Omnibox) yankSelectedURL() { | |
| 1072 | 1071 | log := logging.FromContext(o.ctx) | |
| 1073 | 1072 | ||
| 1074 | - | if o.clipboard == nil { | |
| 1075 | - | log.Warn().Msg("yank URL: clipboard is nil") | |
| 1073 | + | if o.copyURLUC == nil { | |
| 1074 | + | log.Warn().Msg("yank URL: copy URL use case is nil") | |
| 1076 | 1075 | return | |
| 1077 | 1076 | } | |
| 1078 | 1077 | ||
| @@ -1105,11 +1104,9 @@ func (o *Omnibox) yankSelectedURL() { | |||
| 1105 | 1104 | ||
| 1106 | 1105 | go func() { | |
| 1107 | 1106 | ctx := o.ctx | |
| 1108 | - | if err := o.clipboard.WriteText(ctx, url); err != nil { | |
| 1109 | - | log.Error().Err(err).Str("url", url).Msg("failed to copy URL to clipboard") | |
| 1110 | - | return | |
| 1107 | + | if err := o.copyURLUC.Copy(ctx, url); err != nil { | |
| 1108 | + | return // Use case already logs the error | |
| 1111 | 1109 | } | |
| 1112 | - | log.Debug().Str("url", url).Msg("URL copied to clipboard") | |
| 1113 | 1110 | ||
| 1114 | 1111 | // Show toast notification on success (must run on GTK main thread) | |
| 1115 | 1112 | if o.onToast != nil { | |
| @@ -7,7 +7,6 @@ import ( | |||||
| 7 | "sync" | 7 | "sync" | ||
| 8 | "time" | 8 | "time" | ||
| 9 | 9 | ||||
| 10 | - | "github.com/bnema/dumber/internal/application/port" | |||
| 11 | "github.com/bnema/dumber/internal/application/usecase" | 10 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 12 | "github.com/bnema/dumber/internal/domain/entity" | 11 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 13 | "github.com/bnema/dumber/internal/domain/url" | 12 | "github.com/bnema/dumber/internal/domain/url" | ||
| @@ -81,7 +80,7 @@ type Omnibox struct { | |||||
| 81 | historyUC *usecase.SearchHistoryUseCase | 80 | historyUC *usecase.SearchHistoryUseCase | ||
| 82 | favoritesUC *usecase.ManageFavoritesUseCase | 81 | favoritesUC *usecase.ManageFavoritesUseCase | ||
| 83 | faviconCache *cache.FaviconCache | 82 | faviconCache *cache.FaviconCache | ||
| 84 | - | clipboard port.Clipboard | 83 | + | copyURLUC *usecase.CopyURLUseCase |
| 85 | shortcuts map[string]config.SearchShortcut | 84 | shortcuts map[string]config.SearchShortcut | ||
| 86 | defaultSearch string | 85 | defaultSearch string | ||
| 87 | initialBehavior string | 86 | initialBehavior string | ||
| @@ -114,7 +113,7 @@ type OmniboxConfig struct { | |||||
| 114 | HistoryUC *usecase.SearchHistoryUseCase | 113 | HistoryUC *usecase.SearchHistoryUseCase | ||
| 115 | FavoritesUC *usecase.ManageFavoritesUseCase | 114 | FavoritesUC *usecase.ManageFavoritesUseCase | ||
| 116 | FaviconCache *cache.FaviconCache | 115 | FaviconCache *cache.FaviconCache | ||
| 117 | - | Clipboard port.Clipboard | 116 | + | CopyURLUC *usecase.CopyURLUseCase |
| 118 | Shortcuts map[string]config.SearchShortcut | 117 | Shortcuts map[string]config.SearchShortcut | ||
| 119 | DefaultSearch string | 118 | DefaultSearch string | ||
| 120 | InitialBehavior string | 119 | InitialBehavior string | ||
| @@ -139,7 +138,7 @@ func NewOmnibox(ctx context.Context, cfg OmniboxConfig) *Omnibox { | |||||
| 139 | historyUC: cfg.HistoryUC, | 138 | historyUC: cfg.HistoryUC, | ||
| 140 | favoritesUC: cfg.FavoritesUC, | 139 | favoritesUC: cfg.FavoritesUC, | ||
| 141 | faviconCache: cfg.FaviconCache, | 140 | faviconCache: cfg.FaviconCache, | ||
| 142 | - | clipboard: cfg.Clipboard, | 141 | + | copyURLUC: cfg.CopyURLUC, |
| 143 | shortcuts: cfg.Shortcuts, | 142 | shortcuts: cfg.Shortcuts, | ||
| 144 | defaultSearch: cfg.DefaultSearch, | 143 | defaultSearch: cfg.DefaultSearch, | ||
| 145 | initialBehavior: cfg.InitialBehavior, | 144 | initialBehavior: cfg.InitialBehavior, | ||
| @@ -1071,8 +1070,8 @@ func (o *Omnibox) toggleFavorite() { | |||||
| 1071 | func (o *Omnibox) yankSelectedURL() { | 1070 | func (o *Omnibox) yankSelectedURL() { | ||
| 1072 | log := logging.FromContext(o.ctx) | 1071 | log := logging.FromContext(o.ctx) | ||
| 1073 | 1072 | ||||
| 1074 | - | if o.clipboard == nil { | 1073 | + | if o.copyURLUC == nil { |
| 1075 | - | log.Warn().Msg("yank URL: clipboard is nil") | 1074 | + | log.Warn().Msg("yank URL: copy URL use case is nil") |
| 1076 | return | 1075 | return | ||
| 1077 | } | 1076 | } | ||
| 1078 | 1077 | ||||
| @@ -1105,11 +1104,9 @@ func (o *Omnibox) yankSelectedURL() { | |||||
| 1105 | 1104 | ||||
| 1106 | go func() { | 1105 | go func() { | ||
| 1107 | ctx := o.ctx | 1106 | ctx := o.ctx | ||
| 1108 | - | if err := o.clipboard.WriteText(ctx, url); err != nil { | 1107 | + | if err := o.copyURLUC.Copy(ctx, url); err != nil { |
| 1109 | - | log.Error().Err(err).Str("url", url).Msg("failed to copy URL to clipboard") | 1108 | + | return // Use case already logs the error |
| 1110 | - | return | |||
| 1111 | } | 1109 | } | ||
| 1112 | - | log.Debug().Str("url", url).Msg("URL copied to clipboard") | |||
| 1113 | 1110 | ||||
| 1114 | // Show toast notification on success (must run on GTK main thread) | 1111 | // Show toast notification on success (must run on GTK main thread) | ||
| 1115 | if o.onToast != nil { | 1112 | if o.onToast != nil { | ||
M internal/ui/coordinator/workspace.go +13 -0
| @@ -1580,3 +1580,16 @@ func (c *WorkspaceCoordinator) ShowZoomToast(ctx context.Context, zoomPercent in | |||
| 1580 | 1580 | paneView.ShowZoomToast(ctx, zoomPercent) | |
| 1581 | 1581 | } | |
| 1582 | 1582 | } | |
| 1583 | + | ||
| 1584 | + | // ShowToastOnActivePane displays a toast notification on the active pane. | |
| 1585 | + | func (c *WorkspaceCoordinator) ShowToastOnActivePane(ctx context.Context, message string, level component.ToastLevel) { | |
| 1586 | + | _, wsView := c.getActiveWS() | |
| 1587 | + | if wsView == nil { | |
| 1588 | + | return | |
| 1589 | + | } | |
| 1590 | + | ||
| 1591 | + | paneView := wsView.GetActivePaneView() | |
| 1592 | + | if paneView != nil { | |
| 1593 | + | paneView.ShowToast(ctx, message, level) | |
| 1594 | + | } | |
| 1595 | + | } | |
| @@ -1580,3 +1580,16 @@ func (c *WorkspaceCoordinator) ShowZoomToast(ctx context.Context, zoomPercent in | |||||
| 1580 | paneView.ShowZoomToast(ctx, zoomPercent) | 1580 | paneView.ShowZoomToast(ctx, zoomPercent) | ||
| 1581 | } | 1581 | } | ||
| 1582 | } | 1582 | } | ||
| 1583 | + | ||||
| 1584 | + | // ShowToastOnActivePane displays a toast notification on the active pane. | |||
| 1585 | + | func (c *WorkspaceCoordinator) ShowToastOnActivePane(ctx context.Context, message string, level component.ToastLevel) { | |||
| 1586 | + | _, wsView := c.getActiveWS() | |||
| 1587 | + | if wsView == nil { | |||
| 1588 | + | return | |||
| 1589 | + | } | |||
| 1590 | + | ||||
| 1591 | + | paneView := wsView.GetActivePaneView() | |||
| 1592 | + | if paneView != nil { | |||
| 1593 | + | paneView.ShowToast(ctx, message, level) | |||
| 1594 | + | } | |||
| 1595 | + | } | |||
M internal/ui/dispatcher/keyboard.go +58 -9
| @@ -6,17 +6,20 @@ import ( | |||
| 6 | 6 | "github.com/bnema/dumber/internal/application/usecase" | |
| 7 | 7 | "github.com/bnema/dumber/internal/domain/entity" | |
| 8 | 8 | "github.com/bnema/dumber/internal/logging" | |
| 9 | + | "github.com/bnema/dumber/internal/ui/component" | |
| 9 | 10 | "github.com/bnema/dumber/internal/ui/coordinator" | |
| 10 | 11 | "github.com/bnema/dumber/internal/ui/input" | |
| 12 | + | "github.com/jwijenbergh/puregotk/v4/glib" | |
| 11 | 13 | ) | |
| 12 | 14 | ||
| 13 | 15 | // KeyboardDispatcher routes keyboard actions to appropriate coordinators. | |
| 14 | 16 | type KeyboardDispatcher struct { | |
| 15 | - | tabCoord *coordinator.TabCoordinator | |
| 16 | - | wsCoord *coordinator.WorkspaceCoordinator | |
| 17 | - | navCoord *coordinator.NavigationCoordinator | |
| 18 | - | zoomUC *usecase.ManageZoomUseCase | |
| 19 | - | onQuit func() | |
| 17 | + | tabCoord *coordinator.TabCoordinator | |
| 18 | + | wsCoord *coordinator.WorkspaceCoordinator | |
| 19 | + | navCoord *coordinator.NavigationCoordinator | |
| 20 | + | zoomUC *usecase.ManageZoomUseCase | |
| 21 | + | copyURLUC *usecase.CopyURLUseCase | |
| 22 | + | onQuit func() | |
| 20 | 23 | } | |
| 21 | 24 | ||
| 22 | 25 | // NewKeyboardDispatcher creates a new KeyboardDispatcher. | |
| @@ -26,15 +29,17 @@ func NewKeyboardDispatcher( | |||
| 26 | 29 | wsCoord *coordinator.WorkspaceCoordinator, | |
| 27 | 30 | navCoord *coordinator.NavigationCoordinator, | |
| 28 | 31 | zoomUC *usecase.ManageZoomUseCase, | |
| 32 | + | copyURLUC *usecase.CopyURLUseCase, | |
| 29 | 33 | ) *KeyboardDispatcher { | |
| 30 | 34 | log := logging.FromContext(ctx) | |
| 31 | 35 | log.Debug().Msg("creating keyboard dispatcher") | |
| 32 | 36 | ||
| 33 | 37 | return &KeyboardDispatcher{ | |
| 34 | - | tabCoord: tabCoord, | |
| 35 | - | wsCoord: wsCoord, | |
| 36 | - | navCoord: navCoord, | |
| 37 | - | zoomUC: zoomUC, | |
| 38 | + | tabCoord: tabCoord, | |
| 39 | + | wsCoord: wsCoord, | |
| 40 | + | navCoord: navCoord, | |
| 41 | + | zoomUC: zoomUC, | |
| 42 | + | copyURLUC: copyURLUC, | |
| 38 | 43 | } | |
| 39 | 44 | } | |
| 40 | 45 | ||
| @@ -136,6 +141,10 @@ func (d *KeyboardDispatcher) Dispatch(ctx context.Context, action input.Action) | |||
| 136 | 141 | case input.ActionToggleFullscreen: | |
| 137 | 142 | log.Debug().Msg("toggle fullscreen action (not yet implemented)") | |
| 138 | 143 | ||
| 144 | + | // Clipboard | |
| 145 | + | case input.ActionCopyURL: | |
| 146 | + | return d.handleCopyURL(ctx) | |
| 147 | + | ||
| 139 | 148 | // Application | |
| 140 | 149 | case input.ActionQuit: | |
| 141 | 150 | if d.onQuit != nil { | |
| @@ -212,3 +221,43 @@ func (d *KeyboardDispatcher) handleZoom(ctx context.Context, action string) erro | |||
| 212 | 221 | ||
| 213 | 222 | return nil | |
| 214 | 223 | } | |
| 224 | + | ||
| 225 | + | // handleCopyURL copies the active pane's URL to clipboard. | |
| 226 | + | func (d *KeyboardDispatcher) handleCopyURL(ctx context.Context) error { | |
| 227 | + | log := logging.FromContext(ctx) | |
| 228 | + | ||
| 229 | + | if d.copyURLUC == nil { | |
| 230 | + | log.Warn().Msg("copy URL use case not available") | |
| 231 | + | return nil | |
| 232 | + | } | |
| 233 | + | ||
| 234 | + | wv := d.navCoord.ActiveWebView(ctx) | |
| 235 | + | if wv == nil { | |
| 236 | + | log.Debug().Msg("no active webview for copy URL") | |
| 237 | + | return nil | |
| 238 | + | } | |
| 239 | + | ||
| 240 | + | uri := wv.URI() | |
| 241 | + | if uri == "" { | |
| 242 | + | log.Debug().Msg("active webview has empty URI") | |
| 243 | + | return nil | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | // Copy URL in background goroutine | |
| 247 | + | go func() { | |
| 248 | + | if err := d.copyURLUC.Copy(ctx, uri); err != nil { | |
| 249 | + | log.Error().Err(err).Str("uri", uri).Msg("copy URL failed") | |
| 250 | + | return | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | // Show toast on GTK main thread | |
| 254 | + | var cb glib.SourceFunc | |
| 255 | + | cb = func(_ uintptr) bool { | |
| 256 | + | d.wsCoord.ShowToastOnActivePane(ctx, "URL copied", component.ToastSuccess) | |
| 257 | + | return false | |
| 258 | + | } | |
| 259 | + | glib.IdleAdd(&cb, 0) | |
| 260 | + | }() | |
| 261 | + | ||
| 262 | + | return nil | |
| 263 | + | } | |
| @@ -6,17 +6,20 @@ import ( | |||||
| 6 | "github.com/bnema/dumber/internal/application/usecase" | 6 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 7 | "github.com/bnema/dumber/internal/domain/entity" | 7 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 8 | "github.com/bnema/dumber/internal/logging" | 8 | "github.com/bnema/dumber/internal/logging" | ||
| 9 | + | "github.com/bnema/dumber/internal/ui/component" | |||
| 9 | "github.com/bnema/dumber/internal/ui/coordinator" | 10 | "github.com/bnema/dumber/internal/ui/coordinator" | ||
| 10 | "github.com/bnema/dumber/internal/ui/input" | 11 | "github.com/bnema/dumber/internal/ui/input" | ||
| 12 | + | "github.com/jwijenbergh/puregotk/v4/glib" | |||
| 11 | ) | 13 | ) | ||
| 12 | 14 | ||||
| 13 | // KeyboardDispatcher routes keyboard actions to appropriate coordinators. | 15 | // KeyboardDispatcher routes keyboard actions to appropriate coordinators. | ||
| 14 | type KeyboardDispatcher struct { | 16 | type KeyboardDispatcher struct { | ||
| 15 | - | tabCoord *coordinator.TabCoordinator | 17 | + | tabCoord *coordinator.TabCoordinator |
| 16 | - | wsCoord *coordinator.WorkspaceCoordinator | 18 | + | wsCoord *coordinator.WorkspaceCoordinator |
| 17 | - | navCoord *coordinator.NavigationCoordinator | 19 | + | navCoord *coordinator.NavigationCoordinator |
| 18 | - | zoomUC *usecase.ManageZoomUseCase | 20 | + | zoomUC *usecase.ManageZoomUseCase |
| 19 | - | onQuit func() | 21 | + | copyURLUC *usecase.CopyURLUseCase |
| 22 | + | onQuit func() | |||
| 20 | } | 23 | } | ||
| 21 | 24 | ||||
| 22 | // NewKeyboardDispatcher creates a new KeyboardDispatcher. | 25 | // NewKeyboardDispatcher creates a new KeyboardDispatcher. | ||
| @@ -26,15 +29,17 @@ func NewKeyboardDispatcher( | |||||
| 26 | wsCoord *coordinator.WorkspaceCoordinator, | 29 | wsCoord *coordinator.WorkspaceCoordinator, | ||
| 27 | navCoord *coordinator.NavigationCoordinator, | 30 | navCoord *coordinator.NavigationCoordinator, | ||
| 28 | zoomUC *usecase.ManageZoomUseCase, | 31 | zoomUC *usecase.ManageZoomUseCase, | ||
| 32 | + | copyURLUC *usecase.CopyURLUseCase, | |||
| 29 | ) *KeyboardDispatcher { | 33 | ) *KeyboardDispatcher { | ||
| 30 | log := logging.FromContext(ctx) | 34 | log := logging.FromContext(ctx) | ||
| 31 | log.Debug().Msg("creating keyboard dispatcher") | 35 | log.Debug().Msg("creating keyboard dispatcher") | ||
| 32 | 36 | ||||
| 33 | return &KeyboardDispatcher{ | 37 | return &KeyboardDispatcher{ | ||
| 34 | - | tabCoord: tabCoord, | 38 | + | tabCoord: tabCoord, |
| 35 | - | wsCoord: wsCoord, | 39 | + | wsCoord: wsCoord, |
| 36 | - | navCoord: navCoord, | 40 | + | navCoord: navCoord, |
| 37 | - | zoomUC: zoomUC, | 41 | + | zoomUC: zoomUC, |
| 42 | + | copyURLUC: copyURLUC, | |||
| 38 | } | 43 | } | ||
| 39 | } | 44 | } | ||
| 40 | 45 | ||||
| @@ -136,6 +141,10 @@ func (d *KeyboardDispatcher) Dispatch(ctx context.Context, action input.Action) | |||||
| 136 | case input.ActionToggleFullscreen: | 141 | case input.ActionToggleFullscreen: | ||
| 137 | log.Debug().Msg("toggle fullscreen action (not yet implemented)") | 142 | log.Debug().Msg("toggle fullscreen action (not yet implemented)") | ||
| 138 | 143 | ||||
| 144 | + | // Clipboard | |||
| 145 | + | case input.ActionCopyURL: | |||
| 146 | + | return d.handleCopyURL(ctx) | |||
| 147 | + | ||||
| 139 | // Application | 148 | // Application | ||
| 140 | case input.ActionQuit: | 149 | case input.ActionQuit: | ||
| 141 | if d.onQuit != nil { | 150 | if d.onQuit != nil { | ||
| @@ -212,3 +221,43 @@ func (d *KeyboardDispatcher) handleZoom(ctx context.Context, action string) erro | |||||
| 212 | 221 | ||||
| 213 | return nil | 222 | return nil | ||
| 214 | } | 223 | } | ||
| 224 | + | ||||
| 225 | + | // handleCopyURL copies the active pane's URL to clipboard. | |||
| 226 | + | func (d *KeyboardDispatcher) handleCopyURL(ctx context.Context) error { | |||
| 227 | + | log := logging.FromContext(ctx) | |||
| 228 | + | ||||
| 229 | + | if d.copyURLUC == nil { | |||
| 230 | + | log.Warn().Msg("copy URL use case not available") | |||
| 231 | + | return nil | |||
| 232 | + | } | |||
| 233 | + | ||||
| 234 | + | wv := d.navCoord.ActiveWebView(ctx) | |||
| 235 | + | if wv == nil { | |||
| 236 | + | log.Debug().Msg("no active webview for copy URL") | |||
| 237 | + | return nil | |||
| 238 | + | } | |||
| 239 | + | ||||
| 240 | + | uri := wv.URI() | |||
| 241 | + | if uri == "" { | |||
| 242 | + | log.Debug().Msg("active webview has empty URI") | |||
| 243 | + | return nil | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | // Copy URL in background goroutine | |||
| 247 | + | go func() { | |||
| 248 | + | if err := d.copyURLUC.Copy(ctx, uri); err != nil { | |||
| 249 | + | log.Error().Err(err).Str("uri", uri).Msg("copy URL failed") | |||
| 250 | + | return | |||
| 251 | + | } | |||
| 252 | + | ||||
| 253 | + | // Show toast on GTK main thread | |||
| 254 | + | var cb glib.SourceFunc | |||
| 255 | + | cb = func(_ uintptr) bool { | |||
| 256 | + | d.wsCoord.ShowToastOnActivePane(ctx, "URL copied", component.ToastSuccess) | |||
| 257 | + | return false | |||
| 258 | + | } | |||
| 259 | + | glib.IdleAdd(&cb, 0) | |||
| 260 | + | }() | |||
| 261 | + | ||||
| 262 | + | return nil | |||
| 263 | + | } | |||
M internal/ui/input/shortcuts.go +5 -1
| @@ -96,6 +96,9 @@ const ( | |||
| 96 | 96 | ActionOpenDevTools Action = "open_devtools" | |
| 97 | 97 | ActionToggleFullscreen Action = "toggle_fullscreen" | |
| 98 | 98 | ||
| 99 | + | // Clipboard | |
| 100 | + | ActionCopyURL Action = "copy_url" | |
| 101 | + | ||
| 99 | 102 | // Application | |
| 100 | 103 | ActionQuit Action = "quit" | |
| 101 | 104 | ) | |
| @@ -171,7 +174,7 @@ func (s *ShortcutSet) buildGlobalShortcuts(ctx context.Context, cfg *config.Work | |||
| 171 | 174 | // Standard browser shortcuts (hardcoded defaults) | |
| 172 | 175 | s.Global[KeyBinding{uint(gdk.KEY_l), ModCtrl}] = ActionOpenOmnibox | |
| 173 | 176 | s.Global[KeyBinding{uint(gdk.KEY_r), ModCtrl}] = ActionReload | |
| 174 | - | s.Global[KeyBinding{uint(gdk.KEY_r), ModCtrl | ModShift}] = ActionHardReload | |
| 177 | + | s.Global[KeyBinding{uint(gdk.KEY_R), ModCtrl | ModShift}] = ActionHardReload | |
| 175 | 178 | s.Global[KeyBinding{uint(gdk.KEY_F5), ModNone}] = ActionReload | |
| 176 | 179 | s.Global[KeyBinding{uint(gdk.KEY_F5), ModCtrl}] = ActionHardReload | |
| 177 | 180 | s.Global[KeyBinding{uint(gdk.KEY_F12), ModNone}] = ActionOpenDevTools | |
| @@ -183,6 +186,7 @@ func (s *ShortcutSet) buildGlobalShortcuts(ctx context.Context, cfg *config.Work | |||
| 183 | 186 | s.Global[KeyBinding{uint(gdk.KEY_0), ModCtrl}] = ActionZoomReset | |
| 184 | 187 | s.Global[KeyBinding{uint(gdk.KEY_q), ModCtrl}] = ActionQuit | |
| 185 | 188 | s.Global[KeyBinding{uint(gdk.KEY_F11), ModNone}] = ActionToggleFullscreen | |
| 189 | + | s.Global[KeyBinding{uint(gdk.KEY_C), ModCtrl | ModShift}] = ActionCopyURL | |
| 186 | 190 | ||
| 187 | 191 | // Pane navigation (Alt+HJKL like vim) | |
| 188 | 192 | s.Global[KeyBinding{uint(gdk.KEY_h), ModAlt}] = ActionFocusLeft | |
| @@ -96,6 +96,9 @@ const ( | |||||
| 96 | ActionOpenDevTools Action = "open_devtools" | 96 | ActionOpenDevTools Action = "open_devtools" | ||
| 97 | ActionToggleFullscreen Action = "toggle_fullscreen" | 97 | ActionToggleFullscreen Action = "toggle_fullscreen" | ||
| 98 | 98 | ||||
| 99 | + | // Clipboard | |||
| 100 | + | ActionCopyURL Action = "copy_url" | |||
| 101 | + | ||||
| 99 | // Application | 102 | // Application | ||
| 100 | ActionQuit Action = "quit" | 103 | ActionQuit Action = "quit" | ||
| 101 | ) | 104 | ) | ||
| @@ -171,7 +174,7 @@ func (s *ShortcutSet) buildGlobalShortcuts(ctx context.Context, cfg *config.Work | |||||
| 171 | // Standard browser shortcuts (hardcoded defaults) | 174 | // Standard browser shortcuts (hardcoded defaults) | ||
| 172 | s.Global[KeyBinding{uint(gdk.KEY_l), ModCtrl}] = ActionOpenOmnibox | 175 | s.Global[KeyBinding{uint(gdk.KEY_l), ModCtrl}] = ActionOpenOmnibox | ||
| 173 | s.Global[KeyBinding{uint(gdk.KEY_r), ModCtrl}] = ActionReload | 176 | s.Global[KeyBinding{uint(gdk.KEY_r), ModCtrl}] = ActionReload | ||
| 174 | - | s.Global[KeyBinding{uint(gdk.KEY_r), ModCtrl | ModShift}] = ActionHardReload | 177 | + | s.Global[KeyBinding{uint(gdk.KEY_R), ModCtrl | ModShift}] = ActionHardReload |
| 175 | s.Global[KeyBinding{uint(gdk.KEY_F5), ModNone}] = ActionReload | 178 | s.Global[KeyBinding{uint(gdk.KEY_F5), ModNone}] = ActionReload | ||
| 176 | s.Global[KeyBinding{uint(gdk.KEY_F5), ModCtrl}] = ActionHardReload | 179 | s.Global[KeyBinding{uint(gdk.KEY_F5), ModCtrl}] = ActionHardReload | ||
| 177 | s.Global[KeyBinding{uint(gdk.KEY_F12), ModNone}] = ActionOpenDevTools | 180 | s.Global[KeyBinding{uint(gdk.KEY_F12), ModNone}] = ActionOpenDevTools | ||
| @@ -183,6 +186,7 @@ func (s *ShortcutSet) buildGlobalShortcuts(ctx context.Context, cfg *config.Work | |||||
| 183 | s.Global[KeyBinding{uint(gdk.KEY_0), ModCtrl}] = ActionZoomReset | 186 | s.Global[KeyBinding{uint(gdk.KEY_0), ModCtrl}] = ActionZoomReset | ||
| 184 | s.Global[KeyBinding{uint(gdk.KEY_q), ModCtrl}] = ActionQuit | 187 | s.Global[KeyBinding{uint(gdk.KEY_q), ModCtrl}] = ActionQuit | ||
| 185 | s.Global[KeyBinding{uint(gdk.KEY_F11), ModNone}] = ActionToggleFullscreen | 188 | s.Global[KeyBinding{uint(gdk.KEY_F11), ModNone}] = ActionToggleFullscreen | ||
| 189 | + | s.Global[KeyBinding{uint(gdk.KEY_C), ModCtrl | ModShift}] = ActionCopyURL | |||
| 186 | 190 | ||||
| 187 | // Pane navigation (Alt+HJKL like vim) | 191 | // Pane navigation (Alt+HJKL like vim) | ||
| 188 | s.Global[KeyBinding{uint(gdk.KEY_h), ModAlt}] = ActionFocusLeft | 192 | s.Global[KeyBinding{uint(gdk.KEY_h), ModAlt}] = ActionFocusLeft | ||