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

@@ -769,9 +769,15 @@ pub struct Window {
impl Window {
pub(crate) fn new(
el: &ActiveEventLoop,
_window_attrs: window::WindowAttributes,
window_attrs: window::WindowAttributes,
) -> Result<Self, RequestError> {
// FIXME this ignores requested window attributes
if window_attrs.window_type() == window::WindowType::Popup {
return Err(RequestError::NotSupported(NotSupportedError::new(
"Popups are not implemented for Android",
)));
}
// FIXME this ignores the rest of the requested window attributes
Ok(Self {
app: el.app.clone(),
@@ -825,6 +831,10 @@ impl rwh_06::HasWindowHandle for Window {
}
impl CoreWindow for Window {
fn window_type(&self) -> window::WindowType {
window::WindowType::Window
}
fn id(&self) -> WindowId {
GLOBAL_WINDOW
}

View File

@@ -15,7 +15,7 @@ use winit_core::icon::Icon;
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use winit_core::window::{
ImeCapabilities, ImeRequest, ImeRequestError, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowType,
};
use super::event_loop::ActiveEventLoop;
@@ -26,6 +26,7 @@ pub(crate) struct Window {
window: MainThreadBound<Retained<NSWindow>>,
/// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Retained<WindowDelegate>>,
window_type: WindowType,
}
impl Window {
@@ -34,12 +35,14 @@ impl Window {
attributes: WindowAttributes,
) -> Result<Self, RequestError> {
let mtm = window_target.mtm;
let window_type = attributes.window_type;
let delegate =
autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?;
window_target.app_state.register_window(&delegate, mtm);
Ok(Window {
window: MainThreadBound::new(delegate.window().retain(), mtm),
delegate: MainThreadBound::new(delegate, mtm),
window_type,
})
}
@@ -95,6 +98,10 @@ impl rwh_06::HasWindowHandle for Window {
}
impl CoreWindow for Window {
fn window_type(&self) -> WindowType {
self.window_type
}
fn id(&self) -> winit_core::window::WindowId {
self.maybe_wait_on_main(|delegate| delegate.id())
}

View File

@@ -52,7 +52,7 @@ use winit_core::icon::Icon;
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider};
use winit_core::window::{
CursorGrabMode, ImeCapabilities, ImeRequest, ImeRequestError, ResizeDirection, Theme,
UserAttentionType, WindowAttributes, WindowButtons, WindowId, WindowLevel,
UserAttentionType, WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowType,
};
use super::app_state::AppState;
@@ -112,6 +112,7 @@ pub(crate) struct State {
is_simple_fullscreen: Cell<bool>,
saved_style: Cell<Option<NSWindowStyleMask>>,
is_borderless_game: Cell<bool>,
is_popup: Cell<bool>,
}
define_class!(
@@ -655,6 +656,7 @@ fn new_window(
app_state: &Rc<AppState>,
attrs: &WindowAttributes,
macos_attrs: &WindowAttributesMacOS,
is_popup: bool,
mtm: MainThreadMarker,
) -> Option<Retained<NSWindow>> {
autoreleasepool(|_| {
@@ -681,6 +683,10 @@ fn new_window(
None => NSSize::new(800.0, 600.0),
};
let position = match attrs.position {
// A popup's position is parent-relative; it's applied in `WindowDelegate::new`
// (after the delegate exists) via the shared translation in
// `set_outer_position`.
_ if is_popup => NSPoint::new(0.0, 0.0),
Some(position) => {
let position = position.to_logical(scale_factor);
flip_window_screen_coordinates(NSRect::new(
@@ -738,6 +744,8 @@ fn new_window(
// confusing issues with the window not being properly activated.
//
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
// Panels (including popups) are non-activating so they don't steal key focus
// from their parent (matching menu/combobox semantics).
let window: Retained<NSWindow> = if macos_attrs.panel {
masks |= NSWindowStyleMask::NonactivatingPanel;
@@ -821,7 +829,8 @@ fn new_window(
if !macos_attrs.has_shadow {
window.setHasShadow(false);
}
if attrs.position.is_none() {
// Popups are positioned relative to their parent in `WindowDelegate::new`.
if attrs.position.is_none() && !is_popup {
window.center();
}
@@ -884,13 +893,27 @@ impl WindowDelegate {
mut attrs: WindowAttributes,
mtm: MainThreadMarker,
) -> Result<Retained<Self>, RequestError> {
let macos_attrs = attrs
let mut macos_attrs = attrs
.platform
.take()
.and_then(|attrs| attrs.cast::<WindowAttributesMacOS>().ok())
.unwrap_or_default();
let window = new_window(app_state, &attrs, &macos_attrs, mtm)
let is_popup = matches!(attrs.window_type(), WindowType::Popup);
if is_popup {
// A popup is an undecorated, non-activating panel with no titlebar buttons. Model it
// as such so it flows through the existing borderless + panel paths in `new_window`
// instead of needing dedicated branches.
attrs.decorations = false;
attrs.enabled_buttons = WindowButtons::empty();
// Unless grab_keyboard is requested, use a non-activating panel so the popup doesn't
// steal keyboard focus from the parent window.
if !attrs.active {
macos_attrs.panel = true;
}
}
let window = new_window(app_state, &attrs, &macos_attrs, is_popup, mtm)
.ok_or_else(|| os_error!("couldn't create `NSWindow`"))?;
match attrs.parent_window() {
@@ -909,6 +932,11 @@ impl WindowDelegate {
unsafe { parent.addChildWindow_ordered(&window, NSWindowOrderingMode::Above) };
},
Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"),
None if is_popup => {
return Err(RequestError::NotSupported(NotSupportedError::new(
"a popup window requires a parent window",
)));
},
None => (),
}
@@ -946,6 +974,7 @@ impl WindowDelegate {
is_simple_fullscreen: Cell::new(false),
saved_style: Cell::new(None),
is_borderless_game: Cell::new(macos_attrs.borderless_game),
is_popup: Cell::new(is_popup),
});
let delegate: Retained<WindowDelegate> = unsafe { msg_send![super(delegate), init] };
@@ -992,6 +1021,14 @@ impl WindowDelegate {
delegate.set_window_level(attrs.window_level);
// The popup position is relative to the parent window, and the parent is only
// attached above, so apply the (translated) position now. Default to the parent's
// content top-left when no position was given.
if is_popup {
let position = attrs.position.unwrap_or_else(|| LogicalPosition::new(0.0, 0.0).into());
delegate.set_outer_position(position);
}
delegate.set_cursor(attrs.cursor);
// Set fullscreen mode after we setup everything
@@ -1147,7 +1184,9 @@ impl WindowDelegate {
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
let position = flip_window_screen_coordinates(self.window().frame());
Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor()))
let position =
self.translate_popup_position_to_parent(LogicalPosition::new(position.x, position.y));
Ok(position.to_physical(self.scale_factor()))
}
pub fn surface_position(&self) -> PhysicalPosition<i32> {
@@ -1171,6 +1210,7 @@ impl WindowDelegate {
pub fn set_outer_position(&self, position: Position) {
let position = position.to_logical(self.scale_factor());
let position = self.translate_popup_position(position);
let point = flip_window_screen_coordinates(NSRect::new(
NSPoint::new(position.x, position.y),
self.window().frame().size,
@@ -1178,6 +1218,40 @@ impl WindowDelegate {
self.window().setFrameOrigin(point);
}
/// Popups receive their position relative to the top-left of the parent window's
/// content area (matching the Win32 and Wayland backends). macOS positions windows
/// in global screen coordinates, so add the parent content area's origin.
fn translate_popup_position(&self, position: LogicalPosition<f64>) -> LogicalPosition<f64> {
if !self.ivars().is_popup.get() {
return position;
}
let Some(parent) = self.window().parentWindow() else {
return position;
};
let parent_origin =
flip_window_screen_coordinates(parent.contentRectForFrameRect(parent.frame()));
LogicalPosition::new(parent_origin.x + position.x, parent_origin.y + position.y)
}
/// Inverse of [`Self::translate_popup_position`]. Popups report their position
/// relative to the top-left of the parent window's content area (matching the
/// Win32 and Wayland backends), so subtract the parent content area's origin from
/// the global screen coordinates. Non-popup windows are returned unchanged.
fn translate_popup_position_to_parent(
&self,
position: LogicalPosition<f64>,
) -> LogicalPosition<f64> {
if !self.ivars().is_popup.get() {
return position;
}
let Some(parent) = self.window().parentWindow() else {
return position;
};
let parent_origin =
flip_window_screen_coordinates(parent.contentRectForFrameRect(parent.frame()));
LogicalPosition::new(position.x - parent_origin.x, position.y - parent_origin.y)
}
#[inline]
pub fn surface_size(&self) -> PhysicalSize<u32> {
self.view().surface_size()

View File

@@ -46,6 +46,26 @@ impl fmt::Debug for WindowId {
}
}
/// The role of a window, used to request platform-specific window behavior.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub enum WindowType {
/// A normal, top-level window.
#[default]
Window,
/// A short-lived window anchored to a parent, such as a menu, combo-box dropdown, or
/// tooltip. Requires a parent set via [`WindowAttributes::with_parent_window`], and its
/// position is interpreted relative to that parent.
///
/// ## Platform-specific
///
/// - **macOS:** A borderless, non-activating child window. The system does *not* draw rounded
/// corners for it. To get a rounded, native-looking popup, create it transparent (via
/// [`WindowAttributes::with_transparent`]) and render the round border yourself.
/// - **X11, Web, Android, iOS, Orbital:** An error is returned because it is not implemented.
Popup,
}
/// Attributes used when creating a window.
#[derive(Debug)]
#[non_exhaustive]
@@ -54,6 +74,11 @@ pub struct WindowAttributes {
pub min_surface_size: Option<Size>,
pub max_surface_size: Option<Size>,
pub surface_resize_increments: Option<Size>,
/// The initial position of the window in screen coordinates.
///
/// For popups, this position is relative to the parent window.
///
/// **Wayland:** See `WindowAttributesWayland` for more options to position a popup.
pub position: Option<Position>,
pub resizable: bool,
pub enabled_buttons: WindowButtons,
@@ -67,11 +92,19 @@ pub struct WindowAttributes {
pub preferred_theme: Option<Theme>,
pub content_protected: bool,
pub window_level: WindowLevel,
/// Whether the window should be activated (focused) when shown.
///
/// For [`WindowType::Popup`] windows this also controls keyboard grabbing:
/// - `true` — the popup captures keyboard input (Win32: omits `WS_EX_NOACTIVATE`, macOS: uses
/// an activating `NSWindow`, Wayland: issues `xdg_popup.grab`).
/// - `false` — the popup is non-activating and the parent window keeps focus (Win32:
/// `WS_EX_NOACTIVATE`, macOS: `NSWindowStyleMask::NonactivatingPanel`, Wayland: no grab).
pub active: bool,
pub cursor: Cursor,
pub(crate) parent_window: Option<SendSyncRawWindowHandle>,
pub fullscreen: Option<Fullscreen>,
pub platform: Option<Box<dyn PlatformWindowAttributes>>,
pub window_type: WindowType,
}
impl WindowAttributes {
@@ -145,6 +178,8 @@ impl WindowAttributes {
/// position. There may be a small gap between this position and the window due to the
/// specifics of the Window Manager.
/// - **X11:** The top left corner of the window, the window's "outer" position.
/// - **Wayland:** The top left corner of the window if the window type is `WindowType::Popup`
/// otherwise ignored
/// - **Others:** Ignored.
#[inline]
pub fn with_position<P: Into<Position>>(mut self, position: P) -> Self {
@@ -324,9 +359,13 @@ impl WindowAttributes {
/// The window should be assumed as not focused by default
/// following by the [`WindowEvent::Focused`].
///
/// For [`WindowType::Popup`] windows, also controls keyboard grabbing — see
/// [`WindowAttributes::active`] for details.
///
/// ## Platform-specific:
///
/// **Android / iOS / X11 / Wayland / Orbital:** Unsupported.
/// **Android / iOS / X11 / Orbital:** Unsupported.
/// **Wayland:** Only supported for [`WindowType::Popup`].
///
/// [`WindowEvent::Focused`]: crate::event::WindowEvent::Focused
#[inline]
@@ -378,6 +417,27 @@ impl WindowAttributes {
self.platform = Some(platform);
self
}
/// Sets the [`WindowType`] (window vs. popup).
///
/// Used by the Windows, Wayland and macOS backends; on X11 [`WindowType::Popup`] is not
/// implemented and window creation returns an error.
/// If the type is [`WindowType::Popup`], the parent must also be set via
/// [`with_parent_window`](Self::with_parent_window), and the position is interpreted
/// relative to that parent.
///
/// See [`WindowType::Popup`] for the per-platform behavior, including how to obtain a
/// rounded, native-looking popup on macOS.
pub fn with_window_type(mut self, window_type: WindowType) -> Self {
self.window_type = window_type;
self
}
/// Returns if the window type is a popup or a normal window
#[inline]
pub fn window_type(&self) -> WindowType {
self.window_type
}
}
impl Clone for WindowAttributes {
@@ -405,6 +465,7 @@ impl Clone for WindowAttributes {
parent_window: self.parent_window.clone(),
fullscreen: self.fullscreen.clone(),
platform: self.platform.as_ref().map(|platform| platform.box_clone()),
window_type: self.window_type,
}
}
}
@@ -435,6 +496,7 @@ impl Default for WindowAttributes {
platform: Default::default(),
cursor: Cursor::default(),
blur: Default::default(),
window_type: Default::default(),
}
}
}
@@ -476,6 +538,9 @@ impl_dyn_casting!(PlatformWindowAttributes);
/// **Web:** The [`Window`], which is represented by a `HTMLElementCanvas`, can
/// not be closed by dropping the [`Window`].
pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// Returns the window type of this window
fn window_type(&self) -> WindowType;
/// Returns an identifier unique to the window.
fn id(&self) -> WindowId;
@@ -648,10 +713,18 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// The coordinates can be negative if the top-left hand corner of the window is outside
/// of the visible screen region, or on another monitor than the primary.
///
/// For a [`WindowType::Popup`] with a parent, the position is instead reported relative to the
/// top-left hand corner of the parent window's content area, mirroring the coordinate system
/// used by [`Window::set_outer_position`].
///
/// ## Platform-specific
///
/// - **Web:** Returns the top-left coordinates relative to the viewport.
/// - **Android / Wayland:** Always returns [`RequestError::NotSupported`].
/// - **Android:** Always returns [`RequestError::NotSupported`].
/// - **Wayland:** For a top-level window this always returns [`RequestError::NotSupported`],
/// since the compositor does not report absolute positions. For a [`WindowType::Popup`] the
/// compositor-decided position relative to the parent is returned once the popup has been
/// configured (before that, [`RequestError::NotSupported`]).
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError>;
/// Sets the position of the window on the desktop.

View File

@@ -37,6 +37,12 @@ impl Window {
el: &ActiveEventLoop,
attrs: window::WindowAttributes,
) -> Result<Self, RequestError> {
if attrs.window_type() == window::WindowType::Popup {
return Err(RequestError::NotSupported(NotSupportedError::new(
"Popups are not implemented for Orbital",
)));
}
let scale = 1.;
let (x, y) = if let Some(pos) = attrs.position {
@@ -154,6 +160,10 @@ impl Window {
}
impl CoreWindow for Window {
fn window_type(&self) -> window::WindowType {
window::WindowType::Window
}
fn id(&self) -> WindowId {
WindowId::from_raw(self.window_socket.fd())
}

View File

@@ -25,7 +25,7 @@ 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,
WindowLevel, WindowType,
};
use super::app_state::EventWrapper;
@@ -490,6 +490,12 @@ impl Window {
event_loop: &ActiveEventLoop,
mut window_attributes: WindowAttributes,
) -> Result<Window, RequestError> {
if window_attributes.window_type() == WindowType::Popup {
return Err(RequestError::NotSupported(NotSupportedError::new(
"Popups are not implemented for iOS",
)));
}
let mtm = event_loop.mtm;
if window_attributes.min_surface_size.is_some() {
@@ -590,6 +596,10 @@ impl rwh_06::HasWindowHandle for Window {
}
impl CoreWindow for Window {
fn window_type(&self) -> WindowType {
WindowType::Window
}
fn id(&self) -> winit_core::window::WindowId {
self.maybe_wait_on_main(|delegate| delegate.id())
}

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> {
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
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,
handles: Handles {
queue_handle,
window_requests,
monitors,
event_loop_awakener,
window_events_sink,
compositor,
xdg_activation,
attention_requested: Arc::new(AtomicBool::new(false)),
event_loop_awakener,
window_requests,
window_events_sink,
},
})
}
@@ -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
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
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();
.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,7 +498,8 @@ 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| {
@@ -420,7 +512,7 @@ impl WindowState {
.unwrap_or(true);
// NOTE: Set the configure before doing a resize, since we query it during it.
self.last_configure = Some(configure);
*last_configure = Some(configure);
if state_change_requires_resize || new_size != self.surface_size() {
self.resize(new_size);
@@ -428,6 +520,13 @@ impl WindowState {
} 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
}
}
/// Compute the bounds for the surface size of the surface.
@@ -458,7 +557,9 @@ 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| {
@@ -469,11 +570,18 @@ impl WindowState {
});
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();
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() {
@@ -483,6 +591,11 @@ impl WindowState {
});
Ok(())
},
WindowType::Popup { .. } => Err(RequestError::NotSupported(NotSupportedError::new(
"Drag for popup not supported",
))),
}
}
/// Tells whether the window should be closed.
@@ -497,10 +610,12 @@ impl WindowState {
window_id: WindowId,
updates: &mut Vec<WindowCompositorUpdate>,
) -> Option<bool> {
match &self.window {
WindowType::Window { window, .. } => {
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::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) => {
@@ -516,13 +631,16 @@ impl WindowState {
ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
_ => return None,
};
self.window.resize(seat, serial, edge);
window.resize(seat, serial, edge);
},
FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
FrameAction::ShowMenu(x, y) => window.show_window_menu(seat, serial, (x, y)),
_ => (),
};
Some(false)
},
WindowType::Popup { .. } => None,
}
}
pub fn frame_point_left(&mut self) {
@@ -540,6 +658,8 @@ impl WindowState {
x: f64,
y: f64,
) -> Option<CursorIcon> {
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();
@@ -548,7 +668,7 @@ impl WindowState {
// 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);
window.move_(seat, serial);
None
} else {
cursor
@@ -556,6 +676,9 @@ impl WindowState {
} else {
None
}
},
WindowType::Popup { .. } => None,
}
}
/// Get the stored resizable state.
@@ -615,13 +738,14 @@ 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
match &mut self.window {
WindowType::Window { last_configure, .. } => {
let csd = last_configure
.as_ref()
.map(|configure| configure.decoration_mode == DecorationMode::Client)
.unwrap_or(false);
@@ -631,6 +755,9 @@ impl WindowState {
// Server side decorations.
true
}
},
WindowType::Popup { .. } => false, // Popup window does not have any decoration
}
}
/// Get the outer size of the window.
@@ -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,9 +838,20 @@ 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) {
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,9 +861,11 @@ impl WindowState {
self.size = surface_size;
// Update the stateless size.
if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
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.
let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
@@ -855,6 +1002,7 @@ impl WindowState {
/// Set maximum inner window size.
pub fn set_min_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
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);
@@ -868,11 +1016,13 @@ impl WindowState {
.unwrap_or(size);
self.min_surface_size = size;
self.window.set_min_size(Some(size.into()));
window.set_min_size(Some(size.into()));
}
}
/// Set maximum inner window size.
pub fn set_max_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
if let WindowType::Window { window, .. } = &self.window {
let size = size.map(|size| {
self.frame
.as_ref()
@@ -881,7 +1031,8 @@ impl WindowState {
});
self.max_surface_size = size;
self.window.set_max_size(size.map(Into::into));
window.set_max_size(size.map(Into::into));
}
}
/// Set the CSD theme.
@@ -983,14 +1134,16 @@ impl WindowState {
}
pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
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();
self.window.show_window_menu(seat, serial, position.into());
window.show_window_menu(seat, serial, position.into());
}
});
}
}
/// Set the position of the cursor.
pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), RequestError> {
@@ -1042,15 +1195,19 @@ impl WindowState {
self.decorate = decorate;
match self.last_configure.as_ref().map(|configure| configure.decoration_mode) {
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.
self.window.request_decoration_mode(Some(DecorationMode::Client))
window.request_decoration_mode(Some(DecorationMode::Client))
},
_ if self.decorate && self.prefer_csd => {
self.window.request_decoration_mode(Some(DecorationMode::Client))
window.request_decoration_mode(Some(DecorationMode::Client))
},
_ if self.decorate => {
window.request_decoration_mode(Some(DecorationMode::Server))
},
_ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)),
_ => (),
}
@@ -1059,6 +1216,9 @@ impl WindowState {
// Force the resize.
self.resize(self.size);
}
},
WindowType::Popup { .. } => (), // Popup does not have any decoration
}
}
/// Add seat focus for the window.
@@ -1192,12 +1352,16 @@ 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>) {
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 => {
@@ -1227,13 +1391,14 @@ impl WindowState {
None => (None, None),
};
xdg_toplevel_icon_manager.set_icon(self.window.xdg_toplevel(), xdg_toplevel_icon.as_ref());
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();
}
}
}
/// Mark the window as transparent.
#[inline]
@@ -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 {

View File

@@ -13,7 +13,7 @@ use winit_core::icon::Icon;
use winit_core::monitor::{Fullscreen, MonitorHandle as CoremMonitorHandle};
use winit_core::window::{
CursorGrabMode, ImeRequestError, ResizeDirection, Theme, UserAttentionType,
Window as RootWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel,
Window as RootWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowType,
};
use crate::r#async::Dispatcher;
@@ -46,6 +46,12 @@ impl Window {
target: &ActiveEventLoop,
attr: WindowAttributes,
) -> Result<Self, RequestError> {
if attr.window_type() == WindowType::Popup {
return Err(RequestError::NotSupported(NotSupportedError::new(
"Popups are not implemented for Web",
)));
}
let id = target.generate_id();
let window = target.runner.window();
@@ -103,6 +109,10 @@ impl Window {
}
impl RootWindow for Window {
fn window_type(&self) -> WindowType {
WindowType::Window
}
fn id(&self) -> WindowId {
self.inner.queue(|inner| inner.id)
}

View File

@@ -21,7 +21,7 @@ use windows_sys::Win32::Graphics::Dwm::{
use windows_sys::Win32::Graphics::Gdi::{
CDS_FULLSCREEN, ChangeDisplaySettingsExW, ClientToScreen, CreateRectRgn, DISP_CHANGE_BADFLAGS,
DISP_CHANGE_BADMODE, DISP_CHANGE_BADPARAM, DISP_CHANGE_FAILED, DISP_CHANGE_SUCCESSFUL,
DeleteObject, InvalidateRgn, RDW_INTERNALPAINT, RedrawWindow,
DeleteObject, InvalidateRgn, RDW_INTERNALPAINT, RedrawWindow, ScreenToClient,
};
use windows_sys::Win32::System::Com::{
CLSCTX_ALL, COINIT_APARTMENTTHREADED, CoCreateInstance, CoInitializeEx, CoUninitialize,
@@ -36,7 +36,7 @@ use windows_sys::Win32::UI::Input::Touch::{RegisterTouchWindow, TWF_WANTPALM};
use windows_sys::Win32::UI::WindowsAndMessaging::{
CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, CreateWindowExW, EnableMenuItem, FLASHW_ALL,
FLASHW_STOP, FLASHW_TIMERNOFG, FLASHW_TRAY, FLASHWINFO, FlashWindowEx, GWLP_HINSTANCE,
GetClientRect, GetCursorPos, GetForegroundWindow, GetSystemMenu, GetSystemMetrics,
GetClientRect, GetCursorPos, GetForegroundWindow, GetParent, GetSystemMenu, GetSystemMetrics,
GetWindowPlacement, GetWindowTextLengthW, GetWindowTextW, HTBOTTOM, HTBOTTOMLEFT,
HTBOTTOMRIGHT, HTCAPTION, HTLEFT, HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT, IsWindowVisible,
LoadCursorW, MENU_ITEM_STATE, MF_BYCOMMAND, MFS_DISABLED, MFS_ENABLED, NID_READY, PM_NOREMOVE,
@@ -48,13 +48,13 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
WM_SYSCOMMAND, WNDCLASSEXW,
};
use winit_core::cursor::Cursor;
use winit_core::error::RequestError;
use winit_core::error::{NotSupportedError, RequestError};
use winit_core::icon::{Icon, RgbaIcon};
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider};
use winit_core::window::{
CursorGrabMode, ImeCapabilities, ImeRequest, ImeRequestError, ResizeDirection, Theme,
UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
WindowLevel,
WindowLevel, WindowType,
};
use crate::dark_mode::try_theme;
@@ -100,6 +100,9 @@ pub struct Window {
// The events loop proxy.
thread_executor: event_loop::EventLoopThreadExecutor,
/// The type of window of this
window_type: WindowType,
}
impl Window {
@@ -118,6 +121,49 @@ impl Window {
self.window_state.lock().unwrap()
}
// If we have a popup the position is relative to the parent window and not
// relative to the screen. Therefore we have to translate it from the parent
// coordinate system to the display coordinate system
fn translate_outer_position(&self, position: Position) -> PhysicalPosition<i32> {
let position = position.to_physical::<i32>(self.scale_factor());
let mut point = POINT { x: position.x, y: position.y };
let window_flags = self.window_state_lock().window_flags;
if window_flags.contains(WindowFlags::POPUP) && !window_flags.contains(WindowFlags::CHILD) {
let parent = unsafe { GetParent(self.hwnd()) };
if !parent.is_null() {
unsafe {
ClientToScreen(parent, &mut point);
}
}
}
PhysicalPosition::new(point.x, point.y)
}
// Inverse of `translate_outer_position`: if we have a popup the position is
// reported relative to the parent window instead of the screen. Therefore we
// translate it from the display coordinate system back to the parent
// coordinate system. Non-popup windows are left in screen coordinates.
fn translate_outer_position_to_parent(
&self,
position: PhysicalPosition<i32>,
) -> PhysicalPosition<i32> {
let mut point = POINT { x: position.x, y: position.y };
let window_flags = self.window_state_lock().window_flags;
if window_flags.contains(WindowFlags::POPUP) && !window_flags.contains(WindowFlags::CHILD) {
let parent = unsafe { GetParent(self.hwnd()) };
if !parent.is_null() {
unsafe {
ScreenToClient(parent, &mut point);
}
}
}
PhysicalPosition::new(point.x, point.y)
}
/// Returns the `hwnd` of this window.
pub fn hwnd(&self) -> HWND {
self.window.hwnd()
@@ -416,6 +462,10 @@ impl rwh_06::HasWindowHandle for Window {
}
impl CoreWindow for Window {
fn window_type(&self) -> WindowType {
self.window_type
}
fn set_title(&self, text: &str) {
let wide_text = util::encode_wide(text);
unsafe {
@@ -464,7 +514,10 @@ impl CoreWindow for Window {
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
util::WindowArea::Outer
.get_rect(self.hwnd())
.map(|rect| Ok(PhysicalPosition::new(rect.left, rect.top)))
.map(|rect| {
Ok(self
.translate_outer_position_to_parent(PhysicalPosition::new(rect.left, rect.top)))
})
.expect(
"Unexpected GetWindowRect failure; please report this error to \
rust-windowing/winit",
@@ -483,7 +536,7 @@ impl CoreWindow for Window {
}
fn set_outer_position(&self, position: Position) {
let (x, y): (i32, i32) = position.to_physical::<i32>(self.scale_factor()).into();
let position = self.translate_outer_position(position);
let window_state = Arc::clone(&self.window_state);
let window = self.window;
@@ -498,8 +551,8 @@ impl CoreWindow for Window {
SetWindowPos(
self.hwnd(),
ptr::null_mut(),
x,
y,
position.x,
position.y,
0,
0,
SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE,
@@ -1211,6 +1264,7 @@ impl InitData<'_> {
window: SyncWindowHandle(window),
window_state,
thread_executor: self.runner.create_thread_executor(),
window_type: self.attributes.window_type,
}
}
@@ -1369,8 +1423,10 @@ unsafe fn init(
let class_name = util::encode_wide(&win_attributes.class_name);
unsafe { register_window_class(&class_name) };
let is_popup = matches!(attributes.window_type, WindowType::Popup);
let mut window_flags = WindowFlags::empty();
window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations);
window_flags.set(WindowFlags::POPUP, is_popup);
window_flags.set(WindowFlags::MARKER_UNDECORATED_SHADOW, win_attributes.decoration_shadow);
window_flags
.set(WindowFlags::ALWAYS_ON_TOP, attributes.window_level == WindowLevel::AlwaysOnTop);
@@ -1380,7 +1436,9 @@ unsafe fn init(
window_flags.set(WindowFlags::MARKER_ACTIVATE, attributes.active);
window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent);
// WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured.
window_flags.set(WindowFlags::RESIZABLE, attributes.resizable);
// Popups are never resizable, matching the Wayland and macOS backends (and avoiding the
// thick `WS_SIZEBOX` resize frame).
window_flags.set(WindowFlags::RESIZABLE, attributes.resizable && !is_popup);
// Will be changed later using `window.set_enabled_buttons` but we need to set a default here
// so the diffing later can work.
window_flags.set(WindowFlags::CLOSABLE, true);
@@ -1399,14 +1457,23 @@ unsafe fn init(
let parent = match attributes.parent_window() {
Some(rwh_06::RawWindowHandle::Win32(handle)) => {
if !is_popup {
window_flags.set(WindowFlags::CHILD, true);
}
if win_attributes.menu.is_some() {
warn!("Setting a menu on a child window is unsupported");
}
Some(handle.hwnd.get() as HWND)
},
Some(raw) => unreachable!("Invalid raw window handle {raw:?} on Windows"),
None => fallback_parent(),
None => {
if is_popup {
return Err(RequestError::NotSupported(NotSupportedError::new(
"Popup without a parent is not supported!",
)));
}
fallback_parent()
},
};
let menu = win_attributes.menu;

View File

@@ -13,9 +13,9 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
SWP_NOREPOSITION, SWP_NOSIZE, SWP_NOZORDER, SendMessageW, SetWindowLongW, SetWindowPos,
ShowWindow, WINDOW_EX_STYLE, WINDOW_STYLE, WINDOWPLACEMENT, WS_BORDER, WS_CAPTION, WS_CHILD,
WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED,
WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_MAXIMIZE,
WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX,
WS_SYSMENU, WS_VISIBLE,
WS_EX_NOACTIVATE, WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_TRANSPARENT,
WS_EX_WINDOWEDGE, WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX,
WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE,
};
use winit_core::icon::Icon;
use winit_core::keyboard::ModifiersState;
@@ -276,8 +276,18 @@ impl WindowFlags {
pub fn to_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) {
// Required styles to properly support common window functionality like aero snap.
let mut style = WS_CAPTION | WS_BORDER | WS_CLIPSIBLINGS | WS_SYSMENU;
let mut style = WS_CLIPSIBLINGS;
let mut style_ex = WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES;
if self.contains(WindowFlags::POPUP) {
style |= WS_POPUP;
// Don't activate the popup (and thus don't deactivate the parent) when it is shown or
// clicked, unless the caller requested activation via `WindowAttributes::active`.
if !self.contains(WindowFlags::MARKER_ACTIVATE) {
style_ex |= WS_EX_NOACTIVATE;
}
} else {
style |= WS_CAPTION | WS_SYSMENU | WS_BORDER;
};
if self.contains(WindowFlags::RESIZABLE) {
style |= WS_SIZEBOX;
@@ -300,8 +310,8 @@ impl WindowFlags {
if self.contains(WindowFlags::NO_BACK_BUFFER) {
style_ex |= WS_EX_NOREDIRECTIONBITMAP;
}
if self.contains(WindowFlags::CHILD) {
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
if self.contains(WindowFlags::CHILD) && !self.contains(WindowFlags::POPUP) {
style |= WS_CHILD;
// Remove decorations window styles for child
if !self.contains(WindowFlags::MARKER_DECORATIONS) {
@@ -309,9 +319,6 @@ impl WindowFlags {
style_ex &= !WS_EX_WINDOWEDGE;
}
}
if self.contains(WindowFlags::POPUP) {
style |= WS_POPUP;
}
if self.contains(WindowFlags::MINIMIZED) {
style |= WS_MINIMIZE;
}

View File

@@ -66,13 +66,26 @@ impl Window {
event_loop: &ActiveEventLoop,
attribs: WindowAttributes,
) -> Result<Self, RequestError> {
use winit_core::window::WindowType;
match attribs.window_type() {
WindowType::Window => {
let window = Arc::new(UnownedWindow::new(event_loop, attribs)?);
event_loop.windows.borrow_mut().insert(window.id(), Arc::downgrade(&window));
Ok(Window(window))
},
WindowType::Popup => Err(RequestError::NotSupported(NotSupportedError::new(
"Popups are not implemented for X11",
))),
_ => Err(RequestError::NotSupported(NotSupportedError::new("Unsupported window type"))),
}
}
}
impl CoreWindow for Window {
fn window_type(&self) -> winit_core::window::WindowType {
winit_core::window::WindowType::Window
}
fn id(&self) -> WindowId {
self.0.id()
}

View File

@@ -1,4 +1,4 @@
#[cfg(any(x11_platform, macos_platform, windows_platform))]
#[cfg(any(wayland_platform, x11_platform, macos_platform, windows_platform))]
#[allow(deprecated)]
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
@@ -10,7 +10,7 @@ fn main() -> Result<(), impl std::error::Error> {
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::{Window, WindowAttributes, WindowId};
use winit::window::{Window, WindowAttributes, WindowId, WindowType};
#[path = "util/fill.rs"]
mod fill;
@@ -39,6 +39,7 @@ fn main() -> Result<(), impl std::error::Error> {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let attributes = WindowAttributes::default()
.with_title("parent window")
.with_window_type(WindowType::Window)
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
@@ -131,10 +132,10 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.run_app(Application { context, parent_window_id: None, windows: HashMap::new() })
}
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))]
#[cfg(not(any(wayland_platform, x11_platform, macos_platform, windows_platform)))]
fn main() {
panic!(
"This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature \
enabled."
"This example is supported only on wayland, x11, macOS, and Windows, with the `rwh_06` \
feature enabled."
);
}

View File

@@ -51,6 +51,7 @@ changelog entry.
- On Android, added scancode conversions for more obscure key codes.
- On Wayland, added `HoldGesture` event for multi-finger hold gestures
- On Wayland, added ext-background-effect-v1 support.
- On Wayland, Windows and macOS, added native popups (`WindowType::Popup`).
### Changed