Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ pub fn build(b: *std.Build) !void {
\\#include <pointer-constraints-unstable-v1-protocol.c>
\\#include <relative-pointer-unstable-v1-protocol.c>
\\#include <pointer-gestures-unstable-v1-protocol.c>
\\#include <primary-selection-unstable-v1-protocol.c>
\\#include <xdg-activation-v1-protocol.c>
\\#include <wayland-client-protocol.h>
\\#include <viewporter-client-protocol.h>
Expand All @@ -182,6 +183,7 @@ pub fn build(b: *std.Build) !void {
\\#include <pointer-constraints-unstable-v1-client-protocol.h>
\\#include <relative-pointer-unstable-v1-client-protocol.h>
\\#include <pointer-gestures-unstable-v1-client-protocol.h>
\\#include <primary-selection-unstable-v1-client-protocol.h>
\\#include <xdg-activation-v1-client-protocol.h>
\\#include <xkbcommon/xkbcommon.h>
\\#include <xkbcommon/xkbcommon-compose.h>
Expand Down
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
.lazy = true,
},
.wio_unix_headers = .{
.url = "https://github.com/ypsvlq/wio-unix-headers/archive/3bd227608c8fc7e4cd8547e43966b60f81c4caf3.tar.gz",
.hash = "wio_unix_headers-0.0.0-seffXF_FQQC9vaRGpzc1-w6M8YktDc2qzlOgItxv6kWO",
.url = "https://github.com/ypsvlq/wio-unix-headers/archive/779f0441362aa6e860dd182f658df9f271299ff5.tar.gz",
.hash = "wio_unix_headers-0.0.0-seffXMQzQgA4LzxSC2seoGVn7nfVwIIHqYhVsm_Kes-2",
.lazy = true,
},
},
Expand Down
4 changes: 4 additions & 0 deletions src/android.zig
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ pub const Window = struct {
java.env.*.*.CallVoidMethod.?(java.env, java.activity, java.setClipboardText, text_j);
}

pub fn setPrimaryText(_: *Window, _: []const u8) void {}

pub fn getPrimaryText(_: *Window, _: *const fn (?*anyopaque, []const u8) void, _: ?*anyopaque) void {}

pub fn getClipboardText(_: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
const text_j = java.env.*.*.CallObjectMethod.?(java.env, java.activity, java.getClipboardText) orelse return;
defer java.env.*.*.DeleteLocalRef.?(java.env, text_j);
Expand Down
4 changes: 4 additions & 0 deletions src/haiku.zig
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ pub const Window = struct {
wioSetClipboardText(text.ptr, text.len);
}

pub fn setPrimaryText(_: *Window, _: []const u8) void {}

pub fn getPrimaryText(_: *Window, _: *const fn (?*anyopaque, []const u8) void, _: ?*anyopaque) void {}

pub fn getClipboardText(_: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
var len: usize = undefined;
if (wioGetClipboardText(&len)) |ptr| {
Expand Down
4 changes: 4 additions & 0 deletions src/macos.zig
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ pub const Window = struct {
wioSetClipboardText(text.ptr, text.len);
}

pub fn setPrimaryText(_: *Window, _: []const u8) void {}

pub fn getPrimaryText(_: *Window, _: *const fn (?*anyopaque, []const u8) void, _: ?*anyopaque) void {}

pub fn getClipboardText(_: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
var len: usize = undefined;
if (wioGetClipboardText(&len)) |ptr| {
Expand Down
4 changes: 4 additions & 0 deletions src/null.zig
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ pub const Window = struct {
_ = text;
}

pub fn setPrimaryText(_: *Window, _: []const u8) void {}

pub fn getPrimaryText(_: *Window, _: *const fn (?*anyopaque, []const u8) void, _: ?*anyopaque) void {}

pub fn getClipboardText(self: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
_ = self;
_ = clipboardTextFn;
Expand Down
14 changes: 14 additions & 0 deletions src/unix.zig
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,20 @@ pub const Window = union {
}
}

pub fn setPrimaryText(self: *Window, text: []const u8) void {
switch (active) {
.x11 => self.x11.setPrimaryText(text),
.wayland => self.wayland.setPrimaryText(text),
}
}

pub fn getPrimaryText(self: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
switch (active) {
.x11 => self.x11.getPrimaryText(clipboardTextFn, clipboard_text_fn_data),
.wayland => self.wayland.getPrimaryText(clipboardTextFn, clipboard_text_fn_data),
}
}

pub fn getDropData(self: *Window, allocator: std.mem.Allocator) wio.DropData {
switch (active) {
.x11 => return self.x11.getDropData(allocator),
Expand Down
89 changes: 89 additions & 0 deletions src/unix/wayland.zig
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ pub var globals: struct {
data_device: ?*h.wl_data_device = null,
data_offer: ?*h.wl_data_offer = null,
data_source: ?*h.wl_data_source = null,
primary_selection_device_manager: ?*h.zwp_primary_selection_device_manager_v1 = null,
primary_selection_device: ?*h.zwp_primary_selection_device_v1 = null,
primary_offer: ?*h.zwp_primary_selection_offer_v1 = null,
primary_source: ?*h.zwp_primary_selection_source_v1 = null,

xkb: *h.xkb_context = undefined,
keymap: ?*h.xkb_keymap = null,
Expand All @@ -132,6 +136,7 @@ pub var globals: struct {
touch_ids: std.StaticBitSet(256) = .empty,
touch_info: std.AutoHashMapUnmanaged(i32, struct { public_id: u8, window: *Window }) = .empty,
clipboard_text: []const u8 = "",
primary_text: []const u8 = "",

drop: if (build_options.drop) struct {
pending_drag_has_uri: bool = false,
Expand Down Expand Up @@ -217,6 +222,13 @@ pub fn init() !bool {
_ = h.wl_data_device_add_listener(globals.data_device, &data_device_listener, null);
}
}

if (globals.primary_selection_device_manager) |_| {
globals.primary_selection_device = h.zwp_primary_selection_device_manager_v1_get_device(globals.primary_selection_device_manager, globals.seat);
if (globals.primary_selection_device) |_| {
_ = h.zwp_primary_selection_device_v1_add_listener(globals.primary_selection_device, &primary_device_listener, null);
}
}
}

if (build_options.opengl) {
Expand All @@ -234,6 +246,7 @@ pub fn deinit() void {
}

internal.allocator.free(globals.clipboard_text);
internal.allocator.free(globals.primary_text);
globals.touch_info.deinit(internal.allocator);
globals.commit_string.deinit(internal.allocator);
globals.preedit_string.deinit(internal.allocator);
Expand All @@ -260,6 +273,10 @@ fn destroyProxies() void {
if (globals.data_source) |_| h.wl_data_source_destroy(globals.data_source);
if (globals.data_offer) |_| h.wl_data_offer_destroy(globals.data_offer);
if (globals.data_device) |_| h.wl_data_device_destroy(globals.data_device);
if (globals.primary_source) |_| h.zwp_primary_selection_source_v1_destroy(globals.primary_source);
if (globals.primary_offer) |_| h.zwp_primary_selection_offer_v1_destroy(globals.primary_offer);
if (globals.primary_selection_device) |_| h.zwp_primary_selection_device_v1_destroy(globals.primary_selection_device);
if (globals.primary_selection_device_manager) |_| h.zwp_primary_selection_device_manager_v1_destroy(globals.primary_selection_device_manager);
if (globals.gesture_pinch) |_| h.zwp_pointer_gesture_pinch_v1_destroy(globals.gesture_pinch);
if (globals.relative_pointer) |_| h.zwp_relative_pointer_v1_destroy(globals.relative_pointer);
if (globals.cursor_shape_device) |_| h.wp_cursor_shape_device_v1_destroy(globals.cursor_shape_device);
Expand Down Expand Up @@ -559,6 +576,44 @@ pub const Window = struct {
h.wl_data_device_set_selection(globals.data_device, globals.data_source, globals.last_serial);
}

pub fn setPrimaryText(_: *Window, text: []const u8) void {
if (globals.primary_selection_device_manager == null or globals.primary_selection_device == null) return;

internal.allocator.free(globals.primary_text);
globals.primary_text = internal.allocator.dupe(u8, text) catch "";

if (globals.primary_source) |_| h.zwp_primary_selection_source_v1_destroy(globals.primary_source);
globals.primary_source = h.zwp_primary_selection_device_manager_v1_create_source(globals.primary_selection_device_manager);
_ = h.zwp_primary_selection_source_v1_add_listener(globals.primary_source, &primary_source_listener, null);
h.zwp_primary_selection_source_v1_offer(globals.primary_source, "text/plain;charset=utf-8");
h.zwp_primary_selection_device_v1_set_selection(globals.primary_selection_device, globals.primary_source, globals.last_serial);
}

pub fn getPrimaryText(_: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
if (globals.primary_offer == null) return;
var pipe: [2]i32 = undefined;
if (std.c.pipe(&pipe) == -1) return;
defer _ = std.c.close(pipe[0]);
h.zwp_primary_selection_offer_v1_receive(globals.primary_offer, "text/plain;charset=utf-8", pipe[1]);
_ = c.wl_display_roundtrip(globals.display);
_ = std.c.close(pipe[1]);

var buffer: [1024]u8 = undefined;
var text: std.ArrayList(u8) = .empty;
defer text.deinit(internal.allocator);
while (true) {
const count = std.c.read(pipe[0], &buffer, buffer.len);
if (std.c.errno(count) != .SUCCESS) {
return;
} else if (count == 0) {
clipboardTextFn(clipboard_text_fn_data, text.items);
break;
} else {
text.appendSlice(internal.allocator, buffer[0..@intCast(count)]) catch return;
}
}
}

pub fn getClipboardText(_: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
if (globals.data_offer == null) return;
var pipe: [2]i32 = undefined;
Expand Down Expand Up @@ -869,6 +924,8 @@ fn registryGlobal(_: ?*anyopaque, registry: ?*h.wl_registry, name: u32, interfac
globals.pointer_gestures = @ptrCast(h.wl_registry_bind(registry, name, &h.zwp_pointer_gestures_v1_interface, @min(version, 3)));
} else if (std.mem.eql(u8, interface, "wl_data_device_manager")) {
globals.data_device_manager = @ptrCast(h.wl_registry_bind(registry, name, &h.wl_data_device_manager_interface, @min(version, 1)));
} else if (std.mem.eql(u8, interface, "zwp_primary_selection_device_manager_v1")) {
globals.primary_selection_device_manager = @ptrCast(h.wl_registry_bind(registry, name, &h.zwp_primary_selection_device_manager_v1_interface, @min(version, 1)));
} else if (std.mem.eql(u8, interface, "xdg_activation_v1")) {
globals.activation = @ptrCast(h.wl_registry_bind(registry, name, &h.xdg_activation_v1_interface, @min(version, 1)));
}
Expand Down Expand Up @@ -1442,6 +1499,38 @@ fn dataSourceSend(_: ?*anyopaque, _: ?*h.wl_data_source, _: [*c]const u8, fd: i3

fn dataSourceCancelled(_: ?*anyopaque, _: ?*h.wl_data_source) callconv(.c) void {}

const primary_device_listener: h.zwp_primary_selection_device_v1_listener = .{
.data_offer = primaryDeviceDataOffer,
.selection = primaryDeviceSelection,
};

fn primaryDeviceDataOffer(_: ?*anyopaque, _: ?*h.zwp_primary_selection_device_v1, offer: ?*h.zwp_primary_selection_offer_v1) callconv(.c) void {
if (offer) |_| _ = h.zwp_primary_selection_offer_v1_add_listener(offer, &primary_offer_listener, null);
}

fn primaryDeviceSelection(_: ?*anyopaque, _: ?*h.zwp_primary_selection_device_v1, offer: ?*h.zwp_primary_selection_offer_v1) callconv(.c) void {
if (globals.primary_offer) |_| h.zwp_primary_selection_offer_v1_destroy(globals.primary_offer);
globals.primary_offer = offer;
}

const primary_offer_listener: h.zwp_primary_selection_offer_v1_listener = .{
.offer = primaryOfferMime,
};

fn primaryOfferMime(_: ?*anyopaque, _: ?*h.zwp_primary_selection_offer_v1, _: [*c]const u8) callconv(.c) void {}

const primary_source_listener: h.zwp_primary_selection_source_v1_listener = .{
.send = primarySourceSend,
.cancelled = primarySourceCancelled,
};

fn primarySourceSend(_: ?*anyopaque, _: ?*h.zwp_primary_selection_source_v1, _: [*c]const u8, fd: i32) callconv(.c) void {
defer _ = std.c.close(fd);
_ = std.c.write(fd, globals.primary_text.ptr, globals.primary_text.len);
}

fn primarySourceCancelled(_: ?*anyopaque, _: ?*h.zwp_primary_selection_source_v1) callconv(.c) void {}

const activation_token_listener: h.xdg_activation_token_v1_listener = .{
.done = activationTokenDone,
};
Expand Down
25 changes: 21 additions & 4 deletions src/unix/x11.zig
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ pub var globals: struct {
scale: f32 = 1,
xkb_mods: c_uint = 0,
clipboard_text: []const u8 = "",
primary_text: []const u8 = "",
} = .{};

pub fn init() !bool {
Expand Down Expand Up @@ -246,6 +247,7 @@ pub fn init() !bool {

pub fn deinit() void {
internal.allocator.free(globals.clipboard_text);
internal.allocator.free(globals.primary_text);
_ = c.XCloseIM(globals.im);
_ = c.XCloseDisplay(globals.display);
if (build_options.vulkan) globals.libXext.close();
Expand Down Expand Up @@ -596,7 +598,7 @@ pub const Window = struct {
internal.allocator.free(globals.clipboard_text);
globals.clipboard_text = internal.allocator.dupe(u8, text) catch "";
_ = c.XSetSelectionOwner(globals.display, atoms.CLIPBOARD, self.window, h.CurrentTime);
_ = c.XSetSelectionOwner(globals.display, h.XA_PRIMARY, self.window, h.CurrentTime);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding XFlush is probably good here, but I think setting CLIPBOARD should also set PRIMARY. From https://specifications.freedesktop.org/clipboard/latest/:

  • explicit cut/copy commands (i.e. menu items, toolbar buttons) should always set CLIPBOARD to the currently-selected data (i.e. conceptually copy PRIMARY to CLIPBOARD)

  • explicit cut/copy commands should always set both CLIPBOARD and PRIMARY, even when copying doesn’t involve a selection (e.g. a “copy url” -option which explicitly copies an url without the url being selected first)

_ = c.XFlush(globals.display);
}

pub fn getClipboardText(self: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
Expand All @@ -606,6 +608,20 @@ pub const Window = struct {
_ = c.XFlush(globals.display);
}

pub fn setPrimaryText(self: *Window, text: []const u8) void {
internal.allocator.free(globals.primary_text);
globals.primary_text = internal.allocator.dupe(u8, text) catch "";
_ = c.XSetSelectionOwner(globals.display, h.XA_PRIMARY, self.window, h.CurrentTime);
_ = c.XFlush(globals.display);
}

pub fn getPrimaryText(self: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
self.clipboardTextFn = clipboardTextFn;
self.clipboard_text_fn_data = clipboard_text_fn_data;
_ = c.XConvertSelection(globals.display, h.XA_PRIMARY, atoms.UTF8_STRING, atoms.SELECTION, self.window, h.CurrentTime);
_ = c.XFlush(globals.display);
}

pub fn getDropData(self: *Window, allocator: std.mem.Allocator) wio.DropData {
return wio.DropData.dupe(allocator, self.drop.files.items, self.drop.text) catch .{ .files = &.{}, .text = null };
}
Expand Down Expand Up @@ -908,7 +924,8 @@ fn handle(event: *h.XEvent) void {
const targets = [_]h.Atom{ atoms.TARGETS, atoms.UTF8_STRING };
_ = c.XChangeProperty(globals.display, requestor, property, h.XA_ATOM, 32, h.PropModeReplace, @ptrCast(&targets), targets.len);
} else if (target == atoms.UTF8_STRING) {
_ = c.XChangeProperty(globals.display, requestor, property, atoms.UTF8_STRING, 8, h.PropModeReplace, globals.clipboard_text.ptr, std.math.lossyCast(c_int, globals.clipboard_text.len));
const text = if (event.xselectionrequest.selection == h.XA_PRIMARY) globals.primary_text else globals.clipboard_text;
_ = c.XChangeProperty(globals.display, requestor, property, atoms.UTF8_STRING, 8, h.PropModeReplace, text.ptr, std.math.lossyCast(c_int, text.len));
} else {
property = h.None;
}
Expand All @@ -920,10 +937,10 @@ fn handle(event: *h.XEvent) void {
.selection = event.xselectionrequest.selection,
.target = target,
.property = property,
.time = h.CurrentTime,
.time = event.xselectionrequest.time,
},
};
_ = c.XSendEvent(globals.display, requestor, h.True, h.NoEventMask, &reply);
_ = c.XSendEvent(globals.display, requestor, h.False, h.NoEventMask, &reply);

return;
}
Expand Down
4 changes: 4 additions & 0 deletions src/wasm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ pub const Window = struct {
js.setClipboardText(text.ptr, text.len);
}

pub fn setPrimaryText(_: *Window, _: []const u8) void {}

pub fn getPrimaryText(_: *Window, _: *const fn (?*anyopaque, []const u8) void, _: ?*anyopaque) void {}

pub fn getClipboardText(_: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
js.getClipboardText(clipboardTextFn, clipboard_text_fn_data);
}
Expand Down
4 changes: 4 additions & 0 deletions src/win32.zig
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,10 @@ pub const Window = struct {
}
}

pub fn setPrimaryText(_: *Window, _: []const u8) void {}

pub fn getPrimaryText(_: *Window, _: *const fn (?*anyopaque, []const u8) void, _: ?*anyopaque) void {}

pub fn getClipboardText(_: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
if (w.OpenClipboard(null) == 0) return;
defer _ = w.CloseClipboard();
Expand Down
8 changes: 8 additions & 0 deletions src/wio.zig
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ pub const Window = struct {
self.backend.getClipboardText(clipboardTextFn, clipboard_text_fn_data);
}

pub fn setPrimaryText(self: *Window, text: []const u8) void {
self.backend.setPrimaryText(text);
}

pub fn getPrimaryText(self: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void {
self.backend.getPrimaryText(clipboardTextFn, clipboard_text_fn_data);
}

Comment on lines +214 to +221

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than add functions that are only useful on Unix, I think I would rather adapt the existing clipboard API to support the primary selection. The Wayland implementations of getClipboardText/getPrimaryText are also very similar where they might benefit from that.

Something like:

pub const Window = struct {
    pub fn getClipboardText(self: *Window, text: []const u8, options: ClipboardOptions) void;
};

pub const ClipboardOptions = struct {
    /// `.primary` is only supported on X11 and Wayland.
    selection: enum { clipboard, primary } = .clipboard,
};

pub fn getDropData(self: *Window, allocator: std.mem.Allocator) DropData {
assertFeature(.drop);
return self.backend.getDropData(allocator);
Expand Down
Loading