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