feat: subsurfaces

This commit is contained in:
Ashley Wulber 2025-03-14 11:16:25 -04:00 committed by Ashley Wulber
parent 0f37c9922d
commit 93bc4bbd88
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
33 changed files with 1898 additions and 2651 deletions

View file

@ -45,6 +45,7 @@ pub enum SurfaceIdWrapper {
Window(window::Id),
Popup(window::Id),
SessionLock(window::Id),
Subsurface(window::Id),
}
impl SurfaceIdWrapper {
pub fn inner(&self) -> window::Id {
@ -53,6 +54,7 @@ impl SurfaceIdWrapper {
SurfaceIdWrapper::Window(id) => *id,
SurfaceIdWrapper::Popup(id) => *id,
SurfaceIdWrapper::SessionLock(id) => *id,
SurfaceIdWrapper::Subsurface(id) => *id,
}
}
}

View file

@ -5,3 +5,4 @@ pub mod layer_surface;
pub mod overlap_notify;
pub mod popup;
pub mod session_lock;
pub mod subsurface;

View file

@ -1,10 +1,10 @@
use crate::core::window::Id as SurfaceId;
use cctk::sctk::reexports::client::protocol::wl_output::WlOutput;
use iced_runtime::{
self,
platform_specific::{self, wayland},
task, Action, Task,
};
use cctk::sctk::reexports::client::protocol::wl_output::WlOutput;
pub fn lock<Message>() -> Task<Message> {
task::effect(Action::PlatformSpecific(

View file

@ -0,0 +1,28 @@
use crate::core::window::Id as SurfaceId;
pub use cctk::sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer};
use iced_runtime::{
self,
platform_specific::{
self,
wayland::{self, subsurface::SctkSubsurfaceSettings},
},
task, Action, Task,
};
pub fn get_subsurface<Message>(
subsurface: SctkSubsurfaceSettings,
) -> Task<Message> {
task::effect(Action::PlatformSpecific(
platform_specific::Action::Wayland(wayland::Action::Subsurface(
wayland::subsurface::Action::Subsurface { subsurface },
)),
))
}
pub fn destroy_subsurface<Message>(id: SurfaceId) -> Task<Message> {
task::effect(Action::PlatformSpecific(
platform_specific::Action::Wayland(wayland::Action::Subsurface(
wayland::subsurface::Action::Destroy { id },
)),
))
}

View file

@ -73,47 +73,3 @@ pub fn modifiers_to_native(mods: Modifiers) -> keyboard::Modifiers {
// }
native_mods
}
// pub fn keysym_to_vkey(keysym: RawKeysym) -> Option<KeyCode> {
// key_conversion.get(&keysym).cloned()
// }
// pub(crate) fn cursor_icon(cursor: winit::window::CursorIcon) -> CursorIcon {
// match cursor {
// CursorIcon::Default => todo!(),
// CursorIcon::ContextMenu => todo!(),
// CursorIcon::Help => todo!(),
// CursorIcon::Pointer => todo!(),
// CursorIcon::Progress => todo!(),
// CursorIcon::Wait => todo!(),
// CursorIcon::Cell => todo!(),
// CursorIcon::Crosshair => todo!(),
// CursorIcon::Text => todo!(),
// CursorIcon::VerticalText => todo!(),
// CursorIcon::Alias => todo!(),
// CursorIcon::Copy => todo!(),
// CursorIcon::Move => todo!(),
// CursorIcon::NoDrop => todo!(),
// CursorIcon::NotAllowed => todo!(),
// CursorIcon::Grab => todo!(),
// CursorIcon::Grabbing => todo!(),
// CursorIcon::EResize => todo!(),
// CursorIcon::NResize => todo!(),
// CursorIcon::NeResize => todo!(),
// CursorIcon::NwResize => todo!(),
// CursorIcon::SResize => todo!(),
// CursorIcon::SeResize => todo!(),
// CursorIcon::SwResize => todo!(),
// CursorIcon::WResize => todo!(),
// CursorIcon::EwResize => todo!(),
// CursorIcon::NsResize => todo!(),
// CursorIcon::NeswResize => todo!(),
// CursorIcon::NwseResize => todo!(),
// CursorIcon::ColResize => todo!(),
// CursorIcon::RowResize => todo!(),
// CursorIcon::AllScroll => todo!(),
// CursorIcon::ZoomIn => todo!(),
// CursorIcon::ZoomOut => todo!(),
// _ => todo!(),
// }
// }

View file

@ -41,7 +41,7 @@ use cctk::{
toplevel_management::ToplevelManagerState,
};
use raw_window_handle::HasDisplayHandle;
use state::{FrameStatus, SctkWindow};
use state::{FrameStatus, SctkWindow, send_event};
#[cfg(feature = "a11y")]
use std::sync::{Arc, Mutex};
use std::{
@ -50,7 +50,7 @@ use std::{
};
use tracing::error;
use wayland_backend::client::Backend;
use winit::event_loop::OwnedDisplayHandle;
use winit::{dpi::LogicalSize, event_loop::OwnedDisplayHandle};
use self::state::SctkState;
@ -120,7 +120,36 @@ impl SctkEventLoop {
id,
) => {
// TODO clean up popups matching the window.
state.windows.retain(|window| id != window.id);
if let Some(pos) = state
.windows
.iter()
.position(|window| id == window.id)
{
let w = state.windows.remove(pos);
for subsurface_id in state
.subsurfaces
.iter()
.enumerate()
.filter_map(|(i, s)| {
(winit::window::WindowId::from_raw(
s.instance.parent.as_ptr()
as usize,
) == w.window.id())
.then_some(i)
})
.collect::<Vec<_>>()
{
let s = state
.subsurfaces
.remove(subsurface_id);
crate::subsurface_widget::remove_iced_subsurface(
&s.instance.wl_surface,
);
send_event(&state.events_sender, &state.proxy,
SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) )
);
}
}
}
crate::platform_specific::Action::SetCursor(
icon,
@ -144,6 +173,57 @@ impl SctkEventLoop {
crate::platform_specific::Action::Dropped(id) => {
_ = state.destroyed.remove(&id.inner());
}
crate::platform_specific::Action::SubsurfaceResize(id, size) => {
// reposition the surface
if let Some(pos) = state
.subsurfaces
.iter()
.position(|window| id == window.id)
{
let subsurface = &mut state.subsurfaces[pos];
let settings = &subsurface.settings;
let mut loc = settings.loc;
let guard = subsurface.common.lock().unwrap();
let size: LogicalSize<f32> = size.to_logical(guard.fractional_scale.unwrap_or(1.));
let half_w = size.width / 2.;
let half_h = size.height / 2.;
match settings.gravity {
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::None => {
// center on
loc.x -= half_w;
loc.y -= half_h;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Top => {
loc.x -= half_w;
loc.y -= size.height;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Bottom => {
loc.x -= half_w;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Left => {
loc.y -= half_h;
loc.x -= size.width;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Right => {
loc.y -= half_h;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft => {
loc.y -= size.height;
loc.x -= size.width;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft => {
loc.x -= size.width;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight => {
loc.y -= size.height;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight => {},
_ => unimplemented!(),
};
subsurface.instance.wl_subsurface.set_position(loc.x as i32, loc.y as i32);
}
send_event(&state.events_sender, &state.proxy, SctkEvent::SubsurfaceEvent(crate::sctk_event::SubsurfaceEventVariant::Resized(id, size)))},
},
calloop::channel::Event::Closed => {
log::info!("Calloop channel closed.");
@ -167,7 +247,7 @@ impl SctkEventLoop {
let (viewporter_state, fractional_scaling_manager) =
match FractionalScalingManager::new(&globals, &qh) {
Ok(m) => {
let viewporter_state =
let viewporter_state: Option<ViewporterState> =
match ViewporterState::new(&globals, &qh) {
Ok(s) => Some(s),
Err(e) => {
@ -227,6 +307,7 @@ impl SctkEventLoop {
layer_surfaces: Vec::new(),
popups: Vec::new(),
lock_surfaces: Vec::new(),
subsurfaces: Vec::new(),
_kbd_focus: None,
touch_points: HashMap::new(),
sctk_events: Vec::new(),
@ -243,6 +324,7 @@ impl SctkEventLoop {
activation_token_ctr: 0,
token_senders: HashMap::new(),
overlap_notifications: HashMap::new(),
subsurface_state: None,
},
_features: Default::default(),
};
@ -280,20 +362,23 @@ impl SctkEventLoop {
if let (Ok(wl_subcompositor), Ok(wp_viewporter)) =
(wl_subcompositor, wp_viewporter)
{
let subsurface_state = SubsurfaceState {
wl_compositor,
wl_subcompositor,
wp_viewporter,
wl_shm,
wp_dmabuf,
wp_alpha_modifier,
qh: state.state.queue_handle.clone(),
buffers: HashMap::new(),
unmapped_subsurfaces: Vec::new(),
new_iced_subsurfaces: Vec::new(),
};
state.state.subsurface_state = Some(subsurface_state.clone());
state::send_event(
&state.state.events_sender,
&state.state.proxy,
SctkEvent::Subcompositor(SubsurfaceState {
wl_compositor,
wl_subcompositor,
wp_viewporter,
wl_shm,
wp_dmabuf,
wp_alpha_modifier,
qh: state.state.queue_handle.clone(),
buffers: HashMap::new(),
unmapped_subsurfaces: Vec::new(),
}),
SctkEvent::Subcompositor(subsurface_state),
);
} else {
log::warn!("Subsurfaces not supported.")

View file

@ -1,9 +1,9 @@
use cctk::sctk::reexports::calloop;
use iced_futures::futures::{
channel::mpsc,
task::{Context, Poll},
Sink,
};
use cctk::sctk::reexports::calloop;
use std::pin::Pin;
/// An event loop proxy that implements `Sink`.

View file

@ -1,5 +1,8 @@
use crate::{
Control,
sctk_event::KeyboardEventVariant,
subsurface_widget::SubsurfaceState,
wayland::SubsurfaceInstance,
handlers::{
activation::IcedRequestData,
overlap::{OverlapNotificationV1, OverlapNotifyV1},
@ -15,13 +18,17 @@ use crate::{
},
},
};
use iced_futures::futures::channel::{mpsc, oneshot};
use iced_futures::{
core::{Rectangle, Size},
futures::channel::{mpsc, oneshot},
};
use raw_window_handle::HasWindowHandle;
use std::{
collections::{HashMap, HashSet},
convert::Infallible,
fmt::Debug,
sync::{Arc, Mutex, atomic::AtomicU32},
sync::{atomic::AtomicU32, Arc, Mutex},
thread::panicking,
time::Duration,
};
use wayland_backend::client::ObjectId;
@ -36,9 +43,7 @@ use iced_runtime::{
platform_specific::{
self,
wayland::{
Action,
layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings},
popup::SctkPopupSettings,
layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, popup::SctkPopupSettings, subsurface::{self, SctkSubsurfaceSettings}, Action
},
},
};
@ -87,7 +92,6 @@ use cctk::{cosmic_protocols::overlap_notify::v1::client::zcosmic_overlap_notific
},
shm::{multi::MultiPool, Shm},
}, toplevel_info::ToplevelInfoState, toplevel_management::ToplevelManagerState};
use wayland_protocols::{
wp::{
fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1,
@ -183,6 +187,10 @@ pub enum CommonSurface {
Popup(Popup, Arc<XdgPositioner>),
Layer(LayerSurface),
Lock(SessionLockSurface),
Subsurface {
wl_surface: WlSurface,
wl_subsurface: WlSubsurface,
},
}
impl CommonSurface {
@ -193,6 +201,7 @@ impl CommonSurface {
CommonSurface::Lock(session_lock_surface) => {
session_lock_surface.wl_surface()
}
CommonSurface::Subsurface { wl_surface, .. } => wl_surface,
};
wl_surface
}
@ -241,6 +250,7 @@ pub struct SctkPopup {
pub(crate) data: SctkPopupData,
pub(crate) common: Arc<Mutex<Common>>,
pub(crate) wp_fractional_scale: Option<WpFractionalScaleV1>,
pub(crate) close_with_children: bool,
}
impl SctkPopup {
@ -249,10 +259,19 @@ impl SctkPopup {
self.popup
.xdg_surface()
.set_window_geometry(0, 0, w as i32, h as i32);
self.update_viewport(w, h);
// update positioner
self.data.positioner.set_size(w as i32, h as i32);
self.popup.reposition(&self.data.positioner, token);
}
pub(crate) fn update_viewport(&mut self, w: u32, h: u32) {
let common = self.common.lock().unwrap();
if let Some(viewport) = common.wp_viewport.as_ref() {
// Set inner size without the borders.
viewport.set_destination(w as i32, h as i32);
}
}
}
#[derive(Debug)]
@ -261,8 +280,27 @@ pub struct SctkLockSurface {
pub(crate) session_lock_surface: SessionLockSurface,
pub(crate) last_configure: Option<SessionLockSurfaceConfigure>,
pub(crate) wp_fractional_scale: Option<WpFractionalScaleV1>,
pub(crate) wp_viewport: Option<WpViewport>,
pub(crate) common: Arc<Mutex<Common>>,
pub(crate) output: WlOutput,
}
impl SctkLockSurface {
pub(crate) fn update_viewport(&mut self, w: u32, h: u32) {
let mut common = self.common.lock().unwrap();
common.size = LogicalSize::new(w, h);
if let Some(viewport) = common.wp_viewport.as_ref() {
// Set inner size without the borders.
viewport.set_destination(w as i32, h as i32);
}
}
}
#[derive(Debug)]
pub struct SctkSubsurface {
pub(crate) common: Arc<Mutex<Common>>,
pub(crate) steals_keyboard_focus: bool,
pub(crate) id: core::window::Id,
pub(crate) instance: SubsurfaceInstance,
pub(crate) settings: SctkSubsurfaceSettings,
}
#[derive(Debug)]
@ -271,6 +309,7 @@ pub struct SctkPopupData {
pub(crate) parent: PopupParent,
pub(crate) toplevel: WlSurface,
pub(crate) positioner: Arc<XdgPositioner>,
pub(crate) grab: bool,
}
pub struct SctkWindow {
@ -347,6 +386,7 @@ pub struct SctkState {
pub(crate) windows: Vec<SctkWindow>,
pub(crate) layer_surfaces: Vec<SctkLayerSurface>,
pub(crate) popups: Vec<SctkPopup>,
pub(crate) subsurfaces: Vec<SctkSubsurface>,
pub(crate) lock_surfaces: Vec<SctkLockSurface>,
pub(crate) _kbd_focus: Option<WlSurface>,
pub(crate) touch_points: HashMap<touch::Finger, (WlSurface, Point)>,
@ -390,6 +430,7 @@ pub struct SctkState {
pub(crate) overlap_notify: Option<OverlapNotifyV1>,
pub(crate) toplevel_info: Option<ToplevelInfoState>,
pub(crate) toplevel_manager: Option<ToplevelManagerState>,
pub(crate) subsurface_state: Option<SubsurfaceState>,
pub(crate) activation_token_ctr: u32,
pub(crate) token_senders: HashMap<u32, oneshot::Sender<Option<String>>>,
@ -431,6 +472,22 @@ pub enum LayerSurfaceCreationError {
LayerSurfaceCreationFailed(GlobalError),
}
/// An error that occurred while running an application.
#[derive(Debug, thiserror::Error)]
pub enum SubsurfaceCreationError {
/// Subsurface creation failed
#[error("Subsurface creation failed")]
CreationFailed(GlobalError),
/// The specified parent is missing
#[error("The specified parent is missing")]
ParentMissing,
/// Subsurfaces are unsupported
#[error("Subsurfaces are unsupported")]
Unsupported,
}
pub(crate) fn receive_frame(
frame_status: &mut HashMap<ObjectId, FrameStatus>,
s: &WlSurface,
@ -460,6 +517,18 @@ impl SctkState {
) {
let mut id = None;
for subsurface in &self.subsurfaces {
if subsurface.instance.parent != surface.id() {
continue;
}
self.sctk_events.push(SctkEvent::SurfaceScaleFactorChanged(
scale_factor,
surface.clone(),
subsurface.id,
));
}
if let Some(popup) = self
.popups
.iter_mut()
@ -688,6 +757,19 @@ impl SctkState {
log::error!("Can't take grab on popup. Missing serial.");
}
}
if let Some(z) = settings.input_zone {
let region = self
.compositor_state
.wl_compositor()
.create_region(&self.queue_handle, ());
region.add(
z.x.round() as i32,
z.y.round() as i32,
z.width.round() as i32,
z.height.round() as i32,
);
}
popup.xdg_surface().set_window_geometry(
0,
0,
@ -719,11 +801,13 @@ impl SctkState {
parent: parent.clone(),
toplevel: toplevel.clone(),
positioner: positioner.clone(),
grab: settings.grab,
},
last_configure: None,
_pending_requests: Default::default(),
wp_fractional_scale,
common: common.clone(),
close_with_children: settings.close_with_children,
});
Ok((
@ -863,15 +947,16 @@ impl SctkState {
self.fractional_scaling_manager.as_ref().map(|fsm| {
fsm.fractional_scaling(&wl_surface, &self.queue_handle)
});
let common =
Arc::new(Mutex::new(Common::from(LogicalSize::new(1, 1))));
let mut common = Common::from(LogicalSize::new(1, 1));
common.wp_viewport = wp_viewport;
let common = Arc::new(Mutex::new(common));
self.lock_surfaces.push(SctkLockSurface {
id,
session_lock_surface: session_lock_surface.clone(),
last_configure: None,
wp_fractional_scale,
wp_viewport,
common: common.clone(),
output: output.clone(),
});
Some((CommonSurface::Lock(session_lock_surface), common))
} else {
@ -920,6 +1005,25 @@ impl SctkState {
platform_specific::wayland::layer_surface::Action::Destroy(id) => {
if let Some(i) = self.layer_surfaces.iter().position(|l| l.id == id) {
let l = self.layer_surfaces.remove(i);
let (removed, remaining): (Vec<_>, Vec<_>) = self
.subsurfaces
.drain(..)
.partition(|s| {
s.instance.parent == l.surface.wl_surface().id()
});
self.subsurfaces = remaining;
for s in removed
{
crate::subsurface_widget::remove_iced_subsurface(
&s.instance.wl_surface,
);
send_event(&self.events_sender, &self.proxy,
SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) )
);
}
if let Some(destroyed) = self.id_map.remove(&l.surface.wl_surface().id()) {
_ = self.destroyed.insert(destroyed);
}
@ -976,28 +1080,77 @@ impl SctkState {
},
},
Action::Popup(action) => match action {
platform_specific::wayland::popup::Action::Popup { popup, .. } => {
let parent_mismatch = self.popups.last().is_some_and(|p| {
self.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p| *p != popup.parent)
platform_specific::wayland::popup::Action::Popup { popup: settings } => {
// first check existing popup
if let Some(existing) = self.popups.iter().position(|p| p.data.id == settings.id
&& (
self.popups.iter().any(|parent| parent.popup.wl_surface() == p.data.parent.wl_surface() && parent.data.id == settings.parent)
|| self.windows.iter().any(|w| w.id == settings.parent && *p.data.parent.wl_surface() == w.wl_surface(&self.connection))
|| self.layer_surfaces.iter().any(|l| l.id == settings.parent && p.data.parent.wl_surface() == l.surface.wl_surface()))
) {
let existing = &mut self.popups[existing];
let size = if settings.positioner.size.is_none() {
log::info!("No configured popup size");
(1, 1)
} else {
settings.positioner.size.unwrap()
};
let Ok(positioner) = XdgPositioner::new(&self.xdg_shell_state)
.map_err(PopupCreationError::PositionerCreationFailed) else {
log::error!("Failed to create popup positioner");
return Ok(());
};
positioner.set_anchor(settings.positioner.anchor);
positioner.set_anchor_rect(
settings.positioner.anchor_rect.x,
settings.positioner.anchor_rect.y,
settings.positioner.anchor_rect.width,
settings.positioner.anchor_rect.height,
);
if let Ok(constraint_adjustment) =
settings.positioner.constraint_adjustment.try_into()
{
positioner.set_constraint_adjustment(constraint_adjustment);
}
positioner.set_gravity(settings.positioner.gravity);
positioner.set_offset(
settings.positioner.offset.0,
settings.positioner.offset.1,
);
if settings.positioner.reactive {
positioner.set_reactive();
}
positioner.set_size(size.0 as i32, size.1 as i32);
existing.data.positioner = Arc::new(positioner);
existing.set_size(size.0, size.1, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed));
_ = send_event(&self.events_sender, &self.proxy,
SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(size.0, size.1), toplevel_id: existing.data.parent.wl_surface().clone(), parent_id: existing.data.parent.wl_surface().clone(), id: existing.popup.wl_surface().clone() });
return Ok(());
}
let parent_mismatch = self.popups.iter().rev().find(|p| {
self.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p_id|{
*p_id != settings.parent && p.data.grab && settings.grab})
});
if !self.destroyed.is_empty() || parent_mismatch {
if parent_mismatch {
if !self.destroyed.is_empty() || parent_mismatch.is_some() {
if parent_mismatch.is_some() {
for i in 0..self.popups.len() {
let id = self.id_map.get(&self.popups[i].popup.wl_surface().id());
if let Some(id) = id {
if *id != popup.parent {
if *id != settings.parent {
_ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy{id: *id}));
}
}
}
}
if self.pending_popup.replace((popup, 0)).is_none() {
if self.pending_popup.replace((settings, 0)).is_none() {
let timer = cctk::sctk::reexports::calloop::timer::Timer::from_duration(Duration::from_millis(30));
let queue_handle = self.queue_handle.clone();
_ = self.loop_handle.insert_source(timer, move |_, _, state| {
let Some((popup, attempt)) = state.pending_popup.take() else {
let Some((mut popup, attempt)) = state.pending_popup.take() else {
return TimeoutAction::Drop;
};
if !state.destroyed.is_empty() || state.popups.last().is_some_and(|p| {
state.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p| *p != popup.parent)
}) {
@ -1029,7 +1182,7 @@ impl SctkState {
// log::error!("Invalid popup Id {:?}", popup.id);
} else {
self.pending_popup = None;
match self.get_popup(popup) {
match self.get_popup(settings) {
Ok((id, parent_id, toplevel_id, surface, common)) => {
let wl_surface = surface.wl_surface().clone();
receive_frame(&mut self.frame_status, &wl_surface);
@ -1054,31 +1207,51 @@ impl SctkState {
{
Some(p) => self.popups.remove(p),
None => {
log::warn!("No popup to destroy");
log::info!("No popup to destroy");
return Ok(());
},
};
let mut to_destroy = vec![sctk_popup];
while let Some(popup_to_destroy) = to_destroy.last() {
match popup_to_destroy.data.parent.clone() {
PopupParent::LayerSurface(_) | PopupParent::Window(_) => {
break;
}
PopupParent::Popup(popup_to_destroy_first) => {
let popup_to_destroy_first = self
.popups
.iter()
.position(|p| p.popup.wl_surface() == &popup_to_destroy_first)
.unwrap();
let popup_to_destroy_first = self.popups.remove(popup_to_destroy_first);
to_destroy.push(popup_to_destroy_first);
}
}
// TODO optionally destroy parents if they request to be destroyed with children
while let Some(popup_to_destroy_last) = to_destroy.last().and_then(|popup| self
.popups
.iter()
.position(|p| popup.data.parent.wl_surface() == p.popup.wl_surface() && p.close_with_children)) {
let popup_to_destroy_last = self.popups.remove(popup_to_destroy_last);
to_destroy.push(popup_to_destroy_last);
}
to_destroy.reverse();
while let Some(popup_to_destroy_first) = to_destroy.last().and_then(|popup| self
.popups
.iter()
.position(|p| p.data.parent.wl_surface() == popup.popup.wl_surface())) {
let popup_to_destroy_first = self.popups.remove(popup_to_destroy_first);
to_destroy.push(popup_to_destroy_first);
}
for popup in to_destroy.into_iter().rev() {
if let Some(id) = self.id_map.remove(&popup.popup.wl_surface().id()) {
_ = self.destroyed.insert(id);
}
let (removed, remaining): (Vec<_>, Vec<_>) = self
.subsurfaces
.drain(..)
.partition(|s| {
s.instance.parent == popup.popup.wl_surface().id()
});
self.subsurfaces = remaining;
for s in removed
{
crate::subsurface_widget::remove_iced_subsurface(
&s.instance.wl_surface,
);
send_event(&self.events_sender, &self.proxy,
SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) )
);
}
_ = send_event(&self.events_sender, &self.proxy,
SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Done, toplevel_id: popup.data.toplevel.clone(), parent_id: popup.data.parent.wl_surface().clone(), id: popup.popup.wl_surface().clone() });
}
@ -1165,11 +1338,15 @@ impl SctkState {
send_event(&self.events_sender, &self.proxy, SctkEvent::SessionUnlocked);
}
platform_specific::wayland::session_lock::Action::LockSurface { id, output } => {
// Should we panic if the id does not match?
if self.lock_surfaces.iter().any(|s| s.output == output) {
tracing::warn!("Cannot create multiple lock surfaces for a single output.");
return Ok(());
}
// TODO how to handle this when there's no lock?
if let Some((surface, common)) = self.get_lock_surface(id, &output) {
if let Some((surface, _)) = self.get_lock_surface(id, &output) {
let wl_surface = surface.wl_surface();
receive_frame(&mut self.frame_status, &wl_surface);
send_event(&self.events_sender, &self.proxy, SctkEvent::SessionLockSurfaceCreated { queue_handle: self.queue_handle.clone(), surface, native_id: id, common, display: self.connection.display() });
}
}
platform_specific::wayland::session_lock::Action::DestroyLockSurface { id } => {
@ -1179,6 +1356,24 @@ impl SctkState {
})
{
let surface = self.lock_surfaces.remove(i);
let (removed, remaining): (Vec<_>, Vec<_>) = self
.subsurfaces
.drain(..)
.partition(|s| {
s.instance.parent == surface.session_lock_surface.wl_surface().id()
});
self.subsurfaces = remaining;
for s in removed
{
crate::subsurface_widget::remove_iced_subsurface(
&s.instance.wl_surface,
);
send_event(&self.events_sender, &self.proxy,
SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) )
);
}
if let Some(id) = self.id_map.remove(&surface.session_lock_surface.wl_surface().id()) {
_ = self.destroyed.insert(id);
}
@ -1208,9 +1403,254 @@ impl SctkState {
tracing::error!("Overlap notify subscription cannot be created for surface. No matching layer surface found.");
}
},
Action::Subsurface(action) => match action {
platform_specific::wayland::subsurface::Action::Subsurface { subsurface: subsurface_settings } => {
let parent_id = subsurface_settings.parent;
if let Ok((_, parent, subsurface, common_surface, common)) = self.get_subsurface(subsurface_settings.clone()) {
// TODO Ashley: all surfaces should probably have an optional title for a11y if nothing else
receive_frame(&mut self.frame_status, &subsurface);
send_event(&self.events_sender, &self.proxy,
SctkEvent::SubsurfaceEvent (crate::sctk_event::SubsurfaceEventVariant::Created{
parent_id,
parent,
surface: subsurface,
qh: self.queue_handle.clone(),
common_surface,
surface_id: subsurface_settings.id,
common,
display: self.connection.display(),
z: subsurface_settings.z,
})
);
}
},
platform_specific::wayland::subsurface::Action::Destroy { id } => {
let mut destroyed = vec![];
if let Some(subsurface) = self.subsurfaces.iter().position(|s| s.id == id) {
let subsurface = self.subsurfaces.remove(subsurface);
destroyed.push((subsurface.instance.wl_surface.clone(), subsurface.instance.parent.clone()));
subsurface.instance.wl_surface.attach(None, 0, 0);
subsurface.instance.wl_surface.commit();
send_event(&self.events_sender, &self.proxy,
SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(subsurface.instance) )
);
}
for (destroyed, parent) in destroyed {
if let Some((wl_surface, f)) = self.seats.iter_mut().find(|f| {
f.kbd_focus.as_ref().is_some_and(|f| *f == destroyed)
}).and_then(|f| WlSurface::from_id(&self.connection, parent).ok().map(|wl| (wl, &mut f.kbd_focus))) {
*f = Some(wl_surface);
}
}
},
},
};
Ok(())
}
pub fn get_subsurface(
&mut self,
settings: SctkSubsurfaceSettings,
) -> Result<
(
core::window::Id,
WlSurface,
WlSurface,
CommonSurface,
Arc<Mutex<Common>>,
),
SubsurfaceCreationError,
> {
let Some(subsurface_state) = self.subsurface_state.as_ref() else {
return Err(SubsurfaceCreationError::Unsupported);
};
let size = settings.size.unwrap_or(Size::new(1., 1.));
let half_w = size.width / 2.;
let half_h = size.height / 2.;
let mut loc = settings.loc;
match settings.gravity {
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::None => {
// center on
loc.x -= half_w;
loc.y -= half_h;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Top => {
loc.x -= half_w;
loc.y -= size.height;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Bottom => {
loc.x -= half_w;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Left => {
loc.y -= half_h;
loc.x -= size.width;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Right => {
loc.y -= half_h;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft => {
loc.y -= size.height;
loc.x -= size.width;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft => {
loc.x -= size.width;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight => {
loc.y -= size.height;
},
wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight => {},
_ => unimplemented!(),
};
let bounds = Rectangle::new(loc, size);
let parent = if let Some(parent) =
self.layer_surfaces.iter().find(|l| l.id == settings.parent)
{
PopupParent::LayerSurface(parent.surface.wl_surface().clone())
} else if let Some(parent) =
self.windows.iter().find(|w| w.id == settings.parent)
{
PopupParent::Window(parent.wl_surface(&self.connection))
} else if let Some(i) = self
.popups
.iter()
.position(|p| p.data.id == settings.parent)
{
let parent = &self.popups[i];
PopupParent::Popup(parent.popup.wl_surface().clone())
} else if let Some(i) = self
.lock_surfaces
.iter()
.position(|p| p.id == settings.parent)
{
let parent = &self.lock_surfaces[i];
PopupParent::Popup(parent.session_lock_surface.wl_surface().clone())
} else {
return Err(SubsurfaceCreationError::ParentMissing);
};
let wl_surface =
self.compositor_state.create_surface(&self.queue_handle);
_ = self.id_map.insert(wl_surface.id(), settings.id.clone());
for s in self.seats.iter_mut() {
if s.kbd_focus
.as_ref()
.is_some_and(|f| f == parent.wl_surface())
{
s.kbd_focus = Some(wl_surface.clone());
}
}
let parent_wl_surface = parent.wl_surface();
let wl_subsurface = subsurface_state.wl_subcompositor.get_subsurface(
&wl_surface,
parent_wl_surface,
&self.queue_handle,
(),
);
wl_subsurface.set_position(bounds.x as i32, bounds.y as i32);
_ = wl_surface.frame(&self.queue_handle, wl_surface.clone());
if let Some(zone) = settings.input_zone {
let region = self
.compositor_state
.wl_compositor()
.create_region(&self.queue_handle, ());
region.add(
zone.x.round() as i32,
zone.y.round() as i32,
zone.width.round() as i32,
zone.height.round() as i32,
);
wl_surface.set_input_region(Some(&region));
region.destroy();
}
wl_surface.commit();
let wp_viewport = subsurface_state.wp_viewporter.get_viewport(
&wl_surface,
&self.queue_handle,
cctk::sctk::globals::GlobalData,
);
let wp_alpha_modifier_surface = subsurface_state
.wp_alpha_modifier
.as_ref()
.map(|wp_alpha_modifier| {
wp_alpha_modifier.get_surface(
&wl_surface,
&self.queue_handle,
(),
)
});
wp_viewport.set_destination(size.width as i32, size.height as i32);
let mut common: Common =
LogicalSize::new(size.width as u32, size.height as u32).into();
let instance = SubsurfaceInstance {
wl_surface: wl_surface.clone(),
wl_subsurface: wl_subsurface.clone(),
wp_viewport: wp_viewport.clone(),
wp_alpha_modifier_surface: wp_alpha_modifier_surface,
wl_buffer: None,
bounds: Some(bounds),
transform:
cctk::wayland_client::protocol::wl_output::Transform::Normal,
z: settings.z,
parent: parent_wl_surface.id(),
};
common.wp_viewport = Some(wp_viewport);
let common = Arc::new(Mutex::new(common));
for focus in &mut self.seats {
if focus
.kbd_focus
.as_ref()
.is_some_and(|s| s == parent_wl_surface)
{
let id = winit::window::WindowId::from_raw(
wl_surface.id().as_ptr() as usize,
);
self.sctk_events.push(SctkEvent::Winit(
id,
winit::event::WindowEvent::Focused(true),
));
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Enter(wl_surface.clone()),
kbd_id: focus.kbd.clone().unwrap(),
seat_id: focus.seat.clone(),
surface: wl_surface.clone(),
});
focus.kbd_focus = Some(wl_surface.clone());
}
}
let id = settings.id;
self.subsurfaces.push(SctkSubsurface {
common: common.clone(),
steals_keyboard_focus: settings.steal_keyboard_focus,
id: settings.id,
instance,
settings,
});
// XXX subsurfaces need to be sorted by z in descending order
self.subsurfaces
.sort_by(|a, b| b.instance.z.cmp(&a.instance.z));
Ok((
id,
parent.wl_surface().clone(),
wl_surface.clone(),
CommonSurface::Subsurface {
wl_surface,
wl_subsurface,
},
common,
))
}
}
pub(crate) fn send_event(

View file

@ -1,9 +1,9 @@
use iced_futures::futures::channel::oneshot::Sender;
use cctk::sctk::{
activation::{ActivationHandler, RequestData, RequestDataExt},
delegate_activation,
reexports::client::protocol::{wl_seat::WlSeat, wl_surface::WlSurface},
};
use iced_futures::futures::channel::oneshot::Sender;
use crate::platform_specific::wayland::event_loop::state::SctkState;

View file

@ -19,7 +19,6 @@ impl KeyboardHandler for SctkState {
_raw: &[u32],
_keysyms: &[Keysym],
) {
self.request_redraw(surface);
let (i, mut is_active, seat) = {
let (i, is_active, my_seat) =
match self.seats.iter_mut().enumerate().find_map(|(i, s)| {
@ -32,36 +31,49 @@ impl KeyboardHandler for SctkState {
Some((i, s)) => (i, i == 0, s),
None => return,
};
let surface = if let Some(subsurface) =
self.subsurfaces.iter().find(|s| {
s.steals_keyboard_focus && s.instance.parent == surface.id()
}) {
&subsurface.instance.wl_surface
} else {
surface
};
_ = my_seat.kbd_focus.replace(surface.clone());
let seat = my_seat.seat.clone();
(i, is_active, seat)
};
// TODO Ashley: thoroughly test this
// swap the active seat to be the current seat if the current "active" seat is not focused on the application anyway
if !is_active && self.seats[0].kbd_focus.is_none() {
is_active = true;
self.seats.swap(0, i);
}
self.request_redraw(&surface);
if is_active {
let id = winit::window::WindowId::from_raw(
surface.id().as_ptr() as usize
);
if self.windows.iter().any(|w| w.window.id() == id) {
return;
let surfaces = self.subsurfaces.iter().filter_map(|s| {
(s.instance.parent == surface.id()).then(|| &s.instance.wl_surface)
});
for surface in surfaces.chain(std::iter::once(surface)) {
if is_active {
let id = winit::window::WindowId::from_raw(
surface.id().as_ptr() as usize,
);
if self.windows.iter().any(|w| w.window.id() == id) {
continue;
}
self.sctk_events.push(SctkEvent::Winit(
id,
winit::event::WindowEvent::Focused(true),
));
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Enter(surface.clone()),
kbd_id: keyboard.clone(),
seat_id: seat.clone(),
surface: surface.clone(),
});
}
self.sctk_events.push(SctkEvent::Winit(
id,
winit::event::WindowEvent::Focused(true),
));
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Enter(surface.clone()),
kbd_id: keyboard.clone(),
seat_id: seat,
surface: surface.clone(),
});
}
}
@ -91,38 +103,42 @@ impl KeyboardHandler for SctkState {
_ = my_seat.kbd_focus.take();
(is_active, seat, kbd)
};
if is_active {
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Leave(surface.clone()),
kbd_id: kbd,
seat_id: seat,
surface: surface.clone(),
});
// if there is another seat with a keyboard focused on a surface make that the new active seat
if let Some(i) =
self.seats.iter().position(|s| s.kbd_focus.is_some())
{
self.seats.swap(0, i);
let s = &self.seats[0];
let id = winit::window::WindowId::from_raw(
surface.id().as_ptr() as usize,
);
if self.windows.iter().any(|w| w.window.id() == id) {
return;
}
self.sctk_events.push(SctkEvent::Winit(
id,
winit::event::WindowEvent::Focused(true),
));
let surfaces = self.subsurfaces.iter().filter_map(|s| {
(s.instance.parent == surface.id()).then(|| &s.instance.wl_surface)
});
for surface in surfaces.chain(std::iter::once(surface)) {
if is_active {
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Enter(
s.kbd_focus.clone().unwrap(),
),
kbd_id: s.kbd.clone().unwrap(),
seat_id: s.seat.clone(),
variant: KeyboardEventVariant::Leave(surface.clone()),
kbd_id: kbd.clone(),
seat_id: seat.clone(),
surface: surface.clone(),
})
});
// if there is another seat with a keyboard focused on a surface make that the new active seat
if let Some(i) =
self.seats.iter().position(|s| s.kbd_focus.is_some())
{
self.seats.swap(0, i);
let s = &self.seats[0];
let id = winit::window::WindowId::from_raw(
surface.id().as_ptr() as usize,
);
if self.windows.iter().any(|w| w.window.id() == id) {
continue;
}
self.sctk_events.push(SctkEvent::Winit(
id,
winit::event::WindowEvent::Focused(true),
));
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Enter(
s.kbd_focus.clone().unwrap(),
),
kbd_id: s.kbd.clone().unwrap(),
seat_id: s.seat.clone(),
surface: surface.clone(),
})
}
}
}
}
@ -150,35 +166,20 @@ impl KeyboardHandler for SctkState {
let kbd_id = keyboard.clone();
_ = my_seat.last_kbd_press.replace((event.clone(), serial));
if is_active {
// FIXME can't create winit key events because of private field
// if let Some(id) = id {
// let physical_key = raw_keycode_to_physicalkey(event.raw_code);
// let (logical_key, location) =
// keysym_to_vkey_location(event.keysym);
// self.sctk_events.push(SctkEvent::Winit(
// id,
// winit::event::WindowEvent::KeyboardInput {
// device_id: Default::default(),
// event: winit::event::KeyEvent {
// physical_key,
// logical_key,
// text: event.utf8.map(|s| s.into()),
// location,
// state: winit::event::ElementState::Pressed,
// repeat: false, // TODO we don't have this info...
// },
// is_synthetic: false,
// },
// ))
// }
if let Some(surface) = my_seat.kbd_focus.clone() {
self.request_redraw(&surface);
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Press(event),
kbd_id,
seat_id,
surface,
let surfaces = self.subsurfaces.iter().filter_map(|s| {
(s.instance.parent == surface.id())
.then(|| &s.instance.wl_surface)
});
for surface in surfaces.chain(std::iter::once(&surface)) {
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Press(event.clone()),
kbd_id: kbd_id.clone(),
seat_id: seat_id.clone(),
surface: surface.clone(),
});
}
}
}
}
@ -208,12 +209,18 @@ impl KeyboardHandler for SctkState {
if is_active {
if let Some(surface) = my_seat.kbd_focus.clone() {
self.request_redraw(&surface);
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Release(event),
kbd_id,
seat_id,
surface,
let surfaces = self.subsurfaces.iter().filter_map(|s| {
(s.instance.parent == surface.id())
.then(|| &s.instance.wl_surface)
});
for surface in surfaces.chain(std::iter::once(&surface)) {
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Release(event.clone()),
kbd_id: kbd_id.clone(),
seat_id: seat_id.clone(),
surface: surface.clone(),
});
}
}
}
}
@ -244,12 +251,20 @@ impl KeyboardHandler for SctkState {
if is_active {
if let Some(surface) = my_seat.kbd_focus.clone() {
self.request_redraw(&surface);
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Modifiers(modifiers),
kbd_id,
seat_id,
surface,
let surfaces = self.subsurfaces.iter().filter_map(|s| {
(s.instance.parent == surface.id())
.then(|| &s.instance.wl_surface)
});
for surface in surfaces.chain(std::iter::once(&surface)) {
self.sctk_events.push(SctkEvent::KeyboardEvent {
variant: KeyboardEventVariant::Modifiers(
modifiers.clone(),
),
kbd_id: kbd_id.clone(),
seat_id: seat_id.clone(),
surface: surface.clone(),
});
}
}
}
}

View file

@ -11,6 +11,7 @@ use cctk::sctk::{
CursorIcon, PointerEvent, PointerEventKind, PointerHandler,
},
};
use iced_futures::core::Point;
use winit::{
dpi::PhysicalPosition,
event::{
@ -56,6 +57,7 @@ impl PointerHandler for SctkState {
if self.windows.iter().any(|w| w.window.id() == id) {
continue;
}
let entry = self
.frame_status
.entry(e.surface.id())
@ -63,6 +65,7 @@ impl PointerHandler for SctkState {
if matches!(entry, FrameStatus::Received) {
*entry = FrameStatus::Ready;
}
if let PointerEventKind::Motion { time } = &e.kind {
self.sctk_events.push(SctkEvent::PointerEvent {
variant: PointerEvent {

View file

@ -6,7 +6,6 @@ use crate::{
event_loop::state::SctkState, sctk_event::SctkEvent,
},
};
use iced_runtime::core::{touch, Point};
use cctk::sctk::{
delegate_touch,
reexports::client::{
@ -15,6 +14,7 @@ use cctk::sctk::{
},
seat::touch::TouchHandler,
};
use iced_runtime::core::{touch, Point};
impl TouchHandler for SctkState {
fn down(

View file

@ -1,5 +1,6 @@
use crate::platform_specific::wayland::{
handlers::SctkState, sctk_event::SctkEvent,
use crate::{
event_loop::state::CommonSurface,
platform_specific::wayland::{handlers::SctkState, sctk_event::SctkEvent},
};
use cctk::sctk::{
delegate_session_lock,
@ -15,8 +16,9 @@ impl SessionLockHandler for SctkState {
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_session_lock: SessionLock,
session_lock: SessionLock,
) {
self.session_lock = Some(session_lock);
self.sctk_events.push(SctkEvent::SessionLocked);
}
@ -44,8 +46,21 @@ impl SessionLockHandler for SctkState {
Some(l) => l,
None => return,
};
lock_surface
.update_viewport(configure.new_size.0, configure.new_size.1);
let first = lock_surface.last_configure.is_none();
_ = lock_surface.last_configure.replace(configure.clone());
self.sctk_events.push(SctkEvent::SessionLockSurfaceCreated {
queue_handle: self.queue_handle.clone(),
surface: CommonSurface::Lock(
lock_surface.session_lock_surface.clone(),
),
native_id: lock_surface.id,
common: lock_surface.common.clone(),
display: self.connection.display(),
});
self.sctk_events
.push(SctkEvent::SessionLockSurfaceConfigure {
surface: session_lock_surface.wl_surface().clone(),

View file

@ -6,6 +6,7 @@ use cctk::sctk::{
delegate_xdg_popup, reexports::client::Proxy,
shell::xdg::popup::PopupHandler,
};
use winit::dpi::LogicalSize;
impl PopupHandler for SctkState {
fn configure(
@ -24,6 +25,9 @@ impl PopupHandler for SctkState {
};
let first = sctk_popup.last_configure.is_none();
_ = sctk_popup.last_configure.replace(configure.clone());
let mut guard = sctk_popup.common.lock().unwrap();
guard.size =
LogicalSize::new(configure.width as u32, configure.height as u32);
self.sctk_events.push(SctkEvent::PopupEvent {
variant: PopupEventVariant::Configure(

View file

@ -1,7 +1,5 @@
use cctk::{
cosmic_protocols::{
toplevel_management::v1::client::zcosmic_toplevel_manager_v1,
},
cosmic_protocols::toplevel_management::v1::client::zcosmic_toplevel_manager_v1,
toplevel_info::{ToplevelInfoHandler, ToplevelInfoState},
toplevel_management::ToplevelManagerHandler,
wayland_client::{self, WEnum},

View file

@ -25,6 +25,7 @@ use std::{collections::HashMap, sync::Arc};
use subsurface_widget::{SubsurfaceInstance, SubsurfaceState};
use wayland_backend::client::ObjectId;
use wayland_client::{Connection, Proxy};
use winit::dpi::Size;
use winit::event_loop::OwnedDisplayHandle;
pub(crate) enum Action {
@ -34,6 +35,7 @@ pub(crate) enum Action {
TrackWindow(Arc<dyn winit::window::Window>, window::Id),
RemoveWindow(window::Id),
Dropped(SurfaceIdWrapper),
SubsurfaceResize(window::Id, Size),
}
impl std::fmt::Debug for Action {
@ -53,6 +55,11 @@ impl std::fmt::Debug for Action {
f.debug_tuple("RemoveWindow").field(arg0).finish()
}
Self::Dropped(_surface_id_wrapper) => write!(f, "Dropped"),
Self::SubsurfaceResize(id, size) => f
.debug_tuple("SubsurfaceResize")
.field(id)
.field(size)
.finish(),
}
}
}

View file

@ -68,8 +68,10 @@ use cctk::{
xdg::{popup::PopupConfigure, window::WindowConfigure},
},
},
wayland_client::protocol::wl_subsurface::WlSubsurface,
};
use std::{
any::Any,
collections::HashMap,
num::NonZeroU32,
sync::{Arc, Mutex},
@ -79,14 +81,18 @@ use wayland_protocols::{
wp::viewporter::client::wp_viewport::WpViewport,
};
use winit::{
dpi::PhysicalSize, event::WindowEvent, event_loop::EventLoopProxy,
dpi::{self, PhysicalSize},
event::WindowEvent,
event_loop::EventLoopProxy,
window::WindowId,
};
use xkeysym::Keysym;
use super::{
SubsurfaceInstance,
event_loop::state::{Common, CommonSurface, SctkState},
keymap::raw_keycode_to_physicalkey,
subsurface_widget::remove_iced_subsurface,
winit_window::SctkWinitWindow,
};
@ -160,6 +166,8 @@ pub enum SctkEvent {
id: WlSurface,
},
SubsurfaceEvent(SubsurfaceEventVariant),
//
// output events
//
@ -273,6 +281,26 @@ pub enum PopupEventVariant {
ScaleFactorChanged(f64, Option<WpViewport>),
}
#[derive(Debug, Clone)]
pub enum SubsurfaceEventVariant {
/// Popup Created
Created {
parent_id: window::Id,
parent: WlSurface,
surface: WlSurface,
qh: QueueHandle<SctkState>,
common_surface: CommonSurface,
surface_id: SurfaceId,
common: Arc<Mutex<Common>>,
display: WlDisplay,
z: u32,
},
/// Destroyed
Destroyed(SubsurfaceInstance),
/// Resized
Resized(SurfaceId, dpi::Size),
}
#[derive(Debug, Clone)]
pub enum LayerSurfaceEventVariant {
/// sent after creation of the layer surface
@ -485,6 +513,7 @@ impl SctkEvent {
),
),
),
SurfaceIdWrapper::Subsurface(id) => None,
})
{
events.push((
@ -546,6 +575,7 @@ impl SctkEvent {
),
),
),
SurfaceIdWrapper::Subsurface(_) => None,
}
.map(|e| (Some(id.inner()), e))
})
@ -568,7 +598,6 @@ impl SctkEvent {
let physical_key = raw_keycode_to_physicalkey(ke.raw_code);
let physical_key =
crate::conversion::physical_key(physical_key);
events.push((
surface_ids.get(&surface.id()).map(|id| id.inner()),
iced_runtime::core::Event::Keyboard(
@ -853,19 +882,19 @@ impl SctkEvent {
events.push((
Some(id),
iced_runtime::core::Event::Window(
window::Event::Resized(
w.state.logical_size(),
),
window::Event::Opened {
size: w.state.logical_size(),
position: Default::default(),
},
),
))
} else {
events.push((
Some(id),
iced_runtime::core::Event::Window(
window::Event::Opened {
size: w.state.logical_size(),
position: Default::default(),
},
window::Event::Resized(
w.state.logical_size(),
),
),
))
}
@ -1068,17 +1097,30 @@ impl SctkEvent {
configure.width as f32,
configure.height as f32,
);
if let Some(id) =
surface_ids.get(&surface.id()).map(|id| id.inner())
if let Some((id, w)) =
surface_ids.get(&surface.id()).and_then(|id| {
window_manager
.get_mut(id.inner())
.map(|v| (id.inner(), v))
})
{
let scale = w.state.scale_factor();
let p_w = (configure.width.max(1) as f64 * scale)
.ceil()
as u32;
let p_h = (configure.height.max(1) as f64 * scale)
.ceil()
as u32;
w.state.update(
program,
w.raw.as_ref(),
&WindowEvent::SurfaceResized(
PhysicalSize::new(p_w, p_h),
),
);
if first {
events.push((
Some(id),
iced_runtime::core::Event::Window(
window::Event::Resized(size),
),
))
} else {
events.push((
Some(id),
iced_runtime::core::Event::Window(
@ -1088,6 +1130,13 @@ impl SctkEvent {
},
),
))
} else {
events.push((
Some(id),
iced_runtime::core::Event::Window(
window::Event::Resized(size),
),
))
}
}
} // TODO
@ -1247,17 +1296,26 @@ impl SctkEvent {
configure.new_size.0 as f32,
configure.new_size.1 as f32,
);
if let Some(id) =
surface_ids.get(&surface.id()).map(|id| id.inner())
if let Some((id, w)) =
surface_ids.get(&surface.id()).and_then(|id| {
window_manager
.get_mut(id.inner())
.map(|v| (id.inner(), v))
})
{
let scale = w.state.scale_factor();
let p_w = (configure.new_size.0.max(1) as f64 * scale)
.round() as u32;
let p_h = (configure.new_size.1.max(1) as f64 * scale)
.round() as u32;
w.state.update(
program,
w.raw.as_ref(),
&WindowEvent::SurfaceResized(PhysicalSize::new(
p_w, p_h,
)),
);
if first {
events.push((
Some(id),
iced_runtime::core::Event::Window(
window::Event::Resized(size),
),
))
} else {
events.push((
Some(id),
iced_runtime::core::Event::Window(
@ -1267,6 +1325,13 @@ impl SctkEvent {
},
),
))
} else {
events.push((
Some(id),
iced_runtime::core::Event::Window(
window::Event::Resized(size),
),
))
}
}
}
@ -1375,6 +1440,237 @@ impl SctkEvent {
))
}
}
SctkEvent::SubsurfaceEvent(variant) => match variant {
SubsurfaceEventVariant::Created {
parent_id,
common_surface,
common,
z,
parent,
surface: _,
qh,
surface_id,
display,
} => {
let CommonSurface::Subsurface {
wl_surface,
wl_subsurface,
} = &common_surface
else {
return;
};
if let Some(subsurface_state) = subsurface_state.as_mut() {
subsurface_state.new_iced_subsurfaces.push((
parent_id,
parent.id(),
surface_id,
wl_subsurface.clone(),
wl_surface.clone(),
z,
));
}
let wrapper = SurfaceIdWrapper::Popup(surface_id);
_ = surface_ids.insert(wl_surface.id(), wrapper.clone());
let sctk_winit = SctkWinitWindow::new(
sctk_tx.clone(),
common,
wrapper,
common_surface,
display,
qh,
);
// #[cfg(feature = "a11y")]
// {
// use crate::a11y::*;
// use iced_accessibility::accesskit::{
// ActivationHandler, NodeBuilder, NodeId, Role, Tree,
// TreeUpdate,
// };
// use iced_accessibility::accesskit_winit::Adapter;
// let node_id = iced_runtime::core::id::window_node_id();
// let activation_handler = WinitActivationHandler {
// proxy: control_sender.clone(),
// title: String::new(),
// };
// let action_handler = WinitActionHandler {
// id: surface_id,
// proxy: control_sender.clone(),
// };
// let deactivation_handler = WinitDeactivationHandler {
// proxy: control_sender.clone(),
// };
// _ = adapters.insert(
// surface_id,
// (
// node_id,
// Adapter::with_direct_handlers(
// sctk_winit.as_ref(),
// activation_handler,
// action_handler,
// deactivation_handler,
// ),
// ),
// );
// }
if clipboard.window_id().is_none() {
*clipboard = Clipboard::connect(
sctk_winit.clone(),
crate::clipboard::ControlSender {
sender: control_sender.clone(),
proxy: proxy.clone(),
},
);
}
let window = window_manager.insert(
surface_id,
sctk_winit,
program,
compositor,
false, // TODO do we want to get this value here?
theme::Mode::None,
);
let logical_size = window.logical_size();
let mut ui = crate::build_user_interface(
program,
user_interface::Cache::default(),
&mut window.renderer,
logical_size,
surface_id,
window.raw.clone(),
window.prev_dnd_destination_rectangles_count,
clipboard,
);
_ = ui.update(
&vec![iced_runtime::core::Event::PlatformSpecific(
iced_runtime::core::event::PlatformSpecific::Wayland(
iced_runtime::core::event::wayland::Event::RequestResize,
),
)],
window.state.cursor(),
&mut window.renderer,
clipboard,
&mut Vec::new(),
);
if let Some(requested_size) =
clipboard.requested_logical_size.lock().unwrap().take()
{
let requested_physical_size =
winit::dpi::PhysicalSize::new(
(requested_size.width as f64
* window.state.scale_factor())
.ceil() as u32,
(requested_size.height as f64
* window.state.scale_factor())
.ceil() as u32,
);
let physical_size = window.state.physical_size();
if requested_physical_size.width != physical_size.width
|| requested_physical_size.height
!= physical_size.height
{
// FIXME what to do when we are stuck in a configure event/resize request loop
// We don't have control over how winit handles this.
window.resize_enabled = true;
let s = winit::dpi::Size::Physical(
requested_physical_size,
);
_ = window.raw.request_surface_size(s);
window.raw.set_min_surface_size(Some(s));
window.raw.set_max_surface_size(Some(s));
window.state.synchronize(
&program,
surface_id,
window.raw.as_ref(),
);
}
}
events.push((
Some(surface_id),
iced_runtime::core::Event::PlatformSpecific(
PlatformSpecific::Wayland(
wayland::Event::Subsurface(
wayland::SubsurfaceEvent::Created,
),
),
),
));
let _ = user_interfaces.insert(surface_id, ui);
}
SubsurfaceEventVariant::Destroyed(instance) => {
remove_iced_subsurface(&instance.wl_surface);
if let Some(id_wrapper) =
surface_ids.remove(&instance.wl_surface.id())
{
_ = user_interfaces.remove(&id_wrapper.inner());
if let Some(w) =
window_manager.remove(id_wrapper.inner())
{
clipboard.register_dnd_destination(
DndSurface(Arc::new(Box::new(w.raw.clone()))),
Vec::new(),
);
if clipboard
.window_id()
.is_some_and(|id| w.raw.id() == id)
{
*clipboard = Clipboard::unconnected();
}
}
events.push((
Some(id_wrapper.inner()),
iced_runtime::core::Event::PlatformSpecific(
PlatformSpecific::Wayland(
wayland::Event::Subsurface(
wayland::SubsurfaceEvent::Destroyed,
),
),
),
));
}
if let Some(subsurface_state) = subsurface_state.as_mut() {
subsurface_state.unmapped_subsurfaces.push(instance);
}
}
SubsurfaceEventVariant::Resized(id, size) => {
if let Some((id, w)) =
window_manager.get_mut(id).map(|v| (id, v))
{
let scale = w.state.scale_factor();
let physical_size = size.to_physical(scale);
w.state.update(
program,
w.raw.as_ref(),
&WindowEvent::SurfaceResized(PhysicalSize::new(
physical_size.width,
physical_size.height,
)),
);
events.push((
Some(id),
iced_runtime::core::Event::Window(
window::Event::Opened {
size: w.state.logical_size(),
position: Default::default(),
},
),
))
}
}
},
}
}
}

View file

@ -7,6 +7,7 @@ use crate::core::{
widget::{self, Widget},
};
use std::{
borrow::BorrowMut,
cell::RefCell,
collections::HashMap,
fmt::Debug,
@ -55,6 +56,7 @@ use wayland_protocols::wp::{
wp_viewport::WpViewport, wp_viewporter::WpViewporter,
},
};
use winit::window::WindowId;
use crate::platform_specific::{
SurfaceIdWrapper, event_loop::state::SctkState,
@ -364,7 +366,15 @@ pub struct SubsurfaceState {
pub wp_alpha_modifier: Option<WpAlphaModifierV1>,
pub qh: QueueHandle<SctkState>,
pub(crate) buffers: HashMap<WeakBufferSource, Vec<WlBuffer>>,
pub unmapped_subsurfaces: Vec<SubsurfaceInstance>,
pub(crate) unmapped_subsurfaces: Vec<SubsurfaceInstance>,
pub new_iced_subsurfaces: Vec<(
window::Id,
ObjectId,
window::Id,
WlSubsurface,
WlSurface,
u32,
)>,
}
impl SubsurfaceState {
@ -446,6 +456,8 @@ impl SubsurfaceState {
wl_buffer: None,
bounds: None,
transform: wl_output::Transform::Normal,
z: 0,
parent: parent.id(),
}
}
@ -461,6 +473,14 @@ impl SubsurfaceState {
//
// They should be safe to destroy by the next time `update_subsurfaces`
// is run.
ICED_SUBSURFACES.with_borrow_mut(|surfaces| {
surfaces.retain(|s| {
!self
.unmapped_subsurfaces
.iter()
.any(|unmapped| unmapped.wl_surface == s.4)
})
});
self.unmapped_subsurfaces.clear();
// Remove cached `wl_buffers` for any `BufferSource`s that no longer exist.
@ -479,11 +499,61 @@ impl SubsurfaceState {
subsurface.unmap();
self.unmapped_subsurfaces.push(subsurface);
}
let needs_sorting = subsurfaces.len() < view_subsurfaces.len()
|| !self.new_iced_subsurfaces.is_empty();
// Create new subsurfaces if there aren't enough.
while subsurfaces.len() < view_subsurfaces.len() {
subsurfaces.push(self.create_subsurface(parent));
}
// Attach buffers to subsurfaces, set viewports, and commit.
if needs_sorting {
let mut sorted_subsurfaces: Vec<_> = view_subsurfaces
.iter()
.zip(subsurfaces.iter_mut())
.map(|(_, instance)| {
(
instance.parent.clone(),
instance.wl_subsurface.clone(),
instance.wl_surface.clone(),
instance.z,
)
})
.chain(self.new_iced_subsurfaces.clone().into_iter().map(
|(_, parent, _, wl_subsurface, wl_surface, z)| {
(parent.clone(), wl_subsurface, wl_surface, z)
},
))
.chain(ICED_SUBSURFACES.with(|surfaces| {
let b = surfaces.borrow();
let v: Vec<_> = b
.iter()
.map(move |s| {
(s.1.clone(), s.3.clone(), s.4.clone(), s.5)
})
.collect();
v.into_iter()
}))
.collect();
sorted_subsurfaces.sort_by(|a, b| a.3.cmp(&b.3));
// Attach buffers to subsurfaces, set viewports, and commit.
for i in 1..sorted_subsurfaces.len() {
let prev = &sorted_subsurfaces[0..i];
let subsurface = &sorted_subsurfaces[i];
for prev in prev.iter().rev() {
if prev.0 != subsurface.0 {
continue;
}
subsurface.1.place_above(&prev.2);
}
}
}
if !self.new_iced_subsurfaces.is_empty() {
ICED_SUBSURFACES.with(|surfaces| {
surfaces.borrow_mut().append(&mut self.new_iced_subsurfaces);
})
};
for (subsurface_data, subsurface) in
view_subsurfaces.iter().zip(subsurfaces.iter_mut())
{
@ -540,12 +610,14 @@ impl Drop for SubsurfaceState {
#[derive(Clone, Debug)]
pub(crate) struct SubsurfaceInstance {
pub(crate) wl_surface: WlSurface,
wl_subsurface: WlSubsurface,
wp_viewport: WpViewport,
wp_alpha_modifier_surface: Option<WpAlphaModifierSurfaceV1>,
wl_buffer: Option<WlBuffer>,
bounds: Option<Rectangle<f32>>,
transform: wl_output::Transform,
pub(crate) wl_subsurface: WlSubsurface,
pub(crate) wp_viewport: WpViewport,
pub(crate) wp_alpha_modifier_surface: Option<WpAlphaModifierSurfaceV1>,
pub(crate) wl_buffer: Option<WlBuffer>,
pub(crate) bounds: Option<Rectangle<f32>>,
pub(crate) transform: wl_output::Transform,
pub(crate) z: u32,
pub parent: ObjectId,
}
impl SubsurfaceInstance {
@ -621,6 +693,7 @@ impl SubsurfaceInstance {
self.wl_buffer = Some(buffer);
self.bounds = Some(info.bounds);
self.transform = info.transform;
self.z = info.z;
}
pub fn unmap(&self) {
@ -646,16 +719,46 @@ pub(crate) struct SubsurfaceInfo {
pub bounds: Rectangle<f32>,
pub alpha: f32,
pub transform: wl_output::Transform,
pub z: u32,
}
thread_local! {
static SUBSURFACES: RefCell<Vec<SubsurfaceInfo>> = RefCell::new(Vec::new());
static ICED_SUBSURFACES: RefCell<Vec<(window::Id, ObjectId, window::Id, WlSubsurface, WlSurface, u32)>> = RefCell::new(Vec::new());
}
pub(crate) fn take_subsurfaces() -> Vec<SubsurfaceInfo> {
SUBSURFACES.with(|subsurfaces| mem::take(&mut *subsurfaces.borrow_mut()))
}
pub(crate) fn subsurface_ids(parent: WindowId) -> Vec<WindowId> {
ICED_SUBSURFACES.with(|subsurfaces| {
subsurfaces
.borrow_mut()
.iter()
.filter_map(|s| {
if winit::window::WindowId::from_raw(s.1.as_ptr() as usize)
== parent
{
Some(winit::window::WindowId::from_raw(
s.4.id().as_ptr() as usize
))
} else {
None
}
})
.collect()
})
}
pub(crate) fn remove_iced_subsurface(surface: &WlSurface) {
ICED_SUBSURFACES.with(|surfaces| {
surfaces
.borrow_mut()
.retain(|(_, _, _, _, s, _)| s != surface)
})
}
#[must_use]
pub struct Subsurface {
buffer: SubsurfaceBuffer,
@ -664,6 +767,7 @@ pub struct Subsurface {
content_fit: ContentFit,
alpha: f32,
transform: wl_output::Transform,
pub z: u32,
}
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Subsurface
@ -729,6 +833,7 @@ where
bounds: layout.bounds(),
alpha: self.alpha,
transform: self.transform,
z: self.z,
})
});
}
@ -744,6 +849,7 @@ impl Subsurface {
content_fit: ContentFit::Contain,
alpha: 1.,
transform: wl_output::Transform::Normal,
z: 0,
}
}
@ -767,6 +873,11 @@ impl Subsurface {
self
}
pub fn z(mut self, z: u32) -> Self {
self.z = z;
self
}
pub fn transform(mut self, transform: wl_output::Transform) -> Self {
self.transform = transform;
self

View file

@ -1,6 +1,6 @@
use crate::platform_specific::wayland::Action;
use cctk::sctk::reexports::{
calloop::channel,
calloop::{LoopHandle, channel},
client::{
Proxy, QueueHandle,
protocol::{wl_display::WlDisplay, wl_surface::WlSurface},
@ -103,14 +103,14 @@ impl winit::window::Window for SctkWinitWindow {
) -> Option<winit::dpi::PhysicalSize<u32>> {
let mut guard = self.common.lock().unwrap();
self.request_redraw();
let size: LogicalSize<u32> =
let logical_size: LogicalSize<u32> =
size.to_logical(guard.fractional_scale.unwrap_or(1.));
match &self.surface {
CommonSurface::Popup(popup, positioner) => {
if size.width == 0 || size.height == 0 {
if logical_size.width == 0 || logical_size.height == 0 {
return None;
}
guard.size = size;
guard.size = logical_size;
guard.requested_size.0 = Some(guard.size.width);
guard.requested_size.1 = Some(guard.size.height);
positioner.set_size(
@ -138,16 +138,16 @@ impl winit::window::Window for SctkWinitWindow {
}
CommonSurface::Layer(layer_surface) => {
guard.requested_size = (
(size.width > 0).then_some(size.width),
(size.height > 0).then_some(size.height),
(logical_size.width > 0).then_some(logical_size.width),
(logical_size.height > 0).then_some(logical_size.height),
);
if size.width > 0 {
guard.size.width = size.width;
if logical_size.width > 0 {
guard.size.width = logical_size.width;
}
if size.height > 0 {
guard.size.height = size.height;
if logical_size.height > 0 {
guard.size.height = logical_size.height;
}
layer_surface.set_size(size.width, size.height);
layer_surface.set_size(logical_size.width, logical_size.height);
if let Some(viewport) = guard.wp_viewport.as_ref() {
// Set inner size without the borders.
viewport.set_destination(
@ -157,6 +157,23 @@ impl winit::window::Window for SctkWinitWindow {
}
}
CommonSurface::Lock(_) => {}
CommonSurface::Subsurface { .. } => {
guard.requested_size = (
(logical_size.width > 0).then_some(logical_size.width),
(logical_size.height > 0).then_some(logical_size.height),
);
guard.size = logical_size;
if let Some(viewport) = guard.wp_viewport.as_ref() {
// Set inner size without the borders.
viewport.set_destination(
guard.size.width as i32,
guard.size.height as i32,
);
}
_ = self
.tx
.send(Action::SubsurfaceResize(self.id.inner(), size));
}
}
None
}

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,7 @@ use crate::core::text;
use crate::core::theme;
use crate::core::time::Instant;
use crate::core::{
Color, InputMethod, Padding, Point, Rectangle, Size, Text, Vector,
Color, Element, InputMethod, Padding, Point, Rectangle, Size, Text, Vector,
};
use crate::graphics::Compositor;
use crate::program::{self, Program};
@ -26,13 +26,17 @@ use winit::monitor::MonitorHandle;
use std::collections::BTreeMap;
use std::sync::Arc;
pub(crate) type ViewFn<M, T, R> = Arc<
Box<dyn Fn() -> Option<Element<'static, M, T, R>> + Send + Sync + 'static>,
>;
pub struct WindowManager<P, C>
where
P: Program,
C: Compositor<Renderer = P::Renderer>,
P::Theme: theme::Base,
{
aliases: BTreeMap<winit::window::WindowId, Id>,
pub(crate) aliases: BTreeMap<winit::window::WindowId, Id>,
entries: BTreeMap<Id, Window<P, C>>,
}