t_rust_less_lib/clipboard/
unix_wayland.rs

1use std::{
2  cell::RefCell,
3  error::Error,
4  fs::File,
5  io::Write,
6  os::unix::io::FromRawFd,
7  rc::Rc,
8  sync::{
9    atomic::{AtomicBool, Ordering},
10    Arc, Mutex, RwLock,
11  },
12  thread,
13};
14
15use log::{debug, error};
16use wayland_client::{global_filter, protocol::wl_seat::WlSeat, Display, GlobalManager, Main};
17use wayland_protocols::wlr::unstable::data_control::v1::client::{
18  zwlr_data_control_manager_v1::ZwlrDataControlManagerV1, zwlr_data_control_source_v1::Event,
19};
20use zeroize::Zeroize;
21
22use crate::api::{ClipboardProviding, EventData, EventHub};
23use crate::clipboard::selection_provider_holder::SelectionProviderHolder;
24
25use super::{ClipboardCommon, ClipboardError, ClipboardResult, SelectionProvider};
26
27const TEXT_MIMES: &[&str] = &[
28  "text/plain;charset=utf-8",
29  "text/plain",
30  "STRING",
31  "UTF8_STRING",
32  "TEXT",
33];
34
35struct Context {
36  display: Display,
37  open: AtomicBool,
38  cancel: AtomicBool,
39  provider_holder: RwLock<SelectionProviderHolder>,
40}
41
42impl Context {
43  fn new<T>(display: Display, provider: T) -> Self
44  where
45    T: SelectionProvider + 'static,
46  {
47    Context {
48      display,
49      open: AtomicBool::new(false),
50      cancel: AtomicBool::new(false),
51      provider_holder: RwLock::new(SelectionProviderHolder::new(provider)),
52    }
53  }
54
55  fn is_open(&self) -> bool {
56    self.open.load(Ordering::Relaxed)
57  }
58
59  fn currently_providing(&self) -> Option<ClipboardProviding> {
60    self.provider_holder.read().ok()?.current_selection()
61  }
62
63  fn provide_next(&self) {
64    if let Ok(mut provider_holder) = self.provider_holder.write() {
65      provider_holder.get_value();
66    }
67  }
68
69  fn destroy(&self) {
70    self.cancel.store(true, Ordering::Relaxed)
71  }
72}
73
74pub struct Clipboard {
75  context: Arc<Context>,
76  handle: Mutex<Option<thread::JoinHandle<()>>>,
77}
78
79impl ClipboardCommon for Clipboard {
80  fn new<T>(selection_provider: T, event_hub: Arc<dyn EventHub>) -> ClipboardResult<Self>
81  where
82    T: SelectionProvider + Clone + 'static,
83  {
84    let display = Display::connect_to_env()?;
85
86    debug!("Got display: {}", display.id());
87
88    match selection_provider.current_selection() {
89      Some(providing) => event_hub.send(EventData::ClipboardProviding(providing)),
90      None => return Err(ClipboardError::Other("Empty provider".to_string())),
91    };
92
93    let context = Arc::new(Context::new(display, selection_provider));
94
95    let handle = thread::spawn({
96      let cloned = context.clone();
97      move || {
98        if let Err(err) = try_run(cloned.clone()) {
99          cloned.open.store(false, Ordering::Relaxed);
100          error!("Wayland clipboard error: {}", err);
101        }
102      }
103    });
104
105    Ok(Clipboard {
106      context,
107      handle: Mutex::new(Some(handle)),
108    })
109  }
110
111  fn is_open(&self) -> bool {
112    self.context.is_open()
113  }
114
115  fn currently_providing(&self) -> Option<ClipboardProviding> {
116    self.context.currently_providing()
117  }
118
119  fn provide_next(&self) {
120    self.context.provide_next()
121  }
122
123  fn destroy(&self) {
124    self.context.destroy()
125  }
126
127  fn wait(&self) -> ClipboardResult<()> {
128    let mut maybe_handle = self.handle.lock()?;
129    if let Some(handle) = maybe_handle.take() {
130      handle
131        .join()
132        .map_err(|_| ClipboardError::Other("wait timeout".to_string()))?;
133    }
134    Ok(())
135  }
136}
137
138fn try_run(context: Arc<Context>) -> Result<(), Box<dyn Error>> {
139  let mut queue = context.display.create_event_queue();
140  let display = context.display.attach(queue.token());
141  let seats = Rc::new(RefCell::new(vec![]));
142  let mut devices = vec![];
143
144  let seats_cloned = seats.clone();
145
146  let manager = GlobalManager::new_with_cb(
147    &display,
148    global_filter!([WlSeat, 2, move |seat: Main<WlSeat>, _: DispatchData| {
149      seats_cloned.borrow_mut().push(seat);
150    }]),
151  );
152
153  queue.sync_roundtrip(&mut (), |_, _, _| {})?;
154
155  debug!("Seats: {:?}", seats.borrow());
156  debug!("Globals: {:?}", manager.list());
157
158  let clipboard_manager: Main<ZwlrDataControlManagerV1> = manager.instantiate_exact(1)?;
159
160  let data_source = clipboard_manager.create_data_source();
161  let context_cloned = context.clone();
162
163  data_source.quick_assign(move |_, event, _| match event {
164    Event::Send { mime_type, fd } if TEXT_MIMES.contains(&mime_type.as_str()) => {
165      debug!("Event send: {} {}", mime_type, fd);
166      match context_cloned.provider_holder.write() {
167        Ok(mut selection_provider) => {
168          if let Some(mut content) = selection_provider.get_value() {
169            let mut f = unsafe { File::from_raw_fd(fd) };
170            f.write_all(content.as_bytes()).ok();
171            content.zeroize();
172          } else {
173            debug!("No more values");
174            context_cloned.cancel.store(true, Ordering::Relaxed);
175          }
176        }
177        Err(err) => {
178          error!("Lock error: {}", err);
179          context_cloned.cancel.store(true, Ordering::Relaxed);
180        }
181      }
182    }
183    Event::Cancelled => {
184      debug!("Event cancel: Lost ownership");
185      context_cloned.cancel.store(true, Ordering::Relaxed)
186    }
187    _ => (),
188  });
189
190  for &mime_type in TEXT_MIMES {
191    data_source.offer(mime_type.to_string());
192  }
193
194  for seat in seats.borrow_mut().iter_mut() {
195    let device = clipboard_manager.get_data_device(seat);
196    device.quick_assign(|_, _, _| {});
197    device.set_selection(Some(&data_source));
198    devices.push(device);
199  }
200
201  debug!("Start event loop");
202  context.open.store(true, Ordering::Relaxed);
203  while !context.cancel.load(Ordering::Relaxed) {
204    queue.dispatch(&mut (), |_, _, _| {})?;
205  }
206  context.open.store(false, Ordering::Relaxed);
207  debug!("End event loop");
208
209  for device in devices {
210    device.destroy();
211  }
212  data_source.destroy();
213  for seat in seats.borrow_mut().iter_mut() {
214    seat.detach();
215  }
216  display.detach();
217
218  Ok(())
219}