diff --git a/build.zig b/build.zig index 5eae966..96ac932 100644 --- a/build.zig +++ b/build.zig @@ -173,6 +173,7 @@ pub fn build(b: *std.Build) !void { \\#include \\#include \\#include + \\#include \\#include \\#include \\#include @@ -182,6 +183,7 @@ pub fn build(b: *std.Build) !void { \\#include \\#include \\#include + \\#include \\#include \\#include \\#include diff --git a/build.zig.zon b/build.zig.zon index 12fa5f8..7041e86 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -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, }, }, diff --git a/src/android.zig b/src/android.zig index 48c0123..487763c 100644 --- a/src/android.zig +++ b/src/android.zig @@ -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); diff --git a/src/haiku.zig b/src/haiku.zig index a3ac78a..cec63c6 100644 --- a/src/haiku.zig +++ b/src/haiku.zig @@ -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| { diff --git a/src/macos.zig b/src/macos.zig index df79019..c07e1a0 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -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| { diff --git a/src/null.zig b/src/null.zig index 5586181..78781fe 100644 --- a/src/null.zig +++ b/src/null.zig @@ -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; diff --git a/src/unix.zig b/src/unix.zig index 295e2fa..72b2c08 100644 --- a/src/unix.zig +++ b/src/unix.zig @@ -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), diff --git a/src/unix/wayland.zig b/src/unix/wayland.zig index fa77f30..4d32047 100644 --- a/src/unix/wayland.zig +++ b/src/unix/wayland.zig @@ -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, @@ -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, @@ -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) { @@ -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); @@ -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); @@ -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; @@ -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))); } @@ -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, }; diff --git a/src/unix/x11.zig b/src/unix/x11.zig index ef8a0cf..cc38ce6 100644 --- a/src/unix/x11.zig +++ b/src/unix/x11.zig @@ -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 { @@ -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(); @@ -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); + _ = c.XFlush(globals.display); } pub fn getClipboardText(self: *Window, clipboardTextFn: *const fn (?*anyopaque, []const u8) void, clipboard_text_fn_data: ?*anyopaque) void { @@ -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 }; } @@ -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; } @@ -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; } diff --git a/src/wasm.zig b/src/wasm.zig index 4007c3b..d39703b 100644 --- a/src/wasm.zig +++ b/src/wasm.zig @@ -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); } diff --git a/src/win32.zig b/src/win32.zig index 97d1f1a..cd6f253 100644 --- a/src/win32.zig +++ b/src/win32.zig @@ -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(); diff --git a/src/wio.zig b/src/wio.zig index e0c9dac..48117c2 100644 --- a/src/wio.zig +++ b/src/wio.zig @@ -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); + } + pub fn getDropData(self: *Window, allocator: std.mem.Allocator) DropData { assertFeature(.drop); return self.backend.getDropData(allocator);