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}