x11-clipboard-0.9.2/.cargo_vcs_info.json0000644000000001360000000000100134120ustar { "git": { "sha1": "a716f799e324a7a00b01d22e8da1acffbd468654" }, "path_in_vcs": "" }x11-clipboard-0.9.2/.gitignore000064400000000000000000000000451046102023000141710ustar 00000000000000target/ **/*.rs.bk Cargo.lock /.idea x11-clipboard-0.9.2/.gitjournal.toml000064400000000000000000000004211046102023000153300ustar 00000000000000categories = ["Added", "Changed", "Fixed", "Improved", "Removed"] category_delimiters = ["[", "]"] colored_output = true enable_debug = true enable_footers = false excluded_commit_tags = [] show_commit_hash = false show_prefix = false sort_by = "date" template_prefix = "" x11-clipboard-0.9.2/.travis.yml000064400000000000000000000003541046102023000143150ustar 00000000000000language: rust rust: - stable cache: cargo os: - linux sudo: required services: - xvfb before_script: - sudo apt-get update -qq - sudo apt-get install -y libxcb-shape0-dev libxcb-xfixes0-dev script: - cargo test x11-clipboard-0.9.2/Cargo.lock0000644000000135170000000000100113740ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys", ] [[package]] name = "gethostname" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ "libc", "windows-targets 0.48.5", ] [[package]] name = "libc" version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "rustix" version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.0", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm 0.52.0", "windows_aarch64_msvc 0.52.0", "windows_i686_gnu 0.52.0", "windows_i686_msvc 0.52.0", "windows_x86_64_gnu 0.52.0", "windows_x86_64_gnullvm 0.52.0", "windows_x86_64_msvc 0.52.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "x11-clipboard" version = "0.9.2" dependencies = [ "libc", "x11rb", ] [[package]] name = "x11rb" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" dependencies = [ "gethostname", "rustix", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" x11-clipboard-0.9.2/Cargo.toml0000644000000016220000000000100114110ustar # 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] name = "x11-clipboard" version = "0.9.2" authors = ["quininer kel "] description = "x11 clipboard support for Rust." documentation = "https://docs.rs/x11-clipboard/" readme = "README.md" keywords = [ "x11", "xcb", "clipboard", ] license = "MIT" repository = "https://github.com/quininer/x11-clipboard" [dependencies.libc] version = "0.2.152" [dependencies.x11rb] version = "0.13.0" features = ["xfixes"] x11-clipboard-0.9.2/Cargo.toml.orig000064400000000000000000000006311046102023000150710ustar 00000000000000[package] name = "x11-clipboard" version = "0.9.2" authors = ["quininer kel "] description = "x11 clipboard support for Rust." repository = "https://github.com/quininer/x11-clipboard" documentation = "https://docs.rs/x11-clipboard/" keywords = [ "x11", "xcb", "clipboard" ] license = "MIT" [dependencies] libc = { version = "0.2.152" } x11rb = { version = "0.13.0", features = ["xfixes"]} x11-clipboard-0.9.2/LICENSE000064400000000000000000000020611046102023000132060ustar 00000000000000MIT License Copyright (c) 2017 quininer@live.com 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. x11-clipboard-0.9.2/README.md000064400000000000000000000012111046102023000134540ustar 00000000000000# x11-clipboard [![travis-ci](https://travis-ci.org/quininer/x11-clipboard.svg?branch=master)](https://travis-ci.org/quininer/x11-clipboard) [![crates](https://img.shields.io/crates/v/x11-clipboard.svg)](https://crates.io/crates/x11-clipboard) [![license](https://img.shields.io/github/license/quininer/x11-clipboard.svg)](https://github.com/quininer/x11-clipboard/blob/master/LICENSE) [![docs.rs](https://docs.rs/x11-clipboard/badge.svg)](https://docs.rs/x11-clipboard/) x11 clipboard support for Rust. ## requirements * xcb ## reference * [2. Peer-to-Peer Communication by Means of Selections](https://tronche.com/gui/x/icccm/sec-2.html#s-2) x11-clipboard-0.9.2/examples/monitor_primary_selection.rs000064400000000000000000000014341046102023000216670ustar 00000000000000extern crate x11_clipboard; use x11_clipboard::Clipboard; fn main() { let clipboard = Clipboard::new().unwrap(); let mut last = String::new(); println!("Waiting for selection..."); loop { if let Ok(curr) = clipboard.load_wait( clipboard.getter.atoms.primary, clipboard.getter.atoms.utf8_string, clipboard.getter.atoms.property ) { let curr = String::from_utf8_lossy(&curr); let curr = curr .trim_matches('\u{0}') .trim(); if !curr.is_empty() && last != curr { last = curr.to_owned(); println!("Contents of primary selection: {}", last); println!("Waiting for selection..."); } } } } x11-clipboard-0.9.2/examples/paste.rs000064400000000000000000000007131046102023000155030ustar 00000000000000extern crate x11_clipboard; use std::time::Duration; use x11_clipboard::Clipboard; fn main() { let clipboard = Clipboard::new().unwrap(); let val = clipboard.load( clipboard.setter.atoms.clipboard, clipboard.setter.atoms.utf8_string, clipboard.setter.atoms.property, Duration::from_secs(3) ) .unwrap(); let val = String::from_utf8(val).unwrap(); print!("{}", val); } x11-clipboard-0.9.2/examples/wait_copy_event.rs000064400000000000000000000007101046102023000175630ustar 00000000000000extern crate x11_clipboard; use x11_clipboard::Clipboard; fn main() { let clipboard = Clipboard::new().unwrap(); loop { let val = clipboard .load_wait( clipboard.setter.atoms.clipboard, clipboard.setter.atoms.string, clipboard.setter.atoms.property, ) .unwrap(); let val = String::from_utf8(val).unwrap(); println!("{}", val); } } x11-clipboard-0.9.2/src/error.rs000064400000000000000000000043541046102023000144760ustar 00000000000000use std::fmt; use std::sync::mpsc::SendError; use std::error::Error as StdError; use x11rb::errors::{ConnectError, ConnectionError, ReplyError, ReplyOrIdError}; use x11rb::protocol::xproto::Atom; #[must_use] #[derive(Debug)] #[non_exhaustive] pub enum Error { Set(SendError), XcbConnect(ConnectError), XcbConnection(ConnectionError), XcbReplyOrId(ReplyOrIdError), XcbReply(ReplyError), Lock, Timeout, Owner, UnexpectedType(Atom), // Could change name on next major, since this uses pipes now. EventFdCreate, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Error::*; match self { Set(e) => write!(f, "XCB - couldn't set atom: {:?}", e), XcbConnect(e) => write!(f, "XCB - couldn't establish conection: {:?}", e), XcbConnection(e) => write!(f, "XCB connection error: {:?}", e), XcbReplyOrId(e) => write!(f, "XCB reply error: {:?}", e), XcbReply(e) => write!(f, "XCB reply error: {:?}", e), Lock => write!(f, "XCB: Lock is poisoned"), Timeout => write!(f, "Selection timed out"), Owner => write!(f, "Failed to set new owner of XCB selection"), UnexpectedType(target) => write!(f, "Unexpected Reply type: {:?}", target), EventFdCreate => write!(f, "Failed to create eventfd"), } } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { use self::Error::*; match self { Set(e) => Some(e), XcbConnection(e) => Some(e), XcbReply(e) => Some(e), XcbReplyOrId(e) => Some(e), XcbConnect(e) => Some(e), Lock | Timeout | Owner | UnexpectedType(_) | EventFdCreate => None, } } } macro_rules! define_from { ( $item:ident from $err:ty ) => { impl From<$err> for Error { fn from(err: $err) -> Error { Error::$item(err) } } } } define_from!(Set from SendError); define_from!(XcbConnect from ConnectError); define_from!(XcbConnection from ConnectionError); define_from!(XcbReply from ReplyError); define_from!(XcbReplyOrId from ReplyOrIdError); x11-clipboard-0.9.2/src/lib.rs000064400000000000000000000276331046102023000141200ustar 00000000000000extern crate x11rb; extern crate libc; pub mod error; mod run; pub use x11rb::protocol::xproto::{Atom, Window}; pub use x11rb::rust_connection::RustConnection; use std::thread; use std::time::{ Duration, Instant }; use std::sync::{ Arc, RwLock }; use std::sync::mpsc::{ Sender, channel }; use std::collections::HashMap; use std::os::fd::OwnedFd; use x11rb::connection::{Connection, RequestConnection}; use x11rb::{COPY_DEPTH_FROM_PARENT, CURRENT_TIME}; use x11rb::errors::ConnectError; use x11rb::protocol::{Event, xfixes}; use x11rb::protocol::xproto::{AtomEnum, ConnectionExt, CreateWindowAux, EventMask, Property, WindowClass}; use error::Error; use run::{create_pipe_drop_fd, PipeDropFds}; pub const INCR_CHUNK_SIZE: usize = 4000; const POLL_DURATION: u64 = 50; type SetMap = Arc)>>>; #[derive(Clone, Debug)] pub struct Atoms { pub primary: Atom, pub clipboard: Atom, pub property: Atom, pub targets: Atom, pub string: Atom, pub utf8_string: Atom, pub incr: Atom, } impl Atoms { fn intern_all(conn: &RustConnection) -> Result { let clipboard = conn.intern_atom( false, b"CLIPBOARD", )?; let property = conn.intern_atom( false, b"THIS_CLIPBOARD_OUT", )?; let targets = conn.intern_atom( false, b"TARGETS", )?; let utf8_string = conn.intern_atom( false, b"UTF8_STRING", )?; let incr = conn.intern_atom( false, b"INCR", )?; Ok(Atoms { primary: Atom::from(AtomEnum::PRIMARY), clipboard: clipboard.reply()?.atom, property: property.reply()?.atom, targets: targets.reply()?.atom, string: Atom::from(AtomEnum::STRING), utf8_string: utf8_string.reply()?.atom, incr: incr.reply()?.atom, }) } } /// X11 Clipboard pub struct Clipboard { pub getter: Context, pub setter: Arc, setmap: SetMap, send: Sender, // Relying on the Drop in OwnedFd to close the fd _drop_fd: OwnedFd, } pub struct Context { pub connection: RustConnection, pub screen: usize, pub window: Window, pub atoms: Atoms } #[inline] fn get_atom(connection: &RustConnection, name: &str) -> Result { let intern_atom = connection.intern_atom( false, name.as_bytes() )?; let reply = intern_atom.reply() .map_err(Error::XcbReply)?; Ok(reply.atom) } impl Context { pub fn new(displayname: Option<&str>) -> Result { let (connection, screen) = RustConnection::connect(displayname)?; let window = connection.generate_id()?; { let screen = connection.setup().roots.get(screen) .ok_or(Error::XcbConnect(ConnectError::InvalidScreen))?; connection.create_window( COPY_DEPTH_FROM_PARENT, window, screen.root, 0, 0, 1, 1, 0, WindowClass::INPUT_OUTPUT, screen.root_visual, &CreateWindowAux::new() .event_mask(EventMask::STRUCTURE_NOTIFY | EventMask::PROPERTY_CHANGE) )? .check()?; } let atoms = Atoms::intern_all(&connection)?; Ok(Context { connection, screen, window, atoms }) } pub fn get_atom(&self, name: &str) -> Result { get_atom(&self.connection, name) } } impl Clipboard { /// Create Clipboard. pub fn new() -> Result { let getter = Context::new(None)?; let setter = Arc::new(Context::new(None)?); let setter2 = Arc::clone(&setter); let setmap = Arc::new(RwLock::new(HashMap::new())); let setmap2 = Arc::clone(&setmap); let PipeDropFds { read_pipe, write_pipe } = create_pipe_drop_fd()?; let (sender, receiver) = channel(); let max_length = setter.connection.maximum_request_bytes(); thread::spawn(move || run::run(setter2, setmap2, max_length, receiver, read_pipe)); Ok(Clipboard { getter, setter, setmap, send: sender, _drop_fd: write_pipe }) } fn process_event(&self, buff: &mut Vec, selection: Atom, target: Atom, property: Atom, timeout: T, use_xfixes: bool, sequence_number: u64) -> Result<(), Error> where T: Into> { let mut is_incr = false; let timeout = timeout.into(); let start_time = if timeout.is_some() { Some(Instant::now()) } else { None }; loop { if timeout.into_iter() .zip(start_time) .next() .map(|(timeout, time)| (Instant::now() - time) >= timeout) .unwrap_or(false) { return Err(Error::Timeout); } let (event, seq) = match use_xfixes { true => self.getter.connection.wait_for_event_with_sequence()?, false => { match self.getter.connection.poll_for_event_with_sequence()? { Some(event) => event, None => { thread::park_timeout(Duration::from_millis(POLL_DURATION)); continue } } } }; if seq < sequence_number { continue; } match event { Event::XfixesSelectionNotify(event) if use_xfixes => { self.getter.connection.convert_selection( self.getter.window, selection, target, property, event.timestamp, )?.check()?; } Event::SelectionNotify(event) => { if event.selection != selection { continue }; // Note that setting the property argument to None indicates that the // conversion requested could not be made. if event.property == Atom::from(AtomEnum::NONE) { break; } let reply = self.getter.connection.get_property( false, self.getter.window, event.property, AtomEnum::NONE, buff.len() as u32, u32::MAX )?.reply()?; if reply.type_ == self.getter.atoms.incr { if let Some(mut value) = reply.value32() { if let Some(size) = value.next() { buff.reserve(size as usize); } } self.getter.connection.delete_property( self.getter.window, property )?.check()?; is_incr = true; continue } else if reply.type_ != target { return Err(Error::UnexpectedType(reply.type_)); } buff.extend_from_slice(&reply.value); break } Event::PropertyNotify(event) if is_incr => { if event.state != Property::NEW_VALUE { continue }; let cookie = self.getter.connection.get_property( false, self.getter.window, property, AtomEnum::NONE, 0, 0 )?; let length = cookie.reply()?.bytes_after; let cookie = self.getter.connection.get_property( true, self.getter.window, property, AtomEnum::NONE, 0, length )?; let reply = cookie.reply()?; if reply.type_ != target { continue }; let value = reply.value; if !value.is_empty() { buff.extend_from_slice(&value); } else { break } }, _ => () } } Ok(()) } /// load value. pub fn load(&self, selection: Atom, target: Atom, property: Atom, timeout: T) -> Result, Error> where T: Into> { let mut buff = Vec::new(); let timeout = timeout.into(); let cookie = self.getter.connection.convert_selection( self.getter.window, selection, target, property, CURRENT_TIME, // FIXME ^ // Clients should not use CurrentTime for the time argument of a ConvertSelection request. // Instead, they should use the timestamp of the event that caused the request to be made. )?; let sequence_number = cookie.sequence_number(); cookie.check()?; self.process_event(&mut buff, selection, target, property, timeout, false, sequence_number)?; self.getter.connection.delete_property( self.getter.window, property )?.check()?; Ok(buff) } /// wait for a new value and load it pub fn load_wait(&self, selection: Atom, target: Atom, property: Atom) -> Result, Error> { let mut buff = Vec::new(); let screen = &self.getter.connection.setup().roots.get(self.getter.screen) .ok_or(Error::XcbConnect(ConnectError::InvalidScreen))?; xfixes::query_version( &self.getter.connection, 5, 0, )?; // Clear selection sources... xfixes::select_selection_input( &self.getter.connection, screen.root, self.getter.atoms.primary, xfixes::SelectionEventMask::default() )?; xfixes::select_selection_input( &self.getter.connection, screen.root, self.getter.atoms.clipboard, xfixes::SelectionEventMask::default() )?; // ...and set the one requested now let cookie = xfixes::select_selection_input( &self.getter.connection, screen.root, selection, xfixes::SelectionEventMask::SET_SELECTION_OWNER | xfixes::SelectionEventMask::SELECTION_CLIENT_CLOSE | xfixes::SelectionEventMask::SELECTION_WINDOW_DESTROY )?; let sequence_number = cookie.sequence_number(); cookie.check()?; self.process_event(&mut buff, selection, target, property, None, true, sequence_number)?; self.getter.connection.delete_property(self.getter.window, property)?.check()?; Ok(buff) } /// store value. pub fn store>>(&self, selection: Atom, target: Atom, value: T) -> Result<(), Error> { self.send.send(selection)?; self.setmap .write() .map_err(|_| Error::Lock)? .insert(selection, (target, value.into())); self.setter.connection.set_selection_owner( self.setter.window, selection, CURRENT_TIME )?.check()?; if self.setter.connection.get_selection_owner( selection )?.reply() .map(|reply| reply.owner == self.setter.window) .unwrap_or(false) { Ok(()) } else { Err(Error::Owner) } } } x11-clipboard-0.9.2/src/run.rs000064400000000000000000000212651046102023000141510ustar 00000000000000use std::cmp; use std::sync::Arc; use std::sync::mpsc::{ Receiver, TryRecvError }; use std::collections::HashMap; use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd}; use ::{AtomEnum, EventMask}; use x11rb::connection::Connection; use x11rb::protocol::Event; use x11rb::protocol::xproto::{Atom, ChangeWindowAttributesAux, ConnectionExt, Property, PropMode, SELECTION_NOTIFY_EVENT, SelectionNotifyEvent, Window}; use ::{ INCR_CHUNK_SIZE, Context, SetMap }; use error::Error; macro_rules! try_continue { ( $expr:expr ) => { match $expr { Some(val) => val, None => continue } }; } struct IncrState { selection: Atom, requestor: Window, property: Atom, pos: usize } pub(crate) struct PipeDropFds { pub(crate) read_pipe: OwnedFd, pub(crate) write_pipe: OwnedFd, } pub(crate) fn create_pipe_drop_fd() -> Result{ let pipe_drop_fds = unsafe { // Docs Linux: https://man7.org/linux/man-pages/man2/pipe.2.html // Posix: https://pubs.opengroup.org/onlinepubs/9699919799/ // Safety: See above docs, api expects a 2-long array of file descriptors, and flags let mut pipes: [libc::c_int; 2] = [0, 0]; let pipe_create_res = libc::pipe(pipes.as_mut_ptr()); if pipe_create_res < 0 { // Don't want to have to read from errno_location, just skip propagating errno. return Err(Error::EventFdCreate); } // Safety: Trusting the OS to give correct FDs let read_pipe = OwnedFd::from_raw_fd(pipes[0]); let write_pipe = OwnedFd::from_raw_fd(pipes[1]); PipeDropFds { read_pipe, write_pipe, } }; Ok(pipe_drop_fds) } pub(crate) fn run(context: Arc, setmap: SetMap, max_length: usize, receiver: Receiver, read_pipe: OwnedFd) { let mut incr_map = HashMap::::new(); let mut state_map = HashMap::::new(); let stream_fd = context.connection.stream().as_fd(); let borrowed_fd = read_pipe.as_fd(); // Poll stream for new Read-ready events, check if the other side of the pipe has been dropped let mut pollfds: [libc::pollfd; 2] = [libc::pollfd { fd: stream_fd.as_raw_fd(), events: libc::POLLIN, revents: 0, }, libc::pollfd { fd: borrowed_fd.as_raw_fd(), // If the other end is dropped, this pipe will get a HUP on poll events: libc::POLLHUP, revents: 0, }]; let len = pollfds.len(); loop { unsafe { // Docs Linux: https://man7.org/linux/man-pages/man2/poll.2.html // Posix: https://pubs.opengroup.org/onlinepubs/9699919799/ // Safety: Passing in a mutable pointer that lives for the duration of the call, the length is // set to the length of that pointer. // Any negative value (-1 for example) means infinite timeout. let poll_res = libc::poll(&mut pollfds as *mut libc::pollfd, len as libc::nfds_t, -1); if poll_res < 0 { // Error polling, can't continue return; } } if pollfds[1].revents & libc::POLLHUP != 0 { // kill-signal on pollfd return; } loop { let evt = if let Ok(evt) = context.connection.poll_for_event() { evt } else { // Connection died, exit return; }; let event = if let Some(evt) = evt { evt } else { // No event on POLLIN happens, fd being readable doesn't mean there's a complete event ready to read. // Poll again. break; }; loop { match receiver.try_recv() { Ok(selection) => if let Some(property) = incr_map.remove(&selection) { state_map.remove(&property); }, Err(TryRecvError::Empty) => break, Err(TryRecvError::Disconnected) => if state_map.is_empty() { return } } } match event { Event::SelectionRequest(event) => { let read_map = try_continue!(setmap.read().ok()); let &(target, ref value) = try_continue!(read_map.get(&event.selection)); if event.target == context.atoms.targets { let _ = x11rb::wrapper::ConnectionExt::change_property32( &context.connection, PropMode::REPLACE, event.requestor, event.property, Atom::from(AtomEnum::ATOM), &[context.atoms.targets, target] ); } else if value.len() < max_length - 24 { let _ = x11rb::wrapper::ConnectionExt::change_property8( &context.connection, PropMode::REPLACE, event.requestor, event.property, target, value ); } else { let _ = context.connection.change_window_attributes( event.requestor, &ChangeWindowAttributesAux::new() .event_mask(EventMask::PROPERTY_CHANGE) ); let _ = x11rb::wrapper::ConnectionExt::change_property32( &context.connection, PropMode::REPLACE, event.requestor, event.property, context.atoms.incr, &[0u32; 0], ); incr_map.insert(event.selection, event.property); state_map.insert( event.property, IncrState { selection: event.selection, requestor: event.requestor, property: event.property, pos: 0 } ); } let _ = context.connection.send_event( false, event.requestor, EventMask::default(), SelectionNotifyEvent { response_type: SELECTION_NOTIFY_EVENT, sequence: 0, time: event.time, requestor: event.requestor, selection: event.selection, target: event.target, property: event.property } ); let _ = context.connection.flush(); }, Event::PropertyNotify(event) => { if event.state != Property::DELETE { continue }; let is_end = { let state = try_continue!(state_map.get_mut(&event.atom)); let read_setmap = try_continue!(setmap.read().ok()); let &(target, ref value) = try_continue!(read_setmap.get(&state.selection)); let len = cmp::min(INCR_CHUNK_SIZE, value.len() - state.pos); let _ = x11rb::wrapper::ConnectionExt::change_property8( &context.connection, PropMode::REPLACE, state.requestor, state.property, target, &value[state.pos..][..len] ); state.pos += len; len == 0 }; if is_end { state_map.remove(&event.atom); } let _ = context.connection.flush(); }, Event::SelectionClear(event) => { if let Some(property) = incr_map.remove(&event.selection) { state_map.remove(&property); } if let Ok(mut write_setmap) = setmap.write() { write_setmap.remove(&event.selection); } } _ => () } } } } x11-clipboard-0.9.2/tests/simple-test.rs000064400000000000000000000022551046102023000161640ustar 00000000000000extern crate x11_clipboard; use std::time::{ Instant, Duration }; use x11_clipboard::Clipboard; #[test] fn it_work() { let data = format!("{:?}", Instant::now()); let clipboard = Clipboard::new().unwrap(); let atom_clipboard = clipboard.setter.atoms.clipboard; let atom_utf8string = clipboard.setter.atoms.utf8_string; let atom_property = clipboard.setter.atoms.property; clipboard.store(atom_clipboard, atom_utf8string, data.as_bytes()).unwrap(); let output = clipboard.load(atom_clipboard, atom_utf8string, atom_property, None).unwrap(); assert_eq!(output, data.as_bytes()); let data = format!("{:?}", Instant::now()); clipboard.store(atom_clipboard, atom_utf8string, data.as_bytes()).unwrap(); let output = clipboard.load(atom_clipboard, atom_utf8string, atom_property, None).unwrap(); assert_eq!(output, data.as_bytes()); let output = clipboard.load(atom_clipboard, atom_utf8string, atom_property, None).unwrap(); assert_eq!(output, data.as_bytes()); let dur = Duration::from_secs(3); let output = clipboard.load(atom_clipboard, atom_utf8string, atom_property, dur).unwrap(); assert_eq!(output, data.as_bytes()); } x11-clipboard-0.9.2/tests/window-leak.rs000064400000000000000000000013241046102023000161330ustar 00000000000000extern crate x11_clipboard; use x11_clipboard::error::Error; use x11_clipboard::Clipboard; pub fn paste_to_clipboard(content: &str) -> Result<(), Error> { let clipboard = Clipboard::new()?; clipboard.store( clipboard.setter.atoms.primary, clipboard.setter.atoms.utf8_string, content, )?; clipboard.store( clipboard.setter.atoms.clipboard, clipboard.setter.atoms.utf8_string, content, )?; Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn should_work_but_does_not() -> Result<(), Error> { for i in 0..1000 { paste_to_clipboard(&format!("I have told you {} times!", i))?; } Ok(()) } }