rat_text/
clipboard.rs

1//!
2//! There are too many clipboard crates.
3//!
4
5use crate::TextError;
6use dyn_clone::{clone_box, DynClone};
7use std::error::Error;
8use std::fmt::{Debug, Display, Formatter};
9use std::sync::{Arc, Mutex, OnceLock};
10
11#[derive(Debug)]
12pub struct ClipboardError;
13
14impl Display for ClipboardError {
15    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
16        write!(f, "{:?}", self)
17    }
18}
19
20impl Error for ClipboardError {}
21
22impl From<ClipboardError> for TextError {
23    fn from(_value: ClipboardError) -> Self {
24        TextError::Clipboard
25    }
26}
27
28/// Access some clipboard.
29pub trait Clipboard: DynClone + Debug {
30    /// Get text from the clipboard.
31    fn get_string(&self) -> Result<String, ClipboardError>;
32
33    /// Set text from the clipboard.
34    fn set_string(&self, s: &str) -> Result<(), ClipboardError>;
35}
36
37static GLOBAL_CLIPBOARD: OnceLock<StaticClipboard> = OnceLock::new();
38
39/// Get a Clone of the global default clipboard.
40pub fn global_clipboard() -> Box<dyn Clipboard> {
41    let c = GLOBAL_CLIPBOARD.get_or_init(StaticClipboard::default);
42    Box::new(c.clone())
43}
44
45/// Change the global default clipboard.
46pub fn set_global_clipboard(clipboard: impl Clipboard + Send + 'static) {
47    let c = GLOBAL_CLIPBOARD.get_or_init(StaticClipboard::default);
48    c.replace(clipboard);
49}
50
51/// Clipboard that can be set as a static.
52/// It can replace the actual clipboard implementation at a later time.
53/// Initializes with a LocalClipboard.
54#[derive(Debug, Clone)]
55struct StaticClipboard {
56    clip: Arc<Mutex<Box<dyn Clipboard + Send>>>,
57}
58
59impl Default for StaticClipboard {
60    fn default() -> Self {
61        Self {
62            clip: Arc::new(Mutex::new(Box::new(LocalClipboard::new()))),
63        }
64    }
65}
66
67impl StaticClipboard {
68    /// Replace the static clipboard with the given one.
69    fn replace(&self, clipboard: impl Clipboard + Send + 'static) {
70        let mut clip = self.clip.lock().expect("clipboard-lock");
71        *clip = Box::new(clipboard);
72    }
73}
74
75impl Clipboard for StaticClipboard {
76    fn get_string(&self) -> Result<String, ClipboardError> {
77        self.clip.lock().expect("clipboard-lock").get_string()
78    }
79
80    fn set_string(&self, s: &str) -> Result<(), ClipboardError> {
81        self.clip.lock().expect("clipboard-lock").set_string(s)
82    }
83}
84
85/// Local clipboard.
86/// A string in disguise.
87#[derive(Debug, Default, Clone)]
88pub struct LocalClipboard {
89    text: Arc<Mutex<String>>,
90}
91
92impl LocalClipboard {
93    pub fn new() -> Self {
94        Self::default()
95    }
96}
97
98impl Clipboard for LocalClipboard {
99    fn get_string(&self) -> Result<String, ClipboardError> {
100        match self.text.lock() {
101            Ok(v) => Ok(v.clone()),
102            Err(_) => Err(ClipboardError),
103        }
104    }
105
106    fn set_string(&self, s: &str) -> Result<(), ClipboardError> {
107        match self.text.lock() {
108            Ok(mut v) => {
109                *v = s.to_string();
110                Ok(())
111            }
112            Err(_) => Err(ClipboardError),
113        }
114    }
115}
116
117impl Clone for Box<dyn Clipboard> {
118    fn clone(&self) -> Self {
119        clone_box(self.as_ref())
120    }
121}
122
123impl Clipboard for Box<dyn Clipboard> {
124    fn get_string(&self) -> Result<String, ClipboardError> {
125        self.as_ref().get_string()
126    }
127
128    fn set_string(&self, s: &str) -> Result<(), ClipboardError> {
129        self.as_ref().set_string(s)
130    }
131}