From bc2c34eb7f9eea251822d94ea534a1d2de03c5a1 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 9 Jun 2019 11:46:31 +0000 Subject: [PATCH] Add wayland primary selection clipboard support --- Cargo.lock | 72 ++++---- Cargo.toml | 1 + alacritty_terminal/Cargo.toml | 2 +- alacritty_terminal/src/clipboard.rs | 57 +++--- alacritty_terminal/src/input.rs | 10 +- alacritty_terminal/src/term/mod.rs | 2 +- copypasta/Cargo.toml | 25 +++ copypasta/LICENSE.apache2 | 201 +++++++++++++++++++++ copypasta/LICENSE.mit | 25 +++ copypasta/README.md | 34 ++++ copypasta/examples/hello_world.rs | 12 ++ copypasta/examples/primary_selection.rs | 19 ++ copypasta/examples/wayland.rs | 227 ++++++++++++++++++++++++ copypasta/src/common.rs | 24 +++ copypasta/src/lib.rs | 69 +++++++ copypasta/src/nop_clipboard.rs | 42 +++++ copypasta/src/osx_clipboard.rs | 84 +++++++++ copypasta/src/wayland_clipboard.rs | 70 ++++++++ copypasta/src/windows_clipboard.rs | 36 ++++ copypasta/src/x11_clipboard.rs | 72 ++++++++ 20 files changed, 1021 insertions(+), 63 deletions(-) create mode 100644 copypasta/Cargo.toml create mode 100644 copypasta/LICENSE.apache2 create mode 100644 copypasta/LICENSE.mit create mode 100644 copypasta/README.md create mode 100644 copypasta/examples/hello_world.rs create mode 100644 copypasta/examples/primary_selection.rs create mode 100644 copypasta/examples/wayland.rs create mode 100644 copypasta/src/common.rs create mode 100644 copypasta/src/lib.rs create mode 100644 copypasta/src/nop_clipboard.rs create mode 100644 copypasta/src/osx_clipboard.rs create mode 100644 copypasta/src/wayland_clipboard.rs create mode 100644 copypasta/src/windows_clipboard.rs create mode 100644 copypasta/src/x11_clipboard.rs diff --git a/Cargo.lock b/Cargo.lock index 798ab8bd..c2d95f63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,7 +44,7 @@ version = "0.3.2" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "clipboard 0.5.0 (git+https://github.com/chrisduerr/rust-clipboard)", + "copypasta 0.6.0", "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "dunce 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -309,20 +309,6 @@ dependencies = [ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "clipboard" -version = "0.5.0" -source = "git+https://github.com/chrisduerr/rust-clipboard#c0c27997a091e217eb2b6e38d4cb6232a3fe593a" -dependencies = [ - "clipboard-win 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "objc_id 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "smithay-clipboard 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wayland-client 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)", - "x11-clipboard 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "clipboard-win" version = "2.2.0" @@ -397,6 +383,21 @@ dependencies = [ "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "copypasta" +version = "0.6.0" +dependencies = [ + "andrew 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clipboard-win 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "objc_id 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "smithay-client-toolkit 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smithay-clipboard 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-client 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)", + "x11-clipboard 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "core-foundation" version = "0.6.4" @@ -544,7 +545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -682,7 +683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -710,7 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -780,7 +781,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1393,10 +1394,11 @@ dependencies = [ [[package]] name = "named_pipe" -version = "0.4.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1428,7 +1430,7 @@ dependencies = [ [[package]] name = "nix" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1497,7 +1499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2091,7 +2093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2230,9 +2232,10 @@ dependencies = [ [[package]] name = "smithay-clipboard" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "smithay-client-toolkit 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2285,7 +2288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "syn" -version = "0.15.34" +version = "0.15.35" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2300,7 +2303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2692,7 +2695,7 @@ dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "downcast-rs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-commons 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-scanner 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-sys 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2712,7 +2715,7 @@ name = "wayland-commons" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-sys 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2867,7 +2870,7 @@ version = "0.1.0" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "embed-resource 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "named_pipe 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "named_pipe 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2990,7 +2993,6 @@ dependencies = [ "checksum cgl 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "55e7ec0b74fe5897894cbc207092c577e87c52f8a59e8ca8d97ef37551f60a49" "checksum clang-sys 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "939a1a34310b120d26eba35c29475933128b0ec58e24b43327f8dbe6036fc538" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum clipboard 0.5.0 (git+https://github.com/chrisduerr/rust-clipboard)" = "" "checksum clipboard-win 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cmake 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "2ca4386c8954b76a8415b63959337d940d724b336cabd3afe189c2b51a7e1ff0" @@ -3108,10 +3110,10 @@ dependencies = [ "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" -"checksum named_pipe 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad9c443cce91fc3e12f017290db75dde490d685cdaaf508d7159d7cf41f0eb2b" +"checksum named_pipe 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ed10a5ac4f5f7e5d75552b12c1d5d542debca81e573279dd1e4c19fde6efa6d" "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f" +"checksum nix 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "319fffb13b63c0f4ff5a4e1c97566e7e741561ff5d03bf8bbf11653454bbd70b" "checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" @@ -3199,7 +3201,7 @@ dependencies = [ "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" "checksum smithay-client-toolkit 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4899558362a65589b53313935099835acf999740915e134dff20cca7c6a28b" "checksum smithay-client-toolkit 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c172926680325cc0cbb6b08193a66fd88e1ef4a6e92651fd459ca4f5d94c8bc" -"checksum smithay-clipboard 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7944fcb3ecb86f0e39537c1c7501931d3a0f00d3ef1dfc0d4b8996884ac77197" +"checksum smithay-clipboard 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6fe304deea9e0cc01649f7f1b3d620c816510b319b1db8ae219e5c37687669e6" "checksum socket2 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "4e626972d3593207547f14bf5fc9efa4d0e7283deb73fef1dff313dae9ab8878" "checksum spsc-buffer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be6c3f39c37a4283ee4b43d1311c828f2e1fb0541e76ea0cb1a2abd9ef2f5b3b" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" @@ -3207,7 +3209,7 @@ dependencies = [ "checksum stb_truetype 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "69b7df505db8e81d54ff8be4693421e5b543e08214bd8d99eb761fcb4d5668ba" "checksum string 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0bbfb8937e38e34c3444ff00afb28b0811d9554f15c5ad64d12b0308d1d1995" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" +"checksum syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)" = "641e117d55514d6d918490e47102f7e08d096fdde360247e4a10f7a91a8478d3" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7dc4738f2e68ed2855de5ac9cdbe05c9216773ecde4739b2f095002ab03a13ef" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" diff --git a/Cargo.toml b/Cargo.toml index 415ceb9f..2e8fd4db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "alacritty", "alacritty_terminal", + "copypasta", "font", "winpty" ] diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index 032191b8..8cd6c5e7 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -32,7 +32,7 @@ static_assertions = "0.3.0" terminfo = "0.6.1" url = "1.7.1" crossbeam-channel = "0.3.8" -clipboard = { git = "https://github.com/chrisduerr/rust-clipboard" } +copypasta = { path = "../copypasta" } [target.'cfg(unix)'.dependencies] nix = "0.13" diff --git a/alacritty_terminal/src/clipboard.rs b/alacritty_terminal/src/clipboard.rs index a310c991..827559bb 100644 --- a/alacritty_terminal/src/clipboard.rs +++ b/alacritty_terminal/src/clipboard.rs @@ -15,16 +15,19 @@ #[cfg(not(any(target_os = "macos", target_os = "windows")))] use std::ffi::c_void; -use clipboard::nop_clipboard::NopClipboardContext; +use copypasta::nop_clipboard::NopClipboardContext; #[cfg(not(any(target_os = "macos", target_os = "windows")))] -use clipboard::wayland_clipboard::WaylandClipboardContext; +use copypasta::wayland_clipboard::{ + Clipboard as WaylandClipboardClipboard, Primary as WaylandPrimaryClipboard, + WaylandClipboardContext, +}; #[cfg(not(any(target_os = "macos", target_os = "windows")))] -use clipboard::x11_clipboard::{Primary as X11SecondaryClipboard, X11ClipboardContext}; -use clipboard::{ClipboardContext, ClipboardProvider}; +use copypasta::x11_clipboard::{Primary as X11SelectionClipboard, X11ClipboardContext}; +use copypasta::{ClipboardContext, ClipboardProvider}; pub struct Clipboard { - primary: Box, - secondary: Option>, + clipboard: Box, + selection: Option>, } impl Clipboard { @@ -37,41 +40,53 @@ impl Clipboard { pub fn new(display: Option<*mut c_void>) -> Self { if let Some(display) = display { return Self { - primary: unsafe { Box::new(WaylandClipboardContext::new_from_external(display)) }, - secondary: None, + clipboard: unsafe { + Box::new( + WaylandClipboardContext::::new_from_external( + display, + ), + ) + }, + selection: unsafe { + Some(Box::new( + WaylandClipboardContext::::new_from_external( + display, + ), + )) + }, }; } Self { - primary: Box::new(ClipboardContext::new().unwrap()), - secondary: Some(Box::new(X11ClipboardContext::::new().unwrap())), + clipboard: Box::new(ClipboardContext::new().unwrap()), + selection: Some(Box::new(X11ClipboardContext::::new().unwrap())), } } // Use for tests and ref-tests pub fn new_nop() -> Self { - Self { primary: Box::new(NopClipboardContext::new().unwrap()), secondary: None } + Self { clipboard: Box::new(NopClipboardContext::new().unwrap()), selection: None } } } impl Default for Clipboard { fn default() -> Self { - Self { primary: Box::new(ClipboardContext::new().unwrap()), secondary: None } + Self { clipboard: Box::new(ClipboardContext::new().unwrap()), selection: None } } } #[derive(Debug)] pub enum ClipboardType { - Primary, - Secondary, + Clipboard, + Selection, } impl Clipboard { pub fn store(&mut self, ty: ClipboardType, text: impl Into) { - let clipboard = match (ty, &mut self.secondary) { - (ClipboardType::Secondary, Some(provider)) => provider, - (ClipboardType::Secondary, None) => return, - _ => &mut self.primary, + let clipboard = match (ty, &mut self.selection) { + (ClipboardType::Selection, Some(provider)) => provider, + (ClipboardType::Selection, None) => return, + _ => &mut self.clipboard, }; clipboard.set_contents(text.into()).unwrap_or_else(|err| { @@ -80,9 +95,9 @@ impl Clipboard { } pub fn load(&mut self, ty: ClipboardType) -> String { - let clipboard = match (ty, &mut self.secondary) { - (ClipboardType::Secondary, Some(provider)) => provider, - _ => &mut self.primary, + let clipboard = match (ty, &mut self.selection) { + (ClipboardType::Selection, Some(provider)) => provider, + _ => &mut self.clipboard, }; match clipboard.get_contents() { diff --git a/alacritty_terminal/src/input.rs b/alacritty_terminal/src/input.rs index bd1610a2..17d427cd 100644 --- a/alacritty_terminal/src/input.rs +++ b/alacritty_terminal/src/input.rs @@ -280,16 +280,16 @@ impl Action { ctx.write_to_pty(s.clone().into_bytes()) }, Action::Copy => { - ctx.copy_selection(ClipboardType::Primary); + ctx.copy_selection(ClipboardType::Clipboard); }, Action::Paste => { - let text = ctx.terminal_mut().clipboard().load(ClipboardType::Primary); + let text = ctx.terminal_mut().clipboard().load(ClipboardType::Clipboard); self.paste(ctx, &text); }, Action::PasteSelection => { // Only paste if mouse events are not captured by an application if !mouse_mode { - let text = ctx.terminal_mut().clipboard().load(ClipboardType::Secondary); + let text = ctx.terminal_mut().clipboard().load(ClipboardType::Selection); self.paste(ctx, &text); } }, @@ -939,9 +939,9 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { /// Copy text selection. fn copy_selection(&mut self) { if self.save_to_clipboard { - self.ctx.copy_selection(ClipboardType::Primary); + self.ctx.copy_selection(ClipboardType::Clipboard); } - self.ctx.copy_selection(ClipboardType::Secondary); + self.ctx.copy_selection(ClipboardType::Selection); } } diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 2ec0621a..5cfd503d 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -1897,7 +1897,7 @@ impl ansi::Handler for Term { /// Set the clipboard #[inline] fn set_clipboard(&mut self, string: &str) { - self.clipboard.store(ClipboardType::Primary, string); + self.clipboard.store(ClipboardType::Clipboard, string); } #[inline] diff --git a/copypasta/Cargo.toml b/copypasta/Cargo.toml new file mode 100644 index 00000000..229404c3 --- /dev/null +++ b/copypasta/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "copypasta" +version = "0.6.0" +authors = ["Christian Duerr "] +description = "copypasta is a cross-platform library for getting and setting the contents of the OS-level clipboard." +repository = "https://github.com/jwilm/alacritty" +license = "MIT / Apache-2.0" +keywords = ["clipboard"] + +[target.'cfg(windows)'.dependencies] +clipboard-win = "2.1" + +[target.'cfg(target_os = "macos")'.dependencies] +objc = "0.2" +objc_id = "0.1" +objc-foundation = "0.1" + +[target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))'.dependencies] +x11-clipboard = "0.3" +smithay-clipboard = "0.3.0" +wayland-client = { version = "0.22", features = ["dlopen"] } + +[target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))'.dev-dependencies] +andrew = "0.2.1" +smithay-client-toolkit = "0.5" diff --git a/copypasta/LICENSE.apache2 b/copypasta/LICENSE.apache2 new file mode 100644 index 00000000..ad410e11 --- /dev/null +++ b/copypasta/LICENSE.apache2 @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/copypasta/LICENSE.mit b/copypasta/LICENSE.mit new file mode 100644 index 00000000..c8b9dd03 --- /dev/null +++ b/copypasta/LICENSE.mit @@ -0,0 +1,25 @@ +Copyright (c) 2017 Avraham Weinstock + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/copypasta/README.md b/copypasta/README.md new file mode 100644 index 00000000..e2502c0d --- /dev/null +++ b/copypasta/README.md @@ -0,0 +1,34 @@ +# copypasta + +copypasta is a (rust-clipboard)[https://github.com/aweinstock314/rust-clipboard] fork, adding support for the Wayland clipboard. + +rust-clipboard is a cross-platform library for getting and setting the contents of the OS-level clipboard. + +## Example + +```rust +extern crate copypasta; + +use copypasta::ClipboardContext; + +fn example() { + let mut ctx = ClipboardContext::new().unwrap(); + println!("{:?}", ctx.get_contents()); + ctx.set_contents("some string".to_owned()).unwrap(); +} +``` + +## API + +The `ClipboardProvider` trait has the following functions: + +```rust +fn get_contents(&mut self) -> Result>; +fn set_contents(&mut self, String) -> Result<(), Box>; +``` + +`ClipboardContext` is a type alias for one of {`WindowsClipboardContext`, `OSXClipboardContext`, `X11ClipboardContext`, `NopClipboardContext`}, all of which implement `ClipboardProvider`. Which concrete type is chosen for `ClipboardContext` depends on the OS (via conditional compilation). + +## License + +`rust-clipboard` is dual-licensed under MIT and Apache2. diff --git a/copypasta/examples/hello_world.rs b/copypasta/examples/hello_world.rs new file mode 100644 index 00000000..7d637f82 --- /dev/null +++ b/copypasta/examples/hello_world.rs @@ -0,0 +1,12 @@ +extern crate copypasta; + +use copypasta::ClipboardContext; +use copypasta::ClipboardProvider; + +fn main() { + let mut ctx = ClipboardContext::new().unwrap(); + + let the_string = "Hello, world!"; + + ctx.set_contents(the_string.to_owned()).unwrap(); +} diff --git a/copypasta/examples/primary_selection.rs b/copypasta/examples/primary_selection.rs new file mode 100644 index 00000000..5525935e --- /dev/null +++ b/copypasta/examples/primary_selection.rs @@ -0,0 +1,19 @@ +extern crate copypasta; + +#[cfg(target_os = "linux")] +use copypasta::x11_clipboard::{Primary, X11ClipboardContext}; +use copypasta::ClipboardProvider; + +#[cfg(target_os = "linux")] +fn main() { + let mut ctx = X11ClipboardContext::::new().unwrap(); + + let the_string = "Hello, world!"; + + ctx.set_contents(the_string.to_owned()).unwrap(); +} + +#[cfg(not(target_os = "linux"))] +fn main() { + println!("Primary selection is only available under linux!"); +} diff --git a/copypasta/examples/wayland.rs b/copypasta/examples/wayland.rs new file mode 100644 index 00000000..a718a3e8 --- /dev/null +++ b/copypasta/examples/wayland.rs @@ -0,0 +1,227 @@ +#[cfg(any(not(unix), target_os = "macos", target_os = "android", target_os = "emscripten"))] +fn main() { + unimplemented!() +} + +#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))] +fn main() { + wayland::main(); +} + +#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))] +mod wayland { + extern crate andrew; + extern crate copypasta; + extern crate smithay_client_toolkit as sctk; + + use wayland::copypasta::wayland_clipboard::{Clipboard, WaylandClipboardContext}; + use wayland::copypasta::ClipboardProvider; + + use std::io::{Read, Seek, SeekFrom, Write}; + use std::sync::{atomic, Arc, Mutex}; + + use wayland::sctk::keyboard::{map_keyboard_auto, Event as KbEvent, KeyState}; + use wayland::sctk::utils::{DoubleMemPool, MemPool}; + use wayland::sctk::window::{ConceptFrame, Event as WEvent, Window}; + use wayland::sctk::Environment; + + use wayland::sctk::reexports::client::protocol::{wl_shm, wl_surface}; + use wayland::sctk::reexports::client::{Display, NewProxy}; + + use wayland::andrew::shapes::rectangle; + use wayland::andrew::text; + use wayland::andrew::text::fontconfig; + + pub fn main() { + let (display, mut event_queue) = + Display::connect_to_env().expect("Failed to connect to the wayland server."); + let env = Environment::from_display(&*display, &mut event_queue).unwrap(); + + let mut ctx = WaylandClipboardContext::::new(&display); + let cb_contents = Arc::new(Mutex::new(String::new())); + + let seat = env.manager.instantiate_range(2, 6, NewProxy::implement_dummy).unwrap(); + + let need_redraw = Arc::new(atomic::AtomicBool::new(false)); + let need_redraw_clone = need_redraw.clone(); + let cb_contents_clone = cb_contents.clone(); + map_keyboard_auto(&seat, move |event: KbEvent, _| { + if let KbEvent::Key { state: KeyState::Pressed, utf8: Some(text), .. } = event { + if text == " " { + *cb_contents_clone.lock().unwrap() = ctx.get_contents().unwrap(); + need_redraw_clone.store(true, atomic::Ordering::Relaxed) + } else if text == "s" { + ctx.set_contents( + "This is an example text thats been copied to the wayland clipboard :)" + .to_string(), + ) + .unwrap(); + } else if text == "t" { + ctx.set_contents("Alternative text :)".to_string()).unwrap(); + } + } + }) + .unwrap(); + + let mut dimensions = (320u32, 240u32); + let surface = env.compositor.create_surface(NewProxy::implement_dummy).unwrap(); + + let next_action = Arc::new(Mutex::new(None::)); + + let waction = next_action.clone(); + let mut window = + Window::::init_from_env(&env, surface, dimensions, move |evt| { + let mut next_action = waction.lock().unwrap(); + // Keep last event in priority order : Close > Configure > Refresh + let replace = match (&evt, &*next_action) { + (_, &None) + | (_, &Some(WEvent::Refresh)) + | (&WEvent::Configure { .. }, &Some(WEvent::Configure { .. })) + | (&WEvent::Close, _) => true, + _ => false, + }; + if replace { + *next_action = Some(evt); + } + }) + .expect("Failed to create a window !"); + + window.new_seat(&seat); + window.set_title("Clipboard".to_string()); + + let mut pools = + DoubleMemPool::new(&env.shm, || {}).expect("Failed to create a memory pool !"); + + let mut font_data = Vec::new(); + std::fs::File::open( + &fontconfig::FontConfig::new().unwrap().get_regular_family_fonts("sans").unwrap()[0], + ) + .unwrap() + .read_to_end(&mut font_data) + .unwrap(); + + if !env.shell.needs_configure() { + // initial draw to bootstrap on wl_shell + if let Some(pool) = pools.pool() { + redraw(pool, window.surface(), dimensions, &font_data, "".to_string()); + } + window.refresh(); + } + + loop { + match next_action.lock().unwrap().take() { + Some(WEvent::Close) => break, + Some(WEvent::Refresh) => { + window.refresh(); + window.surface().commit(); + }, + Some(WEvent::Configure { new_size, .. }) => { + if let Some((w, h)) = new_size { + window.resize(w, h); + dimensions = (w, h) + } + window.refresh(); + if let Some(pool) = pools.pool() { + redraw( + pool, + window.surface(), + dimensions, + &font_data, + cb_contents.lock().unwrap().clone(), + ); + } + }, + None => {}, + } + + if need_redraw.swap(false, atomic::Ordering::Relaxed) { + if let Some(pool) = pools.pool() { + redraw( + pool, + window.surface(), + dimensions, + &font_data, + cb_contents.lock().unwrap().clone(), + ); + } + window.surface().damage_buffer(0, 0, dimensions.0 as i32, dimensions.1 as i32); + window.surface().commit(); + } + + event_queue.dispatch().unwrap(); + } + } + + fn redraw( + pool: &mut MemPool, + surface: &wl_surface::WlSurface, + dimensions: (u32, u32), + font_data: &[u8], + cb_contents: String, + ) { + let (buf_x, buf_y) = (dimensions.0 as usize, dimensions.1 as usize); + + pool.resize(4 * buf_x * buf_y).expect("Failed to resize the memory pool."); + + let mut buf = vec![0; 4 * buf_x * buf_y]; + let mut canvas = + andrew::Canvas::new(&mut buf, buf_x, buf_y, 4 * buf_x, andrew::Endian::native()); + + let bg = rectangle::Rectangle::new((0, 0), (buf_x, buf_y), None, Some([255, 170, 20, 45])); + canvas.draw(&bg); + + let text_box = rectangle::Rectangle::new( + (buf_x / 30, buf_y / 35), + (buf_x - 2 * (buf_x / 30), (buf_x as f32 / 14.) as usize), + Some((3, [255, 255, 255, 255], rectangle::Sides::ALL, Some(4))), + None, + ); + canvas.draw(&text_box); + + let helper_text = text::Text::new( + (buf_x / 25, buf_y / 30), + [255, 255, 255, 255], + font_data, + buf_x as f32 / 40., + 2.0, + "Press space to draw clipboard contents", + ); + canvas.draw(&helper_text); + + let helper_text = text::Text::new( + (buf_x / 25, buf_y / 15), + [255, 255, 255, 255], + font_data, + buf_x as f32 / 40., + 2.0, + "Press 's' to store example text to clipboard", + ); + canvas.draw(&helper_text); + + for i in (0..cb_contents.len()).step_by(36) { + let content = if cb_contents.len() < i + 36 { + cb_contents[i..].to_string() + } else { + cb_contents[i..i + 36].to_string() + }; + let text = text::Text::new( + (buf_x / 10, buf_y / 8 + (i as f32 * buf_y as f32 / 1000.) as usize), + [255, 255, 255, 255], + font_data, + buf_x as f32 / 40., + 2.0, + content, + ); + canvas.draw(&text); + } + + pool.seek(SeekFrom::Start(0)).unwrap(); + pool.write_all(canvas.buffer).unwrap(); + pool.flush().unwrap(); + + let new_buffer = + pool.buffer(0, buf_x as i32, buf_y as i32, 4 * buf_x as i32, wl_shm::Format::Argb8888); + surface.attach(Some(&new_buffer), 0, 0); + surface.commit(); + } +} diff --git a/copypasta/src/common.rs b/copypasta/src/common.rs new file mode 100644 index 00000000..79cf1fb7 --- /dev/null +++ b/copypasta/src/common.rs @@ -0,0 +1,24 @@ +// Copyright 2016 Avraham Weinstock +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::error::Error; + +// TODO: come up with some platform-agnostic API for richer types +/// Trait for clipboard access +pub trait ClipboardProvider: Send { + /// Method to get the clipboard contents as a String + fn get_contents(&mut self) -> Result>; + /// Method to set the clipboard contents as a String + fn set_contents(&mut self, String) -> Result<(), Box>; +} diff --git a/copypasta/src/lib.rs b/copypasta/src/lib.rs new file mode 100644 index 00000000..64b44219 --- /dev/null +++ b/copypasta/src/lib.rs @@ -0,0 +1,69 @@ +// Copyright 2016 Avraham Weinstock +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![crate_name = "copypasta"] +#![crate_type = "lib"] +#![crate_type = "dylib"] +#![crate_type = "rlib"] + +#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))] +extern crate smithay_clipboard; +#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))] +extern crate wayland_client; +#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))] +extern crate x11_clipboard as x11_clipboard_crate; + +#[cfg(windows)] +extern crate clipboard_win; + +#[cfg(target_os = "macos")] +#[macro_use] +extern crate objc; +#[cfg(target_os = "macos")] +extern crate objc_foundation; +#[cfg(target_os = "macos")] +extern crate objc_id; + +mod common; +pub use common::ClipboardProvider; + +#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))] +pub mod wayland_clipboard; +#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))] +pub mod x11_clipboard; + +#[cfg(windows)] +pub mod windows_clipboard; + +#[cfg(target_os = "macos")] +pub mod osx_clipboard; + +pub mod nop_clipboard; + +#[cfg(all(unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten"))))] +pub type ClipboardContext = x11_clipboard::X11ClipboardContext; +#[cfg(windows)] +pub type ClipboardContext = windows_clipboard::WindowsClipboardContext; +#[cfg(target_os = "macos")] +pub type ClipboardContext = osx_clipboard::OSXClipboardContext; +#[cfg(target_os = "android")] +pub type ClipboardContext = nop_clipboard::NopClipboardContext; // TODO: implement AndroidClipboardContext +#[cfg(not(any( + unix, + windows, + target_os = "macos", + target_os = "android", + target_os = "emscripten" +)))] +pub type ClipboardContext = nop_clipboard::NopClipboardContext; diff --git a/copypasta/src/nop_clipboard.rs b/copypasta/src/nop_clipboard.rs new file mode 100644 index 00000000..bdb79e20 --- /dev/null +++ b/copypasta/src/nop_clipboard.rs @@ -0,0 +1,42 @@ +// Copyright 2016 Avraham Weinstock +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use common::ClipboardProvider; +use std::error::Error; + +pub struct NopClipboardContext; + +impl NopClipboardContext { + pub fn new() -> Result> { + Ok(NopClipboardContext) + } +} + +impl ClipboardProvider for NopClipboardContext { + fn get_contents(&mut self) -> Result> { + println!( + "Attempting to get the contents of the clipboard, which hasn't yet been implemented \ + on this platform." + ); + Ok("".to_string()) + } + + fn set_contents(&mut self, _: String) -> Result<(), Box> { + println!( + "Attempting to set the contents of the clipboard, which hasn't yet been implemented \ + on this platform." + ); + Ok(()) + } +} diff --git a/copypasta/src/osx_clipboard.rs b/copypasta/src/osx_clipboard.rs new file mode 100644 index 00000000..0d65679d --- /dev/null +++ b/copypasta/src/osx_clipboard.rs @@ -0,0 +1,84 @@ +// Copyright 2016 Avraham Weinstock +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use common::*; +use objc::runtime::{Class, Object}; +use objc_foundation::{INSArray, INSObject, INSString}; +use objc_foundation::{NSArray, NSDictionary, NSObject, NSString}; +use objc_id::{Id, Owned}; +use std::error::Error; +use std::mem::transmute; + +pub struct OSXClipboardContext { + pasteboard: Id, +} + +// required to bring NSPasteboard into the path of the class-resolver +#[link(name = "AppKit", kind = "framework")] +extern "C" {} + +impl OSXClipboardContext { + pub fn new() -> Result> { + let cls = Class::get("NSPasteboard").ok_or("Class::get(\"NSPasteboard\")")?; + let pasteboard: *mut Object = unsafe { msg_send![cls, generalPasteboard] }; + if pasteboard.is_null() { + return Err("NSPasteboard#generalPasteboard returned null".into()); + } + let pasteboard: Id = unsafe { Id::from_ptr(pasteboard) }; + Ok(OSXClipboardContext { pasteboard }) + } +} + +impl ClipboardProvider for OSXClipboardContext { + fn get_contents(&mut self) -> Result> { + let string_class: Id = { + let cls: Id = unsafe { Id::from_ptr(class("NSString")) }; + unsafe { transmute(cls) } + }; + let classes: Id> = NSArray::from_vec(vec![string_class]); + let options: Id> = NSDictionary::new(); + let string_array: Id> = unsafe { + let obj: *mut NSArray = + msg_send![self.pasteboard, readObjectsForClasses:&*classes options:&*options]; + if obj.is_null() { + return Err("pasteboard#readObjectsForClasses:options: returned null".into()); + } + Id::from_ptr(obj) + }; + if string_array.count() == 0 { + Err("pasteboard#readObjectsForClasses:options: returned empty".into()) + } else { + Ok(string_array[0].as_str().to_owned()) + } + } + + fn set_contents(&mut self, data: String) -> Result<(), Box> { + let string_array = NSArray::from_vec(vec![NSString::from_str(&data)]); + let _: usize = unsafe { msg_send![self.pasteboard, clearContents] }; + let success: bool = unsafe { msg_send![self.pasteboard, writeObjects: string_array] }; + return if success { + Ok(()) + } else { + Err("NSPasteboard#writeObjects: returned false".into()) + }; + } +} + +// this is a convenience function that both cocoa-rs and +// glutin define, which seems to depend on the fact that +// Option::None has the same representation as a null pointer +#[inline] +pub fn class(name: &str) -> *mut Class { + unsafe { transmute(Class::get(name)) } +} diff --git a/copypasta/src/wayland_clipboard.rs b/copypasta/src/wayland_clipboard.rs new file mode 100644 index 00000000..cb450725 --- /dev/null +++ b/copypasta/src/wayland_clipboard.rs @@ -0,0 +1,70 @@ +// Copyright 2017 Avraham Weinstock +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::error::Error; +use std::ffi::c_void; +use std::marker::PhantomData; + +use smithay_clipboard::WaylandClipboard; +use wayland_client::sys::client::wl_display; +use wayland_client::Display; + +use common::ClipboardProvider; + +pub trait ClipboardType: Send {} + +pub struct Clipboard; +impl ClipboardType for Clipboard {} + +pub struct Primary; +impl ClipboardType for Primary {} + +pub struct WaylandClipboardContext(WaylandClipboard, PhantomData); + +impl WaylandClipboardContext { + /// Create a new clipboard context. + pub fn new(display: &Display) -> Self { + WaylandClipboardContext(WaylandClipboard::new(display), PhantomData) + } + + /// Create a new clipboard context from an external pointer. + pub unsafe fn new_from_external(display: *mut c_void) -> Self { + WaylandClipboardContext( + WaylandClipboard::new_from_external(display as *mut wl_display), + PhantomData, + ) + } +} + +impl ClipboardProvider for WaylandClipboardContext { + fn get_contents(&mut self) -> Result> { + Ok(self.0.load(None)) + } + + fn set_contents(&mut self, data: String) -> Result<(), Box> { + self.0.store(None, data); + Ok(()) + } +} + +impl ClipboardProvider for WaylandClipboardContext { + fn get_contents(&mut self) -> Result> { + Ok(self.0.load_primary(None)) + } + + fn set_contents(&mut self, data: String) -> Result<(), Box> { + self.0.store_primary(None, data); + Ok(()) + } +} diff --git a/copypasta/src/windows_clipboard.rs b/copypasta/src/windows_clipboard.rs new file mode 100644 index 00000000..e35d3130 --- /dev/null +++ b/copypasta/src/windows_clipboard.rs @@ -0,0 +1,36 @@ +// Copyright 2016 Avraham Weinstock +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use clipboard_win::{get_clipboard_string, set_clipboard_string}; + +use common::ClipboardProvider; +use std::error::Error; + +pub struct WindowsClipboardContext; + +impl WindowsClipboardContext { + pub fn new() -> Result> { + Ok(WindowsClipboardContext) + } +} + +impl ClipboardProvider for WindowsClipboardContext { + fn get_contents(&mut self) -> Result> { + Ok(get_clipboard_string()?) + } + + fn set_contents(&mut self, data: String) -> Result<(), Box> { + Ok(set_clipboard_string(&data)?) + } +} diff --git a/copypasta/src/x11_clipboard.rs b/copypasta/src/x11_clipboard.rs new file mode 100644 index 00000000..1b7bdff4 --- /dev/null +++ b/copypasta/src/x11_clipboard.rs @@ -0,0 +1,72 @@ +// Copyright 2017 Avraham Weinstock +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use common::*; +use std::error::Error; +use std::marker::PhantomData; +use std::time::Duration; +use x11_clipboard_crate::xcb::xproto::Atom; +use x11_clipboard_crate::Atoms; +use x11_clipboard_crate::Clipboard as X11Clipboard; + +pub trait Selection: Send { + fn atom(atoms: &Atoms) -> Atom; +} + +pub struct Primary; + +impl Selection for Primary { + fn atom(atoms: &Atoms) -> Atom { + atoms.primary + } +} + +pub struct Clipboard; + +impl Selection for Clipboard { + fn atom(atoms: &Atoms) -> Atom { + atoms.clipboard + } +} + +pub struct X11ClipboardContext(X11Clipboard, PhantomData) +where + S: Selection; + +impl X11ClipboardContext +where + S: Selection, +{ + pub fn new() -> Result, Box> { + Ok(X11ClipboardContext(X11Clipboard::new()?, PhantomData)) + } +} + +impl ClipboardProvider for X11ClipboardContext +where + S: Selection, +{ + fn get_contents(&mut self) -> Result> { + Ok(String::from_utf8(self.0.load( + S::atom(&self.0.getter.atoms), + self.0.getter.atoms.utf8_string, + self.0.getter.atoms.property, + Duration::from_secs(3), + )?)?) + } + + fn set_contents(&mut self, data: String) -> Result<(), Box> { + Ok(self.0.store(S::atom(&self.0.setter.atoms), self.0.setter.atoms.utf8_string, data)?) + } +}