summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-06-03 17:06:15 +0100
committertcmal <me@aria.rip>2024-06-03 17:06:15 +0100
commitdc8094726026c5b56b5600f95da8475561b1073d (patch)
treeb850c5076151feb6d3610a4b66087423cd3882e6 /src
initial commit: basic xorg initialisation
Diffstat (limited to 'src')
-rw-r--r--src/atoms.rs27
-rw-r--r--src/clients.rs104
-rw-r--r--src/main.rs103
3 files changed, 234 insertions, 0 deletions
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<Self> {
+ // 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<MonitorInfo>);
+
+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<Client>,
+ 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<T, E = Error> = std::result::Result<T, E>;
+
+#[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<WM<'a>> {
+ // 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()
+ }
+}