Wayland, Windows, MacOS: Popup Implementation (#4543)

Implement proper decorationless popups by specifying the type of the child window with with_type()

With this commit, different kind of child windows can be created

-  Popups: Special windows without any decoration which can be positioned relative to the parent
-  Window: Normal window with a parent or not

The type can be specified during creation of the Window using the window_attributes and the with_type() function. As default a normal Window is used. If Popup is choosen a parent must be specified, otherwise the Popup creation fails with an Error returned by the new() function.

Related issues: #403 and #4256
This commit is contained in:
Martin Marmsoler
2026-07-27 15:43:51 +02:00
committed by GitHub
parent 1fe178b2a6
commit 9674d8ceef
22 changed files with 2035 additions and 368 deletions

View File

@@ -36,7 +36,7 @@ use winit_core::event_loop::{
};
use winit_core::icon::RgbaIcon;
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::Theme;
use winit_core::window::{Theme, WindowType};
use crate::dnd::{MimeData, dnd_action_winit_to_wl};
use crate::types::cursor::WaylandCustomCursor;
@@ -322,6 +322,35 @@ impl EventLoop {
self.single_iteration(app, cause);
}
/// Recursive closing all windows from the child to the parent
fn find_windows_to_close(
window_id: &WindowId,
state: &mut WinitState,
out: &mut Vec<WindowId>,
) -> bool {
if !state.window_requests.get_mut().get(window_id).unwrap().closed.load(Ordering::Relaxed) {
return false;
}
out.push(*window_id);
fn window_to_close(window_id: &WindowId, out: &mut Vec<WindowId>, state: &mut WinitState) {
// We don't need to check here if it should be closed, because if the parent should
// be closed all children must be closed as well
let Some(window_state) = state.windows.get_mut().get(window_id) else {
return;
};
let children = window_state.lock().unwrap().children().clone();
// First all children and then all subchildren
out.extend(&children);
for child in children.clone() {
window_to_close(&child, out, state);
}
}
window_to_close(window_id, out, state);
true
}
fn single_iteration<A: ApplicationHandler>(&mut self, app: &mut A, cause: StartCause) {
// NOTE currently just indented to simplify the diff
@@ -457,14 +486,33 @@ impl EventLoop {
});
for window_id in window_ids.iter() {
let event = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(window_id));
mem::drop(state.windows.get_mut().remove(window_id));
return Some(WindowEvent::Destroyed);
}
if self.with_state(|state| state.window_requests.get_mut().get(window_id).is_none()) {
continue; // The element might not exist anymore so just ignore
}
let mut windows_to_close = Vec::new();
if self.with_state(|state| {
Self::find_windows_to_close(window_id, state, &mut windows_to_close)
}) {
for w in windows_to_close.into_iter().rev() {
self.with_state(|state| {
let parent =
state.windows.get_mut().get_mut(&w).unwrap().lock().unwrap().parent();
if let Some(p) = parent.and_then(|p| state.windows.get_mut().get_mut(&p)) {
p.lock().unwrap().remove_child(&w)
}
let window_requests = state.window_requests.get_mut();
window_requests.get(&w).unwrap().take_closed();
mem::drop(window_requests.remove(&w));
mem::drop(state.windows.get_mut().remove(&w));
});
app.window_event(&self.active_event_loop, w, WindowEvent::Destroyed);
}
continue;
}
let event = self.with_state(|state| {
let mut window =
state.windows.get_mut().get_mut(window_id).unwrap().lock().unwrap();
@@ -474,6 +522,7 @@ impl EventLoop {
// Reset the frame callbacks state.
window.frame_callback_reset();
let window_requests = state.window_requests.get_mut();
let mut redraw_requested =
window_requests.get(window_id).unwrap().take_redraw_requested();
@@ -662,8 +711,17 @@ impl RootActiveEventLoop for ActiveEventLoop {
&self,
window_attributes: winit_core::window::WindowAttributes,
) -> Result<Box<dyn winit_core::window::Window>, RequestError> {
let window = crate::Window::new(self, window_attributes)?;
Ok(Box::new(window))
match window_attributes.window_type() {
WindowType::Window => {
let window = crate::Window::new(self, window_attributes)?;
Ok(Box::new(window))
},
WindowType::Popup => {
let popup = crate::Popup::new(self, window_attributes)?;
Ok(Box::new(popup))
},
_ => Err(RequestError::NotSupported(NotSupportedError::new("Unsupported window type"))),
}
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {

View File

@@ -21,7 +21,7 @@ use std::ffi::c_void;
use std::hash::BuildHasher;
use std::ptr::NonNull;
use dpi::{LogicalSize, PhysicalSize};
use dpi::{LogicalSize, PhysicalSize, Position, Size};
use sctk::reexports::client::Proxy;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
@@ -40,6 +40,7 @@ macro_rules! os_error {
mod dnd;
mod event_loop;
mod output;
mod popup;
mod seat;
mod state;
mod types;
@@ -47,6 +48,7 @@ mod window;
pub use self::dnd::{DataOffer, DragSource, MimeData, MimeType};
pub use self::event_loop::{ActiveEventLoop, EventLoop};
pub use self::popup::Popup;
pub use self::window::Window;
/// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
@@ -95,6 +97,120 @@ impl WindowExtWayland for dyn CoreWindow + '_ {
}
}
/// Additional methods on [`Popup`] that are specific to Wayland.
pub trait PopupExtWayland {
fn anchor_rect(&self) -> Option<(impl Into<Position>, impl Into<Size>)>;
/// Sets the anchor edge of the parent surface the popup is positioned relative to.
///
/// See [`PopupAnchor`] for the available edges and corners.
fn set_anchor(&self, anchor: PopupAnchor);
/// Sets the anchor rectangle within the parent surface the popup is positioned relative to.
///
/// `position` is the top-left corner of the rectangle relative to the parent window's content
/// area, and `size` its dimensions.
fn set_anchor_rect(&self, position: impl Into<Position>, size: impl Into<Size>);
/// Sets how the compositor should reposition the popup when it would be constrained by screen
/// edges.
///
/// See [`PopupConstraintAdjustment`] for the available adjustment flags.
fn set_constraint_adjustment(&self, constraint_adjustment: PopupConstraintAdjustment);
/// Sets the direction the popup surface extends from the anchor point.
///
/// See [`PopupGravity`] for the available directions.
fn set_gravity(&self, gravity: PopupGravity);
/// Set the popup position relative to the anchor rect
fn set_positioner_offset(&self, position: impl Into<Position>);
}
impl PopupExtWayland for dyn CoreWindow + '_ {
fn set_anchor(&self, anchor: PopupAnchor) {
if let Some(popup) = self.cast_ref::<Popup>() {
popup.set_anchor(anchor);
}
}
fn anchor_rect(&self) -> Option<(impl Into<Position>, impl Into<Size>)> {
if let Some(popup) = self.cast_ref::<Popup>() { popup.anchor_rect() } else { None }
}
fn set_anchor_rect(&self, position: impl Into<Position>, size: impl Into<Size>) {
if let Some(popup) = self.cast_ref::<Popup>() {
popup.set_anchor_rect(position, size);
}
}
fn set_constraint_adjustment(&self, constraint_adjustment: PopupConstraintAdjustment) {
if let Some(popup) = self.cast_ref::<Popup>() {
popup.set_constraint_adjustment(constraint_adjustment);
}
}
fn set_gravity(&self, gravity: PopupGravity) {
if let Some(popup) = self.cast_ref::<Popup>() {
popup.set_gravity(gravity);
}
}
fn set_positioner_offset(&self, position: impl Into<Position>) {
if let Some(popup) = self.cast_ref::<Popup>() {
popup.set_positioner_offset(position);
}
}
}
/// Anchor rect within the parent surface
/// See: https://wayland.app/protocols/xdg-shell#xdg_positioner:request:set_anchor_rect
#[derive(Debug, Clone, Copy, Default, PartialEq)]
#[non_exhaustive]
pub enum PopupAnchor {
#[default]
None,
Top,
Bottom,
Left,
Right,
TopLeft,
BottomLeft,
TopRight,
BottomRight,
}
/// Defines in what direction a surface should be positioned
/// See: https://wayland.app/protocols/xdg-shell#xdg_positioner:request:set_gravity
#[derive(Debug, Clone, Copy, Default, PartialEq)]
#[non_exhaustive]
pub enum PopupGravity {
#[default]
None,
Top,
Bottom,
Left,
Right,
TopLeft,
BottomLeft,
TopRight,
BottomRight,
}
bitflags::bitflags! {
/// Specify how the window should be positioned if the originally intended position caused the
/// surface to be constrained See: https://wayland.app/protocols/xdg-shell#xdg_positioner:request:set_constraint_adjustment
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct PopupConstraintAdjustment: u32 {
const SLIDE_X = 1 << 0;
const SLIDE_Y = 1 << 1;
const FLIP_X = 1 << 2;
const FLIP_Y = 1 << 3;
const RESIZE_X = 1 << 4;
const RESIZE_Y = 1 << 5;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ApplicationName {
pub(crate) general: String,
@@ -107,6 +223,13 @@ pub struct WindowAttributesWayland {
pub(crate) name: Option<ApplicationName>,
pub(crate) activation_token: Option<ActivationToken>,
pub(crate) prefer_csd: bool,
pub(crate) anchor: Option<PopupAnchor>,
pub(crate) anchor_rect: Option<(Position, Size)>,
/// Only for Popup: The offset of the popup to the
/// anchor rect
pub(crate) positioner_offset: Option<Position>,
pub(crate) gravity: Option<PopupGravity>,
pub(crate) constraint_adjustment: Option<PopupConstraintAdjustment>,
}
impl WindowAttributesWayland {
@@ -123,6 +246,11 @@ impl WindowAttributesWayland {
self
}
/// Sets an activation token to use when creating the window.
///
/// The activation token allows the compositor to grant focus to the new window,
/// overriding focus-stealing prevention. Obtain a token via
/// [`ActiveEventLoop::request_activation_token`].
#[inline]
pub fn with_activation_token(mut self, token: ActivationToken) -> Self {
self.activation_token = Some(token);
@@ -140,6 +268,63 @@ impl WindowAttributesWayland {
self.prefer_csd = prefer_csd;
self
}
/// Sets the edge or corner of the anchor rectangle the popup is attached to.
///
/// Combined with [`with_gravity`](Self::with_gravity), this controls which corner/edge of the
/// anchor rectangle the popup is pinned to. Has no effect unless the window is created as a
/// [`Popup`](winit_core::window::WindowType::Popup).
#[inline]
pub fn with_anchor(mut self, anchor: PopupAnchor) -> Self {
self.anchor = Some(anchor);
self
}
/// Set the anchor rectangle the popup is positioned relative to.
///
/// `position` is the top-left corner of the rectangle relative to the parent window's content
/// area, and `size` its dimensions. Defaults to a `1x1` rectangle at the content origin.
/// This value overwrites the position value set with `with_position` in the window attributes
#[inline]
pub fn with_anchor_rect(
mut self,
position: impl Into<Position>,
size: impl Into<Size>,
) -> Self {
self.anchor_rect = Some((position.into(), size.into()));
self
}
/// Set the popup position relative to the anchor rect
pub fn with_positioner_offset(mut self, position: impl Into<Position>) -> Self {
self.positioner_offset = Some(position.into());
self
}
/// Sets how the compositor should reposition the popup when it would be constrained.
///
/// The flags in [`PopupConstraintAdjustment`] can be combined to allow sliding, flipping,
/// and/or resizing the popup independently on each axis. Has no effect unless the window is
/// created as a [`Popup`](winit_core::window::WindowType::Popup).
#[inline]
pub fn with_constraint_adjustment(
mut self,
constraint_adjustment: PopupConstraintAdjustment,
) -> Self {
self.constraint_adjustment = Some(constraint_adjustment);
self
}
/// Sets the direction the popup surface extends away from the anchor point.
///
/// Combined with [`with_anchor`](Self::with_anchor), this determines the final position of the
/// popup relative to its anchor rectangle. Has no effect unless the window is created as a
/// [`Popup`](winit_core::window::WindowType::Popup).
#[inline]
pub fn with_gravity(mut self, gravity: PopupGravity) -> Self {
self.gravity = Some(gravity);
self
}
}
impl PlatformWindowAttributes for WindowAttributesWayland {
@@ -195,3 +380,64 @@ fn image_to_buffer(
Ok(buffer)
}
impl From<PopupGravity> for wayland_protocols::xdg::shell::client::xdg_positioner::Gravity {
fn from(value: PopupGravity) -> Self {
use wayland_protocols::xdg::shell::client::xdg_positioner::Gravity;
match value {
PopupGravity::None => Gravity::None,
PopupGravity::Top => Gravity::Top,
PopupGravity::Bottom => Gravity::Bottom,
PopupGravity::Left => Gravity::Left,
PopupGravity::Right => Gravity::Right,
PopupGravity::TopLeft => Gravity::TopLeft,
PopupGravity::BottomLeft => Gravity::BottomLeft,
PopupGravity::TopRight => Gravity::TopRight,
PopupGravity::BottomRight => Gravity::BottomRight,
}
}
}
impl From<PopupAnchor> for wayland_protocols::xdg::shell::client::xdg_positioner::Anchor {
fn from(value: PopupAnchor) -> Self {
use wayland_protocols::xdg::shell::client::xdg_positioner::Anchor;
match value {
PopupAnchor::None => Anchor::None,
PopupAnchor::Top => Anchor::Top,
PopupAnchor::Bottom => Anchor::Bottom,
PopupAnchor::Left => Anchor::Left,
PopupAnchor::Right => Anchor::Right,
PopupAnchor::TopLeft => Anchor::TopLeft,
PopupAnchor::BottomLeft => Anchor::BottomLeft,
PopupAnchor::TopRight => Anchor::TopRight,
PopupAnchor::BottomRight => Anchor::BottomRight,
}
}
}
impl From<PopupConstraintAdjustment>
for wayland_protocols::xdg::shell::client::xdg_positioner::ConstraintAdjustment
{
fn from(value: PopupConstraintAdjustment) -> Self {
use wayland_protocols::xdg::shell::client::xdg_positioner::ConstraintAdjustment;
const _: () = {
assert!(
PopupConstraintAdjustment::SLIDE_X.bits() == ConstraintAdjustment::SlideX.bits()
);
assert!(
PopupConstraintAdjustment::SLIDE_Y.bits() == ConstraintAdjustment::SlideY.bits()
);
assert!(PopupConstraintAdjustment::FLIP_X.bits() == ConstraintAdjustment::FlipX.bits());
assert!(PopupConstraintAdjustment::FLIP_Y.bits() == ConstraintAdjustment::FlipY.bits());
assert!(
PopupConstraintAdjustment::RESIZE_X.bits() == ConstraintAdjustment::ResizeX.bits()
);
assert!(
PopupConstraintAdjustment::RESIZE_Y.bits() == ConstraintAdjustment::ResizeY.bits()
);
};
ConstraintAdjustment::from_bits_retain(value.bits())
}
}

747
winit-wayland/src/popup.rs Normal file
View File

@@ -0,0 +1,747 @@
use core::sync::atomic::Ordering;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex, Weak};
use dpi::{
LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size,
};
use rwh_06::RawWindowHandle;
use sctk::compositor::SurfaceData;
use sctk::shell::WaylandSurface;
use sctk::shell::xdg::popup::Popup as SctkPopup;
use sctk::shell::xdg::{XdgPositioner, XdgSurface};
use wayland_client::Proxy;
use wayland_client::protocol::wl_display::WlDisplay;
use winit_core::cursor::Cursor;
use winit_core::error::{NotSupportedError, RequestError};
use winit_core::event::{Ime, WindowEvent};
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use winit_core::window::{
CursorGrabMode, ImeCapabilities, ImeRequest, ImeRequestError, ResizeDirection, Theme,
UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
WindowLevel,
};
use super::ActiveEventLoop;
use super::output::MonitorHandle;
use crate::window::Handles;
use crate::window::handles::WindowRequests;
use crate::window::state::{WindowState, WindowType};
use crate::{PopupExtWayland, WindowAttributesWayland};
#[derive(Debug)]
pub struct Popup {
/// The state of the popup.
/// The only single truth of the state is stored
/// in the event loop state, because if the server decides to destroy the popup
/// we cannot use it anymore
popup_state: Weak<Mutex<WindowState>>,
/// Window id.
window_id: WindowId,
/// The wayland display used solely for raw window handle.
#[allow(dead_code)]
display: WlDisplay,
handles: Handles,
}
impl Popup {
pub(crate) fn new(
event_loop_window_target: &ActiveEventLoop,
mut attributes: WindowAttributes,
) -> Result<Self, RequestError> {
fn error(message: &'static str) -> RequestError {
RequestError::NotSupported(NotSupportedError::new(message))
}
let parent_window_handle =
attributes.parent_window().ok_or(error("Popup without a parent is not supported!"))?;
if let RawWindowHandle::Wayland(parent_window_handle) = parent_window_handle {
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 xdg_activation = state
.xdg_activation
.as_ref()
.map(|activation_state| activation_state.global().clone());
let positioner = XdgPositioner::new(&state.xdg_shell)
.map_err(|_| error("Failed to create positioner"))?;
let parent_window_id =
WindowId::from_raw(parent_window_handle.surface.as_ptr() as usize);
let (popup, popup_state) = if let Some(parent_window_state) =
state.windows.borrow().get(&parent_window_id)
{
let wayland_attributes = attributes
.platform
.as_ref()
.and_then(|p| p.cast_ref::<WindowAttributesWayland>())
.cloned()
.unwrap_or_default();
let WindowAttributesWayland {
gravity,
anchor,
anchor_rect,
constraint_adjustment,
positioner_offset,
..
} = wayland_attributes;
let grab_keyboard = attributes.active;
let mut parent_window_state = parent_window_state.lock().unwrap();
// Use the scale factor and xdg geometry of the parent.
let scale_factor = parent_window_state.scale_factor();
let size = attributes
.surface_size
.ok_or(error("Invalid size for popup"))?
.to_logical(scale_factor);
if size.width == 0_i32 || size.height == 0_i32 {
return Err(error("The popups size must not be zero"));
}
// Anchoring
// The anchor rect is relative to the parent window geometry, so we need to subtract
// the geometry origin from the position to get the correct anchor rect.
// This is important for client side decorations
let geometry_origin = parent_window_state.content_surface_origin();
let anchor_position = LogicalPosition::new(-geometry_origin.x, -geometry_origin.y);
positioner.set_anchor(anchor.unwrap_or(crate::PopupAnchor::TopLeft).into());
positioner.set_gravity(gravity.unwrap_or(crate::PopupGravity::BottomRight).into());
constraint_adjustment
.inspect(|c| positioner.set_constraint_adjustment((*c).into()));
let (anchor_rect_position, anchor_rect_size) = match anchor_rect {
Some((position, size)) => (
position.to_logical::<i32>(scale_factor),
size.to_logical::<i32>(scale_factor),
),
None => {
// anchor rect was not specified use attributes.position
let pos: LogicalPosition<i32> = attributes
.position
.map(|position| position.to_logical(scale_factor))
.unwrap_or_default();
(pos, LogicalSize::new(1, 1))
},
};
let anchor_rect = (
LogicalPosition::new(
anchor_rect_position.x + anchor_position.x,
anchor_rect_position.y + anchor_position.y,
),
LogicalSize::new(anchor_rect_size.width.max(1), anchor_rect_size.height.max(1)),
);
positioner.set_anchor_rect(
anchor_rect.0.x,
anchor_rect.0.y,
anchor_rect.1.width,
anchor_rect.1.height,
);
positioner_offset.inspect(|o| {
let o = o.to_logical(scale_factor);
positioner.set_offset(o.x, o.y);
});
positioner.set_size(size.width, size.height);
let parent_surface = parent_window_state.window.xdg_surface();
let surface = state.compositor_state.create_surface(&queue_handle);
let popup = SctkPopup::from_surface(
Some(parent_surface),
&positioner,
&queue_handle,
surface.clone(),
&state.xdg_shell,
)
.map_err(|_| error("Failed to create popup"))?;
parent_window_state.add_child(super::make_wid(popup.wl_surface()));
drop(parent_window_state);
let mut popup_state = WindowState::new(
event_loop_window_target,
&state,
size.into(),
WindowType::Popup {
popup: popup.clone(),
positioner,
last_configure: None,
anchor_rect,
parent_origin: geometry_origin,
},
attributes.preferred_theme,
false,
scale_factor,
Some(parent_window_id),
);
// Set transparency hint.
popup_state.set_transparent(attributes.transparent);
// Set blur.
let _ = popup_state.set_blur(attributes.blur);
let WindowAttributesWayland { activation_token, .. } = *attributes
.platform
.take()
.and_then(|p| p.cast::<WindowAttributesWayland>().ok())
.unwrap_or_default();
// Activate the window when the token is passed.
if let (Some(xdg_activation), Some(token)) =
(xdg_activation.as_ref(), activation_token)
{
xdg_activation.activate(token.into_raw(), &surface);
}
// Request a keyboard grab so the compositor routes key events to
// this popup rather than the parent window. Must happen before the
// first commit that maps the surface.
if grab_keyboard {
// Use the seat with the most recent event
let grab = state
.seat_state
.seats()
.filter_map(|seat| {
let serial = state.seats.get(&seat.id())?.latest_serial()?;
Some((seat, serial))
})
.max_by_key(|(_, serial)| *serial);
if let Some((seat, serial)) = grab {
popup.xdg_popup().grab(&seat, serial);
}
}
popup.wl_surface().commit();
// popup.commit(); Trait not implemented in Sctk
let popup_state = Arc::new(Mutex::new(popup_state));
(popup, popup_state)
} else {
return Err(error("Parent window id unknown"));
};
let window_id = super::make_wid(popup.wl_surface());
state.windows.get_mut().insert(window_id, popup_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 !popup_state.lock().unwrap().is_configured() {
event_queue.blocking_dispatch(&mut state).map_err(|err| os_error!(err))?;
// The compositor may dismiss a popup (e.g. invalid grab serial) by sending
// popup_done before configure. Detect that and bail out instead of looping forever.
if state
.window_compositor_updates
.iter()
.any(|u| u.window_id == window_id && u.close_window)
{
return Err(error("Popup was dismissed by the compositor before configure"));
}
}
// 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 {
popup_state: Arc::downgrade(&popup_state),
window_id,
display: event_loop_window_target.handle.connection.display().clone(),
handles: Handles {
queue_handle,
window_requests,
monitors,
event_loop_awakener,
window_events_sink,
xdg_activation,
attention_requested: Arc::new(AtomicBool::new(false)),
compositor: state.compositor_state.clone(),
},
})
} else {
Err(RequestError::NotSupported(NotSupportedError::new(
"A Popup requires a parent wayland window handle",
)))
}
}
}
impl CoreWindow for Popup {
fn window_type(&self) -> winit_core::window::WindowType {
winit_core::window::WindowType::Popup
}
fn id(&self) -> WindowId {
self.window_id
}
fn request_redraw(&self) {
self.handles.request_redraw();
}
#[inline]
fn title(&self) -> String {
let Some(s) = self.popup_state.upgrade() else { return String::new() };
s.lock().unwrap().title().to_owned()
}
fn pre_present_notify(&self) {
let Some(s) = self.popup_state.upgrade() else { return };
s.lock().unwrap().request_frame_callback();
}
fn reset_dead_keys(&self) {
winit_common::xkb::reset_dead_keys()
}
fn surface_position(&self) -> PhysicalPosition<i32> {
(0, 0).into()
}
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
let s = self
.popup_state
.upgrade()
.ok_or_else(|| NotSupportedError::new("the popup has been destroyed"))?;
let state = s.lock().unwrap();
if let WindowType::Popup { last_configure: Some(configure), .. } = &state.window {
let (x, y) = configure.position;
return Ok(LogicalPosition::new(x, y).to_physical(state.scale_factor()));
}
Err(NotSupportedError::new("the popup has not been configured yet").into())
}
fn set_outer_position(&self, position: Position) {
let Some(s) = self.popup_state.upgrade() else { return };
let mut state = s.lock().unwrap();
let scale_factor = state.scale_factor();
if let WindowType::Popup { popup, positioner, anchor_rect, parent_origin, .. } =
&mut state.window
{
let position = position.to_logical(scale_factor);
anchor_rect.0 = position;
positioner.set_anchor_rect(
anchor_rect.0.x - parent_origin.x,
anchor_rect.0.y - parent_origin.y,
anchor_rect.1.width,
anchor_rect.1.height,
);
if popup.xdg_popup().version() >= 3 {
popup.reposition(positioner, 0);
}
}
}
fn surface_size(&self) -> PhysicalSize<u32> {
let Some(s) = self.popup_state.upgrade() else { return PhysicalSize::default() };
let popup_state = s.lock().unwrap();
let scale_factor = popup_state.scale_factor();
super::logical_to_physical_rounded(popup_state.surface_size(), scale_factor)
}
fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
let s = self.popup_state.upgrade()?;
let mut popup_state = s.lock().unwrap();
let new_size = popup_state.request_surface_size(size);
self.request_redraw();
Some(new_size)
}
fn outer_size(&self) -> PhysicalSize<u32> {
let Some(s) = self.popup_state.upgrade() else { return PhysicalSize::default() };
let popup_state = s.lock().unwrap();
let scale_factor = popup_state.scale_factor();
super::logical_to_physical_rounded(popup_state.outer_size(), scale_factor)
}
fn safe_area(&self) -> PhysicalInsets<u32> {
PhysicalInsets::new(0, 0, 0, 0)
}
fn set_min_surface_size(&self, min_size: Option<Size>) {
let scale_factor = self.scale_factor();
let min_size = min_size.map(|size| size.to_logical(scale_factor));
let Some(s) = self.popup_state.upgrade() else { return };
s.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<Size>) {
let scale_factor = self.scale_factor();
let max_size = max_size.map(|size| size.to_logical(scale_factor));
let Some(s) = self.popup_state.upgrade() else { return };
s.lock().unwrap().set_max_surface_size(max_size);
// NOTE: Requires commit to be applied.
self.request_redraw();
}
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
let s = self.popup_state.upgrade()?;
let popup_state = s.lock().unwrap();
let scale_factor = popup_state.scale_factor();
popup_state
.resize_increments()
.map(|size| super::logical_to_physical_rounded(size, scale_factor))
}
fn set_surface_resize_increments(&self, increments: Option<Size>) {
let Some(s) = self.popup_state.upgrade() else { return };
let mut popup_state = s.lock().unwrap();
let scale_factor = popup_state.scale_factor();
let increments = increments.map(|size| size.to_logical(scale_factor));
popup_state.set_resize_increments(increments);
}
fn set_title(&self, title: &str) {
let Some(s) = self.popup_state.upgrade() else { return };
s.lock().unwrap().set_title(title.to_owned());
}
#[inline]
fn set_transparent(&self, transparent: bool) {
let Some(s) = self.popup_state.upgrade() else { return };
s.lock().unwrap().set_transparent(transparent);
}
fn set_visible(&self, _visible: bool) {
// Not possible on Wayland.
}
fn is_visible(&self) -> Option<bool> {
None
}
fn set_resizable(&self, _resizable: bool) {
// A popup cannot be resized with the mouse
}
fn is_resizable(&self) -> bool {
// A popup cannot be resized with the mouse
false
}
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) {
// Not possible for popups
}
fn is_minimized(&self) -> Option<bool> {
// XXX clients don't know whether they are minimized or not.
None
}
fn set_maximized(&self, _maximized: bool) {
// Not possible for popups
}
fn is_maximized(&self) -> bool {
// Not possible for popups
false
}
fn set_fullscreen(&self, _fullscreen: Option<Fullscreen>) {
// Not possible for popups
}
fn fullscreen(&self) -> Option<Fullscreen> {
None
}
#[inline]
fn scale_factor(&self) -> f64 {
let Some(s) = self.popup_state.upgrade() else { return 1.0 };
s.lock().unwrap().scale_factor()
}
#[inline]
fn set_blur(&self, blur: bool) {
let Some(s) = self.popup_state.upgrade() else { return };
if s.lock().unwrap().set_blur(blur) {
self.request_redraw();
}
}
#[inline]
fn set_decorations(&self, _decorate: bool) {
// Popup does not support decorations
}
#[inline]
fn is_decorated(&self) -> bool {
// Popup does not support decorations
false
}
fn set_window_level(&self, _level: WindowLevel) {
// Popup does not have a window level
}
fn set_window_icon(&self, _window_icon: Option<winit_core::icon::Icon>) {
// Popup does not have a window icon
}
#[inline]
fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
let Some(s) = self.popup_state.upgrade() else { return Ok(()) };
let state_changed = s.lock().unwrap().request_ime_update(request)?;
if let Some(allowed) = state_changed {
let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled });
self.handles.push_window_event(event, self.window_id);
}
Ok(())
}
#[inline]
fn ime_capabilities(&self) -> Option<ImeCapabilities> {
let s = self.popup_state.upgrade()?;
s.lock().unwrap().ime_allowed()
}
fn focus_window(&self) {}
fn has_focus(&self) -> bool {
let Some(s) = self.popup_state.upgrade() else { return false };
s.lock().unwrap().has_focus()
}
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
if let Some(state) = self.popup_state.upgrade() {
let state = state.lock().unwrap();
let surface = state.window.wl_surface();
self.handles.request_user_attention(surface, request_type);
}
}
fn set_theme(&self, _theme: Option<Theme>) {
// A popup does not have a frame
}
fn theme(&self) -> Option<Theme> {
// A popup does not have a frame
None
}
fn set_content_protected(&self, _protected: bool) {}
fn set_cursor(&self, cursor: Cursor) {
let Some(s) = self.popup_state.upgrade() else { return };
let mut popup_state = s.lock().unwrap();
match cursor {
Cursor::Icon(icon) => popup_state.set_cursor(icon),
Cursor::Custom(cursor) => popup_state.set_custom_cursor(cursor),
}
}
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
let Some(s) = self.popup_state.upgrade() else { return Err(RequestError::Ignored) };
let scale_factor = s.lock().unwrap().scale_factor();
let position = position.to_logical(scale_factor);
s.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> {
let Some(s) = self.popup_state.upgrade() else { return Err(RequestError::Ignored) };
s.lock().unwrap().set_cursor_grab(mode)
}
fn set_cursor_visible(&self, visible: bool) {
let Some(s) = self.popup_state.upgrade() else { return };
s.lock().unwrap().set_cursor_visible(visible);
}
fn drag_window(&self) -> Result<(), RequestError> {
// Popup does not support dragging
Err(RequestError::Ignored)
}
fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), RequestError> {
// Popup does not support dragging
Err(RequestError::Ignored)
}
fn show_window_menu(&self, _position: Position) {
// A popup does not have a menu
}
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
let Some(state) = self.popup_state.upgrade() else {
return Err(RequestError::Ignored);
};
self.handles.set_cursor_hittest(state.lock().unwrap().window.wl_surface(), hittest)
}
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
let state = self.popup_state.upgrade()?;
let state = state.lock().unwrap();
let data = state.window.wl_surface().data::<SurfaceData<()>>()?;
data.outputs()
.next()
.map(MonitorHandle::new)
.map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
self.handles.available_monitors()
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
// 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
}
}
impl Drop for Popup {
fn drop(&mut self) {
self.handles.window_requests.closed.store(true, Ordering::Relaxed);
self.handles.event_loop_awakener.ping();
}
}
impl rwh_06::HasWindowHandle for Popup {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let state = self.popup_state.upgrade().ok_or(rwh_06::HandleError::Unavailable)?;
let raw = rwh_06::WaylandWindowHandle::new({
let ptr = state.lock().unwrap().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 Popup {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
if self.popup_state.upgrade().is_none() {
return Err(rwh_06::HandleError::Unavailable);
};
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 PopupExtWayland for Popup {
fn set_anchor(&self, anchor: crate::PopupAnchor) {
let Some(state) = self.popup_state.upgrade() else {
return;
};
if let WindowType::Popup { popup, positioner, .. } = &state.lock().unwrap().window {
positioner.set_anchor(anchor.into());
popup.reposition(positioner, 0);
}
}
fn anchor_rect(&self) -> Option<(impl Into<Position>, impl Into<Size>)> {
let state = self.popup_state.upgrade()?;
if let WindowType::Popup { anchor_rect, .. } = &state.lock().unwrap().window {
Some(*anchor_rect)
} else {
None
}
}
fn set_anchor_rect(&self, position: impl Into<Position>, size: impl Into<Size>) {
let Some(state) = self.popup_state.upgrade() else {
return;
};
let state = state.lock().unwrap();
let scale_factor = state.scale_factor();
let size: LogicalSize<i32> = size.into().to_logical(scale_factor);
let position: LogicalPosition<i32> = position.into().to_logical(scale_factor);
if let WindowType::Popup { popup, positioner, parent_origin, .. } = &state.window {
positioner.set_anchor_rect(
position.x - parent_origin.x,
position.y - parent_origin.y,
size.width.max(1),
size.height.max(1),
);
popup.reposition(positioner, 0);
}
}
fn set_positioner_offset(&self, position: impl Into<Position>) {
let Some(state) = self.popup_state.upgrade() else {
return;
};
let scale_factor = state.lock().unwrap().scale_factor();
let position: LogicalPosition<i32> = position.into().to_logical(scale_factor);
if let WindowType::Popup { popup, positioner, .. } = &state.lock().unwrap().window {
positioner.set_offset(position.x, position.y);
popup.reposition(positioner, 0);
}
}
fn set_constraint_adjustment(&self, constraint_adjustment: crate::PopupConstraintAdjustment) {
let Some(state) = self.popup_state.upgrade() else {
return;
};
if let WindowType::Popup { popup, positioner, .. } = &state.lock().unwrap().window {
positioner.set_constraint_adjustment(constraint_adjustment.into());
popup.reposition(positioner, 0);
}
}
fn set_gravity(&self, gravity: crate::PopupGravity) {
let Some(state) = self.popup_state.upgrade() else {
return;
};
if let WindowType::Popup { popup, positioner, .. } = &state.lock().unwrap().window {
positioner.set_gravity(gravity.into());
popup.reposition(positioner, 0);
}
}
}

View File

@@ -129,9 +129,10 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
state.events_sink.push_window_event(WindowEvent::Focused(false), window_id);
}
},
WlKeyboardEvent::Key { key, state: WEnum::Value(key_state), .. }
WlKeyboardEvent::Key { serial, key, state: WEnum::Value(key_state), .. }
if matches!(key_state, WlKeyState::Repeated | WlKeyState::Pressed) =>
{
seat_state.latest_input_serial.set(Some(serial));
let key = key + 8;
key_input(
keyboard_state,
@@ -204,7 +205,10 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
})
.ok();
},
WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Released), .. } => {
WlKeyboardEvent::Key {
serial, key, state: WEnum::Value(WlKeyState::Released), ..
} => {
seat_state.latest_input_serial.set(Some(serial));
let key = key + 8;
key_input(

View File

@@ -1,5 +1,6 @@
//! Seat handling.
use std::cell::Cell;
use std::sync::Arc;
use foldhash::HashMap;
@@ -76,6 +77,11 @@ pub struct WinitSeatState {
/// Whether we have pending modifiers.
modifiers_pending: bool,
/// Serial of the most recent input event (keyboard or pointer) on this seat.
/// Written by both keyboard and pointer handlers through a shared reference,
/// so interior mutability is required.
pub(crate) latest_input_serial: Cell<Option<u32>>,
}
impl WinitSeatState {
@@ -83,6 +89,12 @@ impl WinitSeatState {
Default::default()
}
/// Returns the serial of the most recent input event on this seat, or `None` if none has
/// been received yet.
pub fn latest_serial(&self) -> Option<u32> {
self.latest_input_serial.get()
}
pub(crate) fn data_device(&self) -> Option<&DataDevice> {
self.data_device.as_ref()
}

View File

@@ -166,7 +166,13 @@ impl PointerHandler for WinitState {
ref kind @ PointerEventKind::Press { button, serial, .. }
| ref kind @ PointerEventKind::Release { button, serial, .. } => {
// Update the last button serial.
pointer.winit_data().data().inner.lock().unwrap().latest_button_serial = serial;
if matches!(kind, PointerEventKind::Press { .. }) {
// For Gnome (Mutter) and possible others only the Press event serial must
// be logged!
seat_state.latest_input_serial.set(Some(serial));
}
let button = wayland_button_to_winit(button);
let state = if matches!(kind, PointerEventKind::Press { .. }) {

View File

@@ -17,6 +17,7 @@ use sctk::seat::SeatState;
use sctk::seat::pointer::ThemedPointer;
use sctk::shell::WaylandSurface;
use sctk::shell::xdg::XdgShell;
use sctk::shell::xdg::popup::{Popup as XdgPopup, PopupConfigure, PopupHandler};
use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler};
use sctk::shm::slot::SlotPool;
use sctk::shm::{Shm, ShmHandler};
@@ -37,7 +38,8 @@ use crate::types::wp_tablet_input_v2::TabletManager;
use crate::types::wp_viewporter::ViewporterState;
use crate::types::xdg_activation::XdgActivationState;
use crate::types::xdg_toplevel_icon_manager::XdgToplevelIconManagerState;
use crate::window::{WindowRequests, WindowState};
use crate::window::WindowState;
use crate::window::handles::WindowRequests;
/// Winit's Wayland state.
#[derive(Debug)]
@@ -314,24 +316,24 @@ impl WindowHandler for WinitState {
) {
let window_id = super::make_wid(window.wl_surface());
let pos = if let Some(pos) =
let index = if let Some(index) =
self.window_compositor_updates.iter().position(|update| update.window_id == window_id)
{
pos
index
} else {
self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id));
self.window_compositor_updates.len() - 1
};
// Populate the configure to the window.
self.window_compositor_updates[pos].resized |= self
self.window_compositor_updates[index].resized |= self
.windows
.get_mut()
.get_mut(&window_id)
.expect("got configure for dead window.")
.lock()
.unwrap()
.configure(configure, &self.shm, &self.subcompositor_state);
.configure_window(configure, &self.shm, &self.subcompositor_state);
// NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the
// users, since it can break a lot of things, thus it'll ask users to redraw instead.
@@ -347,6 +349,57 @@ impl WindowHandler for WinitState {
}
}
impl PopupHandler for WinitState {
fn configure(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
popup: &XdgPopup,
configure: PopupConfigure,
) {
let window_id = super::make_wid(popup.wl_surface());
let index = if let Some(index) =
self.window_compositor_updates.iter().position(|update| update.window_id == window_id)
{
index
} else {
self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id));
self.window_compositor_updates.len() - 1
};
self.window_compositor_updates[index].resized |= self
.windows
.get_mut()
.get_mut(&window_id)
.expect("got configure for dead window.")
.lock()
.unwrap()
.configure_popup(configure);
// NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the
// users, since it can break a lot of things, thus it'll ask users to redraw instead
self.window_requests
.get_mut()
.get(&window_id)
.unwrap()
.redraw_requested
.store(true, Ordering::Relaxed);
// Manually mark that we've got an event, since configure may not generate a resize.
self.dispatched_events = true;
}
fn done(&mut self, _: &Connection, _: &QueueHandle<Self>, popup: &XdgPopup) {
let window_id = super::make_wid(popup.wl_surface());
let window_requests = self.window_requests.get_mut().iter().find(|r| *r.0 == window_id);
if let Some(window_requests) = window_requests {
window_requests.1.closed.store(true, Ordering::Relaxed);
}
Self::queue_close(&mut self.window_compositor_updates, window_id);
}
}
impl OutputHandler for WinitState {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state

View File

@@ -0,0 +1,142 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use sctk::compositor::{CompositorState, Region};
use sctk::reexports::client::QueueHandle;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1;
use tracing::warn;
use winit_core::error::RequestError;
use winit_core::event::WindowEvent;
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::{UserAttentionType, WindowId};
use super::super::event_loop::sink::EventSink;
use super::super::output::MonitorHandle;
use super::super::state::WinitState;
use super::super::types::xdg_activation::XdgActivationTokenData;
#[derive(Debug)]
pub struct Handles {
/// Handle to the main queue to perform requests.
pub(crate) queue_handle: QueueHandle<WinitState>,
/// Window requests to the event loop.
pub(crate) window_requests: Arc<WindowRequests>,
/// Observed monitors.
pub(crate) monitors: Arc<Mutex<Vec<MonitorHandle>>>,
/// Source to wake-up the event-loop for window requests.
pub(crate) event_loop_awakener: calloop::ping::Ping,
/// The event sink to deliver synthetic events.
pub(crate) window_events_sink: Arc<Mutex<EventSink>>,
/// Xdg activation to request user attention.
pub(crate) xdg_activation: Option<XdgActivationV1>,
/// The state of the requested attention from the `xdg_activation`.
pub(crate) attention_requested: Arc<AtomicBool>,
/// Compositor to handle WlRegion stuff.
pub(crate) compositor: Arc<CompositorState>,
}
impl Handles {
pub(crate) 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();
}
}
pub(crate) fn push_window_event(&self, event: WindowEvent, id: WindowId) {
self.window_events_sink.lock().unwrap().push_window_event(event, id);
self.event_loop_awakener.ping();
}
pub(crate) fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Box::new(
self.monitors
.lock()
.unwrap()
.clone()
.into_iter()
.map(|inner| CoreMonitorHandle(Arc::new(inner))),
)
}
pub(crate) fn request_user_attention(
&self,
surface: &WlSurface,
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;
},
};
// 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 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(crate) fn set_cursor_hittest(
&self,
surface: &WlSurface,
hittest: bool,
) -> Result<(), RequestError> {
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(())
}
}
}
/// 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)
}
}

View File

@@ -6,11 +6,11 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use dpi::{LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use sctk::compositor::{CompositorState, Region, SurfaceData};
use rwh_06::RawWindowHandle;
use sctk::compositor::SurfaceData;
use sctk::reexports::client::Proxy;
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::WaylandSurface;
use sctk::shell::xdg::window::{Window as SctkWindow, WindowDecorations};
use tracing::warn;
@@ -26,15 +26,15 @@ use winit_core::window::{
};
use super::ActiveEventLoop;
use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
use super::state::WinitState;
use super::types::xdg_activation::XdgActivationTokenData;
use crate::window::state::WindowType;
use crate::{WindowAttributesWayland, output};
pub(crate) mod state;
pub use state::WindowState;
pub(crate) mod handles;
pub use handles::Handles;
use handles::WindowRequests;
/// The Wayland window.
#[derive(Debug)]
@@ -48,33 +48,12 @@ pub struct Window {
/// The state of the window.
window_state: Arc<Mutex<WindowState>>,
/// Compositor to handle WlRegion stuff.
compositor: Arc<CompositorState>,
/// The wayland display used solely for raw window handle.
#[allow(dead_code)]
display: WlDisplay,
/// Xdg activation to request user attention.
xdg_activation: Option<XdgActivationV1>,
/// The state of the requested attention from the `xdg_activation`.
attention_requested: Arc<AtomicBool>,
/// Handle to the main queue to perform requests.
queue_handle: QueueHandle<WinitState>,
/// Window requests to the event loop.
window_requests: Arc<WindowRequests>,
/// Observed monitors.
monitors: Arc<Mutex<Vec<MonitorHandle>>>,
/// 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<Mutex<EventSink>>,
/// Common handles like queue, window requests, monitors and so on
handles: Handles,
}
impl Window {
@@ -106,20 +85,32 @@ impl Window {
let window =
state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle);
let WindowAttributesWayland { name: app_name, activation_token, prefer_csd } = *attributes
.platform
.take()
.and_then(|p| p.cast::<WindowAttributesWayland>().ok())
.unwrap_or_default();
let WindowAttributesWayland { name: app_name, activation_token, prefer_csd, .. } =
*attributes
.platform
.take()
.and_then(|p| p.cast::<WindowAttributesWayland>().ok())
.unwrap_or_default();
let mut scale_factor = None;
if let Some(RawWindowHandle::Wayland(handle)) = attributes.parent_window() {
if let Some(s) =
state.windows.borrow().get(&WindowId::from_raw(handle.surface.as_ptr() as usize))
{
scale_factor = Some(s.lock().unwrap().scale_factor());
}
}
let scale_factor = scale_factor.unwrap_or(1.0);
let mut window_state = WindowState::new(
event_loop_window_target.handle.clone(),
&event_loop_window_target.queue_handle,
event_loop_window_target,
&state,
size,
window.clone(),
state::WindowType::Window { window: window.clone(), last_configure: None },
attributes.preferred_theme,
prefer_csd,
scale_factor,
None,
);
window_state.set_window_icon(attributes.window_icon);
@@ -219,16 +210,22 @@ impl Window {
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,
handles: Handles {
queue_handle,
window_requests,
monitors,
event_loop_awakener,
window_events_sink,
compositor,
xdg_activation,
attention_requested: Arc::new(AtomicBool::new(false)),
},
})
}
@@ -239,7 +236,7 @@ impl Window {
impl Window {
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
let xdg_activation = match self.xdg_activation.as_ref() {
let xdg_activation = match self.handles.xdg_activation.as_ref() {
Some(xdg_activation) => xdg_activation,
None => return Err(NotSupportedError::new("xdg_activation_v1 is not available").into()),
};
@@ -247,7 +244,8 @@ impl Window {
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);
let xdg_activation_token =
xdg_activation.get_activation_token(&self.handles.queue_handle, data);
xdg_activation_token.set_surface(self.surface());
xdg_activation_token.commit();
@@ -262,8 +260,8 @@ impl Window {
impl Drop for Window {
fn drop(&mut self) {
self.window_requests.closed.store(true, Ordering::Relaxed);
self.event_loop_awakener.ping();
self.handles.window_requests.closed.store(true, Ordering::Relaxed);
self.handles.event_loop_awakener.ping();
}
}
@@ -290,23 +288,16 @@ impl rwh_06::HasDisplayHandle for Window {
}
impl CoreWindow for Window {
fn window_type(&self) -> winit_core::window::WindowType {
winit_core::window::WindowType::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();
}
self.handles.request_redraw();
}
#[inline]
@@ -449,13 +440,15 @@ impl CoreWindow for Window {
}
fn is_maximized(&self) -> bool {
self.window_state
.lock()
.unwrap()
.last_configure
.as_ref()
.map(|last_configure| last_configure.is_maximized())
.unwrap_or_default()
if let WindowType::Window { last_configure, .. } = &self.window_state.lock().unwrap().window
{
last_configure
.as_ref()
.map(|last_configure| last_configure.is_maximized())
.unwrap_or_default()
} else {
false
}
}
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
@@ -475,14 +468,16 @@ impl CoreWindow for Window {
}
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();
let is_fullscreen = if let WindowType::Window { last_configure, .. } =
&self.window_state.lock().unwrap().window
{
last_configure
.as_ref()
.map(|last_configure| last_configure.is_fullscreen())
.unwrap_or_default()
} else {
false
};
if is_fullscreen {
let current_monitor = self.current_monitor();
@@ -526,8 +521,7 @@ impl CoreWindow for Window {
if let Some(allowed) = state_changed {
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();
self.handles.push_window_event(event, self.window_id);
}
Ok(())
@@ -545,29 +539,7 @@ impl CoreWindow for Window {
}
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;
},
};
// 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();
self.handles.request_user_attention(self.surface(), request_type);
}
fn set_theme(&self, theme: Option<Theme>) {
@@ -623,17 +595,7 @@ impl CoreWindow for Window {
}
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(())
}
self.handles.set_cursor_hittest(self.surface(), hittest)
}
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
@@ -645,14 +607,7 @@ impl CoreWindow for Window {
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Box::new(
self.monitors
.lock()
.unwrap()
.clone()
.into_iter()
.map(|inner| CoreMonitorHandle(Arc::new(inner))),
)
self.handles.available_monitors()
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
@@ -670,23 +625,3 @@ impl CoreWindow for Window {
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)
}
}

View File

@@ -22,8 +22,9 @@ use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
use sctk::seat::pointer::{PointerData, ThemedPointer};
use sctk::shell::WaylandSurface;
use sctk::shell::xdg::XdgSurface;
use sctk::shell::xdg::popup::{ConfigureKind, Popup, PopupConfigure};
use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
use sctk::shell::xdg::{XdgPositioner, XdgSurface};
use sctk::shm::Shm;
use sctk::shm::slot::SlotPool;
use sctk::subcompositor::SubcompositorState;
@@ -36,7 +37,6 @@ use winit_core::window::{
};
use crate::event_loop::OwnedDisplayHandle;
use crate::logical_to_physical_rounded;
use crate::seat::{
PointerConstraintsState, TextInputClientState, WinitPointerData, WinitPointerDataExt,
ZwpTextInputV3Ext,
@@ -45,6 +45,7 @@ use crate::state::{WindowCompositorUpdate, WinitState};
use crate::types::bgr_effects::{BgrEffectManager, SurfaceBlurEffect};
use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor};
use crate::types::xdg_toplevel_icon_manager::ToplevelIcon;
use crate::{ActiveEventLoop, logical_to_physical_rounded};
#[cfg(feature = "sctk-adwaita")]
pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
@@ -54,6 +55,49 @@ pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame<WinitState
// Minimum window surface size.
const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
#[derive(Debug)]
pub enum WindowType {
// The option is the last received configure
Window {
window: Window,
last_configure: Option<WindowConfigure>,
},
Popup {
popup: Popup,
positioner: XdgPositioner,
last_configure: Option<PopupConfigure>,
parent_origin: LogicalPosition<i32>,
anchor_rect: (LogicalPosition<i32>, LogicalSize<i32>),
},
}
impl WindowType {
pub fn is_configured(&self) -> bool {
match self {
Self::Window { last_configure, .. } => last_configure.is_some(),
Self::Popup { last_configure, .. } => last_configure.is_some(),
}
}
}
impl WaylandSurface for WindowType {
fn wl_surface(&self) -> &wayland_client::protocol::wl_surface::WlSurface {
match self {
Self::Window { window, .. } => window.wl_surface(),
Self::Popup { popup, .. } => popup.wl_surface(),
}
}
}
impl XdgSurface for WindowType {
fn xdg_surface(&self) -> &wayland_protocols::xdg::shell::client::xdg_surface::XdgSurface {
match self {
Self::Window { window, .. } => window.xdg_surface(),
Self::Popup { popup, .. } => popup.xdg_surface(),
}
}
}
/// The state of the window which is being updated from the [`WinitState`].
#[derive(Debug)]
pub struct WindowState {
@@ -63,9 +107,6 @@ pub struct WindowState {
/// The `Shm` to set cursor.
pub shm: WlShm,
/// The last received configure.
pub last_configure: Option<WindowConfigure>,
/// The pointers observed on the window.
pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
@@ -164,7 +205,7 @@ pub struct WindowState {
has_pending_move: Option<u32>,
/// The underlying SCTK window.
pub window: Window,
pub window: WindowType,
// NOTE: The spec says that destroying parent(`window` in our case), will unmap the
// subsurfaces. Thus to achieve atomic unmap of the client, drop the decorations
@@ -172,19 +213,29 @@ pub struct WindowState {
// field drop order guarantees.
/// The window frame, which is created from the configure request.
frame: Option<WinitFrame>,
/// Parent Window if available
parent: Option<WindowId>,
/// Children of this window like popups, dialogs or other windows
children: Vec<WindowId>,
}
impl WindowState {
/// Create new window state.
#[allow(clippy::too_many_arguments)]
pub fn new(
handle: Arc<OwnedDisplayHandle>,
queue_handle: &QueueHandle<WinitState>,
active_event_loop: &ActiveEventLoop,
winit_state: &WinitState,
initial_size: Size,
window: Window,
window: WindowType,
theme: Option<Theme>,
prefer_csd: bool,
scale_factor: f64,
parent: Option<WindowId>,
) -> Self {
let handle = active_event_loop.handle.clone();
let queue_handle = &active_event_loop.queue_handle;
let compositor = winit_state.compositor_state.clone();
let pointer_constraints = winit_state.pointer_constraints.clone();
let viewport = winit_state
@@ -220,7 +271,6 @@ impl WindowState {
seat_focus: Default::default(),
has_pending_move: None,
text_input_state: None,
last_configure: None,
max_surface_size: None,
min_surface_size: MIN_WINDOW_SIZE,
resize_increments: None,
@@ -228,7 +278,7 @@ impl WindowState {
pointers: Default::default(),
queue_handle: queue_handle.clone(),
resizable: true,
scale_factor: 1.,
scale_factor,
shm: winit_state.shm.wl_shm().clone(),
image_pool: winit_state.image_pool.clone(),
size: initial_size.to_logical(1.),
@@ -240,6 +290,8 @@ impl WindowState {
transparent: false,
viewport,
window,
children: Default::default(),
parent,
}
}
@@ -288,8 +340,47 @@ impl WindowState {
FrameCallbackState::Requested => (),
}
}
pub fn configure_popup(&mut self, configure: PopupConfigure) -> bool {
// NOTE: when using fractional scaling or wl_compositor@v6 the scaling
// should be delivered before the first configure, thus apply it to
// properly scale the physical sizes provided by the users.
if let Some(initial_size) = self.initial_size.take() {
self.size = initial_size.to_logical(self.scale_factor());
}
pub fn configure(
// The popup was constrained to a different size by the compositor
let constrained = self.size.width != configure.width as u32
|| self.size.height != configure.height as u32;
let new_size =
LogicalSize { width: configure.width as u32, height: configure.height as u32 };
// NOTE: Set the configure before doing a resize, since we query it during it.
if let WindowType::Popup { last_configure, .. } = &mut self.window {
let kind = configure.kind.clone();
*last_configure = Some(configure);
// Always resize on the initial configure to properly initialize the viewport
// destination and window geometry. This is required for fractional scaling
// to work correctly: without calling resize(), viewport.set_destination()
// is never called, and the compositor would interpret the buffer size as
// logical pixels, making the popup appear at the wrong size. Also resize
// when the compositor constrained us to a different size than requested.
if matches!(kind, ConfigureKind::Initial) || constrained {
self.resize(new_size);
true
} else {
false
}
} else {
tracing::error!(
"configure_popup called for window type unequal of popup. This should never \
happen, because we start configuring with a popup"
);
false
}
}
pub fn configure_window(
&mut self,
configure: WindowConfigure,
shm: &Shm,
@@ -407,25 +498,33 @@ impl WindowState {
}
let new_state = configure.state;
let old_state = self.last_configure.as_ref().map(|configure| configure.state);
if let WindowType::Window { last_configure, .. } = &mut self.window {
let old_state = last_configure.as_ref().map(|configure| configure.state);
let state_change_requires_resize = old_state
.map(|old_state| {
!old_state
.symmetric_difference(new_state)
.difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
.is_empty()
})
// NOTE: `None` is present for the initial configure, thus we must always resize.
.unwrap_or(true);
let state_change_requires_resize = old_state
.map(|old_state| {
!old_state
.symmetric_difference(new_state)
.difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED)
.is_empty()
})
// NOTE: `None` is present for the initial configure, thus we must always resize.
.unwrap_or(true);
// NOTE: Set the configure before doing a resize, since we query it during it.
self.last_configure = Some(configure);
// NOTE: Set the configure before doing a resize, since we query it during it.
*last_configure = Some(configure);
if state_change_requires_resize || new_size != self.surface_size() {
self.resize(new_size);
true
if state_change_requires_resize || new_size != self.surface_size() {
self.resize(new_size);
true
} else {
false
}
} else {
tracing::error!(
"configure_window called for window type unequal of `Window`. This should never \
happen, because we start configuring with a `Window`"
);
false
}
}
@@ -458,31 +557,45 @@ impl WindowState {
/// Start interacting drag resize.
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
let xdg_toplevel = self.window.xdg_toplevel();
match &self.window {
WindowType::Window { window, .. } => {
let xdg_toplevel = window.xdg_toplevel();
// TODO(kchibisov) handle touch serials.
self.apply_on_pointer(|_, data| {
if let Some(serial) = data.latest_button_serial() {
let seat = data.seat();
xdg_toplevel.resize(seat, serial, resize_direction_to_xdg(direction));
}
});
// TODO(kchibisov) handle touch serials.
self.apply_on_pointer(|_, data| {
if let Some(serial) = data.latest_button_serial() {
let seat = data.seat();
xdg_toplevel.resize(seat, serial, resize_direction_to_xdg(direction));
}
});
Ok(())
Ok(())
},
WindowType::Popup { .. } => Err(RequestError::NotSupported(NotSupportedError::new(
"Drag resize for popup not supported",
))),
}
}
/// Start the window drag.
pub fn drag_window(&self) -> Result<(), RequestError> {
let xdg_toplevel = self.window.xdg_toplevel();
// TODO(kchibisov) handle touch serials.
self.apply_on_pointer(|_, data| {
if let Some(serial) = data.latest_button_serial() {
let seat = data.seat();
xdg_toplevel._move(seat, serial);
}
});
match &self.window {
WindowType::Window { window, .. } => {
let xdg_toplevel = window.xdg_toplevel();
// TODO(kchibisov) handle touch serials.
self.apply_on_pointer(|_, data| {
if let Some(serial) = data.latest_button_serial() {
let seat = data.seat();
xdg_toplevel._move(seat, serial);
}
});
Ok(())
Ok(())
},
WindowType::Popup { .. } => Err(RequestError::NotSupported(NotSupportedError::new(
"Drag for popup not supported",
))),
}
}
/// Tells whether the window should be closed.
@@ -497,32 +610,37 @@ impl WindowState {
window_id: WindowId,
updates: &mut Vec<WindowCompositorUpdate>,
) -> Option<bool> {
match self.frame.as_mut()?.on_click(timestamp, click, pressed)? {
FrameAction::Minimize => self.window.set_minimized(),
FrameAction::Maximize => self.window.set_maximized(),
FrameAction::UnMaximize => self.window.unset_maximized(),
FrameAction::Close => WinitState::queue_close(updates, window_id),
FrameAction::Move => self.has_pending_move = Some(serial),
FrameAction::Resize(edge) => {
let edge = match edge {
ResizeEdge::None => XdgResizeEdge::None,
ResizeEdge::Top => XdgResizeEdge::Top,
ResizeEdge::Bottom => XdgResizeEdge::Bottom,
ResizeEdge::Left => XdgResizeEdge::Left,
ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
ResizeEdge::Right => XdgResizeEdge::Right,
ResizeEdge::TopRight => XdgResizeEdge::TopRight,
ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
_ => return None,
match &self.window {
WindowType::Window { window, .. } => {
match self.frame.as_mut()?.on_click(timestamp, click, pressed)? {
FrameAction::Minimize => window.set_minimized(),
FrameAction::Maximize => window.set_maximized(),
FrameAction::UnMaximize => window.unset_maximized(),
FrameAction::Close => WinitState::queue_close(updates, window_id),
FrameAction::Move => self.has_pending_move = Some(serial),
FrameAction::Resize(edge) => {
let edge = match edge {
ResizeEdge::None => XdgResizeEdge::None,
ResizeEdge::Top => XdgResizeEdge::Top,
ResizeEdge::Bottom => XdgResizeEdge::Bottom,
ResizeEdge::Left => XdgResizeEdge::Left,
ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
ResizeEdge::Right => XdgResizeEdge::Right,
ResizeEdge::TopRight => XdgResizeEdge::TopRight,
ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
_ => return None,
};
window.resize(seat, serial, edge);
},
FrameAction::ShowMenu(x, y) => window.show_window_menu(seat, serial, (x, y)),
_ => (),
};
self.window.resize(seat, serial, edge);
},
FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
_ => (),
};
Some(false)
Some(false)
},
WindowType::Popup { .. } => None,
}
}
pub fn frame_point_left(&mut self) {
@@ -540,21 +658,26 @@ impl WindowState {
x: f64,
y: f64,
) -> Option<CursorIcon> {
// Take the serial if we had any, so it doesn't stick around.
let serial = self.has_pending_move.take();
match &self.window {
WindowType::Window { window, .. } => {
// Take the serial if we had any, so it doesn't stick around.
let serial = self.has_pending_move.take();
if let Some(frame) = self.frame.as_mut() {
let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y);
// If we have a cursor change, that means that cursor is over the decorations,
// so try to apply move.
if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
self.window.move_(seat, serial);
None
} else {
cursor
}
} else {
None
if let Some(frame) = self.frame.as_mut() {
let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y);
// If we have a cursor change, that means that cursor is over the decorations,
// so try to apply move.
if let Some(serial) = cursor.is_some().then_some(serial).flatten() {
window.move_(seat, serial);
None
} else {
cursor
}
} else {
None
}
},
WindowType::Popup { .. } => None,
}
}
@@ -615,21 +738,25 @@ impl WindowState {
/// Whether the window received initial configure event from the compositor.
#[inline]
pub fn is_configured(&self) -> bool {
self.last_configure.is_some()
self.window.is_configured()
}
#[inline]
pub fn is_decorated(&mut self) -> bool {
let csd = self
.last_configure
.as_ref()
.map(|configure| configure.decoration_mode == DecorationMode::Client)
.unwrap_or(false);
if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
!frame.is_hidden()
} else {
// Server side decorations.
true
match &mut self.window {
WindowType::Window { last_configure, .. } => {
let csd = last_configure
.as_ref()
.map(|configure| configure.decoration_mode == DecorationMode::Client)
.unwrap_or(false);
if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
!frame.is_hidden()
} else {
// Server side decorations.
true
}
},
WindowType::Popup { .. } => false, // Popup window does not have any decoration
}
}
@@ -642,6 +769,13 @@ impl WindowState {
.unwrap_or(self.size)
}
/// Get the origin of the content surface by considering the client side decoration if available
/// This is required for example when creating a popup, because as parent a xdg_surface must be
/// passed but the frame is only a wl_surface
pub fn content_surface_origin(&self) -> LogicalPosition<i32> {
self.frame.as_ref().map(|frame| frame.location().into()).unwrap_or_else(|| (0, 0).into())
}
/// Register pointer on the top-level.
pub fn pointer_entered(&mut self, added: Weak<ThemedPointer<WinitPointerData>>) {
self.pointers.push(added);
@@ -704,8 +838,19 @@ impl WindowState {
/// Try to resize the window when the user can do so.
pub fn request_surface_size(&mut self, surface_size: Size) -> PhysicalSize<u32> {
if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) {
self.resize(surface_size.to_logical(self.scale_factor()))
match &self.window {
WindowType::Window { last_configure, .. } => {
if last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) {
self.resize(surface_size.to_logical(self.scale_factor()))
}
},
WindowType::Popup { popup, positioner, .. } => {
let size = surface_size.to_logical(self.scale_factor());
positioner.set_size(size.width, size.height);
if popup.xdg_popup().version() >= 3 {
popup.reposition(positioner, 0);
}
},
}
logical_to_physical_rounded(self.surface_size(), self.scale_factor())
@@ -716,8 +861,10 @@ impl WindowState {
self.size = surface_size;
// Update the stateless size.
if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
self.stateless_size = surface_size;
if let WindowType::Window { last_configure, .. } = &mut self.window {
if let Some(true) = last_configure.as_ref().map(Self::is_stateless) {
self.stateless_size = surface_size;
}
}
// Update the inner frame.
@@ -855,33 +1002,37 @@ impl WindowState {
/// Set maximum inner window size.
pub fn set_min_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
// Ensure that the window has the right minimum size.
let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
size.width = size.width.max(MIN_WINDOW_SIZE.width);
size.height = size.height.max(MIN_WINDOW_SIZE.height);
if let WindowType::Window { window, .. } = &self.window {
// Ensure that the window has the right minimum size.
let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
size.width = size.width.max(MIN_WINDOW_SIZE.width);
size.height = size.height.max(MIN_WINDOW_SIZE.height);
// Add the borders.
let size = self
.frame
.as_ref()
.map(|frame| frame.add_borders(size.width, size.height).into())
.unwrap_or(size);
// Add the borders.
let size = self
.frame
.as_ref()
.map(|frame| frame.add_borders(size.width, size.height).into())
.unwrap_or(size);
self.min_surface_size = size;
self.window.set_min_size(Some(size.into()));
self.min_surface_size = size;
window.set_min_size(Some(size.into()));
}
}
/// Set maximum inner window size.
pub fn set_max_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
let size = size.map(|size| {
self.frame
.as_ref()
.map(|frame| frame.add_borders(size.width, size.height).into())
.unwrap_or(size)
});
if let WindowType::Window { window, .. } = &self.window {
let size = size.map(|size| {
self.frame
.as_ref()
.map(|frame| frame.add_borders(size.width, size.height).into())
.unwrap_or(size)
});
self.max_surface_size = size;
self.window.set_max_size(size.map(Into::into));
self.max_surface_size = size;
window.set_max_size(size.map(Into::into));
}
}
/// Set the CSD theme.
@@ -983,13 +1134,15 @@ impl WindowState {
}
pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
// TODO(kchibisov) handle touch serials.
self.apply_on_pointer(|_, data| {
if let Some(serial) = data.latest_button_serial() {
let seat = data.seat();
self.window.show_window_menu(seat, serial, position.into());
}
});
if let WindowType::Window { window, .. } = &self.window {
// TODO(kchibisov) handle touch serials.
self.apply_on_pointer(|_, data| {
if let Some(serial) = data.latest_button_serial() {
let seat = data.seat();
window.show_window_menu(seat, serial, position.into());
}
});
}
}
/// Set the position of the cursor.
@@ -1042,22 +1195,29 @@ impl WindowState {
self.decorate = decorate;
match self.last_configure.as_ref().map(|configure| configure.decoration_mode) {
Some(DecorationMode::Server) if !self.decorate => {
// To disable decorations we should request client and hide the frame.
self.window.request_decoration_mode(Some(DecorationMode::Client))
},
_ if self.decorate && self.prefer_csd => {
self.window.request_decoration_mode(Some(DecorationMode::Client))
},
_ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)),
_ => (),
}
match &self.window {
WindowType::Window { window, last_configure } => {
match last_configure.as_ref().map(|configure| configure.decoration_mode) {
Some(DecorationMode::Server) if !self.decorate => {
// To disable decorations we should request client and hide the frame.
window.request_decoration_mode(Some(DecorationMode::Client))
},
_ if self.decorate && self.prefer_csd => {
window.request_decoration_mode(Some(DecorationMode::Client))
},
_ if self.decorate => {
window.request_decoration_mode(Some(DecorationMode::Server))
},
_ => (),
}
if let Some(frame) = self.frame.as_mut() {
frame.set_hidden(!decorate);
// Force the resize.
self.resize(self.size);
if let Some(frame) = self.frame.as_mut() {
frame.set_hidden(!decorate);
// Force the resize.
self.resize(self.size);
}
},
WindowType::Popup { .. } => (), // Popup does not have any decoration
}
}
@@ -1192,46 +1352,51 @@ impl WindowState {
frame.set_title(&title);
}
self.window.set_title(&title);
match &self.window {
WindowType::Window { window, .. } => window.set_title(&title),
WindowType::Popup { .. } => (), // Popup does not have any title
}
self.title = title;
}
/// Set the window's icon
pub fn set_window_icon(&mut self, window_icon: Option<winit_core::icon::Icon>) {
let xdg_toplevel_icon_manager = match self.xdg_toplevel_icon_manager.as_ref() {
Some(xdg_toplevel_icon_manager) => xdg_toplevel_icon_manager,
None => {
warn!("`xdg_toplevel_icon_manager_v1` is not supported");
return;
},
};
if let WindowType::Window { window, .. } = &self.window {
let xdg_toplevel_icon_manager = match self.xdg_toplevel_icon_manager.as_ref() {
Some(xdg_toplevel_icon_manager) => xdg_toplevel_icon_manager,
None => {
warn!("`xdg_toplevel_icon_manager_v1` is not supported");
return;
},
};
let (toplevel_icon, xdg_toplevel_icon) = match window_icon {
Some(icon) => {
let mut image_pool = self.image_pool.lock().unwrap();
let toplevel_icon = match ToplevelIcon::new(icon, &mut image_pool) {
Ok(toplevel_icon) => toplevel_icon,
Err(error) => {
warn!("Error setting window icon: {error}");
return;
},
};
let (toplevel_icon, xdg_toplevel_icon) = match window_icon {
Some(icon) => {
let mut image_pool = self.image_pool.lock().unwrap();
let toplevel_icon = match ToplevelIcon::new(icon, &mut image_pool) {
Ok(toplevel_icon) => toplevel_icon,
Err(error) => {
warn!("Error setting window icon: {error}");
return;
},
};
let xdg_toplevel_icon =
xdg_toplevel_icon_manager.create_icon(&self.queue_handle, GlobalData);
let xdg_toplevel_icon =
xdg_toplevel_icon_manager.create_icon(&self.queue_handle, GlobalData);
toplevel_icon.add_buffer(&xdg_toplevel_icon);
toplevel_icon.add_buffer(&xdg_toplevel_icon);
(Some(toplevel_icon), Some(xdg_toplevel_icon))
},
None => (None, None),
};
(Some(toplevel_icon), Some(xdg_toplevel_icon))
},
None => (None, None),
};
xdg_toplevel_icon_manager.set_icon(self.window.xdg_toplevel(), xdg_toplevel_icon.as_ref());
self.toplevel_icon = toplevel_icon;
xdg_toplevel_icon_manager.set_icon(window.xdg_toplevel(), xdg_toplevel_icon.as_ref());
self.toplevel_icon = toplevel_icon;
if let Some(xdg_toplevel_icon) = xdg_toplevel_icon {
xdg_toplevel_icon.destroy();
if let Some(xdg_toplevel_icon) = xdg_toplevel_icon {
xdg_toplevel_icon.destroy();
}
}
}
@@ -1263,6 +1428,22 @@ impl WindowState {
pub fn title(&self) -> &str {
&self.title
}
pub fn children(&self) -> &Vec<WindowId> {
&self.children
}
pub fn parent(&self) -> Option<WindowId> {
self.parent
}
pub fn remove_child(&mut self, child: &WindowId) {
self.children.retain(|w| w != child);
}
pub fn add_child(&mut self, child: WindowId) {
self.children.push(child);
}
}
impl Drop for WindowState {