1use std::num::NonZeroU32;
4use std::sync::{Arc, Mutex, Weak};
5use std::time::Duration;
6
7use ahash::HashSet;
8use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt};
9use sctk::reexports::client::backend::ObjectId;
10use sctk::reexports::client::protocol::wl_seat::WlSeat;
11use sctk::reexports::client::protocol::wl_shm::WlShm;
12use sctk::reexports::client::protocol::wl_surface::WlSurface;
13use sctk::reexports::client::{Connection, Proxy, QueueHandle};
14use sctk::reexports::csd_frame::{
15 DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowState as XdgWindowState,
16};
17use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
18use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
19use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
20use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
21use sctk::seat::pointer::{PointerDataExt, ThemedPointer};
22use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
23use sctk::shell::xdg::XdgSurface;
24use sctk::shell::WaylandSurface;
25use sctk::shm::slot::SlotPool;
26use sctk::shm::Shm;
27use sctk::subcompositor::SubcompositorState;
28use tracing::{info, warn};
29use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
30
31use crate::cursor::CustomCursor as RootCustomCursor;
32use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
33use crate::error::{NotSupportedError, RequestError};
34use crate::platform_impl::wayland::logical_to_physical_rounded;
35use crate::platform_impl::wayland::seat::{
36 PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext,
37};
38use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
39use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
40use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
41use crate::platform_impl::{PlatformCustomCursor, WindowId};
42use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
43
44#[cfg(feature = "sctk-adwaita")]
45pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
46#[cfg(not(feature = "sctk-adwaita"))]
47pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame<WinitState>;
48
49const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
51
52pub struct WindowState {
54 pub connection: Connection,
56
57 pub shm: WlShm,
59
60 custom_cursor_pool: Arc<Mutex<SlotPool>>,
62
63 pub last_configure: Option<WindowConfigure>,
65
66 pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
68
69 selected_cursor: SelectedCursor,
70
71 pub cursor_visible: bool,
73
74 pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
76
77 pub queue_handle: QueueHandle<WinitState>,
79
80 theme: Option<Theme>,
82
83 title: String,
85
86 resizable: bool,
88
89 seat_focus: HashSet<ObjectId>,
93
94 scale_factor: f64,
96
97 transparent: bool,
99
100 compositor: Arc<CompositorState>,
102
103 cursor_grab_mode: GrabState,
105
106 ime_allowed: bool,
108
109 ime_purpose: ImePurpose,
111
112 text_inputs: Vec<ZwpTextInputV3>,
114
115 size: LogicalSize<u32>,
117
118 csd_fails: bool,
120
121 decorate: bool,
123
124 min_surface_size: LogicalSize<u32>,
126 max_surface_size: Option<LogicalSize<u32>>,
127
128 stateless_size: LogicalSize<u32>,
132
133 initial_size: Option<Size>,
136
137 frame_callback_state: FrameCallbackState,
139
140 viewport: Option<WpViewport>,
141 fractional_scale: Option<WpFractionalScaleV1>,
142 blur: Option<OrgKdeKwinBlur>,
143 blur_manager: Option<KWinBlurManager>,
144
145 has_pending_move: Option<u32>,
149
150 pub window: Window,
152
153 frame: Option<WinitFrame>,
159}
160
161impl WindowState {
162 pub fn new(
164 connection: Connection,
165 queue_handle: &QueueHandle<WinitState>,
166 winit_state: &WinitState,
167 initial_size: Size,
168 window: Window,
169 theme: Option<Theme>,
170 ) -> Self {
171 let compositor = winit_state.compositor_state.clone();
172 let pointer_constraints = winit_state.pointer_constraints.clone();
173 let viewport = winit_state
174 .viewporter_state
175 .as_ref()
176 .map(|state| state.get_viewport(window.wl_surface(), queue_handle));
177 let fractional_scale = winit_state
178 .fractional_scaling_manager
179 .as_ref()
180 .map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
181
182 Self {
183 blur: None,
184 blur_manager: winit_state.kwin_blur_manager.clone(),
185 compositor,
186 connection,
187 csd_fails: false,
188 cursor_grab_mode: GrabState::new(),
189 selected_cursor: Default::default(),
190 cursor_visible: true,
191 decorate: true,
192 fractional_scale,
193 frame: None,
194 frame_callback_state: FrameCallbackState::None,
195 seat_focus: Default::default(),
196 has_pending_move: None,
197 ime_allowed: false,
198 ime_purpose: ImePurpose::Normal,
199 last_configure: None,
200 max_surface_size: None,
201 min_surface_size: MIN_WINDOW_SIZE,
202 pointer_constraints,
203 pointers: Default::default(),
204 queue_handle: queue_handle.clone(),
205 resizable: true,
206 scale_factor: 1.,
207 shm: winit_state.shm.wl_shm().clone(),
208 custom_cursor_pool: winit_state.custom_cursor_pool.clone(),
209 size: initial_size.to_logical(1.),
210 stateless_size: initial_size.to_logical(1.),
211 initial_size: Some(initial_size),
212 text_inputs: Vec::new(),
213 theme,
214 title: String::default(),
215 transparent: false,
216 viewport,
217 window,
218 }
219 }
220
221 fn apply_on_pointer<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
223 &self,
224 callback: F,
225 ) {
226 self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| {
227 let data = pointer.pointer().winit_data();
228 callback(pointer.as_ref(), data);
229 })
230 }
231
232 pub fn frame_callback_state(&self) -> FrameCallbackState {
234 self.frame_callback_state
235 }
236
237 pub fn frame_callback_received(&mut self) {
239 self.frame_callback_state = FrameCallbackState::Received;
240 }
241
242 pub fn frame_callback_reset(&mut self) {
244 self.frame_callback_state = FrameCallbackState::None;
245 }
246
247 pub fn request_frame_callback(&mut self) {
249 let surface = self.window.wl_surface();
250 match self.frame_callback_state {
251 FrameCallbackState::None | FrameCallbackState::Received => {
252 self.frame_callback_state = FrameCallbackState::Requested;
253 surface.frame(&self.queue_handle, surface.clone());
254 },
255 FrameCallbackState::Requested => (),
256 }
257 }
258
259 pub fn configure(
260 &mut self,
261 configure: WindowConfigure,
262 shm: &Shm,
263 subcompositor: &Option<Arc<SubcompositorState>>,
264 ) -> bool {
265 if let Some(initial_size) = self.initial_size.take() {
269 self.size = initial_size.to_logical(self.scale_factor());
270 self.stateless_size = self.size;
271 }
272
273 if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
274 configure.decoration_mode == DecorationMode::Client
275 && self.frame.is_none()
276 && !self.csd_fails
277 }) {
278 match WinitFrame::new(
279 &self.window,
280 shm,
281 #[cfg(feature = "sctk-adwaita")]
282 self.compositor.clone(),
283 subcompositor.clone(),
284 self.queue_handle.clone(),
285 #[cfg(feature = "sctk-adwaita")]
286 into_sctk_adwaita_config(self.theme),
287 ) {
288 Ok(mut frame) => {
289 frame.set_title(&self.title);
290 frame.set_scaling_factor(self.scale_factor);
291 frame.set_hidden(!self.decorate);
293 self.frame = Some(frame);
294 },
295 Err(err) => {
296 warn!("Failed to create client side decorations frame: {err}");
297 self.csd_fails = true;
298 },
299 }
300 } else if configure.decoration_mode == DecorationMode::Server {
301 self.frame = None;
303 }
304
305 let stateless = Self::is_stateless(&configure);
306
307 let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
308 frame.update_state(configure.state);
310
311 match configure.new_size {
312 (Some(width), Some(height)) => {
313 let (width, height) = frame.subtract_borders(width, height);
314 let width = width.map(|w| w.get()).unwrap_or(1);
315 let height = height.map(|h| h.get()).unwrap_or(1);
316 ((width, height).into(), false)
317 },
318 (..) if stateless => (self.stateless_size, true),
319 _ => (self.size, true),
320 }
321 } else {
322 match configure.new_size {
323 (Some(width), Some(height)) => ((width.get(), height.get()).into(), false),
324 _ if stateless => (self.stateless_size, true),
325 _ => (self.size, true),
326 }
327 };
328
329 if constrain {
331 let bounds = self.surface_size_bounds(&configure);
332 new_size.width =
333 bounds.0.map(|bound_w| new_size.width.min(bound_w.get())).unwrap_or(new_size.width);
334 new_size.height = bounds
335 .1
336 .map(|bound_h| new_size.height.min(bound_h.get()))
337 .unwrap_or(new_size.height);
338 }
339
340 let new_state = configure.state;
341 let old_state = self.last_configure.as_ref().map(|configure| configure.state);
342
343 let state_change_requires_resize = old_state
344 .map(|old_state| {
345 !old_state
346 .symmetric_difference(new_state)
347 .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
348 .is_empty()
349 })
350 .unwrap_or(true);
352
353 self.last_configure = Some(configure);
355
356 if state_change_requires_resize || new_size != self.surface_size() {
357 self.resize(new_size);
358 true
359 } else {
360 false
361 }
362 }
363
364 fn surface_size_bounds(
366 &self,
367 configure: &WindowConfigure,
368 ) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
369 let configure_bounds = match configure.suggested_bounds {
370 Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
371 None => (None, None),
372 };
373
374 if let Some(frame) = self.frame.as_ref() {
375 let (width, height) = frame.subtract_borders(
376 configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
377 configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
378 );
379 (configure_bounds.0.and(width), configure_bounds.1.and(height))
380 } else {
381 configure_bounds
382 }
383 }
384
385 #[inline]
386 fn is_stateless(configure: &WindowConfigure) -> bool {
387 !(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled())
388 }
389
390 pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
392 let xdg_toplevel = self.window.xdg_toplevel();
393
394 self.apply_on_pointer(|_, data| {
396 let serial = data.latest_button_serial();
397 let seat = data.seat();
398 xdg_toplevel.resize(seat, serial, direction.into());
399 });
400
401 Ok(())
402 }
403
404 pub fn drag_window(&self) -> Result<(), RequestError> {
406 let xdg_toplevel = self.window.xdg_toplevel();
407 self.apply_on_pointer(|_, data| {
409 let serial = data.latest_button_serial();
410 let seat = data.seat();
411 xdg_toplevel._move(seat, serial);
412 });
413
414 Ok(())
415 }
416
417 #[allow(clippy::too_many_arguments)]
419 pub fn frame_click(
420 &mut self,
421 click: FrameClick,
422 pressed: bool,
423 seat: &WlSeat,
424 serial: u32,
425 timestamp: Duration,
426 window_id: WindowId,
427 updates: &mut Vec<WindowCompositorUpdate>,
428 ) -> Option<bool> {
429 match self.frame.as_mut()?.on_click(timestamp, click, pressed)? {
430 FrameAction::Minimize => self.window.set_minimized(),
431 FrameAction::Maximize => self.window.set_maximized(),
432 FrameAction::UnMaximize => self.window.unset_maximized(),
433 FrameAction::Close => WinitState::queue_close(updates, window_id),
434 FrameAction::Move => self.has_pending_move = Some(serial),
435 FrameAction::Resize(edge) => {
436 let edge = match edge {
437 ResizeEdge::None => XdgResizeEdge::None,
438 ResizeEdge::Top => XdgResizeEdge::Top,
439 ResizeEdge::Bottom => XdgResizeEdge::Bottom,
440 ResizeEdge::Left => XdgResizeEdge::Left,
441 ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
442 ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
443 ResizeEdge::Right => XdgResizeEdge::Right,
444 ResizeEdge::TopRight => XdgResizeEdge::TopRight,
445 ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
446 _ => return None,
447 };
448 self.window.resize(seat, serial, edge);
449 },
450 FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
451 _ => (),
452 };
453
454 Some(false)
455 }
456
457 pub fn frame_point_left(&mut self) {
458 if let Some(frame) = self.frame.as_mut() {
459 frame.click_point_left();
460 }
461 }
462
463 pub fn frame_point_moved(
465 &mut self,
466 seat: &WlSeat,
467 surface: &WlSurface,
468 timestamp: Duration,
469 x: f64,
470 y: f64,
471 ) -> Option<CursorIcon> {
472 let serial = self.has_pending_move.take();
474
475 if let Some(frame) = self.frame.as_mut() {
476 let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y);
477 if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
480 self.window.move_(seat, serial);
481 None
482 } else {
483 cursor
484 }
485 } else {
486 None
487 }
488 }
489
490 #[inline]
492 pub fn resizable(&self) -> bool {
493 self.resizable
494 }
495
496 #[inline]
500 pub fn set_resizable(&mut self, resizable: bool) -> bool {
501 if self.resizable == resizable {
502 return false;
503 }
504
505 self.resizable = resizable;
506 if resizable {
507 self.reload_min_max_hints();
509 } else {
510 self.set_min_surface_size(Some(self.size));
511 self.set_max_surface_size(Some(self.size));
512 }
513
514 if let Some(frame) = self.frame.as_mut() {
516 frame.set_resizable(resizable);
517 }
518
519 true
520 }
521
522 #[inline]
524 pub fn has_focus(&self) -> bool {
525 !self.seat_focus.is_empty()
526 }
527
528 #[inline]
530 pub fn ime_allowed(&self) -> bool {
531 self.ime_allowed
532 }
533
534 #[inline]
536 pub fn surface_size(&self) -> LogicalSize<u32> {
537 self.size
538 }
539
540 #[inline]
542 pub fn is_configured(&self) -> bool {
543 self.last_configure.is_some()
544 }
545
546 #[inline]
547 pub fn is_decorated(&mut self) -> bool {
548 let csd = self
549 .last_configure
550 .as_ref()
551 .map(|configure| configure.decoration_mode == DecorationMode::Client)
552 .unwrap_or(false);
553 if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
554 !frame.is_hidden()
555 } else {
556 true
558 }
559 }
560
561 #[inline]
563 pub fn outer_size(&self) -> LogicalSize<u32> {
564 self.frame
565 .as_ref()
566 .map(|frame| frame.add_borders(self.size.width, self.size.height).into())
567 .unwrap_or(self.size)
568 }
569
570 pub fn pointer_entered(&mut self, added: Weak<ThemedPointer<WinitPointerData>>) {
572 self.pointers.push(added);
573 self.reload_cursor_style();
574
575 let mode = self.cursor_grab_mode.user_grab_mode;
576 let _ = self.set_cursor_grab_inner(mode);
577 }
578
579 pub fn pointer_left(&mut self, removed: Weak<ThemedPointer<WinitPointerData>>) {
581 let mut new_pointers = Vec::new();
582 for pointer in self.pointers.drain(..) {
583 if let Some(pointer) = pointer.upgrade() {
584 if pointer.pointer() != removed.upgrade().unwrap().pointer() {
585 new_pointers.push(Arc::downgrade(&pointer));
586 }
587 }
588 }
589
590 self.pointers = new_pointers;
591 }
592
593 pub fn refresh_frame(&mut self) -> bool {
595 if let Some(frame) = self.frame.as_mut() {
596 if !frame.is_hidden() && frame.is_dirty() {
597 return frame.draw();
598 }
599 }
600
601 false
602 }
603
604 pub fn reload_cursor_style(&mut self) {
606 if self.cursor_visible {
607 match &self.selected_cursor {
608 SelectedCursor::Named(icon) => self.set_cursor(*icon),
609 SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
610 }
611 } else {
612 self.set_cursor_visible(self.cursor_visible);
613 }
614 }
615
616 pub fn reload_transparency_hint(&self) {
618 let surface = self.window.wl_surface();
619
620 if self.transparent {
621 surface.set_opaque_region(None);
622 } else if let Ok(region) = Region::new(&*self.compositor) {
623 region.add(0, 0, i32::MAX, i32::MAX);
624 surface.set_opaque_region(Some(region.wl_region()));
625 } else {
626 warn!("Failed to mark window opaque.");
627 }
628 }
629
630 pub fn request_surface_size(&mut self, surface_size: Size) -> PhysicalSize<u32> {
632 if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) {
633 self.resize(surface_size.to_logical(self.scale_factor()))
634 }
635
636 logical_to_physical_rounded(self.surface_size(), self.scale_factor())
637 }
638
639 fn resize(&mut self, surface_size: LogicalSize<u32>) {
641 self.size = surface_size;
642
643 if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
645 self.stateless_size = surface_size;
646 }
647
648 let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
650 if !frame.is_hidden() {
652 frame.resize(
653 NonZeroU32::new(self.size.width).unwrap(),
654 NonZeroU32::new(self.size.height).unwrap(),
655 );
656 }
657
658 (frame.location(), frame.add_borders(self.size.width, self.size.height).into())
659 } else {
660 ((0, 0), self.size)
661 };
662
663 self.reload_transparency_hint();
665
666 self.window.xdg_surface().set_window_geometry(
668 x,
669 y,
670 outer_size.width as i32,
671 outer_size.height as i32,
672 );
673
674 if let Some(viewport) = self.viewport.as_ref() {
676 viewport.set_destination(self.size.width as _, self.size.height as _);
678 }
679 }
680
681 #[inline]
683 pub fn scale_factor(&self) -> f64 {
684 self.scale_factor
685 }
686
687 pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
689 self.selected_cursor = SelectedCursor::Named(cursor_icon);
690
691 if !self.cursor_visible {
692 return;
693 }
694
695 self.apply_on_pointer(|pointer, _| {
696 if pointer.set_cursor(&self.connection, cursor_icon).is_err() {
697 warn!("Failed to set cursor to {:?}", cursor_icon);
698 }
699 })
700 }
701
702 pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
704 let cursor = match cursor {
705 RootCustomCursor { inner: PlatformCustomCursor::Wayland(cursor) } => cursor.0,
706 #[cfg(x11_platform)]
707 RootCustomCursor { inner: PlatformCustomCursor::X(_) } => {
708 tracing::error!("passed a X11 cursor to Wayland backend");
709 return;
710 },
711 };
712
713 let cursor = {
714 let mut pool = self.custom_cursor_pool.lock().unwrap();
715 CustomCursor::new(&mut pool, &cursor)
716 };
717
718 if self.cursor_visible {
719 self.apply_custom_cursor(&cursor);
720 }
721
722 self.selected_cursor = SelectedCursor::Custom(cursor);
723 }
724
725 fn apply_custom_cursor(&self, cursor: &CustomCursor) {
726 self.apply_on_pointer(|pointer, _| {
727 let surface = pointer.surface();
728
729 let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
730
731 surface.set_buffer_scale(scale);
732 surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
733 if surface.version() >= 4 {
734 surface.damage_buffer(0, 0, cursor.w, cursor.h);
735 } else {
736 surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
737 }
738 surface.commit();
739
740 let serial = pointer
741 .pointer()
742 .data::<WinitPointerData>()
743 .and_then(|data| data.pointer_data().latest_enter_serial())
744 .unwrap();
745
746 pointer.pointer().set_cursor(
747 serial,
748 Some(surface),
749 cursor.hotspot_x / scale,
750 cursor.hotspot_y / scale,
751 );
752 });
753 }
754
755 pub fn set_min_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
757 let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
759 size.width = size.width.max(MIN_WINDOW_SIZE.width);
760 size.height = size.height.max(MIN_WINDOW_SIZE.height);
761
762 let size = self
764 .frame
765 .as_ref()
766 .map(|frame| frame.add_borders(size.width, size.height).into())
767 .unwrap_or(size);
768
769 self.min_surface_size = size;
770 self.window.set_min_size(Some(size.into()));
771 }
772
773 pub fn set_max_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
775 let size = size.map(|size| {
776 self.frame
777 .as_ref()
778 .map(|frame| frame.add_borders(size.width, size.height).into())
779 .unwrap_or(size)
780 });
781
782 self.max_surface_size = size;
783 self.window.set_max_size(size.map(Into::into));
784 }
785
786 pub fn set_theme(&mut self, theme: Option<Theme>) {
788 self.theme = theme;
789 #[cfg(feature = "sctk-adwaita")]
790 if let Some(frame) = self.frame.as_mut() {
791 frame.set_config(into_sctk_adwaita_config(theme))
792 }
793 }
794
795 #[inline]
797 pub fn theme(&self) -> Option<Theme> {
798 self.theme
799 }
800
801 pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), RequestError> {
803 if self.cursor_grab_mode.user_grab_mode == mode {
804 return Ok(());
805 }
806
807 self.set_cursor_grab_inner(mode)?;
808 self.cursor_grab_mode.user_grab_mode = mode;
810 Ok(())
811 }
812
813 pub fn reload_min_max_hints(&mut self) {
815 self.set_min_surface_size(Some(self.min_surface_size));
816 self.set_max_surface_size(self.max_surface_size);
817 }
818
819 fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), RequestError> {
821 let pointer_constraints = match self.pointer_constraints.as_ref() {
822 Some(pointer_constraints) => pointer_constraints,
823 None if mode == CursorGrabMode::None => return Ok(()),
824 None => {
825 return Err(
826 NotSupportedError::new("zwp_pointer_constraints is not available").into()
827 )
828 },
829 };
830
831 let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
833
834 match old_mode {
835 CursorGrabMode::None => (),
836 CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
837 data.unconfine_pointer();
838 }),
839 CursorGrabMode::Locked => {
840 self.apply_on_pointer(|_, data| data.unlock_pointer());
841 },
842 }
843
844 let surface = self.window.wl_surface();
845 match mode {
846 CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
847 let pointer = pointer.pointer();
848 data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
849 }),
850 CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
851 let pointer = pointer.pointer();
852 data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
853 }),
854 CursorGrabMode::None => {
855 },
857 }
858
859 Ok(())
860 }
861
862 pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
863 self.apply_on_pointer(|_, data| {
865 let serial = data.latest_button_serial();
866 let seat = data.seat();
867 self.window.show_window_menu(seat, serial, position.into());
868 });
869 }
870
871 pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), RequestError> {
873 if self.pointer_constraints.is_none() {
874 return Err(NotSupportedError::new("zwp_pointer_constraints is not available").into());
875 }
876
877 if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
879 return Err(NotSupportedError::new(
880 "cursor position could only be changed for locked pointer",
881 )
882 .into());
883 }
884
885 self.apply_on_pointer(|_, data| {
886 data.set_locked_cursor_position(position.x, position.y);
887 });
888
889 Ok(())
890 }
891
892 pub fn set_cursor_visible(&mut self, cursor_visible: bool) {
894 self.cursor_visible = cursor_visible;
895
896 if self.cursor_visible {
897 match &self.selected_cursor {
898 SelectedCursor::Named(icon) => self.set_cursor(*icon),
899 SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
900 }
901 } else {
902 for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
903 let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();
904
905 pointer.pointer().set_cursor(latest_enter_serial, None, 0, 0);
906 }
907 }
908 }
909
910 #[inline]
912 pub fn set_decorate(&mut self, decorate: bool) {
913 if decorate == self.decorate {
914 return;
915 }
916
917 self.decorate = decorate;
918
919 match self.last_configure.as_ref().map(|configure| configure.decoration_mode) {
920 Some(DecorationMode::Server) if !self.decorate => {
921 self.window.request_decoration_mode(Some(DecorationMode::Client))
923 },
924 _ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)),
925 _ => (),
926 }
927
928 if let Some(frame) = self.frame.as_mut() {
929 frame.set_hidden(!decorate);
930 self.resize(self.size);
932 }
933 }
934
935 #[inline]
937 pub fn add_seat_focus(&mut self, seat: ObjectId) {
938 self.seat_focus.insert(seat);
939 }
940
941 #[inline]
943 pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
944 self.seat_focus.remove(seat);
945 }
946
947 pub fn set_ime_allowed(&mut self, allowed: bool) -> bool {
949 self.ime_allowed = allowed;
950
951 let mut applied = false;
952 for text_input in &self.text_inputs {
953 applied = true;
954 if allowed {
955 text_input.enable();
956 text_input.set_content_type_by_purpose(self.ime_purpose);
957 } else {
958 text_input.disable();
959 }
960 text_input.commit();
961 }
962
963 applied
964 }
965
966 pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
968 let (x, y) = (position.x as i32, position.y as i32);
972 let (width, height) = (size.width as i32, size.height as i32);
973 for text_input in self.text_inputs.iter() {
974 text_input.set_cursor_rectangle(x, y, width, height);
975 text_input.commit();
976 }
977 }
978
979 pub fn set_ime_purpose(&mut self, purpose: ImePurpose) {
981 self.ime_purpose = purpose;
982
983 for text_input in &self.text_inputs {
984 text_input.set_content_type_by_purpose(purpose);
985 text_input.commit();
986 }
987 }
988
989 pub fn ime_purpose(&self) -> ImePurpose {
991 self.ime_purpose
992 }
993
994 #[inline]
996 pub fn set_scale_factor(&mut self, scale_factor: f64) {
997 self.scale_factor = scale_factor;
998
999 if self.fractional_scale.is_none() {
1001 let _ = self.window.set_buffer_scale(self.scale_factor as _);
1002 }
1003
1004 if let Some(frame) = self.frame.as_mut() {
1005 frame.set_scaling_factor(scale_factor);
1006 }
1007 }
1008
1009 #[inline]
1011 pub fn set_blur(&mut self, blurred: bool) {
1012 if blurred && self.blur.is_none() {
1013 if let Some(blur_manager) = self.blur_manager.as_ref() {
1014 let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
1015 blur.commit();
1016 self.blur = Some(blur);
1017 } else {
1018 info!("Blur manager unavailable, unable to change blur")
1019 }
1020 } else if !blurred && self.blur.is_some() {
1021 self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface());
1022 self.blur.take().unwrap().release();
1023 }
1024 }
1025
1026 pub fn set_title(&mut self, mut title: String) {
1030 if title.len() > 1024 {
1033 let mut new_len = 1024;
1034 while !title.is_char_boundary(new_len) {
1035 new_len -= 1;
1036 }
1037 title.truncate(new_len);
1038 }
1039
1040 if let Some(frame) = self.frame.as_mut() {
1042 frame.set_title(&title);
1043 }
1044
1045 self.window.set_title(&title);
1046 self.title = title;
1047 }
1048
1049 #[inline]
1051 pub fn set_transparent(&mut self, transparent: bool) {
1052 self.transparent = transparent;
1053 self.reload_transparency_hint();
1054 }
1055
1056 #[inline]
1058 pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) {
1059 if !self.text_inputs.iter().any(|t| t == text_input) {
1060 self.text_inputs.push(text_input.clone());
1061 }
1062 }
1063
1064 #[inline]
1066 pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) {
1067 if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) {
1068 self.text_inputs.remove(position);
1069 }
1070 }
1071
1072 #[inline]
1074 pub fn title(&self) -> &str {
1075 &self.title
1076 }
1077}
1078
1079impl Drop for WindowState {
1080 fn drop(&mut self) {
1081 if let Some(blur) = self.blur.take() {
1082 blur.release();
1083 }
1084
1085 if let Some(fs) = self.fractional_scale.take() {
1086 fs.destroy();
1087 }
1088
1089 if let Some(viewport) = self.viewport.take() {
1090 viewport.destroy();
1091 }
1092
1093 }
1096}
1097
1098#[derive(Clone, Copy)]
1100struct GrabState {
1101 user_grab_mode: CursorGrabMode,
1103
1104 current_grab_mode: CursorGrabMode,
1106}
1107
1108impl GrabState {
1109 fn new() -> Self {
1110 Self { user_grab_mode: CursorGrabMode::None, current_grab_mode: CursorGrabMode::None }
1111 }
1112}
1113
1114#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1116pub enum FrameCallbackState {
1117 #[default]
1119 None,
1120 Requested,
1122 Received,
1124}
1125
1126impl From<ResizeDirection> for XdgResizeEdge {
1127 fn from(value: ResizeDirection) -> Self {
1128 match value {
1129 ResizeDirection::North => XdgResizeEdge::Top,
1130 ResizeDirection::West => XdgResizeEdge::Left,
1131 ResizeDirection::NorthWest => XdgResizeEdge::TopLeft,
1132 ResizeDirection::NorthEast => XdgResizeEdge::TopRight,
1133 ResizeDirection::East => XdgResizeEdge::Right,
1134 ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft,
1135 ResizeDirection::SouthEast => XdgResizeEdge::BottomRight,
1136 ResizeDirection::South => XdgResizeEdge::Bottom,
1137 }
1138 }
1139}
1140
1141#[cfg(feature = "sctk-adwaita")]
1143fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
1144 match theme {
1145 Some(Theme::Light) => sctk_adwaita::FrameConfig::light(),
1146 Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(),
1147 None => sctk_adwaita::FrameConfig::auto(),
1148 }
1149}