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);
128
129 window_state.set_decorate(attributes.decorations);
131
132 if let Some(name) = attributes.platform_specific.name.map(|name| name.general) {
134 window.set_app_id(name);
135 }
136
137 window_state.set_title(attributes.title);
139
140 let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.));
143 let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.));
144 window_state.set_min_inner_size(min_size);
145 window_state.set_max_inner_size(max_size);
146
147 window_state.set_resizable(attributes.resizable);
149
150 match attributes.fullscreen.map(Into::into) {
152 Some(Fullscreen::Exclusive(_)) => {
153 warn!("`Fullscreen::Exclusive` is ignored on Wayland");
154 }
155 #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
156 Some(Fullscreen::Borderless(monitor)) => {
157 let output = monitor.and_then(|monitor| match monitor {
158 PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
159 #[cfg(x11_platform)]
160 PlatformMonitorHandle::X(_) => None,
161 });
162
163 window.set_fullscreen(output.as_ref())
164 }
165 _ if attributes.maximized => window.set_maximized(),
166 _ => (),
167 };
168
169 match attributes.cursor {
170 Cursor::Icon(icon) => window_state.set_cursor(icon),
171 Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
172 }
173
174 if let (Some(xdg_activation), Some(token)) = (
176 xdg_activation.as_ref(),
177 attributes.platform_specific.activation_token,
178 ) {
179 xdg_activation.activate(token._token, &surface);
180 }
181
182 window.commit();
184
185 let window_state = Arc::new(Mutex::new(window_state));
187 let window_id = super::make_wid(&surface);
188 state
189 .windows
190 .get_mut()
191 .insert(window_id, window_state.clone());
192
193 let window_requests = WindowRequests {
194 redraw_requested: AtomicBool::new(true),
195 closed: AtomicBool::new(false),
196 };
197 let window_requests = Arc::new(window_requests);
198 state
199 .window_requests
200 .get_mut()
201 .insert(window_id, window_requests.clone());
202
203 let window_events_sink = state.window_events_sink.clone();
205
206 let mut wayland_source =
207 event_loop_window_target.wayland_dispatcher.as_source_mut();
208 let event_queue = wayland_source.queue();
209
210 event_queue.roundtrip(&mut state).map_err(|error| {
212 os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(
213 error
214 ))))
215 })?;
216
217 while !window_state.lock().unwrap().is_configured() {
219 event_queue.blocking_dispatch(&mut state).map_err(|error| {
220 os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(
221 error
222 ))))
223 })?;
224 }
225
226 let event_loop_awakener = event_loop_window_target.event_loop_awakener.clone();
228 event_loop_awakener.ping();
229
230 Ok(Self {
231 window,
232 display,
233 monitors,
234 window_id,
235 compositor,
236 window_state,
237 queue_handle,
238 xdg_activation,
239 attention_requested: Arc::new(AtomicBool::new(false)),
240 event_loop_awakener,
241 window_requests,
242 window_events_sink,
243 })
244 }
245}
246
247impl Window {
248 #[inline]
249 pub fn id(&self) -> WindowId {
250 self.window_id
251 }
252
253 #[inline]
254 pub fn set_title(&self, title: impl ToString) {
255 let new_title = title.to_string();
256 self.window_state.lock().unwrap().set_title(new_title);
257 }
258
259 #[inline]
260 pub fn set_visible(&self, _visible: bool) {
261 }
263
264 #[inline]
265 pub fn is_visible(&self) -> Option<bool> {
266 None
267 }
268
269 #[inline]
270 pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
271 Err(NotSupportedError::new())
272 }
273
274 #[inline]
275 pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
276 Err(NotSupportedError::new())
277 }
278
279 #[inline]
280 pub fn set_outer_position(&self, _: Position) {
281 }
283
284 #[inline]
285 pub fn inner_size(&self) -> PhysicalSize<u32> {
286 let window_state = self.window_state.lock().unwrap();
287 let scale_factor = window_state.scale_factor();
288 super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
289 }
290
291 #[inline]
292 pub fn request_redraw(&self) {
293 if self
298 .window_requests
299 .redraw_requested
300 .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
301 .is_ok()
302 {
303 self.event_loop_awakener.ping();
304 }
305 }
306
307 #[inline]
308 pub fn pre_present_notify(&self) {
309 self.window_state.lock().unwrap().request_frame_callback();
310 }
311
312 #[inline]
313 pub fn outer_size(&self) -> PhysicalSize<u32> {
314 let window_state = self.window_state.lock().unwrap();
315 let scale_factor = window_state.scale_factor();
316 super::logical_to_physical_rounded(window_state.outer_size(), scale_factor)
317 }
318
319 #[inline]
320 pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
321 let mut window_state = self.window_state.lock().unwrap();
322 let new_size = window_state.request_inner_size(size);
323 self.request_redraw();
324 Some(new_size)
325 }
326
327 #[inline]
329 pub fn set_min_inner_size(&self, min_size: Option<Size>) {
330 let scale_factor = self.scale_factor();
331 let min_size = min_size.map(|size| size.to_logical(scale_factor));
332 self.window_state
333 .lock()
334 .unwrap()
335 .set_min_inner_size(min_size);
336 self.request_redraw();
338 }
339
340 #[inline]
342 pub fn set_max_inner_size(&self, max_size: Option<Size>) {
343 let scale_factor = self.scale_factor();
344 let max_size = max_size.map(|size| size.to_logical(scale_factor));
345 self.window_state
346 .lock()
347 .unwrap()
348 .set_max_inner_size(max_size);
349 self.request_redraw();
351 }
352
353 #[inline]
354 pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
355 None
356 }
357
358 #[inline]
359 pub fn set_resize_increments(&self, _increments: Option<Size>) {
360 warn!("`set_resize_increments` is not implemented for Wayland");
361 }
362
363 #[inline]
364 pub fn set_transparent(&self, transparent: bool) {
365 self.window_state
366 .lock()
367 .unwrap()
368 .set_transparent(transparent);
369 }
370
371 #[inline]
372 pub fn has_focus(&self) -> bool {
373 self.window_state.lock().unwrap().has_focus()
374 }
375
376 #[inline]
377 pub fn is_minimized(&self) -> Option<bool> {
378 None
380 }
381
382 #[inline]
383 pub fn show_window_menu(&self, position: Position) {
384 let scale_factor = self.scale_factor();
385 let position = position.to_logical(scale_factor);
386 self.window_state.lock().unwrap().show_window_menu(position);
387 }
388
389 #[inline]
390 pub fn drag_resize_window(
391 &self,
392 direction: ResizeDirection,
393 ) -> Result<(), ExternalError> {
394 self.window_state
395 .lock()
396 .unwrap()
397 .drag_resize_window(direction)
398 }
399
400 #[inline]
401 pub fn set_resizable(&self, resizable: bool) {
402 if self.window_state.lock().unwrap().set_resizable(resizable) {
403 self.request_redraw();
405 }
406 }
407
408 #[inline]
409 pub fn is_resizable(&self) -> bool {
410 self.window_state.lock().unwrap().resizable()
411 }
412
413 #[inline]
414 pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {
415 }
417
418 #[inline]
419 pub fn enabled_buttons(&self) -> WindowButtons {
420 WindowButtons::all()
422 }
423
424 #[inline]
425 pub fn scale_factor(&self) -> f64 {
426 self.window_state.lock().unwrap().scale_factor()
427 }
428
429 #[inline]
430 pub fn set_blur(&self, blur: bool) {
431 self.window_state.lock().unwrap().set_blur(blur);
432 }
433
434 #[inline]
435 pub fn set_decorations(&self, decorate: bool) {
436 self.window_state.lock().unwrap().set_decorate(decorate)
437 }
438
439 #[inline]
440 pub fn is_decorated(&self) -> bool {
441 self.window_state.lock().unwrap().is_decorated()
442 }
443
444 #[inline]
445 pub fn set_window_level(&self, _level: WindowLevel) {}
446
447 #[inline]
448 pub(crate) fn set_window_icon(&self, _window_icon: Option<PlatformIcon>) {}
449
450 #[inline]
451 pub fn set_minimized(&self, minimized: bool) {
452 if !minimized {
454 warn!("Unminimizing is ignored on Wayland.");
455 return;
456 }
457
458 self.window.set_minimized();
459 }
460
461 #[inline]
462 pub fn is_maximized(&self) -> bool {
463 self.window_state
464 .lock()
465 .unwrap()
466 .last_configure
467 .as_ref()
468 .map(|last_configure| last_configure.is_maximized())
469 .unwrap_or_default()
470 }
471
472 #[inline]
473 pub fn set_maximized(&self, maximized: bool) {
474 if maximized {
475 self.window.set_maximized()
476 } else {
477 self.window.unset_maximized()
478 }
479 }
480
481 #[inline]
482 pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
483 let is_fullscreen = self
484 .window_state
485 .lock()
486 .unwrap()
487 .last_configure
488 .as_ref()
489 .map(|last_configure| last_configure.is_fullscreen())
490 .unwrap_or_default();
491
492 if is_fullscreen {
493 let current_monitor =
494 self.current_monitor().map(PlatformMonitorHandle::Wayland);
495 Some(Fullscreen::Borderless(current_monitor))
496 } else {
497 None
498 }
499 }
500
501 #[inline]
502 pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
503 match fullscreen {
504 Some(Fullscreen::Exclusive(_)) => {
505 warn!("`Fullscreen::Exclusive` is ignored on Wayland");
506 }
507 #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))]
508 Some(Fullscreen::Borderless(monitor)) => {
509 let output = monitor.and_then(|monitor| match monitor {
510 PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
511 #[cfg(x11_platform)]
512 PlatformMonitorHandle::X(_) => None,
513 });
514
515 self.window.set_fullscreen(output.as_ref())
516 }
517 None => self.window.unset_fullscreen(),
518 }
519 }
520
521 #[inline]
522 pub fn set_cursor(&self, cursor: Cursor) {
523 let window_state = &mut self.window_state.lock().unwrap();
524
525 match cursor {
526 Cursor::Icon(icon) => window_state.set_cursor(icon),
527 Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
528 }
529 }
530
531 #[inline]
532 pub fn set_cursor_visible(&self, visible: bool) {
533 self.window_state
534 .lock()
535 .unwrap()
536 .set_cursor_visible(visible);
537 }
538
539 pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
540 let xdg_activation = match self.xdg_activation.as_ref() {
541 Some(xdg_activation) => xdg_activation,
542 None => {
543 warn!("`request_user_attention` isn't supported");
544 return;
545 }
546 };
547
548 if request_type.is_none() || self.attention_requested.load(Ordering::Relaxed) {
551 return;
552 }
553
554 self.attention_requested.store(true, Ordering::Relaxed);
555 let surface = self.surface().clone();
556 let data = XdgActivationTokenData::Attention((
557 surface.clone(),
558 Arc::downgrade(&self.attention_requested),
559 ));
560 let xdg_activation_token =
561 xdg_activation.get_activation_token(&self.queue_handle, data);
562 xdg_activation_token.set_surface(&surface);
563 xdg_activation_token.commit();
564 }
565
566 pub fn request_activation_token(
567 &self,
568 ) -> Result<AsyncRequestSerial, NotSupportedError> {
569 let xdg_activation = match self.xdg_activation.as_ref() {
570 Some(xdg_activation) => xdg_activation,
571 None => return Err(NotSupportedError::new()),
572 };
573
574 let serial = AsyncRequestSerial::get();
575
576 let data = XdgActivationTokenData::Obtain((self.window_id, serial));
577 let xdg_activation_token =
578 xdg_activation.get_activation_token(&self.queue_handle, data);
579 xdg_activation_token.set_surface(self.surface());
580 xdg_activation_token.commit();
581
582 Ok(serial)
583 }
584
585 #[inline]
586 pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
587 self.window_state.lock().unwrap().set_cursor_grab(mode)
588 }
589
590 #[inline]
591 pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
592 let scale_factor = self.scale_factor();
593 let position = position.to_logical(scale_factor);
594 self.window_state
595 .lock()
596 .unwrap()
597 .set_cursor_position(position)
598 .map(|_| self.request_redraw())
600 }
601
602 #[inline]
603 pub fn drag_window(&self) -> Result<(), ExternalError> {
604 self.window_state.lock().unwrap().drag_window()
605 }
606
607 #[inline]
608 pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
609 let surface = self.window.wl_surface();
610
611 if hittest {
612 surface.set_input_region(None);
613 Ok(())
614 } else {
615 let region = Region::new(&*self.compositor).map_err(|_| {
616 ExternalError::Os(os_error!(OsError::Misc("failed to set input region.")))
617 })?;
618 region.add(0, 0, 0, 0);
619 surface.set_input_region(Some(region.wl_region()));
620 Ok(())
621 }
622 }
623
624 #[inline]
625 pub fn set_ime_cursor_area(&self, position: Position, size: Size) {
626 let window_state = self.window_state.lock().unwrap();
627 if window_state.ime_allowed() {
628 let scale_factor = window_state.scale_factor();
629 let position = position.to_logical(scale_factor);
630 let size = size.to_logical(scale_factor);
631 window_state.set_ime_cursor_area(position, size);
632 }
633 }
634
635 #[inline]
636 pub fn set_ime_allowed(&self, allowed: bool) {
637 let mut window_state = self.window_state.lock().unwrap();
638
639 if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed)
640 {
641 let event =
642 WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled });
643 self.window_events_sink
644 .lock()
645 .unwrap()
646 .push_window_event(event, self.window_id);
647 self.event_loop_awakener.ping();
648 }
649 }
650
651 #[inline]
652 pub fn set_ime_purpose(&self, purpose: ImePurpose) {
653 self.window_state.lock().unwrap().set_ime_purpose(purpose);
654 }
655
656 #[inline]
657 pub fn focus_window(&self) {}
658
659 #[inline]
660 pub fn surface(&self) -> &WlSurface {
661 self.window.wl_surface()
662 }
663
664 #[inline]
665 pub fn current_monitor(&self) -> Option<MonitorHandle> {
666 let data = self.window.wl_surface().data::<SurfaceData>()?;
667 data.outputs().next().map(MonitorHandle::new)
668 }
669
670 #[inline]
671 pub fn available_monitors(&self) -> Vec<MonitorHandle> {
672 self.monitors.lock().unwrap().clone()
673 }
674
675 #[inline]
676 pub fn primary_monitor(&self) -> Option<MonitorHandle> {
677 None
679 }
680
681 #[inline]
682 pub fn raw_window_handle_raw_window_handle(
683 &self,
684 ) -> Result<raw_window_handle::RawWindowHandle, raw_window_handle::HandleError> {
685 Ok(raw_window_handle::WaylandWindowHandle::new({
686 let ptr = self.window.wl_surface().id().as_ptr();
687 std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null")
688 })
689 .into())
690 }
691
692 #[inline]
693 pub fn raw_display_handle_raw_window_handle(
694 &self,
695 ) -> Result<raw_window_handle::RawDisplayHandle, raw_window_handle::HandleError> {
696 Ok(raw_window_handle::WaylandDisplayHandle::new({
697 let ptr = self.display.id().as_ptr();
698 std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null")
699 })
700 .into())
701 }
702
703 #[inline]
704 pub fn set_theme(&self, theme: Option<Theme>) {
705 self.window_state.lock().unwrap().set_theme(theme)
706 }
707
708 #[inline]
709 pub fn theme(&self) -> Option<Theme> {
710 self.window_state.lock().unwrap().theme()
711 }
712
713 pub fn set_content_protected(&self, _protected: bool) {}
714
715 #[inline]
716 pub fn title(&self) -> String {
717 self.window_state.lock().unwrap().title().to_owned()
718 }
719}
720
721impl Drop for Window {
722 fn drop(&mut self) {
723 self.window_requests.closed.store(true, Ordering::Relaxed);
724 self.event_loop_awakener.ping();
725 }
726}
727
728#[derive(Debug)]
730pub struct WindowRequests {
731 pub closed: AtomicBool,
733
734 pub redraw_requested: AtomicBool,
736}
737
738impl WindowRequests {
739 pub fn take_closed(&self) -> bool {
740 self.closed.swap(false, Ordering::Relaxed)
741 }
742
743 pub fn take_redraw_requested(&self) -> bool {
744 self.redraw_requested.swap(false, Ordering::Relaxed)
745 }
746}
747
748impl TryFrom<&str> for Theme {
749 type Error = ();
750
751 fn try_from(theme: &str) -> Result<Self, Self::Error> {
758 if theme.eq_ignore_ascii_case("dark") {
759 Ok(Self::Dark)
760 } else if theme.eq_ignore_ascii_case("light") {
761 Ok(Self::Light)
762 } else {
763 Err(())
764 }
765 }
766}