wayland_clipboard_listener/
lib.rs1#![allow(clippy::needless_doctest_main)]
116
117mod constvar;
118mod dispatch;
119
120#[cfg(feature = "wlr-data-control")]
121mod dispatch_wlr;
122
123use std::io::Read;
124
125use wayland_client::{protocol::wl_seat, Connection, DispatchError, EventQueue};
126
127use wayland_protocols::ext::data_control::v1::client::{
128 ext_data_control_device_v1, ext_data_control_manager_v1,
129};
130
131#[cfg(feature = "wlr-data-control")]
132use wayland_protocols_wlr::data_control::v1::client::{
133 zwlr_data_control_device_v1, zwlr_data_control_manager_v1,
134};
135
136use std::sync::{Arc, Mutex};
137
138use thiserror::Error;
139
140use constvar::{IMAGE, TEXT};
141
142#[derive(Debug)]
148pub enum WlListenType {
149 ListenOnSelect,
150 ListenOnCopy,
151}
152
153#[derive(Error, Debug)]
159pub enum WlClipboardListenerError {
160 #[error("Init Failed")]
161 InitFailed(String),
162 #[error("Error during queue")]
163 QueueError(String),
164 #[error("DispatchError")]
165 DispatchError(#[from] DispatchError),
166 #[error("PipeError")]
167 PipeError,
168}
169
170#[derive(Debug)]
175pub struct ClipBoardListenContext {
176 pub mime_type: String,
177 pub context: Vec<u8>,
178}
179
180#[derive(Debug)]
181pub struct ClipBoardListenMessage {
182 pub mime_types: Vec<String>,
183 pub context: ClipBoardListenContext,
184}
185
186pub struct WlClipboardPasteStream {
189 inner: WlClipboardListenerStream,
190}
191
192impl WlClipboardPasteStream {
193 pub fn init(listentype: WlListenType) -> Result<Self, WlClipboardListenerError> {
197 Ok(Self {
198 inner: WlClipboardListenerStream::init(listentype)?,
199 })
200 }
201
202 pub fn paste_stream(&mut self) -> &mut WlClipboardListenerStream {
214 &mut self.inner
215 }
216 pub fn get_clipboard(&mut self) -> Result<ClipBoardListenMessage, WlClipboardListenerError> {
218 self.inner.get_clipboard_sync()
219 }
220 pub fn try_get_clipboard(
222 &mut self,
223 ) -> Result<Option<ClipBoardListenMessage>, WlClipboardListenerError> {
224 self.inner.try_get_clipboard()
225 }
226
227 pub fn set_priority(&mut self, val: Vec<String>) {
229 self.inner.set_priority = Some(val);
230 }
231}
232
233pub struct WlClipboardCopyStream {
236 inner: WlClipboardListenerStream,
237}
238
239impl WlClipboardCopyStream {
240 pub fn init() -> Result<Self, WlClipboardListenerError> {
242 Ok(Self {
243 inner: WlClipboardListenerStream::init(WlListenType::ListenOnCopy)?,
244 })
245 }
246
247 pub fn copy_to_clipboard(
264 &mut self,
265 data: Vec<u8>,
266 mimetypes: Vec<&str>,
267 useprimary: bool,
268 ) -> Result<(), WlClipboardListenerError> {
269 self.inner.copy_to_clipboard(data, mimetypes, useprimary)
270 }
271}
272pub struct WlClipboardListenerStream {
276 listentype: WlListenType,
277 seat: Option<wl_seat::WlSeat>,
278 seat_name: Option<String>,
279 data_manager: Option<ext_data_control_manager_v1::ExtDataControlManagerV1>,
280 data_device: Option<ext_data_control_device_v1::ExtDataControlDeviceV1>,
281 mime_types: Vec<String>,
282 set_priority: Option<Vec<String>>,
283 pipereader: Option<os_pipe::PipeReader>,
284 current_type: Option<String>,
285 queue: Option<Arc<Mutex<EventQueue<Self>>>>,
286 copy_data: Option<Vec<u8>>,
287 copy_cancelled: bool,
288}
289
290impl Iterator for WlClipboardListenerStream {
291 type Item = Result<ClipBoardListenMessage, WlClipboardListenerError>;
292
293 fn next(&mut self) -> Option<Self::Item> {
294 let data = self.get_clipboard_sync();
295 if let Err(WlClipboardListenerError::DispatchError(err)) = data.as_ref() {
296 panic!("error with wayland side: {err}");
297 }
298 Some(data)
299 }
300}
301
302impl WlClipboardListenerStream {
303 fn init(listentype: WlListenType) -> Result<Self, WlClipboardListenerError> {
306 let conn = Connection::connect_to_env().map_err(|_| {
307 WlClipboardListenerError::InitFailed("Cannot connect to wayland".to_string())
308 })?;
309
310 let mut event_queue = conn.new_event_queue();
311 let qhandle = event_queue.handle();
312
313 let display = conn.display();
314
315 display.get_registry(&qhandle, ());
316 let mut state = WlClipboardListenerStream {
317 listentype,
318 seat: None,
319 seat_name: None,
320 data_manager: None,
321 data_device: None,
322 mime_types: Vec::new(),
323 set_priority: None,
324 pipereader: None,
325 current_type: None,
326 queue: None,
327 copy_data: None,
328 copy_cancelled: false,
329 };
330
331 event_queue.blocking_dispatch(&mut state).map_err(|e| {
332 WlClipboardListenerError::InitFailed(format!("Initial dispatch failed: {e}"))
333 })?;
334
335 if !state.device_ready() {
336 return Err(WlClipboardListenerError::InitFailed(
337 "Cannot get seat and data manager".to_string(),
338 ));
339 }
340
341 while state.seat_name.is_none() {
342 event_queue.roundtrip(&mut state).map_err(|_| {
343 WlClipboardListenerError::InitFailed("Cannot roundtrip during init".to_string())
344 })?;
345 }
346
347 state.set_data_device(&qhandle);
348 state.queue = Some(Arc::new(Mutex::new(event_queue)));
349 Ok(state)
350 }
351
352 fn copy_to_clipboard(
357 &mut self,
358 data: Vec<u8>,
359 mimetypes: Vec<&str>,
360 useprimary: bool,
361 ) -> Result<(), WlClipboardListenerError> {
362 let eventqh = self.queue.clone().unwrap();
363 let mut event_queue = eventqh.lock().unwrap();
364 let qh = event_queue.handle();
365 let manager = self.data_manager.as_ref().unwrap();
366 let source = manager.create_data_source(&qh, ());
367 let device = self.data_device.as_ref().unwrap();
368
369 for mimetype in mimetypes {
370 source.offer(mimetype.to_string());
371 }
372
373 if useprimary {
374 device.set_primary_selection(Some(&source));
375 } else {
376 device.set_selection(Some(&source));
377 }
378
379 self.copy_data = Some(data);
380 while !self.copy_cancelled {
381 event_queue
382 .blocking_dispatch(self)
383 .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
384 }
385 self.copy_data = None;
386 self.copy_cancelled = false;
387 Ok(())
388 }
389
390 fn get_clipboard_sync(&mut self) -> Result<ClipBoardListenMessage, WlClipboardListenerError> {
393 let queue = self.queue.clone().unwrap();
395 let mut queue = queue
396 .lock()
397 .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
398 while self.pipereader.is_none() {
399 queue.blocking_dispatch(self)?;
400 }
401
402 queue.roundtrip(self)?;
404 let mut read = self.pipereader.as_ref().unwrap();
405 let mut context = vec![];
406 read.read_to_end(&mut context)
407 .map_err(|_| WlClipboardListenerError::PipeError)?;
408 self.pipereader = None;
409 let mime_types = self.mime_types.clone();
410 self.mime_types.clear();
411 let mime_type = self.current_type.clone().unwrap();
412 Ok(ClipBoardListenMessage {
413 mime_types,
414 context: ClipBoardListenContext { mime_type, context },
415 })
416 }
417
418 fn try_get_clipboard(
421 &mut self,
422 ) -> Result<Option<ClipBoardListenMessage>, WlClipboardListenerError> {
423 let queue = self.queue.clone().unwrap();
425 let mut queue = queue
426 .lock()
427 .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
428 queue.blocking_dispatch(self)?;
429 if self.pipereader.is_some() {
430 queue.roundtrip(self)?;
432 let mut read = self.pipereader.as_ref().unwrap();
433 let mut context = vec![];
434 read.read_to_end(&mut context)
435 .map_err(|_| WlClipboardListenerError::PipeError)?;
436 self.pipereader = None;
437 let mime_types = self.mime_types.clone();
438 self.mime_types.clear();
439 let mime_type = self.current_type.clone().unwrap();
440 Ok(Some(ClipBoardListenMessage {
441 mime_types,
442 context: ClipBoardListenContext { mime_type, context },
443 }))
444 } else {
445 Ok(None)
446 }
447 }
448
449 fn device_ready(&self) -> bool {
450 self.seat.is_some() && self.data_manager.is_some()
451 }
452
453 fn set_data_device(&mut self, qh: &wayland_client::QueueHandle<Self>) {
454 let seat = self.seat.as_ref().unwrap();
455 let manager = self.data_manager.as_ref().unwrap();
456 let device = manager.get_data_device(seat, qh, ());
457
458 self.data_device = Some(device);
459 }
460
461 fn is_text(&self) -> bool {
462 !self.mime_types.is_empty()
463 && self.mime_types.contains(&TEXT.to_string())
464 && !self.mime_types.contains(&IMAGE.to_string())
465 }
466}
467
468#[cfg(feature = "wlr-data-control")]
470pub struct WlClipboardPasteStreamWlr {
471 inner: WlClipboardListenerStreamWlr,
472}
473
474#[cfg(feature = "wlr-data-control")]
475impl WlClipboardPasteStreamWlr {
476 pub fn init(listentype: WlListenType) -> Result<Self, WlClipboardListenerError> {
480 Ok(Self {
481 inner: WlClipboardListenerStreamWlr::init(listentype)?,
482 })
483 }
484
485 pub fn paste_stream(&mut self) -> &mut WlClipboardListenerStreamWlr {
497 &mut self.inner
498 }
499 pub fn get_clipboard(&mut self) -> Result<ClipBoardListenMessage, WlClipboardListenerError> {
501 self.inner.get_clipboard_sync()
502 }
503 pub fn try_get_clipboard(
505 &mut self,
506 ) -> Result<Option<ClipBoardListenMessage>, WlClipboardListenerError> {
507 self.inner.try_get_clipboard()
508 }
509
510 pub fn set_priority(&mut self, val: Vec<String>) {
512 self.inner.set_priority = Some(val);
513 }
514}
515
516#[cfg(feature = "wlr-data-control")]
519pub struct WlClipboardCopyStreamWlr {
520 inner: WlClipboardListenerStreamWlr,
521}
522
523#[cfg(feature = "wlr-data-control")]
524impl WlClipboardCopyStreamWlr {
525 pub fn init() -> Result<Self, WlClipboardListenerError> {
527 Ok(Self {
528 inner: WlClipboardListenerStreamWlr::init(WlListenType::ListenOnCopy)?,
529 })
530 }
531
532 pub fn copy_to_clipboard(
549 &mut self,
550 data: Vec<u8>,
551 mimetypes: Vec<&str>,
552 useprimary: bool,
553 ) -> Result<(), WlClipboardListenerError> {
554 self.inner.copy_to_clipboard(data, mimetypes, useprimary)
555 }
556}
557
558#[cfg(feature = "wlr-data-control")]
562pub struct WlClipboardListenerStreamWlr {
563 listentype: WlListenType,
564 seat: Option<wl_seat::WlSeat>,
565 seat_name: Option<String>,
566 data_manager: Option<zwlr_data_control_manager_v1::ZwlrDataControlManagerV1>,
567 data_device: Option<zwlr_data_control_device_v1::ZwlrDataControlDeviceV1>,
568 mime_types: Vec<String>,
569 set_priority: Option<Vec<String>>,
570 pipereader: Option<os_pipe::PipeReader>,
571 current_type: Option<String>,
572 queue: Option<Arc<Mutex<EventQueue<Self>>>>,
573 copy_data: Option<Vec<u8>>,
574 copy_cancelled: bool,
575}
576
577#[cfg(feature = "wlr-data-control")]
578impl Iterator for WlClipboardListenerStreamWlr {
579 type Item = Result<ClipBoardListenMessage, WlClipboardListenerError>;
580
581 fn next(&mut self) -> Option<Self::Item> {
582 let data = self.get_clipboard_sync();
583 if let Err(WlClipboardListenerError::DispatchError(err)) = data.as_ref() {
584 panic!("error with wayland side: {err}");
585 }
586 Some(data)
587 }
588}
589
590#[cfg(feature = "wlr-data-control")]
591impl WlClipboardListenerStreamWlr {
592 fn init(listentype: WlListenType) -> Result<Self, WlClipboardListenerError> {
595 let conn = Connection::connect_to_env().map_err(|_| {
596 WlClipboardListenerError::InitFailed("Cannot connect to wayland".to_string())
597 })?;
598
599 let mut event_queue = conn.new_event_queue();
600 let qhandle = event_queue.handle();
601
602 let display = conn.display();
603
604 display.get_registry(&qhandle, ());
605 let mut state = WlClipboardListenerStreamWlr {
606 listentype,
607 seat: None,
608 seat_name: None,
609 data_manager: None,
610 data_device: None,
611 mime_types: Vec::new(),
612 set_priority: None,
613 pipereader: None,
614 current_type: None,
615 queue: None,
616 copy_data: None,
617 copy_cancelled: false,
618 };
619
620 event_queue.blocking_dispatch(&mut state).map_err(|e| {
621 WlClipboardListenerError::InitFailed(format!("Initial dispatch failed: {e}"))
622 })?;
623
624 if !state.device_ready() {
625 return Err(WlClipboardListenerError::InitFailed(
626 "Cannot get seat and data manager".to_string(),
627 ));
628 }
629
630 while state.seat_name.is_none() {
631 event_queue.roundtrip(&mut state).map_err(|_| {
632 WlClipboardListenerError::InitFailed("Cannot roundtrip during init".to_string())
633 })?;
634 }
635
636 state.set_data_device(&qhandle);
637 state.queue = Some(Arc::new(Mutex::new(event_queue)));
638 Ok(state)
639 }
640
641 fn copy_to_clipboard(
646 &mut self,
647 data: Vec<u8>,
648 mimetypes: Vec<&str>,
649 useprimary: bool,
650 ) -> Result<(), WlClipboardListenerError> {
651 let eventqh = self.queue.clone().unwrap();
652 let mut event_queue = eventqh.lock().unwrap();
653 let qh = event_queue.handle();
654 let manager = self.data_manager.as_ref().unwrap();
655 let source = manager.create_data_source(&qh, ());
656 let device = self.data_device.as_ref().unwrap();
657
658 for mimetype in mimetypes {
659 source.offer(mimetype.to_string());
660 }
661
662 if useprimary {
663 device.set_primary_selection(Some(&source));
664 } else {
665 device.set_selection(Some(&source));
666 }
667
668 self.copy_data = Some(data);
669 while !self.copy_cancelled {
670 event_queue
671 .blocking_dispatch(self)
672 .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
673 }
674 self.copy_data = None;
675 self.copy_cancelled = false;
676 Ok(())
677 }
678
679 fn get_clipboard_sync(&mut self) -> Result<ClipBoardListenMessage, WlClipboardListenerError> {
682 let queue = self.queue.clone().unwrap();
684 let mut queue = queue
685 .lock()
686 .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
687 while self.pipereader.is_none() {
688 queue.blocking_dispatch(self)?;
689 }
690
691 queue.roundtrip(self)?;
693 let mut read = self.pipereader.as_ref().unwrap();
694 let mut context = vec![];
695 read.read_to_end(&mut context)
696 .map_err(|_| WlClipboardListenerError::PipeError)?;
697 self.pipereader = None;
698 let mime_types = self.mime_types.clone();
699 self.mime_types.clear();
700 let mime_type = self.current_type.clone().unwrap();
701 Ok(ClipBoardListenMessage {
702 mime_types,
703 context: ClipBoardListenContext { mime_type, context },
704 })
705 }
706
707 fn try_get_clipboard(
710 &mut self,
711 ) -> Result<Option<ClipBoardListenMessage>, WlClipboardListenerError> {
712 let queue = self.queue.clone().unwrap();
714 let mut queue = queue
715 .lock()
716 .map_err(|e| WlClipboardListenerError::QueueError(e.to_string()))?;
717 queue.blocking_dispatch(self)?;
718 if self.pipereader.is_some() {
719 queue.roundtrip(self)?;
721 let mut read = self.pipereader.as_ref().unwrap();
722 let mut context = vec![];
723 read.read_to_end(&mut context)
724 .map_err(|_| WlClipboardListenerError::PipeError)?;
725 self.pipereader = None;
726 let mime_types = self.mime_types.clone();
727 self.mime_types.clear();
728 let mime_type = self.current_type.clone().unwrap();
729 Ok(Some(ClipBoardListenMessage {
730 mime_types,
731 context: ClipBoardListenContext { mime_type, context },
732 }))
733 } else {
734 Ok(None)
735 }
736 }
737
738 fn device_ready(&self) -> bool {
739 self.seat.is_some() && self.data_manager.is_some()
740 }
741
742 fn set_data_device(&mut self, qh: &wayland_client::QueueHandle<Self>) {
743 let seat = self.seat.as_ref().unwrap();
744 let manager = self.data_manager.as_ref().unwrap();
745 let device = manager.get_data_device(seat, qh, ());
746
747 self.data_device = Some(device);
748 }
749
750 fn is_text(&self) -> bool {
751 !self.mime_types.is_empty()
752 && self.mime_types.contains(&TEXT.to_string())
753 && !self.mime_types.contains(&IMAGE.to_string())
754 }
755}