Minimal Wayland surface to attach the SLint UI

- The slint UI is yet to be attached
This commit is contained in:
Candifloss 2025-11-15 23:29:16 +05:30
parent 50948197ba
commit d8fdcab52d
2 changed files with 70 additions and 130 deletions

View File

@ -7,7 +7,8 @@ authors = ["candifloss <candifloss.cc>"]
readme = "README.md" readme = "README.md"
[dependencies] [dependencies]
slint = {version="1.14.1", features = ["backend-winit"]} i-slint-backend-testing = "1.14.1"
slint = { version= "1.14.1", default-features = false, features = ["std", "compat-1-2"] }
smithay-client-toolkit = { version = "0.20.0", features = ["calloop"] } smithay-client-toolkit = { version = "0.20.0", features = ["calloop"] }
tempfile = "3.23.0" tempfile = "3.23.0"
wayland-client = "0.31.11" wayland-client = "0.31.11"

View File

@ -1,49 +1,54 @@
use std::fs::File; use slint::LogicalSize;
use std::io::{BufWriter, Write};
use std::os::unix::io::AsFd;
use tempfile::tempfile;
use wayland_client::{ use wayland_client::{
protocol::{wl_buffer, wl_compositor, wl_registry, wl_shm, wl_shm_pool, wl_surface}, protocol::{wl_compositor, wl_registry, wl_shm, wl_surface},
Connection, Dispatch, EventQueue, QueueHandle, Connection, Dispatch, EventQueue, QueueHandle,
}; };
use wayland_protocols_wlr::layer_shell::v1::client::{ use wayland_protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1, zwlr_layer_surface_v1, zwlr_layer_shell_v1, zwlr_layer_surface_v1,
}; };
// This backend sets a Slint platform that uses the software renderer,
// but does NOT provide its own event loop.
use i_slint_backend_testing as slint_backend;
slint::include_modules!();
// ---------- client state ---------- // ---------- client state ----------
struct State { struct State {
running: bool, running: bool,
bar_height: u32, bar_height: u32,
bar_width: u32,
compositor: Option<wl_compositor::WlCompositor>, compositor: Option<wl_compositor::WlCompositor>,
shm: Option<wl_shm::WlShm>,
layer_shell: Option<zwlr_layer_shell_v1::ZwlrLayerShellV1>, layer_shell: Option<zwlr_layer_shell_v1::ZwlrLayerShellV1>,
surface: Option<wl_surface::WlSurface>, surface: Option<wl_surface::WlSurface>,
layer_surface: Option<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>, layer_surface: Option<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>,
buffer: Option<wl_buffer::WlBuffer>,
current_width: u32, // Slint UI
current_height: u32, ui: TopBar,
} }
impl State { impl State {
fn new(bar_height: u32, default_width: u32) -> Self { fn new(bar_height: u32, bar_width: u32) -> Self {
// Slint platform MUST already be initialized before this
let ui = TopBar::new().expect("Failed to create Slint UI");
ui.set_bar_width(bar_width as i32);
ui.set_bar_height(bar_height as i32);
ui.window()
.set_size(LogicalSize::new(bar_width as f32, bar_height as f32));
Self { Self {
running: true, running: true,
bar_height, bar_height,
bar_width,
compositor: None, compositor: None,
shm: None,
layer_shell: None, layer_shell: None,
surface: None, surface: None,
layer_surface: None, layer_surface: None,
buffer: None, ui,
current_width: default_width,
current_height: bar_height,
} }
} }
@ -55,23 +60,17 @@ impl State {
Some(c) => c, Some(c) => c,
None => return, None => return,
}; };
let shm = match &self.shm {
Some(s) => s,
None => return,
};
let layer_shell = match &self.layer_shell { let layer_shell = match &self.layer_shell {
Some(ls) => ls, Some(ls) => ls,
None => return, None => return,
}; };
// Make sure shm exists so when configure comes we can create a buffer // Wayland surface
let _ = shm; // only to express the dependency, not strictly needed
let surface = compositor.create_surface(qh, ()); let surface = compositor.create_surface(qh, ());
self.surface = Some(surface.clone()); self.surface = Some(surface.clone());
// layer-shell surface
let namespace = "chocobar".to_string(); let namespace = "chocobar".to_string();
let layer_surface = layer_shell.get_layer_surface( let layer_surface = layer_shell.get_layer_surface(
&surface, &surface,
None, // all outputs None, // all outputs
@ -81,15 +80,18 @@ impl State {
LayerSurfaceData, LayerSurfaceData,
); );
// Anchor top, left and right, ask compositor to decide width (pass zero)
layer_surface.set_anchor( layer_surface.set_anchor(
zwlr_layer_surface_v1::Anchor::Top zwlr_layer_surface_v1::Anchor::Top
| zwlr_layer_surface_v1::Anchor::Left | zwlr_layer_surface_v1::Anchor::Left
| zwlr_layer_surface_v1::Anchor::Right, | zwlr_layer_surface_v1::Anchor::Right,
); );
layer_surface.set_exclusive_zone(self.bar_height as i32); 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); // Ask compositor for width (0) but constrain height to bar_height
// or set explicit width if you want a fixed 1366-wide bar:
// layer_surface.set_size(self.bar_width, self.bar_height);
layer_surface.set_size(0, self.bar_height);
layer_surface.set_keyboard_interactivity( layer_surface.set_keyboard_interactivity(
zwlr_layer_surface_v1::KeyboardInteractivity::OnDemand, zwlr_layer_surface_v1::KeyboardInteractivity::OnDemand,
); );
@ -97,63 +99,9 @@ impl State {
self.layer_surface = Some(layer_surface); self.layer_surface = Some(layer_surface);
surface.commit(); 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 // This is the user data type for the layer surface
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct LayerSurfaceData; struct LayerSurfaceData;
@ -181,12 +129,6 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
state.compositor = Some(compositor); state.compositor = Some(compositor);
state.try_create_layer_surface(qh); 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" => { "zwlr_layer_shell_v1" => {
let layer_shell = let layer_shell =
registry.bind::<zwlr_layer_shell_v1::ZwlrLayerShellV1, _, _>( registry.bind::<zwlr_layer_shell_v1::ZwlrLayerShellV1, _, _>(
@ -203,8 +145,8 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
} }
wl_registry::Event::GlobalRemove { .. } => { wl_registry::Event::GlobalRemove { .. } => {
// ignore for this minimal example // ignore for this minimal example
}, }
_ => todo!() _ => todo!("Unhandled wl_registry event"),
} }
} }
} }
@ -231,25 +173,31 @@ impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, LayerSurfaceData> for S
event: zwlr_layer_surface_v1::Event, event: zwlr_layer_surface_v1::Event,
_: &LayerSurfaceData, _: &LayerSurfaceData,
_: &Connection, _: &Connection,
qh: &QueueHandle<Self>, _: &QueueHandle<Self>,
) { ) {
match event { match event {
zwlr_layer_surface_v1::Event::Configure { serial, width, height } => { zwlr_layer_surface_v1::Event::Configure { serial, width, height } => {
surface_role.ack_configure(serial); surface_role.ack_configure(serial);
let w = if width == 0 { // Use compositor-provided size if nonzero, otherwise our defaults
state.current_width let w = if width == 0 { state.bar_width } else { width as u32 };
} else { let h = if height == 0 { state.bar_height } else { height as u32 };
width as u32
};
let h = if height == 0 {
state.bar_height
} else {
height as u32
};
state.ensure_buffer(w, h, qh); state.bar_width = w;
state.repaint(); state.bar_height = h;
// Resize Slint component
state.ui.set_bar_width(w as i32);
state.ui.set_bar_height(h as i32);
state
.ui
.window()
.set_size(LogicalSize::new(w as f32, h as f32));
// NOTE: at this point you still need to render the Slint
// window into a wl_shm buffer and attach it to `state.surface`.
// That is the next step: use `ui.window().take_snapshot()`
// + wl_shm buffer instead of a flat `draw_bar`.
} }
zwlr_layer_surface_v1::Event::Closed => { zwlr_layer_surface_v1::Event::Closed => {
state.running = false; state.running = false;
@ -259,52 +207,43 @@ impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, LayerSurfaceData> for S
} }
} }
// ---------- ignore events from other objects we do not care about ---------- // ---------- ignore events from other objects ----------
wayland_client::delegate_noop!(State: ignore wl_compositor::WlCompositor); 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_surface::WlSurface);
wayland_client::delegate_noop!(State: ignore wl_shm::WlShm); 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 ---------- // ---------- main loop ----------
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let bar_height = 25; let bar_height = 25;
let default_width = 1366; let bar_width = 1366;
// 1. Initialize Slint platform using the testing backend (software renderer,
// no event loop). This fixes the "No default Slint platform" panic.
slint_backend::init_no_event_loop();
// 2. Wayland connection + event loop
let conn = Connection::connect_to_env()?; let conn = Connection::connect_to_env()?;
let display = conn.display(); let display = conn.display();
let mut event_queue: EventQueue<State> = conn.new_event_queue(); let mut event_queue: EventQueue<State> = conn.new_event_queue();
let qh = event_queue.handle(); let qh = event_queue.handle();
// Create Slint UI and Wayland state, now that the platform is set
let mut state = State::new(bar_height, bar_width);
display.get_registry(&qh, ()); display.get_registry(&qh, ());
let mut state = State::new(bar_height, default_width); // 3. Run Wayland loop until layer surface is closed
while state.running { while state.running {
event_queue.blocking_dispatch(&mut state)?; event_queue.blocking_dispatch(&mut state)?;
// Later: call slint::platform::update_timers_and_animations() here
// and trigger re-render into wl_shm when needed.
} }
// Optionally show the Slint window normally (not needed for layer-shell)
// state.ui.run().expect("Failed to run Slint UI");
Ok(()) Ok(())
} }