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