From 3277d91321d690105a402c9feda72e7ec6dc8829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 11 Feb 2021 22:35:53 +0100 Subject: [PATCH] Replace `xcb` with `x11rb` in `clipboard_x11` --- x11/Cargo.toml | 3 +- x11/src/clipboard.rs | 180 ++++++++++++++++++++++--------------------- x11/src/error.rs | 64 +++------------ x11/src/lib.rs | 2 - 4 files changed, 104 insertions(+), 145 deletions(-) diff --git a/x11/Cargo.toml b/x11/Cargo.toml index d62f1c8..174187e 100644 --- a/x11/Cargo.toml +++ b/x11/Cargo.toml @@ -10,4 +10,5 @@ documentation = "https://docs.rs/clipboard_x11" keywords = ["clipboard", "x11"] [dependencies] -xcb = { version = "0.9", features = ["thread"] } +x11rb = "0.8" +thiserror = "1.0" diff --git a/x11/src/clipboard.rs b/x11/src/clipboard.rs index ad94947..0c7db51 100644 --- a/x11/src/clipboard.rs +++ b/x11/src/clipboard.rs @@ -1,8 +1,12 @@ use crate::error::Error; + use std::thread; use std::time::{Duration, Instant}; -use xcb::base::ConnError; -use xcb::{Atom, Connection, Window}; +use x11rb::connection::Connection as _; +use x11rb::errors::ConnectError; +use x11rb::protocol::xproto::{self, Atom, AtomEnum, Window}; +use x11rb::protocol::Event; +use x11rb::rust_connection::RustConnection as Connection; const POLL_DURATION: std::time::Duration = Duration::from_micros(50); @@ -24,65 +28,62 @@ pub struct Clipboard { pub struct Context { pub connection: Connection, - pub screen: i32, + pub screen: usize, pub window: Window, pub atoms: Atoms, } #[inline] fn get_atom(connection: &Connection, name: &str) -> Result { - xcb::intern_atom(connection, false, name) - .get_reply() - .map(|reply| reply.atom()) + 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(); + let window = connection.generate_id().map_err(|_| { + Error::ConnectionFailed(ConnectError::InvalidScreen) + })?; { - let screen = connection - .get_setup() - .roots() - .nth(screen as usize) - .ok_or(Error::XcbConn(ConnError::ClosedInvalidScreen))?; - xcb::create_window( - &connection, - xcb::COPY_FROM_PARENT as u8, - window, - screen.root(), - 0, - 0, - 1, - 1, - 0, - xcb::WINDOW_CLASS_INPUT_OUTPUT as u16, - screen.root_visual(), - &[( - xcb::CW_EVENT_MASK, - xcb::EVENT_MASK_STRUCTURE_NOTIFY - | xcb::EVENT_MASK_PROPERTY_CHANGE, - )], - ); - connection.flush(); - } + let screen = + connection.setup().roots.get(screen as usize).ok_or( + Error::ConnectionFailed(ConnectError::InvalidScreen), + )?; - macro_rules! intern_atom { - ( $name:expr ) => { - get_atom(&connection, $name)? - }; + 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: xcb::ATOM_PRIMARY, - clipboard: intern_atom!("CLIPBOARD"), - property: intern_atom!("THIS_CLIPBOARD_OUT"), - targets: intern_atom!("TARGETS"), - string: xcb::ATOM_STRING, - utf8_string: intern_atom!("UTF8_STRING"), - incr: intern_atom!("INCR"), + 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 { @@ -132,7 +133,7 @@ impl Clipboard { return Err(Error::Timeout); } - let event = match self.getter.connection.poll_for_event() { + let event = match self.getter.connection.poll_for_event()? { Some(event) => event, None => { thread::park_timeout(POLL_DURATION); @@ -140,97 +141,96 @@ impl Clipboard { } }; - let r = event.response_type(); - - match r & !0x80 { - xcb::SELECTION_NOTIFY => { - let event = unsafe { - xcb::cast_event::(&event) - }; - if event.selection() != selection { + 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() == xcb::ATOM_NONE { + if event.property == AtomEnum::NONE.into() { break; } - let reply = xcb::get_property( + let reply = xproto::get_property( &self.getter.connection, false, self.getter.window, - event.property(), - xcb::ATOM_ANY, + event.property, + Atom::from(AtomEnum::ANY), buff.len() as u32, ::std::u32::MAX, // FIXME reasonable buffer size ) - .get_reply()?; + .map_err(Into::into) + .and_then(|cookie| cookie.reply())?; - if reply.type_() == self.getter.atoms.incr { - if let Some(&size) = reply.value::().get(0) { + if reply.type_ == self.getter.atoms.incr { + if let Some(&size) = reply.value.get(0) { buff.reserve(size as usize); } - xcb::delete_property( + + let _ = xproto::delete_property( &self.getter.connection, self.getter.window, property, ); - self.getter.connection.flush(); + + let _ = self.getter.connection.flush(); is_incr = true; + continue; - } else if reply.type_() != target { - return Err(Error::UnexpectedType(reply.type_())); + } else if reply.type_ != target { + return Err(Error::UnexpectedType(reply.type_)); } - buff.extend_from_slice(reply.value()); + buff.extend_from_slice(&reply.value); break; } - xcb::PROPERTY_NOTIFY if is_incr => { - let event = unsafe { - xcb::cast_event::(&event) - }; - if event.state() != xcb::PROPERTY_NEW_VALUE as u8 { + Event::PropertyNotify(event) if is_incr => { + if event.state != xproto::Property::NEW_VALUE { continue; }; - let length = xcb::get_property( + let length = xproto::get_property( &self.getter.connection, false, self.getter.window, property, - xcb::ATOM_ANY, + Atom::from(AtomEnum::ANY), 0, 0, ) - .get_reply() - .map(|reply| reply.bytes_after())?; + .map_err(Into::into) + .and_then(|cookie| cookie.reply())? + .bytes_after; - let reply = xcb::get_property( + let reply = xproto::get_property( &self.getter.connection, true, self.getter.window, property, - xcb::ATOM_ANY, + Atom::from(AtomEnum::ANY), 0, length, ) - .get_reply()?; + .map_err(Into::into) + .and_then(|cookie| cookie.reply())?; - if reply.type_() != target { + if reply.type_ != target { continue; }; - if reply.value_len() != 0 { - buff.extend_from_slice(reply.value()); + if reply.value_len != 0 { + buff.extend_from_slice(&reply.value); } else { break; } } - _ => (), + _ => {} } } + Ok(()) } @@ -248,25 +248,27 @@ impl Clipboard { let mut buff = Vec::new(); let timeout = timeout.into(); - xcb::convert_selection( + let _ = xproto::convert_selection( &self.getter.connection, self.getter.window, selection, target, property, - xcb::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. - ); - self.getter.connection.flush(); + 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.getter.connection.flush(); self.process_event(&mut buff, selection, target, property, timeout)?; - xcb::delete_property( + + let _ = xproto::delete_property( &self.getter.connection, self.getter.window, property, - ); - self.getter.connection.flush(); + )?; + let _ = self.getter.connection.flush()?; + Ok(buff) } } diff --git a/x11/src/error.rs b/x11/src/error.rs index 3e2c90b..d9a0160 100644 --- a/x11/src/error.rs +++ b/x11/src/error.rs @@ -1,59 +1,17 @@ -use std::error::Error as StdError; -use std::fmt; -use std::sync::mpsc::SendError; -use xcb::base::{ConnError, GenericError}; -use xcb::Atom; +use x11rb::errors::{ConnectError, ConnectionError, ReplyError}; +use x11rb::protocol::xproto::Atom; #[must_use] -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum Error { - Set(SendError), - XcbConn(ConnError), - XcbGeneric(GenericError), + #[error("connection failed: {0}")] + ConnectionFailed(#[from] ConnectError), + #[error("connection errored: {0}")] + ConnectionErrored(#[from] ConnectionError), + #[error("reply failed: {0}")] + ReplyError(#[from] ReplyError), + #[error("timeout")] Timeout, - Owner, + #[error("unexpected type: {0}")] UnexpectedType(Atom), } - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::Set(e) => write!(f, "XCB - couldn't set atom: {:?}", e), - Error::XcbConn(e) => write!(f, "XCB connection error: {:?}", e), - Error::XcbGeneric(e) => write!(f, "XCB generic error: {:?}", e), - Error::Timeout => write!(f, "Selection timed out"), - Error::Owner => { - write!(f, "Failed to set new owner of XCB selection") - } - Error::UnexpectedType(target) => { - write!(f, "Unexpected Reply type: {}", target) - } - } - } -} - -impl StdError for Error { - fn source(&self) -> Option<&(dyn StdError + 'static)> { - use self::Error::*; - match self { - Set(e) => Some(e), - XcbConn(e) => Some(e), - XcbGeneric(e) => Some(e), - Timeout | Owner | UnexpectedType(_) => 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!(XcbConn from ConnError); -define_from!(XcbGeneric from GenericError); diff --git a/x11/src/lib.rs b/x11/src/lib.rs index 8a28492..a2cf2bd 100644 --- a/x11/src/lib.rs +++ b/x11/src/lib.rs @@ -1,8 +1,6 @@ mod clipboard; mod error; -pub use xcb::*; - use std::error::Error; pub struct Clipboard(clipboard::Clipboard);