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;
14pub mod uri_list;
15#[cfg(all(unix, not(target_os = "macos")))]
16pub(crate) mod wayland;
17#[cfg(all(unix, not(target_os = "macos")))]
18mod x11;
19
20#[cfg(any(windows, test))]
21#[path = "clipboard/windows/cf_html.rs"]
22mod cf_html;
23#[cfg(windows)]
24mod windows;
25#[cfg(all(test, not(windows)))]
26#[path = "clipboard/windows/drop_files.rs"]
27mod windows_drop_files;
28#[cfg(all(test, not(windows)))]
29#[path = "clipboard/windows/image.rs"]
30mod windows_image;
31
32/// Backends must suppress changes caused by their own `Set` commands so that
33/// remote clipboard writes cannot create a copy/announce feedback loop.
34#[derive(Debug)]
35pub struct Clipboard {
36    /// The backend can read and offer PNG images.
37    pub images: bool,
38    /// The backend can read copied file lists into `LocalChange::files` and
39    /// offer a [`LazyItem`] of mime `a7n_core::clipboard::FILES`. Its paste
40    /// reply is a `text/uri-list` ([`uri_list`]) of local paths.
41    pub files: bool,
42    pub changes: mpsc::Receiver<LocalChange>,
43    pub commands: mpsc::Sender<ClipboardCommand>,
44    /// The OS asked to paste an item that was offered without data.
45    pub pastes: mpsc::Receiver<PasteRequest>,
46}
47
48#[derive(Debug)]
49pub enum ClipboardCommand {
50    Set {
51        generation: Generation,
52        items: Vec<Item>,
53        /// Offered now, fetched when something pastes them.
54        lazy: Vec<LazyItem>,
55    },
56}
57
58/// An item whose data stays on the origin until a paste requests it.
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct LazyItem {
61    pub mime: String,
62    pub size: u64,
63    /// Index in the origin's `ClipboardAnnounce.items`.
64    pub item_index: u32,
65    /// For a file list: how many top-level entries a paste publishes, so a
66    /// backend that offers one pasteboard item per entry knows the count up
67    /// front. 0 for every other item.
68    pub top_level: u32,
69}
70
71impl LazyItem {
72    /// Files are limited by policy, not by the clipboard size limits.
73    pub(crate) fn within_limit(&self) -> bool {
74        self.mime == a7n_core::clipboard::FILES
75            || self.size <= a7n_core::clipboard::limit_for(&self.mime) as u64
76    }
77}
78
79/// How long a backend waits for a pasted file list: the reply arrives only
80/// after the files are downloaded. The daemon answers `None` earlier on failure.
81pub(crate) const FILE_PASTE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10 * 60);
82
83/// Backends block the paste until `reply` answers; `None` means the data is
84/// gone (newer generation, peer unreachable, integrity failure).
85#[derive(Debug)]
86pub struct PasteRequest {
87    pub generation: Generation,
88    pub item_index: u32,
89    pub reply: oneshot::Sender<Option<Vec<u8>>>,
90}
91
92#[derive(Debug, thiserror::Error)]
93pub enum ClipboardError {
94    #[error("clipboard is not supported on this platform")]
95    Unsupported,
96    #[cfg(target_os = "macos")]
97    #[error("macOS clipboard failed: {0}")]
98    Macos(#[from] std::io::Error),
99    #[cfg(windows)]
100    #[error("Windows clipboard failed: {0}")]
101    Windows(#[from] std::io::Error),
102    #[cfg(all(unix, not(target_os = "macos")))]
103    #[error("X11 clipboard failed: {0}")]
104    X11(#[from] crate::input::InputError),
105    #[cfg(all(unix, not(target_os = "macos")))]
106    #[error("Wayland clipboard failed: {0}")]
107    Wayland(#[from] wayland::WaylandError),
108}
109
110/// On Wayland, call `input::start()` first: on desktops without data-control
111/// the clipboard rides on the input backend's portal session.
112///
113/// # Errors
114/// Returns an error when the session is unsupported or the backend cannot start.
115pub fn start() -> Result<Clipboard, ClipboardError> {
116    #[cfg(all(unix, not(target_os = "macos")))]
117    {
118        use crate::input::{Backend, detect_backend};
119        match detect_backend() {
120            Backend::X11 => return Ok(x11::start()?),
121            Backend::Wayland => return Ok(wayland::start()?),
122            Backend::None => {}
123        }
124    }
125    #[cfg(windows)]
126    {
127        Ok(windows::start()?)
128    }
129    #[cfg(target_os = "macos")]
130    {
131        Ok(macos::start()?)
132    }
133    #[cfg(not(any(windows, target_os = "macos")))]
134    Err(ClipboardError::Unsupported)
135}