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, PhysicalPosition, 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 resize_increments: Option<LogicalSize<u32>>,
131
132 stateless_size: LogicalSize<u32>,
136
137 initial_size: Option<Size>,
140
141 frame_callback_state: FrameCallbackState,
143
144 viewport: Option<WpViewport>,
145 fractional_scale: Option<WpFractionalScaleV1>,
146 blur: Option<OrgKdeKwinBlur>,
147 blur_manager: Option<KWinBlurManager>,
148
149 has_pending_move: Option<u32>,
153
154 pub window: Window,
156
157 frame: Option<WinitFrame>,
163}
164
165impl WindowState {
166 pub fn new(
168 connection: Connection,
169 queue_handle: &QueueHandle<WinitState>,
170 winit_state: &WinitState,
171 initial_size: Size,
172 window: Window,
173 theme: Option<Theme>,
174 ) -> Self {
175 let compositor = winit_state.compositor_state.clone();
176 let pointer_constraints = winit_state.pointer_constraints.clone();
177 let viewport = winit_state
178 .viewporter_state
179 .as_ref()
180 .map(|state| state.get_viewport(window.wl_surface(), queue_handle));
181 let fractional_scale = winit_state
182 .fractional_scaling_manager
183 .as_ref()
184 .map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
185
186 Self {
187 blur: None,
188 blur_manager: winit_state.kwin_blur_manager.clone(),
189 compositor,
190 connection,
191 csd_fails: false,
192 cursor_grab_mode: GrabState::new(),
193 selected_cursor: Default::default(),
194 cursor_visible: true,
195 decorate: true,
196 fractional_scale,
197 frame: None,
198 frame_callback_state: FrameCallbackState::None,
199 seat_focus: Default::default(),
200 has_pending_move: None,
201 ime_allowed: false,
202 ime_purpose: ImePurpose::Normal,
203 last_configure: None,
204 max_inner_size: None,
205 min_inner_size: MIN_WINDOW_SIZE,
206 resize_increments: None,
207 pointer_constraints,
208 pointers: Default::default(),
209 queue_handle: queue_handle.clone(),
210 resizable: true,
211 scale_factor: 1.,
212 shm: winit_state.shm.wl_shm().clone(),
213 custom_cursor_pool: winit_state.custom_cursor_pool.clone(),
214 size: initial_size.to_logical(1.),
215 stateless_size: initial_size.to_logical(1.),
216 initial_size: Some(initial_size),
217 text_inputs: Vec::new(),
218 theme,
219 title: String::default(),
220 transparent: false,
221 viewport,
222 window,
223 }
224 }
225
226 fn apply_on_pointer<F: FnMut(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
228 &self,
229 mut callback: F,
230 ) {
231 self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| {
232 let data = pointer.pointer().winit_data();
233 callback(pointer.as_ref(), data);
234 })
235 }
236
237 pub fn frame_callback_state(&self) -> FrameCallbackState {
239 self.frame_callback_state
240 }
241
242 pub fn frame_callback_received(&mut self) {
244 self.frame_callback_state = FrameCallbackState::Received;
245 }
246
247 pub fn frame_callback_reset(&mut self) {
249 self.frame_callback_state = FrameCallbackState::None;
250 }
251
252 pub fn request_frame_callback(&mut self) {
254 let surface = self.window.wl_surface();
255 match self.frame_callback_state {
256 FrameCallbackState::None | FrameCallbackState::Received => {
257 self.frame_callback_state = FrameCallbackState::Requested;
258 surface.frame(&self.queue_handle, surface.clone());
259 },
260 FrameCallbackState::Requested => (),
261 }
262 }
263
264 pub fn configure(
265 &mut self,
266 configure: WindowConfigure,
267 shm: &Shm,
268 subcompositor: &Option<Arc<SubcompositorState>>,
269 ) -> bool {
270 if let Some(initial_size) = self.initial_size.take() {
274 self.size = initial_size.to_logical(self.scale_factor());
275 self.stateless_size = self.size;
276 }
277
278 if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
279 configure.decoration_mode == DecorationMode::Client
280 && self.frame.is_none()
281 && !self.csd_fails
282 }) {
283 match WinitFrame::new(
284 &self.window,
285 shm,
286 #[cfg(feature = "sctk-adwaita")]
287 self.compositor.clone(),
288 subcompositor.clone(),
289 self.queue_handle.clone(),
290 #[cfg(feature = "sctk-adwaita")]
291 into_sctk_adwaita_config(self.theme),
292 ) {
293 Ok(mut frame) => {
294 frame.set_title(&self.title);
295 frame.set_scaling_factor(self.scale_factor);
296 frame.set_hidden(!self.decorate);
298 self.frame = Some(frame);
299 },
300 Err(err) => {
301 warn!("Failed to create client side decorations frame: {err}");
302 self.csd_fails = true;
303 },
304 }
305 } else if configure.decoration_mode == DecorationMode::Server {
306 self.frame = None;
308 }
309
310 let stateless = Self::is_stateless(&configure);
311
312 let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
313 frame.update_state(configure.state);
315
316 match configure.new_size {
317 (Some(width), Some(height)) => {
318 let (width, height) = frame.subtract_borders(width, height);
319 let width = width.map(|w| w.get()).unwrap_or(1);
320 let height = height.map(|h| h.get()).unwrap_or(1);
321 ((width, height).into(), false)
322 },
323 (..) if stateless => (self.stateless_size, true),
324 _ => (self.size, true),
325 }
326 } else {
327 match configure.new_size {
328 (Some(width), Some(height)) => ((width.get(), height.get()).into(), false),
329 _ if stateless => (self.stateless_size, true),
330 _ => (self.size, true),
331 }
332 };
333
334 if constrain {
336 let bounds = self.inner_size_bounds(&configure);
337 new_size.width =
338 bounds.0.map(|bound_w| new_size.width.min(bound_w.get())).unwrap_or(new_size.width);
339 new_size.height = bounds
340 .1
341 .map(|bound_h| new_size.height.min(bound_h.get()))
342 .unwrap_or(new_size.height);
343 }
344
345 if (constrain || configure.is_resizing())
358 && !configure.is_maximized()
359 && !configure.is_fullscreen()
360 && !configure.is_tiled()
361 {
362 if let Some(increments) = self.resize_increments {
363 let (delta_width, delta_height) = (
368 new_size.width.saturating_sub(self.min_inner_size.width),
369 new_size.height.saturating_sub(self.min_inner_size.height),
370 );
371
372 let width =
373 self.min_inner_size.width + (delta_width / increments.width) * increments.width;
374 let height = self.min_inner_size.height
375 + (delta_height / increments.height) * increments.height;
376
377 new_size = (width, height).into();
378 }
379 }
380
381 let new_state = configure.state;
382 let old_state = self.last_configure.as_ref().map(|configure| configure.state);
383
384 let state_change_requires_resize = old_state
385 .map(|old_state| {
386 !old_state
387 .symmetric_difference(new_state)
388 .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
389 .is_empty()
390 })
391 .unwrap_or(true);
393
394 self.last_configure = Some(configure);
396
397 if state_change_requires_resize || new_size != self.inner_size() {
398 self.resize(new_size);
399 true
400 } else {
401 false
402 }
403 }
404
405 fn inner_size_bounds(
407 &self,
408 configure: &WindowConfigure,
409 ) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
410 let configure_bounds = match configure.suggested_bounds {
411 Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
412 None => (None, None),
413 };
414
415 if let Some(frame) = self.frame.as_ref() {
416 let (width, height) = frame.subtract_borders(
417 configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
418 configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
419 );
420 (configure_bounds.0.and(width), configure_bounds.1.and(height))
421 } else {
422 configure_bounds
423 }
424 }
425
426 #[inline]
427 fn is_stateless(configure: &WindowConfigure) -> bool {
428 !(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled())
429 }
430
431 pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
433 let xdg_toplevel = self.window.xdg_toplevel();
434
435 self.apply_on_pointer(|_, data| {
437 let serial = data.latest_button_serial();
438 let seat = data.seat();
439 xdg_toplevel.resize(seat, serial, direction.into());
440 });
441
442 Ok(())
443 }
444
445 pub fn drag_window(&self) -> Result<(), ExternalError> {
447 let xdg_toplevel = self.window.xdg_toplevel();
448 self.apply_on_pointer(|_, data| {
450 let serial = data.latest_button_serial();
451 let seat = data.seat();
452 xdg_toplevel._move(seat, serial);
453 });
454
455 Ok(())
456 }
457
458 #[allow(clippy::too_many_arguments)]
460 pub fn frame_click(
461 &mut self,
462 click: FrameClick,
463 pressed: bool,
464 seat: &WlSeat,
465 serial: u32,
466 timestamp: Duration,
467 window_id: WindowId,
468 updates: &mut Vec<WindowCompositorUpdate>,
469 ) -> Option<bool> {
470 match self.frame.as_mut()?.on_click(timestamp, click, pressed)? {
471 FrameAction::Minimize => self.window.set_minimized(),
472 FrameAction::Maximize => self.window.set_maximized(),
473 FrameAction::UnMaximize => self.window.unset_maximized(),
474 FrameAction::Close => WinitState::queue_close(updates, window_id),
475 FrameAction::Move => self.has_pending_move = Some(serial),
476 FrameAction::Resize(edge) => {
477 let edge = match edge {
478 ResizeEdge::None => XdgResizeEdge::None,
479 ResizeEdge::Top => XdgResizeEdge::Top,
480 ResizeEdge::Bottom => XdgResizeEdge::Bottom,
481 ResizeEdge::Left => XdgResizeEdge::Left,
482 ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
483 ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
484 ResizeEdge::Right => XdgResizeEdge::Right,
485 ResizeEdge::TopRight => XdgResizeEdge::TopRight,
486 ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
487 _ => return None,
488 };
489 self.window.resize(seat, serial, edge);
490 },
491 FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
492 _ => (),
493 };
494
495 Some(false)
496 }
497
498 pub fn frame_point_left(&mut self) {
499 if let Some(frame) = self.frame.as_mut() {
500 frame.click_point_left();
501 }
502 }
503
504 pub fn frame_point_moved(
506 &mut self,
507 seat: &WlSeat,
508 surface: &WlSurface,
509 timestamp: Duration,
510 x: f64,
511 y: f64,
512 ) -> Option<CursorIcon> {
513 let serial = self.has_pending_move.take();
515
516 if let Some(frame) = self.frame.as_mut() {
517 let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y);
518 if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
521 self.window.move_(seat, serial);
522 None
523 } else {
524 cursor
525 }
526 } else {
527 None
528 }
529 }
530
531 #[inline]
533 pub fn resizable(&self) -> bool {
534 self.resizable
535 }
536
537 #[inline]
541 pub fn set_resizable(&mut self, resizable: bool) -> bool {
542 if self.resizable == resizable {
543 return false;
544 }
545
546 self.resizable = resizable;
547 if resizable {
548 self.reload_min_max_hints();
550 } else {
551 self.set_min_inner_size(Some(self.size));
552 self.set_max_inner_size(Some(self.size));
553 }
554
555 if let Some(frame) = self.frame.as_mut() {
557 frame.set_resizable(resizable);
558 }
559
560 true
561 }
562
563 #[inline]
565 pub fn has_focus(&self) -> bool {
566 !self.seat_focus.is_empty()
567 }
568
569 #[inline]
571 pub fn ime_allowed(&self) -> bool {
572 self.ime_allowed
573 }
574
575 #[inline]
577 pub fn inner_size(&self) -> LogicalSize<u32> {
578 self.size
579 }
580
581 #[inline]
583 pub fn is_configured(&self) -> bool {
584 self.last_configure.is_some()
585 }
586
587 #[inline]
588 pub fn is_decorated(&mut self) -> bool {
589 let csd = self
590 .last_configure
591 .as_ref()
592 .map(|configure| configure.decoration_mode == DecorationMode::Client)
593 .unwrap_or(false);
594 if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
595 !frame.is_hidden()
596 } else {
597 true
599 }
600 }
601
602 #[inline]
604 pub fn outer_size(&self) -> LogicalSize<u32> {
605 self.frame
606 .as_ref()
607 .map(|frame| frame.add_borders(self.size.width, self.size.height).into())
608 .unwrap_or(self.size)
609 }
610
611 pub fn pointer_entered(&mut self, added: Weak<ThemedPointer<WinitPointerData>>) {
613 self.pointers.push(added);
614 self.reload_cursor_style();
615
616 let mode = self.cursor_grab_mode.user_grab_mode;
617 let _ = self.set_cursor_grab_inner(mode);
618 }
619
620 pub fn pointer_left(&mut self, removed: Weak<ThemedPointer<WinitPointerData>>) {
622 let mut new_pointers = Vec::new();
623 for pointer in self.pointers.drain(..) {
624 if let Some(pointer) = pointer.upgrade() {
625 if pointer.pointer() != removed.upgrade().unwrap().pointer() {
626 new_pointers.push(Arc::downgrade(&pointer));
627 }
628 }
629 }
630
631 self.pointers = new_pointers;
632 }
633
634 pub fn refresh_frame(&mut self) -> bool {
636 if let Some(frame) = self.frame.as_mut() {
637 if !frame.is_hidden() && frame.is_dirty() {
638 return frame.draw();
639 }
640 }
641
642 false
643 }
644
645 pub fn reload_cursor_style(&mut self) {
647 if self.cursor_visible {
648 match &self.selected_cursor {
649 SelectedCursor::Named(icon) => self.set_cursor(*icon),
650 SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
651 }
652 } else {
653 self.set_cursor_visible(self.cursor_visible);
654 }
655 }
656
657 pub fn reload_transparency_hint(&self) {
659 let surface = self.window.wl_surface();
660
661 if self.transparent {
662 surface.set_opaque_region(None);
663 } else if let Ok(region) = Region::new(&*self.compositor) {
664 region.add(0, 0, i32::MAX, i32::MAX);
665 surface.set_opaque_region(Some(region.wl_region()));
666 } else {
667 warn!("Failed to mark window opaque.");
668 }
669 }
670
671 pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize<u32> {
673 if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) {
674 self.resize(inner_size.to_logical(self.scale_factor()))
675 }
676
677 logical_to_physical_rounded(self.inner_size(), self.scale_factor())
678 }
679
680 fn resize(&mut self, inner_size: LogicalSize<u32>) {
682 self.size = inner_size;
683
684 if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
686 self.stateless_size = inner_size;
687 }
688
689 let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
691 if !frame.is_hidden() {
693 frame.resize(
694 NonZeroU32::new(self.size.width).unwrap(),
695 NonZeroU32::new(self.size.height).unwrap(),
696 );
697 }
698
699 (frame.location(), frame.add_borders(self.size.width, self.size.height).into())
700 } else {
701 ((0, 0), self.size)
702 };
703
704 self.reload_transparency_hint();
706
707 self.window.xdg_surface().set_window_geometry(
709 x,
710 y,
711 outer_size.width as i32,
712 outer_size.height as i32,
713 );
714
715 if let Some(viewport) = self.viewport.as_ref() {
717 viewport.set_destination(self.size.width as _, self.size.height as _);
719 }
720 }
721
722 #[inline]
724 pub fn scale_factor(&self) -> f64 {
725 self.scale_factor
726 }
727
728 pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
730 self.selected_cursor = SelectedCursor::Named(cursor_icon);
731
732 if !self.cursor_visible {
733 return;
734 }
735
736 self.apply_on_pointer(|pointer, _| {
737 if pointer.set_cursor(&self.connection, cursor_icon).is_err() {
738 warn!("Failed to set cursor to {:?}", cursor_icon);
739 }
740 })
741 }
742
743 pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
745 let cursor = match cursor {
746 RootCustomCursor { inner: PlatformCustomCursor::Wayland(cursor) } => cursor.0,
747 #[cfg(x11_platform)]
748 RootCustomCursor { inner: PlatformCustomCursor::X(_) } => {
749 tracing::error!("passed a X11 cursor to Wayland backend");
750 return;
751 },
752 };
753
754 let cursor = {
755 let mut pool = self.custom_cursor_pool.lock().unwrap();
756 CustomCursor::new(&mut pool, &cursor)
757 };
758
759 if self.cursor_visible {
760 self.apply_custom_cursor(&cursor);
761 }
762
763 self.selected_cursor = SelectedCursor::Custom(cursor);
764 }
765
766 pub fn set_resize_increments(&mut self, increments: Option<LogicalSize<u32>>) {
768 self.resize_increments = increments;
769 }
772
773 pub fn resize_increments(&self) -> Option<LogicalSize<u32>> {
775 self.resize_increments
776 }
777
778 fn apply_custom_cursor(&self, cursor: &CustomCursor) {
779 self.apply_on_pointer(|pointer, data| {
780 let surface = pointer.surface();
781
782 let scale = if let Some(viewport) = data.viewport() {
783 let scale = self.scale_factor();
784 let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
785 viewport.set_destination(size.width, size.height);
786 scale
787 } else {
788 let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
789 surface.set_buffer_scale(scale);
790 scale as f64
791 };
792
793 surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
794 if surface.version() >= 4 {
795 surface.damage_buffer(0, 0, cursor.w, cursor.h);
796 } else {
797 let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
798 surface.damage(0, 0, size.width, size.height);
799 }
800 surface.commit();
801
802 let serial = pointer
803 .pointer()
804 .data::<WinitPointerData>()
805 .and_then(|data| data.pointer_data().latest_enter_serial())
806 .unwrap();
807
808 let hotspot =
809 PhysicalPosition::new(cursor.hotspot_x, cursor.hotspot_y).to_logical(scale);
810 pointer.pointer().set_cursor(serial, Some(surface), hotspot.x, hotspot.y);
811 });
812 }
813
814 pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
816 let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
818 size.width = size.width.max(MIN_WINDOW_SIZE.width);
819 size.height = size.height.max(MIN_WINDOW_SIZE.height);
820
821 let size = self
823 .frame
824 .as_ref()
825 .map(|frame| frame.add_borders(size.width, size.height).into())
826 .unwrap_or(size);
827
828 self.min_inner_size = size;
829 self.window.set_min_size(Some(size.into()));
830 }
831
832 pub fn set_max_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
834 let size = size.map(|size| {
835 self.frame
836 .as_ref()
837 .map(|frame| frame.add_borders(size.width, size.height).into())
838 .unwrap_or(size)
839 });
840
841 self.max_inner_size = size;
842 self.window.set_max_size(size.map(Into::into));
843 }
844
845 pub fn set_theme(&mut self, theme: Option<Theme>) {
847 self.theme = theme;
848 #[cfg(feature = "sctk-adwaita")]
849 if let Some(frame) = self.frame.as_mut() {
850 frame.set_config(into_sctk_adwaita_config(theme))
851 }
852 }
853
854 #[inline]
856 pub fn theme(&self) -> Option<Theme> {
857 self.theme
858 }
859
860 pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
862 if self.cursor_grab_mode.user_grab_mode == mode {
863 return Ok(());
864 }
865
866 self.set_cursor_grab_inner(mode)?;
867 self.cursor_grab_mode.user_grab_mode = mode;
869 Ok(())
870 }
871
872 pub fn reload_min_max_hints(&mut self) {
874 self.set_min_inner_size(Some(self.min_inner_size));
875 self.set_max_inner_size(self.max_inner_size);
876 }
877
878 fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
880 let pointer_constraints = match self.pointer_constraints.as_ref() {
881 Some(pointer_constraints) => pointer_constraints,
882 None if mode == CursorGrabMode::None => return Ok(()),
883 None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
884 };
885
886 let mut unset_old = false;
887 match self.cursor_grab_mode.current_grab_mode {
888 CursorGrabMode::None => unset_old = true,
889 CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
890 data.unconfine_pointer();
891 unset_old = true;
892 }),
893 CursorGrabMode::Locked => {
894 self.apply_on_pointer(|_, data| {
895 data.unlock_pointer();
896 unset_old = true;
897 });
898 },
899 }
900
901 if !unset_old {
904 return Ok(());
905 }
906
907 let mut set_mode = false;
908 let surface = self.window.wl_surface();
909 match mode {
910 CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
911 let pointer = pointer.pointer();
912 data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
913 set_mode = true;
914 }),
915 CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
916 let pointer = pointer.pointer();
917 data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
918 set_mode = true;
919 }),
920 CursorGrabMode::None => {
921 set_mode = true;
923 },
924 }
925
926 if set_mode {
928 self.cursor_grab_mode.current_grab_mode = mode;
929 }
930
931 Ok(())
932 }
933
934 pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
935 self.apply_on_pointer(|_, data| {
937 let serial = data.latest_button_serial();
938 let seat = data.seat();
939 self.window.show_window_menu(seat, serial, position.into());
940 });
941 }
942
943 pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), ExternalError> {
945 if self.pointer_constraints.is_none() {
946 return Err(ExternalError::NotSupported(NotSupportedError::new()));
947 }
948
949 if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
951 return Err(ExternalError::Os(os_error!(crate::platform_impl::OsError::Misc(
952 "cursor position can be set only for locked cursor."
953 ))));
954 }
955
956 self.apply_on_pointer(|_, data| {
957 data.set_locked_cursor_position(position.x, position.y);
958 });
959
960 Ok(())
961 }
962
963 pub fn set_cursor_visible(&mut self, cursor_visible: bool) {
965 self.cursor_visible = cursor_visible;
966
967 if self.cursor_visible {
968 match &self.selected_cursor {
969 SelectedCursor::Named(icon) => self.set_cursor(*icon),
970 SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
971 }
972 } else {
973 for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
974 let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();
975
976 pointer.pointer().set_cursor(latest_enter_serial, None, 0, 0);
977 }
978 }
979 }
980
981 #[inline]
983 pub fn set_decorate(&mut self, decorate: bool) {
984 if decorate == self.decorate {
985 return;
986 }
987
988 self.decorate = decorate;
989
990 match self.last_configure.as_ref().map(|configure| configure.decoration_mode) {
991 Some(DecorationMode::Server) if !self.decorate => {
992 self.window.request_decoration_mode(Some(DecorationMode::Client))
994 },
995 _ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)),
996 _ => (),
997 }
998
999 if let Some(frame) = self.frame.as_mut() {
1000 frame.set_hidden(!decorate);
1001 self.resize(self.size);
1003 }
1004 }
1005
1006 #[inline]
1008 pub fn add_seat_focus(&mut self, seat: ObjectId) {
1009 self.seat_focus.insert(seat);
1010 }
1011
1012 #[inline]
1014 pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
1015 self.seat_focus.remove(seat);
1016 }
1017
1018 pub fn set_ime_allowed(&mut self, allowed: bool) -> bool {
1020 self.ime_allowed = allowed;
1021
1022 let mut applied = false;
1023 for text_input in &self.text_inputs {
1024 applied = true;
1025 if allowed {
1026 text_input.enable();
1027 text_input.set_content_type_by_purpose(self.ime_purpose);
1028 } else {
1029 text_input.disable();
1030 }
1031 text_input.commit();
1032 }
1033
1034 applied
1035 }
1036
1037 pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
1039 let (x, y) = (position.x as i32, position.y as i32);
1043 let (width, height) = (size.width as i32, size.height as i32);
1044 for text_input in self.text_inputs.iter() {
1045 text_input.set_cursor_rectangle(x, y, width, height);
1046 text_input.commit();
1047 }
1048 }
1049
1050 pub fn set_ime_purpose(&mut self, purpose: ImePurpose) {
1052 self.ime_purpose = purpose;
1053
1054 for text_input in &self.text_inputs {
1055 text_input.set_content_type_by_purpose(purpose);
1056 text_input.commit();
1057 }
1058 }
1059
1060 pub fn ime_purpose(&self) -> ImePurpose {
1062 self.ime_purpose
1063 }
1064
1065 #[inline]
1067 pub fn set_scale_factor(&mut self, scale_factor: f64) {
1068 self.scale_factor = scale_factor;
1069
1070 if self.fractional_scale.is_none() {
1072 let _ = self.window.set_buffer_scale(self.scale_factor as _);
1073 }
1074
1075 if let Some(frame) = self.frame.as_mut() {
1076 frame.set_scaling_factor(scale_factor);
1077 }
1078 }
1079
1080 #[inline]
1082 pub fn set_blur(&mut self, blurred: bool) {
1083 if blurred && self.blur.is_none() {
1084 if let Some(blur_manager) = self.blur_manager.as_ref() {
1085 let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
1086 blur.commit();
1087 self.blur = Some(blur);
1088 } else {
1089 info!("Blur manager unavailable, unable to change blur")
1090 }
1091 } else if !blurred && self.blur.is_some() {
1092 self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface());
1093 self.blur.take().unwrap().release();
1094 }
1095 }
1096
1097 pub fn set_title(&mut self, mut title: String) {
1101 if title.len() > 1024 {
1104 let mut new_len = 1024;
1105 while !title.is_char_boundary(new_len) {
1106 new_len -= 1;
1107 }
1108 title.truncate(new_len);
1109 }
1110
1111 if let Some(frame) = self.frame.as_mut() {
1113 frame.set_title(&title);
1114 }
1115
1116 self.window.set_title(&title);
1117 self.title = title;
1118 }
1119
1120 #[inline]
1122 pub fn set_transparent(&mut self, transparent: bool) {
1123 self.transparent = transparent;
1124 self.reload_transparency_hint();
1125 }
1126
1127 #[inline]
1129 pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) {
1130 if !self.text_inputs.iter().any(|t| t == text_input) {
1131 self.text_inputs.push(text_input.clone());
1132 }
1133 }
1134
1135 #[inline]
1137 pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) {
1138 if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) {
1139 self.text_inputs.remove(position);
1140 }
1141 }
1142
1143 #[inline]
1145 pub fn title(&self) -> &str {
1146 &self.title
1147 }
1148}
1149
1150impl Drop for WindowState {
1151 fn drop(&mut self) {
1152 if let Some(blur) = self.blur.take() {
1153 blur.release();
1154 }
1155
1156 if let Some(fs) = self.fractional_scale.take() {
1157 fs.destroy();
1158 }
1159
1160 if let Some(viewport) = self.viewport.take() {
1161 viewport.destroy();
1162 }
1163
1164 }
1167}
1168
1169#[derive(Clone, Copy)]
1171struct GrabState {
1172 user_grab_mode: CursorGrabMode,
1174
1175 current_grab_mode: CursorGrabMode,
1177}
1178
1179impl GrabState {
1180 fn new() -> Self {
1181 Self { user_grab_mode: CursorGrabMode::None, current_grab_mode: CursorGrabMode::None }
1182 }
1183}
1184
1185#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1187pub enum FrameCallbackState {
1188 #[default]
1190 None,
1191 Requested,
1193 Received,
1195}
1196
1197impl From<ResizeDirection> for XdgResizeEdge {
1198 fn from(value: ResizeDirection) -> Self {
1199 match value {
1200 ResizeDirection::North => XdgResizeEdge::Top,
1201 ResizeDirection::West => XdgResizeEdge::Left,
1202 ResizeDirection::NorthWest => XdgResizeEdge::TopLeft,
1203 ResizeDirection::NorthEast => XdgResizeEdge::TopRight,
1204 ResizeDirection::East => XdgResizeEdge::Right,
1205 ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft,
1206 ResizeDirection::SouthEast => XdgResizeEdge::BottomRight,
1207 ResizeDirection::South => XdgResizeEdge::Bottom,
1208 }
1209 }
1210}
1211
1212#[cfg(feature = "sctk-adwaita")]
1214fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
1215 match theme {
1216 Some(Theme::Light) => sctk_adwaita::FrameConfig::light(),
1217 Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(),
1218 None => sctk_adwaita::FrameConfig::auto(),
1219 }
1220}