copypasta-ext-0.4.4/.gitignore000064400000000000000000000000231046102023000144150ustar 00000000000000/target **/*.rs.bk copypasta-ext-0.4.4/.gitlab-ci.yml000064400000000000000000000041211046102023000150640ustar 00000000000000image: "rust:slim" stages: - check - build - test - release # Variable defaults variables: RUST_VERSION: stable RUST_TARGET: x86_64-unknown-linux-gnu # Cache rust/cargo/build artifacts cache: key: "$CI_PIPELINE_ID-$RUST_VERSION" paths: - /usr/local/cargo/registry/ - /usr/local/rustup/toolchains/ - /usr/local/rustup/update-hashes/ - target/ # Install compiler, Xorg and OpenSSL dependencies before_script: - apt-get update - apt-get install -y --no-install-recommends xorg-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev python3 - | rustup install $RUST_VERSION rustup default $RUST_VERSION - | rustc --version cargo --version # Check on stable, beta and nightly .check-base: &check-base stage: check script: - cargo update - cargo check --verbose - cargo check --no-default-features --verbose - cargo check --no-default-features --features osc52 --verbose - cargo check --no-default-features --features x11-bin --verbose - cargo check --no-default-features --features x11-fork --verbose - cargo check --no-default-features --features wayland-bin --verbose check-stable: <<: *check-base check-beta: <<: *check-base variables: RUST_VERSION: beta check-nightly: <<: *check-base variables: RUST_VERSION: nightly check-msrv: <<: *check-base variables: RUST_VERSION: "1.60.0" # Build for release using Rust stable build-x86_64-linux-gnu: stage: build needs: - check-stable script: - cargo build --target=$RUST_TARGET --release --verbose # Run the unit tests through Cargo test-cargo: stage: test needs: - check-stable dependencies: [] script: - apt-get install -y --no-install-recommends libx11-xcb-dev libgl1-mesa-dev - cargo test --verbose # Cargo crate release release-crate: stage: release dependencies: [] only: - /^v(\d+\.)*\d+$/ script: - echo "Creating release crate to publish on crates.io..." - echo $CARGO_TOKEN | cargo login - echo "Publishing crate to crates.io..." - cargo publish --verbose --allow-dirty copypasta-ext-0.4.4/Cargo.toml0000644000000031760000000000100116470ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "copypasta-ext" version = "0.4.4" authors = ["Tim Visée <3a4fb3964f@sinenomine.email>"] build = "build.rs" exclude = ["/.github"] description = "A clipboard library providing useful extensions for copypasta." readme = "README.md" keywords = ["clipboard"] license = "MIT / Apache-2.0" repository = "https://gitlab.com/timvisee/copypasta-ext" [package.metadata.docs.rs] all-features = true [dependencies.base64] version = "0.21" optional = true [dependencies.copypasta] version = "=0.8.2" [features] default = [ "x11-bin", "x11-fork", "wayland-bin", ] osc52 = ["base64"] wayland-bin = ["which"] x11-bin = ["which"] x11-fork = [ "libc", "x11-clipboard", ] [target."cfg(all(unix, not(any(target_os=\"macos\", target_os=\"android\", target_os=\"emscripten\"))))".dependencies.libc] version = "0.2" optional = true [target."cfg(all(unix, not(any(target_os=\"macos\", target_os=\"android\", target_os=\"emscripten\"))))".dependencies.which] version = "4.0" optional = true [target."cfg(all(unix, not(any(target_os=\"macos\", target_os=\"android\", target_os=\"emscripten\"))))".dependencies.x11-clipboard] version = "0.7.0" optional = true copypasta-ext-0.4.4/Cargo.toml.orig000064400000000000000000000017321046102023000153240ustar 00000000000000[package] name = "copypasta-ext" version = "0.4.4" authors = ["Tim Visée <3a4fb3964f@sinenomine.email>"] description = "A clipboard library providing useful extensions for copypasta." repository = "https://gitlab.com/timvisee/copypasta-ext" build = "build.rs" license = "MIT / Apache-2.0" readme = "README.md" keywords = ["clipboard"] exclude = ["/.github"] edition = "2018" [features] default = ["x11-bin", "x11-fork", "wayland-bin"] osc52 = ["base64"] x11-bin = ["which"] x11-fork = ["libc", "x11-clipboard"] wayland-bin = ["which"] [dependencies] copypasta = "=0.8.2" # Feature: osc52 base64 = { version = "0.21", optional = true } [target.'cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))'.dependencies] # Feature: x11-bin which = { version = "4.0", optional = true } # Feature: x11-fork libc = { version = "0.2", optional = true } x11-clipboard = { version = "0.7.0", optional = true } [package.metadata.docs.rs] all-features = true copypasta-ext-0.4.4/LICENSE.apache2000064400000000000000000000260731046102023000147510ustar 00000000000000Apache 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.copypasta-ext-0.4.4/LICENSE.mit000064400000000000000000000020361046102023000142300ustar 00000000000000Copyright (c) 2020 Tim Visée 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. copypasta-ext-0.4.4/README.md000064400000000000000000000100071046102023000137070ustar 00000000000000[![Build status on GitLab CI][gitlab-ci-master-badge]][gitlab-ci-link] [![Newest release on crates.io][crate-version-badge]][crate-link] [![Documentation][docs-badge]][docs] [![Number of downloads on crates.io][crate-download-badge]][crate-link] [![Project license][crate-license-badge]](#License) [crate-download-badge]: https://img.shields.io/crates/d/copypasta-ext.svg [crate-license-badge]: https://img.shields.io/crates/l/copypasta-ext.svg [crate-link]: https://crates.io/crates/copypasta-ext [crate-version-badge]: https://img.shields.io/crates/v/copypasta-ext.svg [docs-badge]: https://docs.rs/copypasta-ext/badge.svg [docs]: https://docs.rs/copypasta-ext [gitlab-ci-link]: https://gitlab.com/timvisee/copypasta-ext/pipelines [gitlab-ci-master-badge]: https://gitlab.com/timvisee/copypasta-ext/badges/master/pipeline.svg # copypasta-ext A clipboard library providing useful extensions for the [`copypasta`][copypasta] library. I had a growing annoyance with `copypasta`, because the clipboard is cleared on the Linux/X11 platform when your application exits as per X11 design. The crate maintainer didn't want to implement workarounds (for valid reasons). This `copypasta-ext` crate provides additional clipboard contexts that solve this, along with a few other additions. Here are some of these additions: - [`X11ForkClipboardProvider`](https://docs.rs/copypasta-ext/*/copypasta_ext/x11_fork/index.html): forks process and sets clipboard, keeps contents after exit - [`X11BinClipboardProvider`](https://docs.rs/copypasta-ext/*/copypasta_ext/x11_bin/index.html): invokes `xclip`/`xsel` to set clipboard, keeps contents after exit - [`Osc52ClipboardContext`](https://docs.rs/copypasta-ext/*/copypasta_ext/osc52/index.html): use OSC 52 escape sequence to set clipboard contents - [`CombinedClipboardProvider`](https://docs.rs/copypasta-ext/*/copypasta_ext/struct.CombinedClipboardContext.html): combine two providers, use different for getting/setting clipboard To guess at runtime what clipboard provider is best used see the [`DisplayServer`](https://docs.rs/copypasta-ext/*/copypasta_ext/display/enum.DisplayServer.html) class. Enable all desired compiler feature flags for clipboard systems to support, and use `DisplayServer::select().try_context()` to obtain a clipboard context. This crate should work with the latest [`copypasta`][copypasta]. Feel free to open an issue or pull request otherwise. The `copypasta` crate is exposed as `copypasta_ext::copypasta`. ## Example Get and set clipboard contents. Keeps contents in X11 clipboard after exit by forking the process. Falls back to standard clipboard provider on non X11 platforms. See [`x11_fork`](https://docs.rs/copypasta-ext/*/copypasta_ext/x11_fork/index.html) module for details. ```rust use copypasta_ext::prelude::*; use copypasta_ext::x11_fork::ClipboardContext; fn main() { let mut ctx = ClipboardContext::new().unwrap(); println!("{:?}", ctx.get_contents()); ctx.set_contents("some string".into()).unwrap(); } ``` Get and set clipboard contents. Keeps contents in X11 clipboard after exit by invoking `xclip`/`xsel`. Falls back to standard clipboard provider on non X11 platforms. See [`x11_bin`](https://docs.rs/copypasta-ext/*/copypasta_ext/x11_bin/index.html) module for details. ```rust use copypasta_ext::prelude::*; use copypasta_ext::x11_bin::ClipboardContext; fn main() { let mut ctx = ClipboardContext::new().unwrap(); println!("{:?}", ctx.get_contents()); ctx.set_contents("some string".into()).unwrap(); } ``` ## Requirements - Rust 1.60 or above (MSRV) - Same requirements as [`copypasta`][copypasta] - Requirements noted in specific clipboard context modules ## Special thanks - to the maintainers/contributors of [`rust-clipboard`][rust-clipboard] and [`copypasta`][copypasta] - to everyone involved in all crate dependencies used ## License This project is dual-licensed under the [MIT](./LICENSE.mit) and [Apache2](./LICENSE.apache2) license. [copypasta]: https://github.com/alacritty/copypasta [rust-clipboard]: https://github.com/aweinstock314/rust-clipboard copypasta-ext-0.4.4/build.rs000064400000000000000000000005451046102023000141030ustar 00000000000000fn main() { #[cfg(all( feature = "x11-bin", unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten")) ))] { // xclip and xsel paths are inserted at compile time println!("cargo:rerun-if-env-changed=XCLIP_PATH"); println!("cargo:rerun-if-env-changed=XSEL_PATH"); } } copypasta-ext-0.4.4/src/combined.rs000064400000000000000000000023331046102023000153500ustar 00000000000000use copypasta::ClipboardProvider; /// Combined, use different clipboard context for getting & setting. /// /// Useful to combine different clipboard contexts to get the best of both worlds. /// /// This may be constructed using helpers such as /// [`X11BinClipboardContext::new_with_x11`][new_with_x11] or /// [`X11BinClipboardContext::with_x11`][with_x11]. /// /// [new_with_x11]: ../copypasta_ext/x11_bin/struct.X11BinClipboardContext.html#method.new_with_x11 /// [with_x11]: ../copypasta_ext/x11_bin/struct.X11BinClipboardContext.html#method.with_x11 pub struct CombinedClipboardContext(pub G, pub S) where G: ClipboardProvider, S: ClipboardProvider; // impl CombinedClipboardContext // where // G: ClipboardProvider, // S: ClipboardProvider, // { // pub fn new() -> Result> { // Ok(Self(G::new()?, S::new()?)) // } // } impl ClipboardProvider for CombinedClipboardContext where G: ClipboardProvider, S: ClipboardProvider, { fn get_contents(&mut self) -> crate::ClipResult { self.0.get_contents() } fn set_contents(&mut self, contents: String) -> crate::ClipResult<()> { self.1.set_contents(contents) } } copypasta-ext-0.4.4/src/display.rs000064400000000000000000000134511046102023000152400ustar 00000000000000//! Display server management. //! //! Provides functionality to select used display server based on the runtime environment. use std::env; use crate::prelude::ClipboardProviderExt; /// A display server type. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] #[non_exhaustive] pub enum DisplayServer { /// The X11 display server. X11, /// The Wayland display server. Wayland, /// The default macOS display server. MacOs, /// The default Windows display server. Windows, /// For TTYs. /// Not an actual display server, but something with a clipboard context to fall back to. Tty, } impl DisplayServer { /// Select current used display server. /// /// This selection is made at runtime. This uses a best effort approach and does not reliably /// select the current display server. Selects any recognized display server regardless of /// compiler feature flag configuration. Defaults to `X11` on Unix if display server could not /// be determined. #[allow(unreachable_code)] pub fn select() -> DisplayServer { #[cfg(target_os = "macos")] return DisplayServer::MacOs; #[cfg(windows)] return DisplayServer::Windows; // Runtime check on Unix if is_wayland() { DisplayServer::Wayland } else if is_x11() { DisplayServer::X11 } else if is_tty() { DisplayServer::Tty } else { // TODO: return Option::None if this isn't X11 either. DisplayServer::X11 } } /// Build clipboard context for display server. /// /// This attempts to build a clipboard context for the selected display server based on what /// contexts are available. /// /// If no compatible context is available or if no compatible context could be initialized, /// `None` is returned. pub fn try_context(self) -> Option> { match self { DisplayServer::X11 => { #[cfg(feature = "x11-fork")] { let context = crate::x11_fork::ClipboardContext::new(); if let Ok(context) = context { return Some(Box::new(context)); } } #[cfg(feature = "x11-bin")] { let context = crate::x11_bin::ClipboardContext::new(); if let Ok(context) = context { return Some(Box::new(context)); } } #[cfg(all( unix, not(any( target_os = "macos", target_os = "android", target_os = "ios", target_os = "emscripten" )) ))] { let context = copypasta::x11_clipboard::X11ClipboardContext::new(); if let Ok(context) = context { return Some(Box::new(context)); } } None } DisplayServer::Wayland => { #[cfg(feature = "wayland-bin")] { let context = crate::wayland_bin::ClipboardContext::new(); if let Ok(context) = context { return Some(Box::new(context)); } } // TODO: this correct? copypasta::ClipboardContext::new() .ok() .map(|c| -> Box { Box::new(c) }) } DisplayServer::MacOs | DisplayServer::Windows => copypasta::ClipboardContext::new() .ok() .map(|c| -> Box { Box::new(c) }), DisplayServer::Tty => { #[cfg(feature = "osc52")] { let context = crate::osc52::ClipboardContext::new(); if let Ok(context) = context { return Some(Box::new(context)); } } None } } } } /// Check whether we're in an X11 environment. /// /// This is a best effort, may be unreliable. /// Checks the `XDG_SESSION_TYPE` and `DISPLAY` environment variables. /// Always returns false on unsupported platforms such as Windows/macOS. /// /// Available regardless of the `x11-*` compiler feature flags. pub fn is_x11() -> bool { if !cfg!(all(unix, not(all(target_os = "macos", target_os = "ios")))) { return false; } match env::var("XDG_SESSION_TYPE").ok().as_deref() { Some("x11") => true, Some("wayland") => false, _ => has_non_empty_env("DISPLAY"), } } /// Check whether we're in a Wayland environment. /// /// This is a best effort, may be unreliable. /// Checks the `XDG_SESSION_TYPE` and `WAYLAND_DISPLAY` environment variables. /// Always returns false on Windows/macOS. /// /// Available regardless of the `wayland-*` compiler feature flags. pub fn is_wayland() -> bool { if !cfg!(all(unix, not(all(target_os = "macos", target_os = "ios")))) { return false; } match env::var("XDG_SESSION_TYPE").ok().as_deref() { Some("wayland") => true, Some("x11") => false, _ => has_non_empty_env("WAYLAND_DISPLAY"), } } /// Check whether we're in a TTY environment. /// /// This is a basic check and only returns true if `XDG_SESSION_TYPE` is set to `tty` explicitly. pub fn is_tty() -> bool { env::var("XDG_SESSION_TYPE").as_deref() == Ok("tty") } /// Check if an environment variable is set and is not empty. #[inline] fn has_non_empty_env(env: &str) -> bool { env::var_os(env).map(|v| !v.is_empty()).unwrap_or(false) } copypasta-ext-0.4.4/src/lib.rs000064400000000000000000000166231046102023000143450ustar 00000000000000//! A clipboard library providing useful extensions for the //! [`copypasta`][copypasta] library. //! //! Here are some of these additions: //! //! - [`X11ForkClipboardProvider`](https://docs.rs/copypasta-ext/*/copypasta_ext/x11_fork/index.html): //! forks process and sets clipboard on X11, keeps contents after exit //! - [`X11BinClipboardProvider`](https://docs.rs/copypasta-ext/*/copypasta_ext/x11_bin/index.html): //! invokes `xclip`/`xsel` to set clipboard on X11, keeps contents after exit //! - [`WaylandBinClipboardProvider`](https://docs.rs/copypasta-ext/*/copypasta_ext/wayland_bin/index.html): //! invokes `wl-copy`/`wl-paste` to set clipboard on Wayland //! - [`Osc52ClipboardContext`](https://docs.rs/copypasta-ext/*/copypasta_ext/osc52/index.html): //! use OSC 52 escape sequence to set clipboard contents //! - [`CombinedClipboardProvider`](https://docs.rs/copypasta-ext/*/copypasta_ext/struct.CombinedClipboardContext.html): //! combine two providers, use different for getting/setting clipboard //! //! # Example //! //! Get and set clipboard contents. Tries to select the correct clipboard context at runtime using //! `try_context`. Useful if you just want quick access to the clipboard, and if you don't want to //! implement any clipboard context selecting logic yourself. //! //! ```rust,no_run //! let mut ctx = copypasta_ext::try_context().expect("failed to get clipboard context"); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! Get and set clipboard contents. Keeps contents in X11 clipboard after exit by forking the //! process (which normally doesn't work with copypasta's X11ClipboardContext). Falls back to //! standard clipboard provider on non X11 platforms. See //! [`x11_fork`](https://docs.rs/copypasta-ext/*/copypasta_ext/x11_fork/index.html) module for //! details. //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_fork::ClipboardContext; //! //! let mut ctx = ClipboardContext::new().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! Get and set clipboard contents. Keeps contents in X11 clipboard after exit by //! invoking `xclip`/`xsel`. Falls back to standard clipboard provider on non X11 //! platforms. See [`x11_bin`](https://docs.rs/copypasta-ext/*/copypasta_ext/x11_bin/index.html) //! module for details. //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_bin::ClipboardContext; //! //! let mut ctx = ClipboardContext::new().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! # Requirements //! //! - Rust 1.47 or above //! - Same requirements as [`copypasta`][copypasta] //! - Requirements noted in specific clipboard context modules //! //! [copypasta]: https://github.com/alacritty/copypasta mod combined; pub mod display; #[cfg(feature = "osc52")] pub mod osc52; #[cfg(all( feature = "wayland-bin", unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten")) ))] pub mod wayland_bin; #[cfg(all( feature = "x11-bin", unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten")) ))] pub mod x11_bin; #[cfg(all( feature = "x11-fork", unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten")) ))] pub mod x11_fork; // Expose platform specific contexts #[cfg(not(all( feature = "wayland-bin", unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten")) )))] pub mod wayland_bin { /// No Wayland binary (`wayland-bin`) support. Fallback to `copypasta::ClipboardContext`. pub type ClipboardContext = copypasta::ClipboardContext; } #[cfg(not(all( feature = "x11-bin", unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten")) )))] pub mod x11_bin { /// No X11 binary (`x11-bin`) support. Fallback to `copypasta::ClipboardContext`. pub type ClipboardContext = copypasta::ClipboardContext; } #[cfg(not(all( feature = "x11-fork", unix, not(any(target_os = "macos", target_os = "android", target_os = "emscripten")) )))] pub mod x11_fork { /// No X11 fork (`x11-fork`) support. Fallback to `copypasta::ClipboardContext`. pub type ClipboardContext = copypasta::ClipboardContext; } use std::error::Error; /// Copypasta result type, for your convenience. pub type ClipResult = Result>; // Re-export pub use combined::CombinedClipboardContext; pub use copypasta; /// Try to get clipboard context. /// /// This attempts to obtain a clipboard context suitable for the current environment. This checks /// at runtime which clipboard contexts are available and which is best suited. If no compatible /// clipboard context is avaiable, or if initializing a context failed, `None` is returned. /// /// Note: this function may be used to automatically select an X11 or Wayland clipboard on Unix /// systems based on the runtime environment. pub fn try_context() -> Option> { display::DisplayServer::select().try_context() } /// Trait prelude. /// /// ```rust /// use copypasta_ext::prelude::*; /// ``` pub mod prelude { pub use super::copypasta::ClipboardProvider; pub use super::ClipboardProviderExt; } /// Extension trait for clipboard access pub trait ClipboardProviderExt: prelude::ClipboardProvider { /// Get related display server. fn display_server(&self) -> Option; /// If this clipboard provider only has a clipboard lifetime of the current binary, rather than /// forever. fn has_bin_lifetime(&self) -> bool { false } } impl ClipboardProviderExt for copypasta::nop_clipboard::NopClipboardContext { fn display_server(&self) -> Option { None } fn has_bin_lifetime(&self) -> bool { false } } /// X11 clipboards have binary lifetime, not infinite. #[cfg(all( unix, not(any( target_os = "macos", target_os = "android", target_os = "ios", target_os = "emscripten" )) ))] impl ClipboardProviderExt for copypasta::x11_clipboard::X11ClipboardContext { fn display_server(&self) -> Option { Some(display::DisplayServer::X11) } fn has_bin_lifetime(&self) -> bool { true } } /// Wayland clipboards have binary lifetime, not infinite. #[cfg(all( unix, not(any( target_os = "macos", target_os = "android", target_os = "ios", target_os = "emscripten" )) ))] impl ClipboardProviderExt for copypasta::wayland_clipboard::Clipboard { fn display_server(&self) -> Option { Some(display::DisplayServer::Wayland) } fn has_bin_lifetime(&self) -> bool { true } } #[cfg(windows)] impl ClipboardProviderExt for copypasta::windows_clipboard::WindowsClipboardContext { fn display_server(&self) -> Option { Some(display::DisplayServer::Windows) } fn has_bin_lifetime(&self) -> bool { false } } #[cfg(target_os = "macos")] impl ClipboardProviderExt for copypasta::osx_clipboard::OSXClipboardContext { fn display_server(&self) -> Option { Some(display::DisplayServer::MacOs) } fn has_bin_lifetime(&self) -> bool { false } } copypasta-ext-0.4.4/src/osc52.rs000064400000000000000000000104671046102023000145320ustar 00000000000000//! OSC 52 escape sequence to set clipboard contents. //! //! This provider can set clipboard contents by outputting a sequence to stdout in supported //! terminals. It uses Xterm escape sequences, OSC 52 to be exact. //! //! Getting clipboard contents is not supported through this context and will error. //! //! ## Benefits //! //! - Keeps contents in clipboard for the terminal lifetime even after your application exists. //! //! ## Drawbacks //! //! - Requires terminal that supports these escape codes. //! - Doesn't catch errors while setting clipboard contents. //! - Cannot get clipboard contents. //! //! # Examples //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_bin::X11BinClipboardContext; //! //! let mut ctx = X11BinClipboardContext::new().unwrap(); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! Use `new_with` to combine with another context such as [`X11ClipboardContext`][X11ClipboardContext] to support getting clipboard contents as well. //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::osc52::Osc52ClipboardContext; //! use copypasta_ext::x11_bin::X11BinClipboardContext; //! //! let mut ctx = Osc52ClipboardContext::new_with(X11BinClipboardContext::new().unwrap()).unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! [X11ClipboardContext]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/struct.X11ClipboardContext.html use std::error::Error as StdError; use std::fmt; use base64::engine::Engine; use crate::combined::CombinedClipboardContext; use crate::display::DisplayServer; use crate::prelude::*; /// Platform specific context. /// /// Alias for `Osc52ClipboardContext` on supported platforms, aliases to standard /// `ClipboardContext` provided by `rust-clipboard` on other platforms. pub type ClipboardContext = Osc52ClipboardContext; /// OSC 52 escape sequence to set clipboard contents. /// /// See module documentation for more information. pub struct Osc52ClipboardContext; impl Osc52ClipboardContext { pub fn new() -> Result> { Ok(Self) } /// Construct combined with another context for getting the clipboard. /// /// This clipboard context only supports setting the clipboard contents. /// You can combine this with the given context to support getting clipboard contents as well /// to get the best of both worlds. pub fn new_with(get: G) -> Result, Box> where G: ClipboardProvider, { Self::new()?.with(get) } /// Combine this context with [`X11ClipboardContext`][X11ClipboardContext]. /// /// This clipboard context only supports setting the clipboard contents. /// You can combine this with the given context to support getting clipboard contents as well /// to get the best of both worlds. pub fn with(self, get: G) -> Result, Box> where G: ClipboardProvider, { Ok(CombinedClipboardContext(get, self)) } } impl ClipboardProvider for Osc52ClipboardContext { fn get_contents(&mut self) -> crate::ClipResult { Err(Error::Unsupported.into()) } fn set_contents(&mut self, contents: String) -> crate::ClipResult<()> { // Use OSC 52 escape sequence to set clipboard through stdout print!( "\x1B]52;c;{}\x07", base64::engine::general_purpose::STANDARD.encode(&contents) ); Ok(()) } } impl ClipboardProviderExt for Osc52ClipboardContext { fn display_server(&self) -> Option { Some(DisplayServer::Tty) } fn has_bin_lifetime(&self) -> bool { false } } /// Represents OSC 52 clipboard related error. #[derive(Debug)] #[non_exhaustive] pub enum Error { /// Getting clipboard contents is not supported. Unsupported, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Unsupported => write!( f, "Getting clipboard contents is not supported through this context" ), } } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { None } } copypasta-ext-0.4.4/src/wayland_bin.rs000064400000000000000000000206471046102023000160670ustar 00000000000000//! Invokes [`wl-copy`/`wl-paste`][wl-clipboard] to access clipboard. //! //! This provider allows setting clipboard contentx when using the Wayland display manager. //! //! When getting or setting the clipboard, the `wl-copy` and `wl-paste` binary is invoked to manage the //! contents. When setting the clipboard contents, these binaries are provided by the //! [wl-clipboard][wl-clipboard] clipboard manager. //! //! The `wl-copy` or `wl-paste` must be in `PATH`. Alternatively the paths of either may be set at //! compile time using the `WL_COPY_PATH` and `WL_PASTE_PATH` environment variables. //! //! Use the provided `ClipboardContext` type alias to use this clipboard context on supported //! platforms, but fall back to the standard clipboard on others. //! //! ## Benefits //! //! - Keeps contents in clipboard even after your application exists. //! //! ## Drawbacks //! //! - Requires `wl-copy` and `wl-paste` binaries from [`wl-clipboard`][wl-clipboard] clipboard manager. //! - Less performant than alternatives due to binary invocation. //! - Set contents may not be immediately available, because they are set in an external binary. //! - May have undefined behaviour if `wl-copy` or `wl-paste` are modified. //! //! # Examples //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::wayland_bin::WaylandBinClipboardContext; //! //! let mut ctx = WaylandBinClipboardContext::new().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! Use `ClipboardContext` alias for better platform compatability: //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::wayland_bin::ClipboardContext; //! //! let mut ctx = ClipboardContext::new().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! [wl-clipboard]: https://github.com/bugaevc/wl-clipboard use std::error::Error as StdError; use std::fmt; use std::io::{Error as IoError, ErrorKind as IoErrorKind, Write}; use std::process::{Command, Stdio}; use std::string::FromUtf8Error; use crate::display::DisplayServer; use crate::prelude::*; /// Platform specific context. /// /// Alias for `WaylandBinClipboardContext` on supported platforms, aliases to standard /// `ClipboardContext` provided by `rust-clipboard` on other platforms. pub type ClipboardContext = WaylandBinClipboardContext; /// Invokes [`wl-clipboard`][wl-clipboard] binaries to access clipboard. /// /// See module documentation for more information. /// /// [wl-clipboard]: https://github.com/bugaevc/wl-clipboard pub struct WaylandBinClipboardContext(ClipboardType); impl WaylandBinClipboardContext { pub fn new() -> crate::ClipResult { Ok(Self(ClipboardType::select())) } } impl ClipboardProvider for WaylandBinClipboardContext { fn get_contents(&mut self) -> crate::ClipResult { Ok(self.0.get()?) } fn set_contents(&mut self, contents: String) -> crate::ClipResult<()> { Ok(self.0.set(&contents)?) } } impl ClipboardProviderExt for WaylandBinClipboardContext { fn display_server(&self) -> Option { Some(DisplayServer::Wayland) } fn has_bin_lifetime(&self) -> bool { false } } /// Available clipboard management binaries. /// /// Invoke `ClipboardType::select()` to select the best variant to use determined at runtime. enum ClipboardType { /// Use `wl-copy` and `wl-paste` from `wl-clipboard`. /// /// May contain a binary path if specified at compile time through the `XCLIP_PATH` variable. WlClipboard(Option, Option), } impl ClipboardType { /// Select the clipboard type to use. pub fn select() -> Self { if option_env!("WL_COPY_PATH").is_some() || option_env!("WL_PASTE_PATH").is_some() { ClipboardType::WlClipboard( option_env!("WL_COPY_PATH") .filter(|p| !p.trim().is_empty()) .map(|p| p.into()), option_env!("WL_PASTE_PATH") .filter(|p| !p.trim().is_empty()) .map(|p| p.into()), ) // TODO: return WlClipboard if wl-copy/wl-paste are found, error otherwise // } else if which("wl-copy").is_ok() || which("wl-paste").is_ok() { // ClipboardType::WlClipboard(None, None) } else { ClipboardType::WlClipboard(None, None) } } /// Get clipboard contents through the selected clipboard type. pub fn get(&self) -> Result { match self { ClipboardType::WlClipboard(_, path) => sys_cmd_get( "wl-paste", &mut Command::new(path.as_deref().unwrap_or("wl-paste")), ), } } /// Set clipboard contents through the selected clipboard type. pub fn set(&self, contents: &str) -> Result<(), Error> { match self { ClipboardType::WlClipboard(path, _) => sys_cmd_set( "wl-copy", &mut Command::new(path.as_deref().unwrap_or("wl-copy")), contents, ), } } } /// Get clipboard contents using a system command. fn sys_cmd_get(bin: &'static str, command: &mut Command) -> Result { // Spawn the command process for getting the clipboard let output = match command.output() { Ok(output) => output, Err(err) => { return Err(match err.kind() { IoErrorKind::NotFound => Error::NoBinary, _ => Error::BinaryIo(bin, err), }); } }; // Check process status code if !output.status.success() { return Err(Error::BinaryStatus(bin, output.status.code().unwrap_or(0))); } // Get and parse output String::from_utf8(output.stdout).map_err(Error::NoUtf8) } /// Set clipboard contents using a system command. fn sys_cmd_set(bin: &'static str, command: &mut Command, contents: &str) -> Result<(), Error> { // Spawn the command process for setting the clipboard let mut process = match command.stdin(Stdio::piped()).stdout(Stdio::null()).spawn() { Ok(process) => process, Err(err) => { return Err(match err.kind() { IoErrorKind::NotFound => Error::NoBinary, _ => Error::BinaryIo(bin, err), }); } }; // Write the contents to the xclip process process .stdin .as_mut() .unwrap() .write_all(contents.as_bytes()) .map_err(|err| Error::BinaryIo(bin, err))?; // Wait for process to exit let status = process.wait().map_err(|err| Error::BinaryIo(bin, err))?; if !status.success() { return Err(Error::BinaryStatus(bin, status.code().unwrap_or(0))); } Ok(()) } /// Represents Wayland binary related error. #[derive(Debug)] #[non_exhaustive] pub enum Error { /// The `wl-copy` or `wl-paste` binary could not be found on the system, required for clipboard support. NoBinary, /// An error occurred while using `wl-copy` or `wl-paste` to manage the clipboard contents. /// This problem probably occurred when starting, or while piping the clipboard contents /// from/to the process. BinaryIo(&'static str, IoError), /// `wl-copy` or `wl-paste` unexpectetly exited with a non-successful status code. BinaryStatus(&'static str, i32), /// The clipboard contents could not be parsed as valid UTF-8. NoUtf8(FromUtf8Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::NoBinary => write!( f, "Could not find wl-copy or wl-paste binary for clipboard support" ), Error::BinaryIo(cmd, err) => { write!(f, "Failed to access clipboard using {}: {}", cmd, err) } Error::BinaryStatus(cmd, code) => write!( f, "Failed to use clipboard, {} exited with status code {}", cmd, code ), Error::NoUtf8(err) => write!( f, "Failed to parse clipboard contents as valid UTF-8: {}", err ), } } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Error::BinaryIo(_, err) => Some(err), Error::NoUtf8(err) => Some(err), _ => None, } } } copypasta-ext-0.4.4/src/x11_bin.rs000064400000000000000000000263261046102023000150410ustar 00000000000000//! Invokes [`xclip`][xclip]/[`xsel`][xsel] to access clipboard. //! //! This provider ensures the clipboard contents you set remain available even after your //! application exists, unlike [`X11ClipboardContext`][X11ClipboardContext]. //! //! When getting or setting the clipboard, the `xclip` or `xsel` binary is invoked to manage the //! contents. When setting the clipboard contents, these binaries internally fork and stay alive //! until the clipboard content changes. //! //! The `xclip` or `xsel` must be in `PATH`. Alternatively the paths of either may be set at //! compile time using the `XCLIP_PATH` and `XSEL_PATH` environment variables. If set, the //! clipboard context will automatically use those. //! //! What binary is used is deterimined at runtime on context creation based on the compile time //! variables and the runtime environment. //! //! Use the provided `ClipboardContext` type alias to use this clipboard context on supported //! platforms, but fall back to the standard clipboard on others. //! //! ## Benefits //! //! - Keeps contents in clipboard even after your application exists. //! //! ## Drawbacks //! //! - Requires [`xclip`][xclip] or [`xsel`][xsel] to be available. //! - Less performant than alternatives due to binary invocation. //! - Set contents may not be immediately available, because they are set in an external binary. //! - May have undefined behaviour if `xclip` or `xsel` are modified. //! //! # Examples //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_bin::X11BinClipboardContext; //! //! let mut ctx = X11BinClipboardContext::new().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! Use `ClipboardContext` alias for better platform compatability: //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_bin::ClipboardContext; //! //! let mut ctx = ClipboardContext::new().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! Use `new_with_x11` to combine with [`X11ClipboardContext`][X11ClipboardContext] for better performance. //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_bin::X11BinClipboardContext; //! //! let mut ctx = X11BinClipboardContext::new_with_x11().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! [X11ClipboardContext]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/struct.X11ClipboardContext.html //! [x11_clipboard]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/index.html //! [xclip]: https://github.com/astrand/xclip //! [xsel]: http://www.vergenet.net/~conrad/software/xsel/ use std::error::Error as StdError; use std::fmt; use std::io::{Error as IoError, ErrorKind as IoErrorKind, Write}; use std::process::{Command, Stdio}; use std::string::FromUtf8Error; use copypasta::x11_clipboard::X11ClipboardContext; use which::which; use crate::combined::CombinedClipboardContext; use crate::display::DisplayServer; use crate::prelude::*; /// Platform specific context. /// /// Alias for `X11BinClipboardContext` on supported platforms, aliases to standard /// `ClipboardContext` provided by `rust-clipboard` on other platforms. pub type ClipboardContext = X11BinClipboardContext; /// Invokes [`xclip`][xclip]/[`xsel`][xsel] to access clipboard. /// /// See module documentation for more information. /// /// [xclip]: https://github.com/astrand/xclip /// [xsel]: http://www.vergenet.net/~conrad/software/xsel/ pub struct X11BinClipboardContext(ClipboardType); impl X11BinClipboardContext { pub fn new() -> crate::ClipResult { Ok(Self(ClipboardType::select())) } /// Construct combined with [`X11ClipboardContext`][X11ClipboardContext]. /// /// This clipboard context invokes a binary for getting the clipboard contents. This may /// be considered inefficient and has other drawbacks as noted in the struct documentation. /// This function also constructs a `X11ClipboardContext` for getting clipboard contents and /// combines the two to get the best of both worlds. /// /// [X11ClipboardContext]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/struct.X11ClipboardContext.html pub fn new_with_x11() -> crate::ClipResult> { Self::new()?.with_x11() } /// Combine this context with [`X11ClipboardContext`][X11ClipboardContext]. /// /// This clipboard context invokes a binary for getting the clipboard contents. This may /// be considered inefficient and has other drawbacks as noted in the struct documentation. /// This function constructs a `X11ClipboardContext` for getting clipboard contents and /// combines the two to get the best of both worlds. /// /// [X11ClipboardContext]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/struct.X11ClipboardContext.html pub fn with_x11( self, ) -> crate::ClipResult> { Ok(CombinedClipboardContext(X11ClipboardContext::new()?, self)) } } impl ClipboardProvider for X11BinClipboardContext { fn get_contents(&mut self) -> crate::ClipResult { Ok(self.0.get()?) } fn set_contents(&mut self, contents: String) -> crate::ClipResult<()> { Ok(self.0.set(&contents)?) } } impl ClipboardProviderExt for X11BinClipboardContext { fn display_server(&self) -> Option { Some(DisplayServer::X11) } fn has_bin_lifetime(&self) -> bool { false } } /// Available clipboard management binaries. /// /// Invoke `ClipboardType::select()` to select the best variant to use determined at runtime. enum ClipboardType { /// Use `xclip`. /// /// May contain a binary path if specified at compile time through the `XCLIP_PATH` variable. Xclip(Option), /// Use `xsel`. /// /// May contain a binary path if specified at compile time through the `XSEL_PATH` variable. Xsel(Option), } impl ClipboardType { /// Select the clipboard type to use. pub fn select() -> Self { if let Some(path) = option_env!("XCLIP_PATH") { ClipboardType::Xclip(Some(path.to_owned())) } else if let Some(path) = option_env!("XSEL_PATH") { ClipboardType::Xsel(Some(path.to_owned())) } else if which("xclip").is_ok() { ClipboardType::Xclip(None) } else if which("xsel").is_ok() { ClipboardType::Xsel(None) } else { // TODO: should we error here instead, as no clipboard binary was found? ClipboardType::Xclip(None) } } /// Get clipboard contents through the selected clipboard type. pub fn get(&self) -> Result { match self { ClipboardType::Xclip(path) => sys_cmd_get( "xclip", Command::new(path.as_deref().unwrap_or("xclip")) .arg("-sel") .arg("clip") .arg("-out"), ), ClipboardType::Xsel(path) => sys_cmd_get( "xsel", Command::new(path.as_deref().unwrap_or("xsel")) .arg("--clipboard") .arg("--output"), ), } } /// Set clipboard contents through the selected clipboard type. pub fn set(&self, contents: &str) -> Result<(), Error> { match self { ClipboardType::Xclip(path) => sys_cmd_set( "xclip", Command::new(path.as_deref().unwrap_or("xclip")) .arg("-sel") .arg("clip"), contents, ), ClipboardType::Xsel(path) => sys_cmd_set( "xsel", Command::new(path.as_deref().unwrap_or("xsel")).arg("--clipboard"), contents, ), } } } /// Get clipboard contents using a system command. fn sys_cmd_get(bin: &'static str, command: &mut Command) -> Result { // Spawn the command process for getting the clipboard let output = match command.output() { Ok(output) => output, Err(err) => { return Err(match err.kind() { IoErrorKind::NotFound => Error::NoBinary, _ => Error::BinaryIo(bin, err), }); } }; // Check process status code if !output.status.success() { return Err(Error::BinaryStatus(bin, output.status.code().unwrap_or(0))); } // Get and parse output String::from_utf8(output.stdout).map_err(Error::NoUtf8) } /// Set clipboard contents using a system command. fn sys_cmd_set(bin: &'static str, command: &mut Command, contents: &str) -> Result<(), Error> { // Spawn the command process for setting the clipboard let mut process = match command.stdin(Stdio::piped()).stdout(Stdio::null()).spawn() { Ok(process) => process, Err(err) => { return Err(match err.kind() { IoErrorKind::NotFound => Error::NoBinary, _ => Error::BinaryIo(bin, err), }); } }; // Write the contents to the xclip process process .stdin .as_mut() .unwrap() .write_all(contents.as_bytes()) .map_err(|err| Error::BinaryIo(bin, err))?; // Wait for process to exit let status = process.wait().map_err(|err| Error::BinaryIo(bin, err))?; if !status.success() { return Err(Error::BinaryStatus(bin, status.code().unwrap_or(0))); } Ok(()) } /// Represents X11 binary related error. #[derive(Debug)] #[non_exhaustive] pub enum Error { /// The `xclip` or `xsel` binary could not be found on the system, required for clipboard support. NoBinary, /// An error occurred while using `xclip` or `xsel` to manage the clipboard contents. /// This problem probably occurred when starting, or while piping the clipboard contents /// from/to the process. BinaryIo(&'static str, IoError), /// `xclip` or `xsel` unexpectetly exited with a non-successful status code. BinaryStatus(&'static str, i32), /// The clipboard contents could not be parsed as valid UTF-8. NoUtf8(FromUtf8Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::NoBinary => write!( f, "Could not find xclip or xsel binary for clipboard support" ), Error::BinaryIo(cmd, err) => { write!(f, "Failed to access clipboard using {}: {}", cmd, err) } Error::BinaryStatus(cmd, code) => write!( f, "Failed to use clipboard, {} exited with status code {}", cmd, code ), Error::NoUtf8(err) => write!( f, "Failed to parse clipboard contents as valid UTF-8: {}", err ), } } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Error::BinaryIo(_, err) => Some(err), Error::NoUtf8(err) => Some(err), _ => None, } } } copypasta-ext-0.4.4/src/x11_fork.rs000064400000000000000000000115521046102023000152250ustar 00000000000000//! Like [`x11_clipboard`][x11_clipboard], but forks to set contents. //! //! This provider ensures the clipboard contents you set remain available even after your //! application exists, unlike [`X11ClipboardContext`][X11ClipboardContext]. //! //! When setting the clipboard, the process is forked in which the clipboard is set. The fork is //! kept alive until the clipboard content changes, and may outlive your application. //! //! Use the provided `ClipboardContext` type alias to use this clipboard context on supported //! platforms, but fall back to the standard clipboard on others. //! //! ## Benefits //! //! - Keeps contents in clipboard even after your application exists. //! //! ## Drawbacks //! //! - Set contents may not be immediately available, because they are set in a fork. //! - Errors when setting the clipboard contents are not catched, the fork will panic //! `set_contents` will return no error. //! - The fork might cause weird behaviour for some applications. //! //! # Examples //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_fork::X11ForkClipboardContext; //! //! let mut ctx: X11ForkClipboardContext = X11ForkClipboardContext::new().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! Use `ClipboardContext` alias for better platform compatability: //! //! ```rust,no_run //! use copypasta_ext::prelude::*; //! use copypasta_ext::x11_fork::ClipboardContext; //! //! let mut ctx = ClipboardContext::new().unwrap(); //! println!("{:?}", ctx.get_contents()); //! ctx.set_contents("some string".into()).unwrap(); //! ``` //! //! [copypasta]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/index.html //! [X11ClipboardContext]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/struct.X11ClipboardContext.html use std::error::Error as StdError; use std::fmt; use copypasta::x11_clipboard::{Clipboard, Selection, X11ClipboardContext}; use libc::fork; use x11_clipboard::Clipboard as X11Clipboard; use crate::display::DisplayServer; use crate::prelude::*; /// Platform specific context. /// /// Alias for `X11ForkClipboardContext` on supported platforms, aliases to standard /// `ClipboardContext` provided by `rust-clipboard` on other platforms. pub type ClipboardContext = X11ForkClipboardContext; /// Like [`X11ClipboardContext`][X11ClipboardContext], but forks to set contents. /// /// `set_contents` forks the process, `get_contents` is an alias for /// [`X11ClipboardContext::get_contents`][X11ClipboardContext]. /// /// See module documentation for more information. /// /// [X11ClipboardContext]: https://docs.rs/copypasta/*/copypasta/x11_clipboard/struct.X11ClipboardContext.html pub struct X11ForkClipboardContext(X11ClipboardContext) where S: Selection; impl X11ForkClipboardContext { pub fn new() -> crate::ClipResult { Ok(Self(X11ClipboardContext::new()?)) } } impl ClipboardProvider for X11ForkClipboardContext where S: Selection, { fn get_contents(&mut self) -> crate::ClipResult { self.0.get_contents() } fn set_contents(&mut self, contents: String) -> crate::ClipResult<()> { match unsafe { fork() } { -1 => Err(Error::Fork.into()), 0 => { // Obtain new X11 clipboard context, set clipboard contents let clip = X11Clipboard::new().expect("failed to obtain X11 clipboard context"); clip.store( S::atom(&clip.setter.atoms), clip.setter.atoms.utf8_string, contents, ) .expect("failed to set clipboard contents through forked process"); // Wait for clipboard to change, then kill fork clip.load_wait( S::atom(&clip.getter.atoms), clip.getter.atoms.utf8_string, clip.getter.atoms.property, ) .expect("failed to wait on new clipboard value in forked process"); std::process::exit(0) } _pid => Ok(()), } } } impl ClipboardProviderExt for X11ForkClipboardContext where S: Selection, { fn display_server(&self) -> Option { Some(DisplayServer::X11) } fn has_bin_lifetime(&self) -> bool { false } } /// Represents X11 fork related error. #[derive(Debug)] #[non_exhaustive] pub enum Error { /// Failed to fork process, to set clipboard in. Fork, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Fork => write!(f, "Failed to fork process to set clipboard"), } } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Error::Fork => None, } } }