1 //!Raw bindings to Windows clipboard.
2 //!
3 //!## General information
4 //!
5 //!All pre & post conditions are stated in description of functions.
6 //!
7 //!### Open clipboard
8 //! To access any information inside clipboard it is necessary to open it by means of
9 //! [open()](fn.open.html).
10 //!
11 //! After that Clipboard cannot be opened any more until [close()](fn.close.html) is called.
12 
13 extern crate winapi;
14 
15 use std::cmp;
16 use std::ffi::OsString;
17 use std::os::windows::ffi::{
18     OsStrExt,
19     OsStringExt
20 };
21 use std::os::raw::{
22     c_int,
23     c_uint,
24     c_void,
25 };
26 use std::path::PathBuf;
27 use std::ptr;
28 use std::io;
29 
30 use crate::utils;
31 use crate::formats;
32 
33 use winapi::shared::basetsd::{
34     SIZE_T
35 };
36 
37 use winapi::shared::ntdef::HANDLE;
38 
39 use winapi::um::shellapi::{
40     DragQueryFileW,
41     HDROP
42 };
43 
44 use winapi::um::winbase::{
45     GlobalSize,
46     GlobalLock,
47     GlobalUnlock,
48     GlobalAlloc,
49     GlobalFree
50 };
51 
52 use winapi::um::winuser::{
53     OpenClipboard,
54     CloseClipboard,
55     EmptyClipboard,
56     GetClipboardSequenceNumber,
57     CountClipboardFormats,
58     IsClipboardFormatAvailable,
59     EnumClipboardFormats,
60     RegisterClipboardFormatW,
61     GetClipboardFormatNameW,
62     GetClipboardData,
63     SetClipboardData
64 };
65 
66 #[inline]
67 ///Opens clipboard.
68 ///
69 ///Wrapper around ```OpenClipboard```.
70 ///
71 ///# Pre-conditions:
72 ///
73 ///* Clipboard is not opened yet.
74 ///
75 ///# Post-conditions:
76 ///
77 ///* Clipboard can be accessed for read and write operations.
open() -> io::Result<()>78 pub fn open() -> io::Result<()> {
79     unsafe {
80         if OpenClipboard(ptr::null_mut()) == 0 {
81             return Err(utils::get_last_error());
82         }
83     }
84 
85     Ok(())
86 }
87 
88 #[inline]
89 ///Closes clipboard.
90 ///
91 ///Wrapper around ```CloseClipboard```.
92 ///
93 ///# Pre-conditions:
94 ///
95 ///* [open()](fn.open.html) has been called.
close() -> io::Result<()>96 pub fn close() -> io::Result<()> {
97     unsafe {
98         if CloseClipboard() == 0 {
99             return Err(utils::get_last_error());
100         }
101     }
102 
103     Ok(())
104 }
105 
106 #[inline]
107 ///Empties clipboard.
108 ///
109 ///Wrapper around ```EmptyClipboard```.
110 ///
111 ///# Pre-conditions:
112 ///
113 ///* [open()](fn.open.html) has been called.
empty() -> io::Result<()>114 pub fn empty() -> io::Result<()> {
115     unsafe {
116         if EmptyClipboard() == 0 {
117             return Err(utils::get_last_error());
118         }
119     }
120 
121     Ok(())
122 }
123 
124 #[inline]
125 ///Retrieves clipboard sequence number.
126 ///
127 ///Wrapper around ```GetClipboardSequenceNumber```.
128 ///
129 ///# Returns:
130 ///
131 ///* ```Some``` Contains return value of ```GetClipboardSequenceNumber```.
132 ///* ```None``` In case if you do not have access. It means that zero is returned by system.
seq_num() -> Option<u32>133 pub fn seq_num() -> Option<u32> {
134     let result: u32 = unsafe { GetClipboardSequenceNumber() };
135 
136     if result == 0 {
137         return None;
138     }
139 
140     Some(result)
141 }
142 
143 #[inline]
144 ///Retrieves size of clipboard data for specified format.
145 ///
146 ///# Pre-conditions:
147 ///
148 ///* [open()](fn.open.html) has been called.
149 ///
150 ///# Returns:
151 ///
152 ///Size in bytes if format presents on clipboard.
153 ///
154 ///# Unsafety:
155 ///
156 ///In some cases, clipboard content might be so invalid that it crashes on `GlobalSize` (e.g.
157 ///Bitmap)
158 ///
159 ///Due to that function is marked as unsafe
size_unsafe(format: u32) -> Option<usize>160 pub unsafe fn size_unsafe(format: u32) -> Option<usize> {
161     let clipboard_data = GetClipboardData(format);
162 
163     match clipboard_data.is_null() {
164         false => Some(GlobalSize(clipboard_data) as usize),
165         true => None,
166     }
167 }
168 
169 #[inline]
170 ///Retrieves size of clipboard data for specified format.
171 ///
172 ///# Pre-conditions:
173 ///
174 ///* [open()](fn.open.html) has been called.
175 ///
176 ///# Returns:
177 ///
178 ///Size in bytes if format presents on clipboard.
size(format: u32) -> Option<usize>179 pub fn size(format: u32) -> Option<usize> {
180     let clipboard_data = unsafe {GetClipboardData(format)};
181 
182     if clipboard_data.is_null() {
183         return None
184     }
185 
186     unsafe {
187         if GlobalLock(clipboard_data).is_null() {
188             return None;
189         }
190 
191         let result = Some(GlobalSize(clipboard_data) as usize);
192 
193         GlobalUnlock(clipboard_data);
194 
195         result
196     }
197 }
198 
199 ///Retrieves raw pointer to clipboard data.
200 ///
201 ///Wrapper around ```GetClipboardData```.
202 ///
203 ///# Pre-conditions:
204 ///
205 ///* [open()](fn.open.html) has been called.
get_clipboard_data(format: c_uint) -> io::Result<ptr::NonNull<c_void>>206 pub fn get_clipboard_data(format: c_uint) -> io::Result<ptr::NonNull<c_void>> {
207     let clipboard_data = unsafe { GetClipboardData(format) };
208 
209     match ptr::NonNull::new(clipboard_data as *mut c_void) {
210         Some(ptr) => Ok(ptr),
211         None => Err(utils::get_last_error()),
212     }
213 }
214 
215 ///Retrieves data of specified format from clipboard.
216 ///
217 ///Wrapper around ```GetClipboardData```.
218 ///
219 ///# Pre-conditions:
220 ///
221 ///* [open()](fn.open.html) has been called.
222 ///
223 ///# Note:
224 ///
225 ///Clipboard data is truncated by the size of provided storage.
226 ///
227 ///# Returns:
228 ///
229 ///Number of copied bytes.
get(format: u32, result: &mut [u8]) -> io::Result<usize>230 pub fn get(format: u32, result: &mut [u8]) -> io::Result<usize> {
231     let clipboard_data = unsafe { GetClipboardData(format as c_uint) };
232 
233     if clipboard_data.is_null() {
234         Err(utils::get_last_error())
235     }
236     else {
237         unsafe {
238             let data_ptr = GlobalLock(clipboard_data) as *const u8;
239 
240             if data_ptr.is_null() {
241                 return Err(utils::get_last_error());
242             }
243 
244             let data_size = cmp::min(GlobalSize(clipboard_data) as usize, result.len());
245 
246             ptr::copy_nonoverlapping(data_ptr, result.as_mut_ptr(), data_size);
247             GlobalUnlock(clipboard_data);
248 
249             Ok(data_size)
250         }
251     }
252 }
253 
254 ///Retrieves String from `CF_UNICODETEXT` format
255 ///
256 ///Specialized version of [get](fn.get.html) to avoid unnecessary allocations.
257 ///
258 ///# Note:
259 ///
260 ///Usually WinAPI returns strings with null terminated character at the end.
261 ///This character is trimmed.
262 ///
263 ///# Pre-conditions:
264 ///
265 ///* [open()](fn.open.html) has been called.
get_string() -> io::Result<String>266 pub fn get_string() -> io::Result<String> {
267     let clipboard_data = unsafe { GetClipboardData(formats::CF_UNICODETEXT) };
268 
269     if clipboard_data.is_null() {
270         Err(utils::get_last_error())
271     }
272     else {
273         unsafe {
274             let data_ptr = GlobalLock(clipboard_data) as *const c_void as *const u16;
275 
276             if data_ptr.is_null() {
277                 return Err(utils::get_last_error());
278             }
279 
280             let data_size = GlobalSize(clipboard_data) as usize / std::mem::size_of::<u16>();
281 
282             let str_slice = std::slice::from_raw_parts(data_ptr, data_size);
283             #[cfg(not(feature = "utf16error"))]
284             let mut result = String::from_utf16_lossy(str_slice);
285             #[cfg(feature = "utf16error")]
286             let mut result = match String::from_utf16(str_slice) {
287                 Ok(result) => result,
288                 Err(error) => {
289                     GlobalUnlock(clipboard_data);
290                     return Err(io::Error::new(io::ErrorKind::InvalidData, error));
291                 }
292             };
293 
294             //It seems WinAPI always supposed to have at the end null char.
295             //But just to be safe let's check for it and only then remove.
296             if let Some(null_idx) = result.find('\0') {
297                 result.drain(null_idx..);
298             }
299 
300             GlobalUnlock(clipboard_data);
301 
302             Ok(result)
303         }
304     }
305 }
306 
307 /// Retrieves a list of file paths from the `CF_HDROP` format.
308 ///
309 /// # Pre-conditions:
310 ///
311 /// * [open()](fn.open.html) has been called.
get_file_list() -> io::Result<Vec<PathBuf>>312 pub fn get_file_list() -> io::Result<Vec<PathBuf>> {
313     unsafe {
314         let clipboard_data = GetClipboardData(formats::CF_HDROP);
315         if clipboard_data.is_null() {
316             return Err(utils::get_last_error());
317         }
318 
319         let _locked_data = {
320             let locked_ptr = GlobalLock(clipboard_data);
321             if locked_ptr.is_null() {
322                 return Err(utils::get_last_error());
323             }
324             LockedData(clipboard_data)
325         };
326 
327         let num_files = DragQueryFileW(clipboard_data as HDROP, std::u32::MAX, ptr::null_mut(), 0);
328 
329         let mut file_names = Vec::with_capacity(num_files as usize);
330 
331         for file_index in 0..num_files {
332             let required_size_no_null = DragQueryFileW(clipboard_data as HDROP, file_index, ptr::null_mut(), 0);
333             if required_size_no_null == 0 {
334                 return Err(io::ErrorKind::Other.into());
335             }
336             let required_size = required_size_no_null + 1;
337             let mut file_str_buf = Vec::with_capacity(required_size as usize);
338 
339             let write_retval = DragQueryFileW(
340                 clipboard_data as HDROP,
341                 file_index,
342                 file_str_buf.as_mut_ptr(),
343                 required_size,
344             );
345             if write_retval == 0 {
346                 return Err(io::ErrorKind::Other.into());
347             }
348 
349             file_str_buf.set_len(required_size as usize);
350             // Remove terminating zero
351             let os_string = OsString::from_wide(&file_str_buf[..required_size_no_null as usize]);
352             file_names.push(PathBuf::from(os_string));
353         }
354 
355         Ok(file_names)
356     }
357 }
358 
359 ///Sets data onto clipboard with specified format.
360 ///
361 ///Wrapper around ```SetClipboardData```.
362 ///
363 ///# Pre-conditions:
364 ///
365 ///* [open()](fn.open.html) has been called.
set(format: u32, data: &[u8]) -> io::Result<()>366 pub fn set(format: u32, data: &[u8]) -> io::Result<()> {
367     const GHND: c_uint = 0x42;
368     let size = data.len();
369 
370     let alloc_handle = unsafe { GlobalAlloc(GHND, size as SIZE_T) };
371 
372     if alloc_handle.is_null() {
373         Err(utils::get_last_error())
374     }
375     else {
376         unsafe {
377             let lock = GlobalLock(alloc_handle) as *mut u8;
378 
379             ptr::copy_nonoverlapping(data.as_ptr(), lock, size);
380             GlobalUnlock(alloc_handle);
381             EmptyClipboard();
382 
383             if SetClipboardData(format, alloc_handle).is_null() {
384                 let result = utils::get_last_error();
385                 GlobalFree(alloc_handle);
386                 Err(result)
387             }
388             else {
389                 Ok(())
390             }
391         }
392     }
393 }
394 
395 #[inline(always)]
396 ///Determines whenever provided clipboard format is available on clipboard or not.
is_format_avail(format: u32) -> bool397 pub fn is_format_avail(format: u32) -> bool {
398     unsafe { IsClipboardFormatAvailable(format) != 0 }
399 }
400 
401 #[inline]
402 ///Retrieves number of currently available formats on clipboard.
count_formats() -> io::Result<i32>403 pub fn count_formats() -> io::Result<i32> {
404     let result = unsafe { CountClipboardFormats() };
405 
406     if result == 0 {
407         let error = utils::get_last_error();
408 
409         if let Some(raw_error) = error.raw_os_error() {
410             if raw_error != 0 {
411                 return Err(error)
412             }
413         }
414     }
415 
416     Ok(result)
417 }
418 
419 struct LockedData(HANDLE);
420 
421 impl Drop for LockedData {
drop(&mut self)422     fn drop(&mut self) {
423         unsafe {
424             GlobalUnlock(self.0);
425         }
426     }
427 }
428 
429 ///Enumerator over available clipboard formats.
430 ///
431 ///# Pre-conditions:
432 ///
433 ///* [open()](fn.open.html) has been called.
434 pub struct EnumFormats {
435     idx: u32
436 }
437 
438 impl EnumFormats {
439     /// Constructs enumerator over all available formats.
new() -> EnumFormats440     pub fn new() -> EnumFormats {
441         EnumFormats { idx: 0 }
442     }
443 
444     /// Constructs enumerator that starts from format.
from(format: u32) -> EnumFormats445     pub fn from(format: u32) -> EnumFormats {
446         EnumFormats { idx: format }
447     }
448 
449     /// Resets enumerator to list all available formats.
reset(&mut self) -> &EnumFormats450     pub fn reset(&mut self) -> &EnumFormats {
451         self.idx = 0;
452         self
453     }
454 }
455 
456 impl Iterator for EnumFormats {
457     type Item = u32;
458 
459     /// Returns next format on clipboard.
460     ///
461     /// In case of failure (e.g. clipboard is closed) returns `None`.
next(&mut self) -> Option<u32>462     fn next(&mut self) -> Option<u32> {
463         self.idx = unsafe { EnumClipboardFormats(self.idx) };
464 
465         if self.idx == 0 {
466             None
467         }
468         else {
469             Some(self.idx)
470         }
471     }
472 
473     /// Relies on `count_formats` so it is only reliable
474     /// when hinting size for enumeration of all formats.
475     ///
476     /// Doesn't require opened clipboard.
size_hint(&self) -> (usize, Option<usize>)477     fn size_hint(&self) -> (usize, Option<usize>) {
478         (0, count_formats().ok().map(|val| val as usize))
479     }
480 }
481 
482 macro_rules! match_format_name {
483     ( $name:expr, $( $f:ident ),* ) => {
484         match $name {
485             $( formats::$f => Some(stringify!($f).to_string()),)*
486             formats::CF_GDIOBJFIRST ... formats::CF_GDIOBJLAST => Some(format!("CF_GDIOBJ{}", $name - formats::CF_GDIOBJFIRST)),
487             formats::CF_PRIVATEFIRST ... formats::CF_PRIVATELAST => Some(format!("CF_PRIVATE{}", $name - formats::CF_PRIVATEFIRST)),
488             _ => {
489                 let format_buff = [0u16; 52];
490                 unsafe {
491                     let buff_p = format_buff.as_ptr() as *mut u16;
492 
493                     if GetClipboardFormatNameW($name, buff_p, format_buff.len() as c_int) == 0 {
494                         None
495                     }
496                     else {
497                         Some(String::from_utf16_lossy(&format_buff))
498                     }
499                 }
500             }
501         }
502     }
503 }
504 
505 ///Returns format name based on it's code.
506 ///
507 ///# Parameters:
508 ///
509 ///* ```format``` clipboard format code.
510 ///
511 ///# Return result:
512 ///
513 ///* ```Some``` Name of valid format.
514 ///* ```None``` Format is invalid or doesn't exist.
format_name(format: u32) -> Option<String>515 pub fn format_name(format: u32) -> Option<String> {
516     match_format_name!(format,
517                        CF_BITMAP,
518                        CF_DIB,
519                        CF_DIBV5,
520                        CF_DIF,
521                        CF_DSPBITMAP,
522                        CF_DSPENHMETAFILE,
523                        CF_DSPMETAFILEPICT,
524                        CF_DSPTEXT,
525                        CF_ENHMETAFILE,
526                        CF_HDROP,
527                        CF_LOCALE,
528                        CF_METAFILEPICT,
529                        CF_OEMTEXT,
530                        CF_OWNERDISPLAY,
531                        CF_PALETTE,
532                        CF_PENDATA,
533                        CF_RIFF,
534                        CF_SYLK,
535                        CF_TEXT,
536                        CF_WAVE,
537                        CF_TIFF,
538                        CF_UNICODETEXT)
539 }
540 
541 ///Registers a new clipboard format with specified name.
542 ///
543 ///# Returns:
544 ///
545 ///Newly registered format identifier.
546 ///
547 ///# Note:
548 ///
549 ///Custom format identifier is in range `0xC000...0xFFFF`.
register_format<T: ?Sized + AsRef<std::ffi::OsStr>>(name: &T) -> io::Result<u32>550 pub fn register_format<T: ?Sized + AsRef<std::ffi::OsStr>>(name: &T) -> io::Result<u32> {
551     let mut utf16_buff: Vec<u16> = name.as_ref().encode_wide().collect();
552     utf16_buff.push(0);
553 
554     let result = unsafe { RegisterClipboardFormatW(utf16_buff.as_ptr()) };
555 
556     if result == 0 {
557         Err(utils::get_last_error())
558     }
559     else {
560         Ok(result)
561     }
562 }
563