feat(wayland): corner-radius protocol support

This commit is contained in:
Ashley Wulber 2025-09-24 15:48:18 -04:00
parent 54a69a0523
commit e73bbddbca
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
10 changed files with 252 additions and 62 deletions

View file

@ -278,7 +278,7 @@ tiny-skia = { version = "0.11", default-features = false, features = [
"std",
"simd",
] }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b" }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086" }
softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" }
syntect = "5.2"
tokio = "1.0"

View file

@ -33,6 +33,16 @@ pub enum Action {
Subsurface(subsurface::Action),
/// Keyboard inhibit shortcuts
InhibitShortcuts(bool),
/// Rounded corners in logical space
RoundedCorners(iced_core::window::Id, Option<CornerRadius>),
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct CornerRadius {
pub top_left: u32,
pub top_right: u32,
pub bottom_left: u32,
pub bottom_right: u32,
}
impl Debug for Action {
@ -57,6 +67,9 @@ impl Debug for Action {
Action::InhibitShortcuts(v) => {
f.debug_tuple("InhibitShortcuts").field(v).finish()
}
Action::RoundedCorners(id, v) => {
f.debug_tuple("RoundedCorners").field(id).field(v).finish()
}
}
}
}

View file

@ -0,0 +1,20 @@
use iced_futures::core::{border::Radius, window};
use iced_runtime::{
self,
platform_specific::{
self,
wayland::{self, CornerRadius},
},
task, Action, Task,
};
pub fn corner_radius(
id: window::Id,
corners: Option<CornerRadius>,
) -> Task<()> {
task::oneshot(|_| {
Action::PlatformSpecific(platform_specific::Action::Wayland(
wayland::Action::RoundedCorners(id, corners),
))
})
}

View file

@ -1,6 +1,7 @@
//! Interact with the wayland objects of your application.
pub mod activation;
pub mod corner_radius;
pub mod keyboard_shortcuts_inhibit;
pub mod layer_surface;
pub mod overlap_notify;

View file

@ -18,7 +18,9 @@ use crate::{
subsurface_widget::SubsurfaceState,
};
use cctk::sctk::reexports::calloop_wayland_source::WaylandSource;
use cctk::{
cosmic_protocols::corner_radius::v1::client::cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, sctk::reexports::calloop_wayland_source::WaylandSource, toplevel_info::ToplevelInfoState
};
use cctk::{
sctk::{
activation::ActivationState,
@ -37,7 +39,6 @@ use cctk::{
shell::{WaylandSurface, wlr_layer::LayerShell, xdg::XdgShell},
shm::Shm,
},
toplevel_info::ToplevelInfoState,
toplevel_management::ToplevelManagerState,
};
use raw_window_handle::HasDisplayHandle;
@ -126,7 +127,10 @@ impl SctkEventLoop {
window,
id,
) => {
state.windows.push(SctkWindow { window, id });
state.windows.push(SctkWindow { window, id, corner_radius: Default::default() });
if let Some(v) = state.pending_corner_radius.remove(&id) {
_ = state.handle_action(iced_runtime::platform_specific::wayland::Action::RoundedCorners(id, Some(v)));
}
}
crate::platform_specific::Action::RemoveWindow(
id,
@ -201,7 +205,7 @@ impl SctkEventLoop {
let half_h = size.height / 2.;
match settings.gravity {
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::None => {
// center on
// center on
loc.x -= half_w;
loc.y -= half_h;
},
@ -306,6 +310,11 @@ impl SctkEventLoop {
&registry_state,
&qh,
),
corner_radius_manager: registry_state.bind_one::<CosmicCornerRadiusManagerV1, _, _>(
&qh,
1..=1,
(),
).ok(),
toplevel_manager: ToplevelManagerState::try_new(
&registry_state,
&qh,
@ -348,6 +357,7 @@ impl SctkEventLoop {
token_senders: HashMap::new(),
overlap_notifications: HashMap::new(),
subsurface_state: None,
pending_corner_radius: HashMap::new(),
},
_features: Default::default(),
};

View file

@ -1,8 +1,5 @@
use crate::{
Control,
sctk_event::KeyboardEventVariant,
subsurface_widget::SubsurfaceState,
wayland::SubsurfaceInstance,
handlers::{
activation::IcedRequestData,
overlap::{OverlapNotificationV1, OverlapNotifyV1},
@ -17,6 +14,9 @@ use crate::{
sctk_event::{LayerSurfaceEventVariant, SctkEvent},
},
},
sctk_event::KeyboardEventVariant,
subsurface_widget::SubsurfaceState,
wayland::SubsurfaceInstance,
};
use iced_futures::{
core::{Rectangle, Size},
@ -27,7 +27,7 @@ use std::{
collections::{HashMap, HashSet},
convert::Infallible,
fmt::Debug,
sync::{atomic::AtomicU32, Arc, Mutex},
sync::{Arc, Mutex, atomic::AtomicU32},
thread::panicking,
time::Duration,
};
@ -37,66 +37,85 @@ use winit::{
platform::wayland::WindowExtWayland,
};
use cctk::{
cosmic_protocols::{
corner_radius::v1::client::{
cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1,
cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
},
overlap_notify::v1::client::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1,
},
sctk::{
activation::{ActivationState, RequestData},
compositor::CompositorState,
error::GlobalError,
globals::GlobalData,
output::OutputState,
reexports::{
calloop::{LoopHandle, timer::TimeoutAction},
client::{
Connection, Proxy, QueueHandle, delegate_noop,
protocol::{
wl_keyboard::WlKeyboard,
wl_output::WlOutput,
wl_region::WlRegion,
wl_seat::WlSeat,
wl_subsurface::WlSubsurface,
wl_surface::{self, WlSurface},
wl_touch::WlTouch,
},
},
},
registry::RegistryState,
seat::{
SeatState,
keyboard::KeyEvent,
pointer::{CursorIcon, PointerData, ThemedPointer},
touch::TouchData,
},
session_lock::{
SessionLock, SessionLockState, SessionLockSurface,
SessionLockSurfaceConfigure,
},
shell::{
WaylandSurface,
wlr_layer::{
Anchor, KeyboardInteractivity, Layer, LayerShell, LayerSurface,
LayerSurfaceConfigure, SurfaceKind,
},
xdg::{
XdgPositioner, XdgShell,
popup::{Popup, PopupConfigure},
},
},
shm::{Shm, multi::MultiPool},
},
toplevel_info::ToplevelInfoState,
toplevel_management::ToplevelManagerState,
};
use iced_runtime::{
core::{self, Point, touch},
keyboard::Modifiers,
platform_specific::{
self,
wayland::{
layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, popup::SctkPopupSettings, subsurface::{self, SctkSubsurfaceSettings}, Action
Action, CornerRadius,
layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings},
popup::SctkPopupSettings,
subsurface::{self, SctkSubsurfaceSettings},
},
},
};
use cctk::{cosmic_protocols::overlap_notify::v1::client::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1, sctk::{
activation::{ActivationState, RequestData},
compositor::CompositorState,
error::GlobalError,
globals::GlobalData,
output::OutputState,
reexports::{
calloop::{LoopHandle, timer::TimeoutAction},
client::{
Connection, Proxy, QueueHandle, delegate_noop,
protocol::{
wl_keyboard::WlKeyboard,
wl_output::WlOutput,
wl_region::WlRegion,
wl_seat::WlSeat,
wl_subsurface::WlSubsurface,
wl_surface::{self, WlSurface},
wl_touch::WlTouch,
},
},
},
registry::RegistryState,
seat::{
SeatState,
keyboard::KeyEvent,
pointer::{CursorIcon, PointerData, ThemedPointer},
touch::TouchData,
},
session_lock::{
SessionLock, SessionLockState, SessionLockSurface,
SessionLockSurfaceConfigure,
},
shell::{
WaylandSurface,
wlr_layer::{
Anchor, KeyboardInteractivity, Layer, LayerShell, LayerSurface,
LayerSurfaceConfigure, SurfaceKind,
},
xdg::{
XdgPositioner, XdgShell,
popup::{Popup, PopupConfigure},
},
},
shm::{multi::MultiPool, Shm},
}, toplevel_info::ToplevelInfoState, toplevel_management::ToplevelManagerState};
use wayland_protocols::{
wp::{
fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1, keyboard_shortcuts_inhibit::zv1::client::{zwp_keyboard_shortcuts_inhibit_manager_v1, zwp_keyboard_shortcuts_inhibitor_v1}, viewporter::client::wp_viewport::WpViewport
fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1,
keyboard_shortcuts_inhibit::zv1::client::{
zwp_keyboard_shortcuts_inhibit_manager_v1,
zwp_keyboard_shortcuts_inhibitor_v1,
},
viewporter::client::wp_viewport::WpViewport,
},
xdg::shell::client::xdg_surface::XdgSurface,
xdg::shell::client::{xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel},
};
pub static TOKEN_CTR: AtomicU32 = AtomicU32::new(0);
@ -311,9 +330,22 @@ pub struct SctkPopupData {
pub(crate) grab: bool,
}
#[derive(Debug)]
pub struct MyCosmicCornerRadiusToplevelV1(CosmicCornerRadiusToplevelV1);
impl Drop for MyCosmicCornerRadiusToplevelV1 {
fn drop(&mut self) {
self.0.destroy();
}
}
#[derive(Debug, Clone)]
pub struct SctkCornerRadius(Arc<MyCosmicCornerRadiusToplevelV1>);
pub struct SctkWindow {
pub(crate) window: Arc<dyn winit::window::Window>,
pub(crate) id: core::window::Id,
pub(crate) corner_radius: Option<(SctkCornerRadius, CornerRadius)>,
}
impl SctkWindow {
@ -348,6 +380,19 @@ impl SctkWindow {
.unwrap();
XdgSurface::from_id(conn, id).unwrap()
}
pub fn xdg_toplevel(&self, conn: &Connection) -> XdgToplevel {
let window_handle = self.window.xdg_toplevel().unwrap();
let id = unsafe {
ObjectId::from_ptr(
XdgToplevel::interface(),
window_handle.as_ptr().cast(),
)
}
.unwrap();
XdgToplevel::from_id(conn, id).unwrap()
}
}
pub(crate) enum FrameStatus {
@ -436,6 +481,9 @@ pub struct SctkState {
pub(crate) inhibitor: Option<zwp_keyboard_shortcuts_inhibitor_v1::ZwpKeyboardShortcutsInhibitorV1>,
pub(crate) inhibited: bool,
pub(crate) inhibitor_manager: Option<zwp_keyboard_shortcuts_inhibit_manager_v1::ZwpKeyboardShortcutsInhibitManagerV1>,
pub(crate) corner_radius_manager: Option<CosmicCornerRadiusManagerV1>,
pub(crate) pending_corner_radius: HashMap<core::window::Id, CornerRadius>
}
/// An error that occurred while running an application.
@ -722,6 +770,7 @@ impl SctkState {
else {
return Err(PopupCreationError::ParentMissing);
};
(
&parent_window.wl_surface(&self.connection),
Popup::from_surface(
@ -1468,7 +1517,7 @@ impl SctkState {
Action::InhibitShortcuts(v) => {
if let Some(manager) = self.inhibitor_manager.as_ref() {
if let Some(inhibit) = self.inhibitor.take() {
inhibit.destroy();
inhibit.destroy();
}
if v {
self.inhibitor = self.seats.iter().next()
@ -1476,6 +1525,45 @@ impl SctkState {
}
}
}
Action::RoundedCorners(id, v) => {
if let Some(manager) = self.corner_radius_manager.as_ref() {
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
if let Some(radii) = v {
if let Some((protocol_object, corner_radii)) = w.corner_radius.as_mut() {
if *corner_radii != radii {
protocol_object.0.0.set_radius(radii.top_left,
radii.top_right,
radii.bottom_right,
radii.bottom_left,);
*corner_radii = radii.clone();
}
} else {
let toplevel = w.xdg_toplevel(&self.connection);
let protocol_object = manager.get_corner_radius(&toplevel, &self.queue_handle, ());
protocol_object.set_radius(
radii.top_left,
radii.top_right,
radii.bottom_right,
radii.bottom_left,
);
w.corner_radius = Some((SctkCornerRadius(Arc::new(MyCosmicCornerRadiusToplevelV1( protocol_object))), radii.clone()));
}
} else {
if let Some(old) = w.corner_radius.take() {
old.0.0.as_ref().0.unset_radius();
}
w.corner_radius = None;
}
} else {
if let Some(v) = v{
_ = self.pending_corner_radius.insert(id, v);
} else {
_ = self.pending_corner_radius.remove(&id);
}
}
}
}
};
Ok(())
}

View file

@ -11,14 +11,14 @@ pub mod toplevel;
pub mod wp_fractional_scaling;
pub mod wp_viewporter;
use cctk::sctk::{
use cctk::{sctk::{
delegate_registry, delegate_shm,
output::OutputState,
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
seat::SeatState,
shm::{Shm, ShmHandler},
};
}};
use crate::platform_specific::wayland::event_loop::state::SctkState;

View file

@ -2,11 +2,11 @@ use crate::platform_specific::wayland::{
event_loop::state::SctkState,
sctk_event::{KeyboardEventVariant, SctkEvent},
};
use cctk::sctk::reexports::client::Proxy;
use cctk::sctk::{
delegate_keyboard,
seat::keyboard::{KeyboardHandler, Keysym},
};
use cctk::sctk::{reexports::client::Proxy, seat::keyboard::RawModifiers};
impl KeyboardHandler for SctkState {
fn enter(
@ -232,7 +232,8 @@ impl KeyboardHandler for SctkState {
keyboard: &cctk::sctk::reexports::client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
modifiers: cctk::sctk::seat::keyboard::Modifiers,
layout: u32,
_raw_modifiers: RawModifiers,
_layout: u32,
) {
let (is_active, my_seat) =
match self.seats.iter_mut().enumerate().find_map(|(i, s)| {
@ -268,6 +269,17 @@ impl KeyboardHandler for SctkState {
}
}
}
fn repeat_key(
&mut self,
_conn: &wayland_client::Connection,
_qh: &wayland_client::QueueHandle<Self>,
_keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard,
_serial: u32,
_event: cctk::sctk::seat::keyboard::KeyEvent,
) {
// TODO
}
}
delegate_keyboard!(SctkState);

View file

@ -0,0 +1,45 @@
use cctk::{sctk, cosmic_protocols::{
corner_radius::v1::client::{
cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1,
cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1,
},
overlap_notify::v1::client::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1,
}};
use sctk::reexports::{
client::{Connection, Dispatch, Proxy},
};
use crate::event_loop::state::SctkState;
use crate::platform_specific::wayland::SctkEvent;
impl Dispatch<CosmicCornerRadiusManagerV1, ()> for SctkState {
fn event(
_state: &mut Self,
_proxy: &CosmicCornerRadiusManagerV1,
_event: <CosmicCornerRadiusManagerV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &sctk::reexports::client::QueueHandle<Self>,
) {}
}
impl
Dispatch<
CosmicCornerRadiusToplevelV1,
(),
> for SctkState
{
fn event(
state: &mut Self,
_proxy: &CosmicCornerRadiusToplevelV1,
event: <CosmicCornerRadiusToplevelV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &sctk::reexports::client::QueueHandle<Self>,
) {
match event {
_ => unimplemented!()
}
}
}

View file

@ -1,3 +1,4 @@
pub mod layer;
pub mod xdg_popup;
pub mod xdg_window;
pub mod corner_radius;