From c3acbf1a4956ba7c6409b34036477c9fa957e874 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 22 Jul 2025 07:13:14 -0700 Subject: [PATCH] apprt/gtk-ng: clipboard read --- src/apprt/gtk-ng/Surface.zig | 7 +- src/apprt/gtk-ng/class/surface.zig | 131 +++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 3 deletions(-) diff --git a/src/apprt/gtk-ng/Surface.zig b/src/apprt/gtk-ng/Surface.zig index e25ec48ee..1ee21bbc6 100644 --- a/src/apprt/gtk-ng/Surface.zig +++ b/src/apprt/gtk-ng/Surface.zig @@ -73,9 +73,10 @@ pub fn clipboardRequest( clipboard_type: apprt.Clipboard, state: apprt.ClipboardRequest, ) !void { - _ = self; - _ = clipboard_type; - _ = state; + try self.surface.clipboardRequest( + clipboard_type, + state, + ); } pub fn setClipboardString( diff --git a/src/apprt/gtk-ng/class/surface.zig b/src/apprt/gtk-ng/class/surface.zig index fd2e6c54b..70d769dbc 100644 --- a/src/apprt/gtk-ng/class/surface.zig +++ b/src/apprt/gtk-ng/class/surface.zig @@ -2,6 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const adw = @import("adw"); const gdk = @import("gdk"); +const gio = @import("gio"); const glib = @import("glib"); const gobject = @import("gobject"); const gtk = @import("gtk"); @@ -690,6 +691,18 @@ pub const Surface = extern struct { return env; } + pub fn clipboardRequest( + self: *Self, + clipboard_type: apprt.Clipboard, + state: apprt.ClipboardRequest, + ) !void { + try Clipboard.request( + self, + clipboard_type, + state, + ); + } + //--------------------------------------------------------------- // Virtual Methods @@ -1903,3 +1916,121 @@ fn translateMouseButton(button: c_uint) input.MouseButton { else => .unknown, }; } + +/// A namespace for our clipboard-related functions so Surface isn't SO large. +const Clipboard = struct { + /// Get the specific type of clipboard for a widget. + pub fn get( + widget: *gtk.Widget, + clipboard: apprt.Clipboard, + ) ?*gdk.Clipboard { + return switch (clipboard) { + .standard => widget.getClipboard(), + .selection, .primary => widget.getPrimaryClipboard(), + }; + } + + pub fn request( + self: *Surface, + clipboard_type: apprt.Clipboard, + state: apprt.ClipboardRequest, + ) Allocator.Error!void { + // Get our requested clipboard + const clipboard = get( + self.private().gl_area.as(gtk.Widget), + clipboard_type, + ) orelse return; + + // Allocate our userdata + const alloc = Application.default().allocator(); + const ud = try alloc.create(Request); + errdefer alloc.destroy(ud); + ud.* = .{ + // Important: we ref self here so that we can't free memory + // while we have an outstanding clipboard read. + .self = self.ref(), + .state = state, + }; + errdefer self.unref(); + + // Read + clipboard.readTextAsync( + null, + clipboardReadText, + ud, + ); + } + + fn clipboardReadText( + source: ?*gobject.Object, + res: *gio.AsyncResult, + ud: ?*anyopaque, + ) callconv(.c) void { + const clipboard = gobject.ext.cast( + gdk.Clipboard, + source orelse return, + ) orelse return; + const req: *Request = @ptrCast(@alignCast(ud orelse return)); + + const alloc = Application.default().allocator(); + defer alloc.destroy(req); + + const self = req.self; + defer self.unref(); + + var gerr: ?*glib.Error = null; + const cstr_ = clipboard.readTextFinish(res, &gerr); + if (gerr) |err| { + defer err.free(); + log.warn( + "failed to read clipboard err={s}", + .{err.f_message orelse "(no message)"}, + ); + return; + } + const cstr = cstr_ orelse return; + defer glib.free(cstr); + const str = std.mem.sliceTo(cstr, 0); + + const surface = self.private().core_surface orelse return; + surface.completeClipboardRequest( + req.state, + str, + false, + ) catch |err| switch (err) { + error.UnsafePaste, + error.UnauthorizedPaste, + => { + log.warn("unsafe paste, TODO confirmation", .{}); + + // Create a dialog and ask the user if they want to paste anyway. + // ClipboardConfirmationWindow.create( + // self.app, + // str, + // &self.core_surface, + // req.state, + // self.is_secure_input, + // ) catch |window_err| { + // log.warn( + // "failed to create clipboard confirmation window err={}", + // .{window_err}, + // ); + // }; + return; + }, + + else => log.warn( + "failed to complete clipboard request err={}", + .{err}, + ), + }; + } + + /// The request we send as userdata to the clipboard read. + const Request = struct { + /// "Self" is reffed so we can't dispose it until the clipboard + /// read is complete. Callers must unref when done. + self: *Surface, + state: apprt.ClipboardRequest, + }; +};