Fix text copy via mouse click on Wayland (#50406)

I've been working on a GPUI application which has a button for copying
text. After starting the app in Wayland in Linux I noticed that when I
click the button to copy text it does not work. After interacting with
other buttons and/or copying text via keyboard shortcuts then my copy
button works.

I thought I was handling something wrong, but then I noticed that Zed
also exhibits this behavior: upon starting Zed if you highlight text and
right-click and select "Copy" then nothing happens. After you interact
with other UI elements and/or keyboard shortcuts then copy buttons seem
to work fine.

This paper cut has been annoying me and with the help of Claude Code I
arrived at this small fix. This solves the problem and this appears to
be the way the serial should be handled for clipboard actions via button
clicks in Wayland, as far as I've been able to learn. Here are a couple
related Wayland documentation pages that I double checked:

1.
https://wayland.app/protocols/wayland#wl_data_device:request:set_selection
2.
https://wayland.freedesktop.org/docs/html/ch04.html#sect-Protocol-data-sharing-devices

- [x] Added ~a solid test coverage and/or~ screenshots from doing manual
testing

    - Here's a screen recording of the behavior without this fix:

[wayland-clipboard-problem.webm](https://github.com/user-attachments/assets/6c7d3b3b-56fe-4083-a011-4906ee9bfbec)

    - Here's a screen recording of the behavior with this fix:

[wayland-clipboard-fix.webm](https://github.com/user-attachments/assets/167ee731-118a-4f67-8489-6cfaca4389ce)

    - Here's a screen recording of the problem in Zed itself:

[wayland-clipboard-problem-zed.webm](https://github.com/user-attachments/assets/e41a40e1-54aa-4b3f-a2c6-f3e90d06d50f)


- [x] Done a self-review taking into account security and performance
aspects
    - None that I could find or think of

- [x] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
    - I don't believe this is applicable...

Release Notes:

- Improved first mouse-driven text copy action on Wayland
This commit is contained in:
Locke Bircher authored and GitHub committed 2026-05-15 14:29:14 +00:00
1 parent 8214d6489c
commit 083d16624c
2 files changed
+15 -13

No files matched your search

@@ -951,9 +951,7 @@ impl LinuxClient for WaylandClient {
};
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
state.clipboard.set_primary(item);
let serial = state
.serial_tracker
.latest_of(&[SerialKind::KeyPress, SerialKind::MousePress]);
let serial = state.serial_tracker.get_latest();
let data_source = primary_selection_manager.create_source(&state.globals.qh, ());
for mime_type in TEXT_MIME_TYPES {
data_source.offer(mime_type.to_string());
@@ -973,9 +971,7 @@ impl LinuxClient for WaylandClient {
};
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
state.clipboard.set(item);
let serial = state
.serial_tracker
.latest_of(&[SerialKind::KeyPress, SerialKind::MousePress]);
let serial = state.serial_tracker.get_latest();
let data_source = data_device_manager.create_data_source(&state.globals.qh, ());
for mime_type in TEXT_MIME_TYPES {
data_source.offer(mime_type.to_string());
+13 -7
View File
@@ -47,15 +47,21 @@ impl SerialTracker {
.unwrap_or(0)
}
/// Returns the latest tracked serial of the provided [`SerialKind`]s
/// Returns the most recent serial across all tracked kinds.
///
/// Will return 0 if not tracked.
pub fn latest_of(&self, kinds: &[SerialKind]) -> u32 {
kinds
.iter()
.filter_map(|kind| self.serials.get(kind))
.max_by_key(|serial_data| serial_data.serial)
/// Wayland compositor serial numbers are monotonically increasing, so the
/// highest value is always the most recently received one. This is the
/// correct serial to use for [`set_selection`] when the triggering event
/// may have been a mouse press rather than a key press: using 0 (the
/// default when a kind has never been seen) causes compositors to silently
/// reject the request.
///
/// Returns 0 only if no serial of any kind has been received yet.
pub fn get_latest(&self) -> u32 {
self.serials
.values()
.map(|serial_data| serial_data.serial)
.max()
.unwrap_or(0)
}
}