clipboard_x11-0.4.3/.cargo_vcs_info.json0000644000000001410000000000100134640ustar { "git": { "sha1": "42624c0fadf1998b1b804654cdae42a05a3c4628" }, "path_in_vcs": "x11" }clipboard_x11-0.4.3/Cargo.lock0000644000000166530000000000100114560ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "clipboard_x11" version = "0.4.3" dependencies = [ "thiserror", "x11rb", ] [[package]] name = "errno" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 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.170" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "proc-macro2" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "syn" version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "x11rb" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ "gethostname", "rustix", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" clipboard_x11-0.4.3/Cargo.toml0000644000000021030000000000100114620ustar # 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 = "clipboard_x11" version = "0.4.3" authors = ["Héctor Ramón Jiménez "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A library to obtain access to the X11 clipboard" documentation = "https://docs.rs/clipboard_x11" readme = false keywords = [ "clipboard", "x11", ] license = "MIT" repository = "https://github.com/hecrj/window_clipboard" resolver = "2" [lib] name = "clipboard_x11" path = "src/lib.rs" [dependencies.thiserror] version = "2.0" [dependencies.x11rb] version = "0.13" clipboard_x11-0.4.3/Cargo.toml.orig000064400000000000000000000006131046102023000151470ustar 00000000000000[package] name = "clipboard_x11" version = "0.4.3" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A library to obtain access to the X11 clipboard" license = "MIT" repository = "https://github.com/hecrj/window_clipboard" documentation = "https://docs.rs/clipboard_x11" keywords = ["clipboard", "x11"] [dependencies] x11rb = "0.13" thiserror = "2.0" clipboard_x11-0.4.3/LICENSE000064400000000000000000000021301046102023000132610ustar 00000000000000Copyright (c) 2019 quininer@live.com, Héctor Ramón, window_clipboard_x11 contributors 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. clipboard_x11-0.4.3/src/error.rs000064400000000000000000000014501046102023000145460ustar 00000000000000use x11rb::errors::{ConnectError, ConnectionError, ReplyError}; use x11rb::protocol::xproto::Atom; use std::sync::mpsc; #[must_use] #[derive(Debug, thiserror::Error)] pub enum Error { #[error("connection failed: {0}")] ConnectionFailed(#[from] ConnectError), #[error("connection errored: {0}")] ConnectionErrored(#[from] ConnectionError), #[error("reply failed: {0}")] ReplyError(#[from] ReplyError), #[error("timeout")] Timeout, #[error("unexpected type: {0}")] UnexpectedType(Atom), #[error("invalid utf8 string: {0}")] InvalidUtf8(std::string::FromUtf8Error), #[error("deadlock")] SelectionLocked, #[error("invalid selection owner")] InvalidOwner, #[error("worker communication error")] SendError(#[from] mpsc::SendError), } clipboard_x11-0.4.3/src/lib.rs000064400000000000000000000332071046102023000141700ustar 00000000000000#[forbid(unsafe_code)] mod error; pub use error::Error; use x11rb::connection::Connection as _; use x11rb::errors::ConnectError; use x11rb::protocol::xproto::{self, Atom, AtomEnum, EventMask, Window}; use x11rb::protocol::Event; use x11rb::rust_connection::RustConnection as Connection; use x11rb::wrapper::ConnectionExt; use std::collections::HashMap; use std::sync::{Arc, RwLock}; use std::thread; use std::time::{Duration, Instant}; const POLL_DURATION: std::time::Duration = Duration::from_micros(50); /// A connection to an X11 [`Clipboard`]. pub struct Clipboard { reader: Context, writer: Arc, selections: Arc)>>>, } impl Clipboard { /// Connect to the running X11 server and obtain a [`Clipboard`]. pub fn connect() -> Result { let reader = Context::new(None)?; let writer = Arc::new(Context::new(None)?); let selections = Arc::new(RwLock::new(HashMap::new())); let worker = Worker { context: Arc::clone(&writer), selections: Arc::clone(&selections), }; thread::spawn(move || worker.run()); Ok(Clipboard { reader, writer, selections, }) } fn read_selection(&self, selection: Atom) -> Result { Ok(String::from_utf8(self.load( selection, self.reader.atoms.utf8_string, self.reader.atoms.property, std::time::Duration::from_secs(3), )?) .map_err(Error::InvalidUtf8)?) } /// Read the current CLIPBOARD [`Clipboard`] value. pub fn read(&self) -> Result { self.read_selection(self.reader.atoms.clipboard) } /// Read the current PRIMARY [`Clipboard`] value. pub fn read_primary(&self) -> Result { self.read_selection(self.reader.atoms.primary) } fn write_selection(&mut self, selection: Atom, contents: String) -> Result<(), Error> { let target = self.writer.atoms.utf8_string; self.selections .write() .map_err(|_| Error::SelectionLocked)? .insert(selection, (target, contents.into())); let _ = xproto::set_selection_owner( &self.writer.connection, self.writer.window, selection, x11rb::CURRENT_TIME, )?; let _ = self.writer.connection.flush()?; let reply = xproto::get_selection_owner(&self.writer.connection, selection) .map_err(Into::into) .and_then(|cookie| cookie.reply())?; if reply.owner == self.writer.window { Ok(()) } else { Err(Error::InvalidOwner) } } /// Write a new value to the CLIPBOARD [`Clipboard`]. pub fn write(&mut self, contents: String) -> Result<(), Error> { let selection = self.writer.atoms.clipboard; self.write_selection(selection, contents) } /// Write a new value to the PRIMARY [`Clipboard`]. pub fn write_primary(&mut self, contents: String) -> Result<(), Error> { let selection = self.writer.atoms.primary; self.write_selection(selection, contents) } /// load value. fn load( &self, selection: Atom, target: Atom, property: Atom, timeout: impl Into>, ) -> Result, Error> { let mut buff = Vec::new(); let timeout = timeout.into(); let _ = xproto::convert_selection( &self.reader.connection, self.reader.window, selection, target, property, x11rb::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 _ = self.reader.connection.flush()?; self.process_event(&mut buff, selection, target, property, timeout)?; let _ = xproto::delete_property( &self.reader.connection, self.reader.window, property, )?; let _ = self.reader.connection.flush()?; Ok(buff) } fn process_event( &self, buff: &mut Vec, selection: Atom, target: Atom, property: Atom, timeout: T, ) -> 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 = match self.reader.connection.poll_for_event()? { Some(event) => event, None => { thread::park_timeout(POLL_DURATION); continue; } }; match event { 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 == AtomEnum::NONE.into() { break; } let reply = xproto::get_property( &self.reader.connection, false, self.reader.window, event.property, Atom::from(AtomEnum::ANY), buff.len() as u32, ::std::u32::MAX, // FIXME reasonable buffer size ) .map_err(Into::into) .and_then(|cookie| cookie.reply())?; if reply.type_ == self.reader.atoms.incr { if let Some(&size) = reply.value.get(0) { buff.reserve(size as usize); } let _ = xproto::delete_property( &self.reader.connection, self.reader.window, property, ); let _ = self.reader.connection.flush(); 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 != xproto::Property::NEW_VALUE { continue; }; let length = xproto::get_property( &self.reader.connection, false, self.reader.window, property, Atom::from(AtomEnum::ANY), 0, 0, ) .map_err(Into::into) .and_then(|cookie| cookie.reply())? .bytes_after; let reply = xproto::get_property( &self.reader.connection, true, self.reader.window, property, Atom::from(AtomEnum::ANY), 0, length, ) .map_err(Into::into) .and_then(|cookie| cookie.reply())?; if reply.type_ != target { continue; }; if reply.value_len != 0 { buff.extend_from_slice(&reply.value); } else { break; } } _ => {} } } Ok(()) } } pub struct Context { pub connection: Connection, pub screen: usize, pub window: Window, pub atoms: Atoms, } #[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, } #[inline] fn get_atom(connection: &Connection, name: &str) -> Result { x11rb::protocol::xproto::intern_atom(connection, false, name.as_bytes()) .map_err(Into::into) .and_then(|cookie| cookie.reply()) .map(|reply| reply.atom) .map_err(Into::into) } impl Context { pub fn new(displayname: Option<&str>) -> Result { let (connection, screen) = Connection::connect(displayname)?; let window = connection.generate_id().map_err(|_| { Error::ConnectionFailed(ConnectError::InvalidScreen) })?; { let screen = connection.setup().roots.get(screen as usize).ok_or( Error::ConnectionFailed(ConnectError::InvalidScreen), )?; let _ = xproto::create_window( &connection, x11rb::COPY_DEPTH_FROM_PARENT, window, screen.root, 0, 0, 1, 1, 0, xproto::WindowClass::INPUT_OUTPUT, screen.root_visual, &xproto::CreateWindowAux::new().event_mask( xproto::EventMask::STRUCTURE_NOTIFY | xproto::EventMask::PROPERTY_CHANGE, ), )?; let _ = connection.flush()?; } let atoms = Atoms { primary: AtomEnum::PRIMARY.into(), clipboard: get_atom(&connection, "CLIPBOARD")?, property: get_atom(&connection, "THIS_CLIPBOARD_OUT")?, targets: get_atom(&connection, "TARGETS")?, string: AtomEnum::STRING.into(), utf8_string: get_atom(&connection, "UTF8_STRING")?, incr: get_atom(&connection, "INCR")?, }; Ok(Context { connection, screen, window, atoms, }) } } pub struct Worker { context: Arc, selections: Arc)>>>, } impl Worker { pub const INCR_CHUNK_SIZE: usize = 4000; pub fn run(self) { while let Ok(event) = self.context.connection.wait_for_event() { match event { Event::SelectionRequest(event) => { let selections = match self.selections.read().ok() { Some(selections) => selections, None => continue, }; let &(target, ref value) = match selections.get(&event.selection) { Some(key_value) => key_value, None => continue, }; if event.target == self.context.atoms.targets { let data = [self.context.atoms.targets, target]; self.context .connection .change_property32( xproto::PropMode::REPLACE, event.requestor, event.property, xproto::AtomEnum::ATOM, &data, ) .expect("Change property"); } else { let _ = self .context .connection .change_property8( xproto::PropMode::REPLACE, event.requestor, event.property, target, value, ) .expect("Change property"); } let _ = xproto::send_event( &self.context.connection, false, event.requestor, EventMask::NO_EVENT, xproto::SelectionNotifyEvent { response_type: 31, sequence: event.sequence, time: event.time, requestor: event.requestor, selection: event.selection, target: event.target, property: event.property, }, ) .expect("Send event"); let _ = self.context.connection.flush(); } Event::SelectionClear(event) => { if let Ok(mut write_setmap) = self.selections.write() { write_setmap.remove(&event.selection); } } _ => (), } } } }