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