diff --git a/Cargo.lock b/Cargo.lock index 50e1402..82c33a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,17 @@ dependencies = [ "byteorder", ] +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + [[package]] name = "heck" version = "0.3.3" @@ -157,6 +168,12 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + [[package]] name = "lazy_static" version = "1.4.0" @@ -169,6 +186,7 @@ version = "0.5.0" dependencies = [ "anyhow", "image", + "leanshot-x11", "libc", "log", "stderrlog", @@ -177,6 +195,18 @@ dependencies = [ "xcb-util", ] +[[package]] +name = "leanshot-x11" +version = "0.0.5" +dependencies = [ + "bitflags", + "gl_generator", + "libc", + "log", + "thiserror", + "x11", +] + [[package]] name = "libc" version = "0.2.98" @@ -242,6 +272,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + [[package]] name = "png" version = "0.16.8" @@ -368,6 +404,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -455,6 +511,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "x11" +version = "2.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ecd092546cb16f25783a5451538e73afc8d32e242648d54f4ae5459ba1e773" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "xcb" version = "0.9.0" @@ -474,3 +540,9 @@ dependencies = [ "libc", "xcb", ] + +[[package]] +name = "xml-rs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" diff --git a/Cargo.toml b/Cargo.toml index c6e53f7..981a256 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,13 @@ license = "MIT" edition = "2018" authors = ["Michael Zhang "] +[workspace] +members = ["x11"] + [dependencies] anyhow = "1.0.38" image = { version = "0.23.13", default-features = false, features = ["jpeg", "png"] } +leanshot-x11 = { path = "x11" } log = "0.4.14" stderrlog = "0.5.1" xcb-util = { version = "0.3.0", features = ["image", "cursor"] } diff --git a/LICENSE b/LICENSE index 822f894..4e73e61 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2018-2020 Michael Zhang +Copyright 2018-2021 Michael Zhang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/src/gui_trait.rs b/src/gui_trait.rs new file mode 100644 index 0000000..ef407fd --- /dev/null +++ b/src/gui_trait.rs @@ -0,0 +1,16 @@ +use std::path::Path; + +use anyhow::Result; + +use crate::rect::Rectangle; + +pub trait Gui { + type Capture: Capture; + fn capture_entire_screen(&self) -> Result; + fn interactive_select(&self, capture: &Self::Capture) -> Result>; +} + +pub trait Capture { + fn save_to(&self, path: impl AsRef) -> Result<()>; + fn save_cropped_to(&self, path: impl AsRef, section: Option) -> Result<()>; +} diff --git a/src/gui.rs b/src/gui_xcb.rs similarity index 90% rename from src/gui.rs rename to src/gui_xcb.rs index bd11f30..0385a7b 100644 --- a/src/gui.rs +++ b/src/gui_xcb.rs @@ -4,18 +4,21 @@ use anyhow::Result; use image::{Bgra, DynamicImage, ImageBuffer}; use xcb::{ base::Connection, - xproto::{self, Rectangle, Screen}, + xproto::{self, Screen}, }; use xcb_util::image as xcb_image; -pub struct Gui { +use crate::gui_trait::{Capture, Gui}; +use crate::rect::Rectangle; + +pub struct XcbGui { conn: Connection, } -impl Gui { +impl XcbGui { pub fn new() -> Result { let (conn, _) = Connection::connect(None)?; - Ok(Gui { conn }) + Ok(XcbGui { conn }) } fn get_default_screen(&self) -> Screen { @@ -24,8 +27,12 @@ impl Gui { let mut iter = setup.roots(); iter.next().unwrap() } +} - pub fn capture_entire_screen(&self) -> Result { +impl Gui for XcbGui { + type Capture = ScreenCapture; + + fn capture_entire_screen(&self) -> Result { // get the dimensions of the screen let screen = self.get_default_screen(); let (width, height) = (screen.width_in_pixels(), screen.height_in_pixels()); @@ -45,7 +52,7 @@ impl Gui { Ok(ScreenCapture { image }) } - pub fn interactive_select(&self, image: &ScreenCapture) -> Result> { + fn interactive_select(&self, image: &ScreenCapture) -> Result> { let window_id = self.conn.generate_id(); let screen = self.get_default_screen(); let root_window = screen.root(); @@ -196,20 +203,20 @@ impl Gui { window_gc, &[(xcb::GC_FOREGROUND, screen.black_pixel())], ); - xproto::poly_rectangle(&self.conn, window_id, window_gc, &[rect]); + xproto::poly_rectangle(&self.conn, window_id, window_gc, &[rect.into()]); let rect2 = Rectangle::new( - rect.x() + 1, - rect.y() + 1, - rect.width() - 2, - rect.height() - 2, + rect.x + 1, + rect.y + 1, + rect.width - 2, + rect.height - 2, ); xproto::change_gc( &self.conn, window_gc, &[(xcb::GC_FOREGROUND, screen.white_pixel())], ); - xproto::poly_rectangle(&self.conn, window_id, window_gc, &[rect2]); + xproto::poly_rectangle(&self.conn, window_id, window_gc, &[rect2.into()]); } } @@ -292,12 +299,12 @@ pub struct ScreenCapture { image: xcb_image::Image, } -impl ScreenCapture { - pub fn save_to(&self, to: impl AsRef) -> Result<()> { +impl Capture for ScreenCapture { + fn save_to(&self, to: impl AsRef) -> Result<()> { self.save_cropped_to(to, None) } - pub fn save_cropped_to(&self, to: impl AsRef, section: Option) -> Result<()> { + fn save_cropped_to(&self, to: impl AsRef, section: Option) -> Result<()> { let to = to.as_ref(); let image = ImageBuffer::, _>::from_raw( self.image.width() as u32, @@ -311,10 +318,10 @@ impl ScreenCapture { let image = if let Some(section) = section { // crop the image image.crop( - section.x() as u32, - section.y() as u32, - section.width() as u32, - section.height() as u32, + section.x as u32, + section.y as u32, + section.width as u32, + section.height as u32, ) } else { image diff --git a/src/main.rs b/src/main.rs index ec01cfc..365794f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,11 @@ extern crate log; #[macro_use] extern crate anyhow; -mod gui; +extern crate leanshot_x11 as x11; + +mod gui_trait; +mod gui_xcb; +mod rect; mod singleton; use std::path::PathBuf; @@ -13,7 +17,8 @@ use std::str::FromStr; use anyhow::Result; use structopt::StructOpt; -use crate::gui::Gui; +use crate::gui_trait::{Capture, Gui}; +use crate::gui_xcb::XcbGui; fn main() -> Result<()> { let opt = Options::from_args(); @@ -24,7 +29,7 @@ fn main() -> Result<()> { .init() .unwrap(); - let gui = Gui::new()?; + let gui = XcbGui::new()?; let capture = gui.capture_entire_screen()?; match opt.region { diff --git a/src/rect.rs b/src/rect.rs new file mode 100644 index 0000000..8dfd306 --- /dev/null +++ b/src/rect.rs @@ -0,0 +1,30 @@ +#[derive(Clone, Copy)] +pub struct Rectangle { + pub x: T, + pub y: T, + pub width: U, + pub height: U, +} + +impl Rectangle { + pub fn new(x: T, y: T, width: U, height: U) -> Self { + Rectangle { x, y, width, height } + } +} + +impl From for Rectangle { + fn from(rect: xcb::xproto::Rectangle) -> Self { + Rectangle { + x: rect.x(), + y: rect.y(), + width: rect.width(), + height: rect.height(), + } + } +} + +impl Into for Rectangle { + fn into(self) -> xcb::xproto::Rectangle { + xcb::xproto::Rectangle::new(self.x, self.y, self.width, self.height) + } +} diff --git a/src/singleton.rs b/src/singleton.rs index 13ee12c..caa8d4f 100644 --- a/src/singleton.rs +++ b/src/singleton.rs @@ -2,7 +2,6 @@ use std::fs::{self, File}; use std::io::{Read, Write}; use std::path::PathBuf; use std::process; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; use anyhow::{Error, Result}; @@ -47,7 +46,7 @@ fn get_lock_file() -> Result<(File, PathBuf)> { // let time = UNIX_EPOCH + Duration::from_millis(timestamp); // if the process that has the lockfile open is still running, bail - let status = unsafe {libc::kill(pid, 0)}; + let status = unsafe { libc::kill(pid, 0) }; // 0 means the signal was successfully sent, which means it's still running // if it's 0 then we bail diff --git a/x11/.gitignore b/x11/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/x11/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/x11/Cargo.toml b/x11/Cargo.toml new file mode 100644 index 0000000..d38d1df --- /dev/null +++ b/x11/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "leanshot-x11" +version = "0.0.5" +description = "Safe, high-level X11 bindings" +license-file = "LICENSE" +authors = ["Michael Zhang "] +edition = "2018" +documentation = "https://docs.rs/leanshot-x11" +readme = "README.md" +repository = "https://git.mzhang.io/michael/leanshot" + +[package.metadata.docs.rs] +all-features = true + +[features] +default = ["xlib"] +glx = ["x11/glx", "xlib", "gl_generator"] +xlib = ["x11/xlib"] +xrender = ["x11/xrender"] +xinerama = ["x11/xinerama", "xlib"] +xinput = ["x11/xinput"] + +[dependencies] +libc = "0.2" +x11 = "2.18" +thiserror = "1.0.20" +log = "0.4.8" +bitflags = "1.2.1" + +[build-dependencies] +gl_generator = { version = "0.14.0", optional = true } diff --git a/x11/build.rs b/x11/build.rs new file mode 100644 index 0000000..65c090b --- /dev/null +++ b/x11/build.rs @@ -0,0 +1,18 @@ +use std::env; +use std::fs::File; +use std::path::Path; + +#[cfg(feature = "glx")] +use gl_generator::{Api, Fallbacks, GlobalGenerator, Profile, Registry}; + +fn main() { + #[cfg(feature = "glx")] + { + let dest = env::var("OUT_DIR").unwrap(); + let mut file = File::create(&Path::new(&dest).join("bindings.rs")).unwrap(); + + Registry::new(Api::Gl, (4, 5), Profile::Core, Fallbacks::All, []) + .write_bindings(GlobalGenerator, &mut file) + .unwrap(); + } +} diff --git a/x11/src/errors.rs b/x11/src/errors.rs new file mode 100644 index 0000000..5f084c5 --- /dev/null +++ b/x11/src/errors.rs @@ -0,0 +1,39 @@ +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("no DISPLAY to connect to")] + NoDisplay, + + #[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("utf8 decoding error: {0}")] + Utf8(#[from] std::str::Utf8Error), + + #[error("nul error: {0}")] + Nul(#[from] std::ffi::NulError), + + #[error("error")] + Error, +} diff --git a/x11/src/ffi.rs b/x11/src/ffi.rs new file mode 100644 index 0000000..44fcea5 --- /dev/null +++ b/x11/src/ffi.rs @@ -0,0 +1,15 @@ +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +use crate::Result; + +pub unsafe fn string_from_c_char_star(ptr: *mut c_char) -> Result { + let c_str = CStr::from_ptr(ptr); + let s = c_str.to_str()?; + Ok(s.to_owned()) +} + +pub fn c_char_star_from_string(s: impl AsRef) -> Result<*mut c_char> { + let c_str = CString::new(s.as_ref().as_bytes())?; + Ok(c_str.into_raw()) +} diff --git a/x11/src/glx/context.rs b/x11/src/glx/context.rs new file mode 100644 index 0000000..f3023bf --- /dev/null +++ b/x11/src/glx/context.rs @@ -0,0 +1,20 @@ +use x11::glx; + +use crate::xlib::Display; + +pub struct GlxContext<'a> { + pub(super) display: &'a Display, + pub(super) ctx: glx::GLXContext, +} + +impl<'a> GlxContext<'a> { + pub fn as_raw(&self) -> glx::GLXContext { + self.ctx + } +} + +impl<'a> Drop for GlxContext<'a> { + fn drop(&mut self) { + unsafe { glx::glXDestroyContext(self.display.as_raw(), self.ctx) }; + } +} diff --git a/x11/src/glx/drawable.rs b/x11/src/glx/drawable.rs new file mode 100644 index 0000000..7b3242b --- /dev/null +++ b/x11/src/glx/drawable.rs @@ -0,0 +1,16 @@ +use x11::xlib; + +use crate::xlib::Window; + +pub enum GlxDrawable<'a> { + Window(Window<'a>), + // TODO: pixmap +} + +impl<'a> GlxDrawable<'a> { + pub fn as_raw(&self) -> xlib::XID { + match self { + GlxDrawable::Window(window) => window.as_raw(), + } + } +} diff --git a/x11/src/glx/mod.rs b/x11/src/glx/mod.rs new file mode 100644 index 0000000..ccd143e --- /dev/null +++ b/x11/src/glx/mod.rs @@ -0,0 +1,66 @@ +//! An extension that bridges X11 with OpenGL, an API used for rendering +//! graphics with the GPU. + +mod context; +mod drawable; + +use x11::glx; + +use crate::errors::Result; +use crate::xlib::{Display, VisualInfo}; + +pub use self::context::GlxContext; +pub use self::drawable::GlxDrawable; + +pub trait GlxExtension { + /// Checks if glx is available in this implementation. + fn query_glx_extension(&self) -> Result; + + fn create_context( + &self, + visual_info: &VisualInfo, + share_list: Option<&GlxContext>, + direct: bool, + ) -> Result; + + fn make_current(&self, drawable: GlxDrawable, ctx: GlxContext) -> Result<()>; + + fn choose_visual(&self, screen: i32) -> Result; +} + +impl GlxExtension for Display { + fn query_glx_extension(&self) -> Result { + let result = unsafe { glx::glXQueryExtension(self.inner, 0 as *mut _, 0 as *mut _) }; + Ok(result == 1) + } + + fn create_context( + &self, + visual_info: &VisualInfo, + share_list: Option<&GlxContext>, + direct: bool, + ) -> Result { + let share_list = share_list + .map(|ctx| ctx.as_raw()) + .unwrap_or_else(|| 0 as *mut _); + let ctx = unsafe { + glx::glXCreateContext( + self.as_raw(), + visual_info.as_raw(), + share_list, + direct.into(), + ) + }; + Ok(GlxContext { display: self, ctx }) + } + + fn make_current(&self, drawable: GlxDrawable, ctx: GlxContext) -> Result<()> { + unsafe { glx::glXMakeCurrent(self.as_raw(), drawable.as_raw(), ctx.as_raw()) }; + Ok(()) + } + + fn choose_visual(&self, screen: i32) -> Result { + let visual = unsafe { glx::glXChooseVisual(self.as_raw(), screen, 0 as *mut _) }; + Ok(VisualInfo::from_raw(visual)) + } +} diff --git a/x11/src/lib.rs b/x11/src/lib.rs new file mode 100644 index 0000000..42e5c4e --- /dev/null +++ b/x11/src/lib.rs @@ -0,0 +1,36 @@ +//! Higher level bindings to x11. + +#[macro_use] +extern crate bitflags; +#[macro_use] +extern crate thiserror; +#[macro_use] +extern crate log; + +pub extern crate x11; + +#[cfg(feature = "glx")] +mod gl { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} +#[cfg(feature = "glx")] +pub mod glx; + +#[cfg(feature = "xinput")] +pub mod xinput; + +#[cfg(feature = "xlib")] +pub mod xlib; + +#[cfg(feature = "xrender")] +pub mod xrender; + +#[cfg(feature = "xinerama")] +pub mod xinerama; + +mod errors; +mod ffi; +mod rect; + +pub use crate::errors::{Error, Result}; +pub use crate::rect::Rectangle; diff --git a/x11/src/rect.rs b/x11/src/rect.rs new file mode 100644 index 0000000..b77ad36 --- /dev/null +++ b/x11/src/rect.rs @@ -0,0 +1,35 @@ +/// 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, + } + } +} + +impl From<(i32, i32, u32, u32)> for Rectangle { + fn from((x, y, width, height): (i32, i32, u32, u32)) -> Self { + Rectangle { + x, + y, + width, + height, + } + } +} diff --git a/x11/src/xinerama/mod.rs b/x11/src/xinerama/mod.rs new file mode 100644 index 0000000..9808aac --- /dev/null +++ b/x11/src/xinerama/mod.rs @@ -0,0 +1,6 @@ +//! An extension that enables applications and window managers to use two or +//! more physical displays as one large virtual display. + +mod screen_info; + +pub use self::screen_info::ScreensInfo; diff --git a/x11/src/xinerama/screen_info.rs b/x11/src/xinerama/screen_info.rs new file mode 100644 index 0000000..a5909ca --- /dev/null +++ b/x11/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/x11/src/xinput/mod.rs b/x11/src/xinput/mod.rs new file mode 100644 index 0000000..aab30dd --- /dev/null +++ b/x11/src/xinput/mod.rs @@ -0,0 +1,2 @@ +//! Component of x11 related to listing available input devices, querying +//! information about a device and changing input device settings. diff --git a/x11/src/xlib/atom.rs b/x11/src/xlib/atom.rs new file mode 100644 index 0000000..469ab5b --- /dev/null +++ b/x11/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/x11/src/xlib/colormap.rs b/x11/src/xlib/colormap.rs new file mode 100644 index 0000000..36b2854 --- /dev/null +++ b/x11/src/xlib/colormap.rs @@ -0,0 +1,15 @@ +use x11::xlib; + +use super::display::Display; + +#[derive(Debug)] +pub struct ColorMap<'a> { + pub(crate) display: &'a Display, + pub(crate) inner: xlib::Colormap, +} + +impl<'a> ColorMap<'a> { + pub fn as_raw(&self) -> xlib::Colormap { + self.inner + } +} diff --git a/x11/src/xlib/cursor.rs b/x11/src/xlib/cursor.rs new file mode 100644 index 0000000..1670ad3 --- /dev/null +++ b/x11/src/xlib/cursor.rs @@ -0,0 +1,22 @@ +use x11::xlib; + +use super::display::Display; + +/// Mouse pointer +#[derive(Debug)] +pub struct Cursor<'a> { + pub(crate) display: &'a Display, + pub(crate) inner: xlib::Cursor, +} + +impl<'a> Cursor<'a> { + pub fn as_raw(&self) -> xlib::Cursor { + self.inner + } +} + +impl<'a> Drop for Cursor<'a> { + fn drop(&mut self) { + unsafe { xlib::XFreeCursor(self.display.as_raw(), self.inner) }; + } +} diff --git a/x11/src/xlib/display.rs b/x11/src/xlib/display.rs new file mode 100644 index 0000000..c1b3fca --- /dev/null +++ b/x11/src/xlib/display.rs @@ -0,0 +1,255 @@ +use std::env; +use std::ffi::CString; +use std::mem::MaybeUninit; +use std::os::raw::c_char; +use std::ptr; + +use x11::xlib; + +use crate::errors::{Error, Result}; +use crate::ffi; + +use super::cursor::Cursor; +use super::event::Event; +use super::visual::Visual; +use super::window::Window; + +/// A connection to an X server. +#[derive(Debug)] +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 + /// + /// If `None` is passed, then use $DISPLAY + pub fn connect(display_name: Option<&str>) -> Result { + let display_name = if let Some(display_name) = display_name { + CString::new(display_name).unwrap() + } else if let Ok(display_env) = env::var("DISPLAY") { + CString::new(display_env).unwrap() + } else { + return Err(Error::NoDisplay); + }; + 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: cursor, + }) + } + + /// Get the next event, blocks until an event is reached. + pub fn next_event(&self) -> Result { + debug!("fishing for next event..."); + let mut event = MaybeUninit::uninit(); + let ptr = event.as_mut_ptr(); + unsafe { xlib::XNextEvent(self.inner, ptr) }; + // TODO: check to make sure this isn't null + let event = unsafe { event.assume_init() }; + debug!("event: {:?}", event); + 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, + }; + 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, + }; + 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: 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: focus_return, + }; + return Ok((window, revert_to_return)); + } + + /// Query extension + pub fn query_extension(&self, name: impl AsRef) -> Result<()> { + let name = ffi::c_char_star_from_string(name)?; + let major_opcode_return = ptr::null_mut(); + let first_event_return = ptr::null_mut(); + let first_error_return = ptr::null_mut(); + let _result = unsafe { + xlib::XQueryExtension( + self.inner, + name, + major_opcode_return, + first_event_return, + first_error_return, + ) + }; + // TODO: check reuslt + + unimplemented!() + } + + /// List extensions + pub fn list_extensions(&self) -> Result { + let nextensions_return = ptr::null_mut(); + let result = unsafe { xlib::XListExtensions(self.inner, nextensions_return) }; + // TODO: check result null + // TODO: check nextensions_return + let nextensions_return = unsafe { *nextensions_return } as isize; + Ok(ListExtensions(&self, result, nextensions_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) }; + } +} + +pub struct ListExtensions<'a>(&'a Display, *mut *mut c_char, isize); + +impl<'a> ListExtensions<'a> { + pub fn iter(&self) -> ListExtensionsIter { + ListExtensionsIter(self, 0) + } +} + +pub struct ListExtensionsIter<'a>(&'a ListExtensions<'a>, isize); + +impl<'a> Iterator for ListExtensionsIter<'a> { + type Item = String; + fn next(&mut self) -> Option { + if self.1 >= (self.0).2 { + return None; + } + + // TODO: check for null + let base = unsafe { *(self.0).1.offset(self.1) }; + let s = unsafe { ffi::string_from_c_char_star(base).unwrap() }; + // TODO: don't unwrap here + Some(s) + } +} diff --git a/x11/src/xlib/drawable.rs b/x11/src/xlib/drawable.rs new file mode 100644 index 0000000..60be2cb --- /dev/null +++ b/x11/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/x11/src/xlib/event.rs b/x11/src/xlib/event.rs new file mode 100644 index 0000000..4ab0eeb --- /dev/null +++ b/x11/src/xlib/event.rs @@ -0,0 +1,102 @@ +use x11::xlib; + +use crate::errors::{Error, Result}; + +/// An x11 Event +#[derive(Debug)] +pub struct Event { + inner: xlib::XEvent, + kind: EventKind, +} + +/// Type of event +#[derive(Debug)] +pub enum EventKind { + KeyPress(KeyEvent), + KeyRelease(KeyEvent), + + ButtonPress(ButtonEvent), + ButtonRelease(ButtonEvent), + Motion(MotionEvent), + + Enter(CrossingEvent), + Leave(CrossingEvent), + + FocusIn(FocusChangeEvent), + FocusOut(FocusChangeEvent), + + Keymap(KeymapEvent), + + Configure(ConfigureEvent), + Destroy(DestroyWindowEvent), + Reparent(ReparentEvent), + + Property(PropertyEvent), +} + +impl Event { + /// Returns the EventKind of this event + pub fn kind(&self) -> &EventKind { + &self.kind + } + + pub(super) unsafe fn from_raw(event: xlib::XEvent) -> Result { + let inner = 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)), + + xlib::ButtonPress => EventKind::ButtonPress(ButtonEvent(event.button)), + xlib::ButtonRelease => EventKind::ButtonRelease(ButtonEvent(event.button)), + xlib::MotionNotify => EventKind::Motion(MotionEvent(event.motion)), + + xlib::EnterNotify => EventKind::Enter(CrossingEvent(event.crossing)), + xlib::LeaveNotify => EventKind::Leave(CrossingEvent(event.crossing)), + + xlib::FocusIn => EventKind::FocusIn(FocusChangeEvent(event.focus_change)), + xlib::FocusOut => EventKind::FocusOut(FocusChangeEvent(event.focus_change)), + + xlib::KeymapNotify => EventKind::Keymap(KeymapEvent(event.keymap)), + + xlib::ConfigureNotify => EventKind::Configure(ConfigureEvent(event.configure)), + xlib::DestroyNotify => EventKind::Destroy(DestroyWindowEvent(event.destroy_window)), + xlib::ReparentNotify => EventKind::Reparent(ReparentEvent(event.reparent)), + + xlib::PropertyNotify => EventKind::Property(PropertyEvent(event.property)), + + other => return Err(Error::InvalidEventType(other)), + }; + Ok(Event { inner, kind }) + } +} + +#[derive(Debug)] +pub struct KeyEvent(xlib::XKeyEvent); + +#[derive(Debug)] +pub struct ButtonEvent(xlib::XButtonEvent); + +#[derive(Debug)] +pub struct MotionEvent(xlib::XMotionEvent); + +#[derive(Debug)] +pub struct CrossingEvent(xlib::XCrossingEvent); + +#[derive(Debug)] +pub struct FocusChangeEvent(xlib::XFocusChangeEvent); + +#[derive(Debug)] +pub struct KeymapEvent(xlib::XKeymapEvent); + +#[derive(Debug)] +pub struct ConfigureEvent(xlib::XConfigureEvent); + +#[derive(Debug)] +pub struct DestroyWindowEvent(xlib::XDestroyWindowEvent); + +#[derive(Debug)] +pub struct ReparentEvent(xlib::XReparentEvent); + +#[derive(Debug)] +pub struct PropertyEvent(xlib::XPropertyEvent); diff --git a/x11/src/xlib/image.rs b/x11/src/xlib/image.rs new file mode 100644 index 0000000..71c0c6a --- /dev/null +++ b/x11/src/xlib/image.rs @@ -0,0 +1,126 @@ +use x11::xlib; + +use crate::errors::{Error, Result}; + +/// A handle to an XImage +/// +/// Xlib provides some structures to perform operations on images. However, because there are +/// different in-memory representations, working with images is actually totally fucked. If you +/// want to maintain your sanity, do NOT pry into the raw data buffer of an Image. Instead, index +/// into the image using `PixBuffer::get_pixel` which already performs the conversion and returns a +/// tuple of RGB values. +/// +/// Peeking into the raw data buffer of an Image +/// -------------------------------------------- +/// +/// Images consist of lines of longs in row-major form. +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, +} + +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 (LSB or MSB) + 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) }; + } +} + +/// 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<'a> PixBuffer<'a> { + /// Retrieve a direct reference to the internal buffer. + pub fn get_buffer(&self) -> &[u8] { + unsafe { std::slice::from_raw_parts(self.buf, self.size) } + } + + /// Gets the rgb value of a particular pixel, after performing conversion + /// + /// This uses XImage's get_pixel function to retrieve the pixel value, and then the various + /// red/green/blue masks to actually perform the conversions, and fails (returns `None`) if the + /// (x, y) coordinates given aren't actually in the image. + pub fn get_pixel(&self, x: u32, y: u32) -> Option<(u8, u8, u8)> { + 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) }; + + 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 }; + + let red = ((pixel & red_mask) >> 16) as u8; + let green = ((pixel & green_mask) >> 8) as u8; + let blue = ((pixel & blue_mask) >> 0) as u8; + + Some((red, green, blue)) + } +} diff --git a/x11/src/xlib/mod.rs b/x11/src/xlib/mod.rs new file mode 100644 index 0000000..9e35a04 --- /dev/null +++ b/x11/src/xlib/mod.rs @@ -0,0 +1,28 @@ +//! xlib +//! ---- +//! +//! The core struct used here is Display, which represents a connection to an X server. Almost all +//! other structs depend on a reference to Display, since they only make sense in context of an X +//! server. + +mod atom; +mod colormap; +mod cursor; +mod display; +mod drawable; +mod event; +mod image; +mod pixmap; +mod visual; +mod window; + +pub use self::atom::Atom; +pub use self::colormap::ColorMap; +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, PixBuffer}; +pub use self::pixmap::PixMap; +pub use self::visual::{Visual, VisualInfo}; +pub use self::window::{EventMask, SetWindowAttributes, Window, WindowAttributes}; diff --git a/x11/src/xlib/pixmap.rs b/x11/src/xlib/pixmap.rs new file mode 100644 index 0000000..6ce2a26 --- /dev/null +++ b/x11/src/xlib/pixmap.rs @@ -0,0 +1,28 @@ +use x11::xlib; + +use super::display::Display; + +/// Pixmaps are off-screen resources that are used for various operations, for +/// example, defining cursors as tiling patterns or as the source for certain +/// raster operations +/// +/// Most graphics requests can operate either on a window or on a pixmap. A +/// bitmap is a single bit-plane pixmap. Pixmaps can only be used on the screen +/// on which they were created. +#[derive(Debug)] +pub struct PixMap<'a> { + pub(crate) display: &'a Display, + pub(crate) inner: xlib::Pixmap, +} + +impl<'a> PixMap<'a> { + pub fn as_raw(&self) -> xlib::Pixmap { + self.inner + } +} + +impl<'a> Drop for PixMap<'a> { + fn drop(&mut self) { + unsafe { xlib::XFreePixmap(self.display.as_raw(), self.inner) }; + } +} diff --git a/x11/src/xlib/visual.rs b/x11/src/xlib/visual.rs new file mode 100644 index 0000000..78b1ad0 --- /dev/null +++ b/x11/src/xlib/visual.rs @@ -0,0 +1,35 @@ +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 } + } +} + +pub struct VisualInfo { + pub(super) inner: *mut xlib::XVisualInfo, +} + +impl VisualInfo { + pub fn from_raw(inner: *mut xlib::XVisualInfo) -> Self { + VisualInfo { inner } + } + + pub fn as_raw(&self) -> *mut xlib::XVisualInfo { + self.inner + } +} diff --git a/x11/src/xlib/window/attr.rs b/x11/src/xlib/window/attr.rs new file mode 100644 index 0000000..05ad965 --- /dev/null +++ b/x11/src/xlib/window/attr.rs @@ -0,0 +1,186 @@ +use std::os::raw::c_ulong; + +use x11::xlib; + +use crate::xlib::colormap::ColorMap; +use crate::xlib::cursor::Cursor; +use crate::xlib::display::Display; +use crate::xlib::pixmap::PixMap; +use crate::xlib::window::Window; + +/// Window Attributes +pub struct WindowAttributes<'a> { + pub(crate) display: &'a Display, + pub(crate) inner: *mut xlib::XWindowAttributes, +} + +impl<'a> WindowAttributes<'a> { + /// 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<'a> Drop for WindowAttributes<'a> { + fn drop(&mut self) { + unsafe { libc::free(self.inner as *mut libc::c_void) }; + } +} + +bitflags! { + #[derive(Default)] + pub struct EventMask: c_ulong { + const NO_EVENT_MASK = 0; + const KEY_PRESS_MASK = (1<<0); + const KEY_RELEASE_MASK = (1<<1); + const BUTTON_PRESS_MASK = (1<<2); + const BUTTON_RELEASE_MASK = (1<<3); + const ENTER_WINDOW_MASK = (1<<4); + const LEAVE_WINDOW_MASK = (1<<5); + const POINTER_MOTION_MASK = (1<<6); + const POINTER_MOTION_HINT_MASK = (1<<7); + const BUTTON_1_MOTION_MASK = (1<<8); + const BUTTON_2_MOTION_MASK = (1<<9); + const BUTTON_3_MOTION_MASK = (1<<10); + const BUTTON_4_MOTION_MASK = (1<<11); + const BUTTON_5_MOTION_MASK = (1<<12); + const BUTTON_MOTION_MASK = (1<<13); + const KEYMAP_STATE_MASK = (1<<14); + const EXPOSURE_MASK = (1<<15); + const VISIBILITY_CHANGE_MASK = (1<<16); + const STRUCTURE_NOTIFY_MASK = (1<<17); + const RESIZE_REDIRECT_MASK = (1<<18); + const SUBSTRUCTURE_NOTIFY_MASK = (1<<19); + const SUBSTRUCTURE_REDIRECT_MASK = (1<<20); + const FOCUS_CHANGE_MASK = (1<<21); + const PROPERTY_CHANGE_MASK = (1<<22); + const COLORMAP_CHANGE_MASK = (1<<23); + const OWNER_GRAB_BUTTON_MASK = (1<<24); + } +} + +#[derive(Debug)] +#[repr(i32)] +pub enum BackingStore { + NotUseful = xlib::NotUseful, + WhenMapped = xlib::WhenMapped, + Always = xlib::Always, +} + +impl Default for BackingStore { + fn default() -> Self { + BackingStore::NotUseful + } +} + +#[derive(Debug)] +#[repr(i32)] +pub enum Gravity { + NorthWest = xlib::NorthWestGravity, + North = xlib::NorthGravity, + NorthEast = xlib::NorthEastGravity, + West = xlib::WestGravity, + Center = xlib::CenterGravity, + East = xlib::EastGravity, + SouthWest = xlib::SouthWestGravity, + South = xlib::SouthGravity, + SouthEast = xlib::SouthEastGravity, + + Forget = xlib::ForgetGravity, + Static = xlib::StaticGravity, + // TODO: separate one for window gravity + // Unmap= xlib::UnmapGravity, +} + +// TODO: some of these Options have other options than just None +// TODO: do-not-propagate mask has a subset of options? +#[derive(Debug)] +pub struct SetWindowAttributes<'a> { + pub background_pixmap: Option>, + pub background_pixel: u64, + pub border_pixmap: Option>, + pub border_pixel: u64, + pub bit_gravity: Gravity, + pub win_gravity: Gravity, + pub backing_store: BackingStore, + pub backing_planes: u64, + pub backing_pixel: u64, + pub save_under: bool, + pub event_mask: EventMask, + pub do_not_propagate_mask: EventMask, + pub override_redirect: bool, + pub colormap: Option>, + pub cursor: Option>, +} + +impl<'a> Default for SetWindowAttributes<'a> { + fn default() -> Self { + SetWindowAttributes { + background_pixmap: None, + background_pixel: 0, + border_pixmap: None, + border_pixel: 0, + bit_gravity: Gravity::Forget, + win_gravity: Gravity::NorthWest, + backing_store: BackingStore::NotUseful, + backing_planes: u64::MAX, + backing_pixel: 0, + save_under: false, + event_mask: EventMask::default(), + do_not_propagate_mask: EventMask::default(), + override_redirect: false, + colormap: None, + cursor: None, + } + } +} + +impl<'a> SetWindowAttributes<'a> { + pub fn into_raw(self) -> *mut xlib::XSetWindowAttributes { + let result = xlib::XSetWindowAttributes { + background_pixmap: self + .background_pixmap + .map(|p| p.as_raw()) + .unwrap_or_else(|| 0), + background_pixel: self.background_pixel, + border_pixmap: self.border_pixmap.map(|p| p.as_raw()).unwrap_or_else(|| 0), + border_pixel: self.border_pixel, + bit_gravity: self.bit_gravity as i32, + win_gravity: self.win_gravity as i32, + backing_store: self.backing_store as i32, + backing_planes: self.backing_planes, + backing_pixel: self.backing_pixel, + save_under: self.save_under.into(), + event_mask: self.event_mask.bits() as i64, + do_not_propagate_mask: self.do_not_propagate_mask.bits() as i64, + override_redirect: self.override_redirect.into(), + colormap: self.colormap.map(|p| p.as_raw()).unwrap_or_else(|| 0), + cursor: self.cursor.map(|p| p.as_raw()).unwrap_or_else(|| 0), + }; + + Box::into_raw(Box::new(result)) + } +} diff --git a/x11/src/xlib/window/mod.rs b/x11/src/xlib/window/mod.rs new file mode 100644 index 0000000..22d2fc9 --- /dev/null +++ b/x11/src/xlib/window/mod.rs @@ -0,0 +1,147 @@ +mod attr; + +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; + +pub use self::attr::{EventMask, SetWindowAttributes, WindowAttributes}; + +/// A wrapper around a window handle. +#[derive(Clone, Debug)] +pub struct Window<'a> { + pub(crate) display: &'a Display, + pub(crate) inner: xlib::Window, +} + +impl<'a> Window<'a> { + /// Create a new window + pub fn create( + display: &'a Display, + parent: Option, + location: Rectangle, + set_attributes: SetWindowAttributes, + ) -> 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, + set_attributes.into_raw(), + ) + }; + Ok(Window { + display, + inner: window, + }) + } + + /// Create a new Window instance from an existing ID + pub fn create_from_handle(display: &Display, id: u64) -> Result { + Ok(Window { display, 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.as_raw(), 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.as_raw(), + self.inner, + key.as_raw(), + xlib::XA_ATOM, + 32, + xlib::PropModeReplace, + transmute(&v), + 1, + ); + } + } + + /// "Maps", or shows a window to a display. + pub fn map(&self) { + unsafe { xlib::XMapWindow(self.display.as_raw(), self.inner) }; + } + + /// Requests for events from the server matching a particular event mask. + /// + /// If this function is not called, events will not be reported. + pub fn select_input(&self, event_mask: EventMask) { + debug!("event mask: {:?}", event_mask); + unsafe { xlib::XSelectInput(self.display.as_raw(), self.inner, event_mask.bits() as i64) }; + } +} + +impl<'a> GetDisplay for Window<'a> { + fn get_display(&self) -> *mut xlib::Display { + self.display.as_raw() + } +} + +impl<'a> Drawable for Window<'a> { + fn as_drawable(&self) -> xlib::Drawable { + self.inner + } +} + +impl<'a> Drop for Window<'a> { + fn drop(&mut self) { + unsafe { xlib::XDestroyWindow(self.display.as_raw(), self.inner) }; + } +} diff --git a/x11/src/xrender/mod.rs b/x11/src/xrender/mod.rs new file mode 100644 index 0000000..50da015 --- /dev/null +++ b/x11/src/xrender/mod.rs @@ -0,0 +1,4 @@ +//! An extension that deals with rendering and compositing images with other +//! drawing primitives. + +mod picture; diff --git a/x11/src/xrender/picture.rs b/x11/src/xrender/picture.rs new file mode 100644 index 0000000..8a2023e --- /dev/null +++ b/x11/src/xrender/picture.rs @@ -0,0 +1,7 @@ +pub struct Picture {} + +impl Picture {} + +impl Drop for Picture { + fn drop(&mut self) {} +}