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