Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat: Implement window title decorations on borders
src/decorations.rs | 383 +++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lib.rs | 1 +
src/wayland.rs | 103 +++++++++++++-
3 files changed, 485 insertions(+), 2 deletions(-)
diff --git a/src/decorations.rs b/src/decorations.rs
new file mode 100644
index 0000000..b5328dc
--- /dev/null
+++ b/src/decorations.rs
@@ -0,0 +1,383 @@
+// Wayland decoration surface drawing and management for clearwm
+
+use std::ffi::CString;
+use std::os::fd::RawFd;
+use std::ptr;
+use wayland_client::protocol::{wl_buffer, wl_shm, wl_shm_pool, wl_surface};
+use wayland_client::QueueHandle;
+
+use crate::protocol::river_window_management::client::river_decoration_v1::RiverDecorationV1;
+use crate::wayland::AppState;
+
+/// Struct tracking the Wayland decoration resources for a window.
+pub struct WindowDecoration {
+ pub surface: wl_surface::WlSurface,
+ pub decoration: RiverDecorationV1,
+ pub buffer: Option<wl_buffer::WlBuffer>,
+ pub pool: Option<wl_shm_pool::WlShmPool>,
+ pub width: i32,
+ pub height: i32,
+ pub mapped_data: *mut u32,
+ pub mapped_size: usize,
+}
+
+impl Drop for WindowDecoration {
+ fn drop(&mut self) {
+ if !self.mapped_data.is_null() {
+ unsafe {
+ libc::munmap(self.mapped_data as *mut libc::c_void, self.mapped_size);
+ }
+ }
+ }
+}
+
+// Standard 8x8 bitmap font (IBM CP437 ASCII printable characters 32..=126)
+const FONT_DATA: &[u8; 760] = &[
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // (0x20)
+ 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x00, // ! (0x21)
+ 0x24, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, // " (0x22)
+ 0x24, 0x24, 0x7e, 0x24, 0x7e, 0x24, 0x24, 0x00, // # (0x23)
+ 0x08, 0x3e, 0x28, 0x3e, 0x0a, 0x3e, 0x08, 0x00, // $ (0x24)
+ 0x00, 0x62, 0x64, 0x08, 0x13, 0x23, 0x00, 0x00, // % (0x25)
+ 0x18, 0x24, 0x18, 0x2a, 0x24, 0x24, 0x1b, 0x00, // & (0x26)
+ 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ' (0x27)
+ 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00, // ( (0x28)
+ 0x10, 0x08, 0x04, 0x04, 0x04, 0x08, 0x10, 0x00, // ) (0x29)
+ 0x00, 0x24, 0x18, 0x3c, 0x18, 0x24, 0x00, 0x00, // * (0x2a)
+ 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, 0x00, // + (0x2b)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x10, // , (0x2c)
+ 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, // - (0x2d)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, // . (0x2e)
+ 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, // / (0x2f)
+ 0x3c, 0x46, 0x4a, 0x52, 0x62, 0x3c, 0x00, 0x00, // 0 (0x30)
+ 0x18, 0x28, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, // 1 (0x31)
+ 0x3c, 0x42, 0x02, 0x3c, 0x40, 0x7e, 0x00, 0x00, // 2 (0x32)
+ 0x3c, 0x42, 0x1c, 0x02, 0x42, 0x3c, 0x00, 0x00, // 3 (0x33)
+ 0x08, 0x18, 0x28, 0x48, 0x7e, 0x08, 0x00, 0x00, // 4 (0x34)
+ 0x7e, 0x40, 0x7c, 0x02, 0x42, 0x3c, 0x00, 0x00, // 5 (0x35)
+ 0x3c, 0x40, 0x7c, 0x42, 0x42, 0x3c, 0x00, 0x00, // 6 (0x36)
+ 0x7e, 0x02, 0x04, 0x08, 0x10, 0x10, 0x00, 0x00, // 7 (0x37)
+ 0x3c, 0x42, 0x3c, 0x42, 0x42, 0x3c, 0x00, 0x00, // 8 (0x38)
+ 0x3c, 0x42, 0x3e, 0x02, 0x02, 0x3c, 0x00, 0x00, // 9 (0x39)
+ 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, // : (0x3a)
+ 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x10, 0x00, // ; (0x3b)
+ 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, // < (0x3c)
+ 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, 0x00, // = (0x3d)
+ 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00, // > (0x3e)
+ 0x3c, 0x42, 0x02, 0x0c, 0x00, 0x08, 0x08, 0x00, // ? (0x3f)
+ 0x3c, 0x42, 0x5a, 0x5a, 0x5a, 0x3e, 0x00, 0x00, // @ (0x40)
+ 0x18, 0x24, 0x42, 0x7e, 0x42, 0x42, 0x00, 0x00, // A (0x41)
+ 0x7c, 0x42, 0x7c, 0x42, 0x42, 0x7c, 0x00, 0x00, // B (0x42)
+ 0x3c, 0x42, 0x40, 0x40, 0x42, 0x3c, 0x00, 0x00, // C (0x43)
+ 0x78, 0x44, 0x42, 0x42, 0x44, 0x78, 0x00, 0x00, // D (0x44)
+ 0x7e, 0x40, 0x7c, 0x40, 0x40, 0x7e, 0x00, 0x00, // E (0x45)
+ 0x7e, 0x40, 0x7c, 0x40, 0x40, 0x40, 0x00, 0x00, // F (0x46)
+ 0x3c, 0x42, 0x40, 0x4e, 0x42, 0x3c, 0x00, 0x00, // G (0x47)
+ 0x42, 0x42, 0x7e, 0x42, 0x42, 0x42, 0x00, 0x00, // H (0x48)
+ 0x3e, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, // I (0x49)
+ 0x02, 0x02, 0x02, 0x02, 0x42, 0x3c, 0x00, 0x00, // J (0x4a)
+ 0x44, 0x48, 0x70, 0x48, 0x44, 0x42, 0x00, 0x00, // K (0x4b)
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x7e, 0x00, 0x00, // L (0x4c)
+ 0x42, 0x66, 0x5a, 0x42, 0x42, 0x42, 0x00, 0x00, // M (0x4d)
+ 0x42, 0x62, 0x52, 0x4a, 0x46, 0x42, 0x00, 0x00, // N (0x4e)
+ 0x3c, 0x42, 0x42, 0x42, 0x42, 0x3c, 0x00, 0x00, // O (0x4f)
+ 0x7c, 0x42, 0x7c, 0x40, 0x40, 0x40, 0x00, 0x00, // P (0x50)
+ 0x3c, 0x42, 0x42, 0x42, 0x4a, 0x3c, 0x02, 0x00, // Q (0x51)
+ 0x7c, 0x42, 0x7c, 0x48, 0x44, 0x42, 0x00, 0x00, // R (0x52)
+ 0x3c, 0x40, 0x3c, 0x02, 0x02, 0x3c, 0x00, 0x00, // S (0x53)
+ 0x7e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, // T (0x54)
+ 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c, 0x00, 0x00, // U (0x55)
+ 0x42, 0x42, 0x42, 0x24, 0x24, 0x18, 0x00, 0x00, // V (0x56)
+ 0x42, 0x42, 0x42, 0x5a, 0x5a, 0x24, 0x00, 0x00, // W (0x57)
+ 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 0x00, // X (0x58)
+ 0x42, 0x42, 0x24, 0x18, 0x08, 0x08, 0x00, 0x00, // Y (0x59)
+ 0x7e, 0x02, 0x04, 0x08, 0x10, 0x7e, 0x00, 0x00, // Z (0x5a)
+ 0x3c, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x00, 0x00, // [ (0x5b)
+ 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, // \ (0x5c)
+ 0x3c, 0x02, 0x02, 0x02, 0x02, 0x3c, 0x00, 0x00, // ] (0x5d)
+ 0x08, 0x14, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, // ^ (0x5e)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, // _ (0x5f)
+ 0x08, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // ` (0x60)
+ 0x00, 0x3c, 0x02, 0x3e, 0x42, 0x3e, 0x00, 0x00, // a (0x61)
+ 0x40, 0x40, 0x7c, 0x42, 0x42, 0x7c, 0x00, 0x00, // b (0x62)
+ 0x00, 0x3c, 0x40, 0x40, 0x42, 0x3c, 0x00, 0x00, // c (0x63)
+ 0x02, 0x02, 0x3e, 0x42, 0x42, 0x3e, 0x00, 0x00, // d (0x64)
+ 0x00, 0x3c, 0x42, 0x7e, 0x40, 0x3c, 0x00, 0x00, // e (0x65)
+ 0x1c, 0x20, 0x78, 0x20, 0x20, 0x20, 0x00, 0x00, // f (0x66)
+ 0x00, 0x3e, 0x42, 0x3e, 0x02, 0x3c, 0x00, 0x00, // g (0x67)
+ 0x40, 0x40, 0x7c, 0x42, 0x42, 0x42, 0x00, 0x00, // h (0x68)
+ 0x08, 0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, // i (0x69)
+ 0x02, 0x00, 0x02, 0x02, 0x02, 0x42, 0x3c, 0x00, // j (0x6a)
+ 0x40, 0x44, 0x48, 0x70, 0x48, 0x44, 0x00, 0x00, // k (0x6b)
+ 0x18, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, // l (0x6c)
+ 0x00, 0x66, 0x5a, 0x42, 0x42, 0x42, 0x00, 0x00, // m (0x6d)
+ 0x00, 0x7c, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00, // n (0x6e)
+ 0x00, 0x3c, 0x42, 0x42, 0x42, 0x3c, 0x00, 0x00, // o (0x6f)
+ 0x00, 0x7c, 0x42, 0x7c, 0x40, 0x40, 0x00, 0x00, // p (0x70)
+ 0x00, 0x3e, 0x42, 0x3e, 0x02, 0x02, 0x00, 0x00, // q (0x71)
+ 0x00, 0x7c, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, // r (0x72)
+ 0x00, 0x3e, 0x40, 0x3c, 0x02, 0x3c, 0x00, 0x00, // s (0x73)
+ 0x08, 0x08, 0x3e, 0x08, 0x08, 0x0a, 0x04, 0x00, // t (0x74)
+ 0x00, 0x42, 0x42, 0x42, 0x42, 0x3c, 0x00, 0x00, // u (0x75)
+ 0x00, 0x42, 0x42, 0x42, 0x24, 0x18, 0x00, 0x00, // v (0x76)
+ 0x00, 0x42, 0x42, 0x5a, 0x5a, 0x24, 0x00, 0x00, // w (0x77)
+ 0x00, 0x42, 0x24, 0x18, 0x24, 0x42, 0x00, 0x00, // x (0x78)
+ 0x00, 0x42, 0x42, 0x3e, 0x02, 0x3c, 0x00, 0x00, // y (0x79)
+ 0x00, 0x7e, 0x04, 0x08, 0x10, 0x7e, 0x00, 0x00, // z (0x7a)
+ 0x0c, 0x10, 0x10, 0x20, 0x10, 0x10, 0x0c, 0x00, // { (0x7b)
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, // | (0x7c)
+ 0x30, 0x08, 0x08, 0x04, 0x08, 0x08, 0x30, 0x00, // } (0x7d)
+ 0x00, 0x00, 0x00, 0x14, 0x28, 0x00, 0x00, 0x00, // ~ (0x7e)
+];
+
+/// Helper to draw a character at (x, y) with a given scale and BGRA color.
+fn draw_char(
+ buf: &mut [u32],
+ buf_w: i32,
+ buf_h: i32,
+ c: char,
+ x: i32,
+ y: i32,
+ scale: i32,
+ color: u32,
+) {
+ let code = (c as usize).saturating_sub(32);
+ if code >= 95 {
+ return;
+ }
+ let glyph = &FONT_DATA[code * 8..(code + 1) * 8];
+ for row in 0..8 {
+ let row_byte = glyph[row];
+ for col in 0..8 {
+ let bit = (row_byte >> (7 - col)) & 1;
+ if bit != 0 {
+ for dy in 0..scale {
+ for dx in 0..scale {
+ let px = x + (col as i32) * scale + dx;
+ let py = y + (row as i32) * scale + dy;
+ if px >= 0 && px < buf_w && py >= 0 && py < buf_h {
+ buf[(py * buf_w + px) as usize] = color;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Create a temporary shared memory file descriptor.
+fn create_memfd(size: usize) -> Option<RawFd> {
+ let name = CString::new("clearwm-decoration").ok()?;
+ let fd = unsafe { libc::memfd_create(name.as_ptr(), libc::MFD_CLOEXEC) };
+ if fd < 0 {
+ return None;
+ }
+ if unsafe { libc::ftruncate(fd, size as libc::off_t) } < 0 {
+ unsafe { libc::close(fd); }
+ return None;
+ }
+ Some(fd)
+}
+
+/// Main entry point to create or update decoration surfaces during rendering.
+pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>) {
+ let active_tags = state.wm.active_tags;
+
+ // We can only create decoration surfaces if the compositor and shm globals are bound
+ let (compositor, shm) = match (&state.compositor, &state.shm) {
+ (Some(c), Some(s)) => (c, s),
+ _ => return,
+ };
+
+ // Calculate dynamic border colors
+ let border_colors = crate::borders::compute_border_colors(&state.wm);
+ let text_color = 0xFFE0E0E0u32;
+
+ // Collect window IDs to modify so we don't violate the borrow checker
+ let windows_to_decorate: Vec<(u64, i32, i32, String, u32)> = state
+ .wm
+ .windows
+ .iter()
+ .enumerate()
+ .filter(|(_, w)| !w.closed && w.app_id.as_deref() != Some("clear-status-interface"))
+ .filter(|(_, w)| (w.tags & active_tags) != 0)
+ .map(|(idx, w)| {
+ let title = w.title.clone().unwrap_or_else(|| {
+ w.app_id.clone().unwrap_or_else(|| "Window".to_string())
+ });
+ let layout_idx = state.wm.windows
+ .iter()
+ .filter(|win| !win.closed && win.app_id.as_deref() != Some("clear-status-interface"))
+ .position(|win| win.id == w.id)
+ .unwrap_or(0);
+ let title_with_idx = format!("[{}] {}", layout_idx, title);
+
+ // Find matching computed border color for this window
+ let bc = border_colors.iter().find(|b| b.window_idx == idx);
+ let (r, g, b, a) = match bc {
+ Some(b) => (b.r, b.g, b.b, b.a),
+ None => (
+ state.wm.layout.border_r,
+ state.wm.layout.border_g,
+ state.wm.layout.border_b,
+ state.wm.layout.border_a,
+ ),
+ };
+
+ let border_r = (r & 0xFF) as u8;
+ let border_g = (g & 0xFF) as u8;
+ let border_b = (b & 0xFF) as u8;
+ let border_a = (a & 0xFF) as u8;
+ let bg_color = ((border_a as u32) << 24) | ((border_r as u32) << 16) | ((border_g as u32) << 8) | (border_b as u32);
+
+ // Border width is mode-specific
+ let border_w = match w.tiling_mode {
+ crate::types::TilingMode::Cascade => state.wm.layout.cascade_border_width,
+ crate::types::TilingMode::Fullscreen => state.wm.layout.fullscreen_border_width,
+ crate::types::TilingMode::Grid => state.wm.layout.grid_border_width,
+ crate::types::TilingMode::Vsplit => state.wm.layout.vsplit_border_width,
+ crate::types::TilingMode::Hsplit => state.wm.layout.hsplit_border_width,
+ crate::types::TilingMode::Floating => state.wm.layout.floating_border_width,
+ };
+ (w.id, w.width, border_w, title_with_idx, bg_color)
+ })
+ .collect();
+
+ for (wid, win_width, border_width, title, bg_color) in windows_to_decorate {
+ // Find or create decoration proxy
+ let wp_idx = match state.window_proxies.iter().position(|(id, _)| *id == wid) {
+ Some(idx) => idx,
+ None => continue,
+ };
+
+ let wp = &mut state.window_proxies[wp_idx].1;
+
+ // Determine titlebar dimensions:
+ // Height equals border_width (or 16 if border_width is too small to display font)
+ let dec_height = std::cmp::max(border_width, 16);
+ let dec_width = win_width + 2 * border_width;
+
+ let needs_new_buffer = match &wp.decoration {
+ Some(dec) => dec.width != dec_width || dec.height != dec_height,
+ None => true,
+ };
+
+ if wp.decoration.is_none() {
+ eprintln!("[decorations] creating decoration for window {}", wid);
+ let surface: wl_surface::WlSurface = compositor.create_surface(qhandle, ());
+ let decoration: RiverDecorationV1 = wp.river_window.get_decoration_above(&surface, qhandle, ());
+
+ wp.decoration = Some(WindowDecoration {
+ surface,
+ decoration,
+ buffer: None,
+ pool: None,
+ width: 0,
+ height: 0,
+ mapped_data: ptr::null_mut(),
+ mapped_size: 0,
+ });
+ }
+
+ let dec = wp.decoration.as_mut().unwrap();
+
+ // Position decoration on top of the window top border
+ dec.decoration.set_offset(-border_width, -border_width);
+
+ if needs_new_buffer {
+ eprintln!(
+ "[decorations] allocating buffer {}x{} for window {}",
+ dec_width, dec_height, wid
+ );
+
+ // Clean up old mapping if it exists
+ if !dec.mapped_data.is_null() {
+ unsafe {
+ libc::munmap(dec.mapped_data as *mut libc::c_void, dec.mapped_size);
+ }
+ dec.mapped_data = ptr::null_mut();
+ }
+
+ let stride = dec_width * 4;
+ let size = (stride * dec_height) as usize;
+
+ if let Some(fd) = create_memfd(size) {
+ let mapped_data = unsafe {
+ libc::mmap(
+ ptr::null_mut(),
+ size,
+ libc::PROT_READ | libc::PROT_WRITE,
+ libc::MAP_SHARED,
+ fd,
+ 0,
+ ) as *mut u32
+ };
+
+ if mapped_data != libc::MAP_FAILED as *mut u32 {
+ let borrowed_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(fd) };
+ let pool = shm.create_pool(borrowed_fd, size as i32, qhandle, ());
+ let buffer = pool.create_buffer(
+ 0,
+ dec_width,
+ dec_height,
+ stride,
+ wl_shm::Format::Argb8888,
+ qhandle,
+ (),
+ );
+
+ dec.pool = Some(pool);
+ dec.buffer = Some(buffer);
+ dec.width = dec_width;
+ dec.height = dec_height;
+ dec.mapped_data = mapped_data;
+ dec.mapped_size = size;
+ } else {
+ eprintln!("[decorations] failed to mmap decoration buffer");
+ unsafe { libc::close(fd); }
+ continue;
+ }
+ unsafe { libc::close(fd); }
+ } else {
+ eprintln!("[decorations] failed to create memfd");
+ continue;
+ }
+ }
+
+ // Draw titlebar background and text
+ if !dec.mapped_data.is_null() {
+ let buffer_slice = unsafe {
+ std::slice::from_raw_parts_mut(dec.mapped_data, (dec_width * dec_height) as usize)
+ };
+
+ // Clear background
+ for pixel in buffer_slice.iter_mut() {
+ *pixel = bg_color;
+ }
+
+ // Draw window title text
+ // Determine font scale (1x if height < 32, 2x if height >= 32)
+ let scale = if dec_height >= 32 { 2 } else { 1 };
+ let font_h = 8 * scale;
+
+ // Vertically center the text inside the titlebar
+ let text_y = (dec_height - font_h) / 2;
+ let mut text_x = 8; // Margin from left
+
+ for c in title.chars() {
+ if text_x + 8 * scale > dec_width {
+ break; // Out of bounds
+ }
+ draw_char(buffer_slice, dec_width, dec_height, c, text_x, text_y, scale, text_color);
+ text_x += 8 * scale;
+ }
+
+ // Commit surface rendering
+ dec.decoration.sync_next_commit();
+ if let Some(ref wl_buf) = dec.buffer {
+ dec.surface.attach(Some(wl_buf), 0, 0);
+ }
+ dec.surface.commit();
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 84a0af0..87da6c3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,6 +2,7 @@
pub mod borders;
pub mod config;
+pub mod decorations;
pub mod ipc;
pub mod ipc_server;
pub mod protocol;
diff --git a/src/wayland.rs b/src/wayland.rs
index 4fbf2bf..56806ba 100644
--- a/src/wayland.rs
+++ b/src/wayland.rs
@@ -19,6 +19,7 @@ use crate::protocol::river_layer_shell::client::{
river_layer_shell_v1::{self, RiverLayerShellV1},
};
use crate::protocol::river_window_management::client::{
+ river_decoration_v1::{self, RiverDecorationV1},
river_node_v1::{self, RiverNodeV1},
river_output_v1::{self, RiverOutputV1},
river_pointer_binding_v1::{self, RiverPointerBindingV1},
@@ -40,7 +41,9 @@ use crate::protocol::wlr_output_management::client::{
};
use crate::types::{Action, BindingUserData, Output, Seat, TilingMode, Window, WindowManager};
-use wayland_client::protocol::{wl_pointer, wl_seat, wl_output};
+use wayland_client::protocol::{
+ wl_buffer, wl_compositor, wl_output, wl_pointer, wl_seat, wl_shm, wl_shm_pool, wl_surface,
+};
use wayland_protocols::wp::cursor_shape::v1::client::{
wp_cursor_shape_device_v1::{self, WpCursorShapeDeviceV1, Shape},
wp_cursor_shape_manager_v1::{self, WpCursorShapeManagerV1},
@@ -59,6 +62,7 @@ const IFACE_WLR_OUTPUT_MANAGER: &str = "zwlr_output_manager_v1";
/// call protocol methods (set_position, propose_dimensions, etc.) on it.
pub struct WindowProxy {
pub river_window: RiverWindowV1,
+ pub decoration: Option<crate::decorations::WindowDecoration>,
}
/// Wayland proxy objects stored alongside each Seat.
@@ -98,6 +102,8 @@ pub struct AppState {
pub layer_shell: Option<RiverLayerShellV1>,
pub input_manager: Option<RiverInputManagerV1>,
pub cursor_shape_manager: Option<WpCursorShapeManagerV1>,
+ pub compositor: Option<wl_compositor::WlCompositor>,
+ pub shm: Option<wl_shm::WlShm>,
// Whether we got all required globals
pub has_window_manager: bool,
@@ -176,6 +182,8 @@ impl AppState {
layer_shell: None,
input_manager: None,
cursor_shape_manager: None,
+ compositor: None,
+ shm: None,
has_window_manager: false,
has_xkb_bindings: false,
window_proxies: Vec::new(),
@@ -309,6 +317,16 @@ impl Dispatch<wl_registry::WlRegistry, RegistryData> for AppState {
let csm: WpCursorShapeManagerV1 =
registry.bind::<WpCursorShapeManagerV1, _, _>(name, 1, qhandle, ());
state.cursor_shape_manager = Some(csm);
+ } else if interface == "wl_compositor" {
+ eprintln!("registry: binding wl_compositor name={}", name);
+ let comp: wl_compositor::WlCompositor =
+ registry.bind::<wl_compositor::WlCompositor, _, _>(name, 4, qhandle, ());
+ state.compositor = Some(comp);
+ } else if interface == "wl_shm" {
+ eprintln!("registry: binding wl_shm name={}", name);
+ let shm: wl_shm::WlShm =
+ registry.bind::<wl_shm::WlShm, _, _>(name, 1, qhandle, ());
+ state.shm = Some(shm);
} else if interface == "wl_output" {
eprintln!("registry: binding wl_output name={}", name);
let wl_out: wl_output::WlOutput =
@@ -402,6 +420,10 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
for (closed_id, closed_app_id) in &closed_ids {
state.window_proxies.retain(|(id, wp)| {
if id == closed_id {
+ if let Some(dec) = wp.decoration.as_ref() {
+ dec.decoration.destroy();
+ dec.surface.destroy();
+ }
wp.river_window.destroy();
false
} else {
@@ -783,6 +805,9 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
// and must happen during ManageStart.
crate::wm::render_borders(state);
+ // Update and render window title decorations on borders
+ crate::decorations::update_decorations(state, qhandle);
+
// Raise the focused window to the top of the visual stack.
// place_top() modifies rendering state and must be called
// during a render sequence.
@@ -890,7 +915,7 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
state.wm.windows.push(window);
state
.window_proxies
- .push((id, WindowProxy { river_window }));
+ .push((id, WindowProxy { river_window, decoration: None }));
eprintln!("[window] new window id={} (app_id pending)", id);
}
@@ -2741,3 +2766,77 @@ impl Dispatch<WpCursorShapeDeviceV1, ()> for AppState {
) {
}
}
+
+// --- core Wayland and River decoration events dispatch ---
+
+impl Dispatch<wl_compositor::WlCompositor, ()> for AppState {
+ fn event(
+ _state: &mut Self,
+ _proxy: &wl_compositor::WlCompositor,
+ _event: wl_compositor::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qhandle: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<wl_shm::WlShm, ()> for AppState {
+ fn event(
+ _state: &mut Self,
+ _proxy: &wl_shm::WlShm,
+ _event: wl_shm::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qhandle: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<wl_shm_pool::WlShmPool, ()> for AppState {
+ fn event(
+ _state: &mut Self,
+ _proxy: &wl_shm_pool::WlShmPool,
+ _event: wl_shm_pool::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qhandle: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<wl_surface::WlSurface, ()> for AppState {
+ fn event(
+ _state: &mut Self,
+ _proxy: &wl_surface::WlSurface,
+ _event: wl_surface::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qhandle: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<wl_buffer::WlBuffer, ()> for AppState {
+ fn event(
+ _state: &mut Self,
+ _proxy: &wl_buffer::WlBuffer,
+ _event: wl_buffer::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qhandle: &QueueHandle<Self>,
+ ) {
+ }
+}
+
+impl Dispatch<RiverDecorationV1, ()> for AppState {
+ fn event(
+ _state: &mut Self,
+ _proxy: &RiverDecorationV1,
+ _event: river_decoration_v1::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qhandle: &QueueHandle<Self>,
+ ) {
+ }
+}