summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock56
-rw-r--r--Cargo.toml1
-rw-r--r--src/clients.rs142
-rw-r--r--src/colours.rs6
-rw-r--r--src/config.rs20
-rw-r--r--src/cursors.rs7
-rw-r--r--src/error.rs50
-rw-r--r--src/focus.rs24
-rw-r--r--src/keys.rs99
-rw-r--r--src/main.rs7
10 files changed, 215 insertions, 197 deletions
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",
]
@@ -30,15 +29,6 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -48,52 +38,6 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
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<bool> {
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<MonitorInfo>,
+ /// 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<Item = &mut Client> {
+ /// Iterate over all tiled clients, returning a mutable reference to each.
+ pub fn clients_tiled_mut(&mut self) -> impl Iterator<Item = &mut Client> {
// 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<ScreenInfo> 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<ScreenInfo> 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<Self> {
+ // 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<T, E = Error> = std::result::Result<T, E>;
/// 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<xcb::Error> for Error {
+ fn from(e: xcb::Error) -> Self {
+ Self::Xcb(e)
+ }
+}
+impl From<xcb::ConnError> for Error {
+ fn from(e: xcb::ConnError) -> Self {
+ Self::Connection(e)
+ }
+}
+impl From<xcb::ProtocolError> 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<RawKeyCode>,
+
+ /// 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<Item = (KeyCode, Keysym)> + '_ {
+ pub 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| 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<KeyCode> {
+ /// Lookup the first keycode which has the given keysym in any column
+ pub(crate) fn keysym_to_keycode(&self, keysym: Keysym) -> Option<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)
+ 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(())
}