From b39388d91aec80aba5131cc638e28cd28c0713c1 Mon Sep 17 00:00:00 2001 From: Shawn Wallace Date: Thu, 6 Nov 2025 00:50:30 -0500 Subject: [PATCH] Add client side decorations to toplevels These simple decorations will be rendered only when the host compositor doesn't support server side decorations and the X11 window does not render its own decorations. Closes #31 --- Cargo.lock | 180 +++++++++++++++ Cargo.toml | 3 + OpenSans-Regular.ttf | Bin 0 -> 130832 bytes src/server/clientside.rs | 21 +- src/server/decoration.rs | 480 +++++++++++++++++++++++++++++++++++++++ src/server/event.rs | 168 ++++++++++++-- src/server/mod.rs | 88 +++++-- src/server/tests.rs | 80 ++++++- tests/integration.rs | 8 +- testwl/src/lib.rs | 108 ++++++++- 10 files changed, 1087 insertions(+), 49 deletions(-) create mode 100644 OpenSans-Regular.ttf create mode 100644 src/server/decoration.rs diff --git a/Cargo.lock b/Cargo.lock index 9893e62..62f22d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,28 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -23,12 +45,30 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "bindgen" version = "0.72.1" @@ -61,6 +101,12 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + [[package]] name = "cc" version = "1.2.24" @@ -96,6 +142,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "cursor-icon" version = "1.2.0" @@ -218,12 +273,47 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fontdue" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e57e16b3fe8ff4364c0661fdaac543fb38b29ea9bc9c2f45612d90adf931d2b" +dependencies = [ + "hashbrown 0.15.5", + "ttf-parser 0.21.1", +] + [[package]] name = "glob" version = "0.3.2" @@ -244,6 +334,11 @@ name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "hecs" @@ -379,6 +474,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "nom" version = "7.1.3" @@ -432,12 +537,34 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser 0.25.1", +] + [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -598,6 +725,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "smallvec" version = "1.15.0" @@ -635,6 +768,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "strsim" version = "0.11.1" @@ -725,6 +864,32 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -742,6 +907,18 @@ dependencies = [ "winnow", ] +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1135,8 +1312,10 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" name = "xwayland-satellite" version = "0.7.0" dependencies = [ + "ab_glyph", "anyhow", "bitflags 2.9.1", + "fontdue", "hecs", "log", "macros", @@ -1146,6 +1325,7 @@ dependencies = [ "sd-notify", "smithay-client-toolkit", "testwl", + "tiny-skia", "vergen-gitcl", "wayland-client", "wayland-protocols", diff --git a/Cargo.toml b/Cargo.toml index f81a41a..eead711 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,9 @@ sd-notify = { version = "0.4.2", optional = true } macros = { version = "0.1.0", path = "macros" } hecs = { version = "0.10.5", features = ["macros"] } num_enum = "0.7.4" +tiny-skia = "0.11.4" +ab_glyph = "0.2.32" +fontdue = "0.9.3" [features] default = [] diff --git a/OpenSans-Regular.ttf b/OpenSans-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..134d225f663c8fb7710d09df9cfbe5664c5bd4c3 GIT binary patch literal 130832 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�%(1$HKu5J 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 n&{-|ffSrWb2}>v|6Tw#Cb;6e = ::Event; delegate_noop!(MyWorld: WlCompositor); +delegate_noop!(MyWorld: WlSubcompositor); delegate_noop!(MyWorld: WlRegion); delegate_noop!(MyWorld: ignore WlShm); delegate_noop!(MyWorld: ignore ZwpLinuxDmabufV1); @@ -179,8 +183,8 @@ delegate_noop!(MyWorld: ZwpTabletManagerV2); delegate_noop!(MyWorld: XdgActivationV1); delegate_noop!(MyWorld: ZxdgDecorationManagerV1); delegate_noop!(MyWorld: WpFractionalScaleManagerV1); -delegate_noop!(MyWorld: ignore ZxdgToplevelDecorationV1); delegate_noop!(MyWorld: ZwpPrimarySelectionDeviceManagerV1); +delegate_noop!(MyWorld: WlSubsurface); impl Dispatch for MyWorld { fn event( @@ -236,6 +240,18 @@ impl Dispatch for MyWorld { } } +impl Dispatch for MyWorld { + fn event( + _: &mut Self, + _: &WlSurface, + _: ::Event, + _: &DecorationMarker, + _: &Connection, + _: &QueueHandle, + ) { + } +} + macro_rules! push_events { ($type:ident) => { impl Dispatch<$type, Entity> for MyWorld { @@ -270,6 +286,7 @@ push_events!(WlTouch); push_events!(ZwpConfinedPointerV1); push_events!(ZwpLockedPointerV1); push_events!(WpFractionalScaleV1); +push_events!(ZxdgToplevelDecorationV1); pub(crate) struct LateInitObjectKey { key: OnceLock, diff --git a/src/server/decoration.rs b/src/server/decoration.rs new file mode 100644 index 0000000..393e75b --- /dev/null +++ b/src/server/decoration.rs @@ -0,0 +1,480 @@ +use crate::server::{InnerServerState, ServerState, SurfaceRole}; +use crate::{X11Selection, XConnection}; + +use ab_glyph::{Font, FontRef, Glyph, PxScaleFont, ScaleFont}; +use hecs::{CommandBuffer, Entity, World}; +use log::{error, warn}; +use smithay_client_toolkit::registry::SimpleGlobal; +use smithay_client_toolkit::shm::slot::SlotPool; +use std::sync::LazyLock; +use tiny_skia::{Color, Paint, PathBuilder, Pixmap, Stroke, Transform}; +use tiny_skia::{ColorU8, Rect}; +use wayland_client::protocol::wl_seat::WlSeat; +use wayland_client::protocol::wl_shm; +use wayland_client::protocol::wl_subsurface::WlSubsurface; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::Proxy; +use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport; +use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1; +use wayland_protocols::xdg::shell::client::xdg_toplevel::XdgToplevel; +use xcb::x; + +#[derive(Debug)] +pub struct DecorationsData { + pub wl: Option, + // Boxed to avoid making ToplevelData so much bigger than PopupData + pub satellite: Option>, +} + +pub struct DecorationMarker { + pub parent: Entity, +} + +#[derive(Debug)] +pub struct DecorationsDataSatellite { + surface: WlSurface, + subsurface: WlSubsurface, + pool: Entity, + viewport: WpViewport, + scale: f32, + pixmap: Pixmap, + x_data: DecorationsBox, + title: Option, + title_rect: Rect, +} + +impl Drop for DecorationsDataSatellite { + fn drop(&mut self) { + self.subsurface.destroy(); + self.surface.destroy(); + self.viewport.destroy(); + } +} + +impl DecorationsDataSatellite { + pub const TITLEBAR_HEIGHT: i32 = 25; + + pub fn try_new( + state: &InnerServerState, + parent: &WlSurface, + title: Option<&str>, + ) -> Option<(Box, Option)> { + let mut new_pool = None; + let mut query = state.world.query::<&SlotPool>(); + let pool_entity = if let Some((pool_entity, _)) = query.into_iter().next() { + pool_entity + } else { + new_pool = Some( + SlotPool::new(1, &SimpleGlobal::from_bound(state.shm.clone())) + .inspect_err(|e| { + warn!("Couldn't create slot pool for decorations: {e:?}"); + }) + .ok()?, + ); + state.world.reserve_entity() + }; + + let surface = state.compositor.create_surface( + &state.qh, + DecorationMarker { + parent: parent.data().copied().unwrap(), + }, + ); + let subsurface = { + state + .subcompositor + .get_subsurface(&surface, parent, &state.qh, ()) + }; + subsurface.set_desync(); + subsurface.set_position(0, -Self::TITLEBAR_HEIGHT); + let viewport = state.viewporter.get_viewport(&surface, &state.qh, ()); + + Some(( + Self { + surface, + subsurface, + pool: pool_entity, + viewport, + x_data: DecorationsBox::default(), + pixmap: Pixmap::new(1, 1).unwrap(), + scale: 1.0, + title: title.map(str::to_string), + title_rect: Rect::from_ltrb(0.0, 0.0, 0.0, 0.0).unwrap(), + } + .into(), + new_pool.map(|p| { + let mut buf = CommandBuffer::new(); + buf.insert_one(pool_entity, p); + buf + }), + )) + } + + fn pool<'a>(&self, world: &'a World) -> hecs::RefMut<'a, SlotPool> { + world.get::<&mut SlotPool>(self.pool).unwrap() + } + + fn update_buffer(&mut self, world: &World) { + let mut pool = self.pool(world); + let (buffer, data) = match pool.create_buffer( + self.pixmap.width() as i32, + self.pixmap.height() as i32, + self.pixmap.width() as i32 * 4, + wl_shm::Format::Xrgb8888, + ) { + Ok(b) => b, + Err(err) => { + error!("Failed to create buffer for decorations: {err:?}"); + return; + } + }; + + draw_pixmap_to_buffer(&self.pixmap, data); + buffer.attach_to(&self.surface).unwrap(); + self.surface.commit(); + } + + pub fn draw_decorations(&mut self, world: &World, width: i32, parent_scale_factor: f32) { + if width <= 0 { + return; + } + + self.scale = parent_scale_factor; + let mut drawn_width = (width as f32 * self.scale).ceil() as i32; + let drawn_height = (Self::TITLEBAR_HEIGHT as f32 * self.scale).ceil() as i32; + + let x = x_pixmap(drawn_height as u32, self.scale, self.x_data.hovered); + if x.width() > drawn_width as u32 { + drawn_width = x.width() as i32; + } + + let title = self.title.as_ref().and_then(|t| { + let width = (drawn_width as u32).saturating_sub(x.width()); + if width > 0 { + title_pixmap(t, width, drawn_height as u32, self.scale) + } else { + None + } + }); + + // Draw the bar and its components + let mut bar = Pixmap::new(drawn_width as u32, drawn_height as u32).unwrap(); + bar.fill(Color::WHITE); + + if let Some(title) = title { + bar.draw_pixmap( + 0, + 0, + title.as_ref(), + &Default::default(), + Transform::identity(), + None, + ); + self.title_rect = + Rect::from_xywh(0.0, 0.0, title.width() as f32, title.height() as f32).unwrap(); + } + + bar.draw_pixmap( + (bar.width() - x.width()) as i32, + 0, + x.as_ref(), + &Default::default(), + Transform::identity(), + None, + ); + self.x_data = DecorationsBox { + rect: Rect::from_ltrb( + width as f32 - Self::TITLEBAR_HEIGHT as f32, + 0.0, + width as f32, + Self::TITLEBAR_HEIGHT as f32, + ) + .unwrap(), + hovered: false, + }; + + self.pixmap = bar; + self.viewport.set_destination(width, Self::TITLEBAR_HEIGHT); + self.update_buffer(world); + } + + fn redraw_x_pixmap(&mut self, world: &World) { + let x = x_pixmap(self.pixmap.height(), self.scale, self.x_data.hovered); + + self.pixmap.draw_pixmap( + (self.pixmap.width() - x.width()) as i32, + 0, + x.as_ref(), + &Default::default(), + Transform::identity(), + None, + ); + + self.surface.damage_buffer( + (self.pixmap.width() - x.width()) as i32, + 0, + x.width() as i32, + x.height() as i32, + ); + self.update_buffer(world); + } + + pub fn set_title(&mut self, world: &World, title: &str) { + self.title = Some(title.to_string()); + + // Don't draw title if there's not enough space + let title_pixmap = title_pixmap( + title, + self.pixmap.width() - self.x_data.rect.width() as u32, + self.pixmap.height(), + self.scale, + ); + + let new_title_rect = title_pixmap + .as_ref() + .map(|p| Rect::from_xywh(0.0, 0.0, p.width() as f32, p.height() as f32).unwrap()) + .unwrap_or_else(|| Rect::from_ltrb(0.0, 0.0, 0.0, 0.0).unwrap()); + + let last_title_rect = std::mem::replace(&mut self.title_rect, new_title_rect); + + // Clear last title with white + let mut paint = Paint::default(); + paint.set_color(Color::WHITE); + self.pixmap + .fill_rect(last_title_rect, &paint, Transform::identity(), None); + + if let Some(p) = title_pixmap.as_ref() { + self.pixmap.draw_pixmap( + 0, + 0, + p.as_ref(), + &Default::default(), + Transform::identity(), + None, + ); + } + + let damaged_width = last_title_rect + .width() + .max(title_pixmap.map(|p| p.width() as f32).unwrap_or(0.0)); + + self.surface + .damage_buffer(0, 0, damaged_width as i32, last_title_rect.height() as i32); + self.update_buffer(world); + } + + fn handle_motion(&mut self, world: &World, x: f64, y: f64) { + if self.x_data.check_hovered(x as f32, y as f32) { + self.redraw_x_pixmap(world); + } + } + + fn handle_leave(&mut self, world: &World) { + if self.x_data.hovered { + self.x_data.hovered = false; + self.redraw_x_pixmap(world); + } + } + + /// Returns true if the toplevel should be closed + fn handle_click(&self, toplevel: &XdgToplevel, seat: &WlSeat, serial: u32) -> bool { + if self.x_data.hovered { + true + } else { + toplevel._move(seat, serial); + false + } + } +} + +#[derive(Debug)] +struct DecorationsBox { + rect: Rect, + hovered: bool, +} + +impl Default for DecorationsBox { + fn default() -> Self { + Self { + rect: Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap(), + hovered: false, + } + } +} + +impl DecorationsBox { + /// Returns true if hover state changed. + fn check_hovered(&mut self, x: f32, y: f32) -> bool { + let old_hovered = self.hovered; + self.hovered = (self.rect.left()..=self.rect.right()).contains(&x) + && (self.rect.top()..=self.rect.bottom()).contains(&y); + + old_hovered != self.hovered + } +} + +fn draw_pixmap_to_buffer(pixmap: &Pixmap, buffer: &mut [u8]) { + // TODO: support big endian? + for (data, pixel) in buffer.chunks_exact_mut(4).zip(pixmap.pixels()) { + data[0] = pixel.blue(); + data[1] = pixel.green(); + data[2] = pixel.red(); + data[3] = pixel.alpha(); + } +} + +fn x_pixmap(bar_height: u32, scale: f32, hovered: bool) -> Pixmap { + let mut x = Pixmap::new(bar_height, bar_height).unwrap(); + if hovered { + x.fill(Color::from_rgba(1.0, 0.0, 0.0, 0.8).unwrap()); + } else { + x.fill(Color::WHITE); + } + let size = x.width() as f32; + let margin = 8.4 * scale; + + let mut line = PathBuilder::new(); + line.move_to(margin, margin); + line.line_to(size - margin, size - margin); + line.move_to(size - margin, margin); + line.line_to(margin, size - margin); + let line = line.finish().unwrap(); + x.stroke_path( + &line, + &Default::default(), + &Stroke { + width: scale + 0.5, + ..Default::default() + }, + Default::default(), + None, + ); + + x +} + +fn title_pixmap(title: &str, max_width: u32, height: u32, scale: f32) -> Option { + if title.is_empty() { + return None; + } + + let (glyphs, font) = layout_title_glyphs(title, max_width, height, scale); + + let width = glyphs + .last() + .map(|g| g.position.x + font.h_advance(g.id)) + .unwrap() + .ceil() as u32; + + let mut pixmap = Pixmap::new(width, height).unwrap(); + let data = pixmap.pixels_mut(); + + for glyph in glyphs { + if let Some(og) = font.outline_glyph(glyph) { + let bounds = og.px_bounds(); + og.draw(|x, y, coverage| { + let pixel_idx = + ((bounds.min.x as u32 + x) + (bounds.min.y as u32 + y) * width) as usize; + + data[pixel_idx] = + ColorU8::from_rgba(0, 0, 0, (coverage * 255.0) as u8).premultiply(); + }); + } + } + + Some(pixmap) +} + +fn layout_title_glyphs( + text: &str, + max_width: u32, + height: u32, + scale: f32, +) -> (Vec, PxScaleFont<&FontRef<'_>>) { + const TEXT_SIZE: f32 = 10.0; + const TEXT_MARGIN: f32 = 11.0; + static FONT: LazyLock> = LazyLock::new(|| { + FontRef::try_from_slice(include_bytes!("../../OpenSans-Regular.ttf")).unwrap() + }); + + let mut ret = Vec::::new(); + + let px_scale = FONT.pt_to_px_scale(TEXT_SIZE * scale).unwrap(); + let font = FONT.as_scaled(px_scale); + for c in text.chars() { + let mut glyph = font.scaled_glyph(c); + // This centers the glyphs vertically + glyph.position.y = (height as f32 / 2.0) - font.descent(); + if let Some(previous) = ret.last() { + glyph.position.x = previous.position.x + + font.h_advance(previous.id) + + font.kern(glyph.id, previous.id); + } else { + glyph.position.x = TEXT_MARGIN * scale; + } + if (glyph.position.x + font.h_advance(glyph.id)).ceil() as u32 > max_width { + break; + } + + ret.push(glyph); + } + + (ret, font) +} + +fn get_decoration( + world: &World, + parent: Entity, +) -> Option>> { + let role = world.get::<&mut SurfaceRole>(parent).ok()?; + Some(hecs::RefMut::map(role, |role| { + let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role else { + unreachable!() + }; + + toplevel.decoration.satellite.as_mut().unwrap() + })) +} + +pub fn handle_pointer_leave(state: &InnerServerState, parent: Entity) { + if let Some(mut decoration) = get_decoration(&state.world, parent) { + decoration.handle_leave(&state.world); + } +} + +pub fn handle_pointer_motion( + state: &InnerServerState, + parent: Entity, + surface_x: f64, + surface_y: f64, +) { + if let Some(mut decoration) = get_decoration(&state.world, parent) { + decoration.handle_motion(&state.world, surface_x, surface_y); + } +} + +pub fn handle_pointer_click( + state: &mut ServerState, + parent: Entity, + seat: &WlSeat, + serial: u32, +) { + let Ok(mut role) = state.world.get::<&mut SurfaceRole>(parent) else { + return; + }; + let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role else { + unreachable!() + }; + + if toplevel + .decoration + .satellite + .as_mut() + .unwrap() + .handle_click(&toplevel.toplevel, seat, serial) + { + let window = *state.world.get::<&x::Window>(parent).unwrap(); + drop(role); + state.close_x_window(window); + } +} diff --git a/src/server/event.rs b/src/server/event.rs index 215a4f3..ad85bde 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -1,7 +1,8 @@ use super::clientside::LateInitObjectKey; +use super::decoration::DecorationMarker; use super::*; -use hecs::CommandBuffer; -use log::{debug, trace, warn}; +use hecs::{CommandBuffer, World}; +use log::{debug, error, trace, warn}; use macros::simple_event_shunt; use std::os::fd::AsFd; use wayland_client::{protocol as client, Proxy}; @@ -35,6 +36,7 @@ use wayland_protocols::{ viewporter::client::wp_viewport::WpViewport, }, xdg::{ + decoration::zv1::client::zxdg_toplevel_decoration_v1, shell::client::{xdg_popup, xdg_surface, xdg_toplevel}, xdg_output::zv1::{ client::zxdg_output_v1, server::zxdg_output_v1::ZxdgOutputV1 as XdgOutputServer, @@ -64,6 +66,7 @@ pub(crate) enum SurfaceEvents { Toplevel(xdg_toplevel::Event), Popup(xdg_popup::Event), FractionalScale(wp_fractional_scale_v1::Event), + DecorationEvent(zxdg_toplevel_decoration_v1::Event), } macro_rules! impl_from { ($type:ty, $variant:ident) => { @@ -79,6 +82,7 @@ impl_from!(xdg_surface::Event, XdgSurface); impl_from!(xdg_toplevel::Event, Toplevel); impl_from!(xdg_popup::Event, Popup); impl_from!(wp_fractional_scale_v1::Event, FractionalScale); +impl_from!(zxdg_toplevel_decoration_v1::Event, DecorationEvent); impl Event for SurfaceEvents { fn handle(self, target: Entity, state: &mut ServerState) { @@ -114,6 +118,63 @@ impl Event for SurfaceEvents { } _ => unreachable!(), }, + SurfaceEvents::DecorationEvent(event) => { + use zxdg_toplevel_decoration_v1::{Event, Mode}; + let Event::Configure { mode } = event else { + error!("unhandled toplevel decoration event: {event:?}"); + return; + }; + + let entity = state.world.entity(target).unwrap(); + let Some(window_data) = entity.get::<&WindowData>() else { + return; + }; + let Ok(mode) = mode.into_result() else { + warn!("unknown decoration mode: {mode:?}"); + return; + }; + + let needs_server_side_decorations = window_data + .attrs + .decorations + .is_none_or(|d| d == Decorations::Server); + + if mode == Mode::ServerSide || !needs_server_side_decorations { + let mut role = entity.get::<&mut SurfaceRole>().unwrap(); + if let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role { + toplevel.decoration.satellite.take(); + } + return; + } + + let Some((sat_decoration, buf)) = entity + .get::<&client::wl_surface::WlSurface>() + .and_then(|surface| { + DecorationsDataSatellite::try_new( + state, + &surface, + window_data.attrs.title.as_ref().map(WmName::name), + ) + }) + else { + warn!("Needed to create decorations for window, but couldn't create them!"); + return; + }; + + let mut role = entity.get::<&mut SurfaceRole>().unwrap(); + // This should always be the case, but, you never know. + if let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role { + toplevel.decoration.satellite = Some(sat_decoration); + } else { + warn!("Created a decoration for a surface that isn't a toplevel?"); + } + + drop(window_data); + drop(role); + if let Some(mut buf) = buf { + buf.run_on(&mut state.world); + } + } } } } @@ -223,8 +284,13 @@ impl SurfaceEvents { drop(xdg); if let Some(pending) = pending { - let mut query = data.query::<(&SurfaceScaleFactor, &x::Window, &mut WindowData)>(); - let (scale_factor, window, window_data) = query.get().unwrap(); + let mut query = data.query::<( + &SurfaceScaleFactor, + &x::Window, + &mut WindowData, + &mut SurfaceRole, + )>(); + let (scale_factor, window, window_data, role) = query.get().unwrap(); let window = *window; let x = (pending.x.max(0) as f64 * scale_factor.0) as i32 + window_data.output_offset.x; @@ -234,7 +300,7 @@ impl SurfaceEvents { } else { window_data.attrs.dims.width }; - let height = if pending.height > 0 { + let mut height = if pending.height > 0 { (pending.height as f64 * scale_factor.0) as u16 } else { window_data.attrs.dims.height @@ -243,6 +309,20 @@ impl SurfaceEvents { "configuring {} ({window:?}): {x}x{y}, {width}x{height}", data.get::<&WlSurface>().unwrap().id(), ); + + if let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role { + if let Some(d) = &mut toplevel.decoration.satellite { + let surface_width = (width as f64 / scale_factor.0) as i32; + d.draw_decorations(&state.world, surface_width, scale_factor.0 as f32); + height = height + .saturating_sub( + (DecorationsDataSatellite::TITLEBAR_HEIGHT as f64 * scale_factor.0) + as u16, + ) + .max(DecorationsDataSatellite::TITLEBAR_HEIGHT as u16); + } + } + connection.set_window_dims( window, PendingSurfaceState { @@ -446,7 +526,16 @@ impl Event for client::wl_seat::Event { } struct PendingEnter(client::wl_pointer::Event); -struct CurrentSurface(Entity); +enum CurrentSurface { + Xwayland(Entity), + Decoration(Entity), +} + +impl CurrentSurface { + fn is_decoration(&self) -> bool { + matches!(self, Self::Decoration(..)) + } +} pub struct LastClickSerial(pub client::wl_seat::WlSeat, pub u32); impl Event for client::wl_pointer::Event { @@ -511,7 +600,6 @@ impl Event for client::wl_pointer::Event { let state = &mut state.inner; let mut cmd = CommandBuffer::new(); let pending_enter = state.world.remove_one::(target).ok(); - let server = state.world.get::<&WlPointer>(target).unwrap(); let surface_entity = surface.data().copied(); let mut query = surface_entity.and_then(|e| { state @@ -521,10 +609,20 @@ impl Event for client::wl_pointer::Event { }); let Some((surface, role, scale, window)) = query.as_mut().and_then(|q| q.get()) else { - warn!("could not enter surface: stale surface"); + if let Some(&DecorationMarker { parent }) = surface.data() { + drop(query); + state + .world + .insert_one(target, CurrentSurface::Decoration(parent)) + .unwrap(); + } else { + warn!("could not enter surface: stale surface"); + } + return; }; + let server = state.world.get::<&WlPointer>(target).unwrap(); cmd.insert(target, (*scale,)); let surface_is_popup = matches!(role, SurfaceRole::Popup(_)); @@ -535,7 +633,7 @@ impl Event for client::wl_pointer::Event { if !surface_is_popup { state.last_hovered = Some(*window); } - cmd.insert(target, (CurrentSurface(surface_entity.unwrap()),)); + cmd.insert_one(target, CurrentSurface::Xwayland(surface_entity.unwrap())); }; if !surface_is_popup { @@ -565,13 +663,18 @@ impl Event for client::wl_pointer::Event { drop(server); cmd.run_on(&mut state.world); } - client::wl_pointer::Event::Leave { serial, surface } => { + Self::Leave { serial, surface } => { let _ = state.world.remove_one::(target); - let _ = state.world.remove_one::(target); if !surface.is_alive() { return; } debug!("leaving surface ({serial})"); + if let Ok(CurrentSurface::Decoration(parent)) = + state.world.remove_one::(target) + { + decoration::handle_pointer_leave(state, parent); + return; + } if let Some(surface) = surface .data() .copied() @@ -586,7 +689,7 @@ impl Event for client::wl_pointer::Event { warn!("could not leave surface: stale surface"); } } - client::wl_pointer::Event::Motion { + Self::Motion { time, surface_x, surface_y, @@ -594,6 +697,13 @@ impl Event for client::wl_pointer::Event { if !handle_pending_enter(target, state, "motion") { return; } + { + let surface = state.world.get::<&CurrentSurface>(target).unwrap(); + if let CurrentSurface::Decoration(parent) = &*surface { + decoration::handle_pointer_motion(state, *parent, surface_x, surface_y); + return; + } + } let (server, scale) = state .world .query_one_mut::<(&WlPointer, &SurfaceScaleFactor)>(target) @@ -606,7 +716,7 @@ impl Event for client::wl_pointer::Event { ); server.motion(time, surface_x * scale.0, surface_y * scale.0); } - client::wl_pointer::Event::Button { + Self::Button { serial, time, button, @@ -616,13 +726,14 @@ impl Event for client::wl_pointer::Event { return; } let mut cmd = CommandBuffer::new(); - let (server, seat, CurrentSurface(surface)) = state + + let mut query = state .world - .query_one_mut::<(&WlPointer, &client::wl_seat::WlSeat, &CurrentSurface)>( - target, - ) + .query_one::<(&WlPointer, &client::wl_seat::WlSeat, &CurrentSurface)>(target) .unwrap(); + let (server, seat, current_surface) = query.get().unwrap(); + // from linux/input-event-codes.h mod button_codes { pub const LEFT: u32 = 0x110; @@ -631,14 +742,33 @@ impl Event for client::wl_pointer::Event { if button_state == WEnum::Value(client::wl_pointer::ButtonState::Pressed) && button == button_codes::LEFT { - cmd.insert(*surface, (LastClickSerial(seat.clone(), serial),)); + match current_surface { + CurrentSurface::Xwayland(entity) => { + cmd.insert(*entity, (LastClickSerial(seat.clone(), serial),)); + } + CurrentSurface::Decoration(parent) => { + let seat = seat.clone(); + let parent = *parent; + drop(query); + decoration::handle_pointer_click(state, parent, &seat, serial); + return; + } + } } server.button(serial, time, button, convert_wenum(button_state)); + drop(query); cmd.run_on(&mut state.world); } _ => { - let server = state.world.get::<&WlPointer>(target).unwrap(); + let (server, current_surface) = state + .world + .query_one_mut::<(&WlPointer, Option<&CurrentSurface>)>(target) + .unwrap(); + + if current_surface.is_some_and(CurrentSurface::is_decoration) { + return; + } simple_event_shunt! { server, self => [ Frame, diff --git a/src/server/mod.rs b/src/server/mod.rs index ce6af7b..73e8ddd 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,4 +1,5 @@ mod clientside; +mod decoration; mod dispatch; mod event; pub(crate) mod selection; @@ -9,7 +10,8 @@ use self::event::*; use crate::xstate::{Decorations, MoveResizeDirection, WindowDims, WmHints, WmName, WmNormalHints}; use crate::{timespec_from_millis, X11Selection, XConnection}; use clientside::MyWorld; -use hecs::{Entity, World}; +use decoration::{DecorationsData, DecorationsDataSatellite}; +use hecs::Entity; use log::{debug, warn}; use rustix::event::{poll, PollFd, PollFlags}; use smithay_client_toolkit::activation::ActivationState; @@ -17,14 +19,13 @@ use std::collections::{HashMap, HashSet}; use std::ops::{Deref, DerefMut}; use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::net::UnixStream; +use wayland_client::protocol::wl_subcompositor::WlSubcompositor; use wayland_client::{ globals::{registry_queue_init, Global}, protocol as client, Connection, EventQueue, Proxy, QueueHandle, }; use wayland_protocols::xdg::decoration::zv1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1; -use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::{ - self, ZxdgToplevelDecorationV1, -}; +use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::{self}; use wayland_protocols::xdg::shell::client::xdg_positioner::ConstraintAdjustment; use wayland_protocols::{ wp::{ @@ -194,7 +195,7 @@ impl SurfaceRole { fn destroy(&mut self) { match self { SurfaceRole::Toplevel(Some(ref mut t)) => { - if let Some(decoration) = t.decoration.take() { + if let Some(decoration) = t.decoration.wl.take() { decoration.destroy(); } t.toplevel.destroy(); @@ -222,7 +223,7 @@ struct ToplevelData { toplevel: XdgToplevel, xdg: XdgSurfaceData, fullscreen: bool, - decoration: Option, + decoration: decoration::DecorationsData, } #[derive(Debug)] @@ -447,6 +448,9 @@ pub struct InnerServerState { last_hovered: Option, xdg_wm_base: XdgWmBase, + compositor: client::wl_compositor::WlCompositor, + subcompositor: WlSubcompositor, + shm: client::wl_shm::WlShm, viewporter: WpViewporter, fractional_scale: Option, decoration_manager: Option, @@ -482,6 +486,18 @@ impl ServerState> { warn!("xdg_wm_base version 2 detected. Popup repositioning will not work, and some popups may not work correctly."); } + let compositor = global_list + .bind::(&qh, 4..=6, ()) + .expect("Could not bind wl_compositor"); + + let subcompositor = global_list + .bind::(&qh, 1..=1, ()) + .expect("Could not bind wl_subcompositor"); + + let shm = global_list + .bind::(&qh, 1..=1, ()) + .expect("Could not bind wl_shm"); + let viewporter = global_list .bind::(&qh, 1..=1, ()) .expect("Could not bind wp_viewporter"); @@ -511,6 +527,7 @@ impl ServerState> { .contents() .with_list(|globals| handle_globals::(&dh, globals)); + let world = MyWorld::new(global_list); let client = dh.insert_client(client, std::sync::Arc::new(())).unwrap(); let inner = InnerServerState { @@ -525,6 +542,9 @@ impl ServerState> { last_focused_toplevel: None, last_hovered: None, xdg_wm_base, + compositor, + subcompositor, + shm, viewporter, fractional_scale, selection_states, @@ -544,7 +564,7 @@ impl ServerState> { updated_outputs: Vec::new(), new_scale: None, decoration_manager, - world: MyWorld::new(global_list), + world, }; Self { inner, @@ -778,9 +798,12 @@ impl InnerServerState { return; }; - if let Some(role) = data.get::<&SurfaceRole>() { - if let SurfaceRole::Toplevel(Some(data)) = &*role { + if let Some(mut role) = data.get::<&mut SurfaceRole>() { + if let SurfaceRole::Toplevel(Some(data)) = &mut *role { data.toplevel.set_title(title.name().to_string()); + if let Some(d) = &mut data.decoration.satellite { + d.set_title(&self.world, title.name()); + } } } } @@ -850,10 +873,6 @@ impl InnerServerState { } pub fn set_win_decorations(&mut self, window: x::Window, decorations: Decorations) { - if self.decoration_manager.is_none() { - return; - }; - let Some(data) = self .windows .get(&window) @@ -870,10 +889,9 @@ impl InnerServerState { debug!("setting {window:?} decorations {decorations:?}"); if let Some(role) = data.get::<&SurfaceRole>() { if let SurfaceRole::Toplevel(Some(data)) = &*role { - data.decoration - .as_ref() - .unwrap() - .set_mode(decorations.into()); + if let Some(decoration) = &data.decoration.wl { + decoration.set_mode(decorations.into()); + } } } win.attrs.decorations = Some(decorations); @@ -1312,8 +1330,9 @@ impl InnerServerState { toplevel.set_fullscreen(None); } - let decoration = self.decoration_manager.as_ref().map(|decoration_manager| { - let decoration = decoration_manager.get_toplevel_decoration(&toplevel, &self.qh, ()); + let wl_decoration = self.decoration_manager.as_ref().map(|decoration_manager| { + let decoration = + decoration_manager.get_toplevel_decoration(&toplevel, &self.qh, entity); decoration.set_mode( window .attrs @@ -1323,10 +1342,29 @@ impl InnerServerState { decoration }); + // X11 side wants server side decorations, but compositor won't provide them + // so we provide our own + let surface = self .world .get::<&client::wl_surface::WlSurface>(entity) .unwrap(); + let needs_satellite_decorations = wl_decoration.is_none() + && window + .attrs + .decorations + .is_none_or(|d| d == Decorations::Server); + let (sat_decoration, buf) = needs_satellite_decorations + .then(|| { + DecorationsDataSatellite::try_new( + self, + &surface, + window.attrs.title.as_ref().map(WmName::name), + ) + }) + .flatten() + .unzip(); + if let (Some(activation_state), Some(token)) = ( self.activation_state.as_ref(), window.activation_token.clone(), @@ -1356,6 +1394,13 @@ impl InnerServerState { } } + drop(window); + drop(group); + drop(surface); + if let Some(mut b) = buf.flatten() { + b.run_on(&mut self.world); + } + ToplevelData { xdg: XdgSurfaceData { surface: xdg, @@ -1364,7 +1409,10 @@ impl InnerServerState { }, toplevel, fullscreen: false, - decoration, + decoration: DecorationsData { + wl: wl_decoration, + satellite: sat_decoration, + }, } } diff --git a/src/server/tests.rs b/src/server/tests.rs index 24da976..270979a 100644 --- a/src/server/tests.rs +++ b/src/server/tests.rs @@ -8,7 +8,7 @@ use std::io::Write; use std::os::fd::{AsRawFd, BorrowedFd}; use std::os::unix::net::UnixStream; use std::sync::{Arc, Mutex}; -use testwl::SendDataForMimeFn; +use testwl::{SendDataForMimeFn, SurfaceRole}; use wayland_client::{ backend::{protocol::Message, Backend, ObjectData, ObjectId, WaylandError}, protocol::{ @@ -27,6 +27,7 @@ use wayland_client::{ }, Connection, Proxy, WEnum, }; +use wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1; use wayland_protocols::{ wp::{ linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, @@ -2600,6 +2601,83 @@ fn scaled_pointer_lock_position_hint() { ); } +#[test] +fn client_side_decorations() { + let (mut f, compositor) = TestFixture::new_with_compositor(); + let window = unsafe { Window::new(1) }; + let (_, id) = f.create_toplevel(&compositor, window); + f.testwl + .force_decoration_mode(id, zxdg_toplevel_decoration_v1::Mode::ClientSide); + f.run(); + + let subsurface_id = f.testwl.last_created_surface_id().unwrap(); + assert_ne!(subsurface_id, id); + let data = f.testwl.get_surface_data(subsurface_id).unwrap(); + let Some(SurfaceRole::Subsurface(subsurface)) = &data.role else { + panic!("surface was not a subsurface: {:?}", data.role); + }; + + assert_eq!(subsurface.position, testwl::Vec2 { x: 0, y: -25 }); + assert_eq!(subsurface.parent, id); + let subsurface = subsurface.subsurface.clone(); + + f.testwl + .force_decoration_mode(id, zxdg_toplevel_decoration_v1::Mode::ServerSide); + f.run(); + + assert!(f.testwl.get_surface_data(subsurface_id).is_none()); + assert!(!subsurface.is_alive()); +} + +#[test] +fn client_side_decorations_no_global() { + let mut f = TestFixture::new_pre_connect(|testwl| { + testwl.disable_decorations_global(); + }); + let compositor = f.compositor(); + let window = unsafe { Window::new(1) }; + let (buffer, surface) = compositor.create_surface(); + + let data = WindowData { + mapped: true, + dims: WindowDims { + x: 0, + y: 0, + width: 50, + height: 50, + }, + fullscreen: false, + }; + + f.new_window(window, false, data); + f.map_window(&compositor, window, &surface.obj, &buffer); + f.run(); + + let surfaces = f.testwl.created_surfaces(); + assert_eq!(surfaces.len(), 2); + let mut toplevel = None; + let mut subsurface_parent = None; + for id in surfaces { + let data = f.testwl.get_surface_data(*id).unwrap(); + match data + .role + .as_ref() + .expect("A surface was created without a role") + { + SurfaceRole::Toplevel(_) => { + toplevel = Some(*id); + } + SurfaceRole::Subsurface(sub) => { + assert_eq!(sub.position, testwl::Vec2 { x: 0, y: -25 }); + subsurface_parent = Some(sub.parent); + } + other => panic!("got surface with unexpected role: {other:?}"), + } + } + + assert_eq!(toplevel.unwrap(), subsurface_parent.unwrap()); +} + /// See Pointer::handle_event for an explanation. #[test] fn popup_pointer_motion_workaround() {} diff --git a/tests/integration.rs b/tests/integration.rs index a189d23..8a6f24c 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1766,7 +1766,7 @@ fn xdg_decorations() { let window = connection.new_window(connection.root, 0, 0, 20, 20, false); let surface = f.map_as_toplevel(&mut connection, window); let data = f.testwl.get_surface_data(surface).unwrap(); - // The default decoration mode in x11 is SDD + // The default decoration mode in x11 is SSD assert_eq!( data.toplevel() .decoration @@ -1782,8 +1782,7 @@ fn xdg_decorations() { connection.atoms.motif_wm_hints, &[2u32, 0, 0, 0, 0], ); - std::thread::sleep(std::time::Duration::from_millis(1)); - f.testwl.dispatch(); + f.wait_and_dispatch(); let data = f.testwl.get_surface_data(surface).unwrap(); assert_eq!( data.toplevel() @@ -1800,8 +1799,7 @@ fn xdg_decorations() { connection.atoms.motif_wm_hints, &[2u32, 0, 1, 0, 0], ); - std::thread::sleep(std::time::Duration::from_millis(1)); - f.testwl.dispatch(); + f.wait_and_dispatch(); let data = f.testwl.get_surface_data(surface).unwrap(); assert_eq!( data.toplevel() diff --git a/testwl/src/lib.rs b/testwl/src/lib.rs index 899e0b4..742b7c5 100644 --- a/testwl/src/lib.rs +++ b/testwl/src/lib.rs @@ -59,6 +59,9 @@ use wayland_protocols::{ }, }, }; +use wayland_server::backend::GlobalId; +use wayland_server::protocol::wl_subcompositor::WlSubcompositor; +use wayland_server::protocol::wl_subsurface::WlSubsurface; use wayland_server::{ backend::{ protocol::{Interface, ProtocolError}, @@ -119,6 +122,7 @@ impl SurfaceData { match self.role.as_ref().expect("Surface missing role") { SurfaceRole::Toplevel(ref t) => &t.xdg, SurfaceRole::Popup(ref p) => &p.xdg, + SurfaceRole::Subsurface(_) => panic!("subsurface doesn't have an XdgSurface"), SurfaceRole::Cursor => panic!("cursor surface doesn't have an XdgSurface"), } } @@ -142,6 +146,7 @@ pub enum SurfaceRole { Toplevel(Toplevel), Popup(Popup), Cursor, + Subsurface(Subsurface), } #[derive(Debug, PartialEq, Eq)] @@ -169,6 +174,13 @@ pub struct Popup { pub positioner_state: PositionerState, } +#[derive(Debug, PartialEq, Eq)] +pub struct Subsurface { + pub subsurface: WlSubsurface, + pub position: Vec2, + pub parent: SurfaceId, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub struct Vec2 { pub x: i32, @@ -251,6 +263,7 @@ struct State { buffers: HashSet, begin: Instant, last_surface_id: Option, + created_surfaces: Vec, last_output: Option, callbacks: Vec, seat: Option, @@ -275,6 +288,7 @@ impl Default for State { fn default() -> Self { Self { surfaces: Default::default(), + created_surfaces: Default::default(), outputs: Default::default(), buffers: Default::default(), positioners: Default::default(), @@ -415,6 +429,7 @@ pub struct Server { dh: DisplayHandle, state: State, client: Option, + decorations_global: GlobalId, } pub trait SendDataForMimeFn: FnMut(&str, &mut Server) -> bool {} @@ -448,6 +463,7 @@ impl Server { }; } dh.create_global::(6, ()); + dh.create_global::(1, ()); dh.create_global::(1, ()); dh.create_global::(6, ()); dh.create_global::(5, ()); @@ -455,7 +471,7 @@ impl Server { dh.create_global::(1, ()); dh.create_global::(1, ()); dh.create_global::(1, ()); - dh.create_global::(1, ()); + let decorations_global = dh.create_global::(1, ()); dh.create_global::(1, ()); dh.create_global::(1, ()); global_noop!(ZwpLinuxDmabufV1); @@ -515,6 +531,7 @@ impl Server { dh, state: State::default(), client: None, + decorations_global, } } @@ -542,6 +559,7 @@ impl Server { } pub fn get_surface_data(&self, surface_id: SurfaceId) -> Option<&SurfaceData> { + println!("{:?}", self.state.surfaces); self.state.surfaces.get(&surface_id) } @@ -549,6 +567,10 @@ impl Server { self.state.last_surface_id } + pub fn created_surfaces(&self) -> &[SurfaceId] { + &self.state.created_surfaces + } + #[track_caller] pub fn last_created_output(&self) -> WlOutput { self.state @@ -893,6 +915,27 @@ impl Server { pub fn tablet(&mut self) -> &ZwpTabletV2 { self.state.tablet.as_ref().expect("No tablet created") } + + pub fn force_decoration_mode( + &mut self, + surface: SurfaceId, + mode: zxdg_toplevel_decoration_v1::Mode, + ) { + let toplevel = self.state.get_toplevel(surface); + toplevel + .decoration + .as_mut() + .expect("Missing toplevel decoration") + .0 + .configure(mode); + self.display.flush_clients().unwrap(); + } + + pub fn disable_decorations_global(&self) { + self.display + .handle() + .remove_global::(self.decorations_global.clone()); + } } #[derive(Clone, Eq, PartialEq, Debug)] @@ -930,6 +973,7 @@ impl Write for TransferFd { simple_global_dispatch!(WlShm); simple_global_dispatch!(WlCompositor); +simple_global_dispatch!(WlSubcompositor); simple_global_dispatch!(XdgWmBase); simple_global_dispatch!(ZxdgOutputManagerV1); simple_global_dispatch!(ZwpTabletManagerV2); @@ -1629,7 +1673,7 @@ impl Dispatch for State { || match data.role.as_ref().unwrap() { SurfaceRole::Toplevel(t) => t.toplevel.is_alive(), SurfaceRole::Popup(p) => p.popup.is_alive(), - SurfaceRole::Cursor => false, + _ => unreachable!() }; if role_alive { client.kill( @@ -1875,12 +1919,44 @@ impl Dispatch for State { }, ); state.last_surface_id = Some(SurfaceId(id)); + state.created_surfaces.push(SurfaceId(id)); } _ => unreachable!(), } } } +impl Dispatch for State { + fn request( + state: &mut Self, + _: &Client, + _: &WlSubcompositor, + request: ::Request, + _: &(), + _: &DisplayHandle, + data_init: &mut wayland_server::DataInit<'_, Self>, + ) { + use proto::wl_subcompositor::Request::*; + match request { + GetSubsurface { + id, + surface, + parent, + } => { + let surface_id = SurfaceId::from(&surface); + let data = state.surfaces.get_mut(&surface_id).unwrap(); + data.role = Some(SurfaceRole::Subsurface(Subsurface { + parent: SurfaceId::from(&parent), + subsurface: data_init.init(id, surface_id), + position: Vec2::default(), + })); + } + Destroy => {} + other => todo!("unhandled subcompositor request {other:?}"), + } + } +} + impl Dispatch for State { fn request( state: &mut Self, @@ -1940,6 +2016,32 @@ impl Dispatch for State { } } +impl Dispatch for State { + fn request( + state: &mut Self, + _: &Client, + _: &WlSubsurface, + request: ::Request, + surface_id: &SurfaceId, + _: &DisplayHandle, + _: &mut wayland_server::DataInit<'_, Self>, + ) { + use proto::wl_subsurface::Request::*; + match request { + SetPosition { x, y } => { + let data = state.surfaces.get_mut(surface_id).unwrap(); + let Some(SurfaceRole::Subsurface(subsurface)) = &mut data.role else { + unreachable!(); + }; + + subsurface.position = Vec2 { x, y }; + } + SetDesync | Destroy => {} + other => todo!("unhandled wl_subsurface request: {other:?}"), + } + } +} + impl Dispatch for State { fn request( _: &mut Self, @@ -2137,6 +2239,8 @@ impl Dispatch for State { .as_mut() .map(|(_, decoration)| decoration) .unwrap() = Some(mode); + + resource.configure(mode); } else { resource.post_error( zxdg_toplevel_decoration_v1::Error::Orphaned,