rio_window/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, 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: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
226        &self,
227        callback: F,
228    ) {
229        self.pointers
230            .iter()
231            .filter_map(Weak::upgrade)
232            .for_each(|pointer| {
233                let data = pointer.pointer().winit_data();
234                callback(pointer.as_ref(), data);
235            })
236    }
237
238    /// Get the current state of the frame callback.
239    pub fn frame_callback_state(&self) -> FrameCallbackState {
240        self.frame_callback_state
241    }
242
243    /// The frame callback was received, but not yet sent to the user.
244    pub fn frame_callback_received(&mut self) {
245        self.frame_callback_state = FrameCallbackState::Received;
246    }
247
248    /// Reset the frame callbacks state.
249    pub fn frame_callback_reset(&mut self) {
250        self.frame_callback_state = FrameCallbackState::None;
251    }
252
253    /// Request a frame callback if we don't have one for this window in flight.
254    pub fn request_frame_callback(&mut self) {
255        let surface = self.window.wl_surface();
256        match self.frame_callback_state {
257            FrameCallbackState::None | FrameCallbackState::Received => {
258                self.frame_callback_state = FrameCallbackState::Requested;
259                surface.frame(&self.queue_handle, surface.clone());
260            }
261            FrameCallbackState::Requested => (),
262        }
263    }
264
265    pub fn configure(
266        &mut self,
267        configure: WindowConfigure,
268        shm: &Shm,
269        subcompositor: &Option<Arc<SubcompositorState>>,
270    ) -> bool {
271        // NOTE: when using fractional scaling or wl_compositor@v6 the scaling
272        // should be delivered before the first configure, thus apply it to
273        // properly scale the physical sizes provided by the users.
274        if let Some(initial_size) = self.initial_size.take() {
275            self.size = initial_size.to_logical(self.scale_factor());
276            self.stateless_size = self.size;
277        }
278
279        if let Some(subcompositor) = subcompositor.as_ref().filter(|_| {
280            configure.decoration_mode == DecorationMode::Client
281                && self.frame.is_none()
282                && !self.csd_fails
283        }) {
284            match WinitFrame::new(
285                &self.window,
286                shm,
287                #[cfg(feature = "sctk-adwaita")]
288                self.compositor.clone(),
289                subcompositor.clone(),
290                self.queue_handle.clone(),
291                #[cfg(feature = "sctk-adwaita")]
292                into_sctk_adwaita_config(self.theme),
293            ) {
294                Ok(mut frame) => {
295                    frame.set_title(&self.title);
296                    frame.set_scaling_factor(self.scale_factor);
297                    // Hide the frame if we were asked to not decorate.
298                    frame.set_hidden(!self.decorate);
299                    self.frame = Some(frame);
300                }
301                Err(err) => {
302                    warn!("Failed to create client side decorations frame: {err}");
303                    self.csd_fails = true;
304                }
305            }
306        } else if configure.decoration_mode == DecorationMode::Server {
307            // Drop the frame for server side decorations to save resources.
308            self.frame = None;
309        }
310
311        let stateless = Self::is_stateless(&configure);
312
313        let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() {
314            // Configure the window states.
315            frame.update_state(configure.state);
316
317            match configure.new_size {
318                (Some(width), Some(height)) => {
319                    let (width, height) = frame.subtract_borders(width, height);
320                    let width = width.map(|w| w.get()).unwrap_or(1);
321                    let height = height.map(|h| h.get()).unwrap_or(1);
322                    ((width, height).into(), false)
323                }
324                (..) if stateless => (self.stateless_size, true),
325                _ => (self.size, true),
326            }
327        } else {
328            match configure.new_size {
329                (Some(width), Some(height)) => {
330                    ((width.get(), height.get()).into(), false)
331                }
332                _ if stateless => (self.stateless_size, true),
333                _ => (self.size, true),
334            }
335        };
336
337        // Apply configure bounds only when compositor let the user decide what size to pick.
338        if constrain {
339            let bounds = self.inner_size_bounds(&configure);
340            new_size.width = bounds
341                .0
342                .map(|bound_w| new_size.width.min(bound_w.get()))
343                .unwrap_or(new_size.width);
344            new_size.height = bounds
345                .1
346                .map(|bound_h| new_size.height.min(bound_h.get()))
347                .unwrap_or(new_size.height);
348        }
349
350        let new_state = configure.state;
351        let old_state = self
352            .last_configure
353            .as_ref()
354            .map(|configure| configure.state);
355
356        let state_change_requires_resize = old_state
357            .map(|old_state| {
358                !old_state
359                    .symmetric_difference(new_state)
360                    .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
361                    .is_empty()
362            })
363            // NOTE: `None` is present for the initial configure, thus we must always resize.
364            .unwrap_or(true);
365
366        // NOTE: Set the configure before doing a resize, since we query it during it.
367        self.last_configure = Some(configure);
368
369        if state_change_requires_resize || new_size != self.inner_size() {
370            self.resize(new_size);
371            true
372        } else {
373            false
374        }
375    }
376
377    /// Compute the bounds for the inner size of the surface.
378    fn inner_size_bounds(
379        &self,
380        configure: &WindowConfigure,
381    ) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
382        let configure_bounds = match configure.suggested_bounds {
383            Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)),
384            None => (None, None),
385        };
386
387        if let Some(frame) = self.frame.as_ref() {
388            let (width, height) = frame.subtract_borders(
389                configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()),
390                configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()),
391            );
392            (
393                configure_bounds.0.and(width),
394                configure_bounds.1.and(height),
395            )
396        } else {
397            configure_bounds
398        }
399    }
400
401    #[inline]
402    fn is_stateless(configure: &WindowConfigure) -> bool {
403        !(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled())
404    }
405
406    /// Start interacting drag resize.
407    pub fn drag_resize_window(
408        &self,
409        direction: ResizeDirection,
410    ) -> Result<(), ExternalError> {
411        let xdg_toplevel = self.window.xdg_toplevel();
412
413        // TODO(kchibisov) handle touch serials.
414        self.apply_on_pointer(|_, data| {
415            let serial = data.latest_button_serial();
416            let seat = data.seat();
417            xdg_toplevel.resize(seat, serial, direction.into());
418        });
419
420        Ok(())
421    }
422
423    /// Start the window drag.
424    pub fn drag_window(&self) -> Result<(), ExternalError> {
425        let xdg_toplevel = self.window.xdg_toplevel();
426        // TODO(kchibisov) handle touch serials.
427        self.apply_on_pointer(|_, data| {
428            let serial = data.latest_button_serial();
429            let seat = data.seat();
430            xdg_toplevel._move(seat, serial);
431        });
432
433        Ok(())
434    }
435
436    /// Tells whether the window should be closed.
437    #[allow(clippy::too_many_arguments)]
438    pub fn frame_click(
439        &mut self,
440        click: FrameClick,
441        pressed: bool,
442        seat: &WlSeat,
443        serial: u32,
444        timestamp: Duration,
445        window_id: WindowId,
446        updates: &mut Vec<WindowCompositorUpdate>,
447    ) -> Option<bool> {
448        match self.frame.as_mut()?.on_click(timestamp, click, pressed)? {
449            FrameAction::Minimize => self.window.set_minimized(),
450            FrameAction::Maximize => self.window.set_maximized(),
451            FrameAction::UnMaximize => self.window.unset_maximized(),
452            FrameAction::Close => WinitState::queue_close(updates, window_id),
453            FrameAction::Move => self.has_pending_move = Some(serial),
454            FrameAction::Resize(edge) => {
455                let edge = match edge {
456                    ResizeEdge::None => XdgResizeEdge::None,
457                    ResizeEdge::Top => XdgResizeEdge::Top,
458                    ResizeEdge::Bottom => XdgResizeEdge::Bottom,
459                    ResizeEdge::Left => XdgResizeEdge::Left,
460                    ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
461                    ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
462                    ResizeEdge::Right => XdgResizeEdge::Right,
463                    ResizeEdge::TopRight => XdgResizeEdge::TopRight,
464                    ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
465                    _ => return None,
466                };
467                self.window.resize(seat, serial, edge);
468            }
469            FrameAction::ShowMenu(x, y) => {
470                self.window.show_window_menu(seat, serial, (x, y))
471            }
472            _ => (),
473        };
474
475        Some(false)
476    }
477
478    pub fn frame_point_left(&mut self) {
479        if let Some(frame) = self.frame.as_mut() {
480            frame.click_point_left();
481        }
482    }
483
484    // Move the point over decorations.
485    pub fn frame_point_moved(
486        &mut self,
487        seat: &WlSeat,
488        surface: &WlSurface,
489        timestamp: Duration,
490        x: f64,
491        y: f64,
492    ) -> Option<CursorIcon> {
493        // Take the serial if we had any, so it doesn't stick around.
494        let serial = self.has_pending_move.take();
495
496        if let Some(frame) = self.frame.as_mut() {
497            let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y);
498            // If we have a cursor change, that means that cursor is over the decorations,
499            // so try to apply move.
500            if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
501                self.window.move_(seat, serial);
502                None
503            } else {
504                cursor
505            }
506        } else {
507            None
508        }
509    }
510
511    /// Get the stored resizable state.
512    #[inline]
513    pub fn resizable(&self) -> bool {
514        self.resizable
515    }
516
517    /// Set the resizable state on the window.
518    ///
519    /// Returns `true` when the state was applied.
520    #[inline]
521    pub fn set_resizable(&mut self, resizable: bool) -> bool {
522        if self.resizable == resizable {
523            return false;
524        }
525
526        self.resizable = resizable;
527        if resizable {
528            // Restore min/max sizes of the window.
529            self.reload_min_max_hints();
530        } else {
531            self.set_min_inner_size(Some(self.size));
532            self.set_max_inner_size(Some(self.size));
533        }
534
535        // Reload the state on the frame as well.
536        if let Some(frame) = self.frame.as_mut() {
537            frame.set_resizable(resizable);
538        }
539
540        true
541    }
542
543    /// Whether the window is focused by any seat.
544    #[inline]
545    pub fn has_focus(&self) -> bool {
546        !self.seat_focus.is_empty()
547    }
548
549    /// Whether the IME is allowed.
550    #[inline]
551    pub fn ime_allowed(&self) -> bool {
552        self.ime_allowed
553    }
554
555    /// Get the size of the window.
556    #[inline]
557    pub fn inner_size(&self) -> LogicalSize<u32> {
558        self.size
559    }
560
561    /// Whether the window received initial configure event from the compositor.
562    #[inline]
563    pub fn is_configured(&self) -> bool {
564        self.last_configure.is_some()
565    }
566
567    #[inline]
568    pub fn is_decorated(&mut self) -> bool {
569        let csd = self
570            .last_configure
571            .as_ref()
572            .map(|configure| configure.decoration_mode == DecorationMode::Client)
573            .unwrap_or(false);
574        if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
575            !frame.is_hidden()
576        } else {
577            // Server side decorations.
578            true
579        }
580    }
581
582    /// Get the outer size of the window.
583    #[inline]
584    pub fn outer_size(&self) -> LogicalSize<u32> {
585        self.frame
586            .as_ref()
587            .map(|frame| frame.add_borders(self.size.width, self.size.height).into())
588            .unwrap_or(self.size)
589    }
590
591    /// Register pointer on the top-level.
592    pub fn pointer_entered(&mut self, added: Weak<ThemedPointer<WinitPointerData>>) {
593        self.pointers.push(added);
594        self.reload_cursor_style();
595
596        let mode = self.cursor_grab_mode.user_grab_mode;
597        let _ = self.set_cursor_grab_inner(mode);
598    }
599
600    /// Pointer has left the top-level.
601    pub fn pointer_left(&mut self, removed: Weak<ThemedPointer<WinitPointerData>>) {
602        let mut new_pointers = Vec::new();
603        for pointer in self.pointers.drain(..) {
604            if let Some(pointer) = pointer.upgrade() {
605                if pointer.pointer() != removed.upgrade().unwrap().pointer() {
606                    new_pointers.push(Arc::downgrade(&pointer));
607                }
608            }
609        }
610
611        self.pointers = new_pointers;
612    }
613
614    /// Refresh the decorations frame if it's present returning whether the client should redraw.
615    pub fn refresh_frame(&mut self) -> bool {
616        if let Some(frame) = self.frame.as_mut() {
617            if !frame.is_hidden() && frame.is_dirty() {
618                return frame.draw();
619            }
620        }
621
622        false
623    }
624
625    /// Reload the cursor style on the given window.
626    pub fn reload_cursor_style(&mut self) {
627        if self.cursor_visible {
628            match &self.selected_cursor {
629                SelectedCursor::Named(icon) => self.set_cursor(*icon),
630                SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
631            }
632        } else {
633            self.set_cursor_visible(self.cursor_visible);
634        }
635    }
636
637    /// Reissue the transparency hint to the compositor.
638    pub fn reload_transparency_hint(&self) {
639        let surface = self.window.wl_surface();
640
641        if self.transparent {
642            surface.set_opaque_region(None);
643        } else if let Ok(region) = Region::new(&*self.compositor) {
644            region.add(0, 0, i32::MAX, i32::MAX);
645            surface.set_opaque_region(Some(region.wl_region()));
646        } else {
647            warn!("Failed to mark window opaque.");
648        }
649    }
650
651    /// Try to resize the window when the user can do so.
652    pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize<u32> {
653        if self
654            .last_configure
655            .as_ref()
656            .map(Self::is_stateless)
657            .unwrap_or(true)
658        {
659            self.resize(inner_size.to_logical(self.scale_factor()))
660        }
661
662        logical_to_physical_rounded(self.inner_size(), self.scale_factor())
663    }
664
665    /// Resize the window to the new inner size.
666    fn resize(&mut self, inner_size: LogicalSize<u32>) {
667        self.size = inner_size;
668
669        // Update the stateless size.
670        if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
671            self.stateless_size = inner_size;
672        }
673
674        // Update the inner frame.
675        let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
676            // Resize only visible frame.
677            if !frame.is_hidden() {
678                frame.resize(
679                    NonZeroU32::new(self.size.width).unwrap(),
680                    NonZeroU32::new(self.size.height).unwrap(),
681                );
682            }
683
684            (
685                frame.location(),
686                frame.add_borders(self.size.width, self.size.height).into(),
687            )
688        } else {
689            ((0, 0), self.size)
690        };
691
692        // Reload the hint.
693        self.reload_transparency_hint();
694
695        // Set the window geometry.
696        self.window.xdg_surface().set_window_geometry(
697            x,
698            y,
699            outer_size.width as i32,
700            outer_size.height as i32,
701        );
702
703        // Update the target viewport, this is used if and only if fractional scaling is in use.
704        if let Some(viewport) = self.viewport.as_ref() {
705            // Set inner size without the borders.
706            viewport.set_destination(self.size.width as _, self.size.height as _);
707        }
708    }
709
710    /// Get the scale factor of the window.
711    #[inline]
712    pub fn scale_factor(&self) -> f64 {
713        self.scale_factor
714    }
715
716    /// Set the cursor icon.
717    pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
718        self.selected_cursor = SelectedCursor::Named(cursor_icon);
719
720        if !self.cursor_visible {
721            return;
722        }
723
724        self.apply_on_pointer(|pointer, _| {
725            if pointer.set_cursor(&self.connection, cursor_icon).is_err() {
726                warn!("Failed to set cursor to {:?}", cursor_icon);
727            }
728        })
729    }
730
731    /// Set the custom cursor icon.
732    pub(crate) fn set_custom_cursor(&mut self, cursor: RootCustomCursor) {
733        let cursor = match cursor {
734            RootCustomCursor {
735                inner: PlatformCustomCursor::Wayland(cursor),
736            } => cursor.0,
737            #[cfg(x11_platform)]
738            RootCustomCursor {
739                inner: PlatformCustomCursor::X(_),
740            } => {
741                tracing::error!("passed a X11 cursor to Wayland backend");
742                return;
743            }
744        };
745
746        let cursor = {
747            let mut pool = self.custom_cursor_pool.lock().unwrap();
748            CustomCursor::new(&mut pool, &cursor)
749        };
750
751        if self.cursor_visible {
752            self.apply_custom_cursor(&cursor);
753        }
754
755        self.selected_cursor = SelectedCursor::Custom(cursor);
756    }
757
758    fn apply_custom_cursor(&self, cursor: &CustomCursor) {
759        self.apply_on_pointer(|pointer, _| {
760            let surface = pointer.surface();
761
762            let scale = surface
763                .data::<SurfaceData>()
764                .unwrap()
765                .surface_data()
766                .scale_factor();
767
768            surface.set_buffer_scale(scale);
769            surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
770            if surface.version() >= 4 {
771                surface.damage_buffer(0, 0, cursor.w, cursor.h);
772            } else {
773                surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
774            }
775            surface.commit();
776
777            let serial = pointer
778                .pointer()
779                .data::<WinitPointerData>()
780                .and_then(|data| data.pointer_data().latest_enter_serial())
781                .unwrap();
782
783            pointer.pointer().set_cursor(
784                serial,
785                Some(surface),
786                cursor.hotspot_x / scale,
787                cursor.hotspot_y / scale,
788            );
789        });
790    }
791
792    /// Set maximum inner window size.
793    pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
794        // Ensure that the window has the right minimum size.
795        let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
796        size.width = size.width.max(MIN_WINDOW_SIZE.width);
797        size.height = size.height.max(MIN_WINDOW_SIZE.height);
798
799        // Add the borders.
800        let size = self
801            .frame
802            .as_ref()
803            .map(|frame| frame.add_borders(size.width, size.height).into())
804            .unwrap_or(size);
805
806        self.min_inner_size = size;
807        self.window.set_min_size(Some(size.into()));
808    }
809
810    /// Set maximum inner window size.
811    pub fn set_max_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
812        let size = size.map(|size| {
813            self.frame
814                .as_ref()
815                .map(|frame| frame.add_borders(size.width, size.height).into())
816                .unwrap_or(size)
817        });
818
819        self.max_inner_size = size;
820        self.window.set_max_size(size.map(Into::into));
821    }
822
823    /// Set the CSD theme.
824    pub fn set_theme(&mut self, theme: Option<Theme>) {
825        self.theme = theme;
826        #[cfg(feature = "sctk-adwaita")]
827        if let Some(frame) = self.frame.as_mut() {
828            frame.set_config(into_sctk_adwaita_config(theme))
829        }
830    }
831
832    /// The current theme for CSD decorations.
833    #[inline]
834    pub fn theme(&self) -> Option<Theme> {
835        self.theme
836    }
837
838    /// Set the cursor grabbing state on the top-level.
839    pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
840        if self.cursor_grab_mode.user_grab_mode == mode {
841            return Ok(());
842        }
843
844        self.set_cursor_grab_inner(mode)?;
845        // Update user grab on success.
846        self.cursor_grab_mode.user_grab_mode = mode;
847        Ok(())
848    }
849
850    /// Reload the hints for minimum and maximum sizes.
851    pub fn reload_min_max_hints(&mut self) {
852        self.set_min_inner_size(Some(self.min_inner_size));
853        self.set_max_inner_size(self.max_inner_size);
854    }
855
856    /// Set the grabbing state on the surface.
857    fn set_cursor_grab_inner(
858        &mut self,
859        mode: CursorGrabMode,
860    ) -> Result<(), ExternalError> {
861        let pointer_constraints = match self.pointer_constraints.as_ref() {
862            Some(pointer_constraints) => pointer_constraints,
863            None if mode == CursorGrabMode::None => return Ok(()),
864            None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
865        };
866
867        // Replace the current mode.
868        let old_mode =
869            std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
870
871        match old_mode {
872            CursorGrabMode::None => (),
873            CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
874                data.unconfine_pointer();
875            }),
876            CursorGrabMode::Locked => {
877                self.apply_on_pointer(|_, data| data.unlock_pointer());
878            }
879        }
880
881        let surface = self.window.wl_surface();
882        match mode {
883            CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
884                let pointer = pointer.pointer();
885                data.lock_pointer(
886                    pointer_constraints,
887                    surface,
888                    pointer,
889                    &self.queue_handle,
890                )
891            }),
892            CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
893                let pointer = pointer.pointer();
894                data.confine_pointer(
895                    pointer_constraints,
896                    surface,
897                    pointer,
898                    &self.queue_handle,
899                )
900            }),
901            CursorGrabMode::None => {
902                // Current lock/confine was already removed.
903            }
904        }
905
906        Ok(())
907    }
908
909    pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
910        // TODO(kchibisov) handle touch serials.
911        self.apply_on_pointer(|_, data| {
912            let serial = data.latest_button_serial();
913            let seat = data.seat();
914            self.window.show_window_menu(seat, serial, position.into());
915        });
916    }
917
918    /// Set the position of the cursor.
919    pub fn set_cursor_position(
920        &self,
921        position: LogicalPosition<f64>,
922    ) -> Result<(), ExternalError> {
923        if self.pointer_constraints.is_none() {
924            return Err(ExternalError::NotSupported(NotSupportedError::new()));
925        }
926
927        // Position can be set only for locked cursor.
928        if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
929            return Err(ExternalError::Os(os_error!(
930                crate::platform_impl::OsError::Misc(
931                    "cursor position can be set only for locked cursor."
932                )
933            )));
934        }
935
936        self.apply_on_pointer(|_, data| {
937            data.set_locked_cursor_position(position.x, position.y);
938        });
939
940        Ok(())
941    }
942
943    /// Set the visibility state of the cursor.
944    pub fn set_cursor_visible(&mut self, cursor_visible: bool) {
945        self.cursor_visible = cursor_visible;
946
947        if self.cursor_visible {
948            match &self.selected_cursor {
949                SelectedCursor::Named(icon) => self.set_cursor(*icon),
950                SelectedCursor::Custom(cursor) => self.apply_custom_cursor(cursor),
951            }
952        } else {
953            for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
954                let latest_enter_serial =
955                    pointer.pointer().winit_data().latest_enter_serial();
956
957                pointer
958                    .pointer()
959                    .set_cursor(latest_enter_serial, None, 0, 0);
960            }
961        }
962    }
963
964    /// Whether show or hide client side decorations.
965    #[inline]
966    pub fn set_decorate(&mut self, decorate: bool) {
967        if decorate == self.decorate {
968            return;
969        }
970
971        self.decorate = decorate;
972
973        match self
974            .last_configure
975            .as_ref()
976            .map(|configure| configure.decoration_mode)
977        {
978            Some(DecorationMode::Server) if !self.decorate => {
979                // To disable decorations we should request client and hide the frame.
980                self.window
981                    .request_decoration_mode(Some(DecorationMode::Client))
982            }
983            _ if self.decorate => self
984                .window
985                .request_decoration_mode(Some(DecorationMode::Server)),
986            _ => (),
987        }
988
989        if let Some(frame) = self.frame.as_mut() {
990            frame.set_hidden(!decorate);
991            // Force the resize.
992            self.resize(self.size);
993        }
994    }
995
996    /// Add seat focus for the window.
997    #[inline]
998    pub fn add_seat_focus(&mut self, seat: ObjectId) {
999        self.seat_focus.insert(seat);
1000    }
1001
1002    /// Remove seat focus from the window.
1003    #[inline]
1004    pub fn remove_seat_focus(&mut self, seat: &ObjectId) {
1005        self.seat_focus.remove(seat);
1006    }
1007
1008    /// Returns `true` if the requested state was applied.
1009    pub fn set_ime_allowed(&mut self, allowed: bool) -> bool {
1010        self.ime_allowed = allowed;
1011
1012        let mut applied = false;
1013        for text_input in &self.text_inputs {
1014            applied = true;
1015            if allowed {
1016                text_input.enable();
1017                text_input.set_content_type_by_purpose(self.ime_purpose);
1018            } else {
1019                text_input.disable();
1020            }
1021            text_input.commit();
1022        }
1023
1024        applied
1025    }
1026
1027    /// Set the IME position.
1028    pub fn set_ime_cursor_area(
1029        &self,
1030        position: LogicalPosition<u32>,
1031        size: LogicalSize<u32>,
1032    ) {
1033        // FIXME: This won't fly unless user will have a way to request IME window per seat, since
1034        // the ime windows will be overlapping, but winit doesn't expose API to specify for
1035        // which seat we're setting IME position.
1036        let (x, y) = (position.x as i32, position.y as i32);
1037        let (width, height) = (size.width as i32, size.height as i32);
1038        for text_input in self.text_inputs.iter() {
1039            text_input.set_cursor_rectangle(x, y, width, height);
1040            text_input.commit();
1041        }
1042    }
1043
1044    /// Set the IME purpose.
1045    pub fn set_ime_purpose(&mut self, purpose: ImePurpose) {
1046        self.ime_purpose = purpose;
1047
1048        for text_input in &self.text_inputs {
1049            text_input.set_content_type_by_purpose(purpose);
1050            text_input.commit();
1051        }
1052    }
1053
1054    /// Get the IME purpose.
1055    pub fn ime_purpose(&self) -> ImePurpose {
1056        self.ime_purpose
1057    }
1058
1059    /// Set the scale factor for the given window.
1060    #[inline]
1061    pub fn set_scale_factor(&mut self, scale_factor: f64) {
1062        self.scale_factor = scale_factor;
1063
1064        // NOTE: When fractional scaling is not used update the buffer scale.
1065        if self.fractional_scale.is_none() {
1066            let _ = self.window.set_buffer_scale(self.scale_factor as _);
1067        }
1068
1069        if let Some(frame) = self.frame.as_mut() {
1070            frame.set_scaling_factor(scale_factor);
1071        }
1072    }
1073
1074    /// Make window background blurred
1075    #[inline]
1076    pub fn set_blur(&mut self, blurred: bool) {
1077        if blurred && self.blur.is_none() {
1078            if let Some(blur_manager) = self.blur_manager.as_ref() {
1079                let blur =
1080                    blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
1081                blur.commit();
1082                self.blur = Some(blur);
1083            } else {
1084                info!("Blur manager unavailable, unable to change blur")
1085            }
1086        } else if !blurred && self.blur.is_some() {
1087            self.blur_manager
1088                .as_ref()
1089                .unwrap()
1090                .unset(self.window.wl_surface());
1091            self.blur.take().unwrap().release();
1092        }
1093    }
1094
1095    /// Set the window title to a new value.
1096    ///
1097    /// This will automatically truncate the title to something meaningful.
1098    pub fn set_title(&mut self, mut title: String) {
1099        // Truncate the title to at most 1024 bytes, so that it does not blow up the protocol
1100        // messages
1101        if title.len() > 1024 {
1102            let mut new_len = 1024;
1103            while !title.is_char_boundary(new_len) {
1104                new_len -= 1;
1105            }
1106            title.truncate(new_len);
1107        }
1108
1109        // Update the CSD title.
1110        if let Some(frame) = self.frame.as_mut() {
1111            frame.set_title(&title);
1112        }
1113
1114        self.window.set_title(&title);
1115        self.title = title;
1116    }
1117
1118    /// Mark the window as transparent.
1119    #[inline]
1120    pub fn set_transparent(&mut self, transparent: bool) {
1121        self.transparent = transparent;
1122        self.reload_transparency_hint();
1123    }
1124
1125    /// Register text input on the top-level.
1126    #[inline]
1127    pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) {
1128        if !self.text_inputs.iter().any(|t| t == text_input) {
1129            self.text_inputs.push(text_input.clone());
1130        }
1131    }
1132
1133    /// The text input left the top-level.
1134    #[inline]
1135    pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) {
1136        if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) {
1137            self.text_inputs.remove(position);
1138        }
1139    }
1140
1141    /// Get the cached title.
1142    #[inline]
1143    pub fn title(&self) -> &str {
1144        &self.title
1145    }
1146}
1147
1148impl Drop for WindowState {
1149    fn drop(&mut self) {
1150        if let Some(blur) = self.blur.take() {
1151            blur.release();
1152        }
1153
1154        if let Some(fs) = self.fractional_scale.take() {
1155            fs.destroy();
1156        }
1157
1158        if let Some(viewport) = self.viewport.take() {
1159            viewport.destroy();
1160        }
1161
1162        // NOTE: the wl_surface used by the window is being cleaned up when
1163        // dropping SCTK `Window`.
1164    }
1165}
1166
1167/// The state of the cursor grabs.
1168#[derive(Clone, Copy)]
1169struct GrabState {
1170    /// The grab mode requested by the user.
1171    user_grab_mode: CursorGrabMode,
1172
1173    /// The current grab mode.
1174    current_grab_mode: CursorGrabMode,
1175}
1176
1177impl GrabState {
1178    fn new() -> Self {
1179        Self {
1180            user_grab_mode: CursorGrabMode::None,
1181            current_grab_mode: CursorGrabMode::None,
1182        }
1183    }
1184}
1185
1186/// The state of the frame callback.
1187#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1188pub enum FrameCallbackState {
1189    /// No frame callback was requested.
1190    #[default]
1191    None,
1192    /// The frame callback was requested, but not yet arrived, the redraw events are throttled.
1193    Requested,
1194    /// The callback was marked as done, and user could receive redraw requested
1195    Received,
1196}
1197
1198impl From<ResizeDirection> for XdgResizeEdge {
1199    fn from(value: ResizeDirection) -> Self {
1200        match value {
1201            ResizeDirection::North => XdgResizeEdge::Top,
1202            ResizeDirection::West => XdgResizeEdge::Left,
1203            ResizeDirection::NorthWest => XdgResizeEdge::TopLeft,
1204            ResizeDirection::NorthEast => XdgResizeEdge::TopRight,
1205            ResizeDirection::East => XdgResizeEdge::Right,
1206            ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft,
1207            ResizeDirection::SouthEast => XdgResizeEdge::BottomRight,
1208            ResizeDirection::South => XdgResizeEdge::Bottom,
1209        }
1210    }
1211}
1212
1213// NOTE: Rust doesn't allow `From<Option<Theme>>`.
1214#[cfg(feature = "sctk-adwaita")]
1215fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
1216    match theme {
1217        Some(Theme::Light) => sctk_adwaita::FrameConfig::light(),
1218        Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(),
1219        None => sctk_adwaita::FrameConfig::auto(),
1220    }
1221}