commit 4966ee84323da1bffdc55e4a613e70533acd5bb2 Author: Michael Zhang Date: Sun Jun 28 04:43:22 2020 -0500 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a144c4a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "leanshot_x11" +version = "0.2.0" +description = "x11 bindings" +license-file = "../LICENSE" +authors = ["Michael Zhang "] + +[features] +default = [] +xlib = ["x11/xlib"] +xrender = ["x11/xrender"] +xinerama = ["x11/xlib", "x11/xinerama"] + +[dependencies] +libc = "0.2" +x11 = { version = "2.18" } +thiserror = "1.0.20" +log = "0.4.8" diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..db4f989 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,30 @@ +pub type Result = std::result::Result; + +/// Any error that can be raised when using this library. +#[allow(missing_docs)] +#[derive(Debug, Error)] +pub enum Error { + #[error("invalid image byte order: {0}")] + InvalidByteOrder(i32), + + #[error("invalid event type: {0}")] + InvalidEventType(i32), + + #[error("failed to get attributes")] + GetAttributesError, + + #[error("failed to create cursor")] + CreateCursorError, + + #[error("failed to open display")] + DisplayOpenError, + + #[error("failed to get window")] + GetWindowError, + + #[error("failed to translate coordinates")] + TranslateCoordinatesError, + + #[error("error")] + Error, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2227a9f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,23 @@ +//! Higher level bindings to x11. + +#[macro_use] +extern crate thiserror; +#[macro_use] +extern crate log; + +pub extern crate x11; + +#[cfg(feature = "xlib")] +pub mod xlib; + +#[cfg(feature = "xrender")] +pub mod xrender; + +#[cfg(feature = "xinerama")] +pub mod xinerama; + +mod errors; +mod rect; + +pub use crate::errors::{Error, Result}; +pub use crate::rect::Rectangle; diff --git a/src/rect.rs b/src/rect.rs new file mode 100644 index 0000000..1dd4023 --- /dev/null +++ b/src/rect.rs @@ -0,0 +1,24 @@ +/// A rectangle. +#[derive(Debug)] +pub struct Rectangle { + /// x + pub x: i32, + /// y + pub y: i32, + /// width + pub width: u32, + /// height + pub height: u32, +} + +impl Rectangle { + /// Create a new Rectangle from u32s + pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self { + Rectangle { + x, + y, + width, + height, + } + } +} diff --git a/src/xinerama/mod.rs b/src/xinerama/mod.rs new file mode 100644 index 0000000..8e3b882 --- /dev/null +++ b/src/xinerama/mod.rs @@ -0,0 +1,3 @@ +mod screen_info; + +pub use self::screen_info::ScreensInfo; diff --git a/src/xinerama/screen_info.rs b/src/xinerama/screen_info.rs new file mode 100644 index 0000000..a5909ca --- /dev/null +++ b/src/xinerama/screen_info.rs @@ -0,0 +1,64 @@ +use x11::xinerama; + +use crate::errors::Result; +use crate::xlib::Display; + +pub struct ScreensInfo { + inner: *mut xinerama::XineramaScreenInfo, + heads: isize, +} + +impl ScreensInfo { + pub fn query(display: &Display) -> Result { + let mut heads = 0; + let inner = unsafe { xinerama::XineramaQueryScreens(display.inner, &mut heads as *mut _) }; + // TODO: check for error + Ok(ScreensInfo { + inner, + heads: heads as isize, + }) + } + + pub fn iter(&self) -> ScreenInfoIter { + ScreenInfoIter(self, 0) + } +} + +impl Drop for ScreensInfo { + fn drop(&mut self) { + unsafe { x11::xlib::XFree(self.inner as *mut _) }; + } +} + +pub struct ScreenInfoIter<'a>(&'a ScreensInfo, isize); + +pub struct ScreenInfo { + pub x: i16, + pub y: i16, + pub width: i16, + pub height: i16, +} + +impl<'a> Iterator for ScreenInfoIter<'a> { + type Item = (i32, ScreenInfo); + fn next(&mut self) -> Option { + if self.1 >= self.0.heads { + return None; + } + + let screen = unsafe { self.0.inner.offset(self.1) }; + self.1 += 1; + + unsafe { + Some(( + (*screen).screen_number as i32, + ScreenInfo { + x: (*screen).x_org as i16, + y: (*screen).y_org as i16, + width: (*screen).width as i16, + height: (*screen).height as i16, + }, + )) + } + } +} diff --git a/src/xlib/atom.rs b/src/xlib/atom.rs new file mode 100644 index 0000000..469ab5b --- /dev/null +++ b/src/xlib/atom.rs @@ -0,0 +1,36 @@ +use std::ffi::CString; + +use x11::xlib; + +use crate::errors::Result; + +use super::display::Display; + +/// A unique string or intger +pub struct Atom { + inner: xlib::Atom, +} + +impl Atom { + /// Create a new atom using a string + pub fn new(display: &Display, val: impl AsRef, only_if_exists: bool) -> Result { + let val = { + let v = val.as_ref(); + let s = CString::new(v).unwrap(); + s.as_ptr() + }; + let inner = + unsafe { xlib::XInternAtom(display.as_raw(), val, if only_if_exists { 1 } else { 0 }) }; + Ok(Atom { inner }) + } + + /// Create a new Atom object from an existing handle + pub fn from(handle: xlib::Atom) -> Self { + Atom { inner: handle } + } + + /// Get the handle + pub fn as_raw(&self) -> xlib::Atom { + self.inner + } +} diff --git a/src/xlib/cursor.rs b/src/xlib/cursor.rs new file mode 100644 index 0000000..2133c20 --- /dev/null +++ b/src/xlib/cursor.rs @@ -0,0 +1,13 @@ +use x11::xlib; + +/// Mouse pointer +pub struct Cursor { + pub(crate) display: *mut xlib::Display, + pub(crate) inner: xlib::Cursor, +} + +impl Drop for Cursor { + fn drop(&mut self) { + unsafe { xlib::XFreeCursor(self.display, self.inner) }; + } +} diff --git a/src/xlib/display.rs b/src/xlib/display.rs new file mode 100644 index 0000000..bf0ec09 --- /dev/null +++ b/src/xlib/display.rs @@ -0,0 +1,181 @@ +use std::ffi::CString; +use std::ptr; + +use x11::xlib; + +use crate::errors::{Error, Result}; + +use super::cursor::Cursor; +use super::event::Event; +use super::visual::Visual; +use super::window::Window; + +/// A connection to an X server. +pub struct Display { + pub(crate) inner: *mut xlib::Display, +} + +pub struct Grab(pub(crate) *mut xlib::Display); + +/// Something that's part of a display. +pub trait GetDisplay { + /// Get the current display + fn get_display(&self) -> *mut xlib::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 { xlib::XOpenDisplay(display_name.as_ptr()) }; + if inner.is_null() { + return Err(Error::DisplayOpenError); + } + Ok(Display { inner }) + } + + /// Create a Display for an existing connection + pub fn from_handle(handle: u64) -> Self { + Display { + inner: handle as *mut xlib::Display, + } + } + + /// Grab + pub fn grab(&self) -> Grab { + unsafe { xlib::XGrabServer(self.inner) }; + Grab(self.inner) + } + + /// Wrapper around XCreateFontCursor + pub fn create_font_cursor(&self, shape: u32) -> Result { + let cursor = unsafe { xlib::XCreateFontCursor(self.inner, shape) as xlib::Cursor }; + if cursor == 0 { + return Err(Error::CreateCursorError); + } + Ok(Cursor { + display: self.inner, + inner: cursor, + }) + } + + /// Get the next event, blocks until an event is reached. + pub fn next_event(&self) -> Result { + let event = ptr::null_mut(); + unsafe { xlib::XNextEvent(self.inner, event) }; + // TODO: check to make sure this isn't null + unsafe { Event::from_raw(event) } + } + + /// Returns the number of events that are still pending + pub fn pending(&self) -> Result { + Ok(unsafe { xlib::XPending(self.inner) }) + } + + /// Gets the raw X Display handle + pub fn as_raw(&self) -> *mut xlib::Display { + self.inner + } + + /// Gets the default visual + pub fn default_visual(&self, screen: i32) -> Visual { + let visual = unsafe { xlib::XDefaultVisual(self.inner, screen) }; + Visual { inner: visual } + } + + /// Returns the root window for the given screen. + pub fn get_root_window(&self, screen: i32) -> Result { + let inner = unsafe { xlib::XRootWindow(self.inner, screen) }; + if inner == 0 { + return Err(Error::GetWindowError); + } + let window = Window { + display: self.inner, + inner, + }; + Ok(window) + } + + /// Returns the root window for the default screen. + pub fn get_default_root_window(&self) -> Result { + let inner = unsafe { xlib::XDefaultRootWindow(self.inner) }; + if inner == 0 { + return Err(Error::GetWindowError); + } + let window = Window { + display: self.inner, + inner, + }; + Ok(window) + } + + /// Translate coordinates relative to w1 to coordinates relative to w2. + /// If the coordinates are contained in a mapped child of the destination window, the third return + /// value will hold that child window. + pub fn translate_coordinates( + &self, + w1: Window, + x: i32, + y: i32, + w2: Window, + ) -> Result<(i32, i32, Option)> { + let mut rx = 0; + let mut ry = 0; + let mut child_return: xlib::Window = 0; + let status = unsafe { + xlib::XTranslateCoordinates( + self.inner, + w1.inner, + w2.inner, + x, + y, + &mut rx, + &mut ry, + &mut child_return, + ) + }; + if status == 0 { + return Err(Error::TranslateCoordinatesError); + } + let child = match child_return { + 0 => None, + val => Some(Window { + display: self.inner, + inner: val, + }), + }; + Ok((rx, ry, child)) + } + + /// Sync + pub fn sync(&self, discard: bool) { + unsafe { xlib::XSync(self.inner, if discard { 1 } else { 0 }) }; + } + + /// Returns the focus window and the current focus state. + pub fn get_input_focus(&self) -> Result<(Window, i32)> { + let mut focus_return: xlib::Window = 0; + let mut revert_to_return = 0; + unsafe { xlib::XGetInputFocus(self.inner, &mut focus_return, &mut revert_to_return) }; + let window = Window { + display: self.inner, + inner: focus_return, + }; + return Ok((window, revert_to_return)); + } +} + +impl Drop for Display { + fn drop(&mut self) { + unsafe { xlib::XCloseDisplay(self.inner) }; + } +} + +impl Drop for Grab { + fn drop(&mut self) { + unsafe { xlib::XUngrabServer(self.0) }; + } +} diff --git a/src/xlib/drawable.rs b/src/xlib/drawable.rs new file mode 100644 index 0000000..60be2cb --- /dev/null +++ b/src/xlib/drawable.rs @@ -0,0 +1,30 @@ +use x11::xlib; + +use crate::errors::Result; +use crate::rect::Rectangle; + +use super::display::GetDisplay; +use super::image::Image; + +/// Anything that's drawable +pub trait Drawable: GetDisplay { + /// Get drawable handle + fn as_drawable(&self) -> xlib::Drawable; + + /// Capture a snapshot of this drawable, clipped by rect. + fn get_image(&self, rect: Rectangle) -> Result { + let image = unsafe { + xlib::XGetImage( + self.get_display(), + self.as_drawable(), + rect.x as i32, + rect.y as i32, + rect.width, + rect.height, + 0xffffffff, + xlib::ZPixmap, + ) + }; + Ok(Image { inner: image }) + } +} diff --git a/src/xlib/event.rs b/src/xlib/event.rs new file mode 100644 index 0000000..8bc6f45 --- /dev/null +++ b/src/xlib/event.rs @@ -0,0 +1,45 @@ +use x11::xlib; + +use crate::errors::{Error, Result}; + +/// An x11 Event +#[derive(Debug)] +pub struct Event { + inner: *mut xlib::XEvent, + kind: EventKind, +} + +/// Type of event +#[derive(Debug)] +pub enum EventKind { + KeyPress(KeyEvent), + KeyRelease(KeyEvent), +} + +impl Event { + /// Returns the EventKind of this event + pub fn kind(&self) -> &EventKind { + &self.kind + } + + pub(super) unsafe fn from_raw(event: *mut xlib::XEvent) -> Result { + let inner = event; + let event = unsafe { *event }; + debug!("event type: {:?}", event.type_); + let kind = match event.type_ { + xlib::KeyPress => EventKind::KeyPress(KeyEvent(event.key)), + xlib::KeyRelease => EventKind::KeyRelease(KeyEvent(event.key)), + other => return Err(Error::InvalidEventType(other)), + }; + Ok(Event { inner, kind }) + } +} + +impl Drop for Event { + fn drop(&mut self) { + unsafe { libc::free(self.inner as *mut libc::c_void) }; + } +} + +#[derive(Debug)] +pub struct KeyEvent(xlib::XKeyEvent); diff --git a/src/xlib/image.rs b/src/xlib/image.rs new file mode 100644 index 0000000..227e902 --- /dev/null +++ b/src/xlib/image.rs @@ -0,0 +1,122 @@ +use std::os::raw::c_ulong; + +use x11::xlib; + +use crate::errors::{Error, Result}; + +/// A handle to an XImage. +pub struct Image { + pub(super) inner: *mut xlib::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<'a> { + /// The raw pointer to the buffer + pub buf: *mut u8, + + /// The size of the buffer + pub size: usize, + + /// Rgb masks + pub masks: (u64, u64, u64), + + pub image: &'a Image, +} + +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 { + xlib::LSBFirst => Ok(ImageByteOrder::LSBFirst), + xlib::MSBFirst => Ok(ImageByteOrder::MSBFirst), + order => Err(Error::InvalidByteOrder(order)), + } + } + + fn get_rgb_masks(&self) -> (u64, u64, u64) { + let red_mask = unsafe { (*self.inner).red_mask }; + let green_mask = unsafe { (*self.inner).green_mask }; + let blue_mask = unsafe { (*self.inner).blue_mask }; + (red_mask, blue_mask, green_mask) + } + + /// Produces a PixBuffer + pub fn buffer(&self) -> PixBuffer { + let size = self.get_size(); + let buf = unsafe { (*self.inner).data as *mut u8 }; + let masks = self.get_rgb_masks(); + PixBuffer { + buf, + size, + masks, + image: self, + } + } +} + +impl Drop for Image { + fn drop(&mut self) { + unsafe { xlib::XDestroyImage(self.inner) }; + } +} + +impl<'a> PixBuffer<'a> { + /// 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 }) + } + + /// Gets the rgb value of a particular pixel + pub fn get_pixel(&self, x: u32, y: u32) -> Option<(u8, u8, u8)> { + // let bytes_per_line = unsafe{(*self.image.inner).bytes_per_line} as u32; + // let bits_per_pixel=unsafe{(*self.image.inner).bits_per_pixel} as u32; + // let pixel_ptr = (y * bytes_per_line + x * bits_per_pixel) / 8; + // let pixel = unsafe{*((*self.image.inner).data.offset(pixel_ptr as isize) as *mut c_ulong)}; + + let get_pixel = unsafe { (*self.image.inner).funcs.get_pixel }.unwrap(); + let pixel = unsafe { get_pixel(self.image.inner, x as i32, y as i32) }; + + // warn!("pixel: {:?} {:#b}", pixel, pixel); + let red_mask = unsafe { (*self.image.inner).red_mask }; + let green_mask = unsafe { (*self.image.inner).green_mask }; + let blue_mask = unsafe { (*self.image.inner).blue_mask }; + // warn!("masks: {:b} {:b} {:b}", red_mask, green_mask, blue_mask); + let red = ((pixel & unsafe { (*self.image.inner).red_mask }) >> 16) as u8; + let green = ((pixel & unsafe { (*self.image.inner).green_mask }) >> 8) as u8; + let blue = ((pixel & unsafe { (*self.image.inner).blue_mask }) >> 0) as u8; + // warn!("rgb: {:?}", (red, green, blue)); + Some((red, green, blue)) + } +} diff --git a/src/xlib/mod.rs b/src/xlib/mod.rs new file mode 100644 index 0000000..e7d6f24 --- /dev/null +++ b/src/xlib/mod.rs @@ -0,0 +1,23 @@ +mod atom; +mod cursor; +mod display; +mod drawable; +mod event; +mod image; +mod visual; +mod window; + +pub use self::atom::Atom; +pub use self::cursor::Cursor; +pub use self::display::{Display, GetDisplay}; +pub use self::drawable::Drawable; +pub use self::event::{Event, EventKind}; +pub use self::image::{Image, ImageByteOrder}; +pub use self::visual::Visual; +pub use self::window::Window; + +pub const XC_crosshair: u32 = 34; +pub const XC_ll_angle: u32 = 76; +pub const XC_lr_angle: u32 = 78; +pub const XC_ul_angle: u32 = 144; +pub const XC_ur_angle: u32 = 148; diff --git a/src/xlib/visual.rs b/src/xlib/visual.rs new file mode 100644 index 0000000..5bcf83d --- /dev/null +++ b/src/xlib/visual.rs @@ -0,0 +1,21 @@ +use x11::xlib; + +use super::display::Display; + +/// A wrapper around a Visual +pub struct Visual { + pub(super) inner: *mut xlib::Visual, +} + +impl Visual { + /// Gets the raw handle to the x11 Visual + pub fn as_raw(&self) -> *mut xlib::Visual { + self.inner + } + + /// Gets the default visual + pub fn default(display: &Display, screen: i32) -> Self { + let inner = unsafe { xlib::XDefaultVisual(display.as_raw(), screen) }; + Visual { inner } + } +} diff --git a/src/xlib/window.rs b/src/xlib/window.rs new file mode 100644 index 0000000..80565b0 --- /dev/null +++ b/src/xlib/window.rs @@ -0,0 +1,185 @@ +use std::mem; + +use x11::xlib; + +use crate::errors::{Error, Result}; +use crate::rect::Rectangle; + +use super::atom::Atom; +use super::display::{Display, GetDisplay}; +use super::drawable::Drawable; +use super::image::Image; + +/// A wrapper around a window handle. +#[derive(Clone, PartialEq, Eq)] +pub struct Window { + pub(super) display: *mut xlib::Display, + pub(super) inner: xlib::Window, +} + +impl Window { + /// Create a new window + pub fn create( + display: &Display, + parent: Option, + location: Rectangle, + ) -> Result { + let parent = match parent { + Some(parent) => parent, + None => display.get_default_root_window()?, + }; + let visual = display.default_visual(0); + let window = unsafe { + xlib::XCreateWindow( + display.as_raw(), + parent.as_raw(), + location.x as i32, + location.y as i32, + location.width, + location.height, + 0, + 0, + 0, + visual.as_raw(), + 0, + 0 as *mut xlib::XSetWindowAttributes, + ) + }; + Ok(Window { + display: display.as_raw(), + inner: window, + }) + } + + /// Create a new Window instance from an existing ID + pub fn create_from_handle(display: &Display, id: u64) -> Result { + Ok(Window { + display: display.as_raw(), + inner: id, + }) + } + + /// Get window attributes. + pub fn get_attributes(&self) -> Result { + let attr = unsafe { + libc::malloc(mem::size_of::()) as *mut xlib::XWindowAttributes + }; + let result = unsafe { xlib::XGetWindowAttributes(self.display, self.inner, attr) }; + match result { + 0 => Err(Error::GetAttributesError), + _ => Ok(WindowAttributes { + display: self.display, + inner: attr, + }), + } + } + + /// Get the raw window handle + pub fn as_raw(&self) -> xlib::Window { + self.inner + } + + /// Gets the image from this window using XGetImage. + pub fn get_image(&self) -> Result { + let attr = self.get_attributes()?; + Drawable::get_image( + self, + Rectangle::new( + attr.get_x(), + attr.get_y(), + attr.get_width(), + attr.get_height(), + ), + ) + } + + /// Change window property + // TODO: make it more general + pub fn change_property(&self, key: &Atom, val: &Atom) { + use std::mem::transmute; + let v = val.as_raw(); + unsafe { + xlib::XChangeProperty( + self.display, + self.inner, + key.as_raw(), + xlib::XA_ATOM, + 32, + xlib::PropModeReplace, + transmute(&v), + 1, + ); + } + } + + /// Map to display + pub fn map(&self) { + unsafe { xlib::XMapWindow(self.display, self.inner) }; + } +} + +impl GetDisplay for Window { + fn get_display(&self) -> *mut xlib::Display { + self.display + } +} + +impl Drawable for Window { + fn as_drawable(&self) -> xlib::Drawable { + self.inner + } +} + +impl Drop for Window { + fn drop(&mut self) { + unsafe { xlib::XDestroyWindow(self.display, self.inner) }; + } +} + +/// Window Attributes +pub struct WindowAttributes { + pub(super) display: *mut xlib::Display, + pub(self) inner: *mut xlib::XWindowAttributes, +} + +// impl AsRef for Window { +// fn as_ref(&self) -> &Drawable { +// &self.inner +// } +// } + +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 } + } + + /// Get the root window of this window + pub fn get_root(&self) -> Window { + Window { + display: self.display, + inner: unsafe { (*self.inner).root }, + } + } +} + +impl Drop for WindowAttributes { + fn drop(&mut self) { + unsafe { libc::free(self.inner as *mut libc::c_void) }; + } +} diff --git a/src/xrender/mod.rs b/src/xrender/mod.rs new file mode 100644 index 0000000..d9a70d2 --- /dev/null +++ b/src/xrender/mod.rs @@ -0,0 +1 @@ +mod picture; diff --git a/src/xrender/picture.rs b/src/xrender/picture.rs new file mode 100644 index 0000000..82c0c32 --- /dev/null +++ b/src/xrender/picture.rs @@ -0,0 +1,7 @@ +pub struct Picture {} + +impl Picture {} + +impl Drop for Picture { + fn drop(&self) {} +}