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