From acf4a04b7452b6dd2e69064b4f85144b8445bbe9 Mon Sep 17 00:00:00 2001 From: Matias Linares Date: Sun, 8 Nov 2015 23:02:00 -0300 Subject: Initial commit This initial commit has the following: * We can spawn a terminal. * Given a new window, we can move it around the screen, but only the window that was created, all the other windows disappear on the void, so.. :( and that's it --- .gitignore | 1 + Cargo.lock | 27 +++++++ Cargo.toml | 11 +++ src/command.rs | 47 ++++++++++++ src/dotwm.rs | 140 ++++++++++++++++++++++++++++++++++ src/event.rs | 69 +++++++++++++++++ src/main.rs | 75 ++++++++++++++++++ src/safe_x11/mod.rs | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ src/safe_x11/window.rs | 144 +++++++++++++++++++++++++++++++++++ 9 files changed, 715 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/command.rs create mode 100644 src/dotwm.rs create mode 100644 src/event.rs create mode 100644 src/main.rs create mode 100644 src/safe_x11/mod.rs create mode 100644 src/safe_x11/window.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4058f90 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,27 @@ +[root] +name = "dotwm" +version = "0.1.0" +dependencies = [ + "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "x11 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pkg-config" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "x11" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2400b4e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "dotwm" +version = "0.1.0" +authors = ["Matias Linares "] + +[dependencies] +libc = "*" + +[dependencies.x11] +version = "*" +features = ["xlib"] diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..ddfa269 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,47 @@ +//! Command execution module. +//! +use std::ffi::OsStr; +use std::process::{Command,Child}; +use std::io::{Result, Error, ErrorKind}; + +use libc::c_int; +use libc::types::os::arch::posix88::pid_t; + +const WNOHANG: c_int = 0x00000001; + +extern { + /// wait for a child process to stop or terminate. + pub fn waitpid(pid: pid_t, stat_loc: *mut c_int, options: c_int) -> pid_t; +} + +/// Extension to the Child struct that allows to check the status without block the +/// current thread. +pub trait ChildExt { + fn wait_nohang(&mut self) -> Result; +} + +impl ChildExt for Child { + /// Checks if the calling child is a zombie and try to kill it, returning + /// if it was killed or not. + fn wait_nohang(&mut self) -> Result { + let mut stat = 0i32; + let pid = unsafe { waitpid(self.id() as i32, &mut stat, WNOHANG) }; + if pid < 0 { + Err(Error::new(ErrorKind::NotFound, format!("pid not found {}", pid))) + } else { + if pid > 0 { + Ok(self.wait() + .map(|x| { + x.success() + }).unwrap_or(false)) + } else { + Ok(false) + } + } + } +} + +/// Executes the given command. +pub fn exec_cmd>(program: S, args: &[S]) -> Result { + Command::new(program).args(args).spawn() +} diff --git a/src/dotwm.rs b/src/dotwm.rs new file mode 100644 index 0000000..2d64584 --- /dev/null +++ b/src/dotwm.rs @@ -0,0 +1,140 @@ +use x11::xlib; +use x11::xlib::{ + Display, + XErrorEvent, + XEvent, + + GrabModeAsync, +}; + +use safe_x11::{ + open_display, + close_display, + grab_key, +}; + +use safe_x11::window::XWindow; + +use std::ptr; +use std::process::exit; +use std::mem::uninitialized; +use std::collections::HashMap; +use libc::c_int; + +#[allow(unused_variables)] +unsafe extern "C" fn error_handler(d: *mut Display, evptr: *mut XErrorEvent) -> c_int { + println!("ERRORR"); + let ev = ptr::read(evptr); + if ev.error_code == xlib::BadAccess { + println!("Another widnow manager is running :C"); + exit(1); + } + + 0 +} + +/// Defines a callback to call no certains events. +pub type ExecFn = fn(&DotWM, XEvent, &[String]) -> bool; + +pub struct DotWM { + pub display: *mut Display, + // Map with the keys as (key, mod) + bindings: HashMap<(u32, u32), (ExecFn, Vec)>, + cw_idx: usize, + pub window_list: Vec, +} + +/// DotWM state. +impl DotWM { + /// Create a state of the Dot Window Manager + pub fn new() -> DotWM { + let d = open_display("").unwrap(); + unsafe { xlib::XSetErrorHandler(Some(error_handler)) }; + // Some testings. + unsafe { + let root = xlib::XDefaultRootWindow(d); + let mut attrs: xlib::XSetWindowAttributes = uninitialized(); + attrs.event_mask = xlib::SubstructureNotifyMask; + xlib::XChangeWindowAttributes(d, root, xlib::CWEventMask, &mut attrs); + } + + DotWM { + display: d, + bindings: HashMap::new(), + cw_idx: 0, + window_list: vec![], + } + } + + /// Add a binding to the WM. + /// + /// # Example + /// + /// ``` + /// fn exec(_: &DotWM, _: xlib::XEvent, args: &[String]) -> bool { + /// if let Some(program) = args.first() { + /// let mut prog_args = vec![]; + /// for arg in args[1..].iter() { + /// prog_args.push(arg); + /// } + /// exec_cmd(program, prog_args.deref()).unwrap(); + /// } + /// true + /// } + /// + /// // ... + /// + /// dotwm.add_binding(keysym::XK_Return, xlib::Mod4Mask, exec, + /// &["xterm"]); + /// ``` + pub fn add_binding(&mut self, key: u32, modifiers: u32, func: ExecFn, args: &[&str]) { + grab_key(self.display, key, modifiers, true, GrabModeAsync, GrabModeAsync); + let mut v = vec![]; + for arg in args { + v.push(arg.to_string()); + } + self.bindings.insert((key, modifiers), (func, v)); + } + + pub fn exec_func(&mut self, key: u32, modifiers: u32, ev: xlib::XEvent) { + if let Some(&(func, ref args)) = self.bindings.get(&(key, modifiers)) { + let v = args.clone(); + func(&self, ev, &v); + } + } + + pub fn add_window(&mut self, w: xlib::Window) { + if let Some(w) = XWindow::new(self.display, w) { + self.window_list.push(w); + // Last windows get focus. + self.cw_idx = self.window_list.len() - 1; + } + } + + pub fn current_window(&self) -> Option<&XWindow> { + if self.cw_idx < self.window_list.len() { + self.window_list.get(self.cw_idx) + } else { + None + } + } + + pub fn remove_window(&mut self, w: xlib::Window) { + let pos = self.window_list.iter().position(|xw| xw.inner == w); + + match pos { + Some(idx) => { + self.window_list.remove(idx); + self.cw_idx = usize::max_value(); + }, + None => (), + } + } +} + +impl Drop for DotWM { + fn drop(&mut self) { + println!("Closing display"); + close_display(self.display); + } +} diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..e15ee18 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,69 @@ +use x11::xlib::{ + XEvent, + XKeyEvent, + XButtonEvent, + XMotionEvent, + XCrossingEvent, + XCreateWindowEvent, + XDestroyWindowEvent, + XConfigureEvent, +}; + +use x11::xlib; +use x11::xlib::Display; +use std::fmt; + +use safe_x11::next_xevent; + +pub enum Event { + Key(XKeyEvent, bool), + Button(XButtonEvent, bool), + Drag(XMotionEvent), + Enter(XCrossingEvent), + Leave(XCrossingEvent), + Generic(XEvent), + Create(XCreateWindowEvent), + Destroy(XDestroyWindowEvent), + Configure(XConfigureEvent), + NoEvent +} + +impl fmt::Debug for Event { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + &Event::Key(ev, pressed) => format!("Key({}, {})", XEvent::from(ev).get_type(), pressed), + &Event::Button(ev, pressed) => format!("Button({}, {})", XEvent::from(ev).get_type(), pressed), + &Event::Drag(ev) => format!("Drag({})", XEvent::from(ev).get_type()), + &Event::Generic(ev) => format!("Generic({})", XEvent::from(ev).get_type()), + &Event::Enter(ev) => format!("Enter({})", XEvent::from(ev).get_type()), + &Event::Leave(ev) => format!("Enter({})", XEvent::from(ev).get_type()), + &Event::Create(ev) => format!("Create({})", XEvent::from(ev).get_type()), + &Event::Destroy(ev) => format!("Destroy({})", XEvent::from(ev).get_type()), + &Event::Configure(ev) => format!("Configure({})", XEvent::from(ev).get_type()), + &Event::NoEvent => format!("NoEvent"), + }; + + write!(f, "{}", s) + } +} + +pub fn next_event(display: *mut Display) -> Event { + let ev = next_xevent(display); + + match ev.get_type() { + 2 => Event::Key(xlib::XKeyEvent::from(ev), true), + 3 => Event::Key(xlib::XKeyEvent::from(ev), false), + 4 => Event::Button(xlib::XButtonEvent::from(ev), true), + 5 => Event::Button(xlib::XButtonEvent::from(ev), false), + 6 => Event::Drag(XMotionEvent::from(ev)), + 7 => Event::Enter(XCrossingEvent::from(ev)), + 8 => Event::Leave(XCrossingEvent::from(ev)), + 16 => Event::Create(XCreateWindowEvent::from(ev)), + 17 => Event::Destroy(XDestroyWindowEvent::from(ev)), + 22 => Event::Configure(XConfigureEvent::from(ev)), + e => { + println!("Unknown event {}", e); + Event::NoEvent + }, + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c89544d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,75 @@ +extern crate libc; +extern crate x11; + +pub mod safe_x11; +pub mod command; +pub mod dotwm; +pub mod event; + +use dotwm::DotWM; +use command::exec_cmd; +use event::{Event,next_event}; +use std::ops::Deref; +use std::process; + +use x11::xlib; +use x11::keysym; + +fn exec(_: &DotWM, _: xlib::XEvent, args: &[String]) -> bool { + if let Some(program) = args.first() { + let mut prog_args = vec![]; + for arg in args[1..].iter() { + prog_args.push(arg); + } + exec_cmd(program, prog_args.deref()).unwrap(); + } + true +} + +fn move_win(wm: &DotWM, _: xlib::XEvent, args: &[String]) -> bool { + println!("Parsing x"); + let x = args[0].parse::().unwrap(); + println!("Parsing y"); + let y = args[1].parse::().unwrap(); + if let Some(ref win) = wm.current_window() { + win.move_offset(x, y); + }; + true +} + +fn quit_dotwm(_: &DotWM, _: xlib::XEvent, _: &[String]) -> bool { + process::exit(0); +} + +fn main() { + println!("Creating dotwm"); + let mut dotwm = DotWM::new(); + + dotwm.add_binding(keysym::XK_Return, xlib::Mod4Mask, exec, + &["xterm"]); + dotwm.add_binding(keysym::XK_h, xlib::Mod4Mask, move_win, &["-10", "0"]); + dotwm.add_binding(keysym::XK_j, xlib::Mod4Mask, move_win, &["0", "10"]); + dotwm.add_binding(keysym::XK_k, xlib::Mod4Mask, move_win, &["0", "-10"]); + dotwm.add_binding(keysym::XK_l, xlib::Mod4Mask, move_win, &["10", "0"]); + + dotwm.add_binding(keysym::XK_q, xlib::Mod4Mask | xlib::ShiftMask, quit_dotwm, &[]); + + // Main loop + loop { + let event = next_event(dotwm.display); + match event { + Event::Key(mut e, true) => { + let keysym = unsafe { xlib::XLookupKeysym(&mut e, 0) as u32 }; + let mask = e.state; + + dotwm.exec_func(keysym, mask, xlib::XEvent::from(e)); + }, + Event::Create(e) => { + // XCreateWindowEvent + let create_event = xlib::XCreateWindowEvent::from(e); + dotwm.add_window(create_event.window); + } + _ => println!("Catched event! {:?}", event), + } + } +} diff --git a/src/safe_x11/mod.rs b/src/safe_x11/mod.rs new file mode 100644 index 0000000..199c402 --- /dev/null +++ b/src/safe_x11/mod.rs @@ -0,0 +1,201 @@ +//! Safe wrapper around the `x11-rs` crate. This should be as much safe as possible. + +#![allow(dead_code)] +use x11::xlib; +use x11::xlib::{ + Display, + Screen, + Cursor, + XDefaultRootWindow, + XQueryTree, + + // Windows + Window, + + // Events + XEvent, +}; +use x11::xlib::XKeyEvent; + +use std::ffi::CString; +use std::ffi::NulError; +use std::ptr; +use std::error; +use std::mem::uninitialized; +use std::slice; +use std::fmt; + +pub mod window; + +#[derive(Debug)] +pub struct XSafeError<'a> { + _description: &'a str, +} + +impl<'a> XSafeError<'a> { + fn new(desc: &'static str) -> XSafeError<'a> { + XSafeError { _description: desc } + } +} + +impl<'a> From for XSafeError<'a> { + fn from(_: NulError) -> XSafeError<'a> { + XSafeError::new("Null ponter error!") + } +} + +impl<'a> error::Error for XSafeError<'a> { + fn description(&self) -> &str { + self._description + } + + fn cause(&self) -> Option<&error::Error> { + None + } +} + +impl<'a> fmt::Display for XSafeError<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", self._description) + } +} + +/// Open a display. If the string passed by parameter is `""`, it will open the +/// default display. +/// +/// # Failures +/// +/// This function can raise a NulError when the bytes yielded contains an internal +/// 0 byte. +/// +pub fn open_display(display: &str) -> Result<*mut Display, XSafeError> { + let d = if display == "" { + println!("Opening default display"); + unsafe { xlib::XOpenDisplay(0x0 as *const i8) } + } else { + println!("Opening {}", display); + let cstr = try!(CString::new(display)); + unsafe { xlib::XOpenDisplay(cstr.as_ptr()) } + }; + + if d.is_null() { + Err(XSafeError::new("Cannot open display!")) + } else { + Ok(d) + } +} + +pub fn close_display(display: *mut Display) { + unsafe { xlib::XCloseDisplay(display); } +} + +/// Grab pointer buttons. +/// +/// # Example +/// +/// ``` +/// grab_button( +/// // Display +/// display, +/// // Button to press +/// xlib::Button1 as i32, +/// // Modifiers (can be an or of modifiers). +/// xlib::Mod1Mask, +/// // owner events. +/// true, +/// // Which motions we'll handle +/// ButtonPressMask | ButtonReleaseMask | PointerMotionMask, +/// // Keyboard and pointer sync moddes +/// GrabModeAsync, GrabModeAsync, +/// // Confine to and cursor. (Can be 0 :) ) +/// 0, 0 +/// ); +/// ``` +pub fn grab_button(display: *mut Display, button: u32, modifiers: u32, + owner_events: bool, event_mask: i64, pointer_mode: i32, + keyboard_mode: i32, confine_to: Window, + cursor: Cursor) { + unsafe { + let default_win = xlib::XDefaultRootWindow(display); + xlib::XGrabButton(display, button, modifiers , default_win, + owner_events as i32, event_mask as u32, pointer_mode, + keyboard_mode, confine_to, cursor); + } +} + +/// Register a combination of keys that will send a `XEvent` +/// +pub fn grab_key(display: *mut Display, key: u32, modifiers: u32, owner_events: bool, + pointer_mode: i32, keyboard_mode: i32) { + unsafe { + let default_win = xlib::XDefaultRootWindow(display); + let keycode: i32 = xlib::XKeysymToKeycode(display, key as u64) as i32; + xlib::XGrabKey(display, keycode, modifiers, default_win, + owner_events as i32, pointer_mode, keyboard_mode); + } +} + +/// Get the next event from the xserver. This call will block until a +/// event spawns. +pub fn next_xevent(display: *mut Display) -> XEvent { + unsafe { + let mut last_event: XEvent = uninitialized(); + println!("xevent: getting next event!"); + xlib::XNextEvent(display, &mut last_event); + last_event + } +} + +// Window code. + +/// Get a list from the active windows. This doesn't generate a hierarchical +/// struture, so it may change on the future. +pub fn window_list(display: *mut Display) -> &'static [Window] { + let mut root: Window = 0; + let mut parent: Window = 0; + let mut children_count: u32 = 0; + unsafe { + let root_window = XDefaultRootWindow(display); + let mut children: *mut Window = uninitialized(); + XQueryTree(display, root_window, + &mut root, &mut parent, + &mut children, &mut children_count); + slice::from_raw_parts(children as *const Window, children_count as usize) + } +} + +fn screen_ratio(d: *mut Display) -> f32 { + let screen: xlib::Screen = unsafe { + let scrptr = xlib::XDefaultScreenOfDisplay(d); + ptr::read(scrptr) + }; + + screen.height as f32 / screen.width as f32 +} + +/// Get certain color from the display. +/// +/// # Example. +/// +/// This function can be called with a &str +/// +/// ``` +/// get_color(display, "red"); +/// ``` +fn get_color>>(display: *mut Display, color: T) -> u64{ + let screen_num = unsafe { xlib::XDefaultScreen(display) }; + let colormap = unsafe { xlib::XDefaultColormap(display, screen_num) }; + unsafe { + let mut xcolor: xlib::XColor = uninitialized(); + let cstr = CString::new(color).unwrap(); + xlib::XAllocNamedColor(display, colormap, cstr.as_ptr(), + &mut xcolor, &mut xcolor); + + xcolor.pixel + } +} + +/// Get the keysym for the given event. +pub fn lookup_keysym(ev: &mut XKeyEvent) -> xlib::KeySym { + unsafe { xlib::XLookupKeysym(ev, 0) } +} diff --git a/src/safe_x11/window.rs b/src/safe_x11/window.rs new file mode 100644 index 0000000..624fea4 --- /dev/null +++ b/src/safe_x11/window.rs @@ -0,0 +1,144 @@ +use x11::xlib; +use x11::xlib::{ + Window, + XWindowAttributes, + XGetWindowAttributes, + XDefaultScreenOfDisplay, + XMoveWindow, + + XSetInputFocus, + XSetWindowBorder, + + IsViewable, RevertToParent, CurrentTime, +}; + +use std::ptr; +use std::mem::uninitialized; +use std::cmp::{max}; + +use safe_x11::{screen_ratio,get_color}; + +/// Representation of [`xlib::Window`](../../x11/xlib/type.Window.html) +pub struct XWindow { + display: *mut xlib::Display, + /// xlib::Window that wraps this struct. + pub inner: Window, +} + +fn fixed_with_ratio(x: i32, ratio: f32) -> i32 { + let fx = x as f32; + let fixed = fx * ratio; + fixed as i32 +} + +impl XWindow { + /// Generate a reference to the window in certain display. This will return + /// `None` if the window is the root window. + pub fn new(d: *mut xlib::Display, w: Window) -> Option { + if w != 0 { + Some(XWindow { + display: d, + inner: w, + }) + } else { + None + } + } + + /// Set the border width To the window. + pub fn set_border_width(&self, size: u32) { + unsafe { xlib::XSetWindowBorderWidth(self.display, self.inner, size) }; + } + + pub fn set_boder_color>>(&self, color: T) { + let c = get_color(self.display, color); + unsafe { XSetWindowBorder(self.display, self.inner, c); } + } + + /// Raises a window. + pub fn raise(&self) { + unsafe { xlib::XRaiseWindow(self.display, self.inner); } + } + + /// Register input events on some [`xlib::Window`](../../x11/xlib/type.Window.html) + /// + /// # Example + /// + /// if we want to handle when a pointer enter into a window rectangle, we + /// should do something like: + /// + /// ``` + /// use x11::xlib; + /// + /// let display = open_display("").unwrap(); + /// // Raw window + /// let window = 0xc00022; + /// + /// window.select_input(xlib::EnterWindowMask); + /// ``` + pub fn select_input(&self, mask: i64) { + unsafe { xlib::XSelectInput(self.display, self.inner, mask); } + } + + /// Returns the window attributes from certain window. + pub fn attributes(&self) -> XWindowAttributes { + unsafe { + let attrptr: *mut XWindowAttributes = uninitialized(); + XGetWindowAttributes(self.display, self.inner, attrptr); + ptr::read(attrptr) + } + } + + /// Moves the window given an offset from where it is. + pub fn move_offset(&self, xoffset: i32, yoffset: i32) { + unsafe { + let mut attributes: XWindowAttributes = uninitialized(); + XGetWindowAttributes(self.display, self.inner, &mut attributes); + println!("Moving window({:x}), {} + {} = {}, {} + {} = {}", + self.inner as u64, + attributes.x, xoffset, attributes.x + xoffset, + attributes.y, yoffset, attributes.y + yoffset); + XMoveWindow(self.display, self.inner, + attributes.x + xoffset, + attributes.y + yoffset); + } + } + + /// Moves the window to a particular coord in the screen. + pub fn move_to(&self, x: i32, y: i32) -> Result<(), &str> { + let screen = unsafe { + let s = XDefaultScreenOfDisplay(self.display); + ptr::read(s) + }; + + if 0 > x && x <= screen.width || 0 > y && y <= screen.height { + Err("Cannot move the window outside the screen!") + } else { + unsafe { XMoveWindow(self.display, self.inner, x, y) }; + Ok(()) + } + } + + /// Resizes the window a fixed amount within the height and width. + pub fn resize(&self, w: i32, h: i32) { + let ratio = screen_ratio(self.display); + let attrs = self.attributes(); + unsafe { + let ww: u32 = max(1, (attrs.width + fixed_with_ratio(w, ratio)) as u32); + let wh: u32 = max(1, (attrs.height + fixed_with_ratio(h, ratio)) as u32); + xlib::XResizeWindow(self.display, self.inner, + ww, wh); + } + } + + /// Raise the focus of the window. + pub fn focus(&self) { + let attrs = self.attributes(); + if attrs.map_state == IsViewable { + unsafe { + XSetInputFocus(self.display, self.inner, + RevertToParent, CurrentTime) + }; + } + } +} -- cgit v1.2.3-70-g09d2