From 5ee2fa911a760d63dbe777a12c061060f873ab7d Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 17 Apr 2026 17:57:48 +0200 Subject: [PATCH] wayland: Support cosmic-corner-radius v2 --- Cargo.lock | 2 +- src/backend/render/cursor.rs | 2 + src/backend/render/mod.rs | 29 +- src/backend/render/wayland/mod.rs | 7 +- src/shell/element/surface.rs | 30 +- src/wayland/handlers/corner_radius.rs | 109 ++++- src/wayland/protocols/corner_radius.rs | 647 +++++++++++++++++++++---- 7 files changed, 679 insertions(+), 147 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36146511..ed27a859 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -906,7 +906,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.2.0" -source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#0d0b0b573fe4836ede4c237dadd365033cd28296" +source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#32283d76a8d0342da74c4cc022a533c52dcf378f" dependencies = [ "bitflags 2.11.0", "wayland-backend", diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 360991de..0585e664 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -212,6 +212,7 @@ pub fn draw_surface_cursor( 1.0, false, [0; 4], + None, blur_strength, Kind::Cursor, &mut |elem| push(elem.into(), h), @@ -247,6 +248,7 @@ pub fn draw_dnd_icon( 1.0, false, [0; 4], + None, blur_strength, FRAME_TIME_FILTER, push, diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 60917112..ac49af7d 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -39,6 +39,7 @@ use crate::{ wayland::{ handlers::{ compositor::FRAME_TIME_FILTER, + corner_radius::{pad_rect, surface_corners, surface_padding}, data_device::get_dnd_icon, image_copy_capture::{FrameHolder, SessionData, render_session}, }, @@ -78,7 +79,7 @@ use smithay::{ utils::{ IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Time, Transform, }, - wayland::{dmabuf::get_dmabuf, session_lock::LockSurface}, + wayland::{compositor::with_states, dmabuf::get_dmabuf, session_lock::LockSurface}, }; #[cfg(feature = "debug")] @@ -838,6 +839,11 @@ where let mut geometry = popup.geometry().as_global(); geometry.loc += location; + let radii = with_states(popup.wl_surface(), |states| { + surface_corners(states, geometry.size.as_logical()) + }) + .unwrap_or([0; 4]); + push_render_elements_from_surface_tree( renderer, popup.wl_surface(), @@ -849,7 +855,8 @@ where Scale::from(scale), 1.0, false, - [0; 4], + radii, + None, blur_strength, FRAME_TIME_FILTER, &mut |elem| { @@ -868,6 +875,17 @@ where } => { let mut geometry = layer.geometry().as_global(); geometry.loc += location; + let geometry = geometry.to_local(output).as_logical(); + + let padded = with_states(layer.wl_surface(), |states| { + surface_padding(states, geometry.size) + .and_then(|padding| pad_rect(geometry, &padding)) + }) + .unwrap_or(geometry); + let radii = with_states(layer.wl_surface(), |states| { + surface_corners(states, padded.size) + }) + .unwrap_or([0; 4]); push_render_elements_from_surface_tree( renderer, @@ -876,11 +894,12 @@ where .to_local(output) .as_logical() .to_physical_precise_round(scale), - geometry.to_local(output).as_logical().to_f64(), + geometry.to_f64(), Scale::from(scale), 1.0, false, - [0; 4], + radii, + padded.to_f64(), blur_strength, FRAME_TIME_FILTER, &mut |elem| { @@ -909,6 +928,7 @@ where 1.0, false, [0; 4], + None, blur_strength, FRAME_TIME_FILTER, &mut |elem| elements.extend(crop_to_output(elem.into()).map(Into::into)), @@ -1056,6 +1076,7 @@ fn session_lock_elements( 1.0, false, [0; 4], + None, 0, FRAME_TIME_FILTER, push, diff --git a/src/backend/render/wayland/mod.rs b/src/backend/render/wayland/mod.rs index 9ac1109c..9f07d08b 100644 --- a/src/backend/render/wayland/mod.rs +++ b/src/backend/render/wayland/mod.rs @@ -35,6 +35,7 @@ pub fn push_render_elements_from_surface_tree( alpha: f32, should_clip: bool, radii: [u8; 4], + blur_geometry: impl Into>>, blur_strength: usize, kind: impl Into, push_above: &mut dyn FnMut(SurfaceRenderElement), @@ -44,7 +45,8 @@ pub fn push_render_elements_from_surface_tree( R::TextureId: Clone + 'static, { let location = location.into().to_f64(); - let geometry = geometry.into().to_f64(); + let geometry = geometry.into(); + let blur_geometry = blur_geometry.into(); let scale = scale.into(); let kind = kind.into(); let mut passed_main = false; @@ -87,10 +89,11 @@ pub fn push_render_elements_from_surface_tree( renderer, surface, states, location, alpha, kind, ) { Ok(Some(surface)) => { + let blur_geo = blur_geometry.unwrap_or(geometry); blur = BlurElement::from_surface( renderer, states, - geometry, + blur_geo, scale.x, radii, blur_strength, diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 4a88d2df..4db3e60a 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -4,9 +4,7 @@ use crate::{ wayland::{SurfaceRenderElement, push_render_elements_from_surface_tree}, }, shell::focus::target::PointerFocusTarget, - wayland::{ - handlers::compositor::frame_time_filter_fn, protocols::corner_radius::CacheableCorners, - }, + wayland::handlers::{compositor::frame_time_filter_fn, corner_radius::surface_corners}, }; use std::{ borrow::Cow, @@ -193,22 +191,7 @@ impl CosmicSurface { pub fn corner_radius(&self, geometry_size: Size) -> Option<[u8; 4]> { self.wl_surface().and_then(|surface| { - with_states(&surface, |states| { - let mut guard = states.cached_state.get::(); - - // guard against corner radius being too large, potentially disconnecting the outline - let half_min_dim = - u8::try_from(geometry_size.w.min(geometry_size.h) / 2).unwrap_or(u8::MAX); - - let corners = guard.current().0?; - - Some([ - corners.top_left.min(half_min_dim), - corners.top_right.min(half_min_dim), - corners.bottom_right.min(half_min_dim), - corners.bottom_left.min(half_min_dim), - ]) - }) + with_states(&surface, |states| surface_corners(states, geometry_size)) }) } @@ -880,6 +863,10 @@ impl CosmicSurface { .to_physical_precise_round(scale); let mut geometry = popup.geometry().to_f64(); geometry.loc += location.to_f64().to_logical(scale) + popup_offset.to_f64(); + let radii = with_states(popup.wl_surface(), |states| { + surface_corners(states, geometry.size.to_i32_round()) + }) + .unwrap_or([0; 4]); push_render_elements_from_surface_tree( renderer, @@ -889,7 +876,8 @@ impl CosmicSurface { scale, alpha, false, - [0; 4], + radii, + None, blur_strength, scanout_kind_eval(None, scanout_node), push, @@ -934,6 +922,7 @@ impl CosmicSurface { alpha, should_clip, radii, + None, blur_strength, scanout_kind_eval(scanout_override, scanout_node), push_above, @@ -954,6 +943,7 @@ impl CosmicSurface { alpha, should_clip, radii, + None, blur_strength, scanout_kind_eval(scanout_override, scanout_node), push_above, diff --git a/src/wayland/handlers/corner_radius.rs b/src/wayland/handlers/corner_radius.rs index dbcae2dc..140b86ff 100644 --- a/src/wayland/handlers/corner_radius.rs +++ b/src/wayland/handlers/corner_radius.rs @@ -1,7 +1,9 @@ -use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1; +use smithay::utils::{Logical, Rectangle, Size}; +use smithay::wayland::compositor::SurfaceData; use crate::wayland::protocols::corner_radius::{ - CornerRadiusData, CornerRadiusHandler, CornerRadiusState, delegate_corner_radius, + CacheableCorners, CacheablePadding, CornerRadiusData, CornerRadiusHandler, CornerRadiusState, + CornerRadiusSurface, delegate_corner_radius, }; use crate::state::State; @@ -11,21 +13,25 @@ impl CornerRadiusHandler for State { &mut self.common.corner_radius_state } - fn set_corner_radius( - &mut self, - _: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, - data: &CornerRadiusData, - ) { + fn set_corner_radius(&mut self, data: &CornerRadiusData) { if force_redraw(self, data).is_none() { tracing::warn!("Failed to force redraw for corner radius change."); } } - fn unset_corner_radius( - &mut self, - _: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, - data: &CornerRadiusData, - ) { + fn unset_corner_radius(&mut self, data: &CornerRadiusData) { + if force_redraw(self, data).is_none() { + tracing::warn!("Failed to force redraw for corner radius reset."); + } + } + + fn set_padding(&mut self, data: &CornerRadiusData) { + if force_redraw(self, data).is_none() { + tracing::warn!("Failed to force redraw for corner radius change."); + } + } + + fn unset_padding(&mut self, data: &CornerRadiusData) { if force_redraw(self, data).is_none() { tracing::warn!("Failed to force redraw for corner radius reset."); } @@ -34,16 +40,83 @@ impl CornerRadiusHandler for State { fn force_redraw(state: &mut State, data: &CornerRadiusData) -> Option<()> { let guard = data.lock().unwrap(); + let shell = state.common.shell.read(); - let toplevel = guard.toplevel.upgrade().ok()?; - - let surface = state.common.xdg_shell_state.get_toplevel(&toplevel)?; - - let guard = state.common.shell.read(); - let output = guard.visible_output_for_surface(surface.wl_surface())?; + let output = match &guard.surface { + CornerRadiusSurface::Toplevel(toplevel) => { + let toplevel = toplevel.upgrade().ok()?; + let surface = state.common.xdg_shell_state.get_toplevel(&toplevel)?; + shell.visible_output_for_surface(surface.wl_surface())? + } + CornerRadiusSurface::Popup(popup) => { + let popup = popup.upgrade().ok()?; + let surface = state.common.xdg_shell_state.get_popup(&popup)?; + shell.visible_output_for_surface(surface.wl_surface())? + } + CornerRadiusSurface::Layer(layer) => { + let layer = layer.upgrade().ok()?; + let surface = state + .common + .layer_shell_state + .layer_surfaces() + .find(|l| l.shell_surface() == &layer)?; + shell.visible_output_for_surface(surface.wl_surface())? + } + }; state.backend.schedule_render(output); Some(()) } +pub fn surface_corners(states: &SurfaceData, size: Size) -> Option<[u8; 4]> { + let mut guard = states.cached_state.get::(); + + // guard against corner radius being too large, potentially disconnecting the outline + let half_min_dim = u8::try_from(size.w.min(size.h) / 2).unwrap_or(u8::MAX); + let corners = guard.current().0?; + + Some([ + u8::try_from(corners.top_left) + .unwrap_or(u8::MAX) + .min(half_min_dim), + u8::try_from(corners.top_right) + .unwrap_or(u8::MAX) + .min(half_min_dim), + u8::try_from(corners.bottom_right) + .unwrap_or(u8::MAX) + .min(half_min_dim), + u8::try_from(corners.bottom_left) + .unwrap_or(u8::MAX) + .min(half_min_dim), + ]) +} + +pub fn surface_padding(states: &SurfaceData, size: Size) -> Option<[i32; 4]> { + let mut guard = states.cached_state.get::(); + + // guard against padding being too large + let half_min_dim = size.w.min(size.h) / 2; + let padding = guard.current().0?; + + Some([ + padding.top.min(half_min_dim), + padding.right.min(half_min_dim), + padding.bottom.min(half_min_dim), + padding.left.min(half_min_dim), + ]) +} + +pub fn pad_rect( + mut rect: Rectangle, + padding: &[i32; 4], +) -> Option> { + rect.size.h = rect.size.h.checked_sub(padding[0])?; + rect.loc.x += padding[0]; + rect.size.w = rect.size.w.checked_sub(padding[1])?; + rect.size.h = rect.size.h.checked_sub(padding[2])?; + rect.size.w = rect.size.w.checked_sub(padding[3])?; + rect.loc.y += padding[3]; + Some(rect) +} + delegate_corner_radius!(State); diff --git a/src/wayland/protocols/corner_radius.rs b/src/wayland/protocols/corner_radius.rs index 5baafa23..f0edf343 100644 --- a/src/wayland/protocols/corner_radius.rs +++ b/src/wayland/protocols/corner_radius.rs @@ -1,12 +1,21 @@ +use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_layer_v1::{ + self, CosmicCornerRadiusLayerV1, +}; use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1; use cosmic_protocols::corner_radius::v1::server::{ cosmic_corner_radius_manager_v1, cosmic_corner_radius_toplevel_v1, }; -use smithay::utils::HookId; +use smithay::desktop::utils::bbox_from_surface_tree; +use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_popup::XdgPopup; +use smithay::reexports::wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; +use smithay::reexports::wayland_server::New; +use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +use smithay::utils::{HookId, Logical, Point, Rectangle}; use smithay::wayland::compositor::Cacheable; use smithay::wayland::compositor::add_pre_commit_hook; use smithay::wayland::compositor::with_states; -use smithay::wayland::shell::xdg::SurfaceCachedState; +use smithay::wayland::shell::wlr_layer::WlrLayerShellHandler; +use smithay::wayland::shell::xdg::{SurfaceCachedState, XdgShellSurfaceUserData}; use smithay::{ reexports::{ wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel, @@ -18,6 +27,7 @@ use std::sync::Mutex; use wayland_backend::server::GlobalId; type ToplevelHookId = Mutex)>>; +type LayerHookId = Mutex)>>; #[derive(Debug)] pub struct CornerRadiusState { @@ -33,12 +43,13 @@ impl CornerRadiusState { + Dispatch< cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData, - > + CornerRadiusHandler + > + Dispatch + + CornerRadiusHandler + 'static, { let global = dh .create_global::( - 1, + 2, (), ); CornerRadiusState { @@ -52,18 +63,12 @@ impl CornerRadiusState { } } -pub trait CornerRadiusHandler: XdgShellHandler { +pub trait CornerRadiusHandler: XdgShellHandler + WlrLayerShellHandler { fn corner_radius_state(&mut self) -> &mut CornerRadiusState; - fn set_corner_radius( - &mut self, - toplevel: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, - data: &CornerRadiusData, - ); - fn unset_corner_radius( - &mut self, - toplevel: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, - data: &CornerRadiusData, - ); + fn set_corner_radius(&mut self, data: &CornerRadiusData); + fn unset_corner_radius(&mut self, data: &CornerRadiusData); + fn set_padding(&mut self, data: &CornerRadiusData); + fn unset_padding(&mut self, data: &CornerRadiusData); } impl GlobalDispatch @@ -72,6 +77,7 @@ where D: GlobalDispatch + Dispatch + Dispatch + + Dispatch + CornerRadiusHandler + 'static, { @@ -96,6 +102,7 @@ where D: GlobalDispatch + Dispatch + Dispatch + + Dispatch + CornerRadiusHandler + 'static, { @@ -115,6 +122,71 @@ where } cosmic_corner_radius_manager_v1::Request::GetCornerRadius { id, toplevel } => { if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { + new_xdg( + surface.wl_surface(), + CornerRadiusSurface::Toplevel(surface.xdg_toplevel().downgrade()), + resource, + data_init, + id, + ) + } // TODO: can this fail? + } + cosmic_corner_radius_manager_v1::Request::GetCornerRadiusSurface { id, surface } => { + if let Some(toplevel) = + state + .xdg_shell_state() + .toplevel_surfaces() + .iter() + .find(|toplevel| { + toplevel + .xdg_toplevel() + .data::() + .unwrap() + .xdg_surface() + == &surface + }) + { + new_xdg( + toplevel.wl_surface(), + CornerRadiusSurface::Toplevel(toplevel.xdg_toplevel().downgrade()), + resource, + data_init, + id, + ) + } else if let Some(popup) = + state + .xdg_shell_state() + .popup_surfaces() + .iter() + .find(|popup| { + popup + .xdg_popup() + .data::() + .unwrap() + .xdg_surface() + == &surface + }) + { + new_xdg( + popup.wl_surface(), + CornerRadiusSurface::Popup(popup.xdg_popup().downgrade()), + resource, + data_init, + id, + ) + } else { + resource.post_error( + cosmic_corner_radius_manager_v1::Error::NoRole as u32, + "xdg_surface has no known role object".to_string(), + ); + } + } + cosmic_corner_radius_manager_v1::Request::GetCornerRadiusLayer { id, layer } => { + if let Some(surface) = state + .shell_state() + .layer_surfaces() + .find(|surface| surface.shell_surface() == &layer) + { let radius_exists = with_states(surface.wl_surface(), |surface_data| { let hook_id = surface_data .data_map @@ -124,63 +196,33 @@ where }); if radius_exists.unwrap_or_default() { resource.post_error( - cosmic_corner_radius_manager_v1::Error::CornerRadiusExists as u32, - format!("{resource:?} CosmicCornerRadiusToplevelV1 object already exists for the surface"), - ); + cosmic_corner_radius_manager_v1::Error::CornerRadiusExists as u32, + format!( + "{resource:?} CosmicCornerRadiusToplevelV1 object already exists for the surface" + ), + ); } let data = Mutex::new(CornerRadiusInternal { - toplevel: toplevel.downgrade(), + surface: CornerRadiusSurface::Layer(layer.downgrade()), corners: None, + padding: None, }); let obj = data_init.init(id, data); let obj_downgrade = obj.downgrade(); let needs_hook = radius_exists.is_none(); if needs_hook { - let hook_id = add_pre_commit_hook::( - surface.wl_surface(), - move |_, _dh, surface| { - let corner_radii_too_big = with_states(surface, |surface_data| { - let corners = *surface_data - .cached_state - .get::() - .pending(); - surface_data - .cached_state - .get::() - .pending() - .geometry - .zip(corners.0.as_ref()) - .is_some_and(|(geo, corners)| { - let half_min_dim = - u8::try_from(geo.size.w.min(geo.size.h) / 2) - .unwrap_or(u8::MAX); - corners.top_right > half_min_dim - || corners.top_left > half_min_dim - || corners.bottom_right > half_min_dim - || corners.bottom_left > half_min_dim - }) - }); - - if corner_radii_too_big { - obj.post_error( - cosmic_corner_radius_toplevel_v1::Error::RadiusTooLarge - as u32, - format!("{obj:?} corner radius too large"), - ); - } - }, - ); - + let hook_id = + add_pre_commit_hook::(surface.wl_surface(), layer_radius_hook); with_states(surface.wl_surface(), |surface_data| { let hook_ids = surface_data .data_map - .get_or_insert_threadsafe(|| ToplevelHookId::new(None)); + .get_or_insert_threadsafe(|| LayerHookId::new(None)); let mut guard = hook_ids.lock().unwrap(); *guard = Some((hook_id, obj_downgrade)); }); } - } + } // TODO: can this fail? } _ => unimplemented!(), } @@ -197,6 +239,55 @@ where } } +fn new_xdg( + wl_surface: &WlSurface, + surface: CornerRadiusSurface, + resource: &cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, + data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>, + id: New, +) where + D: GlobalDispatch + + Dispatch + + Dispatch + + CornerRadiusHandler + + 'static, +{ + let radius_exists = with_states(wl_surface, |surface_data| { + let hook_id = surface_data + .data_map + .get_or_insert_threadsafe(|| ToplevelHookId::new(None)); + let guard = hook_id.lock().unwrap(); + guard.as_ref().map(|(_, t)| t.upgrade().is_ok()) + }); + if radius_exists.unwrap_or_default() { + resource.post_error( + cosmic_corner_radius_manager_v1::Error::CornerRadiusExists as u32, + format!( + "{resource:?} CosmicCornerRadiusToplevelV1 object already exists for the surface" + ), + ); + } + let data = Mutex::new(CornerRadiusInternal { + surface, + corners: None, + padding: None, + }); + let obj = data_init.init(id, data); + let obj_downgrade = obj.downgrade(); + + let needs_hook = radius_exists.is_none(); + if needs_hook { + let hook_id = add_pre_commit_hook::(wl_surface, xdg_radius_hook); + with_states(wl_surface, |surface_data| { + let hook_ids = surface_data + .data_map + .get_or_insert_threadsafe(|| ToplevelHookId::new(None)); + let mut guard = hook_ids.lock().unwrap(); + *guard = Some((hook_id, obj_downgrade)); + }); + } +} + impl Dispatch for CornerRadiusState @@ -221,30 +312,35 @@ where let mut guard = data.lock().unwrap(); guard.corners = None; - let Ok(toplevel) = guard.toplevel.upgrade() else { + let Some(wl_surface) = (match &guard.surface { + CornerRadiusSurface::Toplevel(toplevel) => toplevel + .upgrade() + .ok() + .and_then(|toplevel| state.xdg_shell_state().get_toplevel(&toplevel)) + .map(|toplevel| toplevel.wl_surface().clone()), + CornerRadiusSurface::Popup(popup) => popup + .upgrade() + .ok() + .and_then(|popup| state.xdg_shell_state().get_popup(&popup)) + .map(|popup| popup.wl_surface().clone()), + CornerRadiusSurface::Layer(_) => unreachable!(), + }) else { return; }; - if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { - with_states(surface.wl_surface(), |surface_data| { - if let Some(hook_ids_mutex) = surface_data.data_map.get::() - { - let mut hook_id = hook_ids_mutex.lock().unwrap(); - *hook_id = None; - } - }); - } + with_states(&wl_surface, |surface_data| { + if let Some(hook_ids_mutex) = surface_data.data_map.get::() { + let mut hook_id = hook_ids_mutex.lock().unwrap(); + *hook_id = None; + } - if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { - with_states(surface.wl_surface(), |s| { - let mut cached = s.cached_state.get::(); - let pending = cached.pending(); - *pending = CacheableCorners(None); - }); - } + let mut cached = surface_data.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(None); + }); drop(guard); - state.unset_corner_radius(resource, data); + state.unset_corner_radius(data); } cosmic_corner_radius_toplevel_v1::Request::SetRadius { top_left, @@ -254,66 +350,388 @@ where } => { let mut guard = data.lock().unwrap(); guard.set_corner_radius(top_left, top_right, bottom_right, bottom_left); - let Ok(toplevel) = guard.toplevel.upgrade() else { + + let Some(wl_surface) = (match &guard.surface { + CornerRadiusSurface::Toplevel(toplevel) => toplevel + .upgrade() + .ok() + .and_then(|toplevel| state.xdg_shell_state().get_toplevel(&toplevel)) + .map(|toplevel| toplevel.wl_surface().clone()), + CornerRadiusSurface::Popup(popup) => popup + .upgrade() + .ok() + .and_then(|popup| state.xdg_shell_state().get_popup(&popup)) + .map(|popup| popup.wl_surface().clone()), + CornerRadiusSurface::Layer(_) => unreachable!(), + }) else { resource.post_error( cosmic_corner_radius_toplevel_v1::Error::ToplevelDestroyed as u32, format!("{:?} No toplevel found", resource), ); return; }; - - if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { - with_states(surface.wl_surface(), |s| { - let mut cached = s.cached_state.get::(); - let pending = cached.pending(); - *pending = CacheableCorners(guard.corners); - }); - } + with_states(&wl_surface, |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(guard.corners); + }); drop(guard); - state.set_corner_radius(resource, data); + state.set_corner_radius(data); } cosmic_corner_radius_toplevel_v1::Request::UnsetRadius => { let mut guard = data.lock().unwrap(); guard.corners = None; - let Ok(toplevel) = guard.toplevel.upgrade() else { + + let Some(wl_surface) = (match &guard.surface { + CornerRadiusSurface::Toplevel(toplevel) => toplevel + .upgrade() + .ok() + .and_then(|toplevel| state.xdg_shell_state().get_toplevel(&toplevel)) + .map(|toplevel| toplevel.wl_surface().clone()), + CornerRadiusSurface::Popup(popup) => popup + .upgrade() + .ok() + .and_then(|popup| state.xdg_shell_state().get_popup(&popup)) + .map(|popup| popup.wl_surface().clone()), + CornerRadiusSurface::Layer(_) => unreachable!(), + }) else { resource.post_error( cosmic_corner_radius_toplevel_v1::Error::ToplevelDestroyed as u32, format!("{:?} No toplevel found", resource), ); + return; }; - if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { - with_states(surface.wl_surface(), |s| { - let mut cached = s.cached_state.get::(); - let pending = cached.pending(); - *pending = CacheableCorners(None); - }); - } + with_states(&wl_surface, |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(None); + }); drop(guard); - state.unset_corner_radius(resource, data); + state.unset_corner_radius(data); } _ => unimplemented!(), } } } +impl Dispatch + for CornerRadiusState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + CornerRadiusHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1, + request: ::Request, + data: &CornerRadiusData, + _dhandle: &DisplayHandle, + _data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>, + ) { + match request { + cosmic_corner_radius_layer_v1::Request::Destroy => { + let mut guard = data.lock().unwrap(); + guard.corners = None; + + let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else { + unreachable!("corner_radius_layer without layer shell?"); + }; + let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| { + state + .shell_state() + .layer_surfaces() + .find(|s| s.shell_surface() == &layer) + }) else { + resource.post_error( + cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32, + format!("{:?} No layer found", resource), + ); + return; + }; + + with_states(layer_surface.wl_surface(), |surface_data| { + if let Some(hook_ids_mutex) = surface_data.data_map.get::() { + let mut hook_id = hook_ids_mutex.lock().unwrap(); + *hook_id = None; + } + + let mut cached = surface_data.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(None); + + let mut cached = surface_data.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheablePadding(None); + }); + drop(guard); + + state.unset_corner_radius(data); + state.unset_padding(data); + } + cosmic_corner_radius_layer_v1::Request::SetRadius { + top_left, + top_right, + bottom_right, + bottom_left, + } => { + let mut guard = data.lock().unwrap(); + guard.set_corner_radius(top_left, top_right, bottom_right, bottom_left); + + let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else { + unreachable!("corner_radius_layer without layer shell?"); + }; + let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| { + state + .shell_state() + .layer_surfaces() + .find(|s| s.shell_surface() == &layer) + }) else { + resource.post_error( + cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32, + format!("{:?} No layer found", resource), + ); + return; + }; + + with_states(layer_surface.wl_surface(), |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(guard.corners); + }); + drop(guard); + + state.set_corner_radius(data); + } + cosmic_corner_radius_layer_v1::Request::UnsetRadius => { + let mut guard = data.lock().unwrap(); + guard.corners = None; + + let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else { + unreachable!("corner_radius_layer without layer shell?"); + }; + let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| { + state + .shell_state() + .layer_surfaces() + .find(|s| s.shell_surface() == &layer) + }) else { + resource.post_error( + cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32, + format!("{:?} No layer found", resource), + ); + return; + }; + + with_states(layer_surface.wl_surface(), |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(None); + }); + drop(guard); + + state.unset_corner_radius(data); + } + cosmic_corner_radius_layer_v1::Request::SetPadding { + top, + right, + bottom, + left, + } => { + let mut guard = data.lock().unwrap(); + guard.set_padding(top, right, bottom, left); + + let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else { + unreachable!("corner_radius_layer without layer shell?"); + }; + let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| { + state + .shell_state() + .layer_surfaces() + .find(|s| s.shell_surface() == &layer) + }) else { + resource.post_error( + cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32, + format!("{:?} No layer found", resource), + ); + return; + }; + + with_states(layer_surface.wl_surface(), |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheablePadding(guard.padding); + }); + drop(guard); + + state.set_padding(data); + } + cosmic_corner_radius_layer_v1::Request::UnsetPadding => { + let mut guard = data.lock().unwrap(); + guard.corners = None; + + let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else { + unreachable!("corner_radius_layer without layer shell?"); + }; + let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| { + state + .shell_state() + .layer_surfaces() + .find(|s| s.shell_surface() == &layer) + }) else { + resource.post_error( + cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32, + format!("{:?} No layer found", resource), + ); + return; + }; + + with_states(layer_surface.wl_surface(), |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheablePadding(None); + }); + drop(guard); + + state.unset_padding(data); + } + _ => unimplemented!(), + } + } +} + +fn xdg_radius_hook(_state: &mut D, _dh: &DisplayHandle, surface: &WlSurface) { + with_states(surface, |surface_data| { + let corners = *surface_data + .cached_state + .get::() + .pending(); + if surface_data + .cached_state + .get::() + .pending() + .geometry + .zip(corners.0.as_ref()) + .is_some_and(|(geo, corners)| { + let half_min_dim = (geo.size.w.min(geo.size.h) / 2) as u32; + corners.top_right > half_min_dim + || corners.top_left > half_min_dim + || corners.bottom_right > half_min_dim + || corners.bottom_left > half_min_dim + }) + && let Some(hook) = surface_data.data_map.get::() + { + let hook_ref = hook.lock().unwrap(); + if let Some((_, obj)) = hook_ref.as_ref() + && let Ok(obj) = obj.upgrade() + { + obj.post_error( + cosmic_corner_radius_toplevel_v1::Error::RadiusTooLarge as u32, + format!("{obj:?} corner radius too large"), + ); + } + } + }); +} + +fn pad_rect( + mut rect: Rectangle, + padding: &Padding, +) -> Option> { + rect.size.h = rect.size.h.checked_sub(padding.top)?; + rect.loc.x += padding.top; + rect.size.w = rect.size.w.checked_sub(padding.left)?; + rect.loc.y += padding.left; + rect.size.h = rect.size.h.checked_sub(padding.bottom)?; + rect.size.w = rect.size.w.checked_sub(padding.right)?; + Some(rect) +} + +fn layer_radius_hook(_state: &mut D, _dh: &DisplayHandle, surface: &WlSurface) { + let bbox = bbox_from_surface_tree(surface, Point::default()); + with_states(surface, |surface_data| { + let corners = *surface_data + .cached_state + .get::() + .pending(); + let padding = *surface_data + .cached_state + .get::() + .pending(); + let empty = Padding::default(); + let Some(padded_box) = pad_rect(bbox, padding.0.as_ref().unwrap_or(&empty)) else { + if let Some(hook) = surface_data.data_map.get::() { + let hook_ref = hook.lock().unwrap(); + if let Some((_, obj)) = hook_ref.as_ref() + && let Ok(obj) = obj.upgrade() + { + obj.post_error( + cosmic_corner_radius_layer_v1::Error::PaddingTooLarge as u32, + format!("{obj:?} padding too large"), + ); + } + } + return; + }; + + if corners.0.as_ref().is_some_and(|corners| { + let half_min_dim = (padded_box.size.w.min(padded_box.size.h) / 2) as u32; + corners.top_right > half_min_dim + || corners.top_left > half_min_dim + || corners.bottom_right > half_min_dim + || corners.bottom_left > half_min_dim + }) && let Some(hook) = surface_data.data_map.get::() + { + let hook_ref = hook.lock().unwrap(); + if let Some((_, obj)) = hook_ref.as_ref() + && let Ok(obj) = obj.upgrade() + { + obj.post_error( + cosmic_corner_radius_layer_v1::Error::RadiusTooLarge as u32, + format!("{obj:?} corner radius too large"), + ); + } + } + }); +} + pub type CornerRadiusData = Mutex; +#[derive(Debug)] +pub enum CornerRadiusSurface { + Toplevel(Weak), + Popup(Weak), + Layer(Weak), +} + #[derive(Debug)] pub struct CornerRadiusInternal { - pub toplevel: Weak, + pub surface: CornerRadiusSurface, pub corners: Option, + pub padding: Option, } #[derive(Debug, Copy, Clone)] pub struct Corners { - pub top_left: u8, - pub top_right: u8, - pub bottom_right: u8, - pub bottom_left: u8, + pub top_left: u32, + pub top_right: u32, + pub bottom_right: u32, + pub bottom_left: u32, +} + +#[derive(Debug, Copy, Clone, Default)] +pub struct Padding { + pub top: i32, + pub right: i32, + pub bottom: i32, + pub left: i32, } #[derive(Default, Debug, Copy, Clone)] @@ -328,6 +746,18 @@ impl Cacheable for CacheableCorners { } } +#[derive(Default, Debug, Copy, Clone)] +pub struct CacheablePadding(pub Option); + +impl Cacheable for CacheablePadding { + fn commit(&mut self, _dh: &DisplayHandle) -> Self { + *self + } + fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) { + *into = self; + } +} + impl CornerRadiusInternal { fn set_corner_radius( &mut self, @@ -337,13 +767,23 @@ impl CornerRadiusInternal { bottom_left: u32, ) { let corners = Corners { - top_left: top_left.clamp(u8::MIN as u32, u8::MAX as u32) as u8, - top_right: top_right.clamp(u8::MIN as u32, u8::MAX as u32) as u8, - bottom_right: bottom_right.clamp(u8::MIN as u32, u8::MAX as u32) as u8, - bottom_left: bottom_left.clamp(u8::MIN as u32, u8::MAX as u32) as u8, + top_left, + top_right, + bottom_right, + bottom_left, }; self.corners = Some(corners); } + + fn set_padding(&mut self, top: i32, right: i32, bottom: i32, left: i32) { + let padding = Padding { + top, + right, + bottom, + left, + }; + self.padding = Some(padding); + } } macro_rules! delegate_corner_radius { @@ -357,6 +797,9 @@ macro_rules! delegate_corner_radius { smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1: CornerRadiusData ] => $crate::wayland::protocols::corner_radius::CornerRadiusState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1: CornerRadiusData + ] => $crate::wayland::protocols::corner_radius::CornerRadiusState); }; } pub(crate) use delegate_corner_radius;