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.iter().filter_map(Weak::upgrade).for_each(|pointer| {
230 let data = pointer.pointer().winit_data();
231 callback(pointer.as_ref(), data);
232 })
233 }
234
235 pub fn frame_callback_state(&self) -> FrameCallbackState {
237 self.frame_callback_state
238 }
239
240 pub fn frame_callback_received(&mut self) {
242 self.frame_callback_state = FrameCallbackState::Received;
243 }
244
245 pub fn frame_callback_reset(&mut self) {
247 self.frame_callback_state = FrameCallbackState::None;
248 }
249
250 pub fn request_frame_callback(&mut self) {
252 let surface = self.window.wl_surface();
253 match self.frame_callback_state {
254 FrameCallbackState::None | FrameCallbackState::Received => {
255 self.frame_callback_state = FrameCallbackState::Requested;
256 surface.frame(&self.queue_handle, surface.clone());
257 },
258 FrameCallbackState::Requested => (),
259 }
260 }
261
262 pub fn configure(
263 &mut self,
264 configure: WindowConfigure,
265 shm: &Shm,
266 subcompositor: &Option<Arc<SubcompositorState>>,
267 ) -> bool {
268 if let Some(initial_size) = self.initial_size.take() {
272 self.size = initial_size.to_logical(self.scale_factor());
273 self.stateless_size = self.size;
274 }
275
276 if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
277 configure.decoration_mode == DecorationMode::Client
278 && self.frame.is_none()
279 && !self.csd_fails
280 }) {
281 match WinitFrame::new(
282 &self.window,
283 shm,
284 #[cfg(feature = "sctk-adwaita")]
285 self.compositor.clone(),
286 subcompositor.clone(),
287 self.queue_handle.clone(),
288 #[cfg(feature = "sctk-adwaita")]
289 into_sctk_adwaita_config(self.theme),
290 ) {
291 Ok(mut frame) => {
292 frame.set_title(&self.title);
293 frame.set_scaling_factor(self.scale_factor);
294 frame.set_hidden(!self.decorate);
296 self.frame = Some(frame);
297 },
298 Err(err) => {
299 warn!("Failed to create client side decorations frame: {err}");
300 self.csd_fails = true;
301 },
302 }
303 } else if configure.decoration_mode == DecorationMode::Server {
304 self.frame = None;
306 }
307
308 let stateless = Self::is_stateless(&configure);
309
310 let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
311 frame.update_state(configure.state);
313
314 match configure.new_size {
315 (Some(width), Some(height)) => {
316 let (width, height) = frame.subtract_borders(width, height);
317 let width = width.map(|w| w.get()).unwrap_or(1);
318 let height = height.map(|h| h.get()).unwrap_or(1);
319 ((width, height).into(), false)
320 },
321 (..) if stateless => (self.stateless_size, true),
322 _ => (self.size, true),
323 }
324 } else {
325 match configure.new_size {
326 (Some(width), Some(height)) => ((width.get(), height.get()).into(), false),
327 _ if stateless => (self.stateless_size, true),
328 _ => (self.size, true),
329 }
330 };
331
332 if constrain {
334 let bounds = self.inner_size_bounds(&configure);
335 new_size.width =
336 bounds.0.map(|bound_w| new_size.width.min(bound_w.get())).unwrap_or(new_size.width);
337 new_size.height = bounds
338 .1
339 .map(|bound_h| new_size.height.min(bound_h.get()))
340 .unwrap_or(new_size.height);
341 }
342
343 let new_state = configure.state;
344 let old_state = self.last_configure.as_ref().map(|configure| configure.state);
345
346 let state_change_requires_resize = old_state
347 .map(|old_state| {
348 !old_state
349 .symmetric_difference(new_state)
350 .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
351 .is_empty()
352 })
353 .unwrap_or(true);
355
356 self.last_configure = Some(configure);
358
359 if state_change_requires_resize || new_size != self.inner_size() {
360 self.resize(new_size);
361 true
362 } else {
363 false
364 }
365 }
366
367 fn inner_size_bounds(
369 &self,
370 configure: &WindowConfigure,
371 ) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
372 let configure_bounds = match configure.suggested_bounds {
373 Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
374 None => (None, None),
375 };
376
377 if let Some(frame) = self.frame.as_ref() {
378 let (width, height) = frame.subtract_borders(
379 configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
380 configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
381 );
382 (configure_bounds.0.and(width), configure_bounds.1.and(height))
383 } else {
384 configure_bounds
385 }
386 }
387
388 #[inline]
389 fn is_stateless(configure: &WindowConfigure) -> bool {
390 !(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled())
391 }
392
393 pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
395 let xdg_toplevel = self.window.xdg_toplevel();
396
397 self.apply_on_pointer(|_, data| {
399 let serial = data.latest_button_serial();
400 let seat = data.seat();
401 xdg_toplevel.resize(seat, serial, direction.into());
402 });
403
404 Ok(())
405 }
406
407 pub fn drag_window(&self) -> Result<(), ExternalError> {
409 let xdg_toplevel = self.window.xdg_toplevel();
410 self.apply_on_pointer(|_, data| {
412 let serial = data.latest_button_serial();
413 let seat = data.seat();
414 xdg_toplevel._move(seat, serial);
415 });
416
417 Ok(())
418 }
419
420 #[allow(clippy::too_many_arguments)]
422 pub fn frame_click(
423 &mut self,
424 click: FrameClick,
425 pressed: bool,
426 seat: &WlSeat,
427 serial: u32,
428 timestamp: Duration,
429 window_id: WindowId,
430 updates: &mut Vec<WindowCompositorUpdate>,
431 ) -> Option<bool> {
432 match self.frame.as_mut()?.on_click(timestamp, click, pressed)? {
433 FrameAction::Minimize => self.window.set_minimized(),
434 FrameAction::Maximize => self.window.set_maximized(),
435 FrameAction::UnMaximize => self.window.unset_maximized(),
436 FrameAction::Close => WinitState::queue_close(updates, window_id),
437 FrameAction::Move => self.has_pending_move = Some(serial),
438 FrameAction::Resize(edge) => {
439 let edge = match edge {
440 ResizeEdge::None => XdgResizeEdge::None,
441 ResizeEdge::Top => XdgResizeEdge::Top,
442 ResizeEdge::Bottom => XdgResizeEdge::Bottom,
443 ResizeEdge::Left => XdgResizeEdge::Left,
444 ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
445 ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
446 ResizeEdge::Right => XdgResizeEdge::Right,
447 ResizeEdge::TopRight => XdgResizeEdge::TopRight,
448 ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
449 _ => return None,
450 };
451 self.window.resize(seat, serial, edge);
452 },
453 FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
454 _ => (),
455 };
456
457 Some(false)
458 }
459
460 pub fn frame_point_left(&mut self) {
461 if let Some(frame) = self.frame.as_mut() {
462 frame.click_point_left();
463 }
464 }
465
466 pub fn frame_point_moved(
468 &mut self,
469 seat: &WlSeat,
470 surface: &WlSurface,
471 timestamp: Duration,
472 x: f64,
473 y: f64,
474 ) -> Option<CursorIcon> {
475 let serial = self.has_pending_move.take();
477
478 if let Some(frame) = self.frame.as_mut() {
479 let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y);
480 if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
483 self.window.move_(seat, serial);
484 None
485 } else {
486 cursor
487 }
488 } else {
489 None
490 }
491 }
492
493 #[inline]
495 pub fn resizable(&self) -> bool {
496 self.resizable
497 }
498
499 #[inline]
503 pub fn set_resizable(&mut self, resizable: bool) -> bool {
504 if self.resizable == resizable {
505 return false;
506 }
507
508 self.resizable = resizable;
509 if resizable {
510 self.reload_min_max_hints();
512 } else {
513 self.set_min_inner_size(Some(self.size));
514 self.set_max_inner_size(Some(self.size));
515 }
516
517 if let Some(frame) = self.frame.as_mut() {
519 frame.set_resizable(resizable);
520 }
521
522 true
523 }
524
525 #[inline]
527 pub fn has_focus(&self) -> bool {
528 !self.seat_focus.is_empty()
529 }
530
531 #[inline]
533 pub fn ime_allowed(&self) -> bool {
534 self.ime_allowed
535 }
536
537 #[inline]
539 pub fn inner_size(&self) -> LogicalSize<u32> {
540 self.size
541 }
542
543 #[inline]
545 pub fn is_configured(&self) -> bool {
546 self.last_configure.is_some()
547 }
548
549 #[inline]
550 pub fn is_decorated(&mut self) -> bool {
551 let csd = self
552 .last_configure
553 .as_ref()
554 .map(|configure| configure.decoration_mode == DecorationMode::Client)
555 .unwrap_or(false);
556 if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
557 !frame.is_hidden()
558 } else {
559 true
561 }
562 }
563
564 #[inline]
566 pub fn outer_size(&self) -> LogicalSize<u32> {
567 self.frame
568 .as_ref()
569 .map(|frame| frame.add_borders(self.size.width, self.size.height).into())
570 .unwrap_or(self.size)
571 }
572
573 pub fn pointer_entered(&mut self, added: Weak<ThemedPointer<WinitPointerData>>) {
575 self.pointers.push(added);
576 self.reload_cursor_style();
577
578 let mode = self.cursor_grab_mode.user_grab_mode;
579 let _ = self.set_cursor_grab_inner(mode);
580 }
581
582 pub fn pointer_left(&mut self, removed: Weak<ThemedPointer<WinitPointerData>>) {
584 let mut new_pointers = Vec::new();
585 for pointer in self.pointers.drain(..) {
586 if let Some(pointer) = pointer.upgrade() {
587 if pointer.pointer() != removed.upgrade().unwrap().pointer() {
588 new_pointers.push(Arc::downgrade(&pointer));
589 }
590 }
591 }
592
593 self.pointers = new_pointers;
594 }
595
596 pub fn refresh_frame(&mut self) -> bool {
598 if let Some(frame) = self.frame.as_mut() {
599 if !frame.is_hidden() && frame.is_dirty() {
600 return frame.draw();
601 }
602 }
603
604 false
605 }
606
607 pub fn reload_cursor_style(&mut self) {
609 if self.cursor_visible {
610 match &self.selected_cursor {
611 SelectedCursor::Named(icon) => self.set_cursor(*icon),
612 SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
613 }
614 } else {
615 self.set_cursor_visible(self.cursor_visible);
616 }
617 }
618
619 pub fn reload_transparency_hint(&self) {
621 let surface = self.window.wl_surface();
622
623 if self.transparent {
624 surface.set_opaque_region(None);
625 } else if let Ok(region) = Region::new(&*self.compositor) {
626 region.add(0, 0, i32::MAX, i32::MAX);
627 surface.set_opaque_region(Some(region.wl_region()));
628 } else {
629 warn!("Failed to mark window opaque.");
630 }
631 }
632
633 pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize<u32> {
635 if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) {
636 self.resize(inner_size.to_logical(self.scale_factor()))
637 }
638
639 logical_to_physical_rounded(self.inner_size(), self.scale_factor())
640 }
641
642 fn resize(&mut self, inner_size: LogicalSize<u32>) {
644 self.size = inner_size;
645
646 if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
648 self.stateless_size = inner_size;
649 }
650
651 let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
653 if !frame.is_hidden() {
655 frame.resize(
656 NonZeroU32::new(self.size.width).unwrap(),
657 NonZeroU32::new(self.size.height).unwrap(),
658 );
659 }
660
661 (frame.location(), frame.add_borders(self.size.width, self.size.height).into())
662 } else {
663 ((0, 0), self.size)
664 };
665
666 self.reload_transparency_hint();
668
669 self.window.xdg_surface().set_window_geometry(
671 x,
672 y,
673 outer_size.width as i32,
674 outer_size.height as i32,
675 );
676
677 if let Some(viewport) = self.viewport.as_ref() {
679 viewport.set_destination(self.size.width as _, self.size.height as _);
681 }
682 }
683
684 #[inline]
686 pub fn scale_factor(&self) -> f64 {
687 self.scale_factor
688 }
689
690 pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
692 self.selected_cursor = SelectedCursor::Named(cursor_icon);
693
694 if !self.cursor_visible {
695 return;
696 }
697
698 self.apply_on_pointer(|pointer, _| {
699 if pointer.set_cursor(&self.connection, cursor_icon).is_err() {
700 warn!("Failed to set cursor to {:?}", cursor_icon);
701 }
702 })
703 }
704
705 pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
707 let cursor = match cursor {
708 RootCustomCursor { inner: PlatformCustomCursor::Wayland(cursor) } => cursor.0,
709 #[cfg(x11_platform)]
710 RootCustomCursor { inner: PlatformCustomCursor::X(_) } => {
711 tracing::error!("passed a X11 cursor to Wayland backend");
712 return;
713 },
714 };
715
716 let cursor = {
717 let mut pool = self.custom_cursor_pool.lock().unwrap();
718 CustomCursor::new(&mut pool, &cursor)
719 };
720
721 if self.cursor_visible {
722 self.apply_custom_cursor(&cursor);
723 }
724
725 self.selected_cursor = SelectedCursor::Custom(cursor);
726 }
727
728 fn apply_custom_cursor(&self, cursor: &CustomCursor) {
729 self.apply_on_pointer(|pointer, _| {
730 let surface = pointer.surface();
731
732 let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
733
734 surface.set_buffer_scale(scale);
735 surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
736 if surface.version() >= 4 {
737 surface.damage_buffer(0, 0, cursor.w, cursor.h);
738 } else {
739 surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
740 }
741 surface.commit();
742
743 let serial = pointer
744 .pointer()
745 .data::<WinitPointerData>()
746 .and_then(|data| data.pointer_data().latest_enter_serial())
747 .unwrap();
748
749 pointer.pointer().set_cursor(
750 serial,
751 Some(surface),
752 cursor.hotspot_x / scale,
753 cursor.hotspot_y / scale,
754 );
755 });
756 }
757
758 pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
760 let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
762 size.width = size.width.max(MIN_WINDOW_SIZE.width);
763 size.height = size.height.max(MIN_WINDOW_SIZE.height);
764
765 let size = self
767 .frame
768 .as_ref()
769 .map(|frame| frame.add_borders(size.width, size.height).into())
770 .unwrap_or(size);
771
772 self.min_inner_size = size;
773 self.window.set_min_size(Some(size.into()));
774 }
775
776 pub fn set_max_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
778 let size = size.map(|size| {
779 self.frame
780 .as_ref()
781 .map(|frame| frame.add_borders(size.width, size.height).into())
782 .unwrap_or(size)
783 });
784
785 self.max_inner_size = size;
786 self.window.set_max_size(size.map(Into::into));
787 }
788
789 pub fn set_theme(&mut self, theme: Option<Theme>) {
791 self.theme = theme;
792 #[cfg(feature = "sctk-adwaita")]
793 if let Some(frame) = self.frame.as_mut() {
794 frame.set_config(into_sctk_adwaita_config(theme))
795 }
796 }
797
798 #[inline]
800 pub fn theme(&self) -> Option<Theme> {
801 self.theme
802 }
803
804 pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
806 if self.cursor_grab_mode.user_grab_mode == mode {
807 return Ok(());
808 }
809
810 self.set_cursor_grab_inner(mode)?;
811 self.cursor_grab_mode.user_grab_mode = mode;
813 Ok(())
814 }
815
816 pub fn reload_min_max_hints(&mut self) {
818 self.set_min_inner_size(Some(self.min_inner_size));
819 self.set_max_inner_size(self.max_inner_size);
820 }
821
822 fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
824 let pointer_constraints = match self.pointer_constraints.as_ref() {
825 Some(pointer_constraints) => pointer_constraints,
826 None if mode == CursorGrabMode::None => return Ok(()),
827 None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
828 };
829
830 let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
832
833 match old_mode {
834 CursorGrabMode::None => (),
835 CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
836 data.unconfine_pointer();
837 }),
838 CursorGrabMode::Locked => {
839 self.apply_on_pointer(|_, data| data.unlock_pointer());
840 },
841 }
842
843 let surface = self.window.wl_surface();
844 match mode {
845 CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
846 let pointer = pointer.pointer();
847 data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
848 }),
849 CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
850 let pointer = pointer.pointer();
851 data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
852 }),
853 CursorGrabMode::None => {
854 },
856 }
857
858 Ok(())
859 }
860
861 pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
862 self.apply_on_pointer(|_, data| {
864 let serial = data.latest_button_serial();
865 let seat = data.seat();
866 self.window.show_window_menu(seat, serial, position.into());
867 });
868 }
869
870 pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), ExternalError> {
872 if self.pointer_constraints.is_none() {
873 return Err(ExternalError::NotSupported(NotSupportedError::new()));
874 }
875
876 if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
878 return Err(ExternalError::Os(os_error!(crate::platform_impl::OsError::Misc(
879 "cursor position can be set only for locked cursor."
880 ))));
881 }
882
883 self.apply_on_pointer(|_, data| {
884 data.set_locked_cursor_position(position.x, position.y);
885 });
886
887 Ok(())
888 }
889
890 pub fn set_cursor_visible(&mut self, cursor_visible: bool) {
892 self.cursor_visible = cursor_visible;
893
894 if self.cursor_visible {
895 match &self.selected_cursor {
896 SelectedCursor::Named(icon) => self.set_cursor(*icon),
897 SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
898 }
899 } else {
900 for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
901 let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();
902
903 pointer.pointer().set_cursor(latest_enter_serial, None, 0, 0);
904 }
905 }
906 }
907
908 #[inline]
910 pub fn set_decorate(&mut self, decorate: bool) {
911 if decorate == self.decorate {
912 return;
913 }
914
915 self.decorate = decorate;
916
917 match self.last_configure.as_ref().map(|configure| configure.decoration_mode) {
918 Some(DecorationMode::Server) if !self.decorate => {
919 self.window.request_decoration_mode(Some(DecorationMode::Client))
921 },
922 _ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)),
923 _ => (),
924 }
925
926 if let Some(frame) = self.frame.as_mut() {
927 frame.set_hidden(!decorate);
928 self.resize(self.size);
930 }
931 }
932
933 #[inline]
935 pub fn add_seat_focus(&mut self, seat: ObjectId) {
936 self.seat_focus.insert(seat);
937 }
938
939 #[inline]
941 pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
942 self.seat_focus.remove(seat);
943 }
944
945 pub fn set_ime_allowed(&mut self, allowed: bool) -> bool {
947 self.ime_allowed = allowed;
948
949 let mut applied = false;
950 for text_input in &self.text_inputs {
951 applied = true;
952 if allowed {
953 text_input.enable();
954 text_input.set_content_type_by_purpose(self.ime_purpose);
955 } else {
956 text_input.disable();
957 }
958 text_input.commit();
959 }
960
961 applied
962 }
963
964 pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
966 let (x, y) = (position.x as i32, position.y as i32);
970 let (width, height) = (size.width as i32, size.height as i32);
971 for text_input in self.text_inputs.iter() {
972 text_input.set_cursor_rectangle(x, y, width, height);
973 text_input.commit();
974 }
975 }
976
977 pub fn set_ime_purpose(&mut self, purpose: ImePurpose) {
979 self.ime_purpose = purpose;
980
981 for text_input in &self.text_inputs {
982 text_input.set_content_type_by_purpose(purpose);
983 text_input.commit();
984 }
985 }
986
987 pub fn ime_purpose(&self) -> ImePurpose {
989 self.ime_purpose
990 }
991
992 #[inline]
994 pub fn set_scale_factor(&mut self, scale_factor: f64) {
995 self.scale_factor = scale_factor;
996
997 if self.fractional_scale.is_none() {
999 let _ = self.window.set_buffer_scale(self.scale_factor as _);
1000 }
1001
1002 if let Some(frame) = self.frame.as_mut() {
1003 frame.set_scaling_factor(scale_factor);
1004 }
1005 }
1006
1007 #[inline]
1009 pub fn set_blur(&mut self, blurred: bool) {
1010 if blurred && self.blur.is_none() {
1011 if let Some(blur_manager) = self.blur_manager.as_ref() {
1012 let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
1013 blur.commit();
1014 self.blur = Some(blur);
1015 } else {
1016 info!("Blur manager unavailable, unable to change blur")
1017 }
1018 } else if !blurred && self.blur.is_some() {
1019 self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface());
1020 self.blur.take().unwrap().release();
1021 }
1022 }
1023
1024 pub fn set_title(&mut self, mut title: String) {
1028 if title.len() > 1024 {
1031 let mut new_len = 1024;
1032 while !title.is_char_boundary(new_len) {
1033 new_len -= 1;
1034 }
1035 title.truncate(new_len);
1036 }
1037
1038 if let Some(frame) = self.frame.as_mut() {
1040 frame.set_title(&title);
1041 }
1042
1043 self.window.set_title(&title);
1044 self.title = title;
1045 }
1046
1047 #[inline]
1049 pub fn set_transparent(&mut self, transparent: bool) {
1050 self.transparent = transparent;
1051 self.reload_transparency_hint();
1052 }
1053
1054 #[inline]
1056 pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) {
1057 if !self.text_inputs.iter().any(|t| t == text_input) {
1058 self.text_inputs.push(text_input.clone());
1059 }
1060 }
1061
1062 #[inline]
1064 pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) {
1065 if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) {
1066 self.text_inputs.remove(position);
1067 }
1068 }
1069
1070 #[inline]
1072 pub fn title(&self) -> &str {
1073 &self.title
1074 }
1075}
1076
1077impl Drop for WindowState {
1078 fn drop(&mut self) {
1079 if let Some(blur) = self.blur.take() {
1080 blur.release();
1081 }
1082
1083 if let Some(fs) = self.fractional_scale.take() {
1084 fs.destroy();
1085 }
1086
1087 if let Some(viewport) = self.viewport.take() {
1088 viewport.destroy();
1089 }
1090
1091 }
1094}
1095
1096#[derive(Clone, Copy)]
1098struct GrabState {
1099 user_grab_mode: CursorGrabMode,
1101
1102 current_grab_mode: CursorGrabMode,
1104}
1105
1106impl GrabState {
1107 fn new() -> Self {
1108 Self { user_grab_mode: CursorGrabMode::None, current_grab_mode: CursorGrabMode::None }
1109 }
1110}
1111
1112#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1114pub enum FrameCallbackState {
1115 #[default]
1117 None,
1118 Requested,
1120 Received,
1122}
1123
1124impl From<ResizeDirection> for XdgResizeEdge {
1125 fn from(value: ResizeDirection) -> Self {
1126 match value {
1127 ResizeDirection::North => XdgResizeEdge::Top,
1128 ResizeDirection::West => XdgResizeEdge::Left,
1129 ResizeDirection::NorthWest => XdgResizeEdge::TopLeft,
1130 ResizeDirection::NorthEast => XdgResizeEdge::TopRight,
1131 ResizeDirection::East => XdgResizeEdge::Right,
1132 ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft,
1133 ResizeDirection::SouthEast => XdgResizeEdge::BottomRight,
1134 ResizeDirection::South => XdgResizeEdge::Bottom,
1135 }
1136 }
1137}
1138
1139#[cfg(feature = "sctk-adwaita")]
1141fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
1142 match theme {
1143 Some(Theme::Light) => sctk_adwaita::FrameConfig::light(),
1144 Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(),
1145 None => sctk_adwaita::FrameConfig::auto(),
1146 }
1147}