diff --git a/src/Window.zig b/src/Window.zig index f79ccdb..818e043 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -1995,6 +1995,16 @@ fn focusedReview(self: *Window) ?*Review { return tab.view.focusedReview(); } +/// Whether the keyboard focus sits on `widget` or on something inside it. +/// +/// Finer-grained than the `focused*` lookups above, which answer at the +/// granularity of a pane: a pane is one of these even while the focus is in a +/// bar it draws around its content rather than in the content itself. +fn focusIsIn(self: *Window, widget: *gtk.Widget) bool { + const focus = self.window.as(gtk.Window).getFocus() orelse return false; + return focus == widget or focus.isAncestor(widget) != 0; +} + /// Split the visible tab's focused pane, adding a pane of the given kind. fn addPane(self: *Window, kind: View.Kind) void { const tab = self.activeTab() orelse return; @@ -2244,12 +2254,27 @@ fn perform(self: *Window, action: shortcuts.Action) bool { .toggle_sidebar => self.toggleSidebar(), .open_settings => self.openSettings(), - // Only a terminal needs us to encode a paste for it. A web pane has its - // own clipboard handling, so the key is left alone rather than - // swallowed here. + // Ctrl+Shift+V is the terminal's paste chord, and this window means it + // to be *the* paste chord — but nothing below us binds the shifted + // form. GTK's entries and WebKit's pages both paste on plain Ctrl+V + // and neither has a binding for Ctrl+Shift+V, so leaving the key alone + // over a web pane doesn't hand the paste to the page, it drops it. A + // pane that isn't a terminal has to be handed the paste explicitly. + // + // Only when the page itself holds the focus, though: a web pane's + // address bar is an ordinary GTK entry sitting above the view, and + // pasting into the page while the caret is in the address bar would + // put the text somewhere the user isn't looking. .paste => { - const terminal = self.focusedTerminal() orelse return false; - terminal.pasteFrom(.standard); + if (self.focusedTerminal()) |terminal| { + terminal.pasteFrom(.standard); + } else if (self.focusedBrowser()) |browser| { + if (!self.focusIsIn(browser.view.as(gtk.Widget))) return false; + browser.view.executeEditingCommand("Paste"); + } else if (self.focusedReview()) |pane| { + if (!self.focusIsIn(pane.view.as(gtk.Widget))) return false; + pane.view.executeEditingCommand("Paste"); + } else return false; }, // With nothing selected the key is declined rather than swallowed, so a diff --git a/src/webkit.zig b/src/webkit.zig index 3ed2233..a194c49 100644 --- a/src/webkit.zig +++ b/src/webkit.zig @@ -67,6 +67,13 @@ pub const WebView = opaque { extern fn webkit_web_view_stop_loading(*WebView) void; pub const stopLoading = webkit_web_view_stop_loading; + /// Run one of WebKit's editing commands — "Paste", "Copy", "Cut" and the + /// rest — against whatever the page has focused, as if the page's own key + /// binding for it had fired. A command with nothing to act on, such as a + /// paste with no editable field focused, does nothing. + extern fn webkit_web_view_execute_editing_command(*WebView, [*:0]const u8) void; + pub const executeEditingCommand = webkit_web_view_execute_editing_command; + /// The view's find controller, created on first use and owned by the view, /// so this hands back the same object every time and it does not outlive /// the view it came from.