//! The Wayland window. use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use sctk::compositor::{CompositorState, Region, SurfaceData}; use sctk::reexports::client::protocol::wl_display::WlDisplay; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{Proxy, QueueHandle}; use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; use sctk::shell::xdg::window::{Window as SctkWindow, WindowDecorations}; use sctk::shell::WaylandSurface; use tracing::warn; use super::event_loop::sink::EventSink; use super::output::MonitorHandle; use super::state::WinitState; use super::types::xdg_activation::XdgActivationTokenData; use super::ActiveEventLoop; use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{NotSupportedError, RequestError}; use crate::event::{Ime, WindowEvent}; use crate::event_loop::AsyncRequestSerial; use crate::monitor::MonitorHandle as CoreMonitorHandle; use crate::platform_impl::{Fullscreen, MonitorHandle as PlatformMonitorHandle}; use crate::window::{ Cursor, CursorGrabMode, Fullscreen as CoreFullscreen, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, }; pub(crate) mod state; pub use state::WindowState; /// The Wayland window. pub struct Window { /// Reference to the underlying SCTK window. window: SctkWindow, /// Window id. window_id: WindowId, /// The state of the window. window_state: Arc>, /// Compositor to handle WlRegion stuff. compositor: Arc, /// The wayland display used solely for raw window handle. #[allow(dead_code)] display: WlDisplay, /// Xdg activation to request user attention. xdg_activation: Option, /// The state of the requested attention from the `xdg_activation`. attention_requested: Arc, /// Handle to the main queue to perform requests. queue_handle: QueueHandle, /// Window requests to the event loop. window_requests: Arc, /// Observed monitors. monitors: Arc>>, /// Source to wake-up the event-loop for window requests. event_loop_awakener: calloop::ping::Ping, /// The event sink to deliver synthetic events. window_events_sink: Arc>, } impl Window { pub(crate) fn new( event_loop_window_target: &ActiveEventLoop, attributes: WindowAttributes, ) -> Result { let queue_handle = event_loop_window_target.queue_handle.clone(); let mut state = event_loop_window_target.state.borrow_mut(); let monitors = state.monitors.clone(); let surface = state.compositor_state.create_surface(&queue_handle); let compositor = state.compositor_state.clone(); let xdg_activation = state.xdg_activation.as_ref().map(|activation_state| activation_state.global().clone()); let display = event_loop_window_target.connection.display(); let size: Size = attributes.surface_size.unwrap_or(LogicalSize::new(800., 600.).into()); // We prefer server side decorations, however to not have decorations we ask for client // side decorations instead. let default_decorations = if attributes.decorations { WindowDecorations::RequestServer } else { WindowDecorations::RequestClient }; let window = state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle); let mut window_state = WindowState::new( event_loop_window_target.connection.clone(), &event_loop_window_target.queue_handle, &state, size, window.clone(), attributes.preferred_theme, ); // Set transparency hint. window_state.set_transparent(attributes.transparent); window_state.set_blur(attributes.blur); // Set the decorations hint. window_state.set_decorate(attributes.decorations); // Set the app_id. if let Some(name) = attributes.platform_specific.name.map(|name| name.general) { window.set_app_id(name); } // Set the window title. window_state.set_title(attributes.title); // Set the min and max sizes. We must set the hints upon creating a window, so // we use the default `1.` scaling... let min_size = attributes.min_surface_size.map(|size| size.to_logical(1.)); let max_size = attributes.max_surface_size.map(|size| size.to_logical(1.)); window_state.set_min_surface_size(min_size); window_state.set_max_surface_size(max_size); // Non-resizable implies that the min and max sizes are set to the same value. window_state.set_resizable(attributes.resizable); // Set startup mode. match attributes.fullscreen.map(Into::into) { Some(Fullscreen::Exclusive(_)) => { warn!("`Fullscreen::Exclusive` is ignored on Wayland"); }, #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))] Some(Fullscreen::Borderless(monitor)) => { let output = monitor.and_then(|monitor| match monitor { PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), #[cfg(x11_platform)] PlatformMonitorHandle::X(_) => None, }); window.set_fullscreen(output.as_ref()) }, _ if attributes.maximized => window.set_maximized(), _ => (), }; match attributes.cursor { Cursor::Icon(icon) => window_state.set_cursor(icon), Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor), } // Activate the window when the token is passed. if let (Some(xdg_activation), Some(token)) = (xdg_activation.as_ref(), attributes.platform_specific.activation_token) { xdg_activation.activate(token._token, &surface); } // XXX Do initial commit. window.commit(); // Add the window and window requests into the state. let window_state = Arc::new(Mutex::new(window_state)); let window_id = super::make_wid(&surface); state.windows.get_mut().insert(window_id, window_state.clone()); let window_requests = WindowRequests { redraw_requested: AtomicBool::new(true), closed: AtomicBool::new(false), }; let window_requests = Arc::new(window_requests); state.window_requests.get_mut().insert(window_id, window_requests.clone()); // Setup the event sync to insert `WindowEvents` right from the window. let window_events_sink = state.window_events_sink.clone(); let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut(); let event_queue = wayland_source.queue(); // Do a roundtrip. event_queue.roundtrip(&mut state).map_err(|err| os_error!(err))?; // XXX Wait for the initial configure to arrive. while !window_state.lock().unwrap().is_configured() { event_queue.blocking_dispatch(&mut state).map_err(|err| os_error!(err))?; } // Wake-up event loop, so it'll send initial redraw requested. let event_loop_awakener = event_loop_window_target.event_loop_awakener.clone(); event_loop_awakener.ping(); Ok(Self { window, display, monitors, window_id, compositor, window_state, queue_handle, xdg_activation, attention_requested: Arc::new(AtomicBool::new(false)), event_loop_awakener, window_requests, window_events_sink, }) } } impl Window { pub fn request_activation_token(&self) -> Result { let xdg_activation = match self.xdg_activation.as_ref() { Some(xdg_activation) => xdg_activation, None => return Err(NotSupportedError::new("xdg_activation_v1 is not available").into()), }; let serial = AsyncRequestSerial::get(); let data = XdgActivationTokenData::Obtain((self.window_id, serial)); let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); xdg_activation_token.set_surface(self.surface()); xdg_activation_token.commit(); Ok(serial) } #[inline] pub fn surface(&self) -> &WlSurface { self.window.wl_surface() } } impl Drop for Window { fn drop(&mut self) { self.window_requests.closed.store(true, Ordering::Relaxed); self.event_loop_awakener.ping(); } } impl rwh_06::HasWindowHandle for Window { fn window_handle(&self) -> Result, rwh_06::HandleError> { let raw = rwh_06::WaylandWindowHandle::new({ let ptr = self.window.wl_surface().id().as_ptr(); std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null") }); unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw.into())) } } } impl rwh_06::HasDisplayHandle for Window { fn display_handle(&self) -> Result, rwh_06::HandleError> { let raw = rwh_06::WaylandDisplayHandle::new({ let ptr = self.display.id().as_ptr(); std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null") }); unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw.into())) } } } impl CoreWindow for Window { fn id(&self) -> WindowId { self.window_id } fn request_redraw(&self) { // NOTE: try to not wake up the loop when the event was already scheduled and not yet // processed by the loop, because if at this point the value was `true` it could only // mean that the loop still haven't dispatched the value to the client and will do // eventually, resetting it to `false`. if self .window_requests .redraw_requested .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) .is_ok() { self.event_loop_awakener.ping(); } } #[inline] fn title(&self) -> String { self.window_state.lock().unwrap().title().to_owned() } fn pre_present_notify(&self) { self.window_state.lock().unwrap().request_frame_callback(); } fn reset_dead_keys(&self) { crate::platform_impl::common::xkb::reset_dead_keys() } fn inner_position(&self) -> Result, RequestError> { Err(NotSupportedError::new("window position information is not available on Wayland") .into()) } fn outer_position(&self) -> Result, RequestError> { Err(NotSupportedError::new("window position information is not available on Wayland") .into()) } fn set_outer_position(&self, _position: Position) { // Not possible. } fn surface_size(&self) -> PhysicalSize { let window_state = self.window_state.lock().unwrap(); let scale_factor = window_state.scale_factor(); super::logical_to_physical_rounded(window_state.surface_size(), scale_factor) } fn request_surface_size(&self, size: Size) -> Option> { let mut window_state = self.window_state.lock().unwrap(); let new_size = window_state.request_surface_size(size); self.request_redraw(); Some(new_size) } fn outer_size(&self) -> PhysicalSize { let window_state = self.window_state.lock().unwrap(); let scale_factor = window_state.scale_factor(); super::logical_to_physical_rounded(window_state.outer_size(), scale_factor) } fn set_min_surface_size(&self, min_size: Option) { let scale_factor = self.scale_factor(); let min_size = min_size.map(|size| size.to_logical(scale_factor)); self.window_state.lock().unwrap().set_min_surface_size(min_size); // NOTE: Requires commit to be applied. self.request_redraw(); } /// Set the maximum surface size for the window. #[inline] fn set_max_surface_size(&self, max_size: Option) { let scale_factor = self.scale_factor(); let max_size = max_size.map(|size| size.to_logical(scale_factor)); self.window_state.lock().unwrap().set_max_surface_size(max_size); // NOTE: Requires commit to be applied. self.request_redraw(); } fn surface_resize_increments(&self) -> Option> { None } fn set_surface_resize_increments(&self, _increments: Option) { warn!("`set_surface_resize_increments` is not implemented for Wayland"); } fn set_title(&self, title: &str) { let new_title = title.to_string(); self.window_state.lock().unwrap().set_title(new_title); } #[inline] fn set_transparent(&self, transparent: bool) { self.window_state.lock().unwrap().set_transparent(transparent); } fn set_visible(&self, _visible: bool) { // Not possible on Wayland. } fn is_visible(&self) -> Option { None } fn set_resizable(&self, resizable: bool) { if self.window_state.lock().unwrap().set_resizable(resizable) { // NOTE: Requires commit to be applied. self.request_redraw(); } } fn is_resizable(&self) -> bool { self.window_state.lock().unwrap().resizable() } fn set_enabled_buttons(&self, _buttons: WindowButtons) { // TODO(kchibisov) v5 of the xdg_shell allows that. } fn enabled_buttons(&self) -> WindowButtons { // TODO(kchibisov) v5 of the xdg_shell allows that. WindowButtons::all() } fn set_minimized(&self, minimized: bool) { // You can't unminimize the window on Wayland. if !minimized { warn!("Unminimizing is ignored on Wayland."); return; } self.window.set_minimized(); } fn is_minimized(&self) -> Option { // XXX clients don't know whether they are minimized or not. None } fn set_maximized(&self, maximized: bool) { if maximized { self.window.set_maximized() } else { self.window.unset_maximized() } } fn is_maximized(&self) -> bool { self.window_state .lock() .unwrap() .last_configure .as_ref() .map(|last_configure| last_configure.is_maximized()) .unwrap_or_default() } fn set_fullscreen(&self, fullscreen: Option) { match fullscreen { Some(CoreFullscreen::Exclusive(_)) => { warn!("`Fullscreen::Exclusive` is ignored on Wayland"); }, #[cfg_attr(not(x11_platform), allow(clippy::bind_instead_of_map))] Some(CoreFullscreen::Borderless(monitor)) => { let output = monitor.and_then(|monitor| match monitor.inner { PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), #[cfg(x11_platform)] PlatformMonitorHandle::X(_) => None, }); self.window.set_fullscreen(output.as_ref()) }, None => self.window.unset_fullscreen(), } } fn fullscreen(&self) -> Option { let is_fullscreen = self .window_state .lock() .unwrap() .last_configure .as_ref() .map(|last_configure| last_configure.is_fullscreen()) .unwrap_or_default(); if is_fullscreen { let current_monitor = self.current_monitor(); Some(CoreFullscreen::Borderless(current_monitor)) } else { None } } #[inline] fn scale_factor(&self) -> f64 { self.window_state.lock().unwrap().scale_factor() } #[inline] fn set_blur(&self, blur: bool) { self.window_state.lock().unwrap().set_blur(blur); } #[inline] fn set_decorations(&self, decorate: bool) { self.window_state.lock().unwrap().set_decorate(decorate) } #[inline] fn is_decorated(&self) -> bool { self.window_state.lock().unwrap().is_decorated() } fn set_window_level(&self, _level: WindowLevel) {} fn set_window_icon(&self, _window_icon: Option) {} #[inline] fn set_ime_cursor_area(&self, position: Position, size: Size) { let window_state = self.window_state.lock().unwrap(); if window_state.ime_allowed() { let scale_factor = window_state.scale_factor(); let position = position.to_logical(scale_factor); let size = size.to_logical(scale_factor); window_state.set_ime_cursor_area(position, size); } } #[inline] fn set_ime_allowed(&self, allowed: bool) { let mut window_state = self.window_state.lock().unwrap(); if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed) { let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled }); self.window_events_sink.lock().unwrap().push_window_event(event, self.window_id); self.event_loop_awakener.ping(); } } #[inline] fn set_ime_purpose(&self, purpose: ImePurpose) { self.window_state.lock().unwrap().set_ime_purpose(purpose); } fn focus_window(&self) {} fn has_focus(&self) -> bool { self.window_state.lock().unwrap().has_focus() } fn request_user_attention(&self, request_type: Option) { let xdg_activation = match self.xdg_activation.as_ref() { Some(xdg_activation) => xdg_activation, None => { warn!("`request_user_attention` isn't supported"); return; }, }; // Urgency is only removed by the compositor and there's no need to raise urgency when it // was already raised. if request_type.is_none() || self.attention_requested.load(Ordering::Relaxed) { return; } self.attention_requested.store(true, Ordering::Relaxed); let surface = self.surface().clone(); let data = XdgActivationTokenData::Attention(( surface.clone(), Arc::downgrade(&self.attention_requested), )); let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); xdg_activation_token.set_surface(&surface); xdg_activation_token.commit(); } fn set_theme(&self, theme: Option) { self.window_state.lock().unwrap().set_theme(theme) } fn theme(&self) -> Option { self.window_state.lock().unwrap().theme() } fn set_content_protected(&self, _protected: bool) {} fn set_cursor(&self, cursor: Cursor) { let window_state = &mut self.window_state.lock().unwrap(); match cursor { Cursor::Icon(icon) => window_state.set_cursor(icon), Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor), } } fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> { let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); self.window_state .lock() .unwrap() .set_cursor_position(position) // Request redraw on success, since the state is double buffered. .map(|_| self.request_redraw()) } fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> { self.window_state.lock().unwrap().set_cursor_grab(mode) } fn set_cursor_visible(&self, visible: bool) { self.window_state.lock().unwrap().set_cursor_visible(visible); } fn drag_window(&self) -> Result<(), RequestError> { self.window_state.lock().unwrap().drag_window() } fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> { self.window_state.lock().unwrap().drag_resize_window(direction) } fn show_window_menu(&self, position: Position) { let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); self.window_state.lock().unwrap().show_window_menu(position); } fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> { let surface = self.window.wl_surface(); if hittest { surface.set_input_region(None); Ok(()) } else { let region = Region::new(&*self.compositor).map_err(|err| os_error!(err))?; region.add(0, 0, 0, 0); surface.set_input_region(Some(region.wl_region())); Ok(()) } } fn current_monitor(&self) -> Option { let data = self.window.wl_surface().data::()?; data.outputs() .next() .map(MonitorHandle::new) .map(crate::platform_impl::MonitorHandle::Wayland) .map(|inner| CoreMonitorHandle { inner }) } fn available_monitors(&self) -> Box> { Box::new( self.monitors .lock() .unwrap() .clone() .into_iter() .map(crate::platform_impl::MonitorHandle::Wayland) .map(|inner| CoreMonitorHandle { inner }), ) } fn primary_monitor(&self) -> Option { // NOTE: There's no such concept on Wayland. None } /// Get the raw-window-handle v0.6 display handle. fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle { self } /// Get the raw-window-handle v0.6 window handle. fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle { self } } /// The request from the window to the event loop. #[derive(Debug)] pub struct WindowRequests { /// The window was closed. pub closed: AtomicBool, /// Redraw Requested. pub redraw_requested: AtomicBool, } impl WindowRequests { pub fn take_closed(&self) -> bool { self.closed.swap(false, Ordering::Relaxed) } pub fn take_redraw_requested(&self) -> bool { self.redraw_requested.swap(false, Ordering::Relaxed) } } impl TryFrom<&str> for Theme { type Error = (); /// ``` /// use winit::window::Theme; /// /// assert_eq!("dark".try_into(), Ok(Theme::Dark)); /// assert_eq!("lIghT".try_into(), Ok(Theme::Light)); /// ``` fn try_from(theme: &str) -> Result { if theme.eq_ignore_ascii_case("dark") { Ok(Self::Dark) } else if theme.eq_ignore_ascii_case("light") { Ok(Self::Light) } else { Err(()) } } }