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 sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt};
9use sctk::reexports::client::backend::ObjectId;
10use sctk::reexports::client::protocol::wl_seat::WlSeat;
11use sctk::reexports::client::protocol::wl_shm::WlShm;
12use sctk::reexports::client::protocol::wl_surface::WlSurface;
13use sctk::reexports::client::{Connection, Proxy, QueueHandle};
14use sctk::reexports::csd_frame::{
15    DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowState as XdgWindowState,
16};
17use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
18use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
19use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
20use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
21use sctk::seat::pointer::{PointerDataExt, ThemedPointer};
22use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
23use sctk::shell::xdg::XdgSurface;
24use sctk::shell::WaylandSurface;
25use sctk::shm::slot::SlotPool;
26use sctk::shm::Shm;
27use sctk::subcompositor::SubcompositorState;
28use tracing::{info, warn};
29use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
30
31use crate::cursor::CustomCursor as RootCustomCursor;
32use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
33use crate::error::{NotSupportedError, RequestError};
34use crate::platform_impl::wayland::logical_to_physical_rounded;
35use crate::platform_impl::wayland::seat::{
36    PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext,
37};
38use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
39use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
40use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager;
41use crate::platform_impl::{PlatformCustomCursor, WindowId};
42use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
43
44#[cfg(feature = "sctk-adwaita")]
45pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
46#[cfg(not(feature = "sctk-adwaita"))]
47pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame<WinitState>;
48
49// Minimum window surface size.
50const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
51
52/// The state of the window which is being updated from the [`WinitState`].
53pub struct WindowState {
54    /// The connection to Wayland server.
55    pub connection: Connection,
56
57    /// The `Shm` to set cursor.
58    pub shm: WlShm,
59
60    // A shared pool where to allocate custom cursors.
61    custom_cursor_pool: Arc<Mutex<SlotPool>>,
62
63    /// The last received configure.
64    pub last_configure: Option<WindowConfigure>,
65
66    /// The pointers observed on the window.
67    pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
68
69    selected_cursor: SelectedCursor,
70
71    /// Whether the cursor is visible.
72    pub cursor_visible: bool,
73
74    /// Pointer constraints to lock/confine pointer.
75    pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
76
77    /// Queue handle.
78    pub queue_handle: QueueHandle<WinitState>,
79
80    /// Theme variant.
81    theme: Option<Theme>,
82
83    /// The current window title.
84    title: String,
85
86    /// Whether the frame is resizable.
87    resizable: bool,
88
89    // NOTE: we can't use simple counter, since it's racy when seat getting destroyed and new
90    // is created, since add/removed stuff could be delivered a bit out of order.
91    /// Seats that has keyboard focus on that window.
92    seat_focus: HashSet<ObjectId>,
93
94    /// The scale factor of the window.
95    scale_factor: f64,
96
97    /// Whether the window is transparent.
98    transparent: bool,
99
100    /// The state of the compositor to create WlRegions.
101    compositor: Arc<CompositorState>,
102
103    /// The current cursor grabbing mode.
104    cursor_grab_mode: GrabState,
105
106    /// Whether the IME input is allowed for that window.
107    ime_allowed: bool,
108
109    /// The current IME purpose.
110    ime_purpose: ImePurpose,
111
112    /// The text inputs observed on the window.
113    text_inputs: Vec<ZwpTextInputV3>,
114
115    /// The surface size of the window, as in without client side decorations.
116    size: LogicalSize<u32>,
117
118    /// Whether the CSD fail to create, so we don't try to create them on each iteration.
119    csd_fails: bool,
120
121    /// Whether we should decorate the frame.
122    decorate: bool,
123
124    /// Min size.
125    min_surface_size: LogicalSize<u32>,
126    max_surface_size: Option<LogicalSize<u32>>,
127
128    /// The size of the window when no states were applied to it. The primary use for it
129    /// is to fallback to original window size, before it was maximized, if the compositor
130    /// sends `None` for the new size in the configure.
131    stateless_size: LogicalSize<u32>,
132
133    /// Initial window size provided by the user. Removed on the first
134    /// configure.
135    initial_size: Option<Size>,
136
137    /// The state of the frame callback.
138    frame_callback_state: FrameCallbackState,
139
140    viewport: Option<WpViewport>,
141    fractional_scale: Option<WpFractionalScaleV1>,
142    blur: Option<OrgKdeKwinBlur>,
143    blur_manager: Option<KWinBlurManager>,
144
145    /// Whether the client side decorations have pending move operations.
146    ///
147    /// The value is the serial of the event triggered moved.
148    has_pending_move: Option<u32>,
149
150    /// The underlying SCTK window.
151    pub window: Window,
152
153    // NOTE: The spec says that destroying parent(`window` in our case), will unmap the
154    // subsurfaces. Thus to achieve atomic unmap of the client, drop the decorations
155    // frame after the `window` is dropped. To achieve that we rely on rust's struct
156    // field drop order guarantees.
157    /// The window frame, which is created from the configure request.
158    frame: Option<WinitFrame>,
159}
160
161impl WindowState {
162    /// Create new window state.
163    pub fn new(
164        connection: Connection,
165        queue_handle: &QueueHandle<WinitState>,
166        winit_state: &WinitState,
167        initial_size: Size,
168        window: Window,
169        theme: Option<Theme>,
170    ) -> Self {
171        let compositor = winit_state.compositor_state.clone();
172        let pointer_constraints = winit_state.pointer_constraints.clone();
173        let viewport = winit_state
174            .viewporter_state
175            .as_ref()
176            .map(|state| state.get_viewport(window.wl_surface(), queue_handle));
177        let fractional_scale = winit_state
178            .fractional_scaling_manager
179            .as_ref()
180            .map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
181
182        Self {
183            blur: None,
184            blur_manager: winit_state.kwin_blur_manager.clone(),
185            compositor,
186            connection,
187            csd_fails: false,
188            cursor_grab_mode: GrabState::new(),
189            selected_cursor: Default::default(),
190            cursor_visible: true,
191            decorate: true,
192            fractional_scale,
193            frame: None,
194            frame_callback_state: FrameCallbackState::None,
195            seat_focus: Default::default(),
196            has_pending_move: None,
197            ime_allowed: false,
198            ime_purpose: ImePurpose::Normal,
199            last_configure: None,
200            max_surface_size: None,
201            min_surface_size: MIN_WINDOW_SIZE,
202            pointer_constraints,
203            pointers: Default::default(),
204            queue_handle: queue_handle.clone(),
205            resizable: true,
206            scale_factor: 1.,
207            shm: winit_state.shm.wl_shm().clone(),
208            custom_cursor_pool: winit_state.custom_cursor_pool.clone(),
209            size: initial_size.to_logical(1.),
210            stateless_size: initial_size.to_logical(1.),
211            initial_size: Some(initial_size),
212            text_inputs: Vec::new(),
213            theme,
214            title: String::default(),
215            transparent: false,
216            viewport,
217            window,
218        }
219    }
220
221    /// Apply closure on the given pointer.
222    fn apply_on_pointer<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
223        &self,
224        callback: F,
225    ) {
226        self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| {
227            let data = pointer.pointer().winit_data();
228            callback(pointer.as_ref(), data);
229        })
230    }
231
232    /// Get the current state of the frame callback.
233    pub fn frame_callback_state(&self) -> FrameCallbackState {
234        self.frame_callback_state
235    }
236
237    /// The frame callback was received, but not yet sent to the user.
238    pub fn frame_callback_received(&mut self) {
239        self.frame_callback_state = FrameCallbackState::Received;
240    }
241
242    /// Reset the frame callbacks state.
243    pub fn frame_callback_reset(&mut self) {
244        self.frame_callback_state = FrameCallbackState::None;
245    }
246
247    /// Request a frame callback if we don't have one for this window in flight.
248    pub fn request_frame_callback(&mut self) {
249        let surface = self.window.wl_surface();
250        match self.frame_callback_state {
251            FrameCallbackState::None | FrameCallbackState::Received => {
252                self.frame_callback_state = FrameCallbackState::Requested;
253                surface.frame(&self.queue_handle, surface.clone());
254            },
255            FrameCallbackState::Requested => (),
256        }
257    }
258
259    pub fn configure(
260        &mut self,
261        configure: WindowConfigure,
262        shm: &Shm,
263        subcompositor: &Option<Arc<SubcompositorState>>,
264    ) -> bool {
265        // NOTE: when using fractional scaling or wl_compositor@v6 the scaling
266        // should be delivered before the first configure, thus apply it to
267        // properly scale the physical sizes provided by the users.
268        if let Some(initial_size) = self.initial_size.take() {
269            self.size = initial_size.to_logical(self.scale_factor());
270            self.stateless_size = self.size;
271        }
272
273        if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
274            configure.decoration_mode == DecorationMode::Client
275                && self.frame.is_none()
276                && !self.csd_fails
277        }) {
278            match WinitFrame::new(
279                &self.window,
280                shm,
281                #[cfg(feature = "sctk-adwaita")]
282                self.compositor.clone(),
283                subcompositor.clone(),
284                self.queue_handle.clone(),
285                #[cfg(feature = "sctk-adwaita")]
286                into_sctk_adwaita_config(self.theme),
287            ) {
288                Ok(mut frame) => {
289                    frame.set_title(&self.title);
290                    frame.set_scaling_factor(self.scale_factor);
291                    // Hide the frame if we were asked to not decorate.
292                    frame.set_hidden(!self.decorate);
293                    self.frame = Some(frame);
294                },
295                Err(err) => {
296                    warn!("Failed to create client side decorations frame: {err}");
297                    self.csd_fails = true;
298                },
299            }
300        } else if configure.decoration_mode == DecorationMode::Server {
301            // Drop the frame for server side decorations to save resources.
302            self.frame = None;
303        }
304
305        let stateless = Self::is_stateless(&configure);
306
307        let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
308            // Configure the window states.
309            frame.update_state(configure.state);
310
311            match configure.new_size {
312                (Some(width), Some(height)) => {
313                    let (width, height) = frame.subtract_borders(width, height);
314                    let width = width.map(|w| w.get()).unwrap_or(1);
315                    let height = height.map(|h| h.get()).unwrap_or(1);
316                    ((width, height).into(), false)
317                },
318                (..) if stateless => (self.stateless_size, true),
319                _ => (self.size, true),
320            }
321        } else {
322            match configure.new_size {
323                (Some(width), Some(height)) => ((width.get(), height.get()).into(), false),
324                _ if stateless => (self.stateless_size, true),
325                _ => (self.size, true),
326            }
327        };
328
329        // Apply configure bounds only when compositor let the user decide what size to pick.
330        if constrain {
331            let bounds = self.surface_size_bounds(&configure);
332            new_size.width =
333                bounds.0.map(|bound_w| new_size.width.min(bound_w.get())).unwrap_or(new_size.width);
334            new_size.height = bounds
335                .1
336                .map(|bound_h| new_size.height.min(bound_h.get()))
337                .unwrap_or(new_size.height);
338        }
339
340        let new_state = configure.state;
341        let old_state = self.last_configure.as_ref().map(|configure| configure.state);
342
343        let state_change_requires_resize = old_state
344            .map(|old_state| {
345                !old_state
346                    .symmetric_difference(new_state)
347                    .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
348                    .is_empty()
349            })
350            // NOTE: `None` is present for the initial configure, thus we must always resize.
351            .unwrap_or(true);
352
353        // NOTE: Set the configure before doing a resize, since we query it during it.
354        self.last_configure = Some(configure);
355
356        if state_change_requires_resize || new_size != self.surface_size() {
357            self.resize(new_size);
358            true
359        } else {
360            false
361        }
362    }
363
364    /// Compute the bounds for the surface size of the surface.
365    fn surface_size_bounds(
366        &self,
367        configure: &WindowConfigure,
368    ) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
369        let configure_bounds = match configure.suggested_bounds {
370            Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
371            None => (None, None),
372        };
373
374        if let Some(frame) = self.frame.as_ref() {
375            let (width, height) = frame.subtract_borders(
376                configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
377                configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
378            );
379            (configure_bounds.0.and(width), configure_bounds.1.and(height))
380        } else {
381            configure_bounds
382        }
383    }
384
385    #[inline]
386    fn is_stateless(configure: &WindowConfigure) -> bool {
387        !(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled())
388    }
389
390    /// Start interacting drag resize.
391    pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
392        let xdg_toplevel = self.window.xdg_toplevel();
393
394        // TODO(kchibisov) handle touch serials.
395        self.apply_on_pointer(|_, data| {
396            let serial = data.latest_button_serial();
397            let seat = data.seat();
398            xdg_toplevel.resize(seat, serial, direction.into());
399        });
400
401        Ok(())
402    }
403
404    /// Start the window drag.
405    pub fn drag_window(&self) -> Result<(), RequestError> {
406        let xdg_toplevel = self.window.xdg_toplevel();
407        // TODO(kchibisov) handle touch serials.
408        self.apply_on_pointer(|_, data| {
409            let serial = data.latest_button_serial();
410            let seat = data.seat();
411            xdg_toplevel._move(seat, serial);
412        });
413
414        Ok(())
415    }
416
417    /// Tells whether the window should be closed.
418    #[allow(clippy::too_many_arguments)]
419    pub fn frame_click(
420        &mut self,
421        click: FrameClick,
422        pressed: bool,
423        seat: &WlSeat,
424        serial: u32,
425        timestamp: Duration,
426        window_id: WindowId,
427        updates: &mut Vec<WindowCompositorUpdate>,
428    ) -> Option<bool> {
429        match self.frame.as_mut()?.on_click(timestamp, click, pressed)? {
430            FrameAction::Minimize => self.window.set_minimized(),
431            FrameAction::Maximize => self.window.set_maximized(),
432            FrameAction::UnMaximize => self.window.unset_maximized(),
433            FrameAction::Close => WinitState::queue_close(updates, window_id),
434            FrameAction::Move => self.has_pending_move = Some(serial),
435            FrameAction::Resize(edge) => {
436                let edge = match edge {
437                    ResizeEdge::None => XdgResizeEdge::None,
438                    ResizeEdge::Top => XdgResizeEdge::Top,
439                    ResizeEdge::Bottom => XdgResizeEdge::Bottom,
440                    ResizeEdge::Left => XdgResizeEdge::Left,
441                    ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
442                    ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
443                    ResizeEdge::Right => XdgResizeEdge::Right,
444                    ResizeEdge::TopRight => XdgResizeEdge::TopRight,
445                    ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
446                    _ => return None,
447                };
448                self.window.resize(seat, serial, edge);
449            },
450            FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
451            _ => (),
452        };
453
454        Some(false)
455    }
456
457    pub fn frame_point_left(&mut self) {
458        if let Some(frame) = self.frame.as_mut() {
459            frame.click_point_left();
460        }
461    }
462
463    // Move the point over decorations.
464    pub fn frame_point_moved(
465        &mut self,
466        seat: &WlSeat,
467        surface: &WlSurface,
468        timestamp: Duration,
469        x: f64,
470        y: f64,
471    ) -> Option<CursorIcon> {
472        // Take the serial if we had any, so it doesn't stick around.
473        let serial = self.has_pending_move.take();
474
475        if let Some(frame) = self.frame.as_mut() {
476            let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y);
477            // If we have a cursor change, that means that cursor is over the decorations,
478            // so try to apply move.
479            if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
480                self.window.move_(seat, serial);
481                None
482            } else {
483                cursor
484            }
485        } else {
486            None
487        }
488    }
489
490    /// Get the stored resizable state.
491    #[inline]
492    pub fn resizable(&self) -> bool {
493        self.resizable
494    }
495
496    /// Set the resizable state on the window.
497    ///
498    /// Returns `true` when the state was applied.
499    #[inline]
500    pub fn set_resizable(&mut self, resizable: bool) -> bool {
501        if self.resizable == resizable {
502            return false;
503        }
504
505        self.resizable = resizable;
506        if resizable {
507            // Restore min/max sizes of the window.
508            self.reload_min_max_hints();
509        } else {
510            self.set_min_surface_size(Some(self.size));
511            self.set_max_surface_size(Some(self.size));
512        }
513
514        // Reload the state on the frame as well.
515        if let Some(frame) = self.frame.as_mut() {
516            frame.set_resizable(resizable);
517        }
518
519        true
520    }
521
522    /// Whether the window is focused by any seat.
523    #[inline]
524    pub fn has_focus(&self) -> bool {
525        !self.seat_focus.is_empty()
526    }
527
528    /// Whether the IME is allowed.
529    #[inline]
530    pub fn ime_allowed(&self) -> bool {
531        self.ime_allowed
532    }
533
534    /// Get the size of the window.
535    #[inline]
536    pub fn surface_size(&self) -> LogicalSize<u32> {
537        self.size
538    }
539
540    /// Whether the window received initial configure event from the compositor.
541    #[inline]
542    pub fn is_configured(&self) -> bool {
543        self.last_configure.is_some()
544    }
545
546    #[inline]
547    pub fn is_decorated(&mut self) -> bool {
548        let csd = self
549            .last_configure
550            .as_ref()
551            .map(|configure| configure.decoration_mode == DecorationMode::Client)
552            .unwrap_or(false);
553        if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
554            !frame.is_hidden()
555        } else {
556            // Server side decorations.
557            true
558        }
559    }
560
561    /// Get the outer size of the window.
562    #[inline]
563    pub fn outer_size(&self) -> LogicalSize<u32> {
564        self.frame
565            .as_ref()
566            .map(|frame| frame.add_borders(self.size.width, self.size.height).into())
567            .unwrap_or(self.size)
568    }
569
570    /// Register pointer on the top-level.
571    pub fn pointer_entered(&mut self, added: Weak<ThemedPointer<WinitPointerData>>) {
572        self.pointers.push(added);
573        self.reload_cursor_style();
574
575        let mode = self.cursor_grab_mode.user_grab_mode;
576        let _ = self.set_cursor_grab_inner(mode);
577    }
578
579    /// Pointer has left the top-level.
580    pub fn pointer_left(&mut self, removed: Weak<ThemedPointer<WinitPointerData>>) {
581        let mut new_pointers = Vec::new();
582        for pointer in self.pointers.drain(..) {
583            if let Some(pointer) = pointer.upgrade() {
584                if pointer.pointer() != removed.upgrade().unwrap().pointer() {
585                    new_pointers.push(Arc::downgrade(&pointer));
586                }
587            }
588        }
589
590        self.pointers = new_pointers;
591    }
592
593    /// Refresh the decorations frame if it's present returning whether the client should redraw.
594    pub fn refresh_frame(&mut self) -> bool {
595        if let Some(frame) = self.frame.as_mut() {
596            if !frame.is_hidden() && frame.is_dirty() {
597                return frame.draw();
598            }
599        }
600
601        false
602    }
603
604    /// Reload the cursor style on the given window.
605    pub fn reload_cursor_style(&mut self) {
606        if self.cursor_visible {
607            match &self.selected_cursor {
608                SelectedCursor::Named(icon) => self.set_cursor(*icon),
609                SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
610            }
611        } else {
612            self.set_cursor_visible(self.cursor_visible);
613        }
614    }
615
616    /// Reissue the transparency hint to the compositor.
617    pub fn reload_transparency_hint(&self) {
618        let surface = self.window.wl_surface();
619
620        if self.transparent {
621            surface.set_opaque_region(None);
622        } else if let Ok(region) = Region::new(&*self.compositor) {
623            region.add(0, 0, i32::MAX, i32::MAX);
624            surface.set_opaque_region(Some(region.wl_region()));
625        } else {
626            warn!("Failed to mark window opaque.");
627        }
628    }
629
630    /// Try to resize the window when the user can do so.
631    pub fn request_surface_size(&mut self, surface_size: Size) -> PhysicalSize<u32> {
632        if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) {
633            self.resize(surface_size.to_logical(self.scale_factor()))
634        }
635
636        logical_to_physical_rounded(self.surface_size(), self.scale_factor())
637    }
638
639    /// Resize the window to the new surface size.
640    fn resize(&mut self, surface_size: LogicalSize<u32>) {
641        self.size = surface_size;
642
643        // Update the stateless size.
644        if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
645            self.stateless_size = surface_size;
646        }
647
648        // Update the inner frame.
649        let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
650            // Resize only visible frame.
651            if !frame.is_hidden() {
652                frame.resize(
653                    NonZeroU32::new(self.size.width).unwrap(),
654                    NonZeroU32::new(self.size.height).unwrap(),
655                );
656            }
657
658            (frame.location(), frame.add_borders(self.size.width, self.size.height).into())
659        } else {
660            ((0, 0), self.size)
661        };
662
663        // Reload the hint.
664        self.reload_transparency_hint();
665
666        // Set the window geometry.
667        self.window.xdg_surface().set_window_geometry(
668            x,
669            y,
670            outer_size.width as i32,
671            outer_size.height as i32,
672        );
673
674        // Update the target viewport, this is used if and only if fractional scaling is in use.
675        if let Some(viewport) = self.viewport.as_ref() {
676            // Set surface size without the borders.
677            viewport.set_destination(self.size.width as _, self.size.height as _);
678        }
679    }
680
681    /// Get the scale factor of the window.
682    #[inline]
683    pub fn scale_factor(&self) -> f64 {
684        self.scale_factor
685    }
686
687    /// Set the cursor icon.
688    pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
689        self.selected_cursor = SelectedCursor::Named(cursor_icon);
690
691        if !self.cursor_visible {
692            return;
693        }
694
695        self.apply_on_pointer(|pointer, _| {
696            if pointer.set_cursor(&self.connection, cursor_icon).is_err() {
697                warn!("Failed to set cursor to {:?}", cursor_icon);
698            }
699        })
700    }
701
702    /// Set the custom cursor icon.
703    pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
704        let cursor = match cursor {
705            RootCustomCursor { inner: PlatformCustomCursor::Wayland(cursor) } => cursor.0,
706            #[cfg(x11_platform)]
707            RootCustomCursor { inner: PlatformCustomCursor::X(_) } => {
708                tracing::error!("passed a X11 cursor to Wayland backend");
709                return;
710            },
711        };
712
713        let cursor = {
714            let mut pool = self.custom_cursor_pool.lock().unwrap();
715            CustomCursor::new(&mut pool, &cursor)
716        };
717
718        if self.cursor_visible {
719            self.apply_custom_cursor(&cursor);
720        }
721
722        self.selected_cursor = SelectedCursor::Custom(cursor);
723    }
724
725    fn apply_custom_cursor(&self, cursor: &CustomCursor) {
726        self.apply_on_pointer(|pointer, _| {
727            let surface = pointer.surface();
728
729            let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
730
731            surface.set_buffer_scale(scale);
732            surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
733            if surface.version() >= 4 {
734                surface.damage_buffer(0, 0, cursor.w, cursor.h);
735            } else {
736                surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
737            }
738            surface.commit();
739
740            let serial = pointer
741                .pointer()
742                .data::<WinitPointerData>()
743                .and_then(|data| data.pointer_data().latest_enter_serial())
744                .unwrap();
745
746            pointer.pointer().set_cursor(
747                serial,
748                Some(surface),
749                cursor.hotspot_x / scale,
750                cursor.hotspot_y / scale,
751            );
752        });
753    }
754
755    /// Set maximum inner window size.
756    pub fn set_min_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
757        // Ensure that the window has the right minimum size.
758        let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
759        size.width = size.width.max(MIN_WINDOW_SIZE.width);
760        size.height = size.height.max(MIN_WINDOW_SIZE.height);
761
762        // Add the borders.
763        let size = self
764            .frame
765            .as_ref()
766            .map(|frame| frame.add_borders(size.width, size.height).into())
767            .unwrap_or(size);
768
769        self.min_surface_size = size;
770        self.window.set_min_size(Some(size.into()));
771    }
772
773    /// Set maximum inner window size.
774    pub fn set_max_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
775        let size = size.map(|size| {
776            self.frame
777                .as_ref()
778                .map(|frame| frame.add_borders(size.width, size.height).into())
779                .unwrap_or(size)
780        });
781
782        self.max_surface_size = size;
783        self.window.set_max_size(size.map(Into::into));
784    }
785
786    /// Set the CSD theme.
787    pub fn set_theme(&mut self, theme: Option<Theme>) {
788        self.theme = theme;
789        #[cfg(feature = "sctk-adwaita")]
790        if let Some(frame) = self.frame.as_mut() {
791            frame.set_config(into_sctk_adwaita_config(theme))
792        }
793    }
794
795    /// The current theme for CSD decorations.
796    #[inline]
797    pub fn theme(&self) -> Option<Theme> {
798        self.theme
799    }
800
801    /// Set the cursor grabbing state on the top-level.
802    pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), RequestError> {
803        if self.cursor_grab_mode.user_grab_mode == mode {
804            return Ok(());
805        }
806
807        self.set_cursor_grab_inner(mode)?;
808        // Update user grab on success.
809        self.cursor_grab_mode.user_grab_mode = mode;
810        Ok(())
811    }
812
813    /// Reload the hints for minimum and maximum sizes.
814    pub fn reload_min_max_hints(&mut self) {
815        self.set_min_surface_size(Some(self.min_surface_size));
816        self.set_max_surface_size(self.max_surface_size);
817    }
818
819    /// Set the grabbing state on the surface.
820    fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), RequestError> {
821        let pointer_constraints = match self.pointer_constraints.as_ref() {
822            Some(pointer_constraints) => pointer_constraints,
823            None if mode == CursorGrabMode::None => return Ok(()),
824            None => {
825                return Err(
826                    NotSupportedError::new("zwp_pointer_constraints is not available").into()
827                )
828            },
829        };
830
831        // Replace the current mode.
832        let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
833
834        match old_mode {
835            CursorGrabMode::None => (),
836            CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
837                data.unconfine_pointer();
838            }),
839            CursorGrabMode::Locked => {
840                self.apply_on_pointer(|_, data| data.unlock_pointer());
841            },
842        }
843
844        let surface = self.window.wl_surface();
845        match mode {
846            CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
847                let pointer = pointer.pointer();
848                data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
849            }),
850            CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
851                let pointer = pointer.pointer();
852                data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
853            }),
854            CursorGrabMode::None => {
855                // Current lock/confine was already removed.
856            },
857        }
858
859        Ok(())
860    }
861
862    pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
863        // TODO(kchibisov) handle touch serials.
864        self.apply_on_pointer(|_, data| {
865            let serial = data.latest_button_serial();
866            let seat = data.seat();
867            self.window.show_window_menu(seat, serial, position.into());
868        });
869    }
870
871    /// Set the position of the cursor.
872    pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), RequestError> {
873        if self.pointer_constraints.is_none() {
874            return Err(NotSupportedError::new("zwp_pointer_constraints is not available").into());
875        }
876
877        // Position can be set only for locked cursor.
878        if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
879            return Err(NotSupportedError::new(
880                "cursor position could only be changed for locked pointer",
881            )
882            .into());
883        }
884
885        self.apply_on_pointer(|_, data| {
886            data.set_locked_cursor_position(position.x, position.y);
887        });
888
889        Ok(())
890    }
891
892    /// Set the visibility state of the cursor.
893    pub fn set_cursor_visible(&mut self, cursor_visible: bool) {
894        self.cursor_visible = cursor_visible;
895
896        if self.cursor_visible {
897            match &self.selected_cursor {
898                SelectedCursor::Named(icon) => self.set_cursor(*icon),
899                SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
900            }
901        } else {
902            for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
903                let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();
904
905                pointer.pointer().set_cursor(latest_enter_serial, None, 0, 0);
906            }
907        }
908    }
909
910    /// Whether show or hide client side decorations.
911    #[inline]
912    pub fn set_decorate(&mut self, decorate: bool) {
913        if decorate == self.decorate {
914            return;
915        }
916
917        self.decorate = decorate;
918
919        match self.last_configure.as_ref().map(|configure| configure.decoration_mode) {
920            Some(DecorationMode::Server) if !self.decorate => {
921                // To disable decorations we should request client and hide the frame.
922                self.window.request_decoration_mode(Some(DecorationMode::Client))
923            },
924            _ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)),
925            _ => (),
926        }
927
928        if let Some(frame) = self.frame.as_mut() {
929            frame.set_hidden(!decorate);
930            // Force the resize.
931            self.resize(self.size);
932        }
933    }
934
935    /// Add seat focus for the window.
936    #[inline]
937    pub fn add_seat_focus(&mut self, seat: ObjectId) {
938        self.seat_focus.insert(seat);
939    }
940
941    /// Remove seat focus from the window.
942    #[inline]
943    pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
944        self.seat_focus.remove(seat);
945    }
946
947    /// Returns `true` if the requested state was applied.
948    pub fn set_ime_allowed(&mut self, allowed: bool) -> bool {
949        self.ime_allowed = allowed;
950
951        let mut applied = false;
952        for text_input in &self.text_inputs {
953            applied = true;
954            if allowed {
955                text_input.enable();
956                text_input.set_content_type_by_purpose(self.ime_purpose);
957            } else {
958                text_input.disable();
959            }
960            text_input.commit();
961        }
962
963        applied
964    }
965
966    /// Set the IME position.
967    pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
968        // FIXME: This won't fly unless user will have a way to request IME window per seat, since
969        // the ime windows will be overlapping, but winit doesn't expose API to specify for
970        // which seat we're setting IME position.
971        let (x, y) = (position.x as i32, position.y as i32);
972        let (width, height) = (size.width as i32, size.height as i32);
973        for text_input in self.text_inputs.iter() {
974            text_input.set_cursor_rectangle(x, y, width, height);
975            text_input.commit();
976        }
977    }
978
979    /// Set the IME purpose.
980    pub fn set_ime_purpose(&mut self, purpose: ImePurpose) {
981        self.ime_purpose = purpose;
982
983        for text_input in &self.text_inputs {
984            text_input.set_content_type_by_purpose(purpose);
985            text_input.commit();
986        }
987    }
988
989    /// Get the IME purpose.
990    pub fn ime_purpose(&self) -> ImePurpose {
991        self.ime_purpose
992    }
993
994    /// Set the scale factor for the given window.
995    #[inline]
996    pub fn set_scale_factor(&mut self, scale_factor: f64) {
997        self.scale_factor = scale_factor;
998
999        // NOTE: When fractional scaling is not used update the buffer scale.
1000        if self.fractional_scale.is_none() {
1001            let _ = self.window.set_buffer_scale(self.scale_factor as _);
1002        }
1003
1004        if let Some(frame) = self.frame.as_mut() {
1005            frame.set_scaling_factor(scale_factor);
1006        }
1007    }
1008
1009    /// Make window background blurred
1010    #[inline]
1011    pub fn set_blur(&mut self, blurred: bool) {
1012        if blurred && self.blur.is_none() {
1013            if let Some(blur_manager) = self.blur_manager.as_ref() {
1014                let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
1015                blur.commit();
1016                self.blur = Some(blur);
1017            } else {
1018                info!("Blur manager unavailable, unable to change blur")
1019            }
1020        } else if !blurred && self.blur.is_some() {
1021            self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface());
1022            self.blur.take().unwrap().release();
1023        }
1024    }
1025
1026    /// Set the window title to a new value.
1027    ///
1028    /// This will automatically truncate the title to something meaningful.
1029    pub fn set_title(&mut self, mut title: String) {
1030        // Truncate the title to at most 1024 bytes, so that it does not blow up the protocol
1031        // messages
1032        if title.len() > 1024 {
1033            let mut new_len = 1024;
1034            while !title.is_char_boundary(new_len) {
1035                new_len -= 1;
1036            }
1037            title.truncate(new_len);
1038        }
1039
1040        // Update the CSD title.
1041        if let Some(frame) = self.frame.as_mut() {
1042            frame.set_title(&title);
1043        }
1044
1045        self.window.set_title(&title);
1046        self.title = title;
1047    }
1048
1049    /// Mark the window as transparent.
1050    #[inline]
1051    pub fn set_transparent(&mut self, transparent: bool) {
1052        self.transparent = transparent;
1053        self.reload_transparency_hint();
1054    }
1055
1056    /// Register text input on the top-level.
1057    #[inline]
1058    pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) {
1059        if !self.text_inputs.iter().any(|t| t == text_input) {
1060            self.text_inputs.push(text_input.clone());
1061        }
1062    }
1063
1064    /// The text input left the top-level.
1065    #[inline]
1066    pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) {
1067        if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) {
1068            self.text_inputs.remove(position);
1069        }
1070    }
1071
1072    /// Get the cached title.
1073    #[inline]
1074    pub fn title(&self) -> &str {
1075        &self.title
1076    }
1077}
1078
1079impl Drop for WindowState {
1080    fn drop(&mut self) {
1081        if let Some(blur) = self.blur.take() {
1082            blur.release();
1083        }
1084
1085        if let Some(fs) = self.fractional_scale.take() {
1086            fs.destroy();
1087        }
1088
1089        if let Some(viewport) = self.viewport.take() {
1090            viewport.destroy();
1091        }
1092
1093        // NOTE: the wl_surface used by the window is being cleaned up when
1094        // dropping SCTK `Window`.
1095    }
1096}
1097
1098/// The state of the cursor grabs.
1099#[derive(Clone, Copy)]
1100struct GrabState {
1101    /// The grab mode requested by the user.
1102    user_grab_mode: CursorGrabMode,
1103
1104    /// The current grab mode.
1105    current_grab_mode: CursorGrabMode,
1106}
1107
1108impl GrabState {
1109    fn new() -> Self {
1110        Self { user_grab_mode: CursorGrabMode::None, current_grab_mode: CursorGrabMode::None }
1111    }
1112}
1113
1114/// The state of the frame callback.
1115#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1116pub enum FrameCallbackState {
1117    /// No frame callback was requested.
1118    #[default]
1119    None,
1120    /// The frame callback was requested, but not yet arrived, the redraw events are throttled.
1121    Requested,
1122    /// The callback was marked as done, and user could receive redraw requested
1123    Received,
1124}
1125
1126impl From<ResizeDirection> for XdgResizeEdge {
1127    fn from(value: ResizeDirection) -> Self {
1128        match value {
1129            ResizeDirection::North => XdgResizeEdge::Top,
1130            ResizeDirection::West => XdgResizeEdge::Left,
1131            ResizeDirection::NorthWest => XdgResizeEdge::TopLeft,
1132            ResizeDirection::NorthEast => XdgResizeEdge::TopRight,
1133            ResizeDirection::East => XdgResizeEdge::Right,
1134            ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft,
1135            ResizeDirection::SouthEast => XdgResizeEdge::BottomRight,
1136            ResizeDirection::South => XdgResizeEdge::Bottom,
1137        }
1138    }
1139}
1140
1141// NOTE: Rust doesn't allow `From<Option<Theme>>`.
1142#[cfg(feature = "sctk-adwaita")]
1143fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
1144    match theme {
1145        Some(Theme::Light) => sctk_adwaita::FrameConfig::light(),
1146        Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(),
1147        None => sctk_adwaita::FrameConfig::auto(),
1148    }
1149}