summaryrefslogtreecommitdiff
path: root/src/main.rs
blob: 64e9203464a3f0973dc8e00729da8f666b8cc1a3 (plain)
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
//! 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)]

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!()
}