1use std::collections::HashMap;
17use std::os::fd::AsFd;
18use std::os::unix::net::UnixStream;
19use std::sync::atomic::{AtomicBool, Ordering};
20use std::sync::{Arc, Mutex};
21use std::time::Duration;
22
23use pyo3::{Py, PyAny, Python};
24use wayland_client::backend::ObjectId;
25use wayland_client::protocol::{wl_registry, wl_seat};
26use wayland_client::{Connection, Dispatch, Proxy, QueueHandle, delegate_noop};
27use wayland_protocols::ext::data_control::v1::client::{
28 ext_data_control_device_v1::{self, ExtDataControlDeviceV1},
29 ext_data_control_manager_v1::ExtDataControlManagerV1,
30 ext_data_control_offer_v1::{self, ExtDataControlOfferV1},
31 ext_data_control_source_v1::{self, ExtDataControlSourceV1},
32};
33use wayland_protocols_wlr::data_control::v1::client::{
34 zwlr_data_control_device_v1::{self, ZwlrDataControlDeviceV1},
35 zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
36 zwlr_data_control_offer_v1::{self, ZwlrDataControlOfferV1},
37 zwlr_data_control_source_v1::{self, ZwlrDataControlSourceV1},
38};
39
40use crate::wayland::wlclient::{
41 IO_TIMEOUT, SyncState, bounded_roundtrip, impl_sync_callback, pipe_cloexec, read_fd_to_end,
42 wait_readable, write_fd_all,
43};
44
45const STOP_POLL: Duration = Duration::from_millis(500);
48
49enum DcManager {
55 Ext(ExtDataControlManagerV1),
56 Wlr(ZwlrDataControlManagerV1),
57}
58
59enum DcDevice {
60 Ext(ExtDataControlDeviceV1),
61 Wlr(ZwlrDataControlDeviceV1),
62}
63
64enum DcSource {
65 Ext(ExtDataControlSourceV1),
66 Wlr(ZwlrDataControlSourceV1),
67}
68
69#[derive(Clone)]
70enum DcOffer {
71 Ext(ExtDataControlOfferV1),
72 Wlr(ZwlrDataControlOfferV1),
73}
74
75impl DcManager {
76 fn get_data_device(&self, seat: &wl_seat::WlSeat, qh: &QueueHandle<DcState>) -> DcDevice {
77 match self {
78 DcManager::Ext(m) => DcDevice::Ext(m.get_data_device(seat, qh, ())),
79 DcManager::Wlr(m) => DcDevice::Wlr(m.get_data_device(seat, qh, ())),
80 }
81 }
82
83 fn create_data_source(&self, qh: &QueueHandle<DcState>) -> DcSource {
84 match self {
85 DcManager::Ext(m) => DcSource::Ext(m.create_data_source(qh, ())),
86 DcManager::Wlr(m) => DcSource::Wlr(m.create_data_source(qh, ())),
87 }
88 }
89}
90
91impl DcDevice {
92 fn set_selection(&self, source: Option<&DcSource>) {
93 match self {
94 DcDevice::Ext(d) => d.set_selection(source.map(|s| match s {
95 DcSource::Ext(s) => s,
96 DcSource::Wlr(_) => unreachable!("mixed data-control families"),
97 })),
98 DcDevice::Wlr(d) => d.set_selection(source.map(|s| match s {
99 DcSource::Wlr(s) => s,
100 DcSource::Ext(_) => unreachable!("mixed data-control families"),
101 })),
102 }
103 }
104
105 fn destroy(&self) {
106 match self {
107 DcDevice::Ext(d) => d.destroy(),
108 DcDevice::Wlr(d) => d.destroy(),
109 }
110 }
111}
112
113impl DcSource {
114 fn offer(&self, mime: String) {
115 match self {
116 DcSource::Ext(s) => s.offer(mime),
117 DcSource::Wlr(s) => s.offer(mime),
118 }
119 }
120
121 fn destroy(&self) {
122 match self {
123 DcSource::Ext(s) => s.destroy(),
124 DcSource::Wlr(s) => s.destroy(),
125 }
126 }
127}
128
129impl DcOffer {
130 fn id(&self) -> ObjectId {
131 match self {
132 DcOffer::Ext(o) => o.id(),
133 DcOffer::Wlr(o) => o.id(),
134 }
135 }
136
137 fn receive(&self, mime: String, fd: std::os::fd::BorrowedFd<'_>) {
138 match self {
139 DcOffer::Ext(o) => o.receive(mime, fd),
140 DcOffer::Wlr(o) => o.receive(mime, fd),
141 }
142 }
143
144 fn destroy(&self) {
145 match self {
146 DcOffer::Ext(o) => o.destroy(),
147 DcOffer::Wlr(o) => o.destroy(),
148 }
149 }
150}
151
152#[derive(Default)]
153struct DcState {
154 seat: Option<wl_seat::WlSeat>,
155 manager_ext: Option<ExtDataControlManagerV1>,
156 manager_wlr: Option<ZwlrDataControlManagerV1>,
157 offer_mimes: HashMap<ObjectId, Vec<String>>,
159 selection: Option<DcOffer>,
160 selection_changed: bool,
162 finished: bool,
164 canceled: bool,
166 serve: Vec<(String, Vec<u8>)>,
168 sync_done: bool,
169}
170
171impl SyncState for DcState {
172 fn sync_done_mut(&mut self) -> &mut bool {
173 &mut self.sync_done
174 }
175}
176impl_sync_callback!(DcState);
177
178impl DcState {
179 fn take_manager(&mut self) -> Result<DcManager, String> {
181 if let Some(m) = self.manager_ext.take() {
182 if let Some(w) = self.manager_wlr.take() {
183 w.destroy();
184 }
185 return Ok(DcManager::Ext(m));
186 }
187 self.manager_wlr.take().map(DcManager::Wlr).ok_or_else(|| {
188 "app compositor advertises neither ext_data_control_manager_v1 \
189 nor zwlr_data_control_manager_v1"
190 .to_string()
191 })
192 }
193
194 fn on_data_offer(&mut self, id: ObjectId) {
195 self.offer_mimes.entry(id).or_default();
196 }
197
198 fn on_offer_mime(&mut self, id: ObjectId, mime_type: String) {
199 self.offer_mimes.entry(id).or_default().push(mime_type);
200 }
201
202 fn on_selection(&mut self, offer: Option<DcOffer>) {
203 if let Some(old) = self.selection.take()
206 && offer.as_ref().map(|o| o.id()) != Some(old.id())
207 {
208 self.offer_mimes.remove(&old.id());
209 old.destroy();
210 }
211 self.selection = offer;
212 self.selection_changed = true;
213 }
214
215 fn on_primary_selection(&mut self, offer: Option<DcOffer>) {
219 if let Some(o) = offer
220 && self.selection.as_ref().map(|s| s.id()) != Some(o.id())
221 {
222 self.offer_mimes.remove(&o.id());
223 o.destroy();
224 }
225 }
226
227 fn on_send(&mut self, mime_type: &str, fd: std::os::fd::OwnedFd) {
228 if let Some((_, data)) = self.serve.iter().find(|(m, _)| m == mime_type) {
229 let _ = write_fd_all(&fd, data, IO_TIMEOUT);
230 }
231 }
233}
234
235impl Dispatch<wl_registry::WlRegistry, ()> for DcState {
236 fn event(
237 state: &mut Self,
238 registry: &wl_registry::WlRegistry,
239 event: wl_registry::Event,
240 _: &(),
241 _: &Connection,
242 qh: &QueueHandle<Self>,
243 ) {
244 if let wl_registry::Event::Global {
245 name, interface, ..
246 } = event
247 {
248 match interface.as_str() {
251 "wl_seat" if state.seat.is_none() => {
252 state.seat = Some(registry.bind(name, 1, qh, ()));
253 }
254 "ext_data_control_manager_v1" if state.manager_ext.is_none() => {
255 state.manager_ext = Some(registry.bind(name, 1, qh, ()));
256 }
257 "zwlr_data_control_manager_v1" if state.manager_wlr.is_none() => {
258 state.manager_wlr = Some(registry.bind(name, 1, qh, ()));
259 }
260 _ => {}
261 }
262 }
263 }
264}
265
266delegate_noop!(DcState: ignore wl_seat::WlSeat);
267delegate_noop!(DcState: ExtDataControlManagerV1);
268delegate_noop!(DcState: ZwlrDataControlManagerV1);
269
270impl Dispatch<ExtDataControlOfferV1, ()> for DcState {
271 fn event(
272 state: &mut Self,
273 offer: &ExtDataControlOfferV1,
274 event: ext_data_control_offer_v1::Event,
275 _: &(),
276 _: &Connection,
277 _: &QueueHandle<Self>,
278 ) {
279 if let ext_data_control_offer_v1::Event::Offer { mime_type } = event {
280 state.on_offer_mime(offer.id(), mime_type);
281 }
282 }
283}
284
285impl Dispatch<ZwlrDataControlOfferV1, ()> for DcState {
286 fn event(
287 state: &mut Self,
288 offer: &ZwlrDataControlOfferV1,
289 event: zwlr_data_control_offer_v1::Event,
290 _: &(),
291 _: &Connection,
292 _: &QueueHandle<Self>,
293 ) {
294 if let zwlr_data_control_offer_v1::Event::Offer { mime_type } = event {
295 state.on_offer_mime(offer.id(), mime_type);
296 }
297 }
298}
299
300impl Dispatch<ExtDataControlDeviceV1, ()> for DcState {
301 fn event(
302 state: &mut Self,
303 _: &ExtDataControlDeviceV1,
304 event: ext_data_control_device_v1::Event,
305 _: &(),
306 _: &Connection,
307 _: &QueueHandle<Self>,
308 ) {
309 match event {
310 ext_data_control_device_v1::Event::DataOffer { id } => {
311 state.on_data_offer(id.id());
312 }
313 ext_data_control_device_v1::Event::Selection { id } => {
314 state.on_selection(id.map(DcOffer::Ext));
315 }
316 ext_data_control_device_v1::Event::PrimarySelection { id } => {
317 state.on_primary_selection(id.map(DcOffer::Ext));
318 }
319 ext_data_control_device_v1::Event::Finished => {
320 state.finished = true;
321 }
322 _ => {}
323 }
324 }
325
326 wayland_client::event_created_child!(DcState, ExtDataControlDeviceV1, [
327 ext_data_control_device_v1::EVT_DATA_OFFER_OPCODE => (ExtDataControlOfferV1, ()),
328 ]);
329}
330
331impl Dispatch<ZwlrDataControlDeviceV1, ()> for DcState {
332 fn event(
333 state: &mut Self,
334 _: &ZwlrDataControlDeviceV1,
335 event: zwlr_data_control_device_v1::Event,
336 _: &(),
337 _: &Connection,
338 _: &QueueHandle<Self>,
339 ) {
340 match event {
341 zwlr_data_control_device_v1::Event::DataOffer { id } => {
342 state.on_data_offer(id.id());
343 }
344 zwlr_data_control_device_v1::Event::Selection { id } => {
345 state.on_selection(id.map(DcOffer::Wlr));
346 }
347 zwlr_data_control_device_v1::Event::Finished => {
348 state.finished = true;
349 }
350 _ => {}
351 }
352 }
353
354 wayland_client::event_created_child!(DcState, ZwlrDataControlDeviceV1, [
355 zwlr_data_control_device_v1::EVT_DATA_OFFER_OPCODE => (ZwlrDataControlOfferV1, ()),
356 ]);
357}
358
359impl Dispatch<ExtDataControlSourceV1, ()> for DcState {
360 fn event(
361 state: &mut Self,
362 _: &ExtDataControlSourceV1,
363 event: ext_data_control_source_v1::Event,
364 _: &(),
365 _: &Connection,
366 _: &QueueHandle<Self>,
367 ) {
368 match event {
369 ext_data_control_source_v1::Event::Send { mime_type, fd } => {
370 state.on_send(&mime_type, fd);
371 }
372 ext_data_control_source_v1::Event::Cancelled => {
373 state.canceled = true;
374 }
375 _ => {}
376 }
377 }
378}
379
380impl Dispatch<ZwlrDataControlSourceV1, ()> for DcState {
381 fn event(
382 state: &mut Self,
383 _: &ZwlrDataControlSourceV1,
384 event: zwlr_data_control_source_v1::Event,
385 _: &(),
386 _: &Connection,
387 _: &QueueHandle<Self>,
388 ) {
389 match event {
390 zwlr_data_control_source_v1::Event::Send { mime_type, fd } => {
391 state.on_send(&mime_type, fd);
392 }
393 zwlr_data_control_source_v1::Event::Cancelled => {
394 state.canceled = true;
395 }
396 _ => {}
397 }
398 }
399}
400
401fn open_device(
404 socket_path: &str,
405) -> Result<
406 (
407 Connection,
408 wayland_client::EventQueue<DcState>,
409 DcState,
410 DcDevice,
411 ),
412 String,
413> {
414 let stream =
415 UnixStream::connect(socket_path).map_err(|e| format!("connect {socket_path}: {e}"))?;
416 let conn = Connection::from_socket(stream).map_err(|e| format!("wayland setup: {e}"))?;
417 let mut queue = conn.new_event_queue();
418 let qh = queue.handle();
419 let _registry = conn.display().get_registry(&qh, ());
420 let mut state = DcState::default();
421 bounded_roundtrip(&conn, &mut queue, &mut state)?;
422 let seat = state
423 .seat
424 .clone()
425 .ok_or("app compositor advertises no wl_seat")?;
426 let manager = state.take_manager()?;
427 let device = manager.get_data_device(&seat, &qh);
428 bounded_roundtrip(&conn, &mut queue, &mut state)?;
429 Ok((conn, queue, state, device))
430}
431
432pub(crate) fn list_types(socket_path: &str) -> Result<Vec<String>, String> {
434 let (_conn, _queue, state, device) = open_device(socket_path)?;
435 let out = state
436 .selection
437 .as_ref()
438 .and_then(|o| state.offer_mimes.get(&o.id()).cloned())
439 .unwrap_or_default();
440 device.destroy();
441 Ok(out)
442}
443
444pub(crate) fn read(socket_path: &str, mime: &str) -> Result<Option<Vec<u8>>, String> {
447 let (conn, mut queue, mut state, device) = open_device(socket_path)?;
448 let Some(offer) = state.selection.clone() else {
449 device.destroy();
450 return Ok(None);
451 };
452 let offered = state
453 .offer_mimes
454 .get(&offer.id())
455 .is_some_and(|m| m.iter().any(|x| x == mime));
456 if !offered {
457 device.destroy();
458 return Ok(None);
459 }
460 let (rd, wr) = pipe_cloexec()?;
461 offer.receive(mime.to_string(), wr.as_fd());
462 queue.flush().map_err(|e| format!("flush: {e}"))?;
463 drop(wr);
464 let data = read_fd_to_end(&rd, IO_TIMEOUT)?;
467 let _ = bounded_roundtrip(&conn, &mut queue, &mut state);
468 device.destroy();
469 Ok(Some(data))
470}
471
472pub(crate) fn write(socket_path: &str, entries: Vec<(String, Vec<u8>)>) -> Result<(), String> {
479 let stream =
480 UnixStream::connect(socket_path).map_err(|e| format!("connect {socket_path}: {e}"))?;
481 let conn = Connection::from_socket(stream).map_err(|e| format!("wayland setup: {e}"))?;
482 let mut queue = conn.new_event_queue();
483 let qh = queue.handle();
484 let _registry = conn.display().get_registry(&qh, ());
485 let mut state = DcState::default();
486 bounded_roundtrip(&conn, &mut queue, &mut state)?;
487 let seat = state
488 .seat
489 .clone()
490 .ok_or("app compositor advertises no wl_seat")?;
491 let manager = state.take_manager()?;
492 let device = manager.get_data_device(&seat, &qh);
493 let source = manager.create_data_source(&qh);
494 for (mime, _) in &entries {
495 source.offer(mime.clone());
496 }
497 state.serve = entries;
498 device.set_selection(Some(&source));
499 bounded_roundtrip(&conn, &mut queue, &mut state)?;
500 std::thread::Builder::new()
501 .name("pf-dc-selection".into())
502 .spawn(move || {
503 if let Err(e) = serve_selection(conn, queue, state, device, source) {
504 eprintln!("[Clipboard] app-compositor selection serve ended: {e}");
505 }
506 })
507 .map_err(|e| format!("spawn: {e}"))?;
508 Ok(())
509}
510
511fn serve_selection(
512 conn: Connection,
513 mut queue: wayland_client::EventQueue<DcState>,
514 mut state: DcState,
515 device: DcDevice,
516 source: DcSource,
517) -> Result<(), String> {
518 while !state.canceled && !state.finished {
519 queue.flush().map_err(|e| format!("flush: {e}"))?;
522 if let Some(guard) = conn.prepare_read() {
523 use std::os::fd::AsRawFd;
524 if wait_readable(guard.connection_fd().as_raw_fd(), STOP_POLL)? {
525 guard.read().map_err(|e| format!("read: {e}"))?;
526 }
527 }
528 queue
529 .dispatch_pending(&mut state)
530 .map_err(|e| format!("dispatch: {e}"))?;
531 }
532 source.destroy();
533 device.destroy();
534 let _ = queue.flush();
535 Ok(())
536}
537
538pub(crate) fn clear(socket_path: &str) -> Result<(), String> {
540 let (conn, mut queue, mut state, device) = open_device(socket_path)?;
541 device.set_selection(None);
542 bounded_roundtrip(&conn, &mut queue, &mut state)?;
543 device.destroy();
544 Ok(())
545}
546
547struct WatchHandle {
548 stop: Arc<AtomicBool>,
549}
550
551static WATCHERS: Mutex<Option<HashMap<String, WatchHandle>>> = Mutex::new(None);
552
553pub(crate) fn watch(socket_path: &str, callback: Py<PyAny>) -> Result<(), String> {
557 let stop = Arc::new(AtomicBool::new(false));
558 {
559 let mut reg = WATCHERS.lock().unwrap();
560 let map = reg.get_or_insert_with(HashMap::new);
561 if let Some(old) = map.insert(socket_path.to_string(), WatchHandle { stop: stop.clone() }) {
562 old.stop.store(true, Ordering::Relaxed);
563 }
564 }
565 let path = socket_path.to_string();
566 std::thread::Builder::new()
567 .name("pf-dc-watch".into())
568 .spawn(move || {
569 if let Err(e) = watch_loop(&path, callback, &stop) {
570 eprintln!("[Clipboard] app-compositor watch ended: {e}");
571 }
572 })
573 .map_err(|e| format!("spawn: {e}"))?;
574 Ok(())
575}
576
577pub(crate) fn unwatch_all() {
580 let mut reg = WATCHERS.lock().unwrap();
581 if let Some(map) = reg.as_mut() {
582 for (_, handle) in map.drain() {
583 handle.stop.store(true, Ordering::Relaxed);
584 }
585 }
586}
587
588pub(crate) fn unwatch(socket_path: &str) {
590 let mut reg = WATCHERS.lock().unwrap();
591 if let Some(map) = reg.as_mut()
592 && let Some(handle) = map.remove(socket_path)
593 {
594 handle.stop.store(true, Ordering::Relaxed);
595 }
596}
597
598fn watch_loop(socket_path: &str, callback: Py<PyAny>, stop: &AtomicBool) -> Result<(), String> {
599 let (conn, mut queue, mut state, device) = open_device(socket_path)?;
600 while !stop.load(Ordering::Relaxed) && !state.finished {
601 if state.selection_changed {
602 state.selection_changed = false;
603 let mimes = state
604 .selection
605 .as_ref()
606 .and_then(|o| state.offer_mimes.get(&o.id()).cloned())
607 .unwrap_or_default();
608 if crate::PY_SHUTDOWN.load(Ordering::Relaxed) {
609 break;
610 }
611 Python::attach(|py| {
612 if let Err(e) = callback.call1(py, (mimes,)) {
613 e.print(py);
614 }
615 });
616 }
617 queue.flush().map_err(|e| format!("flush: {e}"))?;
618 if let Some(guard) = conn.prepare_read() {
619 use std::os::fd::AsRawFd;
620 if wait_readable(guard.connection_fd().as_raw_fd(), STOP_POLL)? {
621 guard.read().map_err(|e| format!("read: {e}"))?;
622 }
623 }
624 queue
625 .dispatch_pending(&mut state)
626 .map_err(|e| format!("dispatch: {e}"))?;
627 }
628 device.destroy();
629 let _ = queue.flush();
630 Ok(())
631}