Skip to main content

a7n_platform/
clipboard.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3//! Channels shared by the daemon and future clipboard backend threads.
4
5use a7n_core::clipboard::{Generation, Item, LocalChange};
6use tokio::sync::{mpsc, oneshot};
7
8#[cfg(any(windows, target_os = "macos"))]
9mod fetch;
10#[cfg(all(unix, not(target_os = "macos")))]
11mod formats;
12#[cfg(target_os = "macos")]
13mod macos;
14#[cfg(all(unix, not(target_os = "macos")))]
15pub(crate) mod wayland;
16#[cfg(all(unix, not(target_os = "macos")))]
17mod x11;
18
19#[cfg(any(windows, test))]
20#[path = "clipboard/windows/cf_html.rs"]
21mod cf_html;
22#[cfg(windows)]
23mod windows;
24#[cfg(all(test, not(windows)))]
25#[path = "clipboard/windows/image.rs"]
26mod windows_image;
27
28/// Backends must suppress changes caused by their own `Set` commands so that
29/// remote clipboard writes cannot create a copy/announce feedback loop.
30#[derive(Debug)]
31pub struct Clipboard {
32    /// The backend can read and offer PNG images.
33    pub images: bool,
34    pub changes: mpsc::Receiver<LocalChange>,
35    pub commands: mpsc::Sender<ClipboardCommand>,
36    /// The OS asked to paste an item that was offered without data.
37    pub pastes: mpsc::Receiver<PasteRequest>,
38}
39
40#[derive(Debug)]
41pub enum ClipboardCommand {
42    Set {
43        generation: Generation,
44        items: Vec<Item>,
45        /// Offered now, fetched when something pastes them.
46        lazy: Vec<LazyItem>,
47    },
48}
49
50/// An item whose data stays on the origin until a paste requests it.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct LazyItem {
53    pub mime: String,
54    pub size: u64,
55    /// Index in the origin's `ClipboardAnnounce.items`.
56    pub item_index: u32,
57}
58
59/// Backends block the paste until `reply` answers; `None` means the data is
60/// gone (newer generation, peer unreachable, integrity failure).
61#[derive(Debug)]
62pub struct PasteRequest {
63    pub generation: Generation,
64    pub item_index: u32,
65    pub reply: oneshot::Sender<Option<Vec<u8>>>,
66}
67
68#[derive(Debug, thiserror::Error)]
69pub enum ClipboardError {
70    #[error("clipboard is not supported on this platform")]
71    Unsupported,
72    #[cfg(target_os = "macos")]
73    #[error("macOS clipboard failed: {0}")]
74    Macos(#[from] std::io::Error),
75    #[cfg(windows)]
76    #[error("Windows clipboard failed: {0}")]
77    Windows(#[from] std::io::Error),
78    #[cfg(all(unix, not(target_os = "macos")))]
79    #[error("X11 clipboard failed: {0}")]
80    X11(#[from] crate::input::InputError),
81    #[cfg(all(unix, not(target_os = "macos")))]
82    #[error("Wayland clipboard failed: {0}")]
83    Wayland(#[from] wayland::WaylandError),
84}
85
86/// On Wayland, call `input::start()` first: on desktops without data-control
87/// the clipboard rides on the input backend's portal session.
88///
89/// # Errors
90/// Returns an error when the session is unsupported or the backend cannot start.
91pub fn start() -> Result<Clipboard, ClipboardError> {
92    #[cfg(all(unix, not(target_os = "macos")))]
93    {
94        use crate::input::{Backend, detect_backend};
95        match detect_backend() {
96            Backend::X11 => return Ok(x11::start()?),
97            Backend::Wayland => return Ok(wayland::start()?),
98            Backend::None => {}
99        }
100    }
101    #[cfg(windows)]
102    {
103        Ok(windows::start()?)
104    }
105    #[cfg(target_os = "macos")]
106    {
107        Ok(macos::start()?)
108    }
109    #[cfg(not(any(windows, target_os = "macos")))]
110    Err(ClipboardError::Unsupported)
111}