├── .gitignore ├── src ├── x11 │ ├── mod.rs │ ├── display.rs │ ├── server.rs │ ├── iter.rs │ ├── capturer.rs │ └── ffi.rs ├── quartz │ ├── mod.rs │ ├── frame.rs │ ├── display.rs │ ├── config.rs │ ├── capturer.rs │ └── ffi.rs ├── lib.rs ├── common │ ├── mod.rs │ ├── dxgi.rs │ ├── x11.rs │ └── quartz.rs └── dxgi │ ├── ffi.rs │ └── mod.rs ├── examples ├── list.rs ├── ffplay.rs └── screenshot.rs ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /src/x11/mod.rs: -------------------------------------------------------------------------------- 1 | mod capturer; 2 | mod display; 3 | mod ffi; 4 | mod iter; 5 | mod server; 6 | 7 | pub use self::capturer::*; 8 | pub use self::display::*; 9 | pub use self::iter::*; 10 | pub use self::server::*; 11 | -------------------------------------------------------------------------------- /src/quartz/mod.rs: -------------------------------------------------------------------------------- 1 | mod capturer; 2 | mod config; 3 | mod display; 4 | mod ffi; 5 | mod frame; 6 | 7 | pub use self::capturer::Capturer; 8 | pub use self::config::Config; 9 | pub use self::display::Display; 10 | pub use self::ffi::{CGError, PixelFormat}; 11 | pub use self::frame::Frame; 12 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate cfg_if; 3 | extern crate libc; 4 | 5 | #[cfg(quartz)] extern crate block; 6 | #[cfg(quartz)] pub mod quartz; 7 | 8 | #[cfg(x11)] pub mod x11; 9 | 10 | #[cfg(dxgi)] extern crate winapi; 11 | #[cfg(dxgi)] pub mod dxgi; 12 | 13 | mod common; 14 | pub use common::*; 15 | -------------------------------------------------------------------------------- /examples/list.rs: -------------------------------------------------------------------------------- 1 | extern crate scrap; 2 | 3 | use scrap::Display; 4 | 5 | fn main() { 6 | let displays = Display::all().unwrap(); 7 | 8 | for (i, display) in displays.iter().enumerate() { 9 | println!("Display {} [{}x{}]", 10 | i + 1, 11 | display.width(), 12 | display.height()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | cfg_if! { 2 | if #[cfg(quartz)] { 3 | mod quartz; 4 | pub use self::quartz::*; 5 | } else if #[cfg(x11)] { 6 | mod x11; 7 | pub use self::x11::*; 8 | } else if #[cfg(dxgi)] { 9 | mod dxgi; 10 | pub use self::dxgi::*; 11 | } else { 12 | //TODO: Fallback implementation. 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scrap" 3 | description = "Screen capture made easy." 4 | version = "0.5.0" 5 | repository = "https://github.com/quadrupleslap/scrap" 6 | documentation = "https://docs.rs/scrap" 7 | keywords = ["screen", "capture", "record"] 8 | license = "MIT" 9 | authors = ["Ram "] 10 | 11 | [dependencies] 12 | block = "0.1" 13 | cfg-if = "0.1" 14 | libc = "0.2" 15 | winapi = "0.2" 16 | 17 | [dev-dependencies] 18 | repng = "0.2" 19 | -------------------------------------------------------------------------------- /src/x11/display.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use super::Server; 3 | use super::ffi::*; 4 | 5 | #[derive(Debug)] 6 | pub struct Display { 7 | server: Rc, 8 | default: bool, 9 | rect: Rect, 10 | root: xcb_window_t, 11 | } 12 | 13 | #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] 14 | pub struct Rect { 15 | pub x: i16, 16 | pub y: i16, 17 | pub w: u16, 18 | pub h: u16, 19 | } 20 | 21 | impl Display { 22 | pub unsafe fn new( 23 | server: Rc, 24 | default: bool, 25 | rect: Rect, 26 | root: xcb_window_t 27 | ) -> Display { 28 | Display { server, default, rect, root } 29 | } 30 | 31 | pub fn server(&self) -> &Rc { &self.server } 32 | pub fn is_default(&self) -> bool { self.default } 33 | pub fn rect(&self) -> Rect { self.rect } 34 | pub fn root(&self) -> xcb_window_t { self.root } 35 | } 36 | -------------------------------------------------------------------------------- /src/quartz/frame.rs: -------------------------------------------------------------------------------- 1 | use super::ffi::*; 2 | use std::{ops, ptr, slice}; 3 | 4 | pub struct Frame { 5 | surface: IOSurfaceRef, 6 | inner: &'static [u8] 7 | } 8 | 9 | impl Frame { 10 | pub unsafe fn new(surface: IOSurfaceRef) -> Frame { 11 | CFRetain(surface); 12 | IOSurfaceIncrementUseCount(surface); 13 | 14 | IOSurfaceLock( 15 | surface, 16 | SURFACE_LOCK_READ_ONLY, 17 | ptr::null_mut() 18 | ); 19 | 20 | let inner = slice::from_raw_parts( 21 | IOSurfaceGetBaseAddress(surface) as *const u8, 22 | IOSurfaceGetAllocSize(surface) 23 | ); 24 | 25 | Frame { surface, inner } 26 | } 27 | } 28 | 29 | impl ops::Deref for Frame { 30 | type Target = [u8]; 31 | fn deref<'a>(&'a self) -> &'a [u8] { 32 | self.inner 33 | } 34 | } 35 | 36 | impl Drop for Frame { 37 | fn drop(&mut self) { 38 | unsafe { 39 | IOSurfaceUnlock( 40 | self.surface, 41 | SURFACE_LOCK_READ_ONLY, 42 | ptr::null_mut() 43 | ); 44 | 45 | IOSurfaceDecrementUseCount(self.surface); 46 | CFRelease(self.surface); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/ffplay.rs: -------------------------------------------------------------------------------- 1 | extern crate scrap; 2 | 3 | fn main() { 4 | use scrap::{Capturer, Display}; 5 | use std::io::Write; 6 | use std::io::ErrorKind::WouldBlock; 7 | use std::process::{Command, Stdio}; 8 | 9 | let d = Display::primary().unwrap(); 10 | let (w, h) = (d.width(), d.height()); 11 | 12 | let child = Command::new("ffplay") 13 | .args(&[ 14 | "-f", "rawvideo", 15 | "-pixel_format", "bgr0", 16 | "-video_size", &format!("{}x{}", w, h), 17 | "-framerate", "60", 18 | "-" 19 | ]) 20 | .stdin(Stdio::piped()) 21 | .spawn() 22 | .expect("This example requires ffplay."); 23 | 24 | let mut capturer = Capturer::new(d).unwrap(); 25 | let mut out = child.stdin.unwrap(); 26 | 27 | loop { 28 | match capturer.frame() { 29 | Ok(frame) => { 30 | // Write the frame, removing end-of-row padding. 31 | let stride = frame.len() / h; 32 | let rowlen = 4 * w; 33 | for row in frame.chunks(stride) { 34 | let row = &row[..rowlen]; 35 | out.write_all(row).unwrap(); 36 | } 37 | } 38 | Err(ref e) if e.kind() == WouldBlock => { 39 | // Wait for the frame. 40 | } 41 | Err(_) => { 42 | // We're done here. 43 | break; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scrap 2 | 3 | Scrap records your screen! At least it does if you're on Windows, macOS, or Linux. 4 | 5 | ## Usage 6 | 7 | ```toml 8 | [dependencies] 9 | scrap = "0.5" 10 | ``` 11 | 12 | Its API is as simple as it gets! 13 | 14 | ```rust 15 | struct Display; /// A screen. 16 | struct Frame; /// An array of the pixels that were on-screen. 17 | struct Capturer; /// A recording instance. 18 | 19 | impl Capturer { 20 | /// Begin recording. 21 | pub fn new(display: Display) -> io::Result; 22 | 23 | /// Try to get a frame. 24 | /// Returns WouldBlock if it's not ready yet. 25 | pub fn frame<'a>(&'a mut self) -> io::Result>; 26 | 27 | pub fn width(&self) -> usize; 28 | pub fn height(&self) -> usize; 29 | } 30 | 31 | impl Display { 32 | /// The primary screen. 33 | pub fn primary() -> io::Result; 34 | 35 | /// All the screens. 36 | pub fn all() -> io::Result>; 37 | 38 | pub fn width(&self) -> usize; 39 | pub fn height(&self) -> usize; 40 | } 41 | 42 | impl<'a> ops::Deref for Frame<'a> { 43 | /// A frame is just an array of bytes. 44 | type Target = [u8]; 45 | } 46 | ``` 47 | 48 | ## The Frame Format 49 | 50 | - The frame format is guaranteed to be **packed BGRA**. 51 | - The width and height are guaranteed to remain constant. 52 | - The stride might be greater than the width, and it may also vary between frames. 53 | 54 | ## System Requirements 55 | 56 | OS | Minimum Requirements 57 | --------|--------------------- 58 | macOS | macOS 10.8 59 | Linux | XCB + SHM + RandR 60 | Windows | DirectX 11.1 61 | -------------------------------------------------------------------------------- /src/quartz/display.rs: -------------------------------------------------------------------------------- 1 | use super::ffi::*; 2 | use std::mem; 3 | 4 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 5 | #[repr(C)] 6 | pub struct Display(u32); 7 | 8 | impl Display { 9 | pub fn primary() -> Display { 10 | Display(unsafe { CGMainDisplayID() }) 11 | } 12 | 13 | pub fn online() -> Result, CGError> { 14 | unsafe { 15 | let mut arr: [u32; 16] = mem::uninitialized(); 16 | let mut len: u32 = 0; 17 | 18 | match CGGetOnlineDisplayList(16, arr.as_mut_ptr(), &mut len) { 19 | CGError::Success => (), 20 | x => return Err(x) 21 | } 22 | 23 | let mut res = Vec::with_capacity(16); 24 | for i in 0..len as usize { 25 | res.push(Display(*arr.get_unchecked(i))); 26 | } 27 | Ok(res) 28 | } 29 | } 30 | 31 | pub fn id(self) -> u32 { 32 | self.0 33 | } 34 | 35 | pub fn width(self) -> usize { 36 | unsafe { CGDisplayPixelsWide(self.0) } 37 | } 38 | 39 | pub fn height(self) -> usize { 40 | unsafe { CGDisplayPixelsHigh(self.0) } 41 | } 42 | 43 | pub fn is_builtin(self) -> bool { 44 | unsafe { CGDisplayIsBuiltin(self.0) != 0 } 45 | } 46 | 47 | pub fn is_primary(self) -> bool { 48 | unsafe { CGDisplayIsMain(self.0) != 0 } 49 | } 50 | 51 | pub fn is_active(self) -> bool { 52 | unsafe { CGDisplayIsActive(self.0) != 0 } 53 | } 54 | 55 | pub fn is_online(self) -> bool { 56 | unsafe { CGDisplayIsOnline(self.0) != 0 } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/dxgi/ffi.rs: -------------------------------------------------------------------------------- 1 | use winapi::{ 2 | GUID, 3 | HRESULT, 4 | REFIID, 5 | IDXGIFactory1, 6 | IDXGIAdapter, 7 | D3D_DRIVER_TYPE, 8 | HMODULE, 9 | UINT, 10 | ID3D11Device, 11 | D3D_FEATURE_LEVEL, 12 | ID3D11DeviceContext 13 | }; 14 | 15 | pub const DXGI_MAP_READ: UINT = 1; 16 | 17 | pub const IID_IDXGIFACTORY1: GUID = GUID { 18 | Data1: 0x770aae78, 19 | Data2: 0xf26f, 20 | Data3: 0x4dba, 21 | Data4: [0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87] 22 | }; 23 | 24 | pub const IID_IDXGIOUTPUT1: GUID = GUID { 25 | Data1: 0x00cddea8, 26 | Data2: 0x939b, 27 | Data3: 0x4b83, 28 | Data4: [0xa3, 0x40, 0xa6, 0x85, 0x22, 0x66, 0x66, 0xcc] 29 | }; 30 | 31 | pub const IID_IDXGISURFACE: GUID = GUID { 32 | Data1: 3405559148, 33 | Data2: 27331, 34 | Data3: 18569, 35 | Data4: [191, 71, 158, 35, 187, 210, 96, 236] 36 | }; 37 | 38 | pub const IID_ID3D11TEXTURE2D: GUID = GUID { 39 | Data1: 1863690994, 40 | Data2: 53768, 41 | Data3: 20105, 42 | Data4: [154, 180, 72, 149, 53, 211, 79, 156] 43 | }; 44 | 45 | #[link(name="dxgi")] 46 | #[link(name="d3d11")] 47 | extern "system" { 48 | pub fn CreateDXGIFactory1( 49 | id: REFIID, 50 | pp_factory: *mut *mut IDXGIFactory1 51 | ) -> HRESULT; 52 | 53 | pub fn D3D11CreateDevice( 54 | pAdapter: *mut IDXGIAdapter, 55 | DriverType: D3D_DRIVER_TYPE, 56 | Software: HMODULE, 57 | Flags: UINT, 58 | pFeatureLevels: *mut D3D_FEATURE_LEVEL, 59 | FeatureLevels: UINT, 60 | SDKVersion: UINT, 61 | ppDevice: *mut *mut ID3D11Device, 62 | pFeatureLevel: *mut D3D_FEATURE_LEVEL, 63 | ppImmediateContext: *mut *mut ID3D11DeviceContext 64 | ) -> HRESULT; 65 | } 66 | -------------------------------------------------------------------------------- /src/common/dxgi.rs: -------------------------------------------------------------------------------- 1 | use dxgi; 2 | use std::{io, ops}; 3 | use std::io::ErrorKind::{WouldBlock, TimedOut, NotFound}; 4 | 5 | pub struct Capturer { 6 | inner: dxgi::Capturer, 7 | width: usize, 8 | height: usize 9 | } 10 | 11 | impl Capturer { 12 | pub fn new(display: Display) -> io::Result { 13 | let width = display.width(); 14 | let height = display.height(); 15 | let inner = dxgi::Capturer::new(&display.0)?; 16 | Ok(Capturer { inner, width, height }) 17 | } 18 | 19 | pub fn width(&self) -> usize { 20 | self.width 21 | } 22 | 23 | pub fn height(&self) -> usize { 24 | self.height 25 | } 26 | 27 | pub fn frame<'a>(&'a mut self) -> io::Result> { 28 | const MILLISECONDS_PER_FRAME: u32 = 0; 29 | match self.inner.frame(MILLISECONDS_PER_FRAME) { 30 | Ok(frame) => Ok(Frame(frame)), 31 | Err(ref error) if error.kind() == TimedOut => { 32 | Err(WouldBlock.into()) 33 | }, 34 | Err(error) => Err(error) 35 | } 36 | } 37 | } 38 | 39 | pub struct Frame<'a>(&'a [u8]); 40 | 41 | impl<'a> ops::Deref for Frame<'a> { 42 | type Target = [u8]; 43 | fn deref(&self) -> &[u8] { 44 | self.0 45 | } 46 | } 47 | 48 | pub struct Display(dxgi::Display); 49 | 50 | impl Display { 51 | pub fn primary() -> io::Result { 52 | match dxgi::Displays::new()?.next() { 53 | Some(inner) => Ok(Display(inner)), 54 | None => Err(NotFound.into()) 55 | } 56 | } 57 | 58 | pub fn all() -> io::Result> { 59 | Ok(dxgi::Displays::new()? 60 | .map(Display) 61 | .collect::>()) 62 | } 63 | 64 | pub fn width(&self) -> usize { 65 | self.0.width() as usize 66 | } 67 | 68 | pub fn height(&self) -> usize { 69 | self.0.height() as usize 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/screenshot.rs: -------------------------------------------------------------------------------- 1 | extern crate repng; 2 | extern crate scrap; 3 | 4 | use scrap::{Capturer, Display}; 5 | use std::io::ErrorKind::WouldBlock; 6 | use std::fs::File; 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | fn main() { 11 | let one_second = Duration::new(1, 0); 12 | let one_frame = one_second / 60; 13 | 14 | let display = Display::primary().expect("Couldn't find primary display."); 15 | let mut capturer = Capturer::new(display).expect("Couldn't begin capture."); 16 | let (w, h) = (capturer.width(), capturer.height()); 17 | 18 | loop { 19 | // Wait until there's a frame. 20 | 21 | let buffer = match capturer.frame() { 22 | Ok(buffer) => buffer, 23 | Err(error) => { 24 | if error.kind() == WouldBlock { 25 | // Keep spinning. 26 | thread::sleep(one_frame); 27 | continue; 28 | } else { 29 | panic!("Error: {}", error); 30 | } 31 | } 32 | }; 33 | 34 | println!("Captured! Saving..."); 35 | 36 | // Flip the ARGB image into a BGRA image. 37 | 38 | let mut bitflipped = Vec::with_capacity(w * h * 4); 39 | let stride = buffer.len() / h; 40 | 41 | for y in 0..h { 42 | for x in 0..w { 43 | let i = stride * y + 4 * x; 44 | bitflipped.extend_from_slice(&[ 45 | buffer[i + 2], 46 | buffer[i + 1], 47 | buffer[i], 48 | 255, 49 | ]); 50 | } 51 | } 52 | 53 | // Save the image. 54 | 55 | repng::encode( 56 | File::create("screenshot.png").unwrap(), 57 | w as u32, 58 | h as u32, 59 | &bitflipped, 60 | ).unwrap(); 61 | 62 | println!("Image saved to `screenshot.png`."); 63 | break; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/x11/server.rs: -------------------------------------------------------------------------------- 1 | use std::ptr; 2 | use std::rc::Rc; 3 | use super::DisplayIter; 4 | use super::ffi::*; 5 | 6 | #[derive(Debug)] 7 | pub struct Server { 8 | raw: *mut xcb_connection_t, 9 | screenp: i32, 10 | setup: *const xcb_setup_t 11 | } 12 | 13 | impl Server { 14 | pub fn displays(slf: Rc) -> DisplayIter { 15 | unsafe { 16 | DisplayIter::new(slf) 17 | } 18 | } 19 | 20 | pub fn default() -> Result { 21 | Server::connect(ptr::null()) 22 | } 23 | 24 | pub fn connect(addr: *const i8) -> Result { 25 | unsafe { 26 | let mut screenp = 0; 27 | let raw = xcb_connect(addr, &mut screenp); 28 | 29 | let error = xcb_connection_has_error(raw); 30 | if error != 0 { 31 | xcb_disconnect(raw); 32 | Err(Error::from(error)) 33 | } else { 34 | let setup = xcb_get_setup(raw); 35 | Ok(Server { raw, screenp, setup }) 36 | } 37 | } 38 | } 39 | 40 | pub fn raw(&self) -> *mut xcb_connection_t { self.raw } 41 | pub fn screenp(&self) -> i32 { self.screenp } 42 | pub fn setup(&self) -> *const xcb_setup_t { self.setup } 43 | } 44 | 45 | impl Drop for Server { 46 | fn drop(&mut self) { 47 | unsafe { 48 | xcb_disconnect(self.raw); 49 | } 50 | } 51 | } 52 | 53 | #[derive(Clone, Copy, Debug)] 54 | pub enum Error { 55 | Generic, 56 | UnsupportedExtension, 57 | InsufficientMemory, 58 | RequestTooLong, 59 | ParseError, 60 | InvalidScreen 61 | } 62 | 63 | impl From for Error { 64 | fn from(x: i32) -> Error { 65 | use self::Error::*; 66 | match x { 67 | 2 => UnsupportedExtension, 68 | 3 => InsufficientMemory, 69 | 4 => RequestTooLong, 70 | 5 => ParseError, 71 | 6 => InvalidScreen, 72 | _ => Generic 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/common/x11.rs: -------------------------------------------------------------------------------- 1 | use x11; 2 | use std::{io, ops}; 3 | use std::rc::Rc; 4 | 5 | pub struct Capturer(x11::Capturer); 6 | 7 | impl Capturer { 8 | pub fn new(display: Display) -> io::Result { 9 | x11::Capturer::new(display.0).map(Capturer) 10 | } 11 | 12 | pub fn width(&self) -> usize { 13 | self.0.display().rect().w as usize 14 | } 15 | 16 | pub fn height(&self) -> usize { 17 | self.0.display().rect().h as usize 18 | } 19 | 20 | pub fn frame<'a>(&'a mut self) -> io::Result> { 21 | Ok(Frame(self.0.frame())) 22 | } 23 | } 24 | 25 | pub struct Frame<'a>(&'a [u8]); 26 | 27 | impl<'a> ops::Deref for Frame<'a> { 28 | type Target = [u8]; 29 | fn deref(&self) -> &[u8] { 30 | self.0 31 | } 32 | } 33 | 34 | pub struct Display(x11::Display); 35 | 36 | impl Display { 37 | pub fn primary() -> io::Result { 38 | let server = Rc::new(match x11::Server::default() { 39 | Ok(server) => server, 40 | Err(_) => return Err(io::ErrorKind::ConnectionRefused.into()) 41 | }); 42 | 43 | let mut displays = x11::Server::displays(server); 44 | let mut best = displays.next(); 45 | if best.as_ref().map(|x| x.is_default()) == Some(false) { 46 | best = displays.find(|x| x.is_default()).or(best); 47 | } 48 | 49 | match best { 50 | Some(best) => Ok(Display(best)), 51 | None => Err(io::ErrorKind::NotFound.into()) 52 | } 53 | } 54 | 55 | pub fn all() -> io::Result> { 56 | let server = Rc::new(match x11::Server::default() { 57 | Ok(server) => server, 58 | Err(_) => return Err(io::ErrorKind::ConnectionRefused.into()) 59 | }); 60 | 61 | Ok(x11::Server::displays(server).map(Display).collect()) 62 | } 63 | 64 | pub fn width(&self) -> usize { 65 | self.0.rect().w as usize 66 | } 67 | 68 | pub fn height(&self) -> usize { 69 | self.0.rect().h as usize 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/quartz/config.rs: -------------------------------------------------------------------------------- 1 | use super::ffi::*; 2 | use libc::c_void; 3 | use std::ptr; 4 | 5 | //TODO: Color space, YCbCr matrix. 6 | pub struct Config { 7 | /// Whether the cursor is visible. 8 | pub cursor: bool, 9 | /// Whether it should letterbox or stretch. 10 | pub letterbox: bool, 11 | /// Minimum seconds per frame. 12 | pub throttle: f64, 13 | /// How many frames are allocated. 14 | /// 3 is the recommended value. 15 | /// 8 is the maximum value. 16 | pub queue_length: i8 17 | } 18 | 19 | impl Config { 20 | /// Don't forget to CFRelease this! 21 | pub fn build(self) -> CFDictionaryRef { 22 | unsafe { 23 | let throttle = CFNumberCreate( 24 | ptr::null_mut(), 25 | CFNumberType::Float64, 26 | &self.throttle as *const _ as *const c_void 27 | ); 28 | let queue_length = CFNumberCreate( 29 | ptr::null_mut(), 30 | CFNumberType::SInt8, 31 | &self.queue_length as *const _ as *const c_void 32 | ); 33 | 34 | let keys: [CFStringRef; 4] = [ 35 | kCGDisplayStreamShowCursor, 36 | kCGDisplayStreamPreserveAspectRatio, 37 | kCGDisplayStreamMinimumFrameTime, 38 | kCGDisplayStreamQueueDepth 39 | ]; 40 | let values: [*mut c_void; 4] = [ 41 | cfbool(self.cursor), 42 | cfbool(self.letterbox), 43 | throttle, 44 | queue_length 45 | ]; 46 | 47 | let res = CFDictionaryCreate( 48 | ptr::null_mut(), 49 | keys.as_ptr(), 50 | values.as_ptr(), 51 | 4, 52 | &kCFTypeDictionaryKeyCallBacks, 53 | &kCFTypeDictionaryValueCallBacks 54 | ); 55 | 56 | CFRelease(throttle); 57 | CFRelease(queue_length); 58 | 59 | res 60 | } 61 | } 62 | } 63 | 64 | impl Default for Config { 65 | fn default() -> Config { 66 | Config { 67 | cursor: false, 68 | letterbox: true, 69 | throttle: 0.0, 70 | queue_length: 3 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/quartz/capturer.rs: -------------------------------------------------------------------------------- 1 | use block::{Block, ConcreteBlock}; 2 | use libc::c_void; 3 | use super::ffi::*; 4 | use super::config::Config; 5 | use super::display::Display; 6 | use super::frame::Frame; 7 | use std::ptr; 8 | 9 | pub struct Capturer { 10 | stream: CGDisplayStreamRef, 11 | queue: DispatchQueue, 12 | 13 | width: usize, 14 | height: usize, 15 | format: PixelFormat, 16 | display: Display 17 | } 18 | 19 | impl Capturer { 20 | pub fn new( 21 | display: Display, 22 | width: usize, 23 | height: usize, 24 | format: PixelFormat, 25 | config: Config, 26 | handler: F 27 | ) -> Result { 28 | let handler: FrameAvailableHandler = 29 | ConcreteBlock::new(move |status, _, surface, _| { 30 | use self::CGDisplayStreamFrameStatus::*; 31 | if status == FrameComplete { 32 | handler(unsafe { Frame::new(surface) }); 33 | } 34 | }).copy(); 35 | 36 | let queue = unsafe { 37 | dispatch_queue_create( 38 | b"quadrupleslap.scrap\0".as_ptr() as *const i8, 39 | ptr::null_mut() 40 | ) 41 | }; 42 | 43 | let stream = unsafe { 44 | let config = config.build(); 45 | let stream = CGDisplayStreamCreateWithDispatchQueue( 46 | display.id(), 47 | width, 48 | height, 49 | format, 50 | config, 51 | queue, 52 | &*handler as *const Block<_, _> as *const c_void 53 | ); 54 | CFRelease(config); 55 | stream 56 | }; 57 | 58 | match unsafe { CGDisplayStreamStart(stream) } { 59 | CGError::Success => Ok(Capturer { 60 | stream, queue, width, height, format, display 61 | }), 62 | x => Err(x) 63 | } 64 | } 65 | 66 | pub fn width(&self) -> usize { self.width } 67 | pub fn height(&self) -> usize { self.height } 68 | pub fn format(&self) -> PixelFormat { self.format } 69 | pub fn display(&self) -> Display { self.display } 70 | } 71 | 72 | impl Drop for Capturer { 73 | fn drop(&mut self) { 74 | unsafe { 75 | //TODO: Maybe it should wait until `Stopped` before releasing? 76 | let _ = CGDisplayStreamStop(self.stream); 77 | CFRelease(self.stream); 78 | dispatch_release(self.queue); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/common/quartz.rs: -------------------------------------------------------------------------------- 1 | use quartz; 2 | use std::{io, ops, mem}; 3 | use std::marker::PhantomData; 4 | use std::sync::{Arc, Mutex, TryLockError}; 5 | 6 | pub struct Capturer { 7 | inner: quartz::Capturer, 8 | frame: Arc>> 9 | } 10 | 11 | impl Capturer { 12 | pub fn new(display: Display) -> io::Result { 13 | let frame = Arc::new(Mutex::new(None)); 14 | 15 | let f = frame.clone(); 16 | let inner = quartz::Capturer::new( 17 | display.0, 18 | display.width(), 19 | display.height(), 20 | quartz::PixelFormat::Argb8888, 21 | Default::default(), 22 | move |inner| { 23 | if let Ok(mut f) = f.lock() { 24 | *f = Some(inner); 25 | } 26 | } 27 | ).map_err(|_| io::Error::from(io::ErrorKind::Other))?; 28 | 29 | Ok(Capturer { inner, frame }) 30 | } 31 | 32 | pub fn width(&self) -> usize { 33 | self.inner.width() 34 | } 35 | 36 | pub fn height(&self) -> usize { 37 | self.inner.height() 38 | } 39 | 40 | pub fn frame<'a>(&'a mut self) -> io::Result> { 41 | match self.frame.try_lock() { 42 | Ok(mut handle) => { 43 | let mut frame = None; 44 | mem::swap(&mut frame, &mut handle); 45 | 46 | match frame { 47 | Some(frame) => 48 | Ok(Frame(frame, PhantomData)), 49 | 50 | None => 51 | Err(io::ErrorKind::WouldBlock.into()) 52 | } 53 | } 54 | 55 | Err(TryLockError::WouldBlock) => 56 | Err(io::ErrorKind::WouldBlock.into()), 57 | 58 | Err(TryLockError::Poisoned(..)) => 59 | Err(io::ErrorKind::Other.into()) 60 | } 61 | } 62 | } 63 | 64 | pub struct Frame<'a>( 65 | quartz::Frame, 66 | PhantomData<&'a [u8]> 67 | ); 68 | 69 | impl<'a> ops::Deref for Frame<'a> { 70 | type Target = [u8]; 71 | fn deref(&self) -> &[u8] { 72 | &*self.0 73 | } 74 | } 75 | 76 | pub struct Display(quartz::Display); 77 | 78 | impl Display { 79 | pub fn primary() -> io::Result { 80 | Ok(Display(quartz::Display::primary())) 81 | } 82 | 83 | pub fn all() -> io::Result> { 84 | Ok( 85 | quartz::Display::online() 86 | .map_err(|_| io::Error::from(io::ErrorKind::Other))? 87 | .into_iter() 88 | .map(Display) 89 | .collect() 90 | ) 91 | } 92 | 93 | pub fn width(&self) -> usize { 94 | self.0.width() 95 | } 96 | 97 | pub fn height(&self) -> usize { 98 | self.0.height() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/x11/iter.rs: -------------------------------------------------------------------------------- 1 | use libc; 2 | use std::ptr; 3 | use std::rc::Rc; 4 | use super::{Display, Rect, Server}; 5 | use super::ffi::*; 6 | 7 | //TODO: Do I have to free the displays? 8 | 9 | pub struct DisplayIter { 10 | outer: xcb_screen_iterator_t, 11 | inner: Option<(xcb_randr_monitor_info_iterator_t, xcb_window_t)>, 12 | server: Rc, 13 | } 14 | 15 | impl DisplayIter { 16 | pub unsafe fn new(server: Rc) -> DisplayIter { 17 | let mut outer = xcb_setup_roots_iterator(server.setup()); 18 | let inner = Self::next_screen(&mut outer, &server); 19 | DisplayIter { outer, inner, server } 20 | } 21 | 22 | fn next_screen( 23 | outer: &mut xcb_screen_iterator_t, 24 | server: &Server 25 | ) -> Option<(xcb_randr_monitor_info_iterator_t, xcb_window_t)> { 26 | if outer.rem == 0 { 27 | return None; 28 | } 29 | 30 | unsafe { 31 | let root = (*outer.data).root; 32 | 33 | let cookie = xcb_randr_get_monitors_unchecked( 34 | server.raw(), 35 | root, 36 | 1, //TODO: I don't know if this should be true or false. 37 | ); 38 | 39 | let response = xcb_randr_get_monitors_reply( 40 | server.raw(), 41 | cookie, 42 | ptr::null_mut(), 43 | ); 44 | 45 | let inner = xcb_randr_get_monitors_monitors_iterator(response); 46 | 47 | libc::free(response as *mut _); 48 | xcb_screen_next(outer); 49 | 50 | Some((inner, root)) 51 | } 52 | } 53 | } 54 | 55 | impl Iterator for DisplayIter { 56 | type Item = Display; 57 | 58 | fn next(&mut self) -> Option { 59 | loop { 60 | if let Some((ref mut inner, root)) = self.inner { 61 | // If there is something in the current screen, return that. 62 | if inner.rem != 0 {unsafe { 63 | let data = &*inner.data; 64 | 65 | let display = Display::new( 66 | self.server.clone(), 67 | data.primary != 0, 68 | Rect { 69 | x: data.x, 70 | y: data.y, 71 | w: data.width, 72 | h: data.height, 73 | }, 74 | root 75 | ); 76 | 77 | xcb_randr_monitor_info_next(inner); 78 | return Some(display); 79 | }} 80 | } else { 81 | // If there is no current screen, the screen iterator is empty. 82 | return None; 83 | } 84 | 85 | // The current screen was empty, so try the next screen. 86 | self.inner = Self::next_screen(&mut self.outer, &self.server); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/x11/capturer.rs: -------------------------------------------------------------------------------- 1 | use libc; 2 | use std::{io, ptr, slice}; 3 | use super::Display; 4 | use super::ffi::*; 5 | 6 | pub struct Capturer { 7 | display: Display, 8 | shmid: i32, 9 | xcbid: u32, 10 | buffer: *const u8, 11 | 12 | request: xcb_shm_get_image_cookie_t, 13 | loading: usize, 14 | size: usize 15 | } 16 | 17 | impl Capturer { 18 | pub fn new( 19 | display: Display 20 | ) -> io::Result { 21 | // Calculate dimensions. 22 | 23 | let pixel_width = 4; 24 | let rect = display.rect(); 25 | let size = (rect.w as usize) * (rect.h as usize) * pixel_width; 26 | 27 | // Create a shared memory segment. 28 | 29 | let shmid = unsafe { 30 | libc::shmget( 31 | libc::IPC_PRIVATE, 32 | size * 2, 33 | // Everyone can do anything. 34 | libc::IPC_CREAT | 0o777 35 | ) 36 | }; 37 | 38 | if shmid == -1 { 39 | return Err(io::Error::last_os_error()); 40 | } 41 | 42 | // Attach the segment to a readable address. 43 | 44 | let buffer = unsafe { 45 | libc::shmat( 46 | shmid, 47 | ptr::null(), 48 | libc::SHM_RDONLY 49 | ) 50 | } as *mut u8; 51 | 52 | if buffer as isize == -1 { 53 | return Err(io::Error::last_os_error()); 54 | } 55 | 56 | // Attach the segment to XCB. 57 | 58 | let server = display.server().raw(); 59 | let xcbid = unsafe { xcb_generate_id(server) }; 60 | unsafe { 61 | xcb_shm_attach( 62 | server, 63 | xcbid, 64 | shmid as u32, 65 | 0 // False, i.e. not read-only. 66 | ); 67 | } 68 | 69 | // Start the first screenshot early. 70 | 71 | let request = unsafe { 72 | xcb_shm_get_image_unchecked( 73 | server, 74 | display.root(), 75 | rect.x, rect.y, 76 | rect.w, rect.h, 77 | !0, // Plane mask. 78 | XCB_IMAGE_FORMAT_Z_PIXMAP, 79 | xcbid, 80 | 0 // Byte offset. 81 | ) 82 | }; 83 | 84 | // Return! 85 | 86 | Ok(Capturer { 87 | display, shmid, xcbid, buffer, 88 | request, loading: 0, size 89 | }) 90 | } 91 | 92 | pub fn display(&self) -> &Display { 93 | &self.display 94 | } 95 | 96 | pub fn frame<'b>(&'b mut self) -> &'b [u8] { 97 | // Get the return value. 98 | 99 | let result = unsafe { 100 | let off = self.loading & self.size; 101 | slice::from_raw_parts( 102 | self.buffer.offset(off as isize), 103 | self.size 104 | ) 105 | }; 106 | 107 | // Block for response. 108 | 109 | unsafe { 110 | self.handle_response(); 111 | } 112 | 113 | // Start next request. 114 | 115 | let rect = self.display.rect(); 116 | 117 | self.loading ^= !0; 118 | self.request = unsafe { 119 | xcb_shm_get_image_unchecked( 120 | self.display.server().raw(), 121 | self.display.root(), 122 | rect.x, rect.y, 123 | rect.w, rect.h, 124 | !0, 125 | XCB_IMAGE_FORMAT_Z_PIXMAP, 126 | self.xcbid, 127 | (self.loading & self.size) as u32 128 | ) 129 | }; 130 | 131 | // Return! 132 | 133 | result 134 | } 135 | 136 | unsafe fn handle_response(&self) { 137 | let response = xcb_shm_get_image_reply( 138 | self.display.server().raw(), 139 | self.request, 140 | ptr::null_mut() 141 | ); 142 | 143 | libc::free(response as *mut _); 144 | } 145 | } 146 | 147 | impl Drop for Capturer { 148 | fn drop(&mut self) { 149 | unsafe { 150 | // Process pending request. 151 | self.handle_response(); 152 | // Detach segment from XCB. 153 | xcb_shm_detach(self.display.server().raw(), self.xcbid); 154 | // Detach segment from our space. 155 | libc::shmdt(self.buffer as *mut _); 156 | // Destroy the shared memory segment. 157 | libc::shmctl(self.shmid, libc::IPC_RMID, ptr::null_mut()); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/x11/ffi.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | 3 | use libc::c_void; 4 | 5 | #[link(name="xcb")] 6 | #[link(name="xcb-shm")] 7 | #[link(name="xcb-randr")] 8 | extern { 9 | pub fn xcb_connect( 10 | displayname: *const i8, 11 | screenp: *mut i32 12 | ) -> *mut xcb_connection_t; 13 | 14 | pub fn xcb_disconnect( 15 | c: *mut xcb_connection_t 16 | ); 17 | 18 | pub fn xcb_connection_has_error( 19 | c: *mut xcb_connection_t 20 | ) -> i32; 21 | 22 | pub fn xcb_get_setup( 23 | c: *mut xcb_connection_t 24 | ) -> *const xcb_setup_t; 25 | 26 | pub fn xcb_setup_roots_iterator( 27 | r: *const xcb_setup_t 28 | ) -> xcb_screen_iterator_t; 29 | 30 | pub fn xcb_screen_next( 31 | i: *mut xcb_screen_iterator_t 32 | ); 33 | 34 | pub fn xcb_generate_id( 35 | c: *mut xcb_connection_t 36 | ) -> u32; 37 | 38 | pub fn xcb_shm_attach( 39 | c: *mut xcb_connection_t, 40 | shmseg: xcb_shm_seg_t, 41 | shmid: u32, 42 | read_only: u8 43 | ) -> xcb_void_cookie_t; 44 | 45 | pub fn xcb_shm_detach( 46 | c: *mut xcb_connection_t, 47 | shmseg: xcb_shm_seg_t 48 | ) -> xcb_void_cookie_t; 49 | 50 | pub fn xcb_shm_get_image_unchecked( 51 | c: *mut xcb_connection_t, 52 | drawable: xcb_drawable_t, 53 | x: i16, 54 | y: i16, 55 | width: u16, 56 | height: u16, 57 | plane_mask: u32, 58 | format: u8, 59 | shmseg: xcb_shm_seg_t, 60 | offset: u32 61 | ) -> xcb_shm_get_image_cookie_t; 62 | 63 | pub fn xcb_shm_get_image_reply( 64 | c: *mut xcb_connection_t, 65 | cookie: xcb_shm_get_image_cookie_t, 66 | e: *mut *mut xcb_generic_error_t 67 | ) -> *mut xcb_shm_get_image_reply_t; 68 | 69 | pub fn xcb_randr_get_monitors_unchecked( 70 | c: *mut xcb_connection_t, 71 | window: xcb_window_t, 72 | get_active: u8, 73 | ) -> xcb_randr_get_monitors_cookie_t; 74 | 75 | pub fn xcb_randr_get_monitors_reply( 76 | c: *mut xcb_connection_t, 77 | cookie: xcb_randr_get_monitors_cookie_t, 78 | e: *mut *mut xcb_generic_error_t, 79 | ) -> *mut xcb_randr_get_monitors_reply_t; 80 | 81 | pub fn xcb_randr_get_monitors_monitors_iterator( 82 | r: *const xcb_randr_get_monitors_reply_t, 83 | ) -> xcb_randr_monitor_info_iterator_t; 84 | 85 | pub fn xcb_randr_monitor_info_next( 86 | i: *mut xcb_randr_monitor_info_iterator_t, 87 | ); 88 | } 89 | 90 | pub const XCB_IMAGE_FORMAT_Z_PIXMAP: u8 = 2; 91 | 92 | pub type xcb_atom_t = u32; 93 | pub type xcb_connection_t = c_void; 94 | pub type xcb_window_t = u32; 95 | pub type xcb_keycode_t = u8; 96 | pub type xcb_visualid_t = u32; 97 | pub type xcb_timestamp_t = u32; 98 | pub type xcb_colormap_t = u32; 99 | pub type xcb_shm_seg_t = u32; 100 | pub type xcb_drawable_t = u32; 101 | 102 | #[repr(C)] 103 | pub struct xcb_setup_t { 104 | pub status: u8, 105 | pub pad0: u8, 106 | pub protocol_major_version: u16, 107 | pub protocol_minor_version: u16, 108 | pub length: u16, 109 | pub release_number: u32, 110 | pub resource_id_base: u32, 111 | pub resource_id_mask: u32, 112 | pub motion_buffer_size: u32, 113 | pub vendor_len: u16, 114 | pub maximum_request_length: u16, 115 | pub roots_len: u8, 116 | pub pixmap_formats_len: u8, 117 | pub image_byte_order: u8, 118 | pub bitmap_format_bit_order: u8, 119 | pub bitmap_format_scanline_unit: u8, 120 | pub bitmap_format_scanline_pad: u8, 121 | pub min_keycode: xcb_keycode_t, 122 | pub max_keycode: xcb_keycode_t, 123 | pub pad1: [u8; 4] 124 | } 125 | 126 | #[repr(C)] 127 | pub struct xcb_screen_iterator_t { 128 | pub data: *mut xcb_screen_t, 129 | pub rem: i32, 130 | pub index: i32 131 | } 132 | 133 | #[repr(C)] 134 | pub struct xcb_screen_t { 135 | pub root: xcb_window_t, 136 | pub default_colormap: xcb_colormap_t, 137 | pub white_pixel: u32, 138 | pub black_pixel: u32, 139 | pub current_input_masks: u32, 140 | pub width_in_pixels: u16, 141 | pub height_in_pixels: u16, 142 | pub width_in_millimeters: u16, 143 | pub height_in_millimeters: u16, 144 | pub min_installed_maps: u16, 145 | pub max_installed_maps: u16, 146 | pub root_visual: xcb_visualid_t, 147 | pub backing_stores: u8, 148 | pub save_unders: u8, 149 | pub root_depth: u8, 150 | pub allowed_depths_len: u8 151 | } 152 | 153 | #[repr(C)] 154 | pub struct xcb_randr_monitor_info_iterator_t { 155 | pub data: *mut xcb_randr_monitor_info_t, 156 | pub rem: i32, 157 | pub index: i32, 158 | } 159 | 160 | #[repr(C)] 161 | pub struct xcb_randr_monitor_info_t { 162 | pub name: xcb_atom_t, 163 | pub primary: u8, 164 | pub automatic: u8, 165 | pub n_output: u16, 166 | pub x: i16, 167 | pub y: i16, 168 | pub width: u16, 169 | pub height: u16, 170 | pub width_mm: u32, 171 | pub height_mm: u32, 172 | } 173 | 174 | #[repr(C)] 175 | #[derive(Clone, Copy)] 176 | pub struct xcb_randr_get_monitors_cookie_t { 177 | pub sequence: u32 178 | } 179 | 180 | #[repr(C)] 181 | #[derive(Clone, Copy)] 182 | pub struct xcb_shm_get_image_cookie_t { 183 | pub sequence: u32 184 | } 185 | 186 | #[repr(C)] 187 | #[derive(Clone, Copy)] 188 | pub struct xcb_void_cookie_t { 189 | pub sequence: u32 190 | } 191 | 192 | #[repr(C)] 193 | pub struct xcb_generic_error_t { 194 | pub response_type: u8, 195 | pub error_code: u8, 196 | pub sequence: u16, 197 | pub resource_id: u32, 198 | pub minor_code: u16, 199 | pub major_code: u8, 200 | pub pad0: u8, 201 | pub pad: [u32; 5], 202 | pub full_sequence: u32 203 | } 204 | 205 | #[repr(C)] 206 | pub struct xcb_shm_get_image_reply_t { 207 | pub response_type: u8, 208 | pub depth: u8, 209 | pub sequence: u16, 210 | pub length: u32, 211 | pub visual: xcb_visualid_t, 212 | pub size: u32 213 | } 214 | 215 | #[repr(C)] 216 | pub struct xcb_randr_get_monitors_reply_t { 217 | pub response_type: u8, 218 | pub pad0: u8, 219 | pub sequence: u16, 220 | pub length: u32, 221 | pub timestamp: xcb_timestamp_t, 222 | pub n_monitors: u32, 223 | pub n_outputs: u32, 224 | pub pad1: [u8; 12], 225 | } 226 | -------------------------------------------------------------------------------- /src/quartz/ffi.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use block::RcBlock; 4 | use libc::c_void; 5 | 6 | pub type CGDisplayStreamRef = *mut c_void; 7 | pub type CFDictionaryRef = *mut c_void; 8 | pub type CFBooleanRef = *mut c_void; 9 | pub type CFNumberRef = *mut c_void; 10 | pub type CFStringRef = *mut c_void; 11 | pub type CGDisplayStreamUpdateRef = *mut c_void; 12 | pub type IOSurfaceRef = *mut c_void; 13 | pub type DispatchQueue = *mut c_void; 14 | pub type DispatchQueueAttr = *mut c_void; 15 | pub type CFAllocatorRef = *mut c_void; 16 | 17 | #[repr(C)] 18 | pub struct CFDictionaryKeyCallBacks { 19 | callbacks: [usize; 5], 20 | version: i32 21 | } 22 | 23 | #[repr(C)] 24 | pub struct CFDictionaryValueCallBacks { 25 | callbacks: [usize; 4], 26 | version: i32 27 | } 28 | 29 | macro_rules! pixel_format { 30 | ($a:expr, $b:expr, $c:expr, $d:expr) => { 31 | ($a as i32) << 24 32 | | ($b as i32) << 16 33 | | ($c as i32) << 8 34 | | ($d as i32) 35 | } 36 | } 37 | 38 | pub const SURFACE_LOCK_READ_ONLY: u32 = 0x0000_0001; 39 | pub const SURFACE_LOCK_AVOID_SYNC: u32 = 0x0000_0002; 40 | 41 | pub fn cfbool(x: bool) -> CFBooleanRef { 42 | unsafe { 43 | if x { kCFBooleanTrue } else { kCFBooleanFalse } 44 | } 45 | } 46 | 47 | #[repr(i32)] 48 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 49 | pub enum CGDisplayStreamFrameStatus { 50 | /// A new frame was generated. 51 | FrameComplete = 0, 52 | /// A new frame was not generated because the display did not change. 53 | FrameIdle = 1, 54 | /// A new frame was not generated because the display has gone blank. 55 | FrameBlank = 2, 56 | /// The display stream was stopped. 57 | Stopped = 3, 58 | #[doc(hidden)] 59 | __Nonexhaustive 60 | } 61 | 62 | #[repr(i32)] 63 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 64 | pub enum CFNumberType { 65 | /* Fixed-width types */ 66 | SInt8 = 1, 67 | SInt16 = 2, 68 | SInt32 = 3, 69 | SInt64 = 4, 70 | Float32 = 5, 71 | Float64 = 6, /* 64-bit IEEE 754 */ 72 | /* Basic C types */ 73 | Char = 7, 74 | Short = 8, 75 | Int = 9, 76 | Long = 10, 77 | LongLong = 11, 78 | Float = 12, 79 | Double = 13, 80 | /* Other */ 81 | CFIndex = 14, 82 | NSInteger = 15, 83 | CGFloat = 16 84 | } 85 | 86 | #[repr(i32)] 87 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 88 | #[must_use] 89 | pub enum CGError { 90 | Success = 0, 91 | Failure = 1000, 92 | IllegalArgument = 1001, 93 | InvalidConnection = 1002, 94 | InvalidContext = 1003, 95 | CannotComplete = 1004, 96 | NotImplemented = 1006, 97 | RangeCheck = 1007, 98 | TypeCheck = 1008, 99 | InvalidOperation = 1010, 100 | NoneAvailable = 1011, 101 | #[doc(hidden)] 102 | __Nonexhaustive 103 | } 104 | 105 | #[repr(i32)] 106 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 107 | pub enum PixelFormat { 108 | /// Packed Little Endian ARGB8888 109 | Argb8888 = pixel_format!('B','G','R','A'), 110 | /// Packed Little Endian ARGB2101010 111 | Argb2101010 = pixel_format!('l','1','0','r'), 112 | /// 2-plane "video" range YCbCr 4:2:0 113 | YCbCr420Video = pixel_format!('4','2','0','v'), 114 | /// 2-plane "full" range YCbCr 4:2:0 115 | YCbCr420Full = pixel_format!('4','2','0','f'), 116 | #[doc(hidden)] 117 | __Nonexhaustive 118 | } 119 | 120 | pub type CGDisplayStreamFrameAvailableHandler = *const c_void; 121 | 122 | pub type FrameAvailableHandler = RcBlock<( 123 | CGDisplayStreamFrameStatus, // status 124 | u64, // displayTime 125 | IOSurfaceRef, // frameSurface 126 | CGDisplayStreamUpdateRef // updateRef 127 | ), ()>; 128 | 129 | #[link(name="System", kind="dylib")] 130 | #[link(name="CoreGraphics", kind="framework")] 131 | #[link(name="CoreFoundation", kind="framework")] 132 | #[link(name="IOSurface", kind="framework")] 133 | extern { 134 | // CoreGraphics 135 | 136 | pub static kCGDisplayStreamShowCursor: CFStringRef; 137 | pub static kCGDisplayStreamPreserveAspectRatio: CFStringRef; 138 | pub static kCGDisplayStreamMinimumFrameTime: CFStringRef; 139 | pub static kCGDisplayStreamQueueDepth: CFStringRef; 140 | 141 | pub fn CGDisplayStreamCreateWithDispatchQueue( 142 | display: u32, 143 | output_width: usize, 144 | output_height: usize, 145 | pixel_format: PixelFormat, 146 | properties: CFDictionaryRef, 147 | queue: DispatchQueue, 148 | handler: CGDisplayStreamFrameAvailableHandler 149 | ) -> CGDisplayStreamRef; 150 | 151 | pub fn CGDisplayStreamStart( 152 | displayStream: CGDisplayStreamRef 153 | ) -> CGError; 154 | 155 | pub fn CGDisplayStreamStop( 156 | displayStream: CGDisplayStreamRef 157 | ) -> CGError; 158 | 159 | pub fn CGMainDisplayID() -> u32; 160 | pub fn CGDisplayPixelsWide(display: u32) -> usize; 161 | pub fn CGDisplayPixelsHigh(display: u32) -> usize; 162 | 163 | pub fn CGGetOnlineDisplayList( 164 | max_displays: u32, 165 | online_displays: *mut u32, 166 | display_count: *mut u32 167 | ) -> CGError; 168 | 169 | pub fn CGDisplayIsBuiltin(display: u32) -> i32; 170 | pub fn CGDisplayIsMain(display: u32) -> i32; 171 | pub fn CGDisplayIsActive(display: u32) -> i32; 172 | pub fn CGDisplayIsOnline(display: u32) -> i32; 173 | 174 | // IOSurface 175 | 176 | pub fn IOSurfaceGetAllocSize(buffer: IOSurfaceRef) -> usize; 177 | pub fn IOSurfaceGetBaseAddress(buffer: IOSurfaceRef) -> *mut c_void; 178 | pub fn IOSurfaceIncrementUseCount(buffer: IOSurfaceRef); 179 | pub fn IOSurfaceDecrementUseCount(buffer: IOSurfaceRef); 180 | pub fn IOSurfaceLock( 181 | buffer: IOSurfaceRef, 182 | options: u32, 183 | seed: *mut u32 184 | ) -> i32; 185 | pub fn IOSurfaceUnlock( 186 | buffer: IOSurfaceRef, 187 | options: u32, 188 | seed: *mut u32 189 | ) -> i32; 190 | 191 | // Dispatch 192 | 193 | pub fn dispatch_queue_create( 194 | label: *const i8, 195 | attr: DispatchQueueAttr 196 | ) -> DispatchQueue; 197 | 198 | pub fn dispatch_release( 199 | object: DispatchQueue 200 | ); 201 | 202 | // Core Foundation 203 | 204 | pub static kCFTypeDictionaryKeyCallBacks: CFDictionaryKeyCallBacks; 205 | pub static kCFTypeDictionaryValueCallBacks: CFDictionaryValueCallBacks; 206 | 207 | // EVEN THE BOOLEANS ARE REFERENCES. 208 | pub static kCFBooleanTrue: CFBooleanRef; 209 | pub static kCFBooleanFalse: CFBooleanRef; 210 | 211 | pub fn CFNumberCreate( 212 | allocator: CFAllocatorRef, 213 | theType: CFNumberType, 214 | valuePtr: *const c_void 215 | ) -> CFNumberRef; 216 | 217 | pub fn CFDictionaryCreate( 218 | allocator: CFAllocatorRef, 219 | keys: *const *mut c_void, 220 | values: *const *mut c_void, 221 | numValues: i64, 222 | keyCallBacks: *const CFDictionaryKeyCallBacks, 223 | valueCallBacks: *const CFDictionaryValueCallBacks 224 | ) -> CFDictionaryRef; 225 | 226 | pub fn CFRetain(cf: *const c_void); 227 | pub fn CFRelease(cf: *const c_void); 228 | } 229 | -------------------------------------------------------------------------------- /src/dxgi/mod.rs: -------------------------------------------------------------------------------- 1 | use self::ffi::*; 2 | use std::{io, mem, ptr, slice}; 3 | use winapi::{ 4 | HRESULT, 5 | IDXGIAdapter1, 6 | IDXGIFactory1, 7 | IDXGIOutput1, 8 | S_OK, 9 | UINT, 10 | DXGI_OUTPUT_DESC, 11 | LONG, 12 | DXGI_MODE_ROTATION, 13 | ID3D11Device, 14 | ID3D11DeviceContext, 15 | IDXGIOutputDuplication, 16 | D3D11_SDK_VERSION, 17 | D3D_DRIVER_TYPE_UNKNOWN, 18 | D3D_FEATURE_LEVEL_9_1, 19 | DXGI_ERROR_ACCESS_LOST, 20 | DXGI_ERROR_WAIT_TIMEOUT, 21 | DXGI_ERROR_INVALID_CALL, 22 | E_ACCESSDENIED, 23 | DXGI_ERROR_UNSUPPORTED, 24 | ID3D11Texture2D, 25 | DXGI_ERROR_NOT_CURRENTLY_AVAILABLE, 26 | DXGI_ERROR_SESSION_DISCONNECTED, 27 | TRUE, 28 | IDXGISurface, 29 | IDXGIResource, 30 | DXGI_RESOURCE_PRIORITY_MAXIMUM, 31 | D3D11_CPU_ACCESS_READ, 32 | D3D11_USAGE_STAGING 33 | }; 34 | 35 | mod ffi; 36 | 37 | //TODO: Split up into files. 38 | 39 | pub struct Capturer { 40 | device: *mut ID3D11Device, 41 | context: *mut ID3D11DeviceContext, 42 | duplication: *mut IDXGIOutputDuplication, 43 | fastlane: bool, surface: *mut IDXGISurface, 44 | data: *mut u8, len: usize, 45 | height: usize, 46 | } 47 | 48 | impl Capturer { 49 | pub fn new(display: &Display) -> io::Result { 50 | let mut device = ptr::null_mut(); 51 | let mut context = ptr::null_mut(); 52 | let mut duplication = ptr::null_mut(); 53 | let mut desc = unsafe { mem::uninitialized() }; 54 | 55 | if unsafe { 56 | D3D11CreateDevice( 57 | &mut **display.adapter, 58 | D3D_DRIVER_TYPE_UNKNOWN, 59 | ptr::null_mut(), // No software rasterizer. 60 | 0, // No device flags. 61 | ptr::null_mut(), // Feature levels. 62 | 0, // Feature levels' length. 63 | D3D11_SDK_VERSION, 64 | &mut device, 65 | &mut D3D_FEATURE_LEVEL_9_1, 66 | &mut context 67 | ) 68 | } != S_OK { 69 | // Unknown error. 70 | return Err(io::ErrorKind::Other.into()); 71 | } 72 | 73 | let res = wrap_hresult(unsafe { 74 | (*display.inner).DuplicateOutput( 75 | &mut **device, 76 | &mut duplication 77 | ) 78 | }); 79 | 80 | if let Err(err) = res { 81 | unsafe { 82 | (*device).Release(); 83 | (*context).Release(); 84 | } 85 | return Err(err); 86 | } 87 | 88 | unsafe { 89 | (*duplication).GetDesc(&mut desc); 90 | } 91 | 92 | Ok(unsafe { 93 | let mut capturer = Capturer { 94 | device, context, duplication, 95 | fastlane: desc.DesktopImageInSystemMemory == TRUE, 96 | surface: ptr::null_mut(), 97 | height: display.height() as usize, 98 | data: ptr::null_mut(), 99 | len: 0 100 | }; 101 | let _ = capturer.load_frame(0); 102 | capturer 103 | }) 104 | } 105 | 106 | unsafe fn load_frame(&mut self, timeout: UINT) -> io::Result<()> { 107 | let mut frame = ptr::null_mut(); 108 | let mut info = mem::uninitialized(); 109 | self.data = ptr::null_mut(); 110 | 111 | wrap_hresult((*self.duplication).AcquireNextFrame( 112 | timeout, 113 | &mut info, 114 | &mut frame 115 | ))?; 116 | 117 | if self.fastlane { 118 | let mut rect = mem::uninitialized(); 119 | let res = wrap_hresult( 120 | (*self.duplication).MapDesktopSurface(&mut rect) 121 | ); 122 | 123 | (*frame).Release(); 124 | 125 | if let Err(err) = res { 126 | Err(err) 127 | } else { 128 | self.data = rect.pBits; 129 | self.len = self.height * rect.Pitch as usize; 130 | Ok(()) 131 | } 132 | } else { 133 | self.surface = ptr::null_mut(); 134 | self.surface = self.ohgodwhat(frame)?; 135 | 136 | let mut rect = mem::uninitialized(); 137 | wrap_hresult((*self.surface).Map( 138 | &mut rect, 139 | DXGI_MAP_READ 140 | ))?; 141 | 142 | self.data = rect.pBits; 143 | self.len = self.height * rect.Pitch as usize; 144 | Ok(()) 145 | } 146 | } 147 | 148 | unsafe fn ohgodwhat( 149 | &mut self, 150 | frame: *mut IDXGIResource 151 | ) -> io::Result<*mut IDXGISurface> { 152 | let mut texture: *mut ID3D11Texture2D = ptr::null_mut(); 153 | (*frame).QueryInterface( 154 | &IID_ID3D11TEXTURE2D, 155 | &mut texture as *mut *mut _ as *mut *mut _ 156 | ); 157 | 158 | let mut texture_desc = mem::uninitialized(); 159 | (*texture).GetDesc(&mut texture_desc); 160 | 161 | texture_desc.Usage = D3D11_USAGE_STAGING; 162 | texture_desc.BindFlags = 0; 163 | texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ.0; 164 | texture_desc.MiscFlags = 0; 165 | 166 | let mut readable = ptr::null_mut(); 167 | let res = wrap_hresult((*self.device).CreateTexture2D( 168 | &mut texture_desc, 169 | ptr::null(), 170 | &mut readable 171 | )); 172 | 173 | if let Err(err) = res { 174 | (*frame).Release(); 175 | (*texture).Release(); 176 | (*readable).Release(); 177 | Err(err) 178 | } else { 179 | (*readable).SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM); 180 | 181 | let mut surface = ptr::null_mut(); 182 | (*readable).QueryInterface( 183 | &IID_IDXGISURFACE, 184 | &mut surface as *mut *mut _ as *mut *mut _ 185 | ); 186 | 187 | (*self.context).CopyResource( 188 | &mut **readable, 189 | &mut **texture 190 | ); 191 | 192 | (*frame).Release(); 193 | (*texture).Release(); 194 | (*readable).Release(); 195 | Ok(surface) 196 | } 197 | } 198 | 199 | pub fn frame<'a>(&'a mut self, timeout: UINT) -> io::Result<&'a [u8]> { 200 | unsafe { 201 | // Release last frame. 202 | // No error checking needed because we don't care. 203 | // None of the errors crash anyway. 204 | 205 | if self.fastlane { 206 | (*self.duplication).UnMapDesktopSurface(); 207 | } else { 208 | if !self.surface.is_null() { 209 | (*self.surface).Unmap(); 210 | (*self.surface).Release(); 211 | self.surface = ptr::null_mut(); 212 | } 213 | } 214 | 215 | (*self.duplication).ReleaseFrame(); 216 | 217 | // Get next frame. 218 | 219 | self.load_frame(timeout)?; 220 | Ok(slice::from_raw_parts(self.data, self.len)) 221 | } 222 | } 223 | } 224 | 225 | impl Drop for Capturer { 226 | fn drop(&mut self) { 227 | unsafe { 228 | if !self.surface.is_null() { 229 | (*self.surface).Unmap(); 230 | (*self.surface).Release(); 231 | } 232 | (*self.duplication).Release(); 233 | (*self.device).Release(); 234 | (*self.context).Release(); 235 | } 236 | } 237 | } 238 | 239 | pub struct Displays { 240 | factory: *mut IDXGIFactory1, 241 | adapter: *mut IDXGIAdapter1, 242 | /// Index of the CURRENT adapter. 243 | nadapter: UINT, 244 | /// Index of the NEXT display to fetch. 245 | ndisplay: UINT 246 | } 247 | 248 | impl Displays { 249 | pub fn new() -> io::Result { 250 | let mut factory = ptr::null_mut(); 251 | wrap_hresult(unsafe { 252 | CreateDXGIFactory1(&IID_IDXGIFACTORY1, &mut factory) 253 | })?; 254 | 255 | let mut adapter = ptr::null_mut(); 256 | unsafe { 257 | // On error, our adapter is null, so it's fine. 258 | (*factory).EnumAdapters1(0, &mut adapter); 259 | }; 260 | 261 | Ok(Displays { 262 | factory, 263 | adapter, 264 | nadapter: 0, 265 | ndisplay: 0 266 | }) 267 | } 268 | 269 | // No Adapter => Some(None) 270 | // Non-Empty Adapter => Some(Some(OUTPUT)) 271 | // End of Adapter => None 272 | fn read_and_invalidate(&mut self) -> Option> { 273 | // If there is no adapter, there is nothing left for us to do. 274 | 275 | if self.adapter.is_null() { 276 | return Some(None); 277 | } 278 | 279 | // Otherwise, we get the next output of the current adapter. 280 | 281 | let output = unsafe { 282 | let mut output = ptr::null_mut(); 283 | (*self.adapter).EnumOutputs(self.ndisplay, &mut output); 284 | output 285 | }; 286 | 287 | // If the current adapter is done, we free it. 288 | // We return None so the caller gets the next adapter and tries again. 289 | 290 | if output.is_null() { 291 | unsafe { 292 | (*self.adapter).Release(); 293 | self.adapter = ptr::null_mut(); 294 | } 295 | return None; 296 | } 297 | 298 | // Advance to the next display. 299 | 300 | self.ndisplay += 1; 301 | 302 | // We get the display's details. 303 | 304 | let desc = unsafe { 305 | let mut desc = mem::uninitialized(); 306 | (*output).GetDesc(&mut desc); 307 | desc 308 | }; 309 | 310 | // We cast it up to the version needed for desktop duplication. 311 | 312 | let mut inner = ptr::null_mut(); 313 | unsafe { 314 | (*output).QueryInterface( 315 | &IID_IDXGIOUTPUT1, 316 | &mut inner as *mut *mut _ as *mut *mut _ 317 | ); 318 | (*output).Release(); 319 | } 320 | 321 | // If it's null, we have an error. 322 | // So we act like the adapter is done. 323 | 324 | if inner.is_null() { 325 | unsafe { 326 | (*self.adapter).Release(); 327 | self.adapter = ptr::null_mut(); 328 | } 329 | return None; 330 | } 331 | 332 | unsafe { 333 | (*self.adapter).AddRef(); 334 | } 335 | 336 | Some(Some(Display { inner, adapter: self.adapter, desc })) 337 | } 338 | } 339 | 340 | impl Iterator for Displays { 341 | type Item = Display; 342 | fn next(&mut self) -> Option { 343 | if let Some(res) = self.read_and_invalidate() { 344 | res 345 | } else { 346 | // We need to replace the adapter. 347 | 348 | self.ndisplay = 0; 349 | self.nadapter += 1; 350 | 351 | self.adapter = unsafe { 352 | let mut adapter = ptr::null_mut(); 353 | (*self.factory).EnumAdapters1( 354 | self.nadapter, 355 | &mut adapter 356 | ); 357 | adapter 358 | }; 359 | 360 | if let Some(res) = self.read_and_invalidate() { 361 | res 362 | } else { 363 | // All subsequent adapters will also be empty. 364 | None 365 | } 366 | } 367 | } 368 | } 369 | 370 | impl Drop for Displays { 371 | fn drop(&mut self) { 372 | unsafe { 373 | (*self.factory).Release(); 374 | if !self.adapter.is_null() { 375 | (*self.adapter).Release(); 376 | } 377 | } 378 | } 379 | } 380 | 381 | pub struct Display { 382 | inner: *mut IDXGIOutput1, 383 | adapter: *mut IDXGIAdapter1, 384 | desc: DXGI_OUTPUT_DESC 385 | } 386 | 387 | impl Display { 388 | pub fn width(&self) -> LONG { 389 | self.desc.DesktopCoordinates.right - 390 | self.desc.DesktopCoordinates.left 391 | } 392 | 393 | pub fn height(&self) -> LONG { 394 | self.desc.DesktopCoordinates.bottom - 395 | self.desc.DesktopCoordinates.top 396 | } 397 | 398 | pub fn rotation(&self) -> DXGI_MODE_ROTATION { 399 | self.desc.Rotation 400 | } 401 | 402 | pub fn name(&self) -> &[u16] { 403 | let s = &self.desc.DeviceName; 404 | let i = s.iter() 405 | .position(|&x| x == 0) 406 | .unwrap_or(s.len()); 407 | &s[..i] 408 | } 409 | } 410 | 411 | impl Drop for Display { 412 | fn drop(&mut self) { 413 | unsafe { 414 | (*self.inner).Release(); 415 | (*self.adapter).Release(); 416 | } 417 | } 418 | } 419 | 420 | fn wrap_hresult(x: HRESULT) -> io::Result<()> { 421 | use std::io::ErrorKind::*; 422 | Err((match x { 423 | S_OK => return Ok(()), 424 | DXGI_ERROR_ACCESS_LOST => ConnectionReset, 425 | DXGI_ERROR_WAIT_TIMEOUT => TimedOut, 426 | DXGI_ERROR_INVALID_CALL => InvalidData, 427 | E_ACCESSDENIED => PermissionDenied, 428 | DXGI_ERROR_UNSUPPORTED => ConnectionRefused, 429 | DXGI_ERROR_NOT_CURRENTLY_AVAILABLE => Interrupted, 430 | DXGI_ERROR_SESSION_DISCONNECTED => ConnectionAborted, 431 | _ => Other 432 | }).into()) 433 | } 434 | --------------------------------------------------------------------------------