Skip to main content

tui_lipan/clipboard/
service.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use crate::clipboard::error::ClipboardError;
5use crate::clipboard::provider::{ClipboardProvider, ImageContent};
6use crate::style::Style;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9/// Determines what Shift+Insert should paste.
10pub enum PasteShiftInsertBehavior {
11    /// Paste from the system clipboard.
12    Clipboard,
13    /// Paste from the primary selection (X11), if supported.
14    PrimarySelection,
15}
16
17#[derive(Debug, Clone)]
18/// Clipboard configuration for the runtime.
19pub struct ClipboardConfig {
20    /// When true, Ctrl+C copy is only consumed if selection exists.
21    pub enable_performable_ctrl_c_copy: bool,
22    /// Enable primary selection if supported.
23    pub enable_primary_selection: bool,
24    /// Configure Shift+Insert paste behavior.
25    pub paste_shift_insert_behavior: PasteShiftInsertBehavior,
26    /// Maximum number of bytes to paste at once (0 disables clamping).
27    pub paste_max_bytes: usize,
28    /// Emit OSC52 escape sequence on copy/cut.
29    pub enable_osc52: bool,
30    /// Maximum number of bytes for image paste (0 disables clamping).
31    pub paste_max_image_bytes: usize,
32    /// Duration in milliseconds for the selection copy flash (0 disables).
33    pub copy_feedback_duration_ms: u16,
34    /// Style merged onto the active text selection during the copy flash.
35    pub copy_feedback_style: Style,
36}
37
38impl Default for ClipboardConfig {
39    fn default() -> Self {
40        let enable_primary_selection = cfg!(target_os = "linux")
41            && (std::env::var("DISPLAY").is_ok() || std::env::var("WAYLAND_DISPLAY").is_ok());
42        let paste_shift_insert_behavior = if enable_primary_selection {
43            PasteShiftInsertBehavior::PrimarySelection
44        } else {
45            PasteShiftInsertBehavior::Clipboard
46        };
47
48        Self {
49            enable_performable_ctrl_c_copy: true,
50            enable_primary_selection,
51            paste_shift_insert_behavior,
52            paste_max_bytes: 1_000_000,
53            enable_osc52: true,
54            paste_max_image_bytes: 10_000_000,
55            copy_feedback_duration_ms: 150,
56            copy_feedback_style: Style::new().lighten_by(0.35),
57        }
58    }
59}
60
61pub type ClipboardReporter = Rc<dyn Fn(ClipboardError) + 'static>;
62
63pub struct ClipboardService {
64    provider: RefCell<Box<dyn ClipboardProvider>>,
65    reporter: ClipboardReporter,
66}
67
68impl ClipboardService {
69    pub fn new(provider: Box<dyn ClipboardProvider>, reporter: ClipboardReporter) -> Self {
70        Self {
71            provider: RefCell::new(provider),
72            reporter,
73        }
74    }
75
76    pub fn read_clipboard_text(&self) -> Result<String, ClipboardError> {
77        self.provider.borrow_mut().read_clipboard_text()
78    }
79
80    pub fn write_clipboard_text(&self, text: &str) -> Result<(), ClipboardError> {
81        self.provider.borrow_mut().write_clipboard_text(text)
82    }
83
84    #[cfg(all(target_arch = "wasm32", feature = "web"))]
85    pub(crate) fn set_clipboard_text_cache(&self, text: String) {
86        self.provider.borrow_mut().set_clipboard_text_cache(text);
87    }
88
89    pub fn read_primary_selection_text(&self) -> Result<String, ClipboardError> {
90        self.provider.borrow_mut().read_primary_selection_text()
91    }
92
93    pub fn write_primary_selection_text(&self, text: &str) -> Result<(), ClipboardError> {
94        self.provider
95            .borrow_mut()
96            .write_primary_selection_text(text)
97    }
98
99    pub fn supports_primary_selection(&self) -> bool {
100        self.provider.borrow().supports_primary_selection()
101    }
102
103    pub fn read_clipboard_image(&self) -> Result<ImageContent, ClipboardError> {
104        self.provider.borrow_mut().read_clipboard_image()
105    }
106
107    pub(crate) fn write_clipboard_image(
108        &self,
109        content: &ImageContent,
110    ) -> Result<(), ClipboardError> {
111        self.provider.borrow_mut().write_clipboard_image(content)
112    }
113
114    pub fn report_error(&self, error: ClipboardError) {
115        (self.reporter)(error);
116    }
117
118    #[cfg(all(target_arch = "wasm32", feature = "web"))]
119    pub(crate) fn replace_provider(&self, provider: Box<dyn ClipboardProvider>) {
120        *self.provider.borrow_mut() = provider;
121    }
122}
123
124pub fn default_clipboard_reporter() -> ClipboardReporter {
125    Rc::new(|error| {
126        let op = error.operation();
127        let message = match error {
128            ClipboardError::Unsupported { .. } => "unsupported clipboard operation".to_string(),
129            ClipboardError::Provider { message, .. } => message.to_string(),
130        };
131        crate::debug::internal_log!("[tui-lipan] clipboard {:?}: {}", op, message);
132    })
133}