1 /* 2 SPDX-License-Identifier: Apache-2.0 OR MIT 3 4 Copyright 2020 The arboard contributors 5 6 The project to which this file belongs is licensed under either of 7 the Apache 2.0 or the MIT license at the licensee's choice. The terms 8 and conditions of the chosen license apply to this file. 9 */ 10 11 use std::borrow::Cow; 12 use std::convert::TryInto; 13 use std::io::{self, Read, Seek}; 14 15 use clipboard_win::Clipboard as SystemClipboard; 16 use image::{ 17 bmp::{BmpDecoder, BmpEncoder}, 18 ColorType, ImageDecoder, 19 }; 20 use scopeguard::defer; 21 use winapi::shared::minwindef::DWORD; 22 use winapi::um::{ 23 stringapiset::WideCharToMultiByte, 24 winbase::{GlobalLock, GlobalSize, GlobalUnlock}, 25 wingdi::{ 26 CreateDIBitmap, DeleteObject, BITMAPV4HEADER, BI_BITFIELDS, CBM_INIT, CIEXYZTRIPLE, 27 DIB_RGB_COLORS, 28 }, 29 winnls::CP_UTF8, 30 winnt::LONG, 31 winuser::{GetClipboardData, GetDC, SetClipboardData, CF_BITMAP, CF_UNICODETEXT}, 32 }; 33 34 use super::common::{Error, ImageData}; 35 36 const MAX_OPEN_ATTEMPTS: usize = 5; 37 38 const BITMAP_FILE_HEADER_SIZE: usize = 14; 39 //const BITMAP_INFO_HEADER_SIZE: usize = 40; 40 const BITMAP_V4_INFO_HEADER_SIZE: u32 = 108; 41 42 struct FakeBitmapFile { 43 file_header: [u8; BITMAP_FILE_HEADER_SIZE], 44 bitmap: Vec<u8>, 45 46 curr_pos: usize, 47 } 48 49 impl FakeBitmapFile { 50 fn len(&self) -> usize { 51 self.file_header.len() + self.bitmap.len() 52 } 53 } 54 55 impl Seek for FakeBitmapFile { 56 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> { 57 match pos { 58 io::SeekFrom::Start(p) => self.curr_pos = p as usize, 59 io::SeekFrom::End(p) => self.curr_pos = self.len() + p as usize, 60 io::SeekFrom::Current(p) => self.curr_pos += p as usize, 61 } 62 Ok(self.curr_pos as u64) 63 } 64 } 65 66 impl Read for FakeBitmapFile { 67 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { 68 let remaining = self.len() - self.curr_pos; 69 let total_read_len = buf.len().min(remaining); 70 let mut buf_pos = 0; 71 72 if total_read_len == 0 { 73 return Ok(0); 74 } 75 76 // Read from the header 77 if self.curr_pos < self.file_header.len() { 78 let copy_len = (self.file_header.len() - self.curr_pos).min(total_read_len); 79 let header_end = self.curr_pos + copy_len; 80 buf[0..copy_len].copy_from_slice(&self.file_header[self.curr_pos..header_end]); 81 buf_pos += copy_len; 82 self.curr_pos += copy_len; 83 } 84 // Read from the bitmap 85 let remaining_read_len = total_read_len - buf_pos; 86 if remaining_read_len > 0 { 87 let bitmap_start = self.curr_pos - self.file_header.len(); 88 if bitmap_start < self.bitmap.len() { 89 let copy_len = (self.bitmap.len() - bitmap_start).min(remaining_read_len); 90 let bitmap_end = bitmap_start + copy_len; 91 let buf_end = buf_pos + copy_len; 92 buf[buf_pos..buf_end].copy_from_slice(&self.bitmap[bitmap_start..bitmap_end]); 93 self.curr_pos += copy_len; 94 } 95 } 96 Ok(total_read_len) 97 } 98 } 99 100 unsafe fn add_cf_bitmap(image: &ImageData) -> Result<(), Error> { 101 let header = BITMAPV4HEADER { 102 bV4Size: std::mem::size_of::<BITMAPV4HEADER>() as _, 103 bV4Width: image.width as LONG, 104 bV4Height: -(image.height as LONG), 105 bV4Planes: 1, 106 bV4BitCount: 32, 107 bV4V4Compression: BI_BITFIELDS, 108 bV4SizeImage: (4 * image.width * image.height) as DWORD, 109 bV4XPelsPerMeter: 3000, 110 bV4YPelsPerMeter: 3000, 111 bV4ClrUsed: 0, 112 bV4ClrImportant: 3, 113 bV4RedMask: 0xff0000, 114 bV4GreenMask: 0x00ff00, 115 bV4BlueMask: 0x0000ff, 116 bV4AlphaMask: 0x000000, 117 bV4CSType: 0, 118 bV4Endpoints: std::mem::MaybeUninit::<CIEXYZTRIPLE>::zeroed().assume_init(), 119 bV4GammaRed: 0, 120 bV4GammaGreen: 0, 121 bV4GammaBlue: 0, 122 }; 123 124 let hdc = GetDC(std::ptr::null_mut()); 125 let hbitmap = CreateDIBitmap( 126 hdc, 127 &header as *const BITMAPV4HEADER as *const _, 128 CBM_INIT, 129 image.bytes.as_ptr() as *const _, 130 &header as *const BITMAPV4HEADER as *const _, 131 DIB_RGB_COLORS, 132 ); 133 if SetClipboardData(CF_BITMAP, hbitmap as _).is_null() { 134 DeleteObject(hbitmap as _); 135 return Err(Error::Unknown { 136 description: String::from("Call to `SetClipboardData` returned NULL"), 137 }); 138 } 139 Ok(()) 140 } 141 142 pub fn get_string(out: &mut Vec<u8>) -> Result<(), Error> { 143 use std::mem; 144 use std::ptr; 145 146 // This pointer must not be free'd. 147 let ptr = unsafe { GetClipboardData(CF_UNICODETEXT) }; 148 if ptr.is_null() { 149 return Err(Error::ContentNotAvailable); 150 } 151 152 unsafe { 153 let data_ptr = GlobalLock(ptr); 154 if data_ptr.is_null() { 155 return Err(Error::Unknown { 156 description: "GlobalLock on clipboard data returned null.".into(), 157 }); 158 } 159 defer!( GlobalUnlock(ptr); ); 160 161 let char_count = GlobalSize(ptr) as usize / mem::size_of::<u16>(); 162 let storage_req_size = WideCharToMultiByte( 163 CP_UTF8, 164 0, 165 data_ptr as _, 166 char_count as _, 167 ptr::null_mut(), 168 0, 169 ptr::null(), 170 ptr::null_mut(), 171 ); 172 if storage_req_size == 0 { 173 return Err(Error::ConversionFailure); 174 } 175 176 let storage_cursor = out.len(); 177 out.reserve(storage_req_size as usize); 178 let storage_ptr = out.as_mut_ptr().add(storage_cursor) as *mut _; 179 let output_size = WideCharToMultiByte( 180 CP_UTF8, 181 0, 182 data_ptr as _, 183 char_count as _, 184 storage_ptr, 185 storage_req_size, 186 ptr::null(), 187 ptr::null_mut(), 188 ); 189 if output_size == 0 { 190 return Err(Error::ConversionFailure); 191 } 192 out.set_len(storage_cursor + storage_req_size as usize); 193 194 //It seems WinAPI always supposed to have at the end null char. 195 //But just to be safe let's check for it and only then remove. 196 if let Some(last_byte) = out.last() { 197 if *last_byte == 0 { 198 out.set_len(out.len() - 1); 199 } 200 } 201 } 202 Ok(()) 203 } 204 205 pub struct WindowsClipboardContext; 206 207 impl WindowsClipboardContext { 208 pub(crate) fn new() -> Result<Self, Error> { 209 Ok(WindowsClipboardContext) 210 } 211 pub(crate) fn get_text(&mut self) -> Result<String, Error> { 212 // Using this nifty RAII object to open and close the clipboard. 213 let _cb = SystemClipboard::new_attempts(MAX_OPEN_ATTEMPTS) 214 .map_err(|_| Error::ClipboardOccupied)?; 215 let mut result = String::new(); 216 get_string(unsafe { result.as_mut_vec() })?; 217 Ok(result) 218 } 219 pub(crate) fn set_text(&mut self, data: String) -> Result<(), Error> { 220 let _cb = SystemClipboard::new_attempts(MAX_OPEN_ATTEMPTS) 221 .map_err(|_| Error::ClipboardOccupied)?; 222 clipboard_win::set(clipboard_win::formats::Unicode, data).map_err(|_| Error::Unknown { 223 description: "Could not place the specified text to the clipboard".into(), 224 }) 225 } 226 pub(crate) fn get_image(&mut self) -> Result<ImageData, Error> { 227 let _cb = SystemClipboard::new_attempts(MAX_OPEN_ATTEMPTS) 228 .map_err(|_| Error::ClipboardOccupied)?; 229 let format = clipboard_win::formats::CF_DIB; 230 let size; 231 match clipboard_win::raw::size(format) { 232 Some(s) => size = s, 233 None => return Err(Error::ContentNotAvailable), 234 } 235 let mut data = vec![0u8; size.into()]; 236 clipboard_win::raw::get(format, &mut data).map_err(|_| Error::Unknown { 237 description: "failed to get image data from the clipboard".into(), 238 })?; 239 let info_header_size = u32::from_le_bytes(data[..4].try_into().unwrap()); 240 let mut fake_bitmap_file = 241 FakeBitmapFile { bitmap: data, file_header: [0; BITMAP_FILE_HEADER_SIZE], curr_pos: 0 }; 242 fake_bitmap_file.file_header[0] = b'B'; 243 fake_bitmap_file.file_header[1] = b'M'; 244 245 let file_size = 246 u32::to_le_bytes((fake_bitmap_file.bitmap.len() + BITMAP_FILE_HEADER_SIZE) as u32); 247 fake_bitmap_file.file_header[2..6].copy_from_slice(&file_size); 248 249 let data_offset = u32::to_le_bytes(info_header_size + BITMAP_FILE_HEADER_SIZE as u32); 250 fake_bitmap_file.file_header[10..14].copy_from_slice(&data_offset); 251 252 let bmp_decoder = BmpDecoder::new(fake_bitmap_file).unwrap(); 253 let (w, h) = bmp_decoder.dimensions(); 254 let width = w as usize; 255 let height = h as usize; 256 let image = 257 image::DynamicImage::from_decoder(bmp_decoder).map_err(|_| Error::ConversionFailure)?; 258 Ok(ImageData { width, height, bytes: Cow::from(image.into_rgba8().into_raw()) }) 259 } 260 pub(crate) fn set_image(&mut self, image: ImageData) -> Result<(), Error> { 261 //let clipboard = SystemClipboard::new()?; 262 let mut bmp_data = Vec::with_capacity(image.bytes.len()); 263 let mut cursor = std::io::Cursor::new(&mut bmp_data); 264 let mut encoder = BmpEncoder::new(&mut cursor); 265 encoder 266 .encode(&image.bytes, image.width as u32, image.height as u32, ColorType::Rgba8) 267 .map_err(|_| Error::ConversionFailure)?; 268 let data_without_file_header = &bmp_data[BITMAP_FILE_HEADER_SIZE..]; 269 let header_size = u32::from_le_bytes(data_without_file_header[..4].try_into().unwrap()); 270 let format = if header_size > BITMAP_V4_INFO_HEADER_SIZE { 271 clipboard_win::formats::CF_DIBV5 272 } else { 273 clipboard_win::formats::CF_DIB 274 }; 275 let mut result: Result<(), Error> = Ok(()); 276 //let mut success = false; 277 clipboard_win::with_clipboard(|| { 278 let success = clipboard_win::raw::set(format, data_without_file_header).is_ok(); 279 let bitmap_result = unsafe { add_cf_bitmap(&image) }; 280 if bitmap_result.is_err() && !success { 281 result = Err(Error::Unknown { 282 description: "Could not set the image for the clipboard in neither of `CF_DIB` and `CG_BITMAP` formats.".into(), 283 }); 284 } 285 }) 286 .map_err(|_| Error::ClipboardOccupied)?; 287 288 result 289 } 290 } 291