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