From 996f1036fcc86dc95f6402311175929ed3c00909 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 4 Mar 2024 11:00:24 -0500 Subject: [PATCH] linux: clipboard (#8822) Linux clipboard implementation with `copypasta`. Release Notes: - Added linux clipboard support --- Cargo.lock | 158 ++++++++++++++++++ crates/gpui/Cargo.toml | 1 + crates/gpui/src/platform/linux/client.rs | 5 + crates/gpui/src/platform/linux/platform.rs | 19 ++- .../gpui/src/platform/linux/wayland/client.rs | 25 ++- crates/gpui/src/platform/linux/x11/client.rs | 17 ++ 6 files changed, 216 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ba0cd82ac..5e7864f9b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2141,6 +2141,16 @@ dependencies = [ "util", ] +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi 0.3.9", +] + [[package]] name = "clock" version = "0.1.0" @@ -2490,6 +2500,20 @@ dependencies = [ "zed_actions", ] +[[package]] +name = "copypasta" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb85422867ca93da58b7f95fb5c0c10f6183ed6e1ef8841568968a896d3a858" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -4147,6 +4171,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -4325,6 +4359,7 @@ dependencies = [ "cbindgen", "cocoa", "collections", + "copypasta", "core-foundation", "core-graphics", "core-text", @@ -5333,6 +5368,12 @@ dependencies = [ "util", ] +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + [[package]] name = "lazy_static" version = "1.4.0" @@ -5733,6 +5774,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.7.1" @@ -6376,6 +6426,17 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc_exception" version = "0.1.2" @@ -6385,6 +6446,15 @@ dependencies = [ "cc", ] +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "object" version = "0.32.1" @@ -8915,6 +8985,42 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +[[package]] +name = "smithay-client-toolkit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +dependencies = [ + "bitflags 2.4.2", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2 0.9.4", + "rustix 0.38.30", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c091e7354ea8059d6ad99eace06dd13ddeedbb0ac72d40a9a6e7ff790525882d" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + [[package]] name = "smol" version = "1.3.0" @@ -11730,6 +11836,17 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.4.2", + "cursor-icon", + "wayland-backend", +] + [[package]] name = "wayland-cursor" version = "0.31.1" @@ -11753,6 +11870,19 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.4.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-scanner" version = "0.31.1" @@ -11772,6 +11902,7 @@ checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" dependencies = [ "dlib", "log", + "once_cell", "pkg-config", ] @@ -12417,6 +12548,33 @@ dependencies = [ "tap", ] +[[package]] +name = "x11-clipboard" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98785a09322d7446e28a13203d2cae1059a0dd3dfb32cb06d0a225f023d8286" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11rb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" +dependencies = [ + "gethostname", + "rustix 0.38.30", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" + [[package]] name = "xattr" version = "0.2.3" diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index c917f92cbd..de83868b35 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -113,6 +113,7 @@ xkbcommon = { version = "0.7", features = ["wayland", "x11"] } as-raw-xcb-connection = "1" calloop = "0.12.4" calloop-wayland-source = "0.2.0" +copypasta = "0.10.1" oo7 = "0.3.0" [target.'cfg(windows)'.dependencies] diff --git a/crates/gpui/src/platform/linux/client.rs b/crates/gpui/src/platform/linux/client.rs index d74aac7369..b5d154b7a7 100644 --- a/crates/gpui/src/platform/linux/client.rs +++ b/crates/gpui/src/platform/linux/client.rs @@ -1,5 +1,8 @@ +use std::cell::RefCell; use std::rc::Rc; +use copypasta::ClipboardProvider; + use crate::platform::PlatformWindow; use crate::{AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, WindowOptions}; @@ -12,4 +15,6 @@ pub trait Client { options: WindowOptions, ) -> Box; fn set_cursor_style(&self, style: CursorStyle); + fn get_clipboard(&self) -> Rc>; + fn get_primary(&self) -> Rc>; } diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 8db32e7264..016282b444 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -354,12 +354,21 @@ impl Platform for LinuxPlatform { false } - // todo(linux) - fn write_to_clipboard(&self, item: ClipboardItem) {} + fn write_to_clipboard(&self, item: ClipboardItem) { + let clipboard = self.client.get_clipboard(); + clipboard.borrow_mut().set_contents(item.text); + } - // todo(linux) fn read_from_clipboard(&self) -> Option { - None + let clipboard = self.client.get_clipboard(); + let contents = clipboard.borrow_mut().get_contents(); + match contents { + Ok(text) => Some(ClipboardItem { + metadata: None, + text, + }), + _ => None, + } } //todo!(linux) @@ -382,6 +391,8 @@ impl Platform for LinuxPlatform { }) } + //todo!(linux): add trait methods for accessing the primary selection + //todo!(linux) fn read_credentials(&self, url: &str) -> Task)>>> { let url = url.to_string(); diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 8ac18b78c3..e8f20ebc0e 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -6,6 +6,8 @@ use std::time::Duration; use calloop::timer::{TimeoutAction, Timer}; use calloop::LoopHandle; use calloop_wayland_source::WaylandSource; +use copypasta::wayland_clipboard::{create_clipboards_from_external, Clipboard, Primary}; +use copypasta::ClipboardProvider; use wayland_backend::client::ObjectId; use wayland_backend::protocol::WEnum; use wayland_client::globals::{registry_queue_init, GlobalListContents}; @@ -74,6 +76,8 @@ pub(crate) struct CursorState { pub(crate) struct WaylandClientState { client_state_inner: Rc>, cursor_state: Rc>, + clipboard: Rc>, + primary: Rc>, } pub(crate) struct KeyRepeat { @@ -111,6 +115,9 @@ impl WaylandClient { } }); + let display = conn.backend().display_ptr() as *mut std::ffi::c_void; + let (primary, clipboard) = unsafe { create_clipboards_from_external(display) }; + let mut state_inner = Rc::new(RefCell::new(WaylandClientStateInner { compositor: globals.bind(&qh, 1..=1, ()).unwrap(), wm_base: globals.bind(&qh, 1..=1, ()).unwrap(), @@ -152,20 +159,20 @@ impl WaylandClient { let mut state = WaylandClientState { client_state_inner: Rc::clone(&state_inner), cursor_state: Rc::clone(&cursor_state), + clipboard: Rc::new(RefCell::new(clipboard)), + primary: Rc::new(RefCell::new(primary)), }; + let mut state_loop = state.clone(); linux_platform_inner .loop_handle .insert_source(source, move |_, queue, _| { - queue.dispatch_pending(&mut state) + queue.dispatch_pending(&mut state_loop) }) .unwrap(); Self { platform_inner: linux_platform_inner, - state: WaylandClientState { - client_state_inner: state_inner, - cursor_state, - }, + state, qh: Arc::new(qh), } } @@ -265,6 +272,14 @@ impl Client for WaylandClient { let mut cursor_state = self.state.cursor_state.borrow_mut(); cursor_state.cursor_icon_name = cursor_icon_name; } + + fn get_clipboard(&self) -> Rc> { + self.state.clipboard.clone() + } + + fn get_primary(&self) -> Rc> { + self.state.primary.clone() + } } impl Dispatch for WaylandClientState { diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index dd01102276..53fbc8747f 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -6,6 +6,8 @@ use xcb::{x, Xid as _}; use xkbcommon::xkb; use collections::HashMap; +use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext}; +use copypasta::ClipboardProvider; use crate::platform::linux::client::Client; use crate::platform::{LinuxPlatformInner, PlatformWindow}; @@ -28,6 +30,8 @@ struct WindowRef { struct X11ClientState { windows: HashMap, xkb: xkbcommon::xkb::State, + clipboard: Rc>>, + primary: Rc>>, } pub(crate) struct X11Client { @@ -70,6 +74,9 @@ impl X11Client { xkb::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id) }; + let clipboard = X11ClipboardContext::::new().unwrap(); + let primary = X11ClipboardContext::::new().unwrap(); + let client: Rc = Rc::new(Self { platform_inner: inner.clone(), xcb_connection: Rc::clone(&xcb_connection), @@ -78,6 +85,8 @@ impl X11Client { state: RefCell::new(X11ClientState { windows: HashMap::default(), xkb: xkb_state, + clipboard: Rc::new(RefCell::new(clipboard)), + primary: Rc::new(RefCell::new(primary)), }), }); @@ -354,6 +363,14 @@ impl Client for X11Client { //todo!(linux) fn set_cursor_style(&self, _style: CursorStyle) {} + + fn get_clipboard(&self) -> Rc> { + self.state.borrow().clipboard.clone() + } + + fn get_primary(&self) -> Rc> { + self.state.borrow().primary.clone() + } } // Adatpted from: