From dc8094726026c5b56b5600f95da8475561b1073d Mon Sep 17 00:00:00 2001 From: tcmal Date: Mon, 3 Jun 2024 17:06:15 +0100 Subject: initial commit: basic xorg initialisation --- src/atoms.rs | 27 +++++++++++++++ src/clients.rs | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 234 insertions(+) create mode 100644 src/atoms.rs create mode 100644 src/clients.rs create mode 100644 src/main.rs (limited to 'src') diff --git a/src/atoms.rs b/src/atoms.rs new file mode 100644 index 0000000..363e07c --- /dev/null +++ b/src/atoms.rs @@ -0,0 +1,27 @@ +use crate::Result; +use xcb::Connection; + +#[derive(Debug)] +pub struct InternedAtoms {} + +impl InternedAtoms { + pub fn new_with(conn: &Connection) -> Result { + // TODO: intern atoms + + // utf8string = XInternAtom(dpy, "UTF8_STRING", False); + // wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + // wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + // wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + // wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + // netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + // netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); + // netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + // netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + // netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + // netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + // netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + // netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + // netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + Ok(Self {}) + } +} diff --git a/src/clients.rs b/src/clients.rs new file mode 100644 index 0000000..70a44fa --- /dev/null +++ b/src/clients.rs @@ -0,0 +1,104 @@ +use xcb::{ + xinerama::{self, ScreenInfo}, + Extension, +}; + +use crate::{Error, Result, WM}; + +#[derive(Debug, Default)] +pub struct ClientList(Vec); + +impl ClientList { + /// Set the new amount of screens, moving clients away if necessary + pub fn truncate_screens(&mut self, new_size: usize) { + // hack: double borrow stuff + let mut moved_clients = vec![]; + for old in self.0.drain(new_size - self.0.len()..self.0.len()) { + moved_clients.extend(old.clients.into_iter()); + } + self.0[0].clients.extend(moved_clients.into_iter()); + } + + pub fn set_screen_info(&mut self, i: usize, info: ScreenInfo) { + while i >= self.0.len() { + self.0.push(MonitorInfo::default()) + } + self.0[i].screen_info = info; + } + + pub fn len(&self) -> usize { + self.0.len() + } +} + +#[derive(Debug)] +pub struct MonitorInfo { + clients: Vec, + screen_info: ScreenInfo, +} + +impl Default for MonitorInfo { + fn default() -> Self { + Self { + clients: vec![], + screen_info: ScreenInfo { + x_org: 0, + y_org: 0, + width: 0, + height: 0, + }, + } + } +} + +#[derive(Debug)] +pub struct Client {} + +impl WM<'_> { + /// Update the client list's recorded monitors and monitor sizes + pub(crate) fn update_geometry(&mut self) -> Result<()> { + if self + .conn + .active_extensions() + .any(|e| e == Extension::Xinerama) + { + let reply = self + .conn + .wait_for_reply(self.conn.send_request(&xinerama::QueryScreens {}))?; + + // Monitor removed, move its clients away + if reply.screen_info().len() > self.clients.len() { + self.clients.truncate_screens(reply.screen_info().len()); + } + + // Update screen info & add new client lists if needed + for (i, monitor) in reply.screen_info().iter().enumerate() { + self.clients.set_screen_info(i, monitor.clone()); + } + } else { + // Only one screen + if self.clients.len() > 1 { + self.clients.truncate_screens(1); + } + + // TODO: it looks like this won't actually update when the screen size changes? + let setup = self.conn.get_setup(); + let screen = setup + .roots() + .nth(self.screen_num as usize) + .ok_or(Error::NoSuchScreen)?; + + self.clients.set_screen_info( + 0, + ScreenInfo { + x_org: 0, + y_org: 0, + width: screen.width_in_pixels(), + height: screen.height_in_pixels(), + }, + ); + } + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ea42009 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,103 @@ +use atoms::InternedAtoms; +use clients::ClientList; +use thiserror::Error; +use xcb::{ + x::{self, ChangeWindowAttributes, Screen, Window}, + Connection, Extension, +}; + +mod atoms; +mod clients; + +type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + #[error("xcb returned screen that doesn't exist")] + NoSuchScreen, + + #[error("other wm is running")] + OtherWMRunning, + + #[error("connection error: {0}")] + ConnectionError(#[from] xcb::ConnError), + + #[error("generic xcb error: {0}")] + XCBError(#[from] xcb::Error), +} + +fn main() -> Result<()> { + // todo: cli stuff + let display_name = ":1"; + + cleanup_process_children(); + + let (conn, screen_num) = + Connection::connect_with_extensions(Some(display_name), &[], &[Extension::Xinerama])?; + + let mut wm = WM::new(&conn, screen_num)?; + wm.event_loop()?; + + Ok(()) +} + +struct WM<'a> { + conn: &'a Connection, + screen_num: i32, + + root: Window, + clients: ClientList, + atoms: InternedAtoms, +} + +impl WM<'_> { + fn new<'a>(conn: &'a Connection, screen_num: i32) -> Result> { + // Fetch root window + let setup = conn.get_setup(); + let screen = setup + .roots() + .nth(screen_num as usize) + .ok_or(Error::NoSuchScreen)?; + + // Check no other WM is running + let cookie = conn.send_request_checked(&ChangeWindowAttributes { + window: screen.root(), + value_list: &[ + x::Cw::BackPixel(screen.white_pixel()), + x::Cw::EventMask(x::EventMask::SUBSTRUCTURE_REDIRECT), + ], + }); + + conn.check_request(cookie) + .map_err(|_| Error::OtherWMRunning)?; + + Ok(WM { + atoms: InternedAtoms::new_with(conn)?, + clients: Default::default(), + conn, + screen_num, + root: screen.root(), + }) + } + + fn event_loop(&mut self) -> Result<()> { + self.update_geometry()?; + + Ok(()) + } +} +fn cleanup_process_children() { + // todo: dont transform children into zombies when they terminate + // todo: cleanup zombies +} + +impl<'a> std::fmt::Debug for WM<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WM") + .field("screen_num", &self.screen_num) + .field("root", &self.root) + .field("clients", &self.clients) + .field("atoms", &self.atoms) + .finish() + } +} -- cgit v1.2.3