Skip to main content

telar_platform_desktop/
clipboard.rs

1use std::sync::Mutex;
2
3use services_core::Clipboard;
4
5/// The desktop clipboard, over `arboard`.
6///
7/// The handle is kept rather than opened per call: on X11 and Wayland a clipboard *owner* has to stay alive to
8/// serve the bytes to whoever pastes, so a handle dropped after `set_text` takes the selection with it. Behind
9/// a `Mutex` because the trait is `Send + Sync` and `arboard`'s handle is not.
10pub struct DesktopClipboard(Mutex<Option<arboard::Clipboard>>);
11
12impl DesktopClipboard {
13    /// Opens the platform clipboard, or reports why not. Failing here is normal — a headless session has none.
14    pub fn new() -> Result<Self, arboard::Error> {
15        Ok(Self(Mutex::new(Some(arboard::Clipboard::new()?))))
16    }
17
18    /// Installs this as the app's clipboard, logging and carrying on where the platform has none: a missing
19    /// clipboard is a paste that does nothing, not a startup that fails.
20    pub fn install() {
21        match Self::new() {
22            Ok(clipboard) => services_core::set_clipboard(std::sync::Arc::new(clipboard)),
23            Err(e) => tracing::warn!("no system clipboard: {e}"),
24        }
25    }
26
27    fn with<R>(&self, f: impl FnOnce(&mut arboard::Clipboard) -> R) -> Option<R> {
28        self.0.lock().ok()?.as_mut().map(f)
29    }
30}
31
32impl Clipboard for DesktopClipboard {
33    fn text(&self) -> Option<String> {
34        self.with(|c| c.get_text().ok())?
35    }
36
37    fn set_text(&self, text: &str) {
38        if let Some(Err(e)) = self.with(|c| c.set_text(text.to_owned())) {
39            tracing::warn!("could not set the clipboard: {e}");
40        }
41    }
42}