summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-06-05 17:25:47 +0100
committertcmal <me@aria.rip>2024-06-05 17:25:47 +0100
commit8b3f379c3b3216485d11787bcd63a56acd51ee58 (patch)
treec57364b1a9e6c4417987cda5f3d263f1682e9fce
parent14e708041d32f2a2d3ceaf057181b4ae05ef851a (diff)
add most keybind functionality (not yet running things)
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml1
-rw-r--r--src/keys.rs207
-rw-r--r--src/main.rs7
4 files changed, 155 insertions, 67 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0f1ca2f..ccc89e2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 76ab7d6..6f87d3e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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