diff options
author | tcmal <me@aria.rip> | 2024-06-05 17:25:47 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-06-05 17:25:47 +0100 |
commit | 8b3f379c3b3216485d11787bcd63a56acd51ee58 (patch) | |
tree | c57364b1a9e6c4417987cda5f3d263f1682e9fce | |
parent | 14e708041d32f2a2d3ceaf057181b4ae05ef851a (diff) |
add most keybind functionality (not yet running things)
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/keys.rs | 207 | ||||
-rw-r--r-- | src/main.rs | 7 |
4 files changed, 155 insertions, 67 deletions
@@ -14,6 +14,7 @@ version = "0.1.0" dependencies = [ "thiserror", "xcb", + "xkeysym", ] [[package]] @@ -102,3 +103,9 @@ dependencies = [ "libc", "quick-xml", ] + +[[package]] +name = "xkeysym" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" @@ -6,3 +6,4 @@ edition = "2021" [dependencies] thiserror = "1.0.61" xcb = { version = "1.4.0", features = ["xinerama"] } +xkeysym = "0.2.0" diff --git a/src/keys.rs b/src/keys.rs index 08dd056..7e3296f 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,66 +1,65 @@ use crate::{error::*, WM}; -use xcb::x::{ - GetKeyboardMapping, GetModifierMapping, GrabKey, GrabMode, KeyPressEvent, Keycode, Keysym, - Mapping, MappingNotifyEvent, ModMask, UngrabKey, GRAB_ANY, +use xcb::{ + x::{ + GetKeyboardMapping, GetKeyboardMappingReply, GetModifierMapping, GrabKey, GrabMode, + KeyPressEvent, Mapping, MappingNotifyEvent, ModMask, UngrabKey, GRAB_ANY, + }, + Connection, }; - -// https://github.com/D-Programming-Deimos/libX11/blob/master/c/X11/keysymdef.h -const KEYSYM_NUMLOCK: Keysym = 0xff7f; +use xkeysym::{KeyCode, Keysym, RawKeyCode}; impl WM<'_> { + /// Handles a key press event by dispatching according to [`self::KEYBINDS`] pub(crate) fn handle_key_press(&mut self, e: KeyPressEvent) -> Result<()> { - dbg!(e); + let Some(sym) = self.keyboard_state.keycode_to_keysym(e.detail().into(), 0) else { + return Ok(()); // probably not bound + }; + + KEYBINDS.dispatch( + sym, + ModMask::from_bits_truncate(e.state().bits()) + .difference(self.keyboard_state.numlock_mask() | ModMask::LOCK), + ); Ok(()) } + /// Handles a mapping notify event by updating our keyboard setup if needed. pub(crate) fn handle_mapping_notify(&mut self, e: MappingNotifyEvent) -> Result<()> { if e.request() == Mapping::Keyboard { - self.grabkeys()?; + self.grab_keys()?; } Ok(()) } - pub(crate) fn grabkeys(&self) -> Result<()> { - let numlock_mask = self.get_numlock_mask()?; + /// Grab all keys specified by [`self::KEYBINDS`], ensuring we get events for them. + pub(crate) fn grab_keys(&mut self) -> Result<()> { + // Refresh keyboard state + self.keyboard_state = KeyboardState::new_with(&self.conn)?; - // ungrab all keys + // Ungrab all keys self.conn.send_request(&UngrabKey { key: GRAB_ANY, grab_window: self.root, modifiers: ModMask::ANY, }); - // go through keymap and grab what we need - let (min_keycode, max_keycode) = ( - self.conn.get_setup().min_keycode(), - self.conn.get_setup().max_keycode(), - ); - let mapping = self - .conn - .wait_for_reply(self.conn.send_request(&GetKeyboardMapping { - first_keycode: min_keycode, - count: max_keycode - min_keycode + 1, - }))?; - - for shift in 0..mapping.keysyms_per_keycode() { - for keycode in min_keycode..max_keycode { - let keysym = mapping.keysyms() - [(shift as usize * mapping.keysyms_per_keycode() as usize + keycode as usize)]; - if true { - // todo: is bound + // Bind all of the keycodes which have keysyms we see in our binds. + for (keycode, keysym) in self.keyboard_state.iter_keycodes_keysyms() { + for bind in KEYBINDS.binds() { + if bind.key == keysym { // grab key with any combination of modifiers for modmask in [ ModMask::empty(), ModMask::LOCK, - numlock_mask, - numlock_mask | ModMask::LOCK, + self.keyboard_state.numlock_mask(), + self.keyboard_state.numlock_mask() | ModMask::LOCK, ] { self.conn.send_request(&GrabKey { grab_window: self.root, - key: keycode, - modifiers: modmask, + key: keycode.raw() as u8, + modifiers: bind.modifiers | modmask, owner_events: true, pointer_mode: GrabMode::Async, keyboard_mode: GrabMode::Async, @@ -70,56 +69,134 @@ impl WM<'_> { } } - // ensure all requests succeeded + // Ensure all requests succeeded self.conn.flush()?; Ok(()) } +} - pub(crate) fn get_numlock_mask(&self) -> Result<ModMask> { - let map = self - .conn - .wait_for_reply(self.conn.send_request(&GetModifierMapping {}))?; - let numlock_keycode = self.keysym_to_keycode(KEYSYM_NUMLOCK)?; +/// Cached information about our keyboard layout. +pub struct KeyboardState { + min_keycode: RawKeyCode, + max_keycode: RawKeyCode, + numlock_mask: ModMask, + mapping: GetKeyboardMappingReply, +} - let keypermod = map.keycodes().len() / 8; +impl KeyboardState { + /// Query information about the keyboard layout from the given connection. + pub fn new_with(conn: &Connection) -> Result<Self> { + let min_keycode = conn.get_setup().min_keycode(); + let max_keycode = conn.get_setup().max_keycode(); + + let mapping = conn.wait_for_reply(conn.send_request(&GetKeyboardMapping { + first_keycode: min_keycode, + count: max_keycode - min_keycode + 1, + }))?; + + let mut this = Self { + min_keycode: min_keycode as u32, + max_keycode: max_keycode as u32, + numlock_mask: ModMask::empty(), + mapping, + }; + + let numlock_keycode = this.keysym_to_keycode(Keysym::Num_Lock)?; + let mod_map = conn.wait_for_reply(conn.send_request(&GetModifierMapping {}))?; + let keypermod = mod_map.keycodes().len() / 8; for i in 0..8 { for j in 0..keypermod { - if map.keycodes()[(i * keypermod + j) as usize] == numlock_keycode { - return Ok(ModMask::from_bits(1 << i).expect("x11 has unrecognised modifier")); + if mod_map.keycodes()[(i * keypermod + j) as usize] as u32 == numlock_keycode.raw() + { + this.numlock_mask = + ModMask::from_bits(1 << i).expect("x11 has unrecognised modifier"); } } } - // todo: is this ok? - return Ok(ModMask::empty()); + Ok(this) } - pub(crate) fn keysym_to_keycode(&self, keysym: Keysym) -> Result<Keycode> { - let (min_keycode, max_keycode) = ( - self.conn.get_setup().min_keycode(), - self.conn.get_setup().max_keycode(), - ); - let mapping = self - .conn - .wait_for_reply(self.conn.send_request(&GetKeyboardMapping { - first_keycode: min_keycode, - count: max_keycode - min_keycode + 1, - }))?; - - // todo: apparently there's an optimisation here https://github.com/ArcherPergande/xcbkeys/blob/main/xcbkeys.h - for shift in 0..mapping.keysyms_per_keycode() { - for keycode in min_keycode..max_keycode { - if mapping.keysyms() - [keycode as usize * mapping.keysyms_per_keycode() as usize + shift as usize] - == keysym + /// Get the modifier mask being used for numlock, which varies. + pub fn numlock_mask(&self) -> ModMask { + self.numlock_mask + } + + /// Iterate over all keycodes and their bound keysyms. + /// This is likely to contain duplicate pairs. + pub(crate) fn iter_keycodes_keysyms(&self) -> impl Iterator<Item = (KeyCode, Keysym)> + '_ { + (0..self.mapping.keysyms_per_keycode()) + .flat_map(|shift| { + (self.min_keycode..self.max_keycode).map(move |keycode| (shift, keycode)) + }) + .flat_map(|(shift, keycode)| -> Option<_> { + Some(( + keycode.into(), + self.keycode_to_keysym(keycode.into(), shift)?, + )) + }) + } + + /// Lookup the first keycode which has the given keysym + pub(crate) fn keysym_to_keycode(&self, keysym: Keysym) -> Result<KeyCode> { + for shift in 0..self.mapping.keysyms_per_keycode() { + for keycode in self.min_keycode..self.max_keycode { + if self.mapping.keysyms()[(keycode as usize - self.min_keycode as usize) + * self.mapping.keysyms_per_keycode() as usize + + shift as usize] + == keysym.raw() { - return Ok(keycode); + return Ok(keycode.into()); } } } - // todo: is this right? - return Ok(0); + return Ok(KeyCode::new(0)); + } + + /// Lookup the keysym in the given column for the given keycode + pub fn keycode_to_keysym(&self, keycode: KeyCode, col: u8) -> Option<Keysym> { + xkeysym::keysym( + keycode, + col, + self.min_keycode.into(), + self.mapping.keysyms_per_keycode(), + self.mapping.keysyms(), + ) + } +} + +/// A bound key +#[derive(Debug)] +pub struct Keybind { + modifiers: ModMask, + key: Keysym, + // TODO: add action +} + +/// A set of keybinds. Currently, there is only one instance of this defined statically: [`self::KEYBINDS`]. +pub struct Keybinds(&'static [Keybind]); + +/// The keybinds to use. +const KEYBINDS: Keybinds = Keybinds(&[Keybind { + modifiers: ModMask::CONTROL, + key: Keysym::a, +}]); + +impl Keybinds { + /// Get an iterator over all bound keys + pub fn binds(&self) -> impl Iterator<Item = &Keybind> { + self.0.iter() + } + + /// Attempt to run the action for the matching keybind, if it exists. + pub fn dispatch(&self, key: Keysym, modifiers: ModMask) { + if let Some(bind) = self + .binds() + .find(|b| b.key == key && modifiers.contains(b.modifiers)) + { + dbg!(bind); // TODO: run the bind + } } } diff --git a/src/main.rs b/src/main.rs index f61f422..f99e19b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use atoms::InternedAtoms; use clients::ClientList; use cursors::Cursors; use error::*; +use keys::KeyboardState; use xcb::{ x::{self, ChangeWindowAttributes, PropertyNotifyEvent, Window}, Connection, Event, Extension, @@ -38,6 +39,7 @@ struct WM<'a> { cursors: Cursors, atoms: InternedAtoms, + keyboard_state: KeyboardState, } impl WM<'_> { @@ -62,6 +64,7 @@ impl WM<'_> { Ok(WM { atoms: InternedAtoms::new_with(conn)?, cursors: Cursors::new_with(conn)?, + keyboard_state: KeyboardState::new_with(conn)?, clients: Default::default(), conn, screen_num, @@ -69,7 +72,7 @@ impl WM<'_> { }) } - fn setup_root(&self) -> Result<()> { + fn setup_root(&mut self) -> Result<()> { // TODO: set wm properties self.conn .check_request(self.conn.send_request_checked(&ChangeWindowAttributes { @@ -89,7 +92,7 @@ impl WM<'_> { ], }))?; - self.grabkeys()?; + self.grab_keys()?; // TODO: reset focus |