egui_winit/
clipboard.rs

1use raw_window_handle::RawDisplayHandle;
2
3/// Handles interfacing with the OS clipboard.
4///
5/// If the "clipboard" feature is off, or we cannot connect to the OS clipboard,
6/// then a fallback clipboard that just works within the same app is used instead.
7pub struct Clipboard {
8    #[cfg(all(feature = "arboard", not(target_os = "android")))]
9    arboard: Option<arboard::Clipboard>,
10
11    #[cfg(all(
12        any(
13            target_os = "linux",
14            target_os = "dragonfly",
15            target_os = "freebsd",
16            target_os = "netbsd",
17            target_os = "openbsd"
18        ),
19        feature = "smithay-clipboard"
20    ))]
21    smithay: Option<smithay_clipboard::Clipboard>,
22
23    /// Fallback manual clipboard.
24    clipboard: String,
25}
26
27impl Clipboard {
28    /// Construct a new instance
29    pub fn new(_raw_display_handle: Option<RawDisplayHandle>) -> Self {
30        Self {
31            #[cfg(all(feature = "arboard", not(target_os = "android")))]
32            arboard: init_arboard(),
33
34            #[cfg(all(
35                any(
36                    target_os = "linux",
37                    target_os = "dragonfly",
38                    target_os = "freebsd",
39                    target_os = "netbsd",
40                    target_os = "openbsd"
41                ),
42                feature = "smithay-clipboard"
43            ))]
44            smithay: init_smithay_clipboard(_raw_display_handle),
45
46            clipboard: Default::default(),
47        }
48    }
49
50    pub fn get(&mut self) -> Option<String> {
51        #[cfg(all(
52            any(
53                target_os = "linux",
54                target_os = "dragonfly",
55                target_os = "freebsd",
56                target_os = "netbsd",
57                target_os = "openbsd"
58            ),
59            feature = "smithay-clipboard"
60        ))]
61        if let Some(clipboard) = &mut self.smithay {
62            return match clipboard.load() {
63                Ok(text) => Some(text),
64                Err(err) => {
65                    log::error!("smithay paste error: {err}");
66                    None
67                }
68            };
69        }
70
71        #[cfg(all(feature = "arboard", not(target_os = "android")))]
72        if let Some(clipboard) = &mut self.arboard {
73            return match clipboard.get_text() {
74                Ok(text) => Some(text),
75                Err(err) => {
76                    log::error!("arboard paste error: {err}");
77                    None
78                }
79            };
80        }
81
82        Some(self.clipboard.clone())
83    }
84
85    pub fn set_text(&mut self, text: String) {
86        #[cfg(all(
87            any(
88                target_os = "linux",
89                target_os = "dragonfly",
90                target_os = "freebsd",
91                target_os = "netbsd",
92                target_os = "openbsd"
93            ),
94            feature = "smithay-clipboard"
95        ))]
96        if let Some(clipboard) = &mut self.smithay {
97            clipboard.store(text);
98            return;
99        }
100
101        #[cfg(all(feature = "arboard", not(target_os = "android")))]
102        if let Some(clipboard) = &mut self.arboard {
103            if let Err(err) = clipboard.set_text(text) {
104                log::error!("arboard copy/cut error: {err}");
105            }
106            return;
107        }
108
109        self.clipboard = text;
110    }
111
112    pub fn set_image(&mut self, image: &egui::ColorImage) {
113        #[cfg(all(feature = "arboard", not(target_os = "android")))]
114        if let Some(clipboard) = &mut self.arboard {
115            if let Err(err) = clipboard.set_image(arboard::ImageData {
116                width: image.width(),
117                height: image.height(),
118                bytes: std::borrow::Cow::Borrowed(bytemuck::cast_slice(&image.pixels)),
119            }) {
120                log::error!("arboard copy/cut error: {err}");
121            }
122            log::debug!("Copied image to clipboard");
123            return;
124        }
125
126        log::error!(
127            "Copying images is not supported. Enable the 'clipboard' feature of `egui-winit` to enable it."
128        );
129        _ = image;
130    }
131}
132
133#[cfg(all(feature = "arboard", not(target_os = "android")))]
134fn init_arboard() -> Option<arboard::Clipboard> {
135    profiling::function_scope!();
136
137    log::trace!("Initializing arboard clipboard…");
138    match arboard::Clipboard::new() {
139        Ok(clipboard) => Some(clipboard),
140        Err(err) => {
141            log::warn!("Failed to initialize arboard clipboard: {err}");
142            None
143        }
144    }
145}
146
147#[cfg(all(
148    any(
149        target_os = "linux",
150        target_os = "dragonfly",
151        target_os = "freebsd",
152        target_os = "netbsd",
153        target_os = "openbsd"
154    ),
155    feature = "smithay-clipboard"
156))]
157fn init_smithay_clipboard(
158    raw_display_handle: Option<RawDisplayHandle>,
159) -> Option<smithay_clipboard::Clipboard> {
160    #![allow(clippy::undocumented_unsafe_blocks)]
161
162    profiling::function_scope!();
163
164    if let Some(RawDisplayHandle::Wayland(display)) = raw_display_handle {
165        log::trace!("Initializing smithay clipboard…");
166        #[expect(unsafe_code)]
167        Some(unsafe { smithay_clipboard::Clipboard::new(display.display.as_ptr()) })
168    } else {
169        #[cfg(feature = "wayland")]
170        log::debug!("Cannot init smithay clipboard without a Wayland display handle");
171        #[cfg(not(feature = "wayland"))]
172        log::debug!(
173            "Cannot init smithay clipboard: the 'wayland' feature of 'egui-winit' is not enabled"
174        );
175        None
176    }
177}