1use std::ffi::c_void;
4use std::ptr::NonNull;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, Mutex};
7
8use sctk::reexports::client::protocol::wl_display::WlDisplay;
9use sctk::reexports::client::protocol::wl_surface::WlSurface;
10use sctk::reexports::client::{Proxy, QueueHandle};
11
12use sctk::compositor::{CompositorState, Region, SurfaceData};
13use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1;
14use sctk::shell::xdg::window::{Window as SctkWindow, WindowDecorations};
15use sctk::shell::WaylandSurface;
16
17use tracing::warn;
18
19use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
20use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
21use crate::event::{Ime, WindowEvent};
22use crate::event_loop::AsyncRequestSerial;
23use crate::platform_impl::{
24 Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
25};
26use crate::window::{
27 Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType,
28 WindowAttributes, WindowButtons, WindowLevel,
29};
30
31use super::event_loop::sink::EventSink;
32use super::output::MonitorHandle;
33use super::state::WinitState;
34use super::types::xdg_activation::XdgActivationTokenData;
35use super::{ActiveEventLoop, WaylandError, WindowId};
36
37pub(crate) mod state;
38
39pub use state::WindowState;
40
41pub struct Window {
43 window: SctkWindow,
45
46 window_id: WindowId,
48
49 window_state: Arc<Mutex<WindowState>>,
51
52 compositor: Arc<CompositorState>,
54
55 #[allow(dead_code)]
57 display: WlDisplay,
58
59 xdg_activation: Option<XdgActivationV1>,
61
62 attention_requested: Arc<AtomicBool>,
64
65 queue_handle: QueueHandle<WinitState>,
67
68 window_requests: Arc<WindowRequests>,
70
71 monitors: Arc<Mutex<Vec<MonitorHandle>>>,
73
74 event_loop_awakener: calloop::ping::Ping,
76
77 window_events_sink: Arc<Mutex<EventSink>>,
79}
80
81impl Window {
82 pub(crate) fn new(
83 event_loop_window_target: &ActiveEventLoop,
84 attributes: WindowAttributes,
85 ) -> Result<Self, RootOsError> {
86 let queue_handle = event_loop_window_target.queue_handle.clone();
87 let mut state = event_loop_window_target.state.borrow_mut();
88
89 let monitors = state.monitors.clone();
90
91 let surface = state.compositor_state.create_surface(&queue_handle);
92 let compositor = state.compositor_state.clone();
93 let xdg_activation =
94 state.xdg_activation.as_ref().map(|activation_state| activation_state.global().clone());
95 let display = event_loop_window_target.connection.display();
96
97 let size: Size = attributes.inner_size.unwrap_or(LogicalSize::new(800., 600.).into());
98
99 let default_decorations = if attributes.decorations {
102 WindowDecorations::RequestServer
103 } else {
104 WindowDecorations::RequestClient
105 };
106
107 let window =
108 state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle);
109
110 let mut window_state = WindowState::new(
111 event_loop_window_target.connection.clone(),
112 &event_loop_window_target.queue_handle,
113 &state,
114 size,
115 window.clone(),
116 attributes.preferred_theme,
117 );
118
119 window_state.set_transparent(attributes.transparent);
121
122 window_state.set_blur(attributes.blur);
123
124 window_state.set_decorate(attributes.decorations);
126
127 if let Some(name) = attributes.platform_specific.name.map(|name| name.general) {
129 window.set_app_id(name);
130 }
131
132 window_state.set_title(attributes.title);
134
135 let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.));
138 let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.));
139 window_state.set_min_inner_size(min_size);
140 window_state.set_max_inner_size(max_size);
141
142 window_state.set_resizable(attributes.resizable);
144
145 match attributes.fullscreen.map(Into::into) {
147 Some(Fullscreen::Exclusive(_)) => {
148 warn!("`Fullscreen::Exclusive` is ignored on Wayland");
149 },
150 #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
151 Some(Fullscreen::Borderless(monitor)) => {
152 let output = monitor.and_then(|monitor| match monitor {
153 PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
154 #[cfg(x11_platform)]
155 PlatformMonitorHandle::X(_) => None,
156 });
157
158 window.set_fullscreen(output.as_ref())
159 },
160 _ if attributes.maximized => window.set_maximized(),
161 _ => (),
162 };
163
164 match attributes.cursor {
165 Cursor::Icon(icon) => window_state.set_cursor(icon),
166 Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
167 }
168
169 if let (Some(xdg_activation), Some(token)) =
171 (xdg_activation.as_ref(), attributes.platform_specific.activation_token)
172 {
173 xdg_activation.activate(token.token, &surface);
174 }
175
176 window.commit();
178
179 let window_state = Arc::new(Mutex::new(window_state));
181 let window_id = super::make_wid(&surface);
182 state.windows.get_mut().insert(window_id, window_state.clone());
183
184 let window_requests = WindowRequests {
185 redraw_requested: AtomicBool::new(true),
186 closed: AtomicBool::new(false),
187 };
188 let window_requests = Arc::new(window_requests);
189 state.window_requests.get_mut().insert(window_id, window_requests.clone());
190
191 let window_events_sink = state.window_events_sink.clone();
193
194 let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut();
195 let event_queue = wayland_source.queue();
196
197 event_queue.roundtrip(&mut state).map_err(|error| {
199 os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
200 })?;
201
202 while !window_state.lock().unwrap().is_configured() {
204 event_queue.blocking_dispatch(&mut state).map_err(|error| {
205 os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
206 })?;
207 }
208
209 let event_loop_awakener = event_loop_window_target.event_loop_awakener.clone();
211 event_loop_awakener.ping();
212
213 Ok(Self {
214 window,
215 display,
216 monitors,
217 window_id,
218 compositor,
219 window_state,
220 queue_handle,
221 xdg_activation,
222 attention_requested: Arc::new(AtomicBool::new(false)),
223 event_loop_awakener,
224 window_requests,
225 window_events_sink,
226 })
227 }
228
229 pub(crate) fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
230 NonNull::new(self.window.xdg_toplevel().id().as_ptr().cast())
231 }
232}
233
234impl Window {
235 #[inline]
236 pub fn id(&self) -> WindowId {
237 self.window_id
238 }
239
240 #[inline]
241 pub fn set_title(&self, title: impl ToString) {
242 let new_title = title.to_string();
243 self.window_state.lock().unwrap().set_title(new_title);
244 }
245
246 #[inline]
247 pub fn set_visible(&self, _visible: bool) {
248 }
250
251 #[inline]
252 pub fn is_visible(&self) -> Option<bool> {
253 None
254 }
255
256 #[inline]
257 pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
258 Err(NotSupportedError::new())
259 }
260
261 #[inline]
262 pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
263 Err(NotSupportedError::new())
264 }
265
266 #[inline]
267 pub fn set_outer_position(&self, _: Position) {
268 }
270
271 #[inline]
272 pub fn inner_size(&self) -> PhysicalSize<u32> {
273 let window_state = self.window_state.lock().unwrap();
274 let scale_factor = window_state.scale_factor();
275 super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
276 }
277
278 #[inline]
279 pub fn request_redraw(&self) {
280 if self
285 .window_requests
286 .redraw_requested
287 .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
288 .is_ok()
289 {
290 self.event_loop_awakener.ping();
291 }
292 }
293
294 #[inline]
295 pub fn pre_present_notify(&self) {
296 self.window_state.lock().unwrap().request_frame_callback();
297 }
298
299 #[inline]
300 pub fn outer_size(&self) -> PhysicalSize<u32> {
301 let window_state = self.window_state.lock().unwrap();
302 let scale_factor = window_state.scale_factor();
303 super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
304 }
305
306 #[inline]
307 pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
308 let mut window_state = self.window_state.lock().unwrap();
309 let new_size = window_state.request_inner_size(size);
310 self.request_redraw();
311 Some(new_size)
312 }
313
314 #[inline]
316 pub fn set_min_inner_size(&self, min_size: Option<Size>) {
317 let scale_factor = self.scale_factor();
318 let min_size = min_size.map(|size| size.to_logical(scale_factor));
319 self.window_state.lock().unwrap().set_min_inner_size(min_size);
320 self.request_redraw();
322 }
323
324 #[inline]
326 pub fn set_max_inner_size(&self, max_size: Option<Size>) {
327 let scale_factor = self.scale_factor();
328 let max_size = max_size.map(|size| size.to_logical(scale_factor));
329 self.window_state.lock().unwrap().set_max_inner_size(max_size);
330 self.request_redraw();
332 }
333
334 #[inline]
335 pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
336 None
337 }
338
339 #[inline]
340 pub fn set_resize_increments(&self, _increments: Option<Size>) {
341 warn!("`set_resize_increments` is not implemented for Wayland");
342 }
343
344 #[inline]
345 pub fn set_transparent(&self, transparent: bool) {
346 self.window_state.lock().unwrap().set_transparent(transparent);
347 }
348
349 #[inline]
350 pub fn has_focus(&self) -> bool {
351 self.window_state.lock().unwrap().has_focus()
352 }
353
354 #[inline]
355 pub fn is_minimized(&self) -> Option<bool> {
356 None
358 }
359
360 #[inline]
361 pub fn show_window_menu(&self, position: Position) {
362 let scale_factor = self.scale_factor();
363 let position = position.to_logical(scale_factor);
364 self.window_state.lock().unwrap().show_window_menu(position);
365 }
366
367 #[inline]
368 pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
369 self.window_state.lock().unwrap().drag_resize_window(direction)
370 }
371
372 #[inline]
373 pub fn set_resizable(&self, resizable: bool) {
374 if self.window_state.lock().unwrap().set_resizable(resizable) {
375 self.request_redraw();
377 }
378 }
379
380 #[inline]
381 pub fn is_resizable(&self) -> bool {
382 self.window_state.lock().unwrap().resizable()
383 }
384
385 #[inline]
386 pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {
387 }
389
390 #[inline]
391 pub fn enabled_buttons(&self) -> WindowButtons {
392 WindowButtons::all()
394 }
395
396 #[inline]
397 pub fn scale_factor(&self) -> f64 {
398 self.window_state.lock().unwrap().scale_factor()
399 }
400
401 #[inline]
402 pub fn set_blur(&self, blur: bool) {
403 self.window_state.lock().unwrap().set_blur(blur);
404 }
405
406 #[inline]
407 pub fn set_decorations(&self, decorate: bool) {
408 self.window_state.lock().unwrap().set_decorate(decorate)
409 }
410
411 #[inline]
412 pub fn is_decorated(&self) -> bool {
413 self.window_state.lock().unwrap().is_decorated()
414 }
415
416 #[inline]
417 pub fn set_window_level(&self, _level: WindowLevel) {}
418
419 #[inline]
420 pub(crate) fn set_window_icon(&self, _window_icon: Option<PlatformIcon>) {}
421
422 #[inline]
423 pub fn set_minimized(&self, minimized: bool) {
424 if !minimized {
426 warn!("Unminimizing is ignored on Wayland.");
427 return;
428 }
429
430 self.window.set_minimized();
431 }
432
433 #[inline]
434 pub fn is_maximized(&self) -> bool {
435 self.window_state
436 .lock()
437 .unwrap()
438 .last_configure
439 .as_ref()
440 .map(|last_configure| last_configure.is_maximized())
441 .unwrap_or_default()
442 }
443
444 #[inline]
445 pub fn set_maximized(&self, maximized: bool) {
446 if maximized {
447 self.window.set_maximized()
448 } else {
449 self.window.unset_maximized()
450 }
451 }
452
453 #[inline]
454 pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
455 let is_fullscreen = self
456 .window_state
457 .lock()
458 .unwrap()
459 .last_configure
460 .as_ref()
461 .map(|last_configure| last_configure.is_fullscreen())
462 .unwrap_or_default();
463
464 if is_fullscreen {
465 let current_monitor = self.current_monitor().map(PlatformMonitorHandle::Wayland);
466 Some(Fullscreen::Borderless(current_monitor))
467 } else {
468 None
469 }
470 }
471
472 #[inline]
473 pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
474 match fullscreen {
475 Some(Fullscreen::Exclusive(_)) => {
476 warn!("`Fullscreen::Exclusive` is ignored on Wayland");
477 },
478 #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
479 Some(Fullscreen::Borderless(monitor)) => {
480 let output = monitor.and_then(|monitor| match monitor {
481 PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
482 #[cfg(x11_platform)]
483 PlatformMonitorHandle::X(_) => None,
484 });
485
486 self.window.set_fullscreen(output.as_ref())
487 },
488 None => self.window.unset_fullscreen(),
489 }
490 }
491
492 #[inline]
493 pub fn set_cursor(&self, cursor: Cursor) {
494 let window_state = &mut self.window_state.lock().unwrap();
495
496 match cursor {
497 Cursor::Icon(icon) => window_state.set_cursor(icon),
498 Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
499 }
500 }
501
502 #[inline]
503 pub fn set_cursor_visible(&self, visible: bool) {
504 self.window_state.lock().unwrap().set_cursor_visible(visible);
505 }
506
507 pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
508 let xdg_activation = match self.xdg_activation.as_ref() {
509 Some(xdg_activation) => xdg_activation,
510 None => {
511 warn!("`request_user_attention` isn't supported");
512 return;
513 },
514 };
515
516 if request_type.is_none() || self.attention_requested.load(Ordering::Relaxed) {
519 return;
520 }
521
522 self.attention_requested.store(true, Ordering::Relaxed);
523 let surface = self.surface().clone();
524 let data = XdgActivationTokenData::Attention((
525 surface.clone(),
526 Arc::downgrade(&self.attention_requested),
527 ));
528 let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
529 xdg_activation_token.set_surface(&surface);
530 xdg_activation_token.commit();
531 }
532
533 pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
534 let xdg_activation = match self.xdg_activation.as_ref() {
535 Some(xdg_activation) => xdg_activation,
536 None => return Err(NotSupportedError::new()),
537 };
538
539 let serial = AsyncRequestSerial::get();
540
541 let data = XdgActivationTokenData::Obtain((self.window_id, serial));
542 let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data);
543 xdg_activation_token.set_surface(self.surface());
544 xdg_activation_token.commit();
545
546 Ok(serial)
547 }
548
549 #[inline]
550 pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
551 self.window_state.lock().unwrap().set_cursor_grab(mode)
552 }
553
554 #[inline]
555 pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
556 let scale_factor = self.scale_factor();
557 let position = position.to_logical(scale_factor);
558 self.window_state
559 .lock()
560 .unwrap()
561 .set_cursor_position(position)
562 .map(|_| self.request_redraw())
564 }
565
566 #[inline]
567 pub fn drag_window(&self) -> Result<(), ExternalError> {
568 self.window_state.lock().unwrap().drag_window()
569 }
570
571 #[inline]
572 pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
573 let surface = self.window.wl_surface();
574
575 if hittest {
576 surface.set_input_region(None);
577 Ok(())
578 } else {
579 let region = Region::new(&*self.compositor).map_err(|_| {
580 ExternalError::Os(os_error!(OsError::Misc("failed to set input region.")))
581 })?;
582 region.add(0, 0, 0, 0);
583 surface.set_input_region(Some(region.wl_region()));
584 Ok(())
585 }
586 }
587
588 #[inline]
589 pub fn set_ime_cursor_area(&self, position: Position, size: Size) {
590 let window_state = self.window_state.lock().unwrap();
591 if window_state.ime_allowed() {
592 let scale_factor = window_state.scale_factor();
593 let position = position.to_logical(scale_factor);
594 let size = size.to_logical(scale_factor);
595 window_state.set_ime_cursor_area(position, size);
596 }
597 }
598
599 #[inline]
600 pub fn set_ime_allowed(&self, allowed: bool) {
601 let mut window_state = self.window_state.lock().unwrap();
602
603 if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed) {
604 let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled });
605 self.window_events_sink.lock().unwrap().push_window_event(event, self.window_id);
606 self.event_loop_awakener.ping();
607 }
608 }
609
610 #[inline]
611 pub fn set_ime_purpose(&self, purpose: ImePurpose) {
612 self.window_state.lock().unwrap().set_ime_purpose(purpose);
613 }
614
615 #[inline]
616 pub fn focus_window(&self) {}
617
618 #[inline]
619 pub fn surface(&self) -> &WlSurface {
620 self.window.wl_surface()
621 }
622
623 #[inline]
624 pub fn current_monitor(&self) -> Option<MonitorHandle> {
625 let data = self.window.wl_surface().data::<SurfaceData>()?;
626 data.outputs().next().map(MonitorHandle::new)
627 }
628
629 #[inline]
630 pub fn available_monitors(&self) -> Vec<MonitorHandle> {
631 self.monitors.lock().unwrap().clone()
632 }
633
634 #[inline]
635 pub fn primary_monitor(&self) -> Option<MonitorHandle> {
636 None
638 }
639
640 #[cfg(feature = "rwh_04")]
641 #[inline]
642 pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
643 let mut window_handle = rwh_04::WaylandHandle::empty();
644 window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _;
645 window_handle.display = self.display.id().as_ptr() as *mut _;
646 rwh_04::RawWindowHandle::Wayland(window_handle)
647 }
648
649 #[cfg(feature = "rwh_05")]
650 #[inline]
651 pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
652 let mut window_handle = rwh_05::WaylandWindowHandle::empty();
653 window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _;
654 rwh_05::RawWindowHandle::Wayland(window_handle)
655 }
656
657 #[cfg(feature = "rwh_05")]
658 #[inline]
659 pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
660 let mut display_handle = rwh_05::WaylandDisplayHandle::empty();
661 display_handle.display = self.display.id().as_ptr() as *mut _;
662 rwh_05::RawDisplayHandle::Wayland(display_handle)
663 }
664
665 #[cfg(feature = "rwh_06")]
666 #[inline]
667 pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
668 Ok(rwh_06::WaylandWindowHandle::new({
669 let ptr = self.window.wl_surface().id().as_ptr();
670 std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null")
671 })
672 .into())
673 }
674
675 #[cfg(feature = "rwh_06")]
676 #[inline]
677 pub fn raw_display_handle_rwh_06(
678 &self,
679 ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
680 Ok(rwh_06::WaylandDisplayHandle::new({
681 let ptr = self.display.id().as_ptr();
682 std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null")
683 })
684 .into())
685 }
686
687 #[inline]
688 pub fn set_theme(&self, theme: Option<Theme>) {
689 self.window_state.lock().unwrap().set_theme(theme)
690 }
691
692 #[inline]
693 pub fn theme(&self) -> Option<Theme> {
694 self.window_state.lock().unwrap().theme()
695 }
696
697 pub fn set_content_protected(&self, _protected: bool) {}
698
699 #[inline]
700 pub fn title(&self) -> String {
701 self.window_state.lock().unwrap().title().to_owned()
702 }
703}
704
705impl Drop for Window {
706 fn drop(&mut self) {
707 self.window_requests.closed.store(true, Ordering::Relaxed);
708 self.event_loop_awakener.ping();
709 }
710}
711
712#[derive(Debug)]
714pub struct WindowRequests {
715 pub closed: AtomicBool,
717
718 pub redraw_requested: AtomicBool,
720}
721
722impl WindowRequests {
723 pub fn take_closed(&self) -> bool {
724 self.closed.swap(false, Ordering::Relaxed)
725 }
726
727 pub fn take_redraw_requested(&self) -> bool {
728 self.redraw_requested.swap(false, Ordering::Relaxed)
729 }
730}
731
732impl TryFrom<&str> for Theme {
733 type Error = ();
734
735 fn try_from(theme: &str) -> Result<Self, Self::Error> {
742 if theme.eq_ignore_ascii_case("dark") {
743 Ok(Self::Dark)
744 } else if theme.eq_ignore_ascii_case("light") {
745 Ok(Self::Light)
746 } else {
747 Err(())
748 }
749 }
750}