a73x

f37ca90d

Fix clipboard send fd lifecycle

a73x   2026-04-09 18:32

Commit message
Fix clipboard send fd lifecycle

src/wayland.zig
Old New
@@ -389,6 +389,29 @@ const ClipboardTransfer = struct {
389 } 389 }
390 }; 390 };
391 391
392 fn serveOwnedSelectionRequest(
393 alloc: std.mem.Allocator,
394 text: ?[]const u8,
395 mime_type: []const u8,
396 fd: i32,
397 ) !bool {
398 var handed_off = false;
399 defer {
400 if (!handed_off) _ = c.close(fd);
401 }
402
403 if (text == null) return false;
404 if (!(std.mem.eql(u8, mime_type, "text/plain;charset=utf-8") or
405 std.mem.eql(u8, mime_type, "text/plain"))) return false;
406
407 const transfer = try ClipboardTransfer.init(alloc, text.?, fd);
408 errdefer alloc.free(transfer.payload);
409 const thread = try std.Thread.spawn(.{}, ClipboardTransfer.run, .{transfer});
410 handed_off = true;
411 thread.detach();
412 return true;
413 }
414
392 pub const Clipboard = struct { 415 pub const Clipboard = struct {
393 alloc: std.mem.Allocator, 416 alloc: std.mem.Allocator,
394 display: *wl.Display, 417 display: *wl.Display,
@@ -478,20 +501,15 @@ pub const Clipboard = struct {
478 } 501 }
479 502
480 fn sendOwnedSelection(self: *Clipboard, mime_type: [*:0]const u8, fd: i32) void { 503 fn sendOwnedSelection(self: *Clipboard, mime_type: [*:0]const u8, fd: i32) void {
481 if (!self.owned_selection.canServeMime(std.mem.span(mime_type))) return; 504 _ = serveOwnedSelectionRequest(
482 const text = self.owned_selection.text orelse return; 505 self.alloc,
483 const transfer = ClipboardTransfer.init(self.alloc, text, fd) catch |err| { 506 self.owned_selection.text,
507 std.mem.span(mime_type),
508 fd,
509 ) catch |err| {
484 std.log.err("clipboard transfer setup failed: {any}", .{err}); 510 std.log.err("clipboard transfer setup failed: {any}", .{err});
485 _ = c.close(fd);
486 return; 511 return;
487 }; 512 };
488 const thread = std.Thread.spawn(.{}, ClipboardTransfer.run, .{transfer}) catch |err| {
489 std.log.err("clipboard transfer thread spawn failed: {any}", .{err});
490 self.alloc.free(transfer.payload);
491 _ = c.close(fd);
492 return;
493 };
494 thread.detach();
495 } 513 }
496 }; 514 };
497 515
@@ -985,6 +1003,40 @@ test "writeClipboardTransfer writes payload and closes fd" {
985 try std.testing.expectEqualStrings("clipboard payload", out.items); 1003 try std.testing.expectEqualStrings("clipboard payload", out.items);
986 } 1004 }
987 1005
1006 test "serveOwnedSelectionRequest closes fd for unsupported mime and missing text" {
1007 {
1008 const pipefds = try std.posix.pipe();
1009 defer std.posix.close(pipefds[0]);
1010
1011 try std.testing.expect(!try serveOwnedSelectionRequest(
1012 std.testing.allocator,
1013 null,
1014 "text/plain",
1015 pipefds[1],
1016 ));
1017
1018 var buf: [1]u8 = undefined;
1019 const n = try std.posix.read(pipefds[0], &buf);
1020 try std.testing.expectEqual(@as(usize, 0), n);
1021 }
1022
1023 {
1024 const pipefds = try std.posix.pipe();
1025 defer std.posix.close(pipefds[0]);
1026
1027 try std.testing.expect(!try serveOwnedSelectionRequest(
1028 std.testing.allocator,
1029 "payload",
1030 "text/html",
1031 pipefds[1],
1032 ));
1033
1034 var buf: [1]u8 = undefined;
1035 const n = try std.posix.read(pipefds[0], &buf);
1036 try std.testing.expectEqual(@as(usize, 0), n);
1037 }
1038 }
1039
988 fn wmBaseListener(wm_base: *xdg.WmBase, event: xdg.WmBase.Event, _: *xdg.WmBase) void { 1040 fn wmBaseListener(wm_base: *xdg.WmBase, event: xdg.WmBase.Event, _: *xdg.WmBase) void {
989 switch (event) { 1041 switch (event) {
990 .ping => |p| wm_base.pong(p.serial), 1042 .ping => |p| wm_base.pong(p.serial),