1use 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
49const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
51
52pub struct WindowState {
54 pub connection: Connection,
56
57 frame: Option<WinitFrame>,
59
60 pub shm: WlShm,
62
63 pub last_configure: Option<WindowConfigure>,
65
66 pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
68
69 pub cursor_icon: CursorIcon,
71
72 pub cursor_visible: bool,
74
75 pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
77
78 pub queue_handle: QueueHandle<WinitState>,
80
81 theme: Option<Theme>,
83
84 title: String,
86
87 resizable: bool,
89
90 has_focus: bool,
92
93 scale_factor: f64,
95
96 transparent: bool,
98
99 compositor: Arc<CompositorState>,
101
102 cursor_grab_mode: GrabState,
104
105 ime_allowed: bool,
107
108 ime_purpose: ImePurpose,
110
111 text_inputs: Vec<ZwpTextInputV3>,
113
114 size: LogicalSize<u32>,
116
117 csd_fails: bool,
119
120 decorate: bool,
122
123 min_inner_size: LogicalSize<u32>,
125 max_inner_size: Option<LogicalSize<u32>>,
126
127 stateless_size: LogicalSize<u32>,
131
132 initial_size: Option<Size>,
135
136 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 has_pending_move: Option<u32>,
148
149 pub window: Window,
151}
152
153impl WindowState {
154 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 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 pub fn frame_callback_state(&self) -> FrameCallbackState {
228 self.frame_callback_state
229 }
230
231 pub fn frame_callback_received(&mut self) {
233 self.frame_callback_state = FrameCallbackState::Received;
234 }
235
236 pub fn frame_callback_reset(&mut self) {
238 self.frame_callback_state = FrameCallbackState::None;
239 }
240
241 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 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 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 self.frame = None;
295 }
296
297 let stateless = Self::is_stateless(&configure);
298
299 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 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 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 self.last_configure = Some(configure);
354
355 self.resize(new_size);
357
358 new_size
359 }
360
361 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 pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
392 let xdg_toplevel = self.window.xdg_toplevel();
393
394 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 pub fn drag_window(&self) -> Result<(), ExternalError> {
406 let xdg_toplevel = self.window.xdg_toplevel();
407 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 #[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 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 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 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 #[inline]
492 pub fn resizable(&self) -> bool {
493 self.resizable
494 }
495
496 #[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 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 if let Some(frame) = self.frame.as_mut() {
514 frame.set_resizable(resizable);
515 }
516 }
517
518 #[inline]
520 pub fn has_focus(&self) -> bool {
521 self.has_focus
522 }
523
524 #[inline]
526 pub fn ime_allowed(&self) -> bool {
527 self.ime_allowed
528 }
529
530 #[inline]
532 pub fn inner_size(&self) -> LogicalSize<u32> {
533 self.size
534 }
535
536 #[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 true
554 }
555 }
556
557 #[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 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 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 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 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 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 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 fn resize(&mut self, inner_size: LogicalSize<u32>) {
639 self.size = inner_size;
640
641 if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
643 self.stateless_size = inner_size;
644 }
645
646 let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
648 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 self.reload_transparency_hint();
666
667 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 if let Some(viewport) = self.viewport.as_ref() {
677 viewport.set_destination(self.size.width as _, self.size.height as _);
679 }
680 }
681
682 #[inline]
684 pub fn scale_factor(&self) -> f64 {
685 self.scale_factor
686 }
687
688 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 pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
707 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 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 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 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 #[inline]
747 pub fn theme(&self) -> Option<Theme> {
748 self.theme
749 }
750
751 pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
753 self.cursor_grab_mode.user_grab_mode = mode;
755 self.set_cursor_grab_inner(mode)
756 }
757
758 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 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 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 }
798 }
799
800 Ok(())
801 }
802
803 pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
804 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 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 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 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 #[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 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 self.resize(self.size);
880 }
881 }
882
883 #[inline]
887 pub fn set_has_focus(&mut self, has_focus: bool) {
888 self.has_focus = has_focus;
889 }
890
891 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 pub fn set_ime_cursor_area(&self, position: LogicalPosition<u32>, size: LogicalSize<u32>) {
912 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 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 pub fn ime_purpose(&self) -> ImePurpose {
935 self.ime_purpose
936 }
937
938 #[inline]
940 pub fn set_scale_factor(&mut self, scale_factor: f64) {
941 self.scale_factor = scale_factor;
942
943 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 #[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 pub fn set_title(&mut self, mut title: String) {
977 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 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 #[inline]
998 pub fn set_transparent(&mut self, transparent: bool) {
999 self.transparent = transparent;
1000 self.reload_transparency_hint();
1001 }
1002
1003 #[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 #[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 #[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 }
1043}
1044
1045#[derive(Clone, Copy)]
1047struct GrabState {
1048 user_grab_mode: CursorGrabMode,
1050
1051 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#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1066pub enum FrameCallbackState {
1067 #[default]
1069 None,
1070 Requested,
1072 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#[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}