t_rust_less_lib/clipboard/
unix_x11.rs

1use crate::api::{ClipboardProviding, EventData, EventHub};
2use crate::clipboard::selection_provider_holder::SelectionProviderHolder;
3use crate::clipboard::{ClipboardError, ClipboardResult, SelectionProvider};
4use log::{debug, error};
5use std::ffi::CString;
6use std::mem::MaybeUninit;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::{Arc, Mutex, RwLock};
9use std::{env, thread};
10use x11::xlib;
11use zeroize::Zeroize;
12
13use super::ClipboardCommon;
14
15#[derive(Debug)]
16struct Atoms {
17  pub primary: xlib::Atom,
18  pub clipboard: xlib::Atom,
19  pub targets: xlib::Atom,
20  pub string: xlib::Atom,
21  pub utf8_string: xlib::Atom,
22}
23
24struct Context {
25  display: *mut xlib::Display,
26  window: xlib::Window,
27  atoms: Atoms,
28  open: AtomicBool,
29  provider_holder: RwLock<SelectionProviderHolder>,
30  event_hub: Arc<dyn EventHub>,
31}
32
33impl Context {
34  fn new<T>(event_hub: Arc<dyn EventHub>, provider: T) -> ClipboardResult<Self>
35  where
36    T: SelectionProvider + 'static,
37  {
38    unsafe {
39      let display_name = env::var("DISPLAY")?;
40      let c_display_name = CString::new(display_name)?;
41      let display = xlib::XOpenDisplay(c_display_name.as_ptr());
42
43      if display.is_null() {
44        return Err(ClipboardError::Other("Cannot open display".to_string()));
45      }
46      let root = xlib::XDefaultRootWindow(display);
47      let black = xlib::XBlackPixel(display, xlib::XDefaultScreen(display));
48      let window = xlib::XCreateSimpleWindow(display, root, 0, 0, 1, 1, 0, black, black);
49
50      debug!("Window id: {window}");
51
52      xlib::XSelectInput(display, window, xlib::StructureNotifyMask | xlib::PropertyChangeMask);
53
54      let primary = Self::get_atom(display, "PRIMARY");
55      if primary != xlib::XA_PRIMARY {
56        debug!("XA_PRIMARY is not named PRIMARY");
57      }
58      let clipboard = Self::get_atom(display, "CLIPBOARD");
59      let targets = Self::get_atom(display, "TARGETS");
60      let string = Self::get_atom(display, "STRING");
61      if string != xlib::XA_STRING {
62        debug!("XA_STRING is not named STRING");
63      }
64      let utf8_string = Self::get_atom(display, "UTF8_STRING");
65
66      let atoms = Atoms {
67        primary,
68        clipboard,
69        targets,
70        string,
71        utf8_string,
72      };
73
74      debug!("{atoms:?}");
75
76      Ok(Context {
77        display,
78        window,
79        atoms,
80        open: AtomicBool::new(true),
81        provider_holder: RwLock::new(SelectionProviderHolder::new(provider)),
82        event_hub,
83      })
84    }
85  }
86
87  fn get_atom(display: *mut xlib::Display, name: &str) -> xlib::Atom {
88    unsafe {
89      let c_name = CString::new(name).unwrap();
90      xlib::XInternAtom(display, c_name.as_ptr(), xlib::False)
91    }
92  }
93
94  fn destroy(&self) {
95    if self.open.swap(false, Ordering::Relaxed) {
96      unsafe {
97        xlib::XDestroyWindow(self.display, self.window);
98        xlib::XFlush(self.display);
99      }
100    }
101  }
102
103  fn own_selection(&self) -> bool {
104    unsafe {
105      xlib::XSetSelectionOwner(self.display, self.atoms.clipboard, self.window, xlib::CurrentTime);
106
107      let owner = xlib::XGetSelectionOwner(self.display, self.atoms.clipboard);
108      if owner != self.window {
109        debug!("Failed taking ownership of {}", self.atoms.clipboard);
110        return false;
111      }
112    }
113
114    true
115  }
116
117  fn clear_selection(&self) {
118    unsafe {
119      for selection in &[self.atoms.primary, self.atoms.clipboard] {
120        xlib::XSetSelectionOwner(self.display, *selection, 0, xlib::CurrentTime);
121      }
122      xlib::XFlush(self.display);
123    }
124  }
125
126  fn is_open(&self) -> bool {
127    self.open.load(Ordering::Relaxed)
128  }
129
130  fn currently_providing(&self) -> Option<ClipboardProviding> {
131    self.provider_holder.read().ok()?.current_selection()
132  }
133
134  fn provide_next(&self) {
135    if let Ok(mut provider_holder) = self.provider_holder.write() {
136      provider_holder.get_value();
137    }
138  }
139}
140
141impl Drop for Context {
142  fn drop(&mut self) {
143    unsafe {
144      xlib::XCloseDisplay(self.display);
145    }
146  }
147}
148
149unsafe impl Send for Context {}
150
151unsafe impl Sync for Context {}
152
153pub struct Clipboard {
154  context: Arc<Context>,
155  handle: Mutex<Option<thread::JoinHandle<()>>>,
156}
157
158impl ClipboardCommon for Clipboard {
159  fn new<T>(selection_provider: T, event_hub: Arc<dyn EventHub>) -> ClipboardResult<Self>
160  where
161    T: SelectionProvider + Clone + 'static,
162  {
163    match selection_provider.current_selection() {
164      Some(providing) => event_hub.send(EventData::ClipboardProviding(providing)),
165      None => return Err(ClipboardError::Other("Empty provider".to_string())),
166    };
167
168    let context = Arc::new(Context::new(event_hub, selection_provider)?);
169
170    let handle = thread::spawn({
171      let cloned = context.clone();
172      move || run(cloned)
173    });
174
175    Ok(Clipboard {
176      context,
177      handle: Mutex::new(Some(handle)),
178    })
179  }
180
181  fn destroy(&self) {
182    self.context.destroy()
183  }
184
185  fn is_open(&self) -> bool {
186    self.context.is_open()
187  }
188
189  fn currently_providing(&self) -> Option<ClipboardProviding> {
190    self.context.currently_providing()
191  }
192
193  fn provide_next(&self) {
194    self.context.provide_next()
195  }
196
197  fn wait(&self) -> ClipboardResult<()> {
198    let mut maybe_handle = self.handle.lock()?;
199    if let Some(handle) = maybe_handle.take() {
200      handle
201        .join()
202        .map_err(|_| ClipboardError::Other("wait timeout".to_string()))?;
203    }
204    Ok(())
205  }
206}
207
208impl Drop for Clipboard {
209  fn drop(&mut self) {
210    self.destroy()
211  }
212}
213
214fn run(context: Arc<Context>) {
215  unsafe {
216    if !context.own_selection() {
217      return;
218    }
219
220    let mut event: xlib::XEvent = MaybeUninit::zeroed().assume_init();
221
222    loop {
223      xlib::XFlush(context.display);
224      debug!("Wating for event");
225      xlib::XNextEvent(context.display, &mut event);
226
227      debug!("Got event: {}", event.get_type());
228
229      match event.get_type() {
230        xlib::SelectionRequest => {
231          let mut selection: xlib::XSelectionEvent = MaybeUninit::zeroed().assume_init();
232          selection.type_ = xlib::SelectionNotify;
233          selection.display = event.selection_request.display;
234          selection.requestor = event.selection_request.requestor;
235          selection.selection = event.selection_request.selection;
236          selection.time = event.selection_request.time;
237          selection.target = event.selection_request.target;
238          selection.property = event.selection_request.property;
239
240          debug!("Selection requestor: {}", selection.requestor);
241          debug!("Selection target: {}", selection.target);
242
243          if selection.target == context.atoms.targets {
244            let atoms = [context.atoms.targets, context.atoms.string, context.atoms.utf8_string];
245            xlib::XChangeProperty(
246              context.display,
247              selection.requestor,
248              selection.property,
249              xlib::XA_ATOM,
250              32,
251              xlib::PropModeReplace,
252              &atoms as *const xlib::Atom as *const u8,
253              atoms.len() as i32,
254            );
255          } else if selection.target == context.atoms.string || selection.target == context.atoms.utf8_string {
256            match context.provider_holder.write() {
257              Ok(mut provider_holder) => {
258                match provider_holder.get_value() {
259                  Some(mut value) => {
260                    let content: &[u8] = value.as_ref();
261
262                    xlib::XChangeProperty(
263                      context.display,
264                      selection.requestor,
265                      selection.property,
266                      selection.target,
267                      8,
268                      xlib::PropModeReplace,
269                      content.as_ptr(),
270                      content.len() as i32,
271                    );
272                    value.zeroize();
273                  }
274                  None => {
275                    context.clear_selection();
276                    debug!("Last part: Reply with NONE");
277                    selection.property = 0;
278                  }
279                };
280              }
281              Err(err) => {
282                error!("Unable to lock provider {err}");
283                context.clear_selection();
284                selection.property = 0;
285              }
286            };
287          } else {
288            debug!("Reply with NONE");
289            selection.property = 0;
290          }
291
292          xlib::XSendEvent(
293            context.display,
294            selection.requestor,
295            xlib::False,
296            xlib::NoEventMask,
297            &mut xlib::XEvent { selection } as *mut xlib::XEvent,
298          );
299
300          xlib::XSync(context.display, xlib::False);
301        }
302        xlib::SelectionClear => {
303          debug!("Lost ownership");
304          break;
305        }
306        xlib::DestroyNotify => {
307          debug!("Window destroyed");
308
309          break;
310        }
311        ignored => debug!("Ignoring event: {ignored}"),
312      }
313    }
314
315    debug!("Ending event loop");
316    context.event_hub.send(EventData::ClipboardDone);
317    context.destroy();
318  }
319}