Minimal Wayland surface to attach the SLint UI
- The slint UI is yet to be attached
This commit is contained in:
parent
50948197ba
commit
d8fdcab52d
@ -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"
|
||||||
|
|||||||
197
src/main.rs
197
src/main.rs
@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user