summary refs log tree commit diff
path: root/src/x
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2026-11-11 21:03:30 -0700
committerIrene Knapp <ireneista@irenes.space>2026-11-11 21:32:53 -0700
commit960663d476d8a1391b8bcc4424f3f716147068ab (patch)
tree3bf7e3372295f67c4afcad07c0b74e23624ab60f /src/x
parent89a6a257f89bba997e778160c53938be371ec6ac (diff)
all the xcb wrapper types are in individual files now
the trait inheritance structure isn't THAT convoluted, but having more than one of these in a file was obscuring things.

Change-Id: I5132a6304796654d778d731be1ecb1c7b4e77120
Force-Push: yes
Diffstat (limited to 'src/x')
-rw-r--r--src/x/atoms.rs12
-rw-r--r--src/x/color.rs49
-rw-r--r--src/x/connection.rs97
-rw-r--r--src/x/context.rs44
-rw-r--r--src/x/display.rs56
-rw-r--r--src/x/window.rs134
6 files changed, 392 insertions, 0 deletions
diff --git a/src/x/atoms.rs b/src/x/atoms.rs
new file mode 100644
index 0000000..9f489a3
--- /dev/null
+++ b/src/x/atoms.rs
@@ -0,0 +1,12 @@
+use xcb::x::Atom;
+
+
+pub struct Atoms {
+  pub wm_protocols: Atom,
+  pub wm_delete_window: Atom,
+  pub wm_type: Atom,
+  pub wm_type_dock: Atom,
+  pub wm_strut: Atom,
+  pub wm_strut_partial: Atom,
+}
+
diff --git a/src/x/color.rs b/src/x/color.rs
new file mode 100644
index 0000000..10ad651
--- /dev/null
+++ b/src/x/color.rs
@@ -0,0 +1,49 @@
+/*   Color management in X has become an obscure topic because everyone prefers
+ * to assume they have 24-bit direct. In dealing with it, [1] has been an
+ * important resource.
+ *
+ * [1] https://xcb.freedesktop.org/xlibtoxcbtranslationguide/
+ */
+use crate::x::connection::*;
+use crate::x::display::*;
+
+use xcb::x::{ AllocColor, Visualtype };
+
+
+pub trait DisplayColorExt: DisplayExt {
+  /*   This is sufficiently obscure knowledge that it's worth keeping here
+   * even though it's unused.
+   */
+  #[allow(dead_code)]
+  fn find_root_visual(&self) -> Option<&Visualtype> {
+    let visual_id = self.screen().root_visual();
+
+    for depth in self.screen().allowed_depths() {
+      for visual in depth.visuals() {
+        if visual_id == visual.visual_id() {
+          return Some(visual);
+        }
+      }
+    }
+
+    None
+  }
+
+  fn allocate_color(&self, red: u16, green: u16, blue: u16)
+    -> xcb::Result<u32>
+  {
+    let connection = self.connection();
+
+    let colormap = self.screen().default_colormap();
+
+    let cookie = connection.send_request(&AllocColor {
+      cmap: colormap,
+      red, green, blue,
+    });
+
+    Ok(connection.wait_for_reply(cookie)?.pixel())
+  }
+}
+
+impl<T: DisplayExt> DisplayColorExt for T { }
+
diff --git a/src/x/connection.rs b/src/x/connection.rs
new file mode 100644
index 0000000..d9ed9ca
--- /dev/null
+++ b/src/x/connection.rs
@@ -0,0 +1,97 @@
+use crate::x::atoms::Atoms;
+
+use xcb::XidNew;
+use xcb::x::{ Atom, InternAtom };
+
+
+pub struct Connection(xcb::Connection);
+
+impl Connection {
+  pub fn connect(display_name: Option<&str>)
+    -> xcb::ConnResult<(Connection, i32)>
+  {
+    let (connection, screen_index)
+        = xcb::Connection::connect(display_name)?;
+
+    Ok((Connection(connection), screen_index))
+  }
+}
+
+
+pub trait HasConnection {
+  fn connection(&self) -> &Connection;
+}
+
+impl HasConnection for Connection {
+  fn connection(&self) -> &Connection { self }
+}
+
+
+pub trait ConnectionExt: HasConnection {
+  fn get_setup(&self) -> &xcb::x::Setup {
+    self.connection().0.get_setup()
+  }
+
+  fn send_request<R: xcb::Request>(&self, request: &R) -> R::Cookie {
+    self.connection().0.send_request(request)
+  }
+
+  fn wait_for_reply<C: xcb::CookieWithReplyChecked>(&self, cookie: C)
+    -> xcb::Result<C::Reply>
+  {
+    self.connection().0.wait_for_reply(cookie)
+  }
+
+  fn send_request_checked<R: xcb::RequestWithoutReply>(&self, request: &R)
+    -> xcb::VoidCookieChecked
+  {
+    self.connection().0.send_request_checked(request)
+  }
+
+  fn check_request(&self, cookie: xcb::VoidCookieChecked)
+    -> xcb::ProtocolResult<()>
+  {
+    self.connection().0.check_request(cookie)
+  }
+
+  fn wait_for_event(&self) -> xcb::Result<xcb::Event> {
+    self.connection().0.wait_for_event()
+  }
+
+  fn generate_id<T: XidNew>(&self) -> T {
+    let connection = self.connection();
+
+    connection.0.generate_id()
+  }
+
+  fn get_atom(&self, name: &[u8]) -> xcb::Result<Atom> {
+    let connection = self.connection();
+
+    let cookie = connection.send_request(&InternAtom {
+      only_if_exists: true,
+      name: name,
+    });
+
+    Ok(connection.wait_for_reply(cookie)?.atom())
+  }
+
+  fn get_atoms(&self) -> xcb::Result<Atoms> {
+    let wm_protocols = self.get_atom(b"WM_PROTOCOLS")?;
+    let wm_delete_window = self.get_atom(b"WM_DELETE_WINDOW")?;
+
+    let wm_type = self.get_atom(b"_NET_WM_WINDOW_TYPE")?;
+    let wm_type_dock = self.get_atom(b"_NET_WM_WINDOW_TYPE_DOCK")?;
+
+    let wm_strut = self.get_atom(b"_NET_WM_STRUT")?;
+    let wm_strut_partial = self.get_atom(b"_NET_WM_STRUT_PARTIAL")?;
+
+    Ok(Atoms {
+      wm_protocols, wm_delete_window,
+      wm_type, wm_type_dock,
+      wm_strut, wm_strut_partial,
+    })
+  }
+}
+
+impl<T: HasConnection> ConnectionExt for T { }
+
diff --git a/src/x/context.rs b/src/x/context.rs
new file mode 100644
index 0000000..0a8d11e
--- /dev/null
+++ b/src/x/context.rs
@@ -0,0 +1,44 @@
+use crate::x::*;
+
+use std::sync::Arc;
+
+use xcb::x::{ self, Drawable, CreateGc, FreeGc };
+
+
+pub struct GraphicContext {
+  display: Arc<Display>,
+  context_id: x::Gcontext,
+}
+
+
+impl GraphicContext {
+  pub fn new(display: Arc<Display>, drawable: Drawable)
+    -> xcb::Result<Self>
+  {
+    let context_id: x::Gcontext = display.generate_id();
+    let cookie = display.send_request_checked(&CreateGc {
+      cid: context_id,
+      drawable,
+      value_list: &[],
+    });
+    display.check_request(cookie)?;
+
+    Ok(GraphicContext { display, context_id })
+  }
+}
+
+impl Drop for GraphicContext {
+  fn drop(&mut self) {
+    let cookie = self.send_request_checked(&FreeGc { gc: self.context_id });
+    let _ = self.check_request(cookie); // TODO
+  }
+}
+
+impl HasDisplay for GraphicContext {
+  fn display(&self) -> &Arc<Display> { &self.display }
+}
+
+impl HasConnection for GraphicContext {
+  fn connection(&self) -> &Connection { self.display.connection() }
+}
+
diff --git a/src/x/display.rs b/src/x/display.rs
new file mode 100644
index 0000000..ca552db
--- /dev/null
+++ b/src/x/display.rs
@@ -0,0 +1,56 @@
+use crate::x::atoms::Atoms;
+use crate::x::connection::*;
+
+use std::sync::Arc;
+
+use xcb::x::Screen;
+
+
+pub struct Display {
+  connection: Connection,
+  screen_index: i32,
+  atoms: Atoms,
+}
+
+impl HasConnection for Arc<Display> {
+  fn connection(&self) -> &Connection { &self.connection }
+}
+
+
+impl Display {
+  pub fn new() -> xcb::Result<Display> {
+    let (connection, screen_index) = Connection::connect(None)?;
+
+    let atoms = connection.get_atoms()?;
+
+    Ok(Display { connection, screen_index, atoms })
+  }
+
+  pub fn screen_index(&self) -> i32 {
+    self.screen_index
+  }
+}
+
+
+pub trait HasDisplay {
+  fn display(&self) -> &Arc<Display>;
+
+  fn atoms(&self) -> &Atoms { &self.display().atoms }
+}
+
+impl HasDisplay for Arc<Display> {
+  fn display(&self) -> &Arc<Display> { self }
+}
+
+
+pub trait DisplayExt: HasDisplay + HasConnection {
+  fn screen(&self) -> &Screen {
+    let setup = self.connection().get_setup();
+    let screen_index = self.display().screen_index() as usize;
+
+    &setup.roots().nth(screen_index).unwrap()
+  }
+}
+
+impl<T: HasDisplay + HasConnection> DisplayExt for T { }
+
diff --git a/src/x/window.rs b/src/x/window.rs
new file mode 100644
index 0000000..d264eb9
--- /dev/null
+++ b/src/x/window.rs
@@ -0,0 +1,134 @@
+use crate::x::*;
+
+use std::sync::Arc;
+use std::ops::Deref;
+
+use xcb::x::{
+  self,
+  Atom,
+  ChangeProperty,
+  CreateWindow,
+  MapWindow,
+  PropMode,
+};
+
+
+pub struct Window {
+  display: Arc<Display>,
+  window_id: x::Window,
+}
+
+
+
+impl Window {
+  pub fn new(display: Arc<Display>, create_window: &mut CreateWindow)
+    -> xcb::Result<Window>
+  {
+    let connection = display.connection();
+
+    let window_id: x::Window = connection.generate_id();
+
+    create_window.wid = window_id;
+    let cookie = connection.send_request_checked(create_window);
+    connection.check_request(cookie)?;
+
+    Ok(Window { display, window_id })
+  }
+}
+
+
+pub trait HasWindow {
+  fn window(&self) -> &Window;
+
+  fn window_id(&self) -> x::Window { self.window().window_id }
+}
+
+impl HasWindow for Window {
+  fn window(&self) -> &Window { self }
+}
+
+impl<T: Deref<Target=Window>> HasWindow for T {
+  fn window(&self) -> &Window { &*self }
+}
+
+
+impl HasConnection for Window {
+  fn connection(&self) -> &Connection { self.display.connection() }
+}
+
+
+impl HasDisplay for Window {
+  fn display(&self) -> &Arc<Display> { &self.display }
+}
+
+
+
+
+pub trait WindowExt: HasWindow + HasDisplay + HasConnection {
+  fn map_window(&self) -> xcb::Result<()> {
+    let connection = self.connection();
+
+    let cookie = connection.send_request_checked(&MapWindow {
+      window: self.window_id()
+    });
+    connection.check_request(cookie)?;
+
+    Ok(())
+  }
+
+  fn set_string_property(&self, property: &Atom, value: &[u8])
+    -> xcb::Result<()>
+  {
+    let connection = self.connection();
+
+    let cookie = connection.send_request_checked(&ChangeProperty {
+      mode: PropMode::Replace,
+      window: self.window_id(),
+      property: *property,
+      r#type: x::ATOM_STRING,
+      data: value,
+    });
+    connection.check_request(cookie)?;
+
+    Ok(())
+  }
+
+
+  fn set_atom_property(&self, property: &Atom, values: &[Atom])
+    -> xcb::Result<()>
+  {
+    let connection = self.connection();
+
+    let cookie = connection.send_request_checked(&ChangeProperty {
+      mode: PropMode::Replace,
+      window: self.window_id(),
+      property: *property,
+      r#type: x::ATOM_ATOM,
+      data: values,
+    });
+    connection.check_request(cookie)?;
+
+    Ok(())
+  }
+
+
+  fn set_cardinal_property(&self, property: &Atom, values: &[u32])
+    -> xcb::Result<()>
+  {
+    let connection = self.connection();
+
+    let cookie = connection.send_request_checked(&ChangeProperty {
+      mode: PropMode::Replace,
+      window: self.window_id(),
+      property: *property,
+      r#type: x::ATOM_CARDINAL,
+      data: values,
+    });
+    connection.check_request(cookie)?;
+
+    Ok(())
+  }
+}
+
+impl<T: HasWindow + HasDisplay + HasConnection> WindowExt for T { }
+