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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
//! 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 nix::{
sys::{
signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal},
wait::{waitpid, WaitPidFlag},
},
unistd::Pid,
};
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 helpers;
pub mod keys;
pub mod log;
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) => {
debug!("received event: {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() {
unsafe {
// Don't transform children into zombies when they terminate
sigaction(
Signal::SIGCHLD,
&SigAction::new(
SigHandler::SigIgn,
SaFlags::SA_NOCLDSTOP | SaFlags::SA_NOCLDWAIT | SaFlags::SA_RESTART,
SigSet::empty(),
),
)
.unwrap();
// Immediately wait for zombie processes to die - sometimes these come from startx.
while waitpid(Pid::from_raw(-1), Some(WaitPidFlag::WNOHANG)).is_ok() {}
};
}
|