From a9511b94dee2db33a925a7fd4058743d2b5acbdb Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 10 Sep 2018 20:31:25 -0500 Subject: [PATCH] separate crate for safe bindings --- .dockerignore | 1 + .gitignore | 1 + Cargo.lock | 18 +++++++--- Cargo.toml | 6 ++-- src/capture.rs | 84 +++++---------------------------------------- src/errors.rs | 10 ++++-- src/gui.rs | 75 +++++++--------------------------------- src/image.rs | 47 ++++++++++--------------- src/lib.rs | 5 +-- src/main.rs | 2 +- xlib/Cargo.toml | 10 ++++++ xlib/src/display.rs | 47 +++++++++++++++++++++++++ xlib/src/error.rs | 13 +++++++ xlib/src/image.rs | 77 +++++++++++++++++++++++++++++++++++++++++ xlib/src/lib.rs | 20 +++++++++++ xlib/src/window.rs | 79 ++++++++++++++++++++++++++++++++++++++++++ 16 files changed, 317 insertions(+), 178 deletions(-) create mode 100644 xlib/Cargo.toml create mode 100644 xlib/src/display.rs create mode 100644 xlib/src/error.rs create mode 100644 xlib/src/image.rs create mode 100644 xlib/src/lib.rs create mode 100644 xlib/src/window.rs diff --git a/.dockerignore b/.dockerignore index 53eaa21..c1864d4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ /target **/*.rs.bk +shiet.png diff --git a/.gitignore b/.gitignore index 53eaa21..c1864d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target **/*.rs.bk +shiet.png diff --git a/Cargo.lock b/Cargo.lock index 0013c1b..a95aad0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,7 +141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pkg-config" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -199,11 +199,10 @@ name = "screenshot" version = "0.3.0" dependencies = [ "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", "png 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "x11 2.18.1 (registry+https://github.com/rust-lang/crates.io-index)", + "xlib 0.1.0", ] [[package]] @@ -340,7 +339,16 @@ version = "2.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "xlib" +version = "0.1.0" +dependencies = [ + "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "x11 2.18.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [metadata] @@ -362,7 +370,7 @@ dependencies = [ "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" "checksum num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "630de1ef5cc79d0cdd78b7e33b81f083cbfe90de0f4b2b2f07f905867c70e9fe" -"checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f" +"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" "checksum png 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f54b9600d584d3b8a739e1662a595fab051329eff43f20e7d8cc22872962145b" "checksum proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "ffe022fb8c8bd254524b0b3305906c1921fa37a84a644e29079a9e62200c3901" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" diff --git a/Cargo.toml b/Cargo.toml index e13db52..4be7efe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,15 @@ description = "Screenshot capture utility." version = "0.3.0" authors = ["Michael Zhang "] +[workspace] +members = [".", "xlib"] + [lib] crate-type = ["dylib", "rlib"] [dependencies] failure = "0.1" png = "0.12" -libc = "0.2" structopt = "0.2" time = "0.1" -x11 = { version = "2.18", features = ["xlib"] } +xlib = { path = "xlib" } diff --git a/src/capture.rs b/src/capture.rs index 8c7d6a7..1cebd69 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -1,18 +1,18 @@ use errors::ScreenshotError; use gui::GUI; -use image::Image; use options::{Options, Region}; +use xlib::Image; pub fn capture(opt: &Options) -> Result { let gui = GUI::new()?; let window_to_capture = match opt.region { - Region::Fullscreen | Region::Selection => gui.get_root_window(), - Region::ActiveWindow => gui.get_active_window(), + Region::Fullscreen | Region::Selection => gui.display.get_default_root_window()?, + Region::ActiveWindow => gui.get_active_window()?, }; - let mut capture = gui.window_capture(window_to_capture)?; + let capture = gui.capture_window(window_to_capture)?; println!("captured the window"); // let final_capture = match opt.region { @@ -20,78 +20,10 @@ pub fn capture(opt: &Options) -> Result { // Region::Selection => gui.interactive_select(&window_capture), // }; - if let Region::Selection = opt.region { - let region = gui.interactive_select(&capture)?; - capture.apply_region(®ion); - }; + // if let Region::Selection = opt.region { + // let region = gui.interactive_select(&capture)?; + // capture.apply_region(®ion); + // }; Ok(capture) - - // let display; - // let screen; - // match (Display::get_default(), Screen::get_default()) { - // (Some(d), Some(s)) => { - // display = d; - // screen = s; - // } - // _ => { - // bail!("Failed to open screen and display."); - // } - // } - - // display.sync(); - - // // first, choose the window - // let window_opt = match options.region { - // Region::Fullscreen | Region::Selection => screen.get_root_window(), - // Region::ActiveWindow => screen.get_active_window(), - // }; - // let window: GdkWindow; - // match window_opt { - // Some(window_) => window = window_, - // None => bail!("Failed to locate root window."), - // } - // window.process_updates(true); - - // // take a screenshot of it - // let width: i32 = window.get_width(); - // let height: i32 = window.get_height(); - // let pixbuf: Pixbuf = match window.get_pixbuf(0, 0, width, height) { - // Some(pixbuf) => Ok(pixbuf), - // None => Err(ScreenshotError::InvalidPixbuf), - // }?; - - // // launch selection - // let pixbuf = match options.region { - // Region::Selection => select_area(pixbuf)?, - // _ => pixbuf, - // }; - // let width = pixbuf.get_width(); - // let height = pixbuf.get_height(); - - // // create and draw to the surface - // let surface = match ImageSurface::create(Format::Rgb24, width, height) { - // Ok(surface) => Ok(surface), - // Err(_status) => Err(ScreenshotError::SurfaceCreateFail), - // }?; - // let ctx = Context::new(&surface); - // ctx.set_source_pixbuf(&pixbuf, 0.0, 0.0); - // ctx.paint(); - - // // write surface to file - - // let now = time::now(); - // let path = time::strftime(&options.outfile.as_os_str().to_str().unwrap(), &now)?; - // let mut file = File::create(path)?; - // surface.write_to_png(&mut file)?; - - // if options.clipboard { - // match Clipboard::get_default(&display) { - // Some(clipboard) => { - // clipboard.set_image(&pixbuf); - // clipboard.store(); - // } - // None => eprintln!("Failed to copy to the clipboard."), - // } - // } } diff --git a/src/errors.rs b/src/errors.rs index dc63dbd..71fc461 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,7 +1,7 @@ #[derive(Debug, Fail)] pub enum ScreenshotError { - #[fail(display = "x11 error: {}", message)] - XError { message: String }, + #[fail(display = "x11 error")] + X11Error(#[cause] ::xlib::X11Error), #[fail(display = "io error")] IOError(#[cause] ::std::io::Error), @@ -19,6 +19,12 @@ impl From<::std::io::Error> for ScreenshotError { } } +impl From<::xlib::X11Error> for ScreenshotError { + fn from(err: ::xlib::X11Error) -> Self { + ScreenshotError::X11Error(err) + } +} + impl From<::png::EncodingError> for ScreenshotError { fn from(err: ::png::EncodingError) -> Self { ScreenshotError::PngEncodingError(err) diff --git a/src/gui.rs b/src/gui.rs index e3229b7..5528ebf 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,85 +1,36 @@ -use std::ffi::CString; - -use libc; -use x11::xlib::*; +use xlib::{Display, Image, Window}; use errors::ScreenshotError; -use image::Image; use Rectangle; pub struct GUI { - display: *mut Display, + pub(crate) display: Display, } impl GUI { pub fn new() -> Result { - let display_str = CString::new(":0").unwrap(); - let display = unsafe { XOpenDisplay(display_str.as_ptr()) }; - if display.is_null() { - return Err(ScreenshotError::XError { - message: format!("failed to open display"), - }); - } - // unsafe { XGrabServer(display) }; + let display = Display::connect(":0")?; Ok(GUI { display }) } - fn get_window_attributes( - &self, - window: Window, - ) -> Result<*mut XWindowAttributes, ScreenshotError> { - let attr_size = ::std::mem::size_of::(); - let attr = unsafe { libc::malloc(attr_size) as *mut XWindowAttributes }; - let result = unsafe { XGetWindowAttributes(self.display, window, attr) }; - if result == 0 { - return Err(ScreenshotError::XError { - message: format!("failed to get window attributes"), - }); - } - Ok(attr) - } - /// Captures the window and produces a DynamicImage. - pub fn window_capture(&self, window: Window) -> Result { - let attr = self.get_window_attributes(window)?; - println!("got window attributes"); - let image = unsafe { - XGetImage( - self.display, - window, - (*attr).x, - (*attr).y, - (*attr).width as u32, - (*attr).height as u32, - 0xffffffff, - ZPixmap, - ) - }; - Ok(Image::from(self.display, image)) - } - - /// Get the full screen. - pub fn get_root_window(&self) -> Window { - unsafe { XRootWindow(self.display, 0) as Window } + pub fn capture_window(&self, window: Window) -> Result { + window.get_image().map_err(|err| err.into()) } /// Get the active window. - pub fn get_active_window(&self) -> Window { - let mut window: Window = self.get_root_window(); - let mut revert_to_return: i32 = RevertToParent; - unsafe { XGetInputFocus(self.display, &mut window, &mut revert_to_return) }; - unsafe { XMapRaised(self.display, window) }; - window + pub fn get_active_window(&self) -> Result { + Ok(self.display.get_default_root_window()?) + // let mut window: Window = self.display.get_default_root_window(); + // let mut revert_to_return: i32 = RevertToParent; + // unsafe { XGetInputFocus(self.display, &mut window, &mut revert_to_return) }; + // unsafe { XMapRaised(self.display, window) }; + // window } /// Brings up an interactive selection GUI. + #[allow(dead_code)] pub fn interactive_select(&self, _capture: &Image) -> Result { Err(ScreenshotError::Error) } } - -impl Drop for GUI { - fn drop(&mut self) { - // unsafe { XUngrabServer(self.display) }; - } -} diff --git a/src/image.rs b/src/image.rs index ae23142..0adae6c 100644 --- a/src/image.rs +++ b/src/image.rs @@ -3,39 +3,31 @@ use std::io::BufWriter; use std::path::Path; use png::{self, Encoder, HasParameters}; -use x11::xlib::*; +use xlib::{Image, ImageByteOrder}; use errors::ScreenshotError; -use Rectangle; -#[allow(dead_code)] -pub struct Image { - display: *mut Display, - inner: *mut XImage, +pub trait ImageExt { + fn to_data_buf(&self) -> Result, ScreenshotError>; + fn write_png(&self, out: impl AsRef) -> Result<(), ScreenshotError>; } -impl Image { - pub fn from(display: *mut Display, inner: *mut XImage) -> Self { - Image { inner, display } - } - - pub fn apply_region(&mut self, _region: &Rectangle) {} - +impl ImageExt for Image { /// Converts the image buffer into RGB(A). - fn to_data_buf(&self) -> Vec { - let im = unsafe { *self.inner }; - let size = 4usize * im.width as usize * im.height as usize; + fn to_data_buf(&self) -> Result, ScreenshotError> { + let size = self.get_size(); let mut buf = vec![1; size]; - let sbuf = unsafe { ::std::slice::from_raw_parts(im.data, size) }; // source buffer + let sbuf = self.buffer(); // source buffer let mut sx = 0usize; // source idx let mut dx = 0usize; // dest idx - if im.byte_order == LSBFirst { + if let ImageByteOrder::LSBFirst = self.get_byte_order()? { + // LSBFirst while dx < size { - buf[dx] = sbuf[sx + 2] as u8; - buf[dx + 1] = sbuf[sx + 1] as u8; - buf[dx + 2] = sbuf[sx] as u8; - buf[dx + 3] = if im.depth == 32 { - sbuf[sx + 3] as u8 + buf[dx] = sbuf.get_byte(sx + 2).unwrap() as u8; + buf[dx + 1] = sbuf.get_byte(sx + 1).unwrap() as u8; + buf[dx + 2] = sbuf.get_byte(sx).unwrap() as u8; + buf[dx + 3] = if self.get_depth() == 32 { + sbuf.get_byte(sx + 3).unwrap() as u8 } else { 255u8 }; @@ -43,19 +35,18 @@ impl Image { dx += 4; } } - buf + Ok(buf) } - pub fn write_png(&self, out: impl AsRef) -> Result<(), ScreenshotError> { + fn write_png(&self, out: impl AsRef) -> Result<(), ScreenshotError> { let file = File::create(out.as_ref())?; let ref mut out = BufWriter::new(file); - let (width, height) = unsafe { ((*self.inner).width as u32, (*self.inner).height as u32) }; - let mut encoder = Encoder::new(out, width, height); + let mut encoder = Encoder::new(out, self.get_width(), self.get_height()); encoder.set(png::ColorType::RGBA).set(png::BitDepth::Eight); let mut writer = encoder.write_header()?; - let data = self.to_data_buf(); + let data = self.to_data_buf()?; writer.write_image_data(data.as_slice())?; Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index dace8a4..29cae40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,10 @@ #[macro_use] extern crate failure; -extern crate libc; extern crate png; #[macro_use] extern crate structopt; extern crate time; -extern crate x11; +extern crate xlib; mod capture; mod errors; @@ -16,8 +15,10 @@ mod options; use structopt::StructOpt; pub use capture::capture; +pub use image::ImageExt; pub use options::Options; +#[derive(Debug)] pub struct Rectangle { pub x: u32, pub y: u32, diff --git a/src/main.rs b/src/main.rs index 09a8ff2..7d88836 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ extern crate screenshot; extern crate structopt; use failure::Error; -use screenshot::{capture, Options}; +use screenshot::{capture, ImageExt, Options}; use structopt::StructOpt; pub fn main() -> Result<(), Error> { diff --git a/xlib/Cargo.toml b/xlib/Cargo.toml new file mode 100644 index 0000000..136af32 --- /dev/null +++ b/xlib/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "xlib" +description = "Safe-ish bindings to xlib (or at least the parts that I need)" +version = "0.1.0" +authors = ["Michael Zhang "] + +[dependencies] +failure = "0.1" +libc = "0.2" +x11 = { version = "2.18", features = ["xlib"] } diff --git a/xlib/src/display.rs b/xlib/src/display.rs new file mode 100644 index 0000000..99f8fdb --- /dev/null +++ b/xlib/src/display.rs @@ -0,0 +1,47 @@ +use std::ffi::CString; + +use x11::xlib as x; + +use Window; +use X11Error; + +/// A connection to an X server. +pub struct Display { + inner: *mut x::Display, +} + +impl Display { + /// Opens a new connection to an X server. + /// + /// On POSIX-conformant systems, the display name or DISPLAY environment variable can be a string in the format: + /// hostname:number.screen_number + pub fn connect(display_name: impl AsRef) -> Result { + let display_name = CString::new(display_name.as_ref()).unwrap(); + let inner = unsafe { x::XOpenDisplay(display_name.as_ptr()) }; + Ok(Display { inner }) + } + + /// Returns the root window for the given screen. + pub fn get_root_window(&self, screen: i32) -> Result { + let window = Window { + display: self.inner, + inner: unsafe { x::XRootWindow(self.inner, screen) }, + }; + Ok(window) + } + + /// Returns the root window for the default screen. + pub fn get_default_root_window(&self) -> Result { + let window = Window { + display: self.inner, + inner: unsafe { x::XDefaultRootWindow(self.inner) }, + }; + Ok(window) + } +} + +impl Drop for Display { + fn drop(&mut self) { + unsafe { x::XCloseDisplay(self.inner) }; + } +} diff --git a/xlib/src/error.rs b/xlib/src/error.rs new file mode 100644 index 0000000..a38884b --- /dev/null +++ b/xlib/src/error.rs @@ -0,0 +1,13 @@ +/// Any error that can be raised when using this library. +#[allow(missing_docs)] +#[derive(Debug, Fail)] +pub enum X11Error { + #[fail(display = "failed to get attributes")] + GetAttributesError, + + #[fail(display = "invalid byte order")] + InvalidByteOrder, + + #[fail(display = "error")] + Error, +} diff --git a/xlib/src/image.rs b/xlib/src/image.rs new file mode 100644 index 0000000..ee34794 --- /dev/null +++ b/xlib/src/image.rs @@ -0,0 +1,77 @@ +use x11::xlib as x; + +use X11Error; + +/// A handle to an XImage. +pub struct Image { + pub(super) inner: *mut x::XImage, +} + +/// Image byte order +pub enum ImageByteOrder { + /// Least significant byte first + LSBFirst, + /// Most significant byte first + MSBFirst, +} + +/// The buffer pointed to by an XImage. +pub struct PixBuffer { + pub(self) buf: *mut u8, + pub(self) size: usize, +} + +impl Image { + /// Get the size (in bytes) of the data buffer. + pub fn get_size(&self) -> usize { + 4 * self.get_width() as usize * self.get_height() as usize + } + + /// Get the image width + pub fn get_width(&self) -> u32 { + unsafe { (*self.inner).width as u32 } + } + + /// Get the image height + pub fn get_height(&self) -> u32 { + unsafe { (*self.inner).height as u32 } + } + + /// Get the image depth + pub fn get_depth(&self) -> u32 { + unsafe { (*self.inner).depth as u32 } + } + + /// Get byte order + pub fn get_byte_order(&self) -> Result { + let byte_order = unsafe { (*self.inner).byte_order }; + match byte_order { + x::LSBFirst => Ok(ImageByteOrder::LSBFirst), + x::MSBFirst => Ok(ImageByteOrder::MSBFirst), + _ => Err(X11Error::InvalidByteOrder), + } + } + + /// Produces a PixBuffer + pub fn buffer(&self) -> PixBuffer { + let size = self.get_size(); + let buf = unsafe { (*self.inner).data as *mut u8 }; + PixBuffer { buf, size } + } +} + +impl Drop for Image { + fn drop(&mut self) { + unsafe { x::XDestroyImage(self.inner) }; + } +} + +impl PixBuffer { + /// Gets the byte at the index of the data buffer. + pub fn get_byte(&self, index: usize) -> Option { + if index > self.size { + return None; + } + Some(unsafe { *self.buf.offset(index as isize) as u8 }) + } +} diff --git a/xlib/src/lib.rs b/xlib/src/lib.rs new file mode 100644 index 0000000..37b38fb --- /dev/null +++ b/xlib/src/lib.rs @@ -0,0 +1,20 @@ +//! Safe-ish bindings to parts of x11's xlib module. +//! +//! I need this for my project. + +#![deny(missing_docs)] + +#[macro_use] +extern crate failure; +extern crate libc; +extern crate x11; + +mod display; +mod error; +mod image; +mod window; + +pub use display::Display; +pub use error::X11Error; +pub use image::{Image, ImageByteOrder, PixBuffer}; +pub use window::{Window, WindowAttributes}; diff --git a/xlib/src/window.rs b/xlib/src/window.rs new file mode 100644 index 0000000..7e9c17e --- /dev/null +++ b/xlib/src/window.rs @@ -0,0 +1,79 @@ +use std::mem; + +use libc; +use x11::xlib as x; + +use Image; +use X11Error; + +/// A wrapper around a window handle. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Window { + pub(super) display: *mut x::Display, + pub(super) inner: x::Window, +} + +/// Window Attributes +pub struct WindowAttributes { + pub(self) inner: *mut x::XWindowAttributes, +} + +impl Window { + /// Get window attributes. + pub fn get_attributes(&self) -> Result { + let attr = unsafe { + libc::malloc(mem::size_of::()) as *mut x::XWindowAttributes + }; + let result = unsafe { x::XGetWindowAttributes(self.display, self.inner, attr) }; + match result { + 0 => Err(X11Error::GetAttributesError), + _ => Ok(WindowAttributes { inner: attr }), + } + } + + /// Capture a snapshot of this window. + pub fn get_image(&self) -> Result { + let attr = self.get_attributes()?; + let image = unsafe { + x::XGetImage( + self.display, + self.inner, + attr.get_x(), + attr.get_y(), + attr.get_width(), + attr.get_height(), + 0xffffffff, + x::ZPixmap, + ) + }; + Ok(Image { inner: image }) + } +} + +impl WindowAttributes { + /// Gets the width of the window + pub fn get_x(&self) -> i32 { + unsafe { (*self.inner).x as i32 } + } + + /// Gets the height of the window + pub fn get_y(&self) -> i32 { + unsafe { (*self.inner).y as i32 } + } + + /// Gets the width of the window + pub fn get_width(&self) -> u32 { + unsafe { (*self.inner).width as u32 } + } + + /// Gets the height of the window + pub fn get_height(&self) -> u32 { + unsafe { (*self.inner).height as u32 } + } +} + +impl Drop for WindowAttributes { + fn drop(&mut self) { + unsafe { libc::free(self.inner as *mut libc::c_void) }; + } +}