native_windows_gui2/win32/
clipboard.rs

1use super::base_helper::to_utf16;
2use crate::controls::ControlHandle;
3use winapi::um::winnt::HANDLE;
4use winapi::um::winuser::{CF_BITMAP, CF_TEXT, CF_UNICODETEXT};
5
6#[derive(Copy, Clone)]
7pub enum ClipboardFormat {
8    /// ANSI text. You probably want to use `UnicodeText`.
9    Text,
10
11    /// UnicodeText. Equivalent to a OsString
12    UnicodeText,
13
14    /// A bitmap file
15    Bitmap,
16
17    /// Global clipboard format to share data between applications
18    /// The format name comparison is case-insensitive.
19    Global(&'static str),
20}
21
22impl ClipboardFormat {
23    fn into_raw(&self) -> u32 {
24        use ClipboardFormat::*;
25        use winapi::um::winuser::RegisterClipboardFormatW;
26
27        match self {
28            Text => CF_TEXT,
29            UnicodeText => CF_UNICODETEXT,
30            Bitmap => CF_BITMAP,
31            Global(v) => unsafe {
32                let v = to_utf16(v);
33                RegisterClipboardFormatW(v.as_ptr())
34            },
35        }
36    }
37}
38
39/// Wrapper over a clipboard global allocation handle.
40/// This value should be released with `release` or dropped before closing the clipboard.
41pub struct ClipboardData(HANDLE);
42
43impl ClipboardData {
44    pub unsafe fn cast<D: Copy>(&self) -> *const D {
45        self.0 as *const D
46    }
47    pub fn release(self) { /* See drop implementation */
48    }
49}
50
51impl Drop for ClipboardData {
52    fn drop(&mut self) {
53        unsafe {
54            ::winapi::um::winbase::GlobalUnlock(self.0);
55        }
56    }
57}
58
59/**
60A global object that wraps the system clipboard. It can be used to set or get the system cliboard content.
61
62It's important to keep in mind that there is no way to validate data sent through the clipboard API, as such this wrapper
63is still mostly unsafe and you must validate the data when reading.
64
65Note that NWG clipboard is intentionally keeps things simple and close to the metal. If you want to more robust API, then I recommend you look into https://github.com/DoumanAsh/clipboard-win
66
67Requires the feature "clipboard"
68
69Writing / Reading text
70
71```rust
72use native_windows_gui2 as nwg;
73
74fn clipboard_text(window: &nwg::Window) {
75    nwg::Clipboard::set_data_text(window, "Hello!");
76
77    let text = nwg::Clipboard::data_text(window);
78    assert!(text.is_some());
79    assert!(&text.unwrap() == &"Hello!");
80}
81```
82
83
84Writing / Reading custom data
85
86```rust
87use native_windows_gui2 as nwg;
88
89#[repr(C)]
90#[derive(Clone, Copy)]
91struct Hello {
92    foo: usize,
93    bar: [u16; 3]
94}
95
96fn write_custom_data(window: &nwg::Window) {
97    let data = Hello {
98        foo: 6529,
99        bar: [0, 100, 20]
100    };
101
102    nwg::Clipboard::open(window);
103    nwg::Clipboard::empty();
104    unsafe {
105        nwg::Clipboard::set_data(
106            nwg::ClipboardFormat::Global("Hello"),
107            &data as *const Hello,
108            1
109        );
110    }
111
112    nwg::Clipboard::close();
113}
114
115fn read_custom_data(window: &nwg::Window) -> Option<Hello> {
116    unsafe {
117        nwg::Clipboard::open(window);
118        let data = nwg::Clipboard::data(nwg::ClipboardFormat::Global("Hello"));
119        nwg::Clipboard::close();
120        data
121    }
122}
123
124fn read_custom_data_handle(window: &nwg::Window) -> Option<Hello> {
125    unsafe {
126        nwg::Clipboard::open(window);
127        let handle = nwg::Clipboard::data_handle(nwg::ClipboardFormat::Global("Hello"));
128        let data = match handle {
129            Some(h) => {
130                let data_ptr: *const Hello = h.cast();
131                let data = *data_ptr;
132                h.release();
133                Some(data)
134            },
135            None => None
136        };
137
138        nwg::Clipboard::close();
139        data
140    }
141}
142
143```
144*/
145pub struct Clipboard;
146
147impl Clipboard {
148    /**
149        Fill the clipboard with the selected text.
150        The data use the `ClipboardFormat::UnicodeText` format.
151
152        This is a high level function that handles `open` and `close`
153    */
154    pub fn set_data_text<'a, C: Into<ControlHandle>>(handle: C, text: &'a str) {
155        use core::{mem, ptr};
156        use winapi::shared::basetsd::SIZE_T;
157        use winapi::um::stringapiset::MultiByteToWideChar;
158        use winapi::um::winbase::{
159            GMEM_MOVEABLE, GlobalAlloc, GlobalFree, GlobalLock, GlobalUnlock,
160        };
161        use winapi::um::winnls::CP_UTF8;
162        use winapi::um::winuser::SetClipboardData;
163
164        let size = unsafe {
165            MultiByteToWideChar(
166                CP_UTF8,
167                0,
168                text.as_ptr() as *const _,
169                text.len() as _,
170                ptr::null_mut(),
171                0,
172            )
173        };
174
175        if size == 0 {
176            return;
177        }
178
179        let alloc_size = (mem::size_of::<u16>() * (size as usize + 1)) as SIZE_T;
180        let alloc = unsafe { GlobalAlloc(GMEM_MOVEABLE, alloc_size) };
181
182        unsafe {
183            let locked_ptr = GlobalLock(alloc) as *mut u16;
184            assert!(!locked_ptr.is_null());
185            MultiByteToWideChar(
186                CP_UTF8,
187                0,
188                text.as_ptr() as *const _,
189                text.len() as _,
190                locked_ptr,
191                size,
192            );
193            ptr::write(locked_ptr.offset(size as isize), 0);
194            GlobalUnlock(alloc);
195        }
196
197        Clipboard::open(handle);
198        Clipboard::empty();
199
200        unsafe {
201            if SetClipboardData(CF_UNICODETEXT, alloc as _).is_null() {
202                GlobalFree(alloc);
203            }
204        }
205
206        Clipboard::close();
207    }
208
209    /**
210        Return the current text value in the clipboard (if there is one).
211        This function will return the text if the clipboard has either the `UnicodeText` format or the `Text` format.
212
213        If the clipboard do not have a text format OR the text data is not a valid utf-8 sequence, this function will return `None`.
214    */
215    pub fn data_text<C: Into<ControlHandle>>(handle: C) -> Option<String> {
216        use ClipboardFormat::*;
217        let mut data = None;
218
219        Clipboard::open(handle);
220
221        unsafe {
222            if Clipboard::has_format(UnicodeText) {
223                let handle = Clipboard::data_handle(UnicodeText).unwrap();
224                data = from_wide_ptr(handle.cast());
225                handle.release();
226            } else if Clipboard::has_format(Text) {
227                let handle = Clipboard::data_handle(Text).unwrap();
228                data = from_ptr(handle.cast());
229                handle.release();
230            }
231        }
232
233        Clipboard::close();
234
235        data
236    }
237
238    /**
239        Remove the current data in the clipboard
240    */
241    pub fn clear<C: Into<ControlHandle>>() {
242        use std::ptr;
243        use winapi::um::winuser::{CloseClipboard, EmptyClipboard, OpenClipboard};
244        unsafe {
245            OpenClipboard(ptr::null_mut());
246            EmptyClipboard();
247            CloseClipboard();
248        }
249    }
250
251    /**
252        Opens the clipboard for examination and prevents other applications from modifying the clipboard content.
253        Another call to `close` should be made as soon as the application is done with the clipboard.
254
255        Parameters:
256            handle: A window control that will be identified as the current "owner" of the clipboard
257
258        This function will panic if the control is not HWND based.
259    */
260    pub fn open<C: Into<ControlHandle>>(handle: C) {
261        use winapi::um::winuser::OpenClipboard;
262        let handle = handle.into().hwnd().expect("Control should be a window");
263        unsafe {
264            OpenClipboard(handle);
265        }
266    }
267
268    /**
269        Places data on the clipboard in a specified clipboard format.
270
271        This method is unsafe because there is no way to ensure that data is valid.
272        If possible, it is recommended to use a higher level function such as `set_data_text` instead.
273
274        If the data will be used across applications, make sure that D has `repr(C)` for compatibility.
275
276        The clipboard must be open when calling this function.
277
278        * Note 1: `data` is copied into a global system allocation. It's ok to discard the data as soon as this function returns.
279        * Note 2: When copying text, the null byte must be included.
280    */
281    pub fn set_data<D: Copy>(fmt: ClipboardFormat, data: *const D, count: usize) {
282        use std::{mem, ptr};
283        use winapi::shared::basetsd::SIZE_T;
284        use winapi::um::winbase::{
285            GMEM_MOVEABLE, GlobalAlloc, GlobalFree, GlobalLock, GlobalUnlock,
286        };
287        use winapi::um::winuser::SetClipboardData;
288
289        let fmt = fmt.into_raw();
290        let alloc_size = (mem::size_of::<D>() * count) as SIZE_T;
291        let alloc = unsafe { GlobalAlloc(GMEM_MOVEABLE, alloc_size) };
292
293        unsafe {
294            ptr::copy_nonoverlapping(data, GlobalLock(alloc) as *mut D, count);
295        }
296        unsafe {
297            GlobalUnlock(alloc);
298        }
299
300        unsafe {
301            if SetClipboardData(fmt, alloc as HANDLE).is_null() {
302                GlobalFree(alloc);
303            }
304        }
305    }
306
307    /**
308        Check if the selected format is available in the clipboard.
309    */
310    pub fn has_format(fmt: ClipboardFormat) -> bool {
311        use winapi::um::winuser::IsClipboardFormatAvailable;
312
313        let selected_format = fmt.into_raw();
314        unsafe { IsClipboardFormatAvailable(selected_format) != 0 }
315    }
316
317    /**
318        Get the handle to the clipboard data and copy it's data into a new value of type `D`.
319        This function is very unsafe because it assumes that the handle points to the correct type.
320
321        The clipboard must be open when calling this function.
322
323        If no data is found with the selected clipboard format, `None` is returned.
324    */
325    pub fn data<D: Copy>(fmt: ClipboardFormat) -> Option<D> {
326        use std::{mem, ptr};
327        use winapi::um::winbase::{GlobalLock, GlobalUnlock};
328        use winapi::um::winuser::GetClipboardData;
329
330        let fmt = fmt.into_raw();
331        let handle = unsafe { GetClipboardData(fmt) };
332        if handle.is_null() {
333            return None;
334        }
335
336        let mut data = unsafe { mem::zeroed() };
337        unsafe {
338            ptr::copy_nonoverlapping(GlobalLock(handle) as *const D, &mut data, 1);
339        }
340        unsafe {
341            GlobalUnlock(handle);
342        }
343
344        Some(data)
345    }
346
347    /**
348        Gets the data handle of the clipboard, lock the memory and return it in a `ClipboardData` wrapper.
349        The returned data is read-only. The application should copy the data and release the handle as soon as possible.
350
351        The clipboard must be open when calling this function.
352
353        If no data is found with the selected clipboard format, `None` is returned.
354    */
355    pub fn data_handle(fmt: ClipboardFormat) -> Option<ClipboardData> {
356        use winapi::um::winbase::GlobalLock;
357        use winapi::um::winuser::GetClipboardData;
358
359        let fmt = fmt.into_raw();
360        let handle = unsafe { GetClipboardData(fmt) };
361        unsafe {
362            match handle.is_null() {
363                true => None,
364                false => Some(ClipboardData(GlobalLock(handle))),
365            }
366        }
367    }
368
369    /**
370        A window can place more than one clipboard object on the clipboard, each representing the same information in a different clipboard format.
371        Retrieves the number of different data formats currently on the clipboard.
372    */
373    pub fn count_clipboard_formats() -> u32 {
374        use winapi::um::winuser::CountClipboardFormats;
375        unsafe { CountClipboardFormats() as u32 }
376    }
377
378    /**
379        Empty the clipboard data.
380        This is a low-level function and `open` must have been called first.
381        To only clear the clipboard data use `clear`
382    */
383    pub fn empty() {
384        use winapi::um::winuser::EmptyClipboard;
385        unsafe {
386            EmptyClipboard();
387        }
388    }
389
390    /**
391        Close the clipboard after it was opened with the `open` function.
392    */
393    pub fn close() {
394        use winapi::um::winuser::CloseClipboard;
395        unsafe {
396            CloseClipboard();
397        }
398    }
399
400    /**
401        Return the handle of the window that owns the clipboard
402    */
403    pub fn ownder() -> ControlHandle {
404        let handle = unsafe { ::winapi::um::winuser::GetClipboardOwner() };
405        ControlHandle::Hwnd(handle)
406    }
407}
408
409fn from_wide_ptr(ptr: *const u16) -> Option<String> {
410    use std::ffi::OsString;
411    use std::os::windows::ffi::OsStringExt;
412    use std::slice::from_raw_parts;
413
414    let mut length: isize = 0;
415    unsafe {
416        while *&*ptr.offset(length) != 0 {
417            length += 1;
418        }
419    }
420
421    let array: &[u16] = unsafe { from_raw_parts(ptr, length as usize) };
422
423    OsString::from_wide(&array).into_string().ok()
424}
425
426fn from_ptr(ptr: *const u8) -> Option<String> {
427    use std::slice::from_raw_parts;
428    use std::str;
429
430    let mut length: isize = 0;
431    unsafe {
432        while *&*ptr.offset(length) != 0 {
433            length += 1;
434        }
435    }
436
437    let array: &[u8] = unsafe { from_raw_parts(ptr, length as usize) };
438
439    str::from_utf8(array).map(|s| s.into()).ok()
440}