Minimal working Wayland surface

- Doesn't work on Gnome-Wayland
This commit is contained in:
Candifloss 2025-11-14 23:06:48 +05:30
parent 5aa6396c87
commit 50948197ba
2 changed files with 306 additions and 13 deletions

View File

@ -7,11 +7,12 @@ authors = ["candifloss <candifloss.cc>"]
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"

View File

@ -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<wl_compositor::WlCompositor>,
shm: Option<wl_shm::WlShm>,
layer_shell: Option<zwlr_layer_shell_v1::ZwlrLayerShellV1>,
surface: Option<wl_surface::WlSurface>,
layer_surface: Option<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>,
buffer: Option<wl_buffer::WlBuffer>,
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<Self>) {
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<Self>,
) {
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<wl_registry::WlRegistry, ()> for State {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: wl_registry::Event,
_: &(),
_: &Connection,
qh: &QueueHandle<Self>,
) {
match event {
wl_registry::Event::Global { name, interface, version: _ } => {
match interface.as_str() {
"wl_compositor" => {
let compositor = registry.bind::<wl_compositor::WlCompositor, _, _>(
name,
4,
qh,
(),
);
state.compositor = Some(compositor);
state.try_create_layer_surface(qh);
}
"wl_shm" => {
let shm =
registry.bind::<wl_shm::WlShm, _, _>(name, 1, qh, ());
state.shm = Some(shm);
state.try_create_layer_surface(qh);
}
"zwlr_layer_shell_v1" => {
let layer_shell =
registry.bind::<zwlr_layer_shell_v1::ZwlrLayerShellV1, _, _>(
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<zwlr_layer_shell_v1::ZwlrLayerShellV1, ()> for State {
fn event(
_: &mut Self,
_: &zwlr_layer_shell_v1::ZwlrLayerShellV1,
_: zwlr_layer_shell_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
unreachable!("zwlr_layer_shell_v1 has no events");
}
}
impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, LayerSurfaceData> for State {
fn event(
state: &mut Self,
surface_role: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
event: zwlr_layer_surface_v1::Event,
_: &LayerSurfaceData,
_: &Connection,
qh: &QueueHandle<Self>,
) {
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<dyn std::error::Error>> {
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<State> = 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(())
}