x11_clipboard/
lib.rs

1extern crate x11rb;
2extern crate libc;
3
4pub mod error;
5mod run;
6
7pub use x11rb::protocol::xproto::{Atom, Window};
8pub use x11rb::rust_connection::RustConnection;
9
10use std::thread;
11use std::time::{ Duration, Instant };
12use std::sync::{ Arc, RwLock };
13use std::sync::mpsc::{ Sender, channel };
14use std::collections::HashMap;
15use std::os::fd::OwnedFd;
16use x11rb::connection::{Connection, RequestConnection};
17use x11rb::{COPY_DEPTH_FROM_PARENT, CURRENT_TIME};
18use x11rb::errors::ConnectError;
19use x11rb::protocol::{Event, xfixes};
20use x11rb::protocol::xproto::{AtomEnum, ConnectionExt, CreateWindowAux, EventMask, Property, WindowClass};
21use error::Error;
22use run::{create_pipe_drop_fd, PipeDropFds};
23
24pub const INCR_CHUNK_SIZE: usize = 4000;
25const POLL_DURATION: u64 = 50;
26type SetMap = Arc<RwLock<HashMap<Atom, (Atom, Vec<u8>)>>>;
27
28#[derive(Clone, Debug)]
29pub struct Atoms {
30    pub primary: Atom,
31    pub clipboard: Atom,
32    pub property: Atom,
33    pub targets: Atom,
34    pub string: Atom,
35    pub utf8_string: Atom,
36    pub incr: Atom,
37}
38
39impl Atoms {
40    fn intern_all(conn: &RustConnection) -> Result<Atoms, Error> {
41        let clipboard = conn.intern_atom(
42            false,
43            b"CLIPBOARD",
44        )?;
45        let property = conn.intern_atom(
46            false,
47            b"THIS_CLIPBOARD_OUT",
48        )?;
49        let targets = conn.intern_atom(
50            false,
51            b"TARGETS",
52        )?;
53        let utf8_string = conn.intern_atom(
54            false,
55            b"UTF8_STRING",
56        )?;
57        let incr = conn.intern_atom(
58            false,
59            b"INCR",
60        )?;
61        Ok(Atoms {
62            primary: Atom::from(AtomEnum::PRIMARY),
63            clipboard: clipboard.reply()?.atom,
64            property: property.reply()?.atom,
65            targets: targets.reply()?.atom,
66            string: Atom::from(AtomEnum::STRING),
67            utf8_string: utf8_string.reply()?.atom,
68            incr: incr.reply()?.atom,
69        })
70    }
71}
72
73/// X11 Clipboard
74pub struct Clipboard {
75    pub getter: Context,
76    pub setter: Arc<Context>,
77    setmap: SetMap,
78    send: Sender<Atom>,
79    // Relying on the Drop in OwnedFd to close the fd
80    _drop_fd: OwnedFd,
81}
82
83pub struct Context {
84    pub connection: RustConnection,
85    pub screen: usize,
86    pub window: Window,
87    pub atoms: Atoms
88}
89
90#[inline]
91fn get_atom(connection: &RustConnection, name: &str) -> Result<Atom, Error> {
92    let intern_atom = connection.intern_atom(
93        false,
94        name.as_bytes()
95    )?;
96    let reply = intern_atom.reply()
97        .map_err(Error::XcbReply)?;
98    Ok(reply.atom)
99}
100
101impl Context {
102    pub fn new(displayname: Option<&str>) -> Result<Self, Error> {
103        let (connection, screen) = RustConnection::connect(displayname)?;
104        let window = connection.generate_id()?;
105
106        {
107            let screen = connection.setup().roots.get(screen)
108                .ok_or(Error::XcbConnect(ConnectError::InvalidScreen))?;
109            connection.create_window(
110                COPY_DEPTH_FROM_PARENT,
111                window,
112                screen.root,
113                0,
114                0,
115                1,
116                1,
117                0,
118                WindowClass::INPUT_OUTPUT,
119                screen.root_visual,
120                &CreateWindowAux::new()
121                    .event_mask(EventMask::STRUCTURE_NOTIFY | EventMask::PROPERTY_CHANGE)
122            )?
123                .check()?;
124        }
125
126        let atoms = Atoms::intern_all(&connection)?;
127
128        Ok(Context { connection, screen, window, atoms })
129    }
130
131    pub fn get_atom(&self, name: &str) -> Result<Atom, Error> {
132        get_atom(&self.connection, name)
133    }
134}
135
136
137impl Clipboard {
138    /// Create Clipboard.
139    pub fn new() -> Result<Self, Error> {
140        let getter = Context::new(None)?;
141        let setter = Arc::new(Context::new(None)?);
142        let setter2 = Arc::clone(&setter);
143        let setmap = Arc::new(RwLock::new(HashMap::new()));
144        let setmap2 = Arc::clone(&setmap);
145
146        let PipeDropFds {
147            read_pipe, write_pipe
148        } = create_pipe_drop_fd()?;
149        let (sender, receiver) = channel();
150        let max_length = setter.connection.maximum_request_bytes();
151        thread::spawn(move || run::run(setter2, setmap2, max_length, receiver, read_pipe));
152
153        Ok(Clipboard { getter, setter, setmap, send: sender, _drop_fd: write_pipe })
154    }
155
156    fn process_event<T>(&self, buff: &mut Vec<u8>, selection: Atom, target: Atom, property: Atom, timeout: T, use_xfixes: bool, sequence_number: u64)
157        -> Result<(), Error>
158        where T: Into<Option<Duration>>
159    {
160        let mut is_incr = false;
161        let timeout = timeout.into();
162        let start_time =
163            if timeout.is_some() { Some(Instant::now()) }
164            else { None };
165
166        loop {
167            if timeout.into_iter()
168                .zip(start_time)
169                .next()
170                .map(|(timeout, time)| (Instant::now() - time) >= timeout)
171                .unwrap_or(false)
172            {
173                return Err(Error::Timeout);
174            }
175
176            let (event, seq) = match use_xfixes {
177                true => self.getter.connection.wait_for_event_with_sequence()?,
178                false => {
179                    match self.getter.connection.poll_for_event_with_sequence()? {
180                        Some(event) => event,
181                        None => {
182                            thread::park_timeout(Duration::from_millis(POLL_DURATION));
183                            continue
184                        }
185                    }
186                }
187            };
188
189            if seq < sequence_number {
190                continue;
191            }
192
193            match event {
194                Event::XfixesSelectionNotify(event) if use_xfixes => {
195                    self.getter.connection.convert_selection(
196                        self.getter.window,
197                        selection,
198                        target,
199                        property,
200                        event.timestamp,
201                    )?.check()?;
202                }
203                Event::SelectionNotify(event) => {
204                    if event.selection != selection { continue };
205
206                    // Note that setting the property argument to None indicates that the
207                    // conversion requested could not be made.
208                    if event.property == Atom::from(AtomEnum::NONE) {
209                        break;
210                    }
211
212                    let reply = self.getter.connection.get_property(
213                        false,
214                        self.getter.window,
215                        event.property,
216                        AtomEnum::NONE,
217                        buff.len() as u32,
218                        u32::MAX
219                    )?.reply()?;
220
221                    if reply.type_ == self.getter.atoms.incr {
222                        if let Some(mut value) = reply.value32() {
223                            if let Some(size) = value.next() {
224                                buff.reserve(size as usize);
225                            }
226                        }
227                        self.getter.connection.delete_property(
228                            self.getter.window,
229                            property
230                        )?.check()?;
231                        is_incr = true;
232                        continue
233                    } else if reply.type_ != target {
234                        return Err(Error::UnexpectedType(reply.type_));
235                    }
236
237                    buff.extend_from_slice(&reply.value);
238                    break
239                }
240
241                Event::PropertyNotify(event) if is_incr => {
242                    if event.state != Property::NEW_VALUE { continue };
243
244
245                    let cookie = self.getter.connection.get_property(
246                        false,
247                        self.getter.window,
248                        property,
249                        AtomEnum::NONE,
250                        0,
251                        0
252                    )?;
253
254                    let length = cookie.reply()?.bytes_after;
255
256                    let cookie = self.getter.connection.get_property(
257                        true,
258                        self.getter.window,
259                        property,
260                        AtomEnum::NONE,
261                        0, length
262                    )?;
263                    let reply = cookie.reply()?;
264                    if reply.type_ != target { continue };
265
266                    let value = reply.value;
267
268                    if !value.is_empty() {
269                        buff.extend_from_slice(&value);
270                    } else {
271                        break
272                    }
273                },
274                _ => ()
275            }
276        }
277        Ok(())
278    }
279
280    /// load value.
281    pub fn load<T>(&self, selection: Atom, target: Atom, property: Atom, timeout: T)
282        -> Result<Vec<u8>, Error>
283        where T: Into<Option<Duration>>
284    {
285        let mut buff = Vec::new();
286        let timeout = timeout.into();
287
288        let cookie = self.getter.connection.convert_selection(
289            self.getter.window,
290            selection,
291            target,
292            property,
293            CURRENT_TIME,
294            // FIXME ^
295            // Clients should not use CurrentTime for the time argument of a ConvertSelection request.
296            // Instead, they should use the timestamp of the event that caused the request to be made.
297        )?;
298
299        let sequence_number = cookie.sequence_number();
300        cookie.check()?;
301
302        self.process_event(&mut buff, selection, target, property, timeout, false, sequence_number)?;
303
304        self.getter.connection.delete_property(
305            self.getter.window,
306            property
307        )?.check()?;
308
309        Ok(buff)
310    }
311
312    /// wait for a new value and load it
313    pub fn load_wait(&self, selection: Atom, target: Atom, property: Atom)
314        -> Result<Vec<u8>, Error>
315    {
316        let mut buff = Vec::new();
317
318        let screen = &self.getter.connection.setup().roots.get(self.getter.screen)
319            .ok_or(Error::XcbConnect(ConnectError::InvalidScreen))?;
320
321        xfixes::query_version(
322            &self.getter.connection,
323            5,
324            0,
325        )?;
326        // Clear selection sources...
327        xfixes::select_selection_input(
328            &self.getter.connection,
329            screen.root,
330            self.getter.atoms.primary,
331            xfixes::SelectionEventMask::default()
332        )?;
333        xfixes::select_selection_input(
334            &self.getter.connection,
335            screen.root,
336            self.getter.atoms.clipboard,
337            xfixes::SelectionEventMask::default()
338        )?;
339        // ...and set the one requested now
340        let cookie = xfixes::select_selection_input(
341            &self.getter.connection,
342            screen.root,
343            selection,
344            xfixes::SelectionEventMask::SET_SELECTION_OWNER |
345                xfixes::SelectionEventMask::SELECTION_CLIENT_CLOSE |
346                xfixes::SelectionEventMask::SELECTION_WINDOW_DESTROY
347        )?;
348
349        let sequence_number = cookie.sequence_number();
350        cookie.check()?;
351
352        self.process_event(&mut buff, selection, target, property, None, true, sequence_number)?;
353
354        self.getter.connection.delete_property(self.getter.window, property)?.check()?;
355
356        Ok(buff)
357    }
358
359    /// store value.
360    pub fn store<T: Into<Vec<u8>>>(&self, selection: Atom, target: Atom, value: T)
361        -> Result<(), Error>
362    {
363        self.send.send(selection)?;
364        self.setmap
365            .write()
366            .map_err(|_| Error::Lock)?
367            .insert(selection, (target, value.into()));
368
369        self.setter.connection.set_selection_owner(
370            self.setter.window,
371            selection,
372            CURRENT_TIME
373        )?.check()?;
374
375        if self.setter.connection.get_selection_owner(
376            selection
377        )?.reply()
378            .map(|reply| reply.owner == self.setter.window)
379            .unwrap_or(false) {
380            Ok(())
381        } else {
382            Err(Error::Owner)
383        }
384    }
385}