t_rust_less_lib/clipboard/
unix_wayland.rs1use 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}