From 50948197ba84a3e074da4b6869f0d8ed9948c6a1 Mon Sep 17 00:00:00 2001 From: Candifloss Date: Fri, 14 Nov 2025 23:06:48 +0530 Subject: [PATCH] Minimal working Wayland surface - Doesn't work on Gnome-Wayland --- Cargo.toml | 7 +- src/main.rs | 312 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 306 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 04ec441..c0f2350 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,12 @@ authors = ["candifloss "] readme = "README.md" [dependencies] -slint = "1.14.1" +slint = {version="1.14.1", features = ["backend-winit"]} smithay-client-toolkit = { version = "0.20.0", features = ["calloop"] } +tempfile = "3.23.0" wayland-client = "0.31.11" -winit = "0.30.12" -x11rb = "0.13.2" +wayland-protocols = { version = "0.32.9", features = ["client"] } +wayland-protocols-wlr = "0.3.9" [build-dependencies] slint-build = "1.14.1" diff --git a/src/main.rs b/src/main.rs index d629a6a..b3508c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,310 @@ -use slint::{LogicalPosition, LogicalSize}; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::os::unix::io::AsFd; -slint::include_modules!(); +use tempfile::tempfile; + +use wayland_client::{ + protocol::{wl_buffer, wl_compositor, wl_registry, wl_shm, wl_shm_pool, wl_surface}, + Connection, Dispatch, EventQueue, QueueHandle, +}; + +use wayland_protocols_wlr::layer_shell::v1::client::{ + zwlr_layer_shell_v1, zwlr_layer_surface_v1, +}; + +// ---------- client state ---------- + +struct State { + running: bool, + bar_height: u32, + + compositor: Option, + shm: Option, + layer_shell: Option, + + surface: Option, + layer_surface: Option, + buffer: Option, + + current_width: u32, + current_height: u32, +} + +impl State { + fn new(bar_height: u32, default_width: u32) -> Self { + Self { + running: true, + bar_height, + compositor: None, + shm: None, + layer_shell: None, + surface: None, + layer_surface: None, + buffer: None, + current_width: default_width, + current_height: bar_height, + } + } + + fn try_create_layer_surface(&mut self, qh: &QueueHandle) { + if self.layer_surface.is_some() { + return; + } + let compositor = match &self.compositor { + Some(c) => c, + None => return, + }; + let shm = match &self.shm { + Some(s) => s, + None => return, + }; + let layer_shell = match &self.layer_shell { + Some(ls) => ls, + None => return, + }; + + // Make sure shm exists so when configure comes we can create a buffer + let _ = shm; // only to express the dependency, not strictly needed + + let surface = compositor.create_surface(qh, ()); + self.surface = Some(surface.clone()); + + let namespace = "chocobar".to_string(); + + let layer_surface = layer_shell.get_layer_surface( + &surface, + None, // all outputs + zwlr_layer_shell_v1::Layer::Top, + namespace, + qh, + LayerSurfaceData, + ); + + // Anchor top, left and right, ask compositor to decide width (pass zero) + layer_surface.set_anchor( + zwlr_layer_surface_v1::Anchor::Top + | zwlr_layer_surface_v1::Anchor::Left + | zwlr_layer_surface_v1::Anchor::Right, + ); + layer_surface.set_exclusive_zone(self.bar_height as i32); + layer_surface.set_margin(0, 0, 0, 0); + layer_surface.set_size(0, self.bar_height as u32); + layer_surface.set_keyboard_interactivity( + zwlr_layer_surface_v1::KeyboardInteractivity::OnDemand, + ); + + self.layer_surface = Some(layer_surface); + surface.commit(); + } + + fn ensure_buffer( + &mut self, + width: u32, + height: u32, + qh: &QueueHandle, + ) { + let need_new = + self.buffer.is_none() || self.current_width != width || self.current_height != height; + + if !need_new { + return; + } + + let shm = self + .shm + .as_ref() + .expect("wl_shm not bound before buffer creation"); + + let mut tmp = tempfile().expect("failed to create temp file"); + draw_bar(&mut tmp, width, height).expect("drawing bar failed"); + + let size = (width * height * 4) as i32; + let pool: wl_shm_pool::WlShmPool = shm.create_pool(tmp.as_fd(), size, qh, ()); + let buffer = pool.create_buffer( + 0, + width as i32, + height as i32, + (width * 4) as i32, + wl_shm::Format::Argb8888, + qh, + (), + ); + + self.buffer = Some(buffer); + self.current_width = width; + self.current_height = height; + + // pool is dropped here, that is fine, pattern matches smithay examples + } + + fn repaint(&mut self) { + let surface = match &self.surface { + Some(s) => s, + None => return, + }; + let buffer = match &self.buffer { + Some(b) => b, + None => return, + }; + + surface.attach(Some(buffer), 0, 0); + surface.commit(); + } +} + +// This is the user data type for the layer surface, we do not need anything in it +#[derive(Debug, Clone, Copy)] +struct LayerSurfaceData; + +// ---------- wl_registry dispatch ---------- + +impl Dispatch for State { + fn event( + state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + match event { + wl_registry::Event::Global { name, interface, version: _ } => { + match interface.as_str() { + "wl_compositor" => { + let compositor = registry.bind::( + name, + 4, + qh, + (), + ); + state.compositor = Some(compositor); + state.try_create_layer_surface(qh); + } + "wl_shm" => { + let shm = + registry.bind::(name, 1, qh, ()); + state.shm = Some(shm); + state.try_create_layer_surface(qh); + } + "zwlr_layer_shell_v1" => { + let layer_shell = + registry.bind::( + name, + 1, + qh, + (), + ); + state.layer_shell = Some(layer_shell); + state.try_create_layer_surface(qh); + } + _ => {} + } + } + wl_registry::Event::GlobalRemove { .. } => { + // ignore for this minimal example + }, + _ => todo!() + } + } +} + +// ---------- wlr layer shell dispatch ---------- + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &zwlr_layer_shell_v1::ZwlrLayerShellV1, + _: zwlr_layer_shell_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + unreachable!("zwlr_layer_shell_v1 has no events"); + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + surface_role: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + event: zwlr_layer_surface_v1::Event, + _: &LayerSurfaceData, + _: &Connection, + qh: &QueueHandle, + ) { + match event { + zwlr_layer_surface_v1::Event::Configure { serial, width, height } => { + surface_role.ack_configure(serial); + + let w = if width == 0 { + state.current_width + } else { + width as u32 + }; + let h = if height == 0 { + state.bar_height + } else { + height as u32 + }; + + state.ensure_buffer(w, h, qh); + state.repaint(); + } + zwlr_layer_surface_v1::Event::Closed => { + state.running = false; + } + _ => {} + } + } +} + +// ---------- ignore events from other objects we do not care about ---------- + +wayland_client::delegate_noop!(State: ignore wl_compositor::WlCompositor); +wayland_client::delegate_noop!(State: ignore wl_surface::WlSurface); +wayland_client::delegate_noop!(State: ignore wl_shm::WlShm); +wayland_client::delegate_noop!(State: ignore wl_shm_pool::WlShmPool); +wayland_client::delegate_noop!(State: ignore wl_buffer::WlBuffer); + +// ---------- simple software drawing for the bar ---------- + +fn draw_bar(file: &mut File, width: u32, height: u32) -> std::io::Result<()> { + let mut buf = BufWriter::new(file); + + for _y in 0..height { + for _x in 0..width { + let a: u8 = 0xFF; + let r: u8 = 0x20; + let g: u8 = 0x20; + let b: u8 = 0x20; + + buf.write_all(&[b, g, r, a])?; + } + } + + buf.flush() +} + +// ---------- main loop ---------- fn main() -> Result<(), Box> { let bar_height = 25; - let bar_width = 1366; + let default_width = 1366; - let ui = TopBar::new()?; - ui.set_bar_width(bar_width); - ui.set_bar_height(bar_height); - ui.window() - .set_size(LogicalSize::new(bar_width as f32, bar_height as f32)); - ui.window().set_position(LogicalPosition::new(0.0, 0.0)); - ui.run()?; + let conn = Connection::connect_to_env()?; + let display = conn.display(); + + let mut event_queue: EventQueue = conn.new_event_queue(); + let qh = event_queue.handle(); + + display.get_registry(&qh, ()); + + let mut state = State::new(bar_height, default_width); + + while state.running { + event_queue.blocking_dispatch(&mut state)?; + } Ok(()) }