Merge pull request #266 from mitchellh/primary-clipboard

Primary (selection) clipboard support
This commit is contained in:
Mitchell Hashimoto
2023-08-09 15:14:59 -07:00
committed by GitHub
9 changed files with 278 additions and 87 deletions

View File

@@ -32,6 +32,9 @@ pub const App = struct {
/// Userdata that is passed to all the callbacks.
userdata: AppUD = null,
/// True if the selection clipboard is supported.
supports_selection_clipboard: bool = false,
/// Callback called to wakeup the event loop. This should trigger
/// a full tick of the app loop.
wakeup: *const fn (AppUD) callconv(.C) void,
@@ -47,10 +50,10 @@ pub const App = struct {
/// Read the clipboard value. The return value must be preserved
/// by the host until the next call. If there is no valid clipboard
/// value then this should return null.
read_clipboard: *const fn (SurfaceUD) callconv(.C) ?[*:0]const u8,
read_clipboard: *const fn (SurfaceUD, c_int) callconv(.C) ?[*:0]const u8,
/// Write the clipboard value.
write_clipboard: *const fn (SurfaceUD, [*:0]const u8) callconv(.C) void,
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int) callconv(.C) void,
/// Create a new split view. If the embedder doesn't support split
/// views then this can be null.
@@ -239,13 +242,37 @@ pub const Surface = struct {
);
}
pub fn getClipboardString(self: *const Surface) ![:0]const u8 {
const ptr = self.app.opts.read_clipboard(self.opts.userdata) orelse return "";
pub fn supportsClipboard(
self: *const Surface,
clipboard_type: apprt.Clipboard,
) bool {
return switch (clipboard_type) {
.standard => true,
.selection => self.app.opts.supports_selection_clipboard,
};
}
pub fn getClipboardString(
self: *const Surface,
clipboard_type: apprt.Clipboard,
) ![:0]const u8 {
const ptr = self.app.opts.read_clipboard(
self.opts.userdata,
@intCast(@intFromEnum(clipboard_type)),
) orelse return "";
return std.mem.sliceTo(ptr, 0);
}
pub fn setClipboardString(self: *const Surface, val: [:0]const u8) !void {
self.app.opts.write_clipboard(self.opts.userdata, val.ptr);
pub fn setClipboardString(
self: *const Surface,
val: [:0]const u8,
clipboard_type: apprt.Clipboard,
) !void {
self.app.opts.write_clipboard(
self.opts.userdata,
val.ptr,
@intCast(@intFromEnum(clipboard_type)),
);
}
pub fn setShouldClose(self: *Surface) void {

View File

@@ -25,6 +25,7 @@ const DevMode = @import("../DevMode.zig");
// Get native API access on certain platforms so we can do more customization.
const glfwNative = glfw.Native(.{
.cocoa = builtin.target.isDarwin(),
.x11 = builtin.os.tag == .linux,
});
const log = std.log.scoped(.glfw);
@@ -503,15 +504,39 @@ pub const Surface = struct {
/// Read the clipboard. The windowing system is responsible for allocating
/// a buffer as necessary. This should be a stable pointer until the next
/// time getClipboardString is called.
pub fn getClipboardString(self: *const Surface) ![:0]const u8 {
pub fn getClipboardString(
self: *const Surface,
clipboard_type: apprt.Clipboard,
) ![:0]const u8 {
_ = self;
return glfw.getClipboardString() orelse return glfw.mustGetErrorCode();
return switch (clipboard_type) {
.standard => glfw.getClipboardString() orelse glfw.mustGetErrorCode(),
.selection => selection: {
// Not supported except on Linux
if (comptime builtin.os.tag != .linux) return "";
const raw = glfwNative.getX11SelectionString() orelse
return glfw.mustGetErrorCode();
break :selection std.mem.span(raw);
},
};
}
/// Set the clipboard.
pub fn setClipboardString(self: *const Surface, val: [:0]const u8) !void {
pub fn setClipboardString(
self: *const Surface,
val: [:0]const u8,
clipboard_type: apprt.Clipboard,
) !void {
_ = self;
glfw.setClipboardString(val);
switch (clipboard_type) {
.standard => glfw.setClipboardString(val),
.selection => {
// Not supported except on Linux
if (comptime builtin.os.tag != .linux) return;
glfwNative.setX11SelectionString(val.ptr);
},
}
}
/// The cursor position from glfw directly is in screen coordinates but

View File

@@ -15,6 +15,9 @@ pub const c = @cImport({
@cInclude("gtk/gtk.h");
});
// We need native X11 access to access the primary clipboard.
const glfw_native = glfw.Native(.{ .x11 = true });
/// Compatibility with gobject < 2.74
const G_CONNECT_DEFAULT = if (@hasDecl(c, "G_CONNECT_DEFAULT"))
c.G_CONNECT_DEFAULT
@@ -913,14 +916,25 @@ pub const Surface = struct {
// ));
}
pub fn getClipboardString(self: *Surface) ![:0]const u8 {
const clipboard = c.gtk_widget_get_clipboard(@ptrCast(self.gl_area));
pub fn getClipboardString(
self: *Surface,
clipboard_type: apprt.Clipboard,
) ![:0]const u8 {
const clipboard = getClipboard(@ptrCast(self.gl_area), clipboard_type);
const content = c.gdk_clipboard_get_content(clipboard) orelse {
// On my machine, this NEVER works, so we fallback to glfw's
// implementation...
// implementation... I believe this never works because we need to
// use the async mechanism with GTK but that doesn't play nice
// with what our core expects.
log.debug("no GTK clipboard contents, falling back to glfw", .{});
return glfw.getClipboardString() orelse return glfw.mustGetErrorCode();
return switch (clipboard_type) {
.standard => glfw.getClipboardString() orelse glfw.mustGetErrorCode(),
.selection => value: {
const raw = glfw_native.getX11SelectionString() orelse
return glfw.mustGetErrorCode();
break :value std.mem.span(raw);
},
};
};
c.g_value_unset(&self.clipboard);
@@ -933,12 +947,22 @@ pub const Surface = struct {
return std.mem.sliceTo(ptr, 0);
}
pub fn setClipboardString(self: *const Surface, val: [:0]const u8) !void {
const clipboard = c.gtk_widget_get_clipboard(@ptrCast(self.gl_area));
pub fn setClipboardString(
self: *const Surface,
val: [:0]const u8,
clipboard_type: apprt.Clipboard,
) !void {
const clipboard = getClipboard(@ptrCast(self.gl_area), clipboard_type);
c.gdk_clipboard_set_text(clipboard, val.ptr);
}
fn getClipboard(widget: *c.GtkWidget, clipboard: apprt.Clipboard) ?*c.GdkClipboard {
return switch (clipboard) {
.standard => c.gtk_widget_get_clipboard(widget),
.selection => c.gtk_widget_get_primary_clipboard(widget),
};
}
pub fn getCursorPos(self: *const Surface) !apprt.CursorPos {
return self.cursor_pos;
}

View File

@@ -23,3 +23,11 @@ pub const IMEPos = struct {
x: f64,
y: f64,
};
/// The clipboard type.
///
/// If this is changed, you must also update ghostty.h
pub const Clipboard = enum(u1) {
standard = 0, // ctrl+c/v
selection = 1, // also known as the "primary" clipboard
};