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    resize_increments: Option<LogicalSize<u32>>,
131
132    /// The size of the window when no states were applied to it. The primary use for it
133    /// is to fallback to original window size, before it was maximized, if the compositor
134    /// sends `None` for the new size in the configure.
135    stateless_size: LogicalSize<u32>,
136
137    /// Initial window size provided by the user. Removed on the first
138    /// configure.
139    initial_size: Option<Size>,
140
141    /// The state of the frame callback.
142    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    /// Whether the client side decorations have pending move operations.
150    ///
151    /// The value is the serial of the event triggered moved.
152    has_pending_move: Option<u32>,
153
154    /// The underlying SCTK window.
155    pub window: Window,
156
157    // NOTE: The spec says that destroying parent(`window` in our case), will unmap the
158    // subsurfaces. Thus to achieve atomic unmap of the client, drop the decorations
159    // frame after the `window` is dropped. To achieve that we rely on rust's struct
160    // field drop order guarantees.
161    /// The window frame, which is created from the configure request.
162    frame: Option<WinitFrame>,
163}
164
165impl WindowState {
166    /// Create new window state.
167    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    /// Apply closure on the given pointer.
227    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    /// Get the current state of the frame callback.
238    pub fn frame_callback_state(&self) -> FrameCallbackState {
239        self.frame_callback_state
240    }
241
242    /// The frame callback was received, but not yet sent to the user.
243    pub fn frame_callback_received(&mut self) {
244        self.frame_callback_state = FrameCallbackState::Received;
245    }
246
247    /// Reset the frame callbacks state.
248    pub fn frame_callback_reset(&mut self) {
249        self.frame_callback_state = FrameCallbackState::None;
250    }
251
252    /// Request a frame callback if we don't have one for this window in flight.
253    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        // NOTE: when using fractional scaling or wl_compositor@v6 the scaling
271        // should be delivered before the first configure, thus apply it to
272        // properly scale the physical sizes provided by the users.
273        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                    // Hide the frame if we were asked to not decorate.
297                    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            // Drop the frame for server side decorations to save resources.
307            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            // Configure the window states.
314            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        // Apply configure bounds only when compositor let the user decide what size to pick.
335        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        // Apply size increments.
346        //
347        // We conditionally apply increments to avoid conflicts with the compositor's layout rules:
348        // 1. If the window is floating (constrain == true), we snap to increments to ensure the
349        //    app's grid alignment.
350        // 2. If the user is interactively resizing (is_resizing), we snap the size to provide
351        //    feedback.
352        //
353        // However, we MUST NOT snap if the compositor enforces a specific size (constrain == false,
354        // or states like Maximized/Tiled). Snapping in these cases (e.g. corner tiling) would
355        // shrink the window below the allocated area, creating visible gaps between valid
356        // windows or screen edges.
357        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                // We use min size as a base size for the increments, similar to how X11 does it.
364                //
365                // This ensures that we can always reach the min size and the increments are
366                // calculated from it.
367                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            // NOTE: `None` is present for the initial configure, thus we must always resize.
392            .unwrap_or(true);
393
394        // NOTE: Set the configure before doing a resize, since we query it during it.
395        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    /// Compute the bounds for the inner size of the surface.
406    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    /// Start interacting drag resize.
432    pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
433        let xdg_toplevel = self.window.xdg_toplevel();
434
435        // TODO(kchibisov) handle touch serials.
436        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    /// Start the window drag.
446    pub fn drag_window(&self) -> Result<(), ExternalError> {
447        let xdg_toplevel = self.window.xdg_toplevel();
448        // TODO(kchibisov) handle touch serials.
449        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    /// Tells whether the window should be closed.
459    #[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    // Move the point over decorations.
505    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        // Take the serial if we had any, so it doesn't stick around.
514        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 we have a cursor change, that means that cursor is over the decorations,
519            // so try to apply move.
520            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    /// Get the stored resizable state.
532    #[inline]
533    pub fn resizable(&self) -> bool {
534        self.resizable
535    }
536
537    /// Set the resizable state on the window.
538    ///
539    /// Returns `true` when the state was applied.
540    #[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            // Restore min/max sizes of the window.
549            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        // Reload the state on the frame as well.
556        if let Some(frame) = self.frame.as_mut() {
557            frame.set_resizable(resizable);
558        }
559
560        true
561    }
562
563    /// Whether the window is focused by any seat.
564    #[inline]
565    pub fn has_focus(&self) -> bool {
566        !self.seat_focus.is_empty()
567    }
568
569    /// Whether the IME is allowed.
570    #[inline]
571    pub fn ime_allowed(&self) -> bool {
572        self.ime_allowed
573    }
574
575    /// Get the size of the window.
576    #[inline]
577    pub fn inner_size(&self) -> LogicalSize<u32> {
578        self.size
579    }
580
581    /// Whether the window received initial configure event from the compositor.
582    #[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            // Server side decorations.
598            true
599        }
600    }
601
602    /// Get the outer size of the window.
603    #[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    /// Register pointer on the top-level.
612    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    /// Pointer has left the top-level.
621    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    /// Refresh the decorations frame if it's present returning whether the client should redraw.
635    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    /// Reload the cursor style on the given window.
646    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    /// Reissue the transparency hint to the compositor.
658    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    /// Try to resize the window when the user can do so.
672    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    /// Resize the window to the new inner size.
681    fn resize(&mut self, inner_size: LogicalSize<u32>) {
682        self.size = inner_size;
683
684        // Update the stateless size.
685        if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
686            self.stateless_size = inner_size;
687        }
688
689        // Update the inner frame.
690        let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
691            // Resize only visible frame.
692            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        // Reload the hint.
705        self.reload_transparency_hint();
706
707        // Set the window geometry.
708        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        // Update the target viewport, this is used if and only if fractional scaling is in use.
716        if let Some(viewport) = self.viewport.as_ref() {
717            // Set inner size without the borders.
718            viewport.set_destination(self.size.width as _, self.size.height as _);
719        }
720    }
721
722    /// Get the scale factor of the window.
723    #[inline]
724    pub fn scale_factor(&self) -> f64 {
725        self.scale_factor
726    }
727
728    /// Set the cursor icon.
729    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    /// Set the custom cursor icon.
744    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    /// Set the resize increments of the window.
767    pub fn set_resize_increments(&mut self, increments: Option<LogicalSize<u32>>) {
768        self.resize_increments = increments;
769        // NOTE: We don't update the window size here, because it will be done on the next resize
770        // or configure event.
771    }
772
773    /// Get the resize increments of the window.
774    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    /// Set maximum inner window size.
815    pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
816        // Ensure that the window has the right minimum size.
817        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        // Add the borders.
822        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    /// Set maximum inner window size.
833    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    /// Set the CSD theme.
846    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    /// The current theme for CSD decorations.
855    #[inline]
856    pub fn theme(&self) -> Option<Theme> {
857        self.theme
858    }
859
860    /// Set the cursor grabbing state on the top-level.
861    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        // Update user grab on success.
868        self.cursor_grab_mode.user_grab_mode = mode;
869        Ok(())
870    }
871
872    /// Reload the hints for minimum and maximum sizes.
873    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    /// Set the grabbing state on the surface.
879    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        // In case we haven't unset the old mode, it means that we don't have a cursor above
902        // the window, thus just wait for it to re-appear.
903        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                // Current lock/confine was already removed.
922                set_mode = true;
923            },
924        }
925
926        // Replace the current grab mode after we've ensure that it got updated.
927        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        // TODO(kchibisov) handle touch serials.
936        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    /// Set the position of the cursor.
944    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        // Position can be set only for locked cursor.
950        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    /// Set the visibility state of the cursor.
964    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    /// Whether show or hide client side decorations.
982    #[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                // To disable decorations we should request client and hide the frame.
993                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            // Force the resize.
1002            self.resize(self.size);
1003        }
1004    }
1005
1006    /// Add seat focus for the window.
1007    #[inline]
1008    pub fn add_seat_focus(&mut self, seat: ObjectId) {
1009        self.seat_focus.insert(seat);
1010    }
1011
1012    /// Remove seat focus from the window.
1013    #[inline]
1014    pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
1015        self.seat_focus.remove(seat);
1016    }
1017
1018    /// Returns `true` if the requested state was applied.
1019    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    /// Set the IME position.
1038    pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
1039        // FIXME: This won't fly unless user will have a way to request IME window per seat, since
1040        // the ime windows will be overlapping, but winit doesn't expose API to specify for
1041        // which seat we're setting IME position.
1042        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    /// Set the IME purpose.
1051    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    /// Get the IME purpose.
1061    pub fn ime_purpose(&self) -> ImePurpose {
1062        self.ime_purpose
1063    }
1064
1065    /// Set the scale factor for the given window.
1066    #[inline]
1067    pub fn set_scale_factor(&mut self, scale_factor: f64) {
1068        self.scale_factor = scale_factor;
1069
1070        // NOTE: When fractional scaling is not used update the buffer scale.
1071        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    /// Make window background blurred
1081    #[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    /// Set the window title to a new value.
1098    ///
1099    /// This will automatically truncate the title to something meaningful.
1100    pub fn set_title(&mut self, mut title: String) {
1101        // Truncate the title to at most 1024 bytes, so that it does not blow up the protocol
1102        // messages
1103        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        // Update the CSD title.
1112        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    /// Mark the window as transparent.
1121    #[inline]
1122    pub fn set_transparent(&mut self, transparent: bool) {
1123        self.transparent = transparent;
1124        self.reload_transparency_hint();
1125    }
1126
1127    /// Register text input on the top-level.
1128    #[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    /// The text input left the top-level.
1136    #[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    /// Get the cached title.
1144    #[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        // NOTE: the wl_surface used by the window is being cleaned up when
1165        // dropping SCTK `Window`.
1166    }
1167}
1168
1169/// The state of the cursor grabs.
1170#[derive(Clone, Copy)]
1171struct GrabState {
1172    /// The grab mode requested by the user.
1173    user_grab_mode: CursorGrabMode,
1174
1175    /// The current grab mode.
1176    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/// The state of the frame callback.
1186#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1187pub enum FrameCallbackState {
1188    /// No frame callback was requested.
1189    #[default]
1190    None,
1191    /// The frame callback was requested, but not yet arrived, the redraw events are throttled.
1192    Requested,
1193    /// The callback was marked as done, and user could receive redraw requested
1194    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// NOTE: Rust doesn't allow `From<Option<Theme>>`.
1213#[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}