summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock48
-rw-r--r--Cargo.toml10
-rw-r--r--build.rs6
-rw-r--r--src/main.rs269
5 files changed, 334 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b83d222
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..1be1cf9
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,48 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "bitflags"
+version = "2.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
+
+[[package]]
+name = "grassbar"
+version = "1.0.0"
+dependencies = [
+ "xcb",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.191"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "quick-xml"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "xcb"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6c2ad15e0e922856ee89afe862b8992334bbe7953adad56cd1199358cb30566"
+dependencies = [
+ "bitflags",
+ "libc",
+ "quick-xml",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..74a3222
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "grassbar"
+version = "1.0.0"
+authors = ["Irene Knapp <ireneista@gmail.com>"]
+edition = "2024"
+
+[dependencies.xcb]
+version = "1.7.1"
+features = [ "libxcb_v1_14" ]
+
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..05cb2ea
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,6 @@
+fn main() {
+  // Required on NetBSD 11.0, probably harmless elsewhere...
+  println!("cargo:rustc-link-search=native=/usr/X11R7/lib");
+  println!("cargo:rustc-link-arg=-Wl,-rpath,/usr/X11R7/lib");
+}
+
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..c8bb422
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,269 @@
+use xcb::{ Connection, Xid };
+use xcb::x::{
+  self,
+  Atom,
+  ChangeProperty,
+  ClientMessageData, ClientMessageEvent,
+  Cw, CreateWindow,
+  Event,
+  EventMask,
+  InternAtom,
+  MapWindow,
+  PropMode,
+  Screen,
+  SendEvent, SendEventDest,
+  Window, WindowClass
+};
+
+
+struct Atoms {
+  wm_protocols: Atom,
+  wm_delete_window: Atom,
+  wm_type: Atom,
+  wm_type_dock: Atom,
+  wm_strut: Atom,
+  wm_strut_partial: Atom,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum ScreenSide { Top, Bottom, Left, Right }
+
+struct DockConfiguration {
+  thickness: u16,
+  side: ScreenSide,
+}
+
+#[derive(Debug)]
+struct Rectangle<T> {
+  left: T,
+  top: T,
+  width: T,
+  height: T,
+}
+
+
+fn main() -> xcb::Result<()> {
+  let (connection, screen_index) = Connection::connect(None)?;
+
+  let atoms = get_atoms(&connection)?;
+
+  let setup = connection.get_setup();
+  let screen = setup.roots().nth(screen_index as usize).unwrap();
+
+  let dock_configuration = DockConfiguration {
+    thickness: 50,
+    side: ScreenSide::Bottom,
+  };
+
+  let dock_bounds = compute_dock_bounds(&screen, &dock_configuration);
+
+  let window: Window = connection.generate_id();
+
+  println!("{}x{}", screen.width_in_pixels(), screen.height_in_pixels());
+  println!("dock {:?}", dock_bounds);
+
+  let cookie = connection.send_request_checked(&CreateWindow {
+    depth: x::COPY_FROM_PARENT as u8,
+    wid: window,
+    parent: screen.root(),
+    x: dock_bounds.left as i16,
+    y: dock_bounds.top as i16,
+    width: dock_bounds.width,
+    height: dock_bounds.height,
+    border_width: 0,
+    class: WindowClass::InputOutput,
+    visual: screen.root_visual(),
+    value_list: &[
+      Cw::BackPixel(screen.black_pixel()),
+      Cw::EventMask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS),
+    ],
+  });
+  connection.check_request(cookie)?;
+
+  set_string_property(&connection, &window, &x::ATOM_WM_NAME, b"grass bar")?;
+
+  set_atom_property(&connection, &window,
+		    &atoms.wm_protocols, &[atoms.wm_delete_window])?;
+
+  set_atom_property(&connection, &window,
+		    &atoms.wm_type, &[atoms.wm_type_dock])?;
+  set_dock_strut_properties(&connection, &window, &atoms, &screen,
+			    &dock_configuration)?;
+
+  let cookie = connection.send_request_checked(&MapWindow { window });
+  connection.check_request(cookie)?;
+
+  loop {
+    match connection.wait_for_event()? {
+      xcb::Event::X(x::Event::ButtonPress(event)) => {
+        println!("click {:?}", event);
+      }
+
+      xcb::Event::X(x::Event::ClientMessage(event)) => {
+	if let ClientMessageData::Data32([atom, ..]) = event.data() {
+	  if atom == atoms.wm_delete_window.resource_id() {
+	    break;
+	  }
+	}
+      }
+
+      event => { println!("unknown event {:?}", event); }
+    }
+  }
+
+  Ok(())
+}
+
+
+fn get_atom(connection: &Connection, name: &[u8]) -> xcb::Result<Atom> {
+  let cookie = connection.send_request(&InternAtom {
+    only_if_exists: true,
+    name: name,
+  });
+
+  Ok(connection.wait_for_reply(cookie)?.atom())
+}
+
+
+fn set_string_property(connection: &Connection, window: &Window,
+                       property: &Atom, value: &[u8])
+  -> xcb::Result<()>
+{
+  let cookie = connection.send_request_checked(&ChangeProperty {
+    mode: PropMode::Replace,
+    window: *window,
+    property: *property,
+    r#type: x::ATOM_STRING,
+    data: value,
+  });
+  connection.check_request(cookie)?;
+
+  Ok(())
+}
+
+
+fn set_atom_property(connection: &Connection, window: &Window, property: &Atom, values: &[Atom]) -> xcb::Result<()> {
+  let cookie = connection.send_request_checked(&ChangeProperty {
+    mode: PropMode::Replace,
+    window: *window,
+    property: *property,
+    r#type: x::ATOM_ATOM,
+    data: values,
+  });
+  connection.check_request(cookie)?;
+
+  Ok(())
+}
+
+
+fn set_cardinal_property(connection: &Connection, window: &Window, property: &Atom, values: &[u32]) -> xcb::Result<()> {
+  let cookie = connection.send_request_checked(&ChangeProperty {
+    mode: PropMode::Replace,
+    window: *window,
+    property: *property,
+    r#type: x::ATOM_CARDINAL,
+    data: values,
+  });
+  connection.check_request(cookie)?;
+
+  Ok(())
+}
+
+
+fn get_atoms(connection: &Connection) -> xcb::Result<Atoms> {
+  let wm_protocols = get_atom(&connection, b"WM_PROTOCOLS")?;
+  let wm_delete_window = get_atom(&connection, b"WM_DELETE_WINDOW")?;
+
+  let wm_type = get_atom(&connection, b"_NET_WM_WINDOW_TYPE")?;
+  let wm_type_dock = get_atom(&connection, b"_NET_WM_WINDOW_TYPE_DOCK")?;
+
+  let wm_strut = get_atom(&connection, b"_NET_WM_STRUT")?;
+  let wm_strut_partial = get_atom(&connection, b"_NET_WM_STRUT_PARTIAL")?;
+
+  Ok(Atoms {
+    wm_protocols, wm_delete_window,
+    wm_type, wm_type_dock,
+    wm_strut, wm_strut_partial,
+  })
+}
+
+
+fn compute_dock_bounds(screen: &Screen, configuration: &DockConfiguration)
+  -> Rectangle<u16>
+{
+  if configuration.side == ScreenSide::Top
+     || configuration.side == ScreenSide::Bottom
+  {
+    let left = 0;
+    let width = screen.width_in_pixels();
+    let height = configuration.thickness;
+
+    let top = if configuration.side == ScreenSide::Bottom {
+      screen.height_in_pixels() - height
+    } else { 0 };
+
+    Rectangle { left, top, width, height }
+  } else {
+    let top = 0;
+    let height = screen.height_in_pixels();
+    let width = configuration.thickness;
+
+    let left = if configuration.side == ScreenSide::Right {
+      screen.width_in_pixels() - width
+    } else { 0 };
+
+    Rectangle { left, top, width, height }
+  }
+}
+
+
+fn set_dock_strut_properties(connection: &Connection, window: &Window,
+			     atoms: &Atoms, screen: &Screen,
+			     configuration: &DockConfiguration)
+  -> xcb::Result<()>
+{
+  let thickness = configuration.thickness as u32;
+
+  let strut_values = match configuration.side {
+    ScreenSide::Left => &[thickness, 0 as u32, 0 as u32, 0 as u32],
+    ScreenSide::Right => &[0 as u32, thickness, 0 as u32, 0 as u32],
+    ScreenSide::Top => &[0 as u32, 0 as u32, thickness, 0 as u32],
+    ScreenSide::Bottom => &[0 as u32, 0 as u32, 0 as u32, thickness],
+  };
+  println!("struts {:?}", strut_values);
+  set_cardinal_property(&connection, &window, &atoms.wm_strut, strut_values)?;
+
+  let strut_partial_values = &[
+    strut_values[0],
+    strut_values[1],
+    strut_values[2],
+    strut_values[3],
+
+    0 as u32,
+    if configuration.side == ScreenSide::Left {
+      screen.height_in_pixels() as u32
+    } else { 0 as u32 },
+
+    0 as u32,
+    if configuration.side == ScreenSide::Right {
+      screen.height_in_pixels() as u32
+    } else { 0 as u32 },
+
+    0 as u32,
+    if configuration.side == ScreenSide::Top {
+      screen.width_in_pixels() as u32
+    } else { 0 as u32 },
+
+    0 as u32,
+    if configuration.side == ScreenSide::Bottom {
+      screen.width_in_pixels() as u32
+    } else { 0 as u32 },
+  ];
+  println!("partial {:?}", strut_partial_values);
+
+  set_cardinal_property(&connection, &window, &atoms.wm_strut_partial,
+			strut_partial_values)?;
+
+  Ok(())
+}
+