Fix multi-cursor copy/paste on linux (#13523)
Some checks are pending
CI / Check formatting and spelling (push) Waiting to run
CI / (macOS) Run Clippy and tests (push) Waiting to run
CI / (Linux) Run Clippy and tests (push) Waiting to run
CI / (Windows) Run Clippy and tests (push) Waiting to run
CI / Create a macOS bundle (push) Blocked by required conditions
CI / Create a Linux bundle (push) Blocked by required conditions
CI / Create arm64 Linux bundle (push) Blocked by required conditions
Deploy Docs / Deploy Docs (push) Waiting to run

The clipboard library we use for X11 doesn't yet support multiple
formats on the clipboard, so for now we just store this in memory for
the current zed process, as we do for Wayland.

Fixes: #11971

Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
Conrad Irwin 2024-06-25 14:54:52 -06:00 committed by GitHub
parent 5b7e31c075
commit eb914682b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 42 additions and 40 deletions

View file

@ -671,7 +671,7 @@ impl LinuxClient for WaylandClient {
return;
};
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
state.clipboard.set_primary(item.text);
state.clipboard.set_primary(item);
let serial = state.serial_tracker.get(SerialKind::KeyPress);
let data_source = primary_selection_manager.create_source(&state.globals.qh, ());
data_source.offer(state.clipboard.self_mime());
@ -689,7 +689,7 @@ impl LinuxClient for WaylandClient {
return;
};
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
state.clipboard.set(item.text);
state.clipboard.set(item);
let serial = state.serial_tracker.get(SerialKind::KeyPress);
let data_source = data_device_manager.create_data_source(&state.globals.qh, ());
data_source.offer(state.clipboard.self_mime());
@ -699,25 +699,11 @@ impl LinuxClient for WaylandClient {
}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()
.clipboard
.read_primary()
.map(|s| crate::ClipboardItem {
text: s,
metadata: None,
})
self.0.borrow_mut().clipboard.read_primary()
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()
.clipboard
.read()
.map(|s| crate::ClipboardItem {
text: s,
metadata: None,
})
self.0.borrow_mut().clipboard.read()
}
fn active_window(&self) -> Option<AnyWindowHandle> {

View file

@ -9,7 +9,7 @@ use filedescriptor::Pipe;
use wayland_client::{protocol::wl_data_offer::WlDataOffer, Connection};
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
use crate::{platform::linux::platform::read_fd, WaylandClientStatePtr};
use crate::{platform::linux::platform::read_fd, ClipboardItem, WaylandClientStatePtr};
pub(crate) const TEXT_MIME_TYPE: &str = "text/plain;charset=utf-8";
pub(crate) const FILE_LIST_MIME_TYPE: &str = "text/uri-list";
@ -23,13 +23,13 @@ pub(crate) struct Clipboard {
self_mime: String,
// Internal clipboard
contents: Option<String>,
primary_contents: Option<String>,
contents: Option<ClipboardItem>,
primary_contents: Option<ClipboardItem>,
// External clipboard
cached_read: Option<String>,
cached_read: Option<ClipboardItem>,
current_offer: Option<DataOffer<WlDataOffer>>,
cached_primary_read: Option<String>,
cached_primary_read: Option<ClipboardItem>,
current_primary_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
}
@ -89,12 +89,12 @@ impl Clipboard {
}
}
pub fn set(&mut self, text: String) {
self.contents = Some(text);
pub fn set(&mut self, item: ClipboardItem) {
self.contents = Some(item);
}
pub fn set_primary(&mut self, text: String) {
self.primary_contents = Some(text);
pub fn set_primary(&mut self, item: ClipboardItem) {
self.primary_contents = Some(item);
}
pub fn set_offer(&mut self, data_offer: Option<DataOffer<WlDataOffer>>) {
@ -113,17 +113,17 @@ impl Clipboard {
pub fn send(&self, _mime_type: String, fd: OwnedFd) {
if let Some(contents) = &self.contents {
self.send_internal(fd, contents.as_bytes().to_owned());
self.send_internal(fd, contents.text.as_bytes().to_owned());
}
}
pub fn send_primary(&self, _mime_type: String, fd: OwnedFd) {
if let Some(primary_contents) = &self.primary_contents {
self.send_internal(fd, primary_contents.as_bytes().to_owned());
self.send_internal(fd, primary_contents.text.as_bytes().to_owned());
}
}
pub fn read(&mut self) -> Option<String> {
pub fn read(&mut self) -> Option<ClipboardItem> {
let offer = self.current_offer.clone()?;
if let Some(cached) = self.cached_read.clone() {
return Some(cached);
@ -145,8 +145,8 @@ impl Clipboard {
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_read = Some(v.clone());
Some(v)
self.cached_read = Some(ClipboardItem::new(v));
self.cached_read.clone()
}
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");
@ -155,7 +155,7 @@ impl Clipboard {
}
}
pub fn read_primary(&mut self) -> Option<String> {
pub fn read_primary(&mut self) -> Option<ClipboardItem> {
let offer = self.current_primary_offer.clone()?;
if let Some(cached) = self.cached_primary_read.clone() {
return Some(cached);
@ -177,8 +177,8 @@ impl Clipboard {
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_primary_read = Some(v.clone());
Some(v)
self.cached_primary_read = Some(ClipboardItem::new(v.clone()));
self.cached_primary_read.clone()
}
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");

View file

@ -29,9 +29,9 @@ use xkbcommon::xkb as xkbc;
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point,
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput,
Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
use super::{
@ -129,6 +129,7 @@ pub struct X11ClientState {
pub(crate) common: LinuxCommon,
pub(crate) clipboard: x11_clipboard::Clipboard,
pub(crate) clipboard_item: Option<ClipboardItem>,
}
#[derive(Clone)]
@ -413,6 +414,7 @@ impl X11Client {
scroll_y: None,
clipboard,
clipboard_item: None,
})))
}
@ -1097,7 +1099,7 @@ impl LinuxClient for X11Client {
}
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
let state = self.0.borrow_mut();
let mut state = self.0.borrow_mut();
state
.clipboard
.store(
@ -1106,6 +1108,7 @@ impl LinuxClient for X11Client {
item.text().as_bytes(),
)
.ok();
state.clipboard_item.replace(item);
}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
@ -1127,6 +1130,20 @@ impl LinuxClient for X11Client {
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
let state = self.0.borrow_mut();
// if the last copy was from this app, return our cached item
// which has metadata attached.
if state
.clipboard
.setter
.connection
.get_selection_owner(state.clipboard.setter.atoms.clipboard)
.ok()
.and_then(|r| r.reply().ok())
.map(|reply| reply.owner == state.clipboard.setter.window)
.unwrap_or(false)
{
return state.clipboard_item.clone();
}
state
.clipboard
.load(

View file

@ -34,7 +34,6 @@ use std::{
};
use super::{X11Display, XINPUT_MASTER_DEVICE};
x11rb::atom_manager! {
pub XcbAtoms: AtomsCookie {
UTF8_STRING,