diff --git a/Cargo.toml b/Cargo.toml index f189bb7..c5f0815 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +nix = "0.19.1" x11 = {version = "2.18.2", features = ["xlib"] } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e8a31cd..ede8d05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,15 +3,23 @@ use x11::xlib; use std::ptr::{null, null_mut}; use std::sync::Arc; use std::ffi::CString; -use std::io::{Result /*, Error, ErrorKind */}; +use std::io::{Result, Error, ErrorKind}; use std::sync::atomic::{AtomicPtr, Ordering}; +use x11::xlib::{ButtonPressMask, ButtonReleaseMask, PointerMotionMask, + GrabModeAsync, XEvent}; + +use x11::xlib::{LockMask, ShiftMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask}; + +use nix::unistd::{fork, ForkResult, close, setsid, execvp}; + type Display = Arc>; -#[derive(Debug)] struct XlibState { display: Display, + //buttons: Vec<(u32, u32, Box)>, + keys: Vec<(i32, u32, Box)>, } impl XlibState { @@ -23,6 +31,7 @@ impl XlibState { Ok(Self { display, + keys: vec![], }) } @@ -34,6 +43,53 @@ impl XlibState { unsafe { xlib::XDefaultRootWindow(self.dpy()) } } + fn add_key>(mut self, key: S, mask: u32, handler: Box) + -> Self { + let keycode = self.keycode(key); + self.keys.push((keycode, mask, Box::new(handler))); + + unsafe { + xlib::XGrabKey(self.dpy(), + keycode, + mask, + self.root(), + 1 /* true */, + GrabModeAsync, + GrabModeAsync); + } + + self + } + + // spawn a new process / calls execvp + pub fn spawn(&self, command: T, args: &[T]) -> Result<()> { + let fd = unsafe {xlib::XConnectionNumber(self.dpy())}; + + match unsafe { fork() } { + Ok(ForkResult::Parent{..}) => {Ok(())}, + Ok(ForkResult::Child) => { + // i dont think i want to exit this block without closing the program, + // so unwrap everything + + close(fd).or_else(|_| Err("failed to close x connection")).unwrap(); + setsid().ok().ok_or("failed to setsid").unwrap(); + + let c_cmd = CString::new(command.to_string()).unwrap(); + + let c_args: Vec<_> = args.iter() + .map(|s| CString::new(s.to_string()).unwrap()) + .collect(); + + execvp(&c_cmd, &c_args.iter().map(|s| s.as_c_str()).collect::>()) + .or(Err("failed to execvp()")).unwrap(); + + eprintln!("execvp({}) failed.", c_cmd.to_str().unwrap()); + std::process::exit(0); + }, + Err(_) => {Err(Error::new(ErrorKind::Other, "failed to fork."))}, + } + } + fn keycode>(&self, string: S) -> i32 { let c_string = CString::new(string.into()).unwrap(); unsafe { @@ -41,15 +97,50 @@ impl XlibState { xlib::XKeysymToKeycode(self.dpy(), keysym) as i32 } } + + fn numlock_mask(&self) -> u32 { + unsafe { + let modmap = xlib::XGetModifierMapping(self.dpy()); + let max_keypermod = (*modmap).max_keypermod; + + for i in 0..8 { + for j in 0..max_keypermod { + if *(*modmap).modifiermap.offset((i * max_keypermod + j) as isize) == + xlib::XKeysymToKeycode(self.dpy(), x11::keysym::XK_Num_Lock as u64) { + return 1 << i; + } + } + } + } + + 0 + } + + #[allow(non_snake_case)] + fn clean_mask(&self) -> u32 { + !(self.numlock_mask() | LockMask) + //& (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask) + } } -use x11::xlib::{ButtonPressMask, ButtonReleaseMask, PointerMotionMask, GrabModeAsync, Mod1Mask}; fn main() -> Result<()> { println!("Hello, world!"); let state = XlibState::new()?; + let state = + state.add_key("T", Mod1Mask, Box::new(|state, _| { + let _ = state.spawn("xterm", &[]); + })) + .add_key("F1", Mod1Mask, Box::new(|state, event| { + unsafe { + if event.key.subwindow != 0 { + xlib::XRaiseWindow(state.dpy(), event.key.subwindow); + } + } + })); + unsafe { xlib::XGrabKey(state.dpy(), state.keycode("F1"), @@ -86,8 +177,19 @@ fn main() -> Result<()> { let mut event: xlib::XEvent = std::mem::MaybeUninit::uninit().assume_init(); xlib::XNextEvent(state.dpy(), &mut event); - if event.get_type() == xlib::KeyPress && event.key.subwindow != 0 { - xlib::XRaiseWindow(state.dpy(), event.key.subwindow); + // run keypress handlers + if event.get_type() == xlib::KeyPress { + + // cache clean mask, that way numlock_mask doesnt get called for every cmp + let clean_mask = state.clean_mask(); + + for (key, mask, handler) in state.keys.iter() { + // check if key and mask with any numlock state fit + if event.key.keycode == *key as u32 && + event.key.state & clean_mask == *mask & clean_mask { + handler(&state, &event); + } + } } else if event.get_type() == xlib::ButtonPress && event.button.subwindow != 0 { xlib::XGetWindowAttributes(state.dpy(), event.button.subwindow, &mut attr);