1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
//! A lightweight X11 window manager, inspired by dwm.
//!
//! # Structure
//!
//! The main thing a WM has to do is respond to events: this is done in the [`WM::event_loop`] function, which dispatches to the `handle_*` methods of that struct.
//!
//! [`conn_info`] wraps XCB's [`xcb::Connection`] type and caches common resources such as atoms, colours, cursors, and keyboard layout info.
//!
//! `focus.rs`, [`keys`], and [`clients`] all add some event handlers to [`WM`], but most of the important code is in [`clients`].
//!
//! [`config`] holds all of the variables that a user might want to change.
//!
//! # XCB
//!
//! Unlike dwm, blow uses XCB rather than Xlib. This means requests are asynchronous by default.
//! In most places, we avoid checking for errors unless we need to see the response to a request.
//! Errors will be caught and logged in the event loop instead. See [`xcb`] documentation for more details.
#![deny(clippy::all, clippy::pedantic, clippy::nursery)]
#![allow(clippy::must_use_candidate, clippy::missing_errors_doc)]
use clients::ClientState;
use conn_info::Connection;
pub use error::*;
use xcb::{
x::{self, PropertyNotifyEvent},
Connection as RawConnection, Event, Extension,
};
pub mod clients;
pub mod config;
pub mod conn_info;
#[doc(hidden)]
mod error;
#[doc(hidden)]
mod focus;
pub mod keys;
fn main() -> Result<()> {
cleanup_process_children();
let (conn, screen_num) =
RawConnection::connect_with_extensions(None, &[], &[Extension::Xinerama])?;
#[allow(clippy::cast_sign_loss)]
let mut wm = WM::new(&conn, screen_num as usize)?;
wm.event_loop()?;
Ok(())
}
/// All of the state used by the window manager
pub struct WM<'a> {
conn: Connection<'a>,
clients: ClientState,
}
impl<'a> WM<'a> {
/// Prepare to start the window manager, using the given connection and scren number.
pub fn new(conn: &'a RawConnection, screen_num: usize) -> Result<Self> {
let mut this = Self {
conn: Connection::new(conn, screen_num)?,
clients: ClientState::default(),
};
this.clients.update_geometry(&this.conn)?;
Ok(this)
}
/// Run the main event loop until we encounter a non-recoverable error (usually connection).
/// This will only ever return an error.
pub fn event_loop(&mut self) -> Result<()> {
loop {
match self.conn.wait_for_event() {
Ok(x) => match x {
// See keys.rs
Event::X(x::Event::KeyPress(e)) => self.handle_key_press(&e),
Event::X(x::Event::MappingNotify(e)) => self.handle_mapping_notify(&e)?,
// See clients/mod.rs
Event::X(x::Event::ConfigureRequest(e)) => self.handle_configure_request(&e)?,
Event::X(x::Event::ConfigureNotify(e)) => self.handle_configure_notify(&e)?,
Event::X(x::Event::DestroyNotify(e)) => self.handle_destroy_notify(&e),
Event::X(x::Event::MapRequest(e)) => self.handle_map_request(&e)?,
Event::X(x::Event::UnmapNotify(e)) => self.handle_unmap_notify(&e),
// // See focus.rs
Event::X(x::Event::EnterNotify(e)) => self.handle_enter_notify(&e),
Event::X(x::Event::FocusIn(e)) => self.handle_focus_in(&e),
// // See below
Event::X(x::Event::PropertyNotify(e)) => self.handle_property_notify(&e),
_ => {}
},
Err(Error::Xcb(xcb::Error::Protocol(e))) => {
eprintln!("protocol error in event loop: {e:#?}\ncontinuing anyway");
}
Err(e) => {
eprintln!("unrecoverable error: {e:#?}\nexiting event loop");
return Err(e);
}
};
self.conn.flush()?;
}
}
/// Update client properties when they change in X11.
fn handle_property_notify(&mut self, e: &PropertyNotifyEvent) {
if x::ATOM_WM_HINTS == e.atom() {
let focused = self.clients.is_focused(e.window());
if let Some(c) = self.clients.find_client_mut(e.window()) {
c.sync_properties(&self.conn, focused);
}
}
}
}
/// Cleanup this process' children and set some flags.
/// This is necessary when used with `startx`.
fn cleanup_process_children() {
// TODO: dont transform children into zombies when they terminate
// TODO: cleanup zombies
// todo!()
}
|