use std::cell::RefCell;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use ahash::AHashMap;
use sctk::reexports::calloop::LoopHandle;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::globals::GlobalList;
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::compositor::{CompositorHandler, CompositorState};
use sctk::output::{OutputHandler, OutputState};
use sctk::registry::{ProvidesRegistryState, RegistryState};
use sctk::seat::pointer::ThemedPointer;
use sctk::seat::SeatState;
use sctk::shell::xdg::popup::{Popup, PopupConfigure, PopupHandler};
use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler};
use sctk::shell::xdg::XdgShell;
use sctk::shell::WaylandSurface;
use sctk::shm::slot::SlotPool;
use sctk::shm::{Shm, ShmHandler};
use sctk::subcompositor::SubcompositorState;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::output::MonitorHandle;
use crate::platform_impl::wayland::seat::{
KeyboardShortcutsInhibitState, PointerConstraintsState, RelativePointerState, TextInputState,
WinitPointerData, WinitPointerDataExt, WinitSeatState,
};
use crate::platform_impl::wayland::types::background_effect::BackgroundEffectManager;
use crate::platform_impl::wayland::types::cosmic_animated_resize::CosmicAnimatedResizeManager;
use crate::platform_impl::wayland::types::cosmic_backdrop_color::CosmicBackdropColorManager;
use crate::platform_impl::wayland::types::cosmic_corner_radius::CosmicCornerRadiusManager;
use crate::platform_impl::wayland::types::cosmic_special_action::CosmicSpecialActionManager;
use crate::platform_impl::wayland::types::cosmic_surface_embed::CosmicSurfaceEmbedManager;
use crate::platform_impl::wayland::types::cosmic_tooltip::CosmicTooltipManager;
use crate::platform_impl::wayland::types::kora_app_commands::AppCommandsManager;
use crate::platform_impl::wayland::types::kora_halo_header::KoraHaloHeaderManager;
use crate::platform_impl::wayland::types::kora_toplevel_identity::IdentityManager;
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::wayland::types::layer_shadow::LayerShadowManager;
use crate::platform_impl::wayland::types::wayland_dnd::{DndSessionState, WaylandDndManager};
use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager;
use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState;
use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState;
use crate::platform_impl::wayland::types::xdg_foreign::XdgForeign;
use crate::platform_impl::wayland::types::xdg_popup::{
plan_destroy_tree, release_dismissed, subtree_leaf_first, DismissedPopup, PopupEvent, PopupId,
PopupState,
};
use crate::platform_impl::wayland::types::xdg_toplevel_icon::ToplevelIconManager;
use crate::platform_impl::wayland::window::{WindowRequests, WindowState};
use crate::platform_impl::wayland::{WaylandError, WindowId};
use crate::platform_impl::OsError;
pub struct WinitState {
pub registry_state: RegistryState,
pub output_state: OutputState,
pub compositor_state: Arc<CompositorState>,
pub subcompositor_state: Option<Arc<SubcompositorState>>,
pub seat_state: SeatState,
pub shm: Shm,
pub custom_cursor_pool: Arc<Mutex<SlotPool>>,
pub xdg_shell: XdgShell,
pub windows: RefCell<AHashMap<WindowId, Arc<Mutex<WindowState>>>>,
pub window_requests: RefCell<AHashMap<WindowId, Arc<WindowRequests>>>,
pub window_events_sink: Arc<Mutex<EventSink>>,
pub window_compositor_updates: Vec<WindowCompositorUpdate>,
pub seats: AHashMap<ObjectId, WinitSeatState>,
pub pointer_surfaces: AHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
pub text_input_state: Option<TextInputState>,
pub keyboard_shortcuts_inhibit_state: Option<KeyboardShortcutsInhibitState>,
pub monitors: Arc<Mutex<Vec<MonitorHandle>>>,
pub events_sink: EventSink,
pub xdg_activation: Option<XdgActivationState>,
pub relative_pointer: Option<RelativePointerState>,
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
pub viewporter_state: Option<ViewporterState>,
pub fractional_scaling_manager: Option<FractionalScalingManager>,
pub toplevel_icon_manager: Option<ToplevelIconManager>,
pub kwin_blur_manager: Option<KWinBlurManager>,
pub layer_shadow_manager: Option<LayerShadowManager>,
pub background_effect_manager: Option<BackgroundEffectManager>,
pub animated_resize_manager: Option<CosmicAnimatedResizeManager>,
pub corner_radius_manager: Option<CosmicCornerRadiusManager>,
pub special_action_manager: Option<CosmicSpecialActionManager>,
pub backdrop_color_manager: Option<CosmicBackdropColorManager>,
pub surface_embed_manager: Option<CosmicSurfaceEmbedManager>,
pub xdg_foreign: Option<XdgForeign>,
pub tooltip_manager: Option<CosmicTooltipManager>,
pub halo_header_manager: Option<KoraHaloHeaderManager>,
pub identity_manager: Option<IdentityManager>,
pub app_commands_manager: Option<AppCommandsManager>,
pub dnd_manager: Option<WaylandDndManager>,
pub dnd_data_devices: Vec<sctk::reexports::client::protocol::wl_data_device::WlDataDevice>,
pub dnd_session: DndSessionState,
pub popups: AHashMap<PopupId, PopupState>,
pub popup_events: Vec<PopupEvent>,
pub dismissed_popups: Vec<DismissedPopup>,
pub loop_handle: LoopHandle<'static, Self>,
pub dispatched_events: bool,
}
impl WinitState {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<Self>,
loop_handle: LoopHandle<'static, WinitState>,
) -> Result<Self, OsError> {
let registry_state = RegistryState::new(globals);
let compositor_state =
CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let subcompositor_state = match SubcompositorState::bind(
compositor_state.wl_compositor().clone(),
globals,
queue_handle,
) {
Ok(c) => Some(c),
Err(e) => {
tracing::warn!("Subcompositor protocol not available, ignoring CSD: {e:?}");
None
},
};
let output_state = OutputState::new(globals, queue_handle);
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
let seat_state = SeatState::new(globals, queue_handle);
let mut seats = AHashMap::default();
for seat in seat_state.seats() {
seats.insert(seat.id(), WinitSeatState::new());
}
let (viewporter_state, fractional_scaling_manager) =
if let Ok(fsm) = FractionalScalingManager::new(globals, queue_handle) {
(ViewporterState::new(globals, queue_handle).ok(), Some(fsm))
} else {
(None, None)
};
let shm = Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?;
let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap()));
Ok(Self {
registry_state,
compositor_state: Arc::new(compositor_state),
subcompositor_state: subcompositor_state.map(Arc::new),
output_state,
seat_state,
shm,
custom_cursor_pool,
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?,
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
windows: Default::default(),
window_requests: Default::default(),
window_compositor_updates: Vec::new(),
window_events_sink: Default::default(),
viewporter_state,
fractional_scaling_manager,
toplevel_icon_manager: ToplevelIconManager::new(globals, queue_handle).ok(),
kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(),
layer_shadow_manager: LayerShadowManager::new(globals, queue_handle).ok(),
background_effect_manager: BackgroundEffectManager::new(globals, queue_handle).ok(),
animated_resize_manager: CosmicAnimatedResizeManager::new(globals, queue_handle).ok(),
corner_radius_manager: CosmicCornerRadiusManager::new(globals, queue_handle).ok(),
special_action_manager: CosmicSpecialActionManager::new(globals, queue_handle).ok(),
backdrop_color_manager: CosmicBackdropColorManager::new(globals, queue_handle).ok(),
surface_embed_manager: CosmicSurfaceEmbedManager::new(globals, queue_handle).ok(),
xdg_foreign: XdgForeign::new(globals, queue_handle).ok(),
tooltip_manager: CosmicTooltipManager::new(globals, queue_handle).ok(),
halo_header_manager: KoraHaloHeaderManager::new(globals, queue_handle).ok(),
identity_manager: IdentityManager::new(globals, queue_handle).ok(),
app_commands_manager: AppCommandsManager::new(globals, queue_handle).ok(),
dnd_manager: WaylandDndManager::new(globals, queue_handle).ok(),
dnd_data_devices: Vec::new(),
dnd_session: DndSessionState::default(),
popups: Default::default(),
popup_events: Vec::new(),
dismissed_popups: Vec::new(),
seats,
text_input_state: TextInputState::new(globals, queue_handle).ok(),
keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState::new(
globals,
queue_handle,
)
.ok(),
relative_pointer: RelativePointerState::new(globals, queue_handle).ok(),
pointer_constraints: PointerConstraintsState::new(globals, queue_handle)
.map(Arc::new)
.ok(),
pointer_surfaces: Default::default(),
monitors: Arc::new(Mutex::new(monitors)),
events_sink: EventSink::new(),
loop_handle,
dispatched_events: true,
})
}
pub fn scale_factor_changed(
&mut self,
surface: &WlSurface,
scale_factor: f64,
is_legacy: bool,
) {
let window_id = super::make_wid(surface);
if let Some(window) = self.windows.get_mut().get(&window_id) {
if is_legacy && self.fractional_scaling_manager.is_some() {
return;
}
let pos = if let Some(pos) = self
.window_compositor_updates
.iter()
.position(|update| update.window_id == window_id)
{
pos
} else {
self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id));
self.window_compositor_updates.len() - 1
};
window.lock().unwrap().set_scale_factor(scale_factor);
self.window_compositor_updates[pos].scale_changed = true;
} else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) {
let focused_window = match pointer.pointer().winit_data().focused_window() {
Some(focused_window) => focused_window,
None => return,
};
if let Some(window_state) = self.windows.get_mut().get(&focused_window) {
window_state.lock().unwrap().reload_cursor_style()
}
}
}
pub fn queue_close(updates: &mut Vec<WindowCompositorUpdate>, window_id: WindowId) {
let pos = if let Some(pos) = updates.iter().position(|update| update.window_id == window_id)
{
pos
} else {
updates.push(WindowCompositorUpdate::new(window_id));
updates.len() - 1
};
updates[pos].close_window = true;
}
}
impl ShmHandler for WinitState {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
impl WindowHandler for WinitState {
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, window: &Window) {
let window_id = super::make_wid(window.wl_surface());
Self::queue_close(&mut self.window_compositor_updates, window_id);
}
fn configure(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
window: &Window,
configure: WindowConfigure,
_serial: u32,
) {
let window_id = super::make_wid(window.wl_surface());
let pos = if let Some(pos) =
self.window_compositor_updates.iter().position(|update| update.window_id == window_id)
{
pos
} else {
self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id));
self.window_compositor_updates.len() - 1
};
let is_initial = !self
.windows
.get_mut()
.get(&window_id)
.expect("got configure for dead window.")
.lock()
.unwrap()
.is_configured();
if is_initial {
tracing::debug!(
"configure: INITIAL window={:?} size=({:?},{:?}) state={:?}",
window_id,
configure.new_size.0.map(|v| v.get()),
configure.new_size.1.map(|v| v.get()),
configure.state,
);
let mut window = self
.windows
.get_mut()
.get_mut(&window_id)
.expect("got configure for dead window.")
.lock()
.unwrap();
let resized = window.configure(configure, &self.shm, &self.subcompositor_state);
let occluded = window.take_occluded();
drop(window);
let update = &mut self.window_compositor_updates[pos];
update.resized |= resized;
update.occluded = occluded.or(update.occluded);
} else {
let had_pending = self.window_compositor_updates[pos].pending_configure.is_some();
tracing::debug!(
"configure: DEFERRED window={:?} size=({:?},{:?}) state={:?} had_pending={}",
window_id,
configure.new_size.0.map(|v| v.get()),
configure.new_size.1.map(|v| v.get()),
configure.state,
had_pending,
);
self.window_compositor_updates[pos].pending_configure = Some(configure);
}
self.window_requests
.get_mut()
.get(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
self.dispatched_events = true;
}
}
impl OutputHandler for WinitState {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, output: WlOutput) {
self.monitors.lock().unwrap().push(MonitorHandle::new(output));
}
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, updated: WlOutput) {
let mut monitors = self.monitors.lock().unwrap();
let updated = MonitorHandle::new(updated);
if let Some(pos) = monitors.iter().position(|output| output == &updated) {
monitors[pos] = updated
} else {
monitors.push(updated)
}
}
fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, removed: WlOutput) {
let mut monitors = self.monitors.lock().unwrap();
let removed = MonitorHandle::new(removed);
if let Some(pos) = monitors.iter().position(|output| output == &removed) {
monitors.remove(pos);
}
}
}
impl CompositorHandler for WinitState {
fn transform_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: wayland_client::protocol::wl_output::Transform,
) {
}
fn surface_enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &WlOutput,
) {
}
fn surface_leave(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &WlOutput,
) {
}
fn scale_factor_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
surface: &WlSurface,
scale_factor: i32,
) {
self.scale_factor_changed(surface, scale_factor as f64, true)
}
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, surface: &WlSurface, _: u32) {
let window_id = super::make_wid(surface);
let window = match self.windows.get_mut().get(&window_id) {
Some(window) => window,
None => return,
};
if self
.window_requests
.get_mut()
.get(&window_id)
.unwrap()
.redraw_requested
.load(Ordering::Relaxed)
{
self.dispatched_events = true;
}
window.lock().unwrap().frame_callback_received();
}
}
impl ProvidesRegistryState for WinitState {
sctk::registry_handlers![OutputState, SeatState];
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
}
#[derive(Debug, Clone)]
pub struct WindowCompositorUpdate {
pub window_id: WindowId,
pub resized: bool,
pub scale_changed: bool,
pub close_window: bool,
pub occluded: Option<bool>,
pub pending_configure: Option<WindowConfigure>,
}
impl WindowCompositorUpdate {
fn new(window_id: WindowId) -> Self {
Self {
window_id,
resized: false,
scale_changed: false,
close_window: false,
occluded: None,
pending_configure: None,
}
}
}
sctk::delegate_subcompositor!(WinitState);
sctk::delegate_compositor!(WinitState);
sctk::delegate_output!(WinitState);
sctk::delegate_registry!(WinitState);
sctk::delegate_shm!(WinitState);
sctk::delegate_xdg_shell!(WinitState);
sctk::delegate_xdg_window!(WinitState);
sctk::delegate_xdg_popup!(WinitState);
impl PopupHandler for WinitState {
fn configure(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
popup: &Popup,
config: PopupConfigure,
) {
let popup_id = self
.popups
.iter()
.find(|(_, state)| state.popup.wl_surface() == popup.wl_surface())
.map(|(id, _)| *id);
if let Some(id) = popup_id {
if let Some(state) = self.popups.get_mut(&id) {
state.configured = true;
if let Some(ref viewport) = state.viewport {
viewport.set_destination(state.size.width as i32, state.size.height as i32);
}
}
let (event_width, event_height) = self
.popups
.get(&id)
.map(|s| (s.size.width, s.size.height))
.unwrap_or((config.width as u32, config.height as u32));
tracing::debug!(
"Popup {:?} configured by compositor: position=({}, {}), geometry={}x{}, \
surface={}x{}",
id,
config.position.0,
config.position.1,
config.width,
config.height,
event_width,
event_height,
);
self.popup_events.push(PopupEvent::Configure {
id,
width: event_width,
height: event_height,
});
self.dispatched_events = true;
}
}
fn done(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, popup: &Popup) {
let popup_id = self
.popups
.iter()
.find(|(_, state)| state.popup.wl_surface() == popup.wl_surface())
.map(|(id, _)| *id);
if let Some(id) = popup_id {
for (id, state) in self.take_popup_tree(id) {
self.popup_events.push(PopupEvent::Done { id });
self.dismissed_popups.push(DismissedPopup { id, state, reported: false });
}
self.dispatched_events = true;
}
}
}
impl WinitState {
fn take_popup_tree(&mut self, id: PopupId) -> Vec<(PopupId, PopupState)> {
if !self.popups.contains_key(&id) {
return Vec::new();
}
let order =
subtree_leaf_first(self.popups.iter().map(|(id, popup)| (*id, popup.parent_popup)), id);
order.into_iter().filter_map(|id| Some((id, self.popups.remove(&id)?))).collect()
}
pub fn destroy_popup_tree(&mut self, id: PopupId) -> Option<Vec<PopupId>> {
let dismissed: Vec<PopupId> = self.dismissed_popups.iter().map(|p| p.id).collect();
if !self.popups.contains_key(&id) && !dismissed.contains(&id) {
return None;
}
let links: Vec<_> = self
.popups
.iter()
.map(|(id, popup)| (*id, popup.parent_popup))
.chain(self.dismissed_popups.iter().map(|p| (p.id, p.state.parent_popup)))
.collect();
let plan = plan_destroy_tree(links, &dismissed, id);
if !plan.held.is_empty() {
tracing::debug!("destroy_popup: {:?} stay until their grace window is up", plan.held);
}
let mut live_children = Vec::new();
for popup in plan.destroy {
if self.popups.remove(&popup).is_some() && popup != id {
live_children.push(popup);
}
}
Some(live_children)
}
pub fn release_dismissed_popups(&mut self) {
drop(release_dismissed(&mut self.dismissed_popups));
}
}