From 124e6878b1de561f4bc3fccea768203821c88469 Mon Sep 17 00:00:00 2001 From: tcmal Date: Sun, 9 Jun 2024 17:34:45 +0100 Subject: minor cleanup --- Cargo.lock | 56 ----------------------- Cargo.toml | 1 - src/clients.rs | 142 +++++++++++++++++++++++++++++++-------------------------- src/colours.rs | 6 +++ src/config.rs | 20 ++++++++ src/cursors.rs | 7 ++- src/error.rs | 50 ++++++++++++++++---- src/focus.rs | 24 ++++++---- src/keys.rs | 99 +++++++++++++++++++--------------------- src/main.rs | 7 ++- 10 files changed, 215 insertions(+), 197 deletions(-) create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index ccc89e2..0bb8497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,7 +12,6 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" name = "blow" version = "0.1.0" dependencies = [ - "thiserror", "xcb", "xkeysym", ] @@ -29,15 +28,6 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" -[[package]] -name = "proc-macro2" -version = "1.0.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" -dependencies = [ - "unicode-ident", -] - [[package]] name = "quick-xml" version = "0.30.0" @@ -47,52 +37,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "syn" -version = "2.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - [[package]] name = "xcb" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 6f87d3e..e0ebbcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -thiserror = "1.0.61" xcb = { version = "1.4.0", features = ["xinerama"] } xkeysym = "0.2.0" diff --git a/src/clients.rs b/src/clients.rs index 75ed38a..f04fc36 100644 --- a/src/clients.rs +++ b/src/clients.rs @@ -1,5 +1,6 @@ use std::cmp::min; +use crate::{config::BORDER_WIDTH, error::*, WM}; use xcb::{ x::{ ChangeWindowAttributes, ConfigWindow, ConfigureNotifyEvent, ConfigureRequestEvent, @@ -11,11 +12,8 @@ use xcb::{ BaseEvent, Connection, Extension, Xid, }; -use crate::{error::*, WM}; -const BORDER_WIDTH: u16 = 3; - impl WM<'_> { - /// Update the client state's recorded monitors and monitor sizes + /// Update the client recorded monitors and monitor sizes /// Returns true if any values changed, meaning windows should be re-tiled. pub(crate) fn update_geometry(&mut self) -> Result { let mut dirty = false; @@ -75,7 +73,7 @@ impl WM<'_> { Ok(()) } - /// Handle a configure request, by checking it's valid and performing it if so + /// Perform configure requests if we're happy with them, or they're for an unmanaged window. pub(crate) fn handle_configure_request(&mut self, e: ConfigureRequestEvent) -> Result<()> { if let Some(c) = self.clients.find_client_mut(e.window()) { // TODO: Allow changing some properties: @@ -102,7 +100,7 @@ impl WM<'_> { Ok(()) } - /// Handle a destroyed window, removing it from the client list and rearranging. + /// Removing destroyed windows from the client list and rearrange. pub(crate) fn handle_destroy_notify(&mut self, e: DestroyNotifyEvent) -> Result<()> { if self.clients.remove_client(e.window()).is_some() { self.clients.rearrange(self.conn); @@ -112,7 +110,7 @@ impl WM<'_> { Ok(()) } - /// Map a window on request, starting to manage it if needed. + /// Map a window, starting to manage it if needed. pub(crate) fn handle_map_request(&mut self, e: MapRequestEvent) -> Result<()> { // Ignore already managed windows if self.clients.find_client_mut(e.window()).is_some() { @@ -124,6 +122,7 @@ impl WM<'_> { .send_request(&GetWindowAttributes { window: e.window() }), )?; if attrs.override_redirect() { + // Something special, don't manage it just let it do its thing. return Ok(()); } @@ -135,7 +134,7 @@ impl WM<'_> { Ok(()) } - /// Handle a window being unmapped by updating its client state, or stop managing it. + /// When a window is unmapped, either stop managing it or update its state. pub(crate) fn handle_unmap_notify(&mut self, e: UnmapNotifyEvent) -> Result<()> { if self.clients.find_client_mut(e.window()).is_some() { if e.is_from_send_event() { @@ -143,7 +142,7 @@ impl WM<'_> { } else { self.clients.remove_client(e.window()); self.clients.rearrange(self.conn); - // TODO: 'disown' the window - unmange(c, 0) + // TODO: 'disown' the window - unmanage(c, 0) } } @@ -198,7 +197,7 @@ impl WM<'_> { // TODO: updatewmhints // XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); - c.select_input( + c.set_event_mask( conn, EventMask::ENTER_WINDOW | EventMask::FOCUS_CHANGE @@ -220,32 +219,17 @@ impl WM<'_> { pub struct ClientState { /// The current arranging function. /// This function is expected to ensure that all clients are the correct size, reconfigure them if needed, and map/unmap as needed. + /// The connection will be flushed after it is called. arrange: &'static dyn Fn(&mut MonitorInfo, &Connection), + + /// A client list for each monitor. mons: Vec, + /// Co-ordinates to the currently focused window. focused: (usize, usize), } -impl Default for ClientState { - fn default() -> Self { - Self { - arrange: &tile, - focused: (0, 0), - mons: vec![], - } - } -} - -impl std::fmt::Debug for ClientState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ClientState") - .field("focused", &self.focused) - .field("mons", &self.mons) - .finish() - } -} - -/// Info stored for a single monitor +/// Info stored for each monitor #[derive(Debug)] pub struct MonitorInfo { /// Clients attached to that monitor @@ -256,35 +240,22 @@ pub struct MonitorInfo { } impl MonitorInfo { - fn clients_tiled_mut(&mut self) -> impl Iterator { + /// Iterate over all tiled clients, returning a mutable reference to each. + pub fn clients_tiled_mut(&mut self) -> impl Iterator { // TODO: tag filtering, floating self.clients.iter_mut() } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct MonitorGeometry { - pub x_org: i16, - pub y_org: i16, - pub width: u16, - pub height: u16, -} - -impl From for MonitorGeometry { - fn from(value: ScreenInfo) -> Self { - Self { - x_org: value.x_org, - y_org: value.y_org, - width: value.width, - height: value.height, - } +impl ClientState { + /// Get the amount of monitors this state is currently aware of + pub fn monitor_count(&self) -> usize { + self.mons.len() } -} -impl ClientState { - /// Set the new amount of screens, moving clients away if necessary + /// Set the new amount of screens, without unmanaging any clients. pub fn truncate_screens(&mut self, new_size: usize) { - // hack: double borrow stuff + // hack: working around double borrow stuff let mut moved_clients = vec![]; for old in self.mons.drain(new_size - self.mons.len()..self.mons.len()) { moved_clients.extend(old.clients.into_iter()); @@ -292,7 +263,7 @@ impl ClientState { self.mons[0].clients.extend(moved_clients); } - /// Set the info for the given screen, resizing the monitor list if necessary. + /// Set the given screen's geometry, resizing the monitor list if necessary. /// Returns true if the new info is different from the old one. pub fn set_monitor_geometry(&mut self, i: usize, info: MonitorGeometry) -> bool { while i >= self.mons.len() { @@ -304,26 +275,20 @@ impl ClientState { dirty } - /// Get the amount of monitors this state is currently aware of - pub fn monitor_count(&self) -> usize { - self.mons.len() - } - /// Find the [`Client`] corresponding to the given window pub fn find_client_mut(&mut self, window: Window) -> Option<&mut Client> { - self.mons - .iter_mut() - .flat_map(|mi| mi.clients.iter_mut()) - .find(|c| c.window == window) + let (mon, i) = self.find_client_pos(window)?; + Some(&mut self.mons[mon].clients[i]) } /// Find the position of the client with the given window, returning (monitor, index) - pub(crate) fn find_client_pos(&mut self, window: Window) -> Option<(usize, usize)> { + pub fn find_client_pos(&mut self, window: Window) -> Option<(usize, usize)> { for (pos_mon, mon) in self.mons.iter_mut().enumerate() { if let Some(pos) = mon.clients.iter().position(|c| c.window == window) { return Some((pos_mon, pos)); } } + None } @@ -384,6 +349,7 @@ impl ClientState { (self.arrange)(&mut self.mons[mon], conn); } + /// Get the currently focused monitor fn focused_mon(&self) -> usize { self.focused.0 } @@ -404,6 +370,7 @@ pub struct Client { } impl Client { + /// Send a configure configure notify event with the current geometry. /// This function does not check for success, so conn.flush() should be called after. fn configure_notify(&self, conn: &Connection) { conn.send_request(&SendEvent { @@ -425,6 +392,7 @@ impl Client { } /// Set this client's geometry, also updating the X11 window if needed. + /// This function does not check for success, so conn.flush() should be called after. fn set_geom( &mut self, conn: &Connection, @@ -458,8 +426,8 @@ impl Client { }); } - /// Set the border of the X11 window to the given value. - /// This sends a request but doesn't wait for the response. + /// Set the border colour of the X11 window to the given value (see `crate::colours::Colours`) + /// This function does not check for success, so conn.flush() should be called after. pub fn set_border(&self, conn: &Connection, colour: u32) { conn.send_request(&ChangeWindowAttributes { window: self.window(), @@ -468,6 +436,7 @@ impl Client { } /// Ensure this client is currently mapped / visible + /// This function does not check for success, so conn.flush() should be called after. pub fn ensure_mapped(&mut self, conn: &Connection) { if !self.mapped { conn.send_request(&MapWindow { @@ -478,6 +447,7 @@ impl Client { } /// Ensure this client is currently unmapped / invisible + /// This function does not check for success, so conn.flush() should be called after. pub fn ensure_unmapped(&mut self, conn: &Connection) { if self.mapped { conn.send_request(&UnmapWindow { @@ -492,7 +462,9 @@ impl Client { self.window } - fn select_input(&self, conn: &Connection, event_mask: EventMask) { + /// Set the event mask for this window + /// This function does not check for success, so conn.flush() should be called after. + fn set_event_mask(&self, conn: &Connection, event_mask: EventMask) { conn.send_request(&ChangeWindowAttributes { window: self.window(), value_list: &[Cw::EventMask(event_mask)], @@ -514,6 +486,7 @@ impl Default for MonitorInfo { } } +/// A simple tiling function fn tile(mon: &mut MonitorInfo, conn: &Connection) { if mon.clients.is_empty() { return; @@ -569,3 +542,42 @@ fn tile(mon: &mut MonitorInfo, conn: &Connection) { c.ensure_mapped(conn); } } + +impl Default for ClientState { + fn default() -> Self { + Self { + arrange: &tile, + focused: (0, 0), + mons: vec![], + } + } +} + +impl std::fmt::Debug for ClientState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ClientState") + .field("focused", &self.focused) + .field("mons", &self.mons) + .finish() + } +} + +/// Info on the monitor's geometry. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MonitorGeometry { + pub x_org: i16, + pub y_org: i16, + pub width: u16, + pub height: u16, +} + +impl From for MonitorGeometry { + fn from(value: ScreenInfo) -> Self { + Self { + x_org: value.x_org, + y_org: value.y_org, + width: value.width, + height: value.height, + } + } +} diff --git a/src/colours.rs b/src/colours.rs index 6f862c3..bab92b6 100644 --- a/src/colours.rs +++ b/src/colours.rs @@ -4,14 +4,18 @@ use xcb::{ Connection, }; +/// Caches colours in an X11 color map. pub struct Colours { + #[allow(unused)] // Make sure the colour map we're using doesn't go anywhere cmap: Colormap, border_normal: u32, border_focused: u32, } impl Colours { + /// Load the colours into the given colour map pub fn new_with(conn: &Connection, cmap: Colormap) -> Result { + // TODO: Move these colours out to config.rs let (border_normal, border_focused) = ( conn.wait_for_reply(conn.send_request(&AllocColor { cmap, @@ -34,10 +38,12 @@ impl Colours { }) } + /// Get the pixel ID of the colour for an unfocused window's border. pub fn border_normal(&self) -> u32 { self.border_normal } + /// Get the pixel ID of the colour for a focused window's border. pub fn border_focused(&self) -> u32 { self.border_focused } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..c36a313 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,20 @@ +use std::process::Command; + +use xcb::x::ModMask; +use xkeysym::Keysym; + +use crate::keys::{Keybind, Keybinds}; + +pub const BORDER_WIDTH: u16 = 3; + +/// The keybinds to use. +pub const KEYBINDS: Keybinds = Keybinds(&[Keybind { + modifiers: ModMask::CONTROL, + key: Keysym::t, + action: &|_| { + // TODO: disown this process, probably using another way to spawn commands + if let Err(e) = Command::new("xterm").spawn() { + dbg!(e); + } + }, +}]); diff --git a/src/cursors.rs b/src/cursors.rs index 3153288..0a4e041 100644 --- a/src/cursors.rs +++ b/src/cursors.rs @@ -13,8 +13,7 @@ pub struct Cursors { #[allow(unused)] // Needs to be kept around since the cursors depend on it font: Font, - pub normal: Cursor, - // TODO: ... + normal: Cursor, } impl Cursors { @@ -53,4 +52,8 @@ impl Cursors { Ok(cid) } + + pub fn normal(&self) -> Cursor { + self.normal + } } diff --git a/src/error.rs b/src/error.rs index 3086204..dcbcfe9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,23 +1,53 @@ -use thiserror::Error; +use std::fmt::{Display, Formatter}; /// The Result type used throughout pub type Result = std::result::Result; /// All errors that can be encountered when running -#[derive(Debug, Error)] +#[derive(Debug)] pub enum Error { - #[error("xcb returned screen that doesn't exist")] + // #[error("xcb returned a screen that doesn't exist")] NoSuchScreen, - #[error("other wm is running")] + // #[error("another wm is running")] OtherWMRunning, - #[error("generic xcb error: {0}")] - Xcb(#[from] xcb::Error), + // #[error("generic xcb error: {0}")] + Xcb(xcb::Error), - #[error("connection error: {0}")] - Connection(#[from] xcb::ConnError), + // #[error("connection error: {0}")] + Connection(xcb::ConnError), - #[error("protocol error: {0}")] - Protocol(#[from] xcb::ProtocolError), + // #[error("protocol error: {0}")] + Protocol(xcb::ProtocolError), +} + +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Error::NoSuchScreen => write!(f, "xcb returned a screen that doesn't exist"), + Error::OtherWMRunning => write!(f, "another window manager is running"), + Error::Xcb(e) => write!(f, "generic xcb error: {}", e), + Error::Connection(e) => write!(f, "connection error: {}", e), + Error::Protocol(e) => write!(f, "protocol error: {}", e), + } + } +} + +impl From for Error { + fn from(e: xcb::Error) -> Self { + Self::Xcb(e) + } +} +impl From for Error { + fn from(e: xcb::ConnError) -> Self { + Self::Connection(e) + } +} +impl From for Error { + fn from(e: xcb::ProtocolError) -> Self { + Self::Protocol(e) + } } diff --git a/src/focus.rs b/src/focus.rs index a237e24..bee148d 100644 --- a/src/focus.rs +++ b/src/focus.rs @@ -1,12 +1,13 @@ use xcb::x::{ - self, ChangeWindowAttributes, Cw, EnterNotifyEvent, FocusInEvent, InputFocus, NotifyDetail, - NotifyMode, SetInputFocus, Window, + self, EnterNotifyEvent, FocusInEvent, InputFocus, NotifyDetail, NotifyMode, SetInputFocus, + Window, }; -use crate::{clients::Client, error::*, WM}; +use crate::{error::*, WM}; impl WM<'_> { - pub(crate) fn handle_enter_notify(&mut self, e: EnterNotifyEvent) -> Result<()> { + /// When a new window is entered, focus it. + pub fn handle_enter_notify(&mut self, e: EnterNotifyEvent) -> Result<()> { if (e.mode() != NotifyMode::Normal || e.detail() == NotifyDetail::Inferior) && e.event() != self.root { @@ -19,7 +20,8 @@ impl WM<'_> { Ok(()) } - pub(crate) fn handle_focus_in(&mut self, e: FocusInEvent) -> Result<()> { + /// When a new window requests focus, focus it. + pub fn handle_focus_in(&mut self, e: FocusInEvent) -> Result<()> { if self .clients .focused_mut() @@ -27,12 +29,14 @@ impl WM<'_> { .unwrap_or(true) { self.focus_window(e.event()); + self.conn.flush()?; } + Ok(()) } - /// Attempt to focus the given window, falling back to the root if the window isn't our client. - /// This function sends multiple requests without checking them, so conn.flush() should be called after. + /// Attempt to focus the given window, even if it isn't managed. + /// This function sends multiple requests without checking them, so `conn.flush()` should be called after. pub fn focus_window(&mut self, window: Window) { if let Some((mon, i)) = self.clients.find_client_pos(window) { self.refocus(mon, i); @@ -46,8 +50,8 @@ impl WM<'_> { } } - /// Refocus on the client with the given co-ordinates, setting the X11 properties as required. - /// This function sends multiple requests without checking them, so conn.flush() should be called after. + /// Refocus on the client with the given co-ordinates, setting X11 properties as required. + /// This function sends multiple requests without checking them, so `conn.flush()` should be called after. pub fn refocus(&mut self, mon: usize, i: usize) { self.unfocus(); if let Some(new) = self.clients.set_focused(mon, i) { @@ -60,6 +64,8 @@ impl WM<'_> { } } + /// Unfocus the currently focused window, if it exists. + /// This function sends multiple requests without checking them, so `conn.flush()` should be called after. pub fn unfocus(&mut self) { if let Some(old) = self.clients.focused_mut() { old.set_border(self.conn, self.colours.border_normal()); diff --git a/src/keys.rs b/src/keys.rs index 07dfb40..21217b0 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,6 +1,6 @@ -use std::process::Command; +use std::ops::RangeInclusive; -use crate::{error::*, WM}; +use crate::{config::KEYBINDS, error::*, WM}; use xcb::{ x::{ GetKeyboardMapping, GetKeyboardMappingReply, GetModifierMapping, GrabKey, GrabMode, @@ -11,8 +11,8 @@ use xcb::{ 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<()> { + /// Dispatch the given keypress event according to [`self::KEYBINDS`] + pub fn handle_key_press(&mut self, e: KeyPressEvent) -> Result<()> { let Some(sym) = self.keyboard_state.keycode_to_keysym(e.detail().into(), 0) else { return Ok(()); // probably not bound }; @@ -27,8 +27,8 @@ impl WM<'_> { Ok(()) } - /// Handles a mapping notify event by updating our keyboard setup if needed. - pub(crate) fn handle_mapping_notify(&mut self, e: MappingNotifyEvent) -> Result<()> { + /// Update our keyboard info when the mapping changes. + pub fn handle_mapping_notify(&mut self, e: MappingNotifyEvent) -> Result<()> { if e.request() == Mapping::Keyboard { self.grab_keys()?; } @@ -36,8 +36,8 @@ impl WM<'_> { Ok(()) } - /// Grab all keys specified by [`self::KEYBINDS`], ensuring we get events for them. - pub(crate) fn grab_keys(&mut self) -> Result<()> { + /// Refresh our keyboard info, and ensure that we get events for bound keys. + pub fn grab_keys(&mut self) -> Result<()> { // Refresh keyboard state self.keyboard_state = KeyboardInfo::new_with(self.conn)?; @@ -81,9 +81,14 @@ impl WM<'_> { /// Cached information about our keyboard layout. pub struct KeyboardInfo { - min_keycode: RawKeyCode, - max_keycode: RawKeyCode, + /// The range of keycodes used + keycodes: RangeInclusive, + + /// The ModMask corresponding to NumLock. + /// This varies sometimes, and we need to know to ignore it. numlock_mask: ModMask, + + /// The mapping from keycodes to (multiple) key symbols mapping: GetKeyboardMappingReply, } @@ -99,20 +104,22 @@ impl KeyboardInfo { }))?; let mut this = Self { - min_keycode: min_keycode as u32, - max_keycode: max_keycode as u32, + keycodes: min_keycode as RawKeyCode..=max_keycode as RawKeyCode, numlock_mask: ModMask::empty(), mapping, }; - let numlock_keycode = this.keysym_to_keycode(Keysym::Num_Lock)?; + let Some(numlock_keycode) = this.keysym_to_keycode(Keysym::Num_Lock) else { + // No numlock button, so no modmask for numlock + return Ok(this); + }; 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 mod_map.keycodes()[i * keypermod + j] as u32 == numlock_keycode.raw() { this.numlock_mask = - ModMask::from_bits(1 << i).expect("x11 has unrecognised modifier"); + ModMask::from_bits(1 << i).expect("x11 returned unrecognised modifier"); } } } @@ -120,18 +127,16 @@ impl KeyboardInfo { Ok(this) } - /// Get the modifier mask being used for numlock, which varies. + /// Get the modifier mask being used for numlock 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 + '_ { + pub fn iter_keycodes_keysyms(&self) -> impl Iterator + '_ { (0..self.mapping.keysyms_per_keycode()) - .flat_map(|shift| { - (self.min_keycode..self.max_keycode).map(move |keycode| (shift, keycode)) - }) + .flat_map(|shift| self.keycodes.clone().map(move |keycode| (shift, keycode))) .flat_map(|(shift, keycode)| -> Option<_> { Some(( keycode.into(), @@ -140,21 +145,21 @@ impl KeyboardInfo { }) } - /// Lookup the first keycode which has the given keysym - pub(crate) fn keysym_to_keycode(&self, keysym: Keysym) -> Result { + /// Lookup the first keycode which has the given keysym in any column + pub(crate) fn keysym_to_keycode(&self, keysym: Keysym) -> Option { 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) + for keycode in self.keycodes.clone() { + if self.mapping.keysyms()[(keycode as usize - *self.keycodes.start() as usize) * self.mapping.keysyms_per_keycode() as usize + shift as usize] == keysym.raw() { - return Ok(keycode.into()); + return Some(keycode.into()); } } } - Ok(KeyCode::new(0)) + None } /// Lookup the keysym in the given column for the given keycode @@ -162,43 +167,22 @@ impl KeyboardInfo { xkeysym::keysym( keycode, col, - self.min_keycode.into(), + (*self.keycodes.start()).into(), self.mapping.keysyms_per_keycode(), self.mapping.keysyms(), ) } } -/// A bound key +/// A key bound to some action pub struct Keybind { - modifiers: ModMask, - key: Keysym, - action: &'static dyn Fn(&mut WM<'_>), -} - -impl std::fmt::Debug for Keybind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Keybind") - .field("modifiers", &self.modifiers) - .field("key", &self.key) - .finish() - } + pub modifiers: ModMask, + pub key: Keysym, + pub action: &'static dyn Fn(&mut WM<'_>), } -/// 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::t, - action: &|_| { - // TODO: disown this process, probably using another way to spawn commands - if let Err(e) = Command::new("xterm").spawn() { - dbg!(e); - } - }, -}]); +/// A set of keybinds. Currently, there is only one instance of this defined statically: [`crate::config::KEYBINDS`]. +pub struct Keybinds(pub &'static [Keybind]); impl Keybinds { /// Get an iterator over all bound keys @@ -216,3 +200,12 @@ impl Keybinds { } } } + +impl std::fmt::Debug for Keybind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Keybind") + .field("modifiers", &self.modifiers) + .field("key", &self.key) + .finish() + } +} diff --git a/src/main.rs b/src/main.rs index e5ec003..6ac9dce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +//! A lightweight X11 window manager, inspired by dwm. + use atoms::InternedAtoms; use clients::ClientState; use colours::Colours; @@ -12,6 +14,7 @@ use xcb::{ mod atoms; mod clients; mod colours; +mod config; mod cursors; mod error; mod focus; @@ -93,6 +96,7 @@ impl WM<'_> { /// Set the correct properties on the root window fn setup_root(&mut self) -> Result<()> { // TODO: Set EHWM properties on root window + self.conn .check_request(self.conn.send_request_checked(&ChangeWindowAttributes { window: self.root, @@ -106,13 +110,14 @@ impl WM<'_> { | x::EventMask::STRUCTURE_NOTIFY | x::EventMask::PROPERTY_CHANGE, ), - x::Cw::Cursor(self.cursors.normal), + x::Cw::Cursor(self.cursors.normal()), ], }))?; self.grab_keys()?; // TODO: reset focus + self.unfocus(); Ok(()) } -- cgit v1.2.3