1#![doc = include_str!("../README.md")]
2
3pub use ffi::get_screenshot;
15
16#[derive(Clone, Copy)]
17pub struct Pixel {
18 pub a: u8,
19 pub r: u8,
20 pub g: u8,
21 pub b: u8,
22}
23
24pub struct Screenshot {
27 data: Vec<u8>,
28 height: usize,
29 width: usize,
30 row_len: usize, pixel_width: usize,
32}
33
34impl Screenshot {
35 #[inline]
37 pub fn height(&self) -> usize {
38 self.height
39 }
40
41 #[inline]
43 pub fn width(&self) -> usize {
44 self.width
45 }
46
47 #[inline]
49 pub fn row_len(&self) -> usize {
50 self.row_len
51 }
52
53 #[inline]
55 pub fn pixel_width(&self) -> usize {
56 self.pixel_width
57 }
58
59 #[inline]
62 pub unsafe fn raw_data(&self) -> *const u8 {
63 &self.data[0] as *const u8
64 }
65
66 #[inline]
69 pub unsafe fn raw_data_mut(&mut self) -> *mut u8 {
70 &mut self.data[0] as *mut u8
71 }
72
73 #[inline]
75 pub fn raw_len(&self) -> usize {
76 self.data.len() * size_of::<u8>()
77 }
78
79 pub fn get_pixel(&self, row: usize, col: usize) -> Pixel {
81 let idx = (row * self.row_len() + col * self.pixel_width()) as isize;
82 unsafe {
83 let data = &self.data[0] as *const u8;
84 if idx as usize > self.raw_len() {
85 panic!("Bounds overflow");
86 }
87
88 Pixel {
89 a: *data.offset(idx + 3),
90 r: *data.offset(idx + 2),
91 g: *data.offset(idx + 1),
92 b: *data.offset(idx),
93 }
94 }
95 }
96}
97
98impl AsRef<[u8]> for Screenshot {
99 #[inline]
100 fn as_ref(&self) -> &[u8] {
101 self.data.as_slice()
102 }
103}
104
105pub type ScreenResult = Result<Screenshot, &'static str>;
106
107#[cfg(target_os = "linux")]
108mod ffi {
109 use crate::{ScreenResult, Screenshot};
110 use libc::{c_int, c_uint};
111 use std::ptr::null_mut;
112 use std::slice;
113 use x11::xlib::{
114 XAllPlanes, XCloseDisplay, XDestroyImage, XDestroyWindow, XGetImage, XGetWindowAttributes,
115 XOpenDisplay, XRootWindowOfScreen, XScreenOfDisplay, XWindowAttributes, ZPixmap,
116 };
117
118 pub fn get_screenshot(screen: u32) -> ScreenResult {
119 unsafe {
120 let display = XOpenDisplay(null_mut());
121 let screen = XScreenOfDisplay(display, screen as c_int);
122 let root = XRootWindowOfScreen(screen);
123
124 let mut attr = std::mem::MaybeUninit::<XWindowAttributes>::uninit();
125 XGetWindowAttributes(display, root, attr.as_mut_ptr());
126 let attr = attr.assume_init();
127
128 let img = &mut *XGetImage(
129 display,
130 root,
131 0,
132 0,
133 attr.width as c_uint,
134 attr.height as c_uint,
135 XAllPlanes(),
136 ZPixmap,
137 );
138 XDestroyWindow(display, root);
139 XCloseDisplay(display);
140 let height = img.height as usize;
141 let width = img.width as usize;
142 let row_len = img.bytes_per_line as usize;
143 let pixel_bits = img.bits_per_pixel as usize;
144 if !pixel_bits.is_multiple_of(8) {
145 XDestroyImage(&mut *img);
146 return Err("Pixels aren't integral bytes.");
147 }
148 let pixel_width = pixel_bits / 8;
149
150 let size = width * height * pixel_width;
152 let mut data = slice::from_raw_parts(img.data as *mut u8, size as usize).to_vec();
153 XDestroyImage(&mut *img);
154
155 let has_alpha = data.iter().enumerate().any(|(n, x)| n % 4 == 3 && *x != 0);
157 if !has_alpha {
158 for (n, channel) in data.iter_mut().enumerate() {
159 if n % 4 == 3 {
160 *channel = 255;
161 }
162 }
163 }
164
165 Ok(Screenshot {
166 data,
167 height,
168 width,
169 row_len,
170 pixel_width,
171 })
172 }
173 }
174}
175
176#[cfg(target_os = "macos")]
177mod ffi {
178 #![allow(non_upper_case_globals, dead_code)]
179
180 use crate::{ScreenResult, Screenshot};
181 use std::slice;
182
183 type CFIndex = libc::c_long;
184 type CFDataRef = *const u8; #[cfg(target_arch = "x86")]
187 type CGFloat = libc::c_float;
188 #[cfg(target_arch = "x86_64")]
189 type CGFloat = libc::c_double;
190 type CGError = i32;
191
192 type CGDirectDisplayID = u32;
193 type CGDisplayCount = u32;
194 type CGImageRef = *mut u8; type CGDataProviderRef = *mut u8; const kCGErrorSuccess: CGError = 0;
198 const kCGErrorFailure: CGError = 1000;
199 const CGDisplayNoErr: CGError = kCGErrorSuccess;
200
201 #[link(name = "CoreGraphics", kind = "framework")]
202 unsafe extern "C" {
203 fn CGGetActiveDisplayList(
204 max_displays: u32,
205 active_displays: *mut CGDirectDisplayID,
206 display_count: *mut CGDisplayCount,
207 ) -> CGError;
208 fn CGDisplayCreateImage(displayID: CGDirectDisplayID) -> CGImageRef;
209 fn CGImageRelease(image: CGImageRef);
210
211 fn CGImageGetBitsPerComponent(image: CGImageRef) -> libc::size_t;
212 fn CGImageGetBitsPerPixel(image: CGImageRef) -> libc::size_t;
213 fn CGImageGetBytesPerRow(image: CGImageRef) -> libc::size_t;
214 fn CGImageGetDataProvider(image: CGImageRef) -> CGDataProviderRef;
215 fn CGImageGetHeight(image: CGImageRef) -> libc::size_t;
216 fn CGImageGetWidth(image: CGImageRef) -> libc::size_t;
217
218 fn CGDataProviderCopyData(provider: CGDataProviderRef) -> CFDataRef;
219 }
220 #[link(name = "CoreFoundation", kind = "framework")]
221 unsafe extern "C" {
222 fn CFDataGetLength(theData: CFDataRef) -> CFIndex;
223 fn CFDataGetBytePtr(theData: CFDataRef) -> *const u8;
224 fn CFRelease(cf: *const libc::c_void);
225 }
226
227 pub fn get_screenshot(screen: usize) -> ScreenResult {
229 unsafe {
230 let mut count: CGDisplayCount = 0;
232 use std::ptr::null_mut;
233 let err = CGGetActiveDisplayList(0, null_mut::<CGDirectDisplayID>(), &mut count);
234 if err != CGDisplayNoErr {
235 return Err("Error getting number of displays.");
236 }
237
238 let mut disps: Vec<CGDisplayCount> = vec![0; count as usize];
240 let err = CGGetActiveDisplayList(
241 disps.len() as u32,
242 &mut disps[0] as *mut CGDirectDisplayID,
243 &mut count,
244 );
245 if err != CGDisplayNoErr {
246 return Err("Error getting list of displays.");
247 }
248
249 let disp_id = disps[screen];
251 let cg_img = CGDisplayCreateImage(disp_id);
252
253 let width = CGImageGetWidth(cg_img) as usize;
255 let height = CGImageGetHeight(cg_img) as usize;
256 let row_len = CGImageGetBytesPerRow(cg_img) as usize;
257 let pixel_bits = CGImageGetBitsPerPixel(cg_img) as usize;
258 if !pixel_bits.is_multiple_of(8) {
259 return Err("Pixels aren't integral bytes.");
260 }
261
262 let cf_data = CGDataProviderCopyData(CGImageGetDataProvider(cg_img));
264 let raw_len = CFDataGetLength(cf_data) as usize;
265
266 let res = if width * height * pixel_bits != raw_len * 8 {
267 Err("Image size is inconsistent with W*H*D.")
268 } else {
269 let data = slice::from_raw_parts(CFDataGetBytePtr(cf_data), raw_len).to_vec();
270 Ok(Screenshot {
271 data,
272 height,
273 width,
274 row_len,
275 pixel_width: pixel_bits / 8,
276 })
277 };
278
279 CGImageRelease(cg_img);
281 CFRelease(cf_data as *const libc::c_void);
282
283 res
284 }
285 }
286}
287
288#[cfg(target_os = "windows")]
289mod ffi {
290 #![allow(clippy::upper_case_acronyms)]
291 #![allow(non_snake_case, dead_code)]
292
293 use libc::{c_int, c_long, c_uint, c_void};
294
295 use crate::{ScreenResult, Screenshot};
296
297 type PVOID = *mut c_void;
298 type LPVOID = *mut c_void;
299 type WORD = u16; type DWORD = u32; type BOOL = c_int;
302 type BYTE = u8;
303 type UINT = c_uint;
304 type LONG = c_long;
305 type LPARAM = c_long;
306
307 #[repr(C)]
308 struct RECT {
309 left: LONG,
310 top: LONG,
311 right: LONG, bottom: LONG, }
314 type LPCRECT = *const RECT;
315 type LPRECT = *mut RECT;
316
317 type HANDLE = PVOID;
318 type HMONITOR = HANDLE;
319 type HWND = HANDLE;
320 type HDC = HANDLE;
321 #[repr(C)]
322 struct MONITORINFO {
323 cbSize: DWORD,
324 rcMonitor: RECT,
325 rcWork: RECT,
326 dwFlags: DWORD,
327 }
328 type LPMONITORINFO = *mut MONITORINFO;
329 type MONITORENUMPROC = Option<extern "system" fn(HMONITOR, HDC, LPRECT, LPARAM) -> BOOL>;
330
331 type HBITMAP = HANDLE;
332 type HGDIOBJ = HANDLE;
333 type LPBITMAPINFO = PVOID; const NULL: *mut c_void = std::ptr::null_mut::<c_void>();
336 const HGDI_ERROR: *mut c_void = -1isize as *mut c_void;
337 const SM_CXSCREEN: c_int = 0;
338 const SM_CYSCREEN: c_int = 1;
339
340 const SRCCOPY: u32 = 0x00CC0020;
342 const CAPTUREBLT: u32 = 0x40000000;
343 const DIB_RGB_COLORS: UINT = 0;
344 const BI_RGB: DWORD = 0;
345
346 #[repr(C)]
347 struct BITMAPINFOHEADER {
348 biSize: DWORD,
349 biWidth: LONG,
350 biHeight: LONG,
351 biPlanes: WORD,
352 biBitCount: WORD,
353 biCompression: DWORD,
354 biSizeImage: DWORD,
355 biXPelsPerMeter: LONG,
356 biYPelsPerMeter: LONG,
357 biClrUsed: DWORD,
358 biClrImportant: DWORD,
359 }
360
361 #[repr(C)]
362 struct RGBQUAD {
363 rgbBlue: BYTE,
364 rgbGreen: BYTE,
365 rgbRed: BYTE,
366 rgbReserved: BYTE,
367 }
368
369 #[repr(C)]
371 struct BITMAPINFO {
372 bmiHeader: BITMAPINFOHEADER,
373 bmiColors: [RGBQUAD; 1],
374 }
375
376 #[link(name = "user32")]
377 unsafe extern "system" {
378 fn GetSystemMetrics(m: c_int) -> c_int;
379 fn EnumDisplayMonitors(
380 hdc: HDC,
381 lprcClip: LPCRECT,
382 lpfnEnum: MONITORENUMPROC,
383 dwData: LPARAM,
384 ) -> BOOL;
385 fn GetMonitorInfo(hMonitor: HMONITOR, lpmi: LPMONITORINFO) -> BOOL;
386 fn GetDesktopWindow() -> HWND;
387 fn GetDC(hWnd: HWND) -> HDC;
388 }
389
390 #[link(name = "gdi32")]
391 unsafe extern "system" {
392 fn CreateCompatibleDC(hdc: HDC) -> HDC;
393 fn CreateCompatibleBitmap(hdc: HDC, nWidth: c_int, nHeight: c_int) -> HBITMAP;
394 fn SelectObject(hdc: HDC, hgdiobj: HGDIOBJ) -> HGDIOBJ;
395 fn BitBlt(
396 hdcDest: HDC,
397 nXDest: c_int,
398 nYDest: c_int,
399 nWidth: c_int,
400 nHeight: c_int,
401 hdcSrc: HDC,
402 nXSrc: c_int,
403 nYSrc: c_int,
404 dwRop: DWORD,
405 ) -> BOOL;
406 fn GetDIBits(
407 hdc: HDC,
408 hbmp: HBITMAP,
409 uStartScan: UINT,
410 cScanLines: UINT,
411 lpvBits: LPVOID,
412 lpbi: LPBITMAPINFO,
413 uUsage: UINT,
414 ) -> c_int;
415
416 fn DeleteObject(hObject: HGDIOBJ) -> BOOL;
417 fn ReleaseDC(hWnd: HWND, hDC: HDC) -> c_int;
418 fn DeleteDC(hdc: HDC) -> BOOL;
419 }
420
421 fn flip_rows(data: Vec<u8>, height: usize, row_len: usize) -> Vec<u8> {
424 let mut new_data = vec![0; data.len()];
425 for row_i in 0..height {
426 for byte_i in 0..row_len {
427 let old_idx = (height - row_i - 1) * row_len + byte_i;
428 let new_idx = row_i * row_len + byte_i;
429 new_data[new_idx] = data[old_idx];
430 }
431 }
432 new_data
433 }
434
435 pub fn get_screenshot(_screen: usize) -> ScreenResult {
438 unsafe {
439 let h_wnd_screen = GetDesktopWindow();
442 let h_dc_screen = GetDC(h_wnd_screen);
443 let width = GetSystemMetrics(SM_CXSCREEN);
444 let height = GetSystemMetrics(SM_CYSCREEN);
445
446 let h_dc = CreateCompatibleDC(h_dc_screen);
448 if h_dc == NULL {
449 return Err("Can't get a Windows display.");
450 }
451
452 let h_bmp = CreateCompatibleBitmap(h_dc_screen, width, height);
453 if h_bmp == NULL {
454 return Err("Can't create a Windows buffer");
455 }
456
457 let res = SelectObject(h_dc, h_bmp);
458 if res == NULL || res == HGDI_ERROR {
459 return Err("Can't select Windows buffer.");
460 }
461
462 let res = BitBlt(
463 h_dc,
464 0,
465 0,
466 width,
467 height,
468 h_dc_screen,
469 0,
470 0,
471 SRCCOPY | CAPTUREBLT,
472 );
473 if res == 0 {
474 return Err("Failed to copy screen to Windows buffer");
475 }
476
477 let pixel_width: usize = 4; let mut bmi = BITMAPINFO {
480 bmiHeader: BITMAPINFOHEADER {
481 biSize: size_of::<BITMAPINFOHEADER>() as DWORD,
482 biWidth: width as LONG,
483 biHeight: height as LONG,
484 biPlanes: 1,
485 biBitCount: 8 * pixel_width as WORD,
486 biCompression: BI_RGB,
487 biSizeImage: (width * height * pixel_width as c_int) as DWORD,
488 biXPelsPerMeter: 0,
489 biYPelsPerMeter: 0,
490 biClrUsed: 0,
491 biClrImportant: 0,
492 },
493 bmiColors: [RGBQUAD {
494 rgbBlue: 0,
495 rgbGreen: 0,
496 rgbRed: 0,
497 rgbReserved: 0,
498 }],
499 };
500
501 let size: usize = (width * height) as usize * pixel_width;
503 let mut data: Vec<u8> = vec![0; size];
504
505 GetDIBits(
507 h_dc,
508 h_bmp,
509 0,
510 height as DWORD,
511 &mut data[0] as *mut u8 as *mut c_void,
512 &mut bmi as *mut BITMAPINFO as *mut c_void,
513 DIB_RGB_COLORS,
514 );
515
516 ReleaseDC(h_wnd_screen, h_dc_screen); DeleteDC(h_dc);
519 DeleteObject(h_bmp);
520
521 let data = flip_rows(data, height as usize, width as usize * pixel_width);
522
523 Ok(Screenshot {
524 data,
525 height: height as usize,
526 width: width as usize,
527 row_len: width as usize * pixel_width,
528 pixel_width,
529 })
530 }
531 }
532}
533
534#[test]
535fn test_get_screenshot() {
536 #[cfg(target_os = "linux")]
537 if std::env::var("DISPLAY").is_err() {
538 eprintln!("Skipping screenshot test: DISPLAY not set");
539 return;
540 }
541 let s: Screenshot = get_screenshot(0).unwrap();
542 println!(
543 "width: {}\n height: {}\npixel width: {}\n bytes: {}",
544 s.width(),
545 s.height(),
546 s.pixel_width(),
547 s.raw_len()
548 );
549}