winit/platform_impl/linux/wayland/window/
state.rs

1//! The state of the window, which is shared with the event-loop.
2
3use 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
52// Minimum window inner size.
53const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
54
55/// The state of the window which is being updated from the [`WinitState`].
56pub struct WindowState {
57    /// The connection to Wayland server.
58    pub connection: Connection,
59
60    /// The `Shm` to set cursor.
61    pub shm: WlShm,
62
63    // A shared pool where to allocate custom cursors.
64    custom_cursor_pool: Arc<Mutex<SlotPool>>,
65
66    /// The last received configure.
67    pub last_configure: Option<WindowConfigure>,
68
69    /// The pointers observed on the window.
70    pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
71
72    selected_cursor: SelectedCursor,
73
74    /// Whether the cursor is visible.
75    pub cursor_visible: bool,
76
77    /// Pointer constraints to lock/confine pointer.
78    pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
79
80    /// Queue handle.
81    pub queue_handle: QueueHandle<WinitState>,
82
83    /// Theme variant.
84    theme: Option<Theme>,
85
86    /// The current window title.
87    title: String,
88
89    /// Whether the frame is resizable.
90    resizable: bool,
91
92    // NOTE: we can't use simple counter, since it's racy when seat getting destroyed and new
93    // is created, since add/removed stuff could be delivered a bit out of order.
94    /// Seats that has keyboard focus on that window.
95    seat_focus: HashSet<ObjectId>,
96
97    /// The scale factor of the window.
98    scale_factor: f64,
99
100    /// Whether the window is transparent.
101    transparent: bool,
102
103    /// The state of the compositor to create WlRegions.
104    compositor: Arc<CompositorState>,
105
106    /// The current cursor grabbing mode.
107    cursor_grab_mode: GrabState,
108
109    /// Whether the IME input is allowed for that window.
110    ime_allowed: bool,
111
112    /// The current IME purpose.
113    ime_purpose: ImePurpose,
114
115    /// The text inputs observed on the window.
116    text_inputs: Vec<ZwpTextInputV3>,
117
118    /// The inner size of the window, as in without client side decorations.
119    size: LogicalSize<u32>,
120
121    /// Whether the CSD fail to create, so we don't try to create them on each iteration.
122    csd_fails: bool,
123
124    /// Whether we should decorate the frame.
125    decorate: bool,
126
127    /// Min size.
128    min_inner_size: LogicalSize<u32>,
129    max_inner_size: Option<LogicalSize<u32>>,
130
131    /// The size of the window when no states were applied to it. The primary use for it
132    /// is to fallback to original window size, before it was maximized, if the compositor
133    /// sends `None` for the new size in the configure.
134    stateless_size: LogicalSize<u32>,
135
136    /// Initial window size provided by the user. Removed on the first
137    /// configure.
138    initial_size: Option<Size>,
139
140    /// The state of the frame callback.
141    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    /// Whether the client side decorations have pending move operations.
149    ///
150    /// The value is the serial of the event triggered moved.
151    has_pending_move: Option<u32>,
152
153    /// The underlying SCTK window.
154    pub window: Window,
155
156    // NOTE: The spec says that destroying parent(`window` in our case), will unmap the
157    // subsurfaces. Thus to achieve atomic unmap of the client, drop the decorations
158    // frame after the `window` is dropped. To achieve that we rely on rust's struct
159    // field drop order guarantees.
160    /// The window frame, which is created from the configure request.
161    frame: Option<WinitFrame>,
162}
163
164impl WindowState {
165    /// Create new window state.
166    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    /// Apply closure on the given pointer.
225    fn apply_on_pointer<F: FnMut(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
226        &self,
227        mut 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    /// Get the current state of the frame callback.
236    pub fn frame_callback_state(&self) -> FrameCallbackState {
237        self.frame_callback_state
238    }
239
240    /// The frame callback was received, but not yet sent to the user.
241    pub fn frame_callback_received(&mut self) {
242        self.frame_callback_state = FrameCallbackState::Received;
243    }
244
245    /// Reset the frame callbacks state.
246    pub fn frame_callback_reset(&mut self) {
247        self.frame_callback_state = FrameCallbackState::None;
248    }
249
250    /// Request a frame callback if we don't have one for this window in flight.
251    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        // NOTE: when using fractional scaling or wl_compositor@v6 the scaling
269        // should be delivered before the first configure, thus apply it to
270        // properly scale the physical sizes provided by the users.
271        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                    // Hide the frame if we were asked to not decorate.
295                    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            // Drop the frame for server side decorations to save resources.
305            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            // Configure the window states.
312            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        // Apply configure bounds only when compositor let the user decide what size to pick.
333        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            // NOTE: `None` is present for the initial configure, thus we must always resize.
354            .unwrap_or(true);
355
356        // NOTE: Set the configure before doing a resize, since we query it during it.
357        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    /// Compute the bounds for the inner size of the surface.
368    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    /// Start interacting drag resize.
394    pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
395        let xdg_toplevel = self.window.xdg_toplevel();
396
397        // TODO(kchibisov) handle touch serials.
398        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    /// Start the window drag.
408    pub fn drag_window(&self) -> Result<(), ExternalError> {
409        let xdg_toplevel = self.window.xdg_toplevel();
410        // TODO(kchibisov) handle touch serials.
411        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    /// Tells whether the window should be closed.
421    #[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    // Move the point over decorations.
467    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        // Take the serial if we had any, so it doesn't stick around.
476        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 we have a cursor change, that means that cursor is over the decorations,
481            // so try to apply move.
482            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    /// Get the stored resizable state.
494    #[inline]
495    pub fn resizable(&self) -> bool {
496        self.resizable
497    }
498
499    /// Set the resizable state on the window.
500    ///
501    /// Returns `true` when the state was applied.
502    #[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            // Restore min/max sizes of the window.
511            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        // Reload the state on the frame as well.
518        if let Some(frame) = self.frame.as_mut() {
519            frame.set_resizable(resizable);
520        }
521
522        true
523    }
524
525    /// Whether the window is focused by any seat.
526    #[inline]
527    pub fn has_focus(&self) -> bool {
528        !self.seat_focus.is_empty()
529    }
530
531    /// Whether the IME is allowed.
532    #[inline]
533    pub fn ime_allowed(&self) -> bool {
534        self.ime_allowed
535    }
536
537    /// Get the size of the window.
538    #[inline]
539    pub fn inner_size(&self) -> LogicalSize<u32> {
540        self.size
541    }
542
543    /// Whether the window received initial configure event from the compositor.
544    #[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            // Server side decorations.
560            true
561        }
562    }
563
564    /// Get the outer size of the window.
565    #[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    /// Register pointer on the top-level.
574    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    /// Pointer has left the top-level.
583    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    /// Refresh the decorations frame if it's present returning whether the client should redraw.
597    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    /// Reload the cursor style on the given window.
608    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    /// Reissue the transparency hint to the compositor.
620    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    /// Try to resize the window when the user can do so.
634    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    /// Resize the window to the new inner size.
643    fn resize(&mut self, inner_size: LogicalSize<u32>) {
644        self.size = inner_size;
645
646        // Update the stateless size.
647        if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
648            self.stateless_size = inner_size;
649        }
650
651        // Update the inner frame.
652        let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
653            // Resize only visible frame.
654            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        // Reload the hint.
667        self.reload_transparency_hint();
668
669        // Set the window geometry.
670        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        // Update the target viewport, this is used if and only if fractional scaling is in use.
678        if let Some(viewport) = self.viewport.as_ref() {
679            // Set inner size without the borders.
680            viewport.set_destination(self.size.width as _, self.size.height as _);
681        }
682    }
683
684    /// Get the scale factor of the window.
685    #[inline]
686    pub fn scale_factor(&self) -> f64 {
687        self.scale_factor
688    }
689
690    /// Set the cursor icon.
691    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    /// Set the custom cursor icon.
706    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, data| {
730            let surface = pointer.surface();
731
732            let scale = if let Some(viewport) = data.viewport() {
733                let scale = self.scale_factor();
734                let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
735                viewport.set_destination(size.width, size.height);
736                scale
737            } else {
738                let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
739                surface.set_buffer_scale(scale);
740                scale as f64
741            };
742
743            surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
744            if surface.version() >= 4 {
745                surface.damage_buffer(0, 0, cursor.w, cursor.h);
746            } else {
747                let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
748                surface.damage(0, 0, size.width, size.height);
749            }
750            surface.commit();
751
752            let serial = pointer
753                .pointer()
754                .data::<WinitPointerData>()
755                .and_then(|data| data.pointer_data().latest_enter_serial())
756                .unwrap();
757
758            let hotspot =
759                PhysicalPosition::new(cursor.hotspot_x, cursor.hotspot_y).to_logical(scale);
760            pointer.pointer().set_cursor(serial, Some(surface), hotspot.x, hotspot.y);
761        });
762    }
763
764    /// Set maximum inner window size.
765    pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
766        // Ensure that the window has the right minimum size.
767        let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
768        size.width = size.width.max(MIN_WINDOW_SIZE.width);
769        size.height = size.height.max(MIN_WINDOW_SIZE.height);
770
771        // Add the borders.
772        let size = self
773            .frame
774            .as_ref()
775            .map(|frame| frame.add_borders(size.width, size.height).into())
776            .unwrap_or(size);
777
778        self.min_inner_size = size;
779        self.window.set_min_size(Some(size.into()));
780    }
781
782    /// Set maximum inner window size.
783    pub fn set_max_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
784        let size = size.map(|size| {
785            self.frame
786                .as_ref()
787                .map(|frame| frame.add_borders(size.width, size.height).into())
788                .unwrap_or(size)
789        });
790
791        self.max_inner_size = size;
792        self.window.set_max_size(size.map(Into::into));
793    }
794
795    /// Set the CSD theme.
796    pub fn set_theme(&mut self, theme: Option<Theme>) {
797        self.theme = theme;
798        #[cfg(feature = "sctk-adwaita")]
799        if let Some(frame) = self.frame.as_mut() {
800            frame.set_config(into_sctk_adwaita_config(theme))
801        }
802    }
803
804    /// The current theme for CSD decorations.
805    #[inline]
806    pub fn theme(&self) -> Option<Theme> {
807        self.theme
808    }
809
810    /// Set the cursor grabbing state on the top-level.
811    pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
812        if self.cursor_grab_mode.user_grab_mode == mode {
813            return Ok(());
814        }
815
816        self.set_cursor_grab_inner(mode)?;
817        // Update user grab on success.
818        self.cursor_grab_mode.user_grab_mode = mode;
819        Ok(())
820    }
821
822    /// Reload the hints for minimum and maximum sizes.
823    pub fn reload_min_max_hints(&mut self) {
824        self.set_min_inner_size(Some(self.min_inner_size));
825        self.set_max_inner_size(self.max_inner_size);
826    }
827
828    /// Set the grabbing state on the surface.
829    fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
830        let pointer_constraints = match self.pointer_constraints.as_ref() {
831            Some(pointer_constraints) => pointer_constraints,
832            None if mode == CursorGrabMode::None => return Ok(()),
833            None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
834        };
835
836        let mut unset_old = false;
837        match self.cursor_grab_mode.current_grab_mode {
838            CursorGrabMode::None => unset_old = true,
839            CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
840                data.unconfine_pointer();
841                unset_old = true;
842            }),
843            CursorGrabMode::Locked => {
844                self.apply_on_pointer(|_, data| {
845                    data.unlock_pointer();
846                    unset_old = true;
847                });
848            },
849        }
850
851        // In case we haven't unset the old mode, it means that we don't have a cursor above
852        // the window, thus just wait for it to re-appear.
853        if !unset_old {
854            return Ok(());
855        }
856
857        let mut set_mode = false;
858        let surface = self.window.wl_surface();
859        match mode {
860            CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
861                let pointer = pointer.pointer();
862                data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
863                set_mode = true;
864            }),
865            CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
866                let pointer = pointer.pointer();
867                data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
868                set_mode = true;
869            }),
870            CursorGrabMode::None => {
871                // Current lock/confine was already removed.
872                set_mode = true;
873            },
874        }
875
876        // Replace the current grab mode after we've ensure that it got updated.
877        if set_mode {
878            self.cursor_grab_mode.current_grab_mode = mode;
879        }
880
881        Ok(())
882    }
883
884    pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
885        // TODO(kchibisov) handle touch serials.
886        self.apply_on_pointer(|_, data| {
887            let serial = data.latest_button_serial();
888            let seat = data.seat();
889            self.window.show_window_menu(seat, serial, position.into());
890        });
891    }
892
893    /// Set the position of the cursor.
894    pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), ExternalError> {
895        if self.pointer_constraints.is_none() {
896            return Err(ExternalError::NotSupported(NotSupportedError::new()));
897        }
898
899        // Position can be set only for locked cursor.
900        if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
901            return Err(ExternalError::Os(os_error!(crate::platform_impl::OsError::Misc(
902                "cursor position can be set only for locked cursor."
903            ))));
904        }
905
906        self.apply_on_pointer(|_, data| {
907            data.set_locked_cursor_position(position.x, position.y);
908        });
909
910        Ok(())
911    }
912
913    /// Set the visibility state of the cursor.
914    pub fn set_cursor_visible(&mut self, cursor_visible: bool) {
915        self.cursor_visible = cursor_visible;
916
917        if self.cursor_visible {
918            match &self.selected_cursor {
919                SelectedCursor::Named(icon) => self.set_cursor(*icon),
920                SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
921            }
922        } else {
923            for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
924                let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();
925
926                pointer.pointer().set_cursor(latest_enter_serial, None, 0, 0);
927            }
928        }
929    }
930
931    /// Whether show or hide client side decorations.
932    #[inline]
933    pub fn set_decorate(&mut self, decorate: bool) {
934        if decorate == self.decorate {
935            return;
936        }
937
938        self.decorate = decorate;
939
940        match self.last_configure.as_ref().map(|configure| configure.decoration_mode) {
941            Some(DecorationMode::Server) if !self.decorate => {
942                // To disable decorations we should request client and hide the frame.
943                self.window.request_decoration_mode(Some(DecorationMode::Client))
944            },
945            _ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)),
946            _ => (),
947        }
948
949        if let Some(frame) = self.frame.as_mut() {
950            frame.set_hidden(!decorate);
951            // Force the resize.
952            self.resize(self.size);
953        }
954    }
955
956    /// Add seat focus for the window.
957    #[inline]
958    pub fn add_seat_focus(&mut self, seat: ObjectId) {
959        self.seat_focus.insert(seat);
960    }
961
962    /// Remove seat focus from the window.
963    #[inline]
964    pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
965        self.seat_focus.remove(seat);
966    }
967
968    /// Returns `true` if the requested state was applied.
969    pub fn set_ime_allowed(&mut self, allowed: bool) -> bool {
970        self.ime_allowed = allowed;
971
972        let mut applied = false;
973        for text_input in &self.text_inputs {
974            applied = true;
975            if allowed {
976                text_input.enable();
977                text_input.set_content_type_by_purpose(self.ime_purpose);
978            } else {
979                text_input.disable();
980            }
981            text_input.commit();
982        }
983
984        applied
985    }
986
987    /// Set the IME position.
988    pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
989        // FIXME: This won't fly unless user will have a way to request IME window per seat, since
990        // the ime windows will be overlapping, but winit doesn't expose API to specify for
991        // which seat we're setting IME position.
992        let (x, y) = (position.x as i32, position.y as i32);
993        let (width, height) = (size.width as i32, size.height as i32);
994        for text_input in self.text_inputs.iter() {
995            text_input.set_cursor_rectangle(x, y, width, height);
996            text_input.commit();
997        }
998    }
999
1000    /// Set the IME purpose.
1001    pub fn set_ime_purpose(&mut self, purpose: ImePurpose) {
1002        self.ime_purpose = purpose;
1003
1004        for text_input in &self.text_inputs {
1005            text_input.set_content_type_by_purpose(purpose);
1006            text_input.commit();
1007        }
1008    }
1009
1010    /// Get the IME purpose.
1011    pub fn ime_purpose(&self) -> ImePurpose {
1012        self.ime_purpose
1013    }
1014
1015    /// Set the scale factor for the given window.
1016    #[inline]
1017    pub fn set_scale_factor(&mut self, scale_factor: f64) {
1018        self.scale_factor = scale_factor;
1019
1020        // NOTE: When fractional scaling is not used update the buffer scale.
1021        if self.fractional_scale.is_none() {
1022            let _ = self.window.set_buffer_scale(self.scale_factor as _);
1023        }
1024
1025        if let Some(frame) = self.frame.as_mut() {
1026            frame.set_scaling_factor(scale_factor);
1027        }
1028    }
1029
1030    /// Make window background blurred
1031    #[inline]
1032    pub fn set_blur(&mut self, blurred: bool) {
1033        if blurred && self.blur.is_none() {
1034            if let Some(blur_manager) = self.blur_manager.as_ref() {
1035                let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
1036                blur.commit();
1037                self.blur = Some(blur);
1038            } else {
1039                info!("Blur manager unavailable, unable to change blur")
1040            }
1041        } else if !blurred && self.blur.is_some() {
1042            self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface());
1043            self.blur.take().unwrap().release();
1044        }
1045    }
1046
1047    /// Set the window title to a new value.
1048    ///
1049    /// This will automatically truncate the title to something meaningful.
1050    pub fn set_title(&mut self, mut title: String) {
1051        // Truncate the title to at most 1024 bytes, so that it does not blow up the protocol
1052        // messages
1053        if title.len() > 1024 {
1054            let mut new_len = 1024;
1055            while !title.is_char_boundary(new_len) {
1056                new_len -= 1;
1057            }
1058            title.truncate(new_len);
1059        }
1060
1061        // Update the CSD title.
1062        if let Some(frame) = self.frame.as_mut() {
1063            frame.set_title(&title);
1064        }
1065
1066        self.window.set_title(&title);
1067        self.title = title;
1068    }
1069
1070    /// Mark the window as transparent.
1071    #[inline]
1072    pub fn set_transparent(&mut self, transparent: bool) {
1073        self.transparent = transparent;
1074        self.reload_transparency_hint();
1075    }
1076
1077    /// Register text input on the top-level.
1078    #[inline]
1079    pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) {
1080        if !self.text_inputs.iter().any(|t| t == text_input) {
1081            self.text_inputs.push(text_input.clone());
1082        }
1083    }
1084
1085    /// The text input left the top-level.
1086    #[inline]
1087    pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) {
1088        if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) {
1089            self.text_inputs.remove(position);
1090        }
1091    }
1092
1093    /// Get the cached title.
1094    #[inline]
1095    pub fn title(&self) -> &str {
1096        &self.title
1097    }
1098}
1099
1100impl Drop for WindowState {
1101    fn drop(&mut self) {
1102        if let Some(blur) = self.blur.take() {
1103            blur.release();
1104        }
1105
1106        if let Some(fs) = self.fractional_scale.take() {
1107            fs.destroy();
1108        }
1109
1110        if let Some(viewport) = self.viewport.take() {
1111            viewport.destroy();
1112        }
1113
1114        // NOTE: the wl_surface used by the window is being cleaned up when
1115        // dropping SCTK `Window`.
1116    }
1117}
1118
1119/// The state of the cursor grabs.
1120#[derive(Clone, Copy)]
1121struct GrabState {
1122    /// The grab mode requested by the user.
1123    user_grab_mode: CursorGrabMode,
1124
1125    /// The current grab mode.
1126    current_grab_mode: CursorGrabMode,
1127}
1128
1129impl GrabState {
1130    fn new() -> Self {
1131        Self { user_grab_mode: CursorGrabMode::None, current_grab_mode: CursorGrabMode::None }
1132    }
1133}
1134
1135/// The state of the frame callback.
1136#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1137pub enum FrameCallbackState {
1138    /// No frame callback was requested.
1139    #[default]
1140    None,
1141    /// The frame callback was requested, but not yet arrived, the redraw events are throttled.
1142    Requested,
1143    /// The callback was marked as done, and user could receive redraw requested
1144    Received,
1145}
1146
1147impl From<ResizeDirection> for XdgResizeEdge {
1148    fn from(value: ResizeDirection) -> Self {
1149        match value {
1150            ResizeDirection::North => XdgResizeEdge::Top,
1151            ResizeDirection::West => XdgResizeEdge::Left,
1152            ResizeDirection::NorthWest => XdgResizeEdge::TopLeft,
1153            ResizeDirection::NorthEast => XdgResizeEdge::TopRight,
1154            ResizeDirection::East => XdgResizeEdge::Right,
1155            ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft,
1156            ResizeDirection::SouthEast => XdgResizeEdge::BottomRight,
1157            ResizeDirection::South => XdgResizeEdge::Bottom,
1158        }
1159    }
1160}
1161
1162// NOTE: Rust doesn't allow `From<Option<Theme>>`.
1163#[cfg(feature = "sctk-adwaita")]
1164fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
1165    match theme {
1166        Some(Theme::Light) => sctk_adwaita::FrameConfig::light(),
1167        Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(),
1168        None => sctk_adwaita::FrameConfig::auto(),
1169    }
1170}