t_rust_less_lib/clipboard/
unix_wayland.rs

1use std::{
2  collections::HashMap,
3  error::Error,
4  fs::File,
5  io::Write,
6  os::{fd::AsRawFd, unix::io::FromRawFd},
7  sync::{
8    atomic::{AtomicBool, Ordering},
9    Arc, Mutex, RwLock,
10  },
11  thread,
12};
13
14use log::{debug, error};
15use wayland_client::{
16  event_created_child,
17  globals::{registry_queue_init, BindError, GlobalListContents},
18  protocol::{
19    wl_registry::WlRegistry,
20    wl_seat::{self, WlSeat},
21  },
22  Connection, Dispatch, EventQueue, Proxy,
23};
24use wayland_protocols_wlr::data_control::v1::client::{
25  zwlr_data_control_device_v1::{self, ZwlrDataControlDeviceV1},
26  zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
27  zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
28  zwlr_data_control_source_v1::{self, ZwlrDataControlSourceV1},
29};
30use zeroize::Zeroize;
31
32use crate::api::{ClipboardProviding, EventData, EventHub};
33use crate::clipboard::selection_provider_holder::SelectionProviderHolder;
34
35use super::{ClipboardCommon, ClipboardError, ClipboardResult, SelectionProvider};
36
37const TEXT_MIMES: &[&str] = &[
38  "text/plain;charset=utf-8",
39  "text/plain",
40  "STRING",
41  "UTF8_STRING",
42  "TEXT",
43];
44
45struct Context {
46  open: AtomicBool,
47  cancel: AtomicBool,
48  provider_holder: RwLock<SelectionProviderHolder>,
49}
50
51impl Context {
52  fn new<T>(provider: T) -> Self
53  where
54    T: SelectionProvider + 'static,
55  {
56    Context {
57      open: AtomicBool::new(false),
58      cancel: AtomicBool::new(false),
59      provider_holder: RwLock::new(SelectionProviderHolder::new(provider)),
60    }
61  }
62
63  fn is_open(&self) -> bool {
64    self.open.load(Ordering::Relaxed)
65  }
66
67  fn currently_providing(&self) -> Option<ClipboardProviding> {
68    self.provider_holder.read().ok()?.current_selection()
69  }
70
71  fn provide_next(&self) {
72    if let Ok(mut provider_holder) = self.provider_holder.write() {
73      provider_holder.get_value();
74    }
75  }
76
77  fn destroy(&self) {
78    self.cancel.store(true, Ordering::Relaxed)
79  }
80}
81
82struct State {
83  context: Arc<Context>,
84  clipboard_manager: ZwlrDataControlManagerV1,
85  seats: HashMap<WlSeat, SeatData>,
86}
87
88impl Dispatch<WlRegistry, GlobalListContents> for State {
89  fn event(
90    _state: &mut Self,
91    _proxy: &WlRegistry,
92    _event: <WlRegistry as wayland_client::Proxy>::Event,
93    _data: &GlobalListContents,
94    _conn: &wayland_client::Connection,
95    _qhandle: &wayland_client::QueueHandle<Self>,
96  ) {
97  }
98}
99
100impl Dispatch<WlSeat, ()> for State {
101  fn event(
102    _state: &mut Self,
103    seat: &WlSeat,
104    event: <WlSeat as wayland_client::Proxy>::Event,
105    _data: &(),
106    _conn: &wayland_client::Connection,
107    _qh: &wayland_client::QueueHandle<Self>,
108  ) {
109    if let wl_seat::Event::Name { name } = event {
110      _state.seats.get_mut(seat).unwrap().set_name(name);
111    }
112  }
113}
114
115impl Dispatch<ZwlrDataControlManagerV1, ()> for State {
116  fn event(
117    _state: &mut Self,
118    _proxy: &ZwlrDataControlManagerV1,
119    _event: <ZwlrDataControlManagerV1 as wayland_client::Proxy>::Event,
120    _data: &(),
121    _conn: &wayland_client::Connection,
122    _qhandle: &wayland_client::QueueHandle<Self>,
123  ) {
124  }
125}
126
127impl Dispatch<ZwlrDataControlSourceV1, ()> for State {
128  fn event(
129    _state: &mut Self,
130    _proxy: &ZwlrDataControlSourceV1,
131    _event: <ZwlrDataControlSourceV1 as wayland_client::Proxy>::Event,
132    _data: &(),
133    _conn: &wayland_client::Connection,
134    _qhandle: &wayland_client::QueueHandle<Self>,
135  ) {
136    match _event {
137      zwlr_data_control_source_v1::Event::Send { mime_type, fd } if TEXT_MIMES.contains(&mime_type.as_str()) => {
138        debug!("Event send: {mime_type} {fd:?}");
139        match _state.context.provider_holder.write() {
140          Ok(mut selection_provider) => {
141            if let Some(mut content) = selection_provider.get_value() {
142              let mut f = unsafe { File::from_raw_fd(fd.as_raw_fd()) };
143              f.write_all(content.as_bytes()).ok();
144              content.zeroize();
145            } else {
146              debug!("No more values");
147              _state.context.cancel.store(true, Ordering::Relaxed);
148            }
149          }
150          Err(err) => {
151            error!("Lock error: {err}");
152            _state.context.cancel.store(true, Ordering::Relaxed);
153          }
154        }
155      }
156      zwlr_data_control_source_v1::Event::Cancelled => {
157        debug!("Event cancel: Lost ownership");
158        _state.context.cancel.store(true, Ordering::Relaxed);
159      }
160      _ => (),
161    }
162  }
163}
164
165impl Dispatch<ZwlrDataControlDeviceV1, WlSeat> for State {
166  fn event(
167    state: &mut Self,
168    _device: &ZwlrDataControlDeviceV1,
169    event: <ZwlrDataControlDeviceV1 as Proxy>::Event,
170    seat: &WlSeat,
171    _conn: &wayland_client::Connection,
172    _qhandle: &wayland_client::QueueHandle<Self>,
173  ) {
174    match event {
175      zwlr_data_control_device_v1::Event::DataOffer { id } => id.destroy(),
176      zwlr_data_control_device_v1::Event::Finished => {
177        state.seats.get_mut(seat).unwrap().set_device(None);
178      }
179      _ => (),
180    }
181  }
182
183  event_created_child!(State, ZwlrDataControlDeviceV1, [
184      zwlr_data_control_device_v1::EVT_DATA_OFFER_OPCODE => (ZwlrDataControlOfferV1, ()),
185  ]);
186}
187
188impl Dispatch<ZwlrDataControlOfferV1, ()> for State {
189  fn event(
190    _state: &mut Self,
191    _offer: &ZwlrDataControlOfferV1,
192    _event: <ZwlrDataControlOfferV1 as wayland_client::Proxy>::Event,
193    _data: &(),
194    _conn: &wayland_client::Connection,
195    _qhandle: &wayland_client::QueueHandle<Self>,
196  ) {
197  }
198}
199
200pub struct Clipboard {
201  context: Arc<Context>,
202  handle: Mutex<Option<thread::JoinHandle<()>>>,
203}
204
205impl ClipboardCommon for Clipboard {
206  fn new<T>(selection_provider: T, event_hub: Arc<dyn EventHub>) -> ClipboardResult<Self>
207  where
208    T: SelectionProvider + Clone + 'static,
209  {
210    let conn = Connection::connect_to_env()?;
211    let (globals, mut queue) = registry_queue_init::<State>(&conn)?;
212    let qh = &queue.handle();
213    let clipboard_manager = match globals.bind(qh, 2..=2, ()) {
214      Ok(manager) => manager,
215      Err(BindError::NotPresent | BindError::UnsupportedVersion) => globals.bind(qh, 1..=1, ())?,
216    };
217    let registry = globals.registry();
218    let seats = globals.contents().with_list(|globals| {
219      globals
220        .iter()
221        .filter(|global| global.interface == WlSeat::interface().name && global.version >= 2)
222        .map(|global| {
223          let seat = registry.bind(global.name, 2, qh, ());
224          (seat, SeatData::default())
225        })
226        .collect()
227    });
228
229    match selection_provider.current_selection() {
230      Some(providing) => event_hub.send(EventData::ClipboardProviding(providing)),
231      None => return Err(ClipboardError::Other("Empty provider".to_string())),
232    };
233
234    let context = Arc::new(Context::new(selection_provider));
235    let mut state = State {
236      context: context.clone(),
237      clipboard_manager,
238      seats,
239    };
240
241    queue.roundtrip(&mut state)?;
242
243    let handle = thread::spawn({
244      let cloned = context.clone();
245      move || {
246        if let Err(err) = try_run(queue, state) {
247          cloned.open.store(false, Ordering::Relaxed);
248          error!("Wayland clipboard error: {err}");
249        }
250      }
251    });
252
253    Ok(Clipboard {
254      context,
255      handle: Mutex::new(Some(handle)),
256    })
257  }
258
259  fn is_open(&self) -> bool {
260    self.context.is_open()
261  }
262
263  fn currently_providing(&self) -> Option<ClipboardProviding> {
264    self.context.currently_providing()
265  }
266
267  fn provide_next(&self) {
268    self.context.provide_next()
269  }
270
271  fn destroy(&self) {
272    self.context.destroy()
273  }
274
275  fn wait(&self) -> ClipboardResult<()> {
276    let mut maybe_handle = self.handle.lock()?;
277    if let Some(handle) = maybe_handle.take() {
278      handle
279        .join()
280        .map_err(|_| ClipboardError::Other("wait timeout".to_string()))?;
281    }
282    Ok(())
283  }
284}
285
286fn try_run(mut queue: EventQueue<State>, mut state: State) -> Result<(), Box<dyn Error>> {
287  let data_source = state.clipboard_manager.create_data_source(&queue.handle(), ());
288
289  debug!("Seats: {:?}", &state.seats);
290
291  for &mime_type in TEXT_MIMES {
292    data_source.offer(mime_type.to_string());
293  }
294
295  for (seat, data) in &mut state.seats {
296    let device = state
297      .clipboard_manager
298      .get_data_device(seat, &queue.handle(), seat.clone());
299    device.set_selection(Some(&data_source));
300    data.set_device(Some(device));
301  }
302
303  debug!("Start event loop");
304  state.context.open.store(true, Ordering::Relaxed);
305  while !state.context.cancel.load(Ordering::Relaxed) {
306    queue.blocking_dispatch(&mut state)?;
307  }
308  state.context.open.store(false, Ordering::Relaxed);
309  debug!("End event loop");
310
311  for data in state.seats.values_mut() {
312    data.set_device(None);
313  }
314  data_source.destroy();
315
316  Ok(())
317}
318
319#[derive(Default, Debug)]
320pub struct SeatData {
321  pub name: Option<String>,
322
323  pub device: Option<ZwlrDataControlDeviceV1>,
324}
325
326impl SeatData {
327  pub fn set_name(&mut self, name: String) {
328    self.name = Some(name)
329  }
330
331  pub fn set_device(&mut self, device: Option<ZwlrDataControlDeviceV1>) {
332    let old_device = self.device.take();
333    self.device = device;
334
335    if let Some(device) = old_device {
336      device.destroy();
337    }
338  }
339}