feat: corner-radius protocol support

This commit is contained in:
Ashley Wulber 2025-09-24 15:11:16 -04:00 committed by Victoria Brekenfeld
parent b232a4b24a
commit b3aa10436a
11 changed files with 1130 additions and 490 deletions

View file

@ -195,6 +195,7 @@ impl IndicatorShader {
key: impl Into<Key>,
mut element_geo: Rectangle<i32, Local>,
thickness: u8,
radius: u8,
alpha: f32,
active_window_hint: [f32; 3],
) -> PixelShaderElement {
@ -207,7 +208,8 @@ impl IndicatorShader {
key,
element_geo,
thickness,
thickness * 2,
// FIXME, this seems to look OK, but I doubt it is correct
radius + thickness,
alpha,
active_window_hint,
)

View file

@ -14,11 +14,19 @@ use crate::{
CosmicMapped, CosmicSurface, Direction, ManagedLayer,
},
utils::prelude::*,
wayland::protocols::toplevel_info::{toplevel_enter_output, toplevel_enter_workspace},
wayland::protocols::{
corner_radius::CornerRadiusData,
toplevel_info::{toplevel_enter_output, toplevel_enter_workspace},
},
};
use smithay::{
reexports::wayland_server::{Resource, Weak},
wayland::{compositor::with_states, seat::WaylandFocus},
};
use calloop::LoopHandle;
use cosmic::theme::CosmicTheme;
use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1;
use smithay::{
backend::{
input::ButtonState,
@ -109,6 +117,29 @@ impl MoveGrabState {
- scaling_offset;
let active_window_hint = crate::theme::active_window_hint(theme);
let radius = self
.window()
.wl_surface()
.and_then(|surface| {
with_states(&surface, |s| {
let d = s
.data_map
.get::<Mutex<Weak<CosmicCornerRadiusToplevelV1>>>()?;
let guard = d.lock().unwrap();
let weak_data = guard.upgrade().ok()?;
let corners = weak_data.data::<CornerRadiusData>()?;
let guard = corners.lock().unwrap();
// TODO support multiple radius values
Some(guard.top_left)
})
})
.unwrap_or(self.indicator_thickness);
tracing::error!("{radius}");
let focus_element = if self.indicator_thickness > 0 {
Some(
CosmicMappedRenderElement::from(IndicatorShader::focus_element(
@ -125,6 +156,7 @@ impl MoveGrabState {
)
.as_local(),
self.indicator_thickness,
radius,
alpha,
[
active_window_hint.red,

View file

@ -1,11 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::wayland::protocols::corner_radius::CornerRadiusData;
use smithay::{
reexports::wayland_server::{Resource, Weak},
wayland::compositor::with_states,
};
use std::{
collections::{HashMap, VecDeque},
sync::atomic::{AtomicBool, Ordering},
sync::{
atomic::{AtomicBool, Ordering},
Mutex,
},
time::{Duration, Instant},
};
use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1;
use cosmic_settings_config::shortcuts::action::ResizeDirection;
use keyframe::{ease, functions::EaseInOutCubic};
use smithay::{
@ -1589,13 +1598,34 @@ impl FloatingLayout {
}
let active_window_hint = crate::theme::active_window_hint(theme);
let radius = elem
.active_window()
.wl_surface()
.and_then(|surface| {
with_states(&surface, |s| {
let d = s
.data_map
.get::<Mutex<Weak<CosmicCornerRadiusToplevelV1>>>()?;
let guard = d.lock().unwrap();
let weak_data = guard.upgrade().ok()?;
let corners = weak_data.data::<CornerRadiusData>()?;
let guard = corners.lock().unwrap();
// TODO support multiple radius values
Some(guard.top_left)
})
})
.unwrap_or(indicator_thickness);
if indicator_thickness > 0 {
let element = IndicatorShader::focus_element(
renderer,
Key::Window(Usage::FocusIndicator, elem.key()),
geometry,
indicator_thickness,
radius,
alpha,
[
active_window_hint.red,

View file

@ -29,6 +29,7 @@ use crate::{
wayland::{
handlers::xdg_shell::popup::get_popup_toplevel,
protocols::{
corner_radius::CornerRadiusData,
toplevel_info::{
toplevel_enter_output, toplevel_enter_workspace, toplevel_leave_output,
toplevel_leave_workspace,
@ -37,7 +38,9 @@ use crate::{
},
},
};
use smithay::{reexports::wayland_server::Resource, wayland::compositor::with_states};
use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1;
use cosmic_settings_config::shortcuts::action::{FocusDirection, ResizeDirection};
use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree};
use keyframe::{
@ -59,13 +62,13 @@ use smithay::{
desktop::{layer_map_for_output, space::SpaceElement, PopupKind, WindowSurfaceType},
input::Seat,
output::Output,
reexports::wayland_server::Client,
reexports::wayland_server::{Client, Weak as WsWeak},
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Size},
wayland::{compositor::add_blocker, seat::WaylandFocus},
};
use std::{
collections::{HashMap, VecDeque},
sync::{Arc, Weak},
sync::{Arc, Mutex, Weak},
time::{Duration, Instant},
};
use tracing::trace;
@ -5015,6 +5018,27 @@ where
x => Some(x),
}
}));
let radius = mapped
.active_window()
.wl_surface()
.and_then(|surface| {
with_states(&surface, |s| {
let d = s
.data_map
.get::<Mutex<WsWeak<CosmicCornerRadiusToplevelV1>>>()?;
let guard = d.lock().unwrap();
let weak_data = guard.upgrade().ok()?;
let corners = weak_data.data::<CornerRadiusData>()?;
let guard = corners.lock().unwrap();
// TODO support multiple radius values
Some(guard.top_left)
})
})
.unwrap_or(indicator_thickness);
if is_minimizing && indicator_thickness > 0 {
elements.push(CosmicMappedRenderElement::FocusIndicator(
IndicatorShader::focus_element(
@ -5022,6 +5046,7 @@ where
Key::Window(Usage::FocusIndicator, mapped.clone().key()),
geo,
indicator_thickness,
radius,
alpha,
[window_hint.red, window_hint.green, window_hint.blue],
),
@ -5283,12 +5308,33 @@ where
)
.unwrap();
let radius = window
.wl_surface()
.and_then(|surface| {
with_states(&surface, |s| {
let d = s
.data_map
.get::<Mutex<WsWeak<CosmicCornerRadiusToplevelV1>>>()?;
let guard = d.lock().unwrap();
let weak_data = guard.upgrade().ok()?;
let corners = weak_data.data::<CornerRadiusData>()?;
let guard = corners.lock().unwrap();
// TODO support multiple radius values
Some(guard.top_left)
})
})
.unwrap_or(indicator_thickness);
swap_elements.push(CosmicMappedRenderElement::FocusIndicator(
IndicatorShader::focus_element(
renderer,
Key::from(swapping_stack_surface_id.clone()),
swap_geo,
4,
radius,
transition.unwrap_or(1.0),
[window_hint.red, window_hint.green, window_hint.blue],
),
@ -5359,7 +5405,30 @@ where
group_color,
));
}
let radius = match data {
Data::Mapped { mapped, .. } => mapped
.active_window()
.wl_surface()
.and_then(|surface| {
with_states(&surface, |s| {
let d = s
.data_map
.get::<Mutex<WsWeak<CosmicCornerRadiusToplevelV1>>>()?;
let guard = d.lock().unwrap();
let weak_data = guard.upgrade().ok()?;
let corners = weak_data.data::<CornerRadiusData>()?;
let guard = corners.lock().unwrap();
// TODO support multiple radius values
Some(guard.top_left)
})
})
.unwrap_or(indicator_thickness),
_ => 1,
};
if !swap_desc
.as_ref()
.map(|desc| desc.stack_window.is_some())
@ -5381,6 +5450,7 @@ where
} else {
indicator_thickness
},
radius,
alpha,
[window_hint.red, window_hint.green, window_hint.blue],
));

View file

@ -16,6 +16,7 @@ use crate::{
protocols::{
a11y::A11yState,
atspi::AtspiState,
corner_radius::CornerRadiusState,
drm::WlDrmState,
image_capture_source::ImageCaptureSourceState,
output_configuration::OutputConfigurationState,
@ -212,6 +213,7 @@ pub struct Common {
// wayland state
pub compositor_state: CompositorState,
pub corner_radius_state: CornerRadiusState,
pub data_device_state: DataDeviceState,
pub dmabuf_state: DmabufState,
pub fractional_scale_state: FractionalScaleManagerState,
@ -602,6 +604,7 @@ impl State {
let clock = Clock::new();
let config = Config::load(&handle);
let compositor_state = CompositorState::new::<Self>(dh);
let corner_radius_state = CornerRadiusState::new::<Self>(dh);
let data_device_state = DataDeviceState::new::<Self>(dh);
let dmabuf_state = DmabufState::new();
let fractional_scale_state = FractionalScaleManagerState::new::<State>(dh);
@ -709,6 +712,7 @@ impl State {
theme: cosmic::theme::system_preference(),
compositor_state,
corner_radius_state,
data_device_state,
dmabuf_state,
fractional_scale_state,

View file

@ -0,0 +1,44 @@
use smithay::wayland::shell::xdg::ToplevelSurface;
use crate::wayland::protocols::corner_radius::{
delegate_corner_radius, CornerRadiusData, CornerRadiusHandler,
};
use crate::state::State;
impl CornerRadiusHandler for State {
fn corner_radius_state(
&mut self,
) -> &mut crate::wayland::protocols::corner_radius::CornerRadiusState {
&mut self.common.corner_radius_state
}
fn toplevel_from_resource(
&mut self,
toplevel: &smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel,
) -> Option<ToplevelSurface> {
self.common.xdg_shell_state.get_toplevel(toplevel)
}
fn set_corner_radius(
&mut self,
_toplevel: &cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
_data: &CornerRadiusData,
_top_left: u32,
_top_right: u32,
_bottom_right: u32,
_bottom_left: u32,
) {
// TODO force redraw? of focus element?
}
fn unset_corner_radius(
&mut self,
_toplevel: &cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
_data: &CornerRadiusData,
) {
// TODO force redraw?
}
}
delegate_corner_radius!(State);

View file

@ -5,6 +5,7 @@ pub mod alpha_modifier;
pub mod atspi;
pub mod buffer;
pub mod compositor;
pub mod corner_radius;
pub mod data_control;
pub mod data_device;
pub mod decoration;

View file

@ -0,0 +1,236 @@
use cosmic_protocols::corner_radius::v1::server::{
cosmic_corner_radius_manager_v1, cosmic_corner_radius_toplevel_v1,
};
use smithay::reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel,
wayland_server::{Client, Dispatch, DisplayHandle, GlobalDispatch, Resource},
};
use smithay::wayland::compositor::with_states;
use smithay::wayland::shell::xdg::ToplevelSurface;
use std::sync::Mutex;
use wayland_backend::server::GlobalId;
#[derive(Debug)]
pub struct CornerRadiusState {
instances: Vec<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1>,
global: GlobalId,
}
impl CornerRadiusState {
pub fn new<D>(dh: &DisplayHandle) -> CornerRadiusState
where
D: GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<
cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
CornerRadiusData,
> + CornerRadiusHandler
+ 'static,
{
let global = dh
.create_global::<D, cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, _>(
1,
(),
);
CornerRadiusState {
instances: Vec::new(),
global,
}
}
pub fn global_id(&self) -> GlobalId {
self.global.clone()
}
}
pub trait CornerRadiusHandler {
fn corner_radius_state(&mut self) -> &mut CornerRadiusState;
fn toplevel_from_resource(&mut self, toplevel: &XdgToplevel) -> Option<ToplevelSurface>;
fn set_corner_radius(
&mut self,
toplevel: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
data: &CornerRadiusData,
top_left: u32,
top_right: u32,
bottom_right: u32,
bottom_left: u32,
);
fn unset_corner_radius(
&mut self,
toplevel: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
data: &CornerRadiusData,
);
}
impl<D> GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, (), D>
for CornerRadiusState
where
D: GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData>
+ CornerRadiusHandler
+ 'static,
{
fn bind(
state: &mut D,
_handle: &DisplayHandle,
_client: &Client,
resource: smithay::reexports::wayland_server::New<
cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1,
>,
_global_data: &(),
data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>,
) {
let instance = data_init.init(resource, ());
state.corner_radius_state().instances.push(instance);
}
}
impl<D> Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, (), D>
for CornerRadiusState
where
D: GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData>
+ CornerRadiusHandler
+ 'static,
{
fn request(
state: &mut D,
_client: &Client,
_resource: &cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1,
request: <cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1 as smithay::reexports::wayland_server::Resource>::Request,
_data: &(),
_dhandle: &DisplayHandle,
data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>,
) {
match request {
cosmic_corner_radius_manager_v1::Request::Destroy => {
let corner_radius_state = state.corner_radius_state();
corner_radius_state.instances.retain(|i| i != _resource);
}
cosmic_corner_radius_manager_v1::Request::GetCornerRadius { id, toplevel } => {
let data = CornerRadiusData::default();
let instance = data_init.init(id, data);
if let Some(surface) = state.toplevel_from_resource(&toplevel) {
with_states(surface.wl_surface(), |s| {
let data = s
.data_map
.get_or_insert_threadsafe(|| Mutex::new(instance.downgrade()));
let mut guard = data.lock().unwrap();
*guard = instance.downgrade();
});
}
}
_ => unimplemented!(),
}
}
fn destroyed(
state: &mut D,
_client: wayland_backend::server::ClientId,
resource: &cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1,
_data: &(),
) {
let corner_radius_state = state.corner_radius_state();
corner_radius_state.instances.retain(|i| i != resource);
}
}
impl<D>
Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData, D>
for CornerRadiusState
where
D: GlobalDispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, ()>
+ Dispatch<cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData>
+ CornerRadiusHandler
+ 'static,
{
fn request(
_state: &mut D,
_client: &Client,
resource: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
request: <cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1 as Resource>::Request,
data: &CornerRadiusData,
_dhandle: &DisplayHandle,
_data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>,
) {
match request {
cosmic_corner_radius_toplevel_v1::Request::Destroy => {
// TODO do we want to unset it after it is destroyed or just leave it?
_state.unset_corner_radius(resource, data);
}
cosmic_corner_radius_toplevel_v1::Request::SetRadius {
top_left,
top_right,
bottom_right,
bottom_left,
} => {
{
let mut guard = data.lock().unwrap();
guard.set_corner_radius(
top_left as u8,
top_right as u8,
bottom_right as u8,
bottom_left as u8,
);
}
_state.set_corner_radius(
resource,
data,
top_left,
top_right,
bottom_right,
bottom_left,
);
}
cosmic_corner_radius_toplevel_v1::Request::UnsetRadius => {
_state.unset_corner_radius(resource, data);
}
_ => unimplemented!(),
}
}
}
pub type CornerRadiusData = Mutex<CornerRadiusInternal>;
#[derive(Debug, Default)]
pub struct CornerRadiusInternal {
pub top_left: u8,
pub top_right: u8,
pub bottom_right: u8,
pub bottom_left: u8,
}
impl CornerRadiusInternal {
fn set_corner_radius(
&mut self,
top_left: u8,
top_right: u8,
bottom_right: u8,
bottom_left: u8,
) {
self.top_left = top_left;
self.top_right = top_right;
self.bottom_right = bottom_right;
self.bottom_left = bottom_left;
}
}
macro_rules! delegate_corner_radius {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1: ()
] => $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_manager_v1::CosmicCornerRadiusManagerV1: ()
] => $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_toplevel_v1::CosmicCornerRadiusToplevelV1: CornerRadiusData
] => $crate::wayland::protocols::corner_radius::CornerRadiusState);
};
}
pub(crate) use delegate_corner_radius;

View file

@ -2,6 +2,7 @@
pub mod a11y;
pub mod atspi;
pub mod corner_radius;
pub mod drm;
pub mod image_capture_source;
pub mod output_configuration;