use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_display::WlDisplay;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Proxy, QueueHandle};
use sctk::compositor::{CompositorState, Region, SurfaceData};
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 crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Ime, WindowEvent};
use crate::event_loop::AsyncRequestSerial;
use crate::platform_impl::{
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon,
};
use crate::window::{
Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
};
use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
use super::state::WinitState;
use super::types::xdg_activation::XdgActivationTokenData;
use super::{ActiveEventLoop, WaylandError, WindowId};
pub(crate) mod state;
pub use state::WindowState;
pub struct Window {
window: SctkWindow,
window_id: WindowId,
window_state: Arc<Mutex<WindowState>>,
compositor: Arc<CompositorState>,
#[allow(dead_code)]
display: WlDisplay,
xdg_activation: Option<XdgActivationV1>,
attention_requested: Arc<AtomicBool>,
queue_handle: QueueHandle<WinitState>,
window_requests: Arc<WindowRequests>,
monitors: Arc<Mutex<Vec<MonitorHandle>>>,
event_loop_awakener: calloop::ping::Ping,
window_events_sink: Arc<Mutex<EventSink>>,
}
impl Window {
pub(crate) fn new(
event_loop_window_target: &ActiveEventLoop,
attributes: WindowAttributes,
) -> Result<Self, RootOsError> {
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.inner_size.unwrap_or(LogicalSize::new(800., 600.).into());
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,
);
window_state.set_transparent(attributes.transparent);
window_state.set_blur(attributes.blur);
window_state.set_decorate(attributes.decorations);
if let Some(name) = attributes.platform_specific.name.map(|name| name.general) {
window.set_app_id(name);
}
window_state.set_title(attributes.title);
let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.));
let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.));
window_state.set_min_inner_size(min_size);
window_state.set_max_inner_size(max_size);
window_state.set_resizable(attributes.resizable);
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),
}
if let (Some(xdg_activation), Some(token)) =
(xdg_activation.as_ref(), attributes.platform_specific.activation_token)
{
xdg_activation.activate(token._token, &surface);
}
window.commit();
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());
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();
event_queue.roundtrip(&mut state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
})?;
while !window_state.lock().unwrap().is_configured() {
event_queue.blocking_dispatch(&mut state).map_err(|error| {
os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch(error))))
})?;
}
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 {
#[inline]
pub fn id(&self) -> WindowId {
self.window_id
}
#[inline]
pub fn set_title(&self, title: impl ToString) {
let new_title = title.to_string();
self.window_state.lock().unwrap().set_title(new_title);
}
#[inline]
pub fn set_visible(&self, _visible: bool) {
}
#[inline]
pub fn is_visible(&self) -> Option<bool> {
None
}
#[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
Err(NotSupportedError::new())
}
#[inline]
pub fn set_outer_position(&self, _: Position) {
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
let window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor();
super::logical_to_physical_rounded(window_state.inner_size(), scale_factor)
}
#[inline]
pub fn request_redraw(&self) {
if self
.window_requests
.redraw_requested
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
self.event_loop_awakener.ping();
}
}
#[inline]
pub fn pre_present_notify(&self) {
self.window_state.lock().unwrap().request_frame_callback();
}
#[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> {
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)
}
#[inline]
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
let mut window_state = self.window_state.lock().unwrap();
let new_size = window_state.request_inner_size(size);
self.request_redraw();
Some(new_size)
}
#[inline]
pub fn set_min_inner_size(&self, min_size: Option<Size>) {
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_inner_size(min_size);
self.request_redraw();
}
#[inline]
pub fn set_max_inner_size(&self, max_size: Option<Size>) {
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_inner_size(max_size);
self.request_redraw();
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
#[inline]
pub fn set_resize_increments(&self, _increments: Option<Size>) {
warn!("`set_resize_increments` is not implemented for Wayland");
}
#[inline]
pub fn set_transparent(&self, transparent: bool) {
self.window_state.lock().unwrap().set_transparent(transparent);
}
#[inline]
pub fn has_focus(&self) -> bool {
self.window_state.lock().unwrap().has_focus()
}
#[inline]
pub fn is_minimized(&self) -> Option<bool> {
None
}
#[inline]
pub 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);
}
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
self.window_state.lock().unwrap().drag_resize_window(direction)
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
if self.window_state.lock().unwrap().set_resizable(resizable) {
self.request_redraw();
}
}
#[inline]
pub fn is_resizable(&self) -> bool {
self.window_state.lock().unwrap().resizable()
}
#[inline]
pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {
}
#[inline]
pub fn enabled_buttons(&self) -> WindowButtons {
WindowButtons::all()
}
#[inline]
pub fn scale_factor(&self) -> f64 {
self.window_state.lock().unwrap().scale_factor()
}
#[inline]
pub fn set_blur(&self, blur: bool) {
self.window_state.lock().unwrap().set_blur(blur);
}
#[inline]
pub fn set_decorations(&self, decorate: bool) {
self.window_state.lock().unwrap().set_decorate(decorate)
}
#[inline]
pub fn is_decorated(&self) -> bool {
self.window_state.lock().unwrap().is_decorated()
}
#[inline]
pub fn set_window_level(&self, _level: WindowLevel) {}
#[inline]
pub(crate) fn set_window_icon(&self, _window_icon: Option<PlatformIcon>) {}
#[inline]
pub fn set_minimized(&self, minimized: bool) {
if !minimized {
warn!("Unminimizing is ignored on Wayland.");
return;
}
self.window.set_minimized();
}
#[inline]
pub fn is_maximized(&self) -> bool {
self.window_state
.lock()
.unwrap()
.last_configure
.as_ref()
.map(|last_configure| last_configure.is_maximized())
.unwrap_or_default()
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
if maximized {
self.window.set_maximized()
} else {
self.window.unset_maximized()
}
}
#[inline]
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
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().map(PlatformMonitorHandle::Wayland);
Some(Fullscreen::Borderless(current_monitor))
} else {
None
}
}
#[inline]
pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
match fullscreen {
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,
});
self.window.set_fullscreen(output.as_ref())
},
None => self.window.unset_fullscreen(),
}
}
#[inline]
pub 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),
}
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
self.window_state.lock().unwrap().set_cursor_visible(visible);
}
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
let xdg_activation = match self.xdg_activation.as_ref() {
Some(xdg_activation) => xdg_activation,
None => {
warn!("`request_user_attention` isn't supported");
return;
},
};
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();
}
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
let xdg_activation = match self.xdg_activation.as_ref() {
Some(xdg_activation) => xdg_activation,
None => return Err(NotSupportedError::new()),
};
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 set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
self.window_state.lock().unwrap().set_cursor_grab(mode)
}
#[inline]
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
let scale_factor = self.scale_factor();
let position = position.to_logical(scale_factor);
self.window_state
.lock()
.unwrap()
.set_cursor_position(position)
.map(|_| self.request_redraw())
}
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
self.window_state.lock().unwrap().drag_window()
}
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
let surface = self.window.wl_surface();
if hittest {
surface.set_input_region(None);
Ok(())
} else {
let region = Region::new(&*self.compositor).map_err(|_| {
ExternalError::Os(os_error!(OsError::Misc("failed to set input region.")))
})?;
region.add(0, 0, 0, 0);
surface.set_input_region(Some(region.wl_region()));
Ok(())
}
}
#[inline]
pub 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]
pub 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]
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
self.window_state.lock().unwrap().set_ime_purpose(purpose);
}
#[inline]
pub fn focus_window(&self) {}
#[inline]
pub fn surface(&self) -> &WlSurface {
self.window.wl_surface()
}
#[inline]
pub fn current_monitor(&self) -> Option<MonitorHandle> {
let data = self.window.wl_surface().data::<SurfaceData>()?;
data.outputs().next().map(MonitorHandle::new)
}
#[inline]
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
self.monitors.lock().unwrap().clone()
}
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
None
}
#[cfg(feature = "rwh_04")]
#[inline]
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::WaylandHandle::empty();
window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _;
window_handle.display = self.display.id().as_ptr() as *mut _;
rwh_04::RawWindowHandle::Wayland(window_handle)
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
let mut window_handle = rwh_05::WaylandWindowHandle::empty();
window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _;
rwh_05::RawWindowHandle::Wayland(window_handle)
}
#[cfg(feature = "rwh_05")]
#[inline]
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
let mut display_handle = rwh_05::WaylandDisplayHandle::empty();
display_handle.display = self.display.id().as_ptr() as *mut _;
rwh_05::RawDisplayHandle::Wayland(display_handle)
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
Ok(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")
})
.into())
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(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")
})
.into())
}
#[inline]
pub fn set_theme(&self, theme: Option<Theme>) {
self.window_state.lock().unwrap().set_theme(theme)
}
#[inline]
pub fn theme(&self) -> Option<Theme> {
self.window_state.lock().unwrap().theme()
}
pub fn set_content_protected(&self, _protected: bool) {}
#[inline]
pub fn title(&self) -> String {
self.window_state.lock().unwrap().title().to_owned()
}
}
impl Drop for Window {
fn drop(&mut self) {
self.window_requests.closed.store(true, Ordering::Relaxed);
self.event_loop_awakener.ping();
}
}
#[derive(Debug)]
pub struct WindowRequests {
pub closed: AtomicBool,
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 = ();
fn try_from(theme: &str) -> Result<Self, Self::Error> {
if theme.eq_ignore_ascii_case("dark") {
Ok(Self::Dark)
} else if theme.eq_ignore_ascii_case("light") {
Ok(Self::Light)
} else {
Err(())
}
}
}