From 38c79addb0c8b50b3c1ed7e854d1036f59a72f0e Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Wed, 11 Nov 2026 06:57:36 -0700 Subject: initial Force-Push: yes Change-Id: Ic5f8f9da1be3329b663d2dcb344fb29a78f1a57b --- .gitignore | 1 + Cargo.lock | 48 +++++++++++ Cargo.toml | 10 +++ build.rs | 6 ++ src/main.rs | 269 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 334 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 src/main.rs 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 "] +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 { + 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 { + 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 { + 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 +{ + 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(()) +} + -- cgit 1.4.1