diff --git a/src/Surface.zig b/src/Surface.zig index c9223d0ad..614f40475 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1026,7 +1026,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { return; } - try self.startClipboardRequest(.standard, .{ .osc_52_read = clipboard }); + _ = try self.startClipboardRequest(.standard, .{ .osc_52_read = clipboard }); }, .clipboard_write => |w| switch (w.req) { @@ -4173,7 +4173,7 @@ pub fn mouseButtonCallback( .selection else .standard; - try self.startClipboardRequest(clipboard, .{ .paste = {} }); + _ = try self.startClipboardRequest(clipboard, .{ .paste = {} }); } // Right-click down selects word for context menus. If the apprt @@ -4251,7 +4251,7 @@ pub fn mouseButtonCallback( // request so we need to unlock. self.renderer_state.mutex.unlock(); defer self.renderer_state.mutex.lock(); - try self.startClipboardRequest(.standard, .paste); + _ = try self.startClipboardRequest(.standard, .paste); // We don't need to clear selection because we didn't have // one to begin with. @@ -4266,7 +4266,7 @@ pub fn mouseButtonCallback( // request so we need to unlock. self.renderer_state.mutex.unlock(); defer self.renderer_state.mutex.lock(); - try self.startClipboardRequest(.standard, .paste); + _ = try self.startClipboardRequest(.standard, .paste); }, } @@ -5330,12 +5330,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool return true; }, - .paste_from_clipboard => try self.startClipboardRequest( + .paste_from_clipboard => return try self.startClipboardRequest( .standard, .{ .paste = {} }, ), - .paste_from_selection => try self.startClipboardRequest( + .paste_from_selection => return try self.startClipboardRequest( .selection, .{ .paste = {} }, ), @@ -6049,11 +6049,15 @@ pub fn completeClipboardRequest( /// This starts a clipboard request, with some basic validation. For example, /// an OSC 52 request is not actually requested if OSC 52 is disabled. +/// +/// Returns true if the request was started, false if it was not (e.g., clipboard +/// doesn't contain text for paste requests). This allows performable keybinds +/// to pass through when the action cannot be performed. fn startClipboardRequest( self: *Surface, loc: apprt.Clipboard, req: apprt.ClipboardRequest, -) !void { +) !bool { switch (req) { .paste => {}, // always allowed .osc_52_read => if (self.config.clipboard_read == .deny) { @@ -6061,14 +6065,14 @@ fn startClipboardRequest( "application attempted to read clipboard, but 'clipboard-read' is set to deny", .{}, ); - return; + return false; }, // No clipboard write code paths travel through this function .osc_52_write => unreachable, } - try self.rt_surface.clipboardRequest(loc, req); + return try self.rt_surface.clipboardRequest(loc, req); } fn completeClipboardPaste( diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 64900cef1..a1b6a6e9b 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -652,7 +652,7 @@ pub const Surface = struct { self: *Surface, clipboard_type: apprt.Clipboard, state: apprt.ClipboardRequest, - ) !void { + ) !bool { // We need to allocate to get a pointer to store our clipboard request // so that it is stable until the read_clipboard callback and call // complete_clipboard_request. This sucks but clipboard requests aren't @@ -667,6 +667,10 @@ pub const Surface = struct { @intCast(@intFromEnum(clipboard_type)), state_ptr, ); + + // Embedded apprt can't synchronously check clipboard content types, + // so we always return true to indicate the request was started. + return true; } fn completeClipboardRequest( diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 009ce018d..918e77146 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -73,8 +73,8 @@ pub fn clipboardRequest( self: *Self, clipboard_type: apprt.Clipboard, state: apprt.ClipboardRequest, -) !void { - try self.surface.clipboardRequest( +) !bool { + return try self.surface.clipboardRequest( clipboard_type, state, ); diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index a14d53c32..cb5122314 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -1666,8 +1666,8 @@ pub const Surface = extern struct { self: *Self, clipboard_type: apprt.Clipboard, state: apprt.ClipboardRequest, - ) !void { - try Clipboard.request( + ) !bool { + return try Clipboard.request( self, clipboard_type, state, @@ -3623,16 +3623,30 @@ const Clipboard = struct { /// Request data from the clipboard (read the clipboard). This /// completes asynchronously and will call the `completeClipboardRequest` /// core surface API when done. + /// + /// Returns true if the request was started, false if the clipboard + /// doesn't contain text (allowing performable keybinds to pass through). pub fn request( self: *Surface, clipboard_type: apprt.Clipboard, state: apprt.ClipboardRequest, - ) Allocator.Error!void { + ) Allocator.Error!bool { // Get our requested clipboard const clipboard = get( self.private().gl_area.as(gtk.Widget), clipboard_type, - ) orelse return; + ) orelse return false; + + // For paste requests, check if clipboard has text format available. + // This is a synchronous check that allows performable keybinds to + // pass through when the clipboard contains non-text content (e.g., images). + if (state == .paste) { + const formats = clipboard.getFormats(); + if (formats.containGtype(gobject.ext.types.string) == 0) { + log.debug("clipboard has no text format, not starting paste request", .{}); + return false; + } + } // Allocate our userdata const alloc = Application.default().allocator(); @@ -3652,6 +3666,8 @@ const Clipboard = struct { clipboardReadText, ud, ); + + return true; } /// Paste explicit text directly into the surface, regardless of the