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: Cookie<'_, RustConnection, …> = connection.intern_atom(
93 only_if_exists:false,
94 name:name.as_bytes()
95 )?;
96 let reply: InternAtomReply = intern_atom.reply()
97 .map_err(op: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}
386