use std::num::NonZeroU32;
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;
use ahash::HashSet;
use tracing::{info, warn};
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::csd_frame::{
DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowState as XdgWindowState,
};
use sctk::globals::GlobalData;
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
use sctk::reexports::protocols::wp::keyboard_shortcuts_inhibit::zv1::client::zwp_keyboard_shortcuts_inhibit_manager_v1::ZwpKeyboardShortcutsInhibitManagerV1;
use sctk::reexports::protocols::wp::keyboard_shortcuts_inhibit::zv1::client::zwp_keyboard_shortcuts_inhibitor_v1::ZwpKeyboardShortcutsInhibitorV1;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt};
use sctk::seat::pointer::{PointerDataExt, ThemedPointer};
use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
use sctk::shell::xdg::XdgSurface;
use sctk::shell::WaylandSurface;
use sctk::shm::slot::SlotPool;
use sctk::shm::Shm;
use sctk::subcompositor::SubcompositorState;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::cursor::CustomCursor as RootCustomCursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Size};
use crate::error::{ExternalError, NotSupportedError};
use crate::platform::wayland::ImeSurroundingText;
use crate::platform_impl::wayland::logical_to_physical_rounded;
use crate::platform_impl::wayland::types::background_effect::{
BackgroundEffect, BackgroundEffectManager,
};
use crate::platform_impl::wayland::types::cosmic_animated_resize::{
AnimatedResizeController, CosmicAnimatedResizeManager,
};
use crate::platform_impl::wayland::types::cosmic_backdrop_color::{
BackdropColorController, CosmicBackdropColorManager,
};
use crate::platform_impl::wayland::types::cosmic_corner_radius::{
CornerRadiusController, CosmicCornerRadiusManager,
};
use crate::platform_impl::wayland::types::cosmic_special_action::{
CosmicSpecialActionManager, SpecialActionEvent, SpecialActionReceiver,
};
use crate::platform_impl::wayland::types::cosmic_surface_embed::{
CosmicSurfaceEmbedManager, EmbeddedSurface,
};
use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
use crate::platform_impl::wayland::types::wayland_dnd::{SharedDndOfferState, WaylandDndManager};
use crate::platform_impl::wayland::types::xdg_foreign::ImportedToplevel;
use crate::platform_impl::{PlatformCustomCursor, WindowId};
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
use crate::platform_impl::wayland::seat::{
PointerConstraintsState, SurroundingChange, SurroundingText, WinitPointerData,
WinitPointerDataExt, ZwpTextInputV3Ext,
};
use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
#[cfg(feature = "sctk-adwaita")]
pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
#[cfg(not(feature = "sctk-adwaita"))]
pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame<WinitState>;
const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
pub struct WindowState {
occluded: Option<bool>,
identity: Option<crate::platform_impl::wayland::types::kora_toplevel_identity::IdentityHandle>,
app_commands:
Option<crate::platform_impl::wayland::types::kora_app_commands::AppCommandsHandle>,
last_key_press: Option<(WlSeat, u32)>,
pub connection: Connection,
pub shm: WlShm,
custom_cursor_pool: Arc<Mutex<SlotPool>>,
pub last_configure: Option<WindowConfigure>,
pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
selected_cursor: SelectedCursor,
pub cursor_visible: bool,
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
pub queue_handle: QueueHandle<WinitState>,
theme: Option<Theme>,
title: String,
resizable: bool,
seat_focus: HashSet<ObjectId>,
scale_factor: f64,
transparent: bool,
compositor: Arc<CompositorState>,
cursor_grab_mode: GrabState,
ime_allowed: bool,
ime_purpose: ImePurpose,
ime_cursor_area: Option<(i32, i32, i32, i32)>,
ime_surrounding: Option<SurroundingText>,
ime_committed: bool,
text_inputs: Vec<ZwpTextInputV3>,
keyboard_shortcuts_inhibit_manager: Option<ZwpKeyboardShortcutsInhibitManagerV1>,
keyboard_shortcuts_inhibit_seat: Option<WlSeat>,
keyboard_shortcuts_inhibitor: Option<ZwpKeyboardShortcutsInhibitorV1>,
size: LogicalSize<u32>,
csd_fails: bool,
decorate: bool,
min_inner_size: LogicalSize<u32>,
max_inner_size: Option<LogicalSize<u32>>,
stateless_size: LogicalSize<u32>,
initial_size: Option<Size>,
frame_callback_state: FrameCallbackState,
viewport: Option<WpViewport>,
fractional_scale: Option<WpFractionalScaleV1>,
blur: Option<OrgKdeKwinBlur>,
blur_manager: Option<KWinBlurManager>,
background_effect: Option<BackgroundEffect>,
background_effect_manager: Option<BackgroundEffectManager>,
animated_resize_manager: Option<CosmicAnimatedResizeManager>,
animated_resize_controller: Option<AnimatedResizeController>,
corner_radius_manager: Option<CosmicCornerRadiusManager>,
special_action_manager: Option<CosmicSpecialActionManager>,
special_action_receiver: Option<SpecialActionReceiver>,
corner_radius_controller: Option<CornerRadiusController>,
backdrop_color_manager: Option<CosmicBackdropColorManager>,
backdrop_color_controller: Option<BackdropColorController>,
surface_embed_manager: Option<CosmicSurfaceEmbedManager>,
embedded_surfaces: std::collections::HashMap<u64, EmbeddedSurface>,
next_embed_id: u64,
xdg_imported_parent: Option<ImportedToplevel>,
dnd_manager: Option<WaylandDndManager>,
dnd_data_devices: Vec<sctk::reexports::client::protocol::wl_data_device::WlDataDevice>,
dnd_shared_offer: std::sync::Arc<std::sync::Mutex<SharedDndOfferState>>,
dnd_icon:
Option<(sctk::reexports::client::protocol::wl_surface::WlSurface, sctk::shm::slot::Buffer)>,
has_pending_move: Option<u32>,
last_touch_down: Option<(WlSeat, u32)>,
pub window: Window,
frame: Option<WinitFrame>,
}
impl WindowState {
pub fn new(
connection: Connection,
queue_handle: &QueueHandle<WinitState>,
winit_state: &WinitState,
initial_size: Size,
window: Window,
theme: Option<Theme>,
) -> Self {
let compositor = winit_state.compositor_state.clone();
let pointer_constraints = winit_state.pointer_constraints.clone();
let viewport = winit_state
.viewporter_state
.as_ref()
.map(|state| state.get_viewport(window.wl_surface(), queue_handle));
let fractional_scale = winit_state
.fractional_scaling_manager
.as_ref()
.map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
Self {
identity: winit_state
.identity_manager
.as_ref()
.map(|manager| manager.get_identity(window.xdg_toplevel(), queue_handle)),
app_commands: winit_state
.app_commands_manager
.as_ref()
.map(|manager| manager.get_commands(window.xdg_toplevel(), queue_handle)),
last_key_press: None,
blur: None,
blur_manager: winit_state.kwin_blur_manager.clone(),
background_effect: None,
background_effect_manager: winit_state.background_effect_manager.clone(),
animated_resize_manager: winit_state.animated_resize_manager.clone(),
animated_resize_controller: None,
corner_radius_manager: winit_state.corner_radius_manager.clone(),
corner_radius_controller: None,
special_action_manager: winit_state.special_action_manager.clone(),
special_action_receiver: None,
backdrop_color_manager: winit_state.backdrop_color_manager.clone(),
backdrop_color_controller: None,
surface_embed_manager: winit_state.surface_embed_manager.clone(),
embedded_surfaces: std::collections::HashMap::new(),
next_embed_id: 1,
xdg_imported_parent: None,
dnd_manager: winit_state.dnd_manager.clone(),
dnd_data_devices: winit_state.dnd_data_devices.clone(),
dnd_shared_offer: winit_state.dnd_session.shared_offer.clone(),
dnd_icon: None,
compositor,
connection,
csd_fails: false,
cursor_grab_mode: GrabState::new(),
selected_cursor: Default::default(),
cursor_visible: true,
decorate: true,
fractional_scale,
frame: None,
frame_callback_state: FrameCallbackState::None,
seat_focus: Default::default(),
has_pending_move: None,
last_touch_down: None,
ime_allowed: false,
ime_purpose: ImePurpose::Normal,
ime_cursor_area: None,
ime_surrounding: None,
ime_committed: false,
last_configure: None,
occluded: None,
max_inner_size: None,
min_inner_size: MIN_WINDOW_SIZE,
pointer_constraints,
pointers: Default::default(),
queue_handle: queue_handle.clone(),
resizable: true,
scale_factor: 1.,
shm: winit_state.shm.wl_shm().clone(),
custom_cursor_pool: winit_state.custom_cursor_pool.clone(),
size: initial_size.to_logical(1.),
stateless_size: initial_size.to_logical(1.),
initial_size: Some(initial_size),
text_inputs: Vec::new(),
keyboard_shortcuts_inhibit_manager: winit_state
.keyboard_shortcuts_inhibit_state
.as_ref()
.map(|s| s.manager()),
keyboard_shortcuts_inhibit_seat: winit_state.seat_state.seats().next(),
keyboard_shortcuts_inhibitor: None,
theme,
title: String::default(),
transparent: false,
viewport,
window,
}
}
fn apply_on_pointer<F: FnMut(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
&self,
mut callback: F,
) {
self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| {
let data = pointer.pointer().winit_data();
callback(pointer.as_ref(), data);
})
}
pub fn frame_callback_state(&self) -> FrameCallbackState {
self.frame_callback_state
}
pub fn frame_callback_received(&mut self) {
self.frame_callback_state = FrameCallbackState::Received;
}
pub fn frame_callback_reset(&mut self) {
self.frame_callback_state = FrameCallbackState::None;
}
pub fn request_frame_callback(&mut self) {
let surface = self.window.wl_surface();
match self.frame_callback_state {
FrameCallbackState::None | FrameCallbackState::Received => {
self.frame_callback_state = FrameCallbackState::Requested;
surface.frame(&self.queue_handle, surface.clone());
},
FrameCallbackState::Requested => (),
}
}
pub fn configure(
&mut self,
configure: WindowConfigure,
shm: &Shm,
subcompositor: &Option<Arc<SubcompositorState>>,
) -> bool {
if let Some(initial_size) = self.initial_size.take() {
self.size = initial_size.to_logical(self.scale_factor());
self.stateless_size = self.size;
}
if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
configure.decoration_mode == DecorationMode::Client
&& self.frame.is_none()
&& !self.csd_fails
}) {
match WinitFrame::new(
&self.window,
shm,
#[cfg(feature = "sctk-adwaita")]
self.compositor.clone(),
subcompositor.clone(),
self.queue_handle.clone(),
#[cfg(feature = "sctk-adwaita")]
into_sctk_adwaita_config(self.theme),
) {
Ok(mut frame) => {
frame.set_title(&self.title);
frame.set_scaling_factor(self.scale_factor);
frame.set_hidden(!self.decorate);
self.frame = Some(frame);
},
Err(err) => {
warn!("Failed to create client side decorations frame: {err}");
self.csd_fails = true;
},
}
} else if configure.decoration_mode == DecorationMode::Server {
self.frame = None;
}
let stateless = Self::is_stateless(&configure);
tracing::trace!(
"configure: stateless={}, configure.new_size={:?}, current_size={:?}, \
stateless_size={:?}",
stateless,
configure.new_size,
self.size,
self.stateless_size
);
let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
frame.update_state(configure.state);
match configure.new_size {
(Some(width), Some(height)) => {
let (width, height) = frame.subtract_borders(width, height);
let width = width.map(|w| w.get()).unwrap_or(1);
let height = height.map(|h| h.get()).unwrap_or(1);
((width, height).into(), false)
},
(..) if stateless => (self.stateless_size, true),
_ => (self.size, true),
}
} else {
match configure.new_size {
(Some(width), Some(height)) => ((width.get(), height.get()).into(), false),
_ if stateless => (self.stateless_size, true),
_ => (self.size, true),
}
};
tracing::debug!("configure: chosen new_size={:?}, constrain={}", new_size, constrain);
if constrain {
let bounds = self.inner_size_bounds(&configure);
new_size.width =
bounds.0.map(|bound_w| new_size.width.min(bound_w.get())).unwrap_or(new_size.width);
new_size.height = bounds
.1
.map(|bound_h| new_size.height.min(bound_h.get()))
.unwrap_or(new_size.height);
}
let new_state = configure.state;
let old_state = self.last_configure.as_ref().map(|configure| configure.state);
let suspended = new_state.contains(XdgWindowState::SUSPENDED);
if old_state.map_or(suspended, |old| old.contains(XdgWindowState::SUSPENDED) != suspended) {
self.occluded = Some(suspended);
}
let state_change_requires_resize = old_state
.map(|old_state| {
!old_state
.symmetric_difference(new_state)
.difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
.is_empty()
})
.unwrap_or(true);
self.last_configure = Some(configure);
if state_change_requires_resize || new_size != self.inner_size() {
self.resize(new_size);
true
} else {
false
}
}
pub fn take_occluded(&mut self) -> Option<bool> {
self.occluded.take()
}
fn inner_size_bounds(
&self,
configure: &WindowConfigure,
) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
let configure_bounds = match configure.suggested_bounds {
Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
None => (None, None),
};
if let Some(frame) = self.frame.as_ref() {
let (width, height) = frame.subtract_borders(
configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
);
(configure_bounds.0.and(width), configure_bounds.1.and(height))
} else {
configure_bounds
}
}
#[inline]
fn is_stateless(configure: &WindowConfigure) -> bool {
!(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled())
}
pub fn set_last_touch_down(&mut self, seat: WlSeat, serial: u32) {
self.last_touch_down = Some((seat, serial));
}
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
let xdg_toplevel = self.window.xdg_toplevel();
let mut resized = false;
self.apply_on_pointer(|_, data| {
let serial = data.latest_button_serial();
if serial != 0 {
let seat = data.seat();
xdg_toplevel.resize(seat, serial, direction.into());
resized = true;
}
});
if !resized {
if let Some((seat, serial)) = &self.last_touch_down {
xdg_toplevel.resize(seat, *serial, direction.into());
}
}
Ok(())
}
pub fn drag_window(&self) -> Result<(), ExternalError> {
let xdg_toplevel = self.window.xdg_toplevel();
let mut moved = false;
self.apply_on_pointer(|_, data| {
let serial = data.latest_button_serial();
if serial != 0 {
let seat = data.seat();
xdg_toplevel._move(seat, serial);
moved = true;
}
});
if !moved {
if let Some((seat, serial)) = &self.last_touch_down {
xdg_toplevel._move(seat, *serial);
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn frame_click(
&mut self,
click: FrameClick,
pressed: bool,
seat: &WlSeat,
serial: u32,
timestamp: Duration,
window_id: WindowId,
updates: &mut Vec<WindowCompositorUpdate>,
) -> Option<bool> {
match self.frame.as_mut()?.on_click(timestamp, click, pressed)? {
FrameAction::Minimize => self.window.set_minimized(),
FrameAction::Maximize => self.window.set_maximized(),
FrameAction::UnMaximize => self.window.unset_maximized(),
FrameAction::Close => WinitState::queue_close(updates, window_id),
FrameAction::Move => self.has_pending_move = Some(serial),
FrameAction::Resize(edge) => {
let edge = match edge {
ResizeEdge::None => XdgResizeEdge::None,
ResizeEdge::Top => XdgResizeEdge::Top,
ResizeEdge::Bottom => XdgResizeEdge::Bottom,
ResizeEdge::Left => XdgResizeEdge::Left,
ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
ResizeEdge::Right => XdgResizeEdge::Right,
ResizeEdge::TopRight => XdgResizeEdge::TopRight,
ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
_ => return None,
};
self.window.resize(seat, serial, edge);
},
FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
_ => (),
};
Some(false)
}
pub fn frame_point_left(&mut self) {
if let Some(frame) = self.frame.as_mut() {
frame.click_point_left();
}
}
pub fn frame_point_moved(
&mut self,
seat: &WlSeat,
surface: &WlSurface,
timestamp: Duration,
x: f64,
y: f64,
) -> Option<CursorIcon> {
let serial = self.has_pending_move.take();
if let Some(frame) = self.frame.as_mut() {
let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y);
if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
self.window.move_(seat, serial);
None
} else {
cursor
}
} else {
None
}
}
#[inline]
pub fn resizable(&self) -> bool {
self.resizable
}
#[inline]
pub fn set_resizable(&mut self, resizable: bool) -> bool {
if self.resizable == resizable {
return false;
}
self.resizable = resizable;
if resizable {
self.reload_min_max_hints();
} else {
self.set_min_inner_size(Some(self.size));
self.set_max_inner_size(Some(self.size));
}
if let Some(frame) = self.frame.as_mut() {
frame.set_resizable(resizable);
}
true
}
#[inline]
pub fn has_focus(&self) -> bool {
!self.seat_focus.is_empty()
}
#[inline]
pub fn ime_allowed(&self) -> bool {
self.ime_allowed
}
#[inline]
pub fn inner_size(&self) -> LogicalSize<u32> {
self.size
}
#[inline]
pub fn is_configured(&self) -> bool {
self.last_configure.is_some()
}
#[inline]
pub fn is_decorated(&mut self) -> bool {
let csd = self
.last_configure
.as_ref()
.map(|configure| configure.decoration_mode == DecorationMode::Client)
.unwrap_or(false);
if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
!frame.is_hidden()
} else {
true
}
}
#[inline]
pub fn outer_size(&self) -> LogicalSize<u32> {
self.frame
.as_ref()
.map(|frame| frame.add_borders(self.size.width, self.size.height).into())
.unwrap_or(self.size)
}
pub fn pointer_entered(&mut self, added: Weak<ThemedPointer<WinitPointerData>>) {
self.pointers.push(added);
self.reload_cursor_style();
let mode = self.cursor_grab_mode.user_grab_mode;
let _ = self.set_cursor_grab_inner(mode);
}
pub fn pointer_left(&mut self, removed: Weak<ThemedPointer<WinitPointerData>>) {
let mut new_pointers = Vec::new();
for pointer in self.pointers.drain(..) {
if let Some(pointer) = pointer.upgrade() {
if pointer.pointer() != removed.upgrade().unwrap().pointer() {
new_pointers.push(Arc::downgrade(&pointer));
}
}
}
self.pointers = new_pointers;
}
pub fn refresh_frame(&mut self) -> bool {
if let Some(frame) = self.frame.as_mut() {
if !frame.is_hidden() && frame.is_dirty() {
return frame.draw();
}
}
false
}
pub fn reload_cursor_style(&mut self) {
if self.cursor_visible {
match &self.selected_cursor {
SelectedCursor::Named(icon) => self.set_cursor(*icon),
SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
}
} else {
self.set_cursor_visible(self.cursor_visible);
}
}
pub fn reload_transparency_hint(&self) {
let surface = self.window.wl_surface();
if self.transparent {
surface.set_opaque_region(None);
} else if let Ok(region) = Region::new(&*self.compositor) {
region.add(0, 0, i32::MAX, i32::MAX);
surface.set_opaque_region(Some(region.wl_region()));
} else {
warn!("Failed to mark window opaque.");
}
}
pub fn reload_blur_region(&self) {
let Some(effect) = self.background_effect.as_ref() else {
return;
};
if let Ok(region) = Region::new(&*self.compositor) {
region.add(0, 0, self.size.width as i32, self.size.height as i32);
effect.set_blur_region(Some(region.wl_region()));
} else {
warn!("Failed to set blur region.");
}
}
pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize<u32> {
let logical_size = inner_size.to_logical(self.scale_factor());
let is_stateless = self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true);
tracing::trace!(
"request_inner_size: inner_size={:?}, logical_size={:?}, is_stateless={}, \
current_stateless_size={:?}",
inner_size,
logical_size,
is_stateless,
self.stateless_size
);
if is_stateless {
tracing::trace!("request_inner_size: window is stateless, calling resize()");
self.resize(logical_size);
} else {
tracing::trace!(
"request_inner_size: window is NOT stateless (maximized/tiled), storing {:?} as \
stateless_size for restore",
logical_size
);
self.stateless_size = logical_size;
self.request_animated_resize(logical_size.width as i32, logical_size.height as i32, 0);
}
logical_to_physical_rounded(self.inner_size(), self.scale_factor())
}
fn resize(&mut self, inner_size: LogicalSize<u32>) {
tracing::debug!("resize: old_size={:?} -> new_size={:?}", self.size, inner_size,);
self.size = inner_size;
if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
self.stateless_size = inner_size;
}
let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
if !frame.is_hidden() {
frame.resize(
NonZeroU32::new(self.size.width).unwrap(),
NonZeroU32::new(self.size.height).unwrap(),
);
}
(frame.location(), frame.add_borders(self.size.width, self.size.height).into())
} else {
((0, 0), self.size)
};
self.reload_transparency_hint();
self.reload_blur_region();
tracing::debug!(
"resize: set_window_geometry({}, {}, {}, {})",
x,
y,
outer_size.width,
outer_size.height,
);
self.window.xdg_surface().set_window_geometry(
x,
y,
outer_size.width as i32,
outer_size.height as i32,
);
if let Some(viewport) = self.viewport.as_ref() {
tracing::debug!(
"resize: set_viewport_destination({}, {})",
self.size.width,
self.size.height
);
viewport.set_destination(self.size.width as _, self.size.height as _);
}
}
#[inline]
pub fn scale_factor(&self) -> f64 {
self.scale_factor
}
pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
self.selected_cursor = SelectedCursor::Named(cursor_icon);
if !self.cursor_visible {
return;
}
self.apply_on_pointer(|pointer, _| {
if pointer.set_cursor(&self.connection, cursor_icon).is_err() {
warn!("Failed to set cursor to {:?}", cursor_icon);
}
})
}
pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
let cursor = match cursor {
RootCustomCursor { inner: PlatformCustomCursor::Wayland(cursor) } => cursor.0,
#[cfg(x11_platform)]
RootCustomCursor { inner: PlatformCustomCursor::X(_) } => {
tracing::error!("passed a X11 cursor to Wayland backend");
return;
},
};
let cursor = {
let mut pool = self.custom_cursor_pool.lock().unwrap();
CustomCursor::new(&mut pool, &cursor)
};
if self.cursor_visible {
self.apply_custom_cursor(&cursor);
}
self.selected_cursor = SelectedCursor::Custom(cursor);
}
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
self.apply_on_pointer(|pointer, data| {
let surface = pointer.surface();
let scale = if let Some(viewport) = data.viewport() {
let scale = self.scale_factor();
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
viewport.set_destination(size.width, size.height);
scale
} else {
let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
surface.set_buffer_scale(scale);
scale as f64
};
surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
if surface.version() >= 4 {
surface.damage_buffer(0, 0, cursor.w, cursor.h);
} else {
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
surface.damage(0, 0, size.width, size.height);
}
surface.commit();
let serial = pointer
.pointer()
.data::<WinitPointerData>()
.and_then(|data| data.pointer_data().latest_enter_serial())
.unwrap();
let hotspot =
PhysicalPosition::new(cursor.hotspot_x, cursor.hotspot_y).to_logical(scale);
pointer.pointer().set_cursor(serial, Some(surface), hotspot.x, hotspot.y);
});
}
pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
size.width = size.width.max(MIN_WINDOW_SIZE.width);
size.height = size.height.max(MIN_WINDOW_SIZE.height);
let size = self
.frame
.as_ref()
.map(|frame| frame.add_borders(size.width, size.height).into())
.unwrap_or(size);
self.min_inner_size = size;
self.window.set_min_size(Some(size.into()));
}
pub fn set_max_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
let size = size.map(|size| {
self.frame
.as_ref()
.map(|frame| frame.add_borders(size.width, size.height).into())
.unwrap_or(size)
});
self.max_inner_size = size;
self.window.set_max_size(size.map(Into::into));
}
pub fn set_theme(&mut self, theme: Option<Theme>) {
self.theme = theme;
#[cfg(feature = "sctk-adwaita")]
if let Some(frame) = self.frame.as_mut() {
frame.set_config(into_sctk_adwaita_config(theme))
}
}
#[inline]
pub fn theme(&self) -> Option<Theme> {
self.theme
}
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
if self.cursor_grab_mode.user_grab_mode == mode {
return Ok(());
}
self.set_cursor_grab_inner(mode)?;
self.cursor_grab_mode.user_grab_mode = mode;
Ok(())
}
pub fn reload_min_max_hints(&mut self) {
self.set_min_inner_size(Some(self.min_inner_size));
self.set_max_inner_size(self.max_inner_size);
}
fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let pointer_constraints = match self.pointer_constraints.as_ref() {
Some(pointer_constraints) => pointer_constraints,
None if mode == CursorGrabMode::None => return Ok(()),
None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
};
let mut unset_old = false;
match self.cursor_grab_mode.current_grab_mode {
CursorGrabMode::None => unset_old = true,
CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
data.unconfine_pointer();
unset_old = true;
}),
CursorGrabMode::Locked => {
self.apply_on_pointer(|_, data| {
data.unlock_pointer();
unset_old = true;
});
},
}
if !unset_old {
return Ok(());
}
let mut set_mode = false;
let surface = self.window.wl_surface();
match mode {
CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
let pointer = pointer.pointer();
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
set_mode = true;
}),
CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
let pointer = pointer.pointer();
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
set_mode = true;
}),
CursorGrabMode::None => {
set_mode = true;
},
}
if set_mode {
self.cursor_grab_mode.current_grab_mode = mode;
}
Ok(())
}
pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
self.apply_on_pointer(|_, data| {
let serial = data.latest_button_serial();
let seat = data.seat();
self.window.show_window_menu(seat, serial, position.into());
});
}
pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), ExternalError> {
if self.pointer_constraints.is_none() {
return Err(ExternalError::NotSupported(NotSupportedError::new()));
}
if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
return Err(ExternalError::Os(os_error!(crate::platform_impl::OsError::Misc(
"cursor position can be set only for locked cursor."
))));
}
self.apply_on_pointer(|_, data| {
data.set_locked_cursor_position(position.x, position.y);
});
Ok(())
}
pub fn set_cursor_visible(&mut self, cursor_visible: bool) {
self.cursor_visible = cursor_visible;
if self.cursor_visible {
match &self.selected_cursor {
SelectedCursor::Named(icon) => self.set_cursor(*icon),
SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
}
} else {
for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();
pointer.pointer().set_cursor(latest_enter_serial, None, 0, 0);
}
}
}
#[inline]
pub fn set_decorate(&mut self, decorate: bool) {
if decorate == self.decorate {
return;
}
self.decorate = decorate;
match self.last_configure.as_ref().map(|configure| configure.decoration_mode) {
Some(DecorationMode::Server) if !self.decorate => {
self.window.request_decoration_mode(Some(DecorationMode::Client))
},
_ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)),
_ => (),
}
if let Some(frame) = self.frame.as_mut() {
frame.set_hidden(!decorate);
self.resize(self.size);
}
}
#[inline]
pub fn add_seat_focus(&mut self, seat: ObjectId) {
self.seat_focus.insert(seat);
}
#[inline]
pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
self.seat_focus.remove(seat);
}
pub fn set_keyboard_shortcuts_inhibit(&mut self, inhibit: bool) {
if inhibit {
if self.keyboard_shortcuts_inhibitor.is_none() {
if let (Some(manager), Some(seat)) = (
self.keyboard_shortcuts_inhibit_manager.as_ref(),
self.keyboard_shortcuts_inhibit_seat.as_ref(),
) {
let inhibitor = manager.inhibit_shortcuts(
self.window.wl_surface(),
seat,
&self.queue_handle,
GlobalData,
);
self.keyboard_shortcuts_inhibitor = Some(inhibitor);
}
}
} else if let Some(inhibitor) = self.keyboard_shortcuts_inhibitor.take() {
inhibitor.destroy();
}
}
pub fn set_ime_allowed(&mut self, allowed: bool) -> bool {
self.ime_allowed = allowed;
if !allowed {
self.ime_surrounding = None;
}
let mut applied = false;
for text_input in &self.text_inputs {
applied = true;
if allowed {
self.enable_text_input(text_input);
} else {
text_input.disable();
}
text_input.commit();
}
applied
}
pub fn enable_text_input(&self, text_input: &ZwpTextInputV3) {
text_input.enable();
text_input.set_content_type_by_purpose(self.ime_purpose);
if let Some((x, y, width, height)) = self.ime_cursor_area {
text_input.set_cursor_rectangle(x, y, width, height);
}
if let Some(surrounding) = &self.ime_surrounding {
text_input.set_surrounding(surrounding);
}
}
pub fn set_ime_cursor_area(&mut self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
let (x, y) = (position.x as i32, position.y as i32);
let (width, height) = (size.width as i32, size.height as i32);
self.ime_cursor_area = Some((x, y, width, height));
for text_input in self.text_inputs.iter() {
text_input.set_cursor_rectangle(x, y, width, height);
text_input.commit();
}
}
pub fn set_ime_purpose(&mut self, purpose: ImePurpose) {
self.ime_purpose = purpose;
let forget = purpose == ImePurpose::Password
&& self.ime_surrounding.take().is_some()
&& self.ime_allowed;
for text_input in &self.text_inputs {
if forget {
self.enable_text_input(text_input);
} else {
text_input.set_content_type_by_purpose(purpose);
}
text_input.commit();
}
}
pub fn set_ime_surrounding_text(&mut self, surrounding: Option<&ImeSurroundingText>) {
let next = SurroundingText::for_purpose(self.ime_purpose, surrounding);
let change = SurroundingChange::between(
self.ime_surrounding.as_ref(),
next.as_ref(),
self.ime_committed,
);
if change == SurroundingChange::Unchanged {
return;
}
self.ime_surrounding = next;
self.ime_committed = false;
if !self.ime_allowed {
return;
}
for text_input in &self.text_inputs {
match (change, &self.ime_surrounding) {
(SurroundingChange::Send(cause), Some(surrounding)) => {
text_input.set_surrounding(surrounding);
text_input.set_text_change_cause(cause);
},
_ => self.enable_text_input(text_input),
}
text_input.commit();
}
}
pub fn ime_text_committed(&mut self) {
self.ime_committed = true;
}
pub fn ime_purpose(&self) -> ImePurpose {
self.ime_purpose
}
#[inline]
pub fn set_scale_factor(&mut self, scale_factor: f64) {
self.scale_factor = scale_factor;
if self.fractional_scale.is_none() {
let _ = self.window.set_buffer_scale(self.scale_factor as _);
}
if let Some(frame) = self.frame.as_mut() {
frame.set_scaling_factor(scale_factor);
}
}
#[inline]
pub fn set_blur(&mut self, blurred: bool) {
if blurred && self.background_effect.is_none() {
if let Some(manager) = self.background_effect_manager.as_ref() {
let effect =
manager.get_background_effect(self.window.wl_surface(), &self.queue_handle);
self.background_effect = Some(effect);
self.reload_blur_region();
}
} else if !blurred && self.background_effect.is_some() {
self.background_effect.as_ref().unwrap().unset();
self.background_effect = None;
}
if blurred && self.blur.is_none() {
if let Some(blur_manager) = self.blur_manager.as_ref() {
let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
blur.commit();
self.blur = Some(blur);
} else {
if self.background_effect_manager.is_none() {
info!("No blur protocol available, unable to change blur")
}
}
} else if !blurred && self.blur.is_some() {
self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface());
self.blur.take().unwrap().release();
}
}
#[inline]
pub fn request_animated_resize(&mut self, width: i32, height: i32, duration_ms: u32) -> bool {
if self.animated_resize_controller.is_none() {
if let Some(manager) = self.animated_resize_manager.as_ref() {
let controller =
manager.get_animated_resize(self.window.wl_surface(), &self.queue_handle);
self.animated_resize_controller = Some(controller);
} else {
tracing::trace!("Animated resize manager unavailable");
return false;
}
}
if let Some(controller) = self.animated_resize_controller.as_ref() {
tracing::trace!(width, height, duration_ms, "Requesting animated resize to compositor");
controller.resize_to(width, height, duration_ms);
true
} else {
false
}
}
#[inline]
pub fn request_animated_resize_with_position(
&mut self,
x: i32,
y: i32,
width: i32,
height: i32,
duration_ms: u32,
) -> bool {
if self.animated_resize_controller.is_none() {
if let Some(manager) = self.animated_resize_manager.as_ref() {
let controller =
manager.get_animated_resize(self.window.wl_surface(), &self.queue_handle);
self.animated_resize_controller = Some(controller);
} else {
tracing::trace!("Animated resize manager unavailable");
return false;
}
}
if let Some(controller) = self.animated_resize_controller.as_ref() {
tracing::trace!(
x,
y,
width,
height,
duration_ms,
"Requesting animated resize with position to compositor"
);
controller.resize_to_with_position(x, y, width, height, duration_ms);
true
} else {
false
}
}
#[inline]
pub fn set_corner_radius(
&mut self,
top_left: u32,
top_right: u32,
bottom_right: u32,
bottom_left: u32,
) -> bool {
if self.corner_radius_controller.is_none() {
if let Some(manager) = self.corner_radius_manager.as_ref() {
let controller =
manager.get_corner_radius(self.window.wl_surface(), &self.queue_handle);
self.corner_radius_controller = Some(controller);
} else {
tracing::trace!("Corner radius manager unavailable");
return false;
}
}
if let Some(controller) = self.corner_radius_controller.as_ref() {
tracing::trace!(
top_left,
top_right,
bottom_right,
bottom_left,
"Setting corner radius"
);
controller.set_radius(top_left, top_right, bottom_right, bottom_left);
true
} else {
false
}
}
#[inline]
pub fn set_backdrop_color(&mut self, r: u32, g: u32, b: u32, a: u32) -> bool {
if self.backdrop_color_controller.is_none() {
if let Some(manager) = self.backdrop_color_manager.as_ref() {
let controller =
manager.get_backdrop_color(self.window.wl_surface(), &self.queue_handle);
self.backdrop_color_controller = Some(controller);
} else {
tracing::trace!("Backdrop color manager unavailable");
return false;
}
}
if let Some(controller) = self.backdrop_color_controller.as_ref() {
tracing::trace!(r, g, b, a, "Setting backdrop color");
controller.set_color(r, g, b, a);
true
} else {
false
}
}
pub fn register_special_action(&mut self, is_default: bool) -> bool {
if self.special_action_receiver.is_some() {
return true;
}
let Some(manager) = self.special_action_manager.as_ref() else {
tracing::trace!("Special action manager unavailable");
return false;
};
self.special_action_receiver = Some(manager.get_special_action(
self.window.wl_surface(),
is_default,
&self.queue_handle,
));
tracing::info!(is_default, "Registered window as a special action receiver");
true
}
pub fn unregister_special_action(&mut self) -> bool {
self.special_action_receiver.take().is_some()
}
pub fn take_special_action_events(&self) -> Vec<SpecialActionEvent> {
self.special_action_receiver
.as_ref()
.map(SpecialActionReceiver::take_events)
.unwrap_or_default()
}
pub fn kora_identity(&self) -> Option<crate::window::Identity> {
self.identity.as_ref().and_then(|identity| identity.current())
}
pub fn take_identity_events(&mut self) -> Vec<Option<crate::window::Identity>> {
self.identity.as_mut().map(|identity| identity.take_events()).unwrap_or_default()
}
pub fn set_app_commands(&mut self, commands: &crate::window::AppCommands) {
if let Some(handle) = self.app_commands.as_mut() {
handle.publish(commands);
}
}
pub fn note_key_press(&mut self, seat: WlSeat, serial: u32) {
self.last_key_press = Some((seat, serial));
}
pub fn request_app_palette(&self) {
let Some(handle) = self.app_commands.as_ref() else {
return;
};
if let Some((seat, serial)) = &self.last_key_press {
handle.request_palette(seat, *serial);
return;
}
let mut sent = false;
self.apply_on_pointer(|_, data| {
let serial = data.latest_button_serial();
if serial != 0 && !sent {
handle.request_palette(data.seat(), serial);
sent = true;
}
});
}
pub fn take_app_command_events(&mut self) -> Vec<crate::window::AppCommandRequest> {
self.app_commands.as_mut().map(|handle| handle.take_events()).unwrap_or_default()
}
#[allow(clippy::too_many_arguments)]
pub fn embed_toplevel_by_pid(
&mut self,
pid: u32,
app_id: &str,
x: i32,
y: i32,
width: i32,
height: i32,
interactive: bool,
) -> Option<u64> {
let manager = self.surface_embed_manager.as_ref()?;
let embedded = manager.embed_toplevel_by_pid(
self.window.wl_surface(),
pid,
app_id,
&self.queue_handle,
);
if width > 0 && height > 0 {
embedded.set_geometry(x, y, width, height);
}
embedded.set_interactive(interactive);
embedded.commit();
let embed_id = self.next_embed_id;
self.next_embed_id += 1;
self.embedded_surfaces.insert(embed_id, embedded);
tracing::info!(
embed_id,
pid,
app_id,
x,
y,
width,
height,
interactive,
"Created embed for PID"
);
Some(embed_id)
}
pub fn set_embed_geometry(
&mut self,
embed_id: u64,
x: i32,
y: i32,
width: i32,
height: i32,
) -> bool {
if let Some(embedded) = self.embedded_surfaces.get(&embed_id) {
embedded.set_geometry(x, y, width, height);
embedded.commit();
tracing::trace!(embed_id, x, y, width, height, "Updated embed geometry");
true
} else {
tracing::warn!(embed_id, "Embed not found for geometry update");
false
}
}
#[allow(clippy::too_many_arguments)]
pub fn set_embed_anchor(
&mut self,
embed_id: u64,
anchor: u32,
margin_top: i32,
margin_right: i32,
margin_bottom: i32,
margin_left: i32,
width: i32,
height: i32,
) -> bool {
if let Some(embedded) = self.embedded_surfaces.get(&embed_id) {
embedded.set_anchor(
anchor,
margin_top,
margin_right,
margin_bottom,
margin_left,
width,
height,
);
embedded.commit();
tracing::trace!(
embed_id,
anchor,
margin_top,
margin_right,
margin_bottom,
margin_left,
width,
height,
"Updated embed anchor"
);
true
} else {
tracing::warn!(embed_id, "Embed not found for anchor update");
false
}
}
pub fn set_embed_corner_radius(
&mut self,
embed_id: u64,
top_left: u32,
top_right: u32,
bottom_right: u32,
bottom_left: u32,
) -> bool {
if let Some(embedded) = self.embedded_surfaces.get(&embed_id) {
embedded.set_corner_radius(top_left, top_right, bottom_right, bottom_left);
embedded.commit();
tracing::trace!(
embed_id,
top_left,
top_right,
bottom_right,
bottom_left,
"Updated embed corner radius"
);
true
} else {
tracing::warn!(embed_id, "Embed not found for corner radius update");
false
}
}
pub fn set_embed_interactive(&mut self, embed_id: u64, interactive: bool) -> bool {
if let Some(embedded) = self.embedded_surfaces.get(&embed_id) {
embedded.set_interactive(interactive);
embedded.commit();
tracing::trace!(embed_id, interactive, "Updated embed interactivity");
true
} else {
tracing::warn!(embed_id, "Embed not found for interactive update");
false
}
}
pub fn remove_embed(&mut self, embed_id: u64) -> bool {
if self.embedded_surfaces.remove(&embed_id).is_some() {
tracing::info!(embed_id, "Removed embed");
true
} else {
tracing::warn!(embed_id, "Embed not found for removal");
false
}
}
pub fn is_embed_valid(&self, embed_id: u64) -> bool {
self.embedded_surfaces.get(&embed_id).is_some_and(|e| e.is_valid())
}
pub fn start_drag(
&mut self,
mime_types: Vec<String>,
actions: u32,
data: Vec<Vec<u8>>,
icon_pixels: Option<(u32, u32, Vec<u8>, i32)>,
) -> (bool, Vec<String>, Vec<Vec<u8>>) {
use crate::platform_impl::wayland::types::wayland_dnd::DndSourceData;
let dnd_manager = match self.dnd_manager.as_ref() {
Some(m) => m,
None => {
tracing::error!("DnD manager unavailable, cannot start drag");
return (false, mime_types, data);
},
};
let data_device = match self.dnd_data_devices.first() {
Some(d) => d.clone(),
None => {
tracing::error!("No data device available, cannot start drag");
return (false, mime_types, data);
},
};
let serial = self
.pointers
.iter()
.filter_map(|p| p.upgrade())
.map(|p| p.pointer().winit_data().latest_button_serial())
.find(|&s| s != 0);
let serial = match serial {
Some(s) => s,
None => {
tracing::error!("No pointer serial available for DnD start_drag");
return (false, mime_types, data);
},
};
let seat = self
.pointers
.iter()
.filter_map(|p| p.upgrade())
.map(|p| p.pointer().winit_data().seat().clone())
.next();
let _seat = match seat {
Some(s) => s,
None => {
tracing::error!("No seat available for DnD start_drag");
return (false, mime_types, data);
},
};
let payload = DndSourceData { mime_types: mime_types.clone(), data: data.clone() };
let source = dnd_manager.create_data_source(&self.queue_handle, payload);
for mime in &mime_types {
source.offer(mime.clone());
}
use wayland_client::protocol::wl_data_device_manager::DndAction;
let mut wl_actions = DndAction::empty();
if actions & 1 != 0 {
wl_actions |= DndAction::Copy;
}
if actions & 2 != 0 {
wl_actions |= DndAction::Move;
}
if actions & 4 != 0 {
wl_actions |= DndAction::Ask;
}
source.set_actions(wl_actions);
let icon = {
let mut pool = self.custom_cursor_pool.lock().unwrap();
if let Some((w, h, pixels, scale)) = icon_pixels {
crate::platform_impl::wayland::types::wayland_dnd::create_dnd_icon_from_pixels(
&self.compositor,
&mut pool,
&self.queue_handle,
w,
h,
&pixels,
scale,
)
} else {
crate::platform_impl::wayland::types::wayland_dnd::create_dnd_icon_surface(
&self.compositor,
&mut pool,
&self.queue_handle,
)
}
};
let icon_surface_ref = icon.as_ref().map(|(s, _)| s);
let had_icon = icon_surface_ref.is_some();
let origin = self.window.wl_surface();
data_device.start_drag(Some(&source), origin, icon_surface_ref, serial);
self.dnd_icon = icon;
tracing::info!(
serial,
?mime_types,
has_icon = had_icon,
"DnD: started drag from window (wl_data_device.start_drag)"
);
{
let wid = crate::platform_impl::wayland::make_wid(self.window.wl_surface());
let pairs: Vec<(String, Vec<u8>)> =
mime_types.iter().cloned().zip(data.iter().cloned()).collect();
let mut shared = self.dnd_shared_offer.lock().unwrap();
shared.self_source = Some(pairs);
shared.source_window = Some(wid);
}
(true, mime_types, data)
}
pub fn dnd_accept_mime_type(&self, mime_type: Option<&str>) {
let guard = self.dnd_shared_offer.lock().unwrap();
if let Some(offer) = guard.current_offer.as_ref() {
let serial = self
.pointers
.iter()
.filter_map(|p| p.upgrade())
.map(|p| p.pointer().winit_data().latest_enter_serial())
.find(|&s| s != 0)
.unwrap_or(0);
offer.accept(serial, mime_type.map(String::from));
tracing::trace!(?mime_type, serial, "DnD: accept_mime_type");
} else {
tracing::warn!("DnD: accept_mime_type called with no current offer");
}
}
pub fn dnd_set_actions(&self, actions: u32, preferred: u32) {
let guard = self.dnd_shared_offer.lock().unwrap();
if let Some(offer) = guard.current_offer.as_ref() {
use wayland_client::protocol::wl_data_device_manager::DndAction;
let mut wl_actions = DndAction::empty();
if actions & 1 != 0 {
wl_actions |= DndAction::Copy;
}
if actions & 2 != 0 {
wl_actions |= DndAction::Move;
}
if actions & 4 != 0 {
wl_actions |= DndAction::Ask;
}
let mut wl_preferred = DndAction::empty();
if preferred & 1 != 0 {
wl_preferred |= DndAction::Copy;
}
if preferred & 2 != 0 {
wl_preferred |= DndAction::Move;
}
if preferred & 4 != 0 {
wl_preferred |= DndAction::Ask;
}
offer.set_actions(wl_actions, wl_preferred);
tracing::trace!(?wl_actions, ?wl_preferred, "DnD: set_actions");
} else {
tracing::warn!("DnD: set_actions called with no current offer");
}
}
pub fn dnd_finish(&self) {
let mut guard = self.dnd_shared_offer.lock().unwrap();
if let Some(offer) = guard.current_offer.take() {
offer.finish();
tracing::info!("DnD: finish - called offer.finish() and cleared offer");
} else {
tracing::warn!("DnD: finish called with no current offer");
}
}
pub fn dnd_request_data(&self, mime_type: &str) {
use std::io::Read;
use std::os::fd::AsFd;
let mut guard = self.dnd_shared_offer.lock().unwrap();
if let Some(pairs) = guard.self_source.as_ref() {
if let Some((_, data)) = pairs.iter().find(|(m, _)| m == mime_type) {
let data = data.clone();
tracing::debug!(
mime_type,
bytes = data.len(),
"DnD: self-drag, data read directly"
);
guard.pending_data.push((mime_type.to_string(), data));
return;
}
}
let Some(offer) = guard.current_offer.as_ref() else {
tracing::warn!("DnD: request_data called with no current offer");
return;
};
let (read_fd, write_fd) = match rustix::pipe::pipe() {
Ok(fds) => fds,
Err(e) => {
tracing::error!(?e, "DnD: failed to create pipe for receive");
return;
},
};
tracing::trace!(?mime_type, "DnD: requesting data via receive");
offer.receive(mime_type.to_string(), write_fd.as_fd());
drop(write_fd);
let _ = self.connection.flush();
let mut read_file = std::fs::File::from(read_fd);
let mut data = Vec::new();
if let Err(e) = read_file.read_to_end(&mut data) {
tracing::error!(?e, "DnD: failed to read data from pipe");
return;
}
tracing::trace!(mime_type, bytes = data.len(), "DnD: received data");
guard.pending_data.push((mime_type.to_string(), data));
}
pub fn set_title(&mut self, mut title: String) {
if title.len() > 1024 {
let mut new_len = 1024;
while !title.is_char_boundary(new_len) {
new_len -= 1;
}
title.truncate(new_len);
}
if let Some(frame) = self.frame.as_mut() {
frame.set_title(&title);
}
self.window.set_title(&title);
self.title = title;
}
#[inline]
pub fn set_transparent(&mut self, transparent: bool) {
self.transparent = transparent;
self.reload_transparency_hint();
}
pub fn set_imported_parent(&mut self, imported: ImportedToplevel) {
self.xdg_imported_parent = Some(imported);
}
#[inline]
pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) {
if !self.text_inputs.iter().any(|t| t == text_input) {
self.text_inputs.push(text_input.clone());
}
}
#[inline]
pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) {
if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) {
self.text_inputs.remove(position);
}
}
#[inline]
pub fn title(&self) -> &str {
&self.title
}
}
impl Drop for WindowState {
fn drop(&mut self) {
if let Some(blur) = self.blur.take() {
blur.release();
}
if let Some(fs) = self.fractional_scale.take() {
fs.destroy();
}
if let Some(viewport) = self.viewport.take() {
viewport.destroy();
}
}
}
#[derive(Clone, Copy)]
struct GrabState {
user_grab_mode: CursorGrabMode,
current_grab_mode: CursorGrabMode,
}
impl GrabState {
fn new() -> Self {
Self { user_grab_mode: CursorGrabMode::None, current_grab_mode: CursorGrabMode::None }
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameCallbackState {
#[default]
None,
Requested,
Received,
}
impl From<ResizeDirection> for XdgResizeEdge {
fn from(value: ResizeDirection) -> Self {
match value {
ResizeDirection::North => XdgResizeEdge::Top,
ResizeDirection::West => XdgResizeEdge::Left,
ResizeDirection::NorthWest => XdgResizeEdge::TopLeft,
ResizeDirection::NorthEast => XdgResizeEdge::TopRight,
ResizeDirection::East => XdgResizeEdge::Right,
ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft,
ResizeDirection::SouthEast => XdgResizeEdge::BottomRight,
ResizeDirection::South => XdgResizeEdge::Bottom,
}
}
}
#[cfg(feature = "sctk-adwaita")]
fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
match theme {
Some(Theme::Light) => sctk_adwaita::FrameConfig::light(),
Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(),
None => sctk_adwaita::FrameConfig::auto(),
}
}