From 2592e2f33eecf47bc9c63ec33901edeb2f24fae1 Mon Sep 17 00:00:00 2001 From: bbedward Date: Sun, 18 Oct 2026 11:58:19 -0400 Subject: [PATCH] clipboard: serve selection sends off the shared wayland dispatcher fixes #3812 --- core/internal/server/clipboard/manager.go | 26 +++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/core/internal/server/clipboard/manager.go b/core/internal/server/clipboard/manager.go index ec71cbe64..0eb0c353b 100644 --- a/core/internal/server/clipboard/manager.go +++ b/core/internal/server/clipboard/manager.go @@ -1379,6 +1379,21 @@ func (m *Manager) SetClipboardEntry(entry *Entry) error { return nil } +const selectionWriteTimeout = 30 * time.Second + +// Runs off the shared dispatcher: a receiver that never drains its pipe +// would otherwise stall every Wayland event in the daemon. +func writeSelection(fd int, data []byte, timeout time.Duration) { + _ = syscall.SetNonblock(fd, true) + file := os.NewFile(uintptr(fd), "clipboard-pipe") + defer file.Close() + + _ = file.SetWriteDeadline(time.Now().Add(timeout)) + if _, err := file.Write(data); err != nil { + log.Errorf("Failed to write clipboard data: %v", err) + } +} + // takeSelection makes the daemon the selection owner, serving the given // offers until another client claims the clipboard. func (m *Manager) takeSelection(offers []wlclipboard.Offer) { @@ -1406,19 +1421,12 @@ func (m *Manager) takeSelection(offers []wlclipboard.Offer) { } source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) { - fd := e.Fd - defer syscall.Close(fd) - - file := os.NewFile(uintptr(fd), "clipboard-pipe") - defer file.Close() - data, ok := offerData[e.MimeType] if !ok { + syscall.Close(e.Fd) return } - if _, err := file.Write(data); err != nil { - log.Errorf("Failed to write clipboard data: %v", err) - } + go writeSelection(e.Fd, data, selectionWriteTimeout) }) source.SetCancelledHandler(func(e ext_data_control.ExtDataControlSourceV1CancelledEvent) {