diff --git a/src/platform/wayland.rs b/src/platform/wayland.rs index 84af00388b..6c4eea25a0 100644 --- a/src/platform/wayland.rs +++ b/src/platform/wayland.rs @@ -13,6 +13,9 @@ //! * `wayland-csd-adwaita` (default). //! * `wayland-csd-adwaita-crossfont`. //! * `wayland-csd-adwaita-notitle`. +#[cfg(wayland_platform)] +use sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}; + use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}; use crate::monitor::MonitorHandle; pub use crate::window::Theme; @@ -87,6 +90,21 @@ pub trait WindowAttributesExtWayland { /// For details about application ID conventions, see the /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) fn with_name(self, general: impl Into, instance: impl Into) -> Self; + + #[cfg(wayland_platform)] + fn with_anchor(self, anchor: Anchor) -> Self; + + #[cfg(wayland_platform)] + fn with_exclusive_zone(self, exclusive_zone: i32) -> Self; + + #[cfg(wayland_platform)] + fn with_margin(self, top: i32, right: i32, bottom: i32, left: i32) -> Self; + + #[cfg(wayland_platform)] + fn with_keyboard_interactivity(self, keyboard_interactivity: KeyboardInteractivity) -> Self; + + #[cfg(wayland_platform)] + fn with_layer(self, layer: Layer) -> Self; } impl WindowAttributesExtWayland for WindowAttributes { @@ -96,6 +114,44 @@ impl WindowAttributesExtWayland for WindowAttributes { Some(crate::platform_impl::ApplicationName::new(general.into(), instance.into())); self } + + #[inline] + #[cfg(wayland_platform)] + fn with_anchor(mut self, anchor: Anchor) -> Self { + self.platform_specific.wayland.anchor = Some(anchor); + self + } + + #[inline] + #[cfg(wayland_platform)] + fn with_exclusive_zone(mut self, exclusive_zone: i32) -> Self { + self.platform_specific.wayland.exclusive_zone = Some(exclusive_zone); + self + } + + #[inline] + #[cfg(wayland_platform)] + fn with_margin(mut self, top: i32, right: i32, bottom: i32, left: i32) -> Self { + self.platform_specific.wayland.margin = Some((top, right, bottom, left)); + self + } + + #[inline] + #[cfg(wayland_platform)] + fn with_keyboard_interactivity( + mut self, + keyboard_interactivity: KeyboardInteractivity, + ) -> Self { + self.platform_specific.wayland.keyboard_interactivity = Some(keyboard_interactivity); + self + } + + #[inline] + #[cfg(wayland_platform)] + fn with_layer(mut self, layer: Layer) -> Self { + self.platform_specific.wayland.layer = Some(layer); + self + } } /// Additional methods on `MonitorHandle` that are specific to Wayland. diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 55ba7e2e02..750d7c11ec 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -10,6 +10,7 @@ use std::time::Duration; #[cfg(x11_platform)] use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc, sync::Mutex}; +use sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}; use smol_str::SmolStr; pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey}; @@ -69,6 +70,8 @@ pub struct PlatformSpecificWindowAttributes { pub activation_token: Option, #[cfg(x11_platform)] pub x11: X11WindowAttributes, + #[cfg(wayland_platform)] + pub wayland: WaylandWindowAttributes, } #[derive(Clone, Debug, PartialEq)] @@ -84,6 +87,16 @@ pub struct X11WindowAttributes { pub embed_window: Option, } +#[derive(Clone, Debug, PartialEq)] +#[cfg(wayland_platform)] +pub struct WaylandWindowAttributes { + pub layer: Option, + pub anchor: Option, + pub exclusive_zone: Option, + pub margin: Option<(i32, i32, i32, i32)>, + pub keyboard_interactivity: Option, +} + #[cfg_attr(not(x11_platform), allow(clippy::derivable_impls))] impl Default for PlatformSpecificWindowAttributes { fn default() -> Self { @@ -99,6 +112,14 @@ impl Default for PlatformSpecificWindowAttributes { x11_window_types: vec![XWindowType::Normal], embed_window: None, }, + #[cfg(wayland_platform)] + wayland: WaylandWindowAttributes { + layer: None, + margin: None, + anchor: None, + exclusive_zone: None, + keyboard_interactivity: None, + }, } } } diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index bec0a55d3a..c8a4f1a8a9 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -14,6 +14,7 @@ use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::registry::{ProvidesRegistryState, RegistryState}; use sctk::seat::pointer::ThemedPointer; use sctk::seat::SeatState; +use sctk::shell::wlr_layer::{LayerShell, LayerShellHandler, LayerSurface, LayerSurfaceConfigure}; use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler}; use sctk::shell::xdg::XdgShell; use sctk::shell::WaylandSurface; @@ -61,6 +62,9 @@ pub struct WinitState { /// The XDG shell that is used for windows. pub xdg_shell: XdgShell, + /// The layer shell for layer surfaces + pub layer_shell: LayerShell, + /// The currently present windows. pub windows: RefCell>>>, @@ -171,6 +175,8 @@ impl WinitState { xdg_shell: XdgShell::bind(globals, queue_handle).map_err(|err| os_error!(err))?, xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(), + layer_shell: LayerShell::bind(globals, queue_handle).map_err(|err| os_error!(err))?, + windows: Default::default(), window_requests: Default::default(), window_compositor_updates: Vec::new(), @@ -308,6 +314,46 @@ impl WindowHandler for WinitState { } } +impl LayerShellHandler for WinitState { + fn closed(&mut self, _: &Connection, _: &QueueHandle, layer: &LayerSurface) { + let window_id = super::make_wid(layer.wl_surface()); + Self::queue_close(&mut self.window_compositor_updates, window_id); + } + + fn configure( + &mut self, + _: &Connection, + _: &QueueHandle, + layer: &LayerSurface, + configure: LayerSurfaceConfigure, + _serial: u32, + ) { + let window_id = super::make_wid(layer.wl_surface()); + + let pos = if let Some(pos) = + self.window_compositor_updates.iter().position(|update| update.window_id == window_id) + { + pos + } else { + self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id)); + self.window_compositor_updates.len() - 1 + }; + + // Populate the configure to the window. + // + // XXX the size on the window will be updated right before dispatching the size to the user. + self.windows + .get_mut() + .get_mut(&window_id) + .expect("got configure for dead window.") + .lock() + .unwrap() + .configure_layer(configure); + + self.window_compositor_updates[pos].resized = true; + } +} + impl OutputHandler for WinitState { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state @@ -435,3 +481,4 @@ sctk::delegate_registry!(WinitState); sctk::delegate_shm!(WinitState); sctk::delegate_xdg_shell!(WinitState); sctk::delegate_xdg_window!(WinitState); +sctk::delegate_layer!(WinitState); diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index cb7317737a..98cb92630b 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -8,9 +8,11 @@ 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::wlr_layer::{Anchor, KeyboardInteractivity, Layer, LayerSurface}; use sctk::shell::xdg::window::{Window as SctkWindow, WindowDecorations}; use sctk::shell::WaylandSurface; use tracing::warn; +use wayland_client::protocol::wl_output::WlOutput; use super::event_loop::sink::EventSink; use super::output::MonitorHandle; @@ -35,8 +37,8 @@ pub use state::WindowState; /// The Wayland window. pub struct Window { - /// Reference to the underlying SCTK window. - window: SctkWindow, + /// The underlying window shell and state. + window: WindowShell, /// Window id. window_id: WindowId, @@ -99,17 +101,75 @@ impl Window { WindowDecorations::RequestClient }; - let window = - state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle); - - let mut window_state = WindowState::new( - event_loop_window_target.handle.clone(), - &event_loop_window_target.queue_handle, - &state, - size, - window.clone(), - attributes.preferred_theme, - ); + let (window, mut window_state) = if attributes.platform_specific.wayland.layer.is_some() + || attributes.window_level != WindowLevel::Normal + { + let layer_surface = state.layer_shell.create_layer_surface( + &queue_handle, + surface.clone(), + attributes + .platform_specific + .wayland + .layer + .unwrap_or(attributes.window_level.into()), + Some(attributes.title.clone()), + None, + ); + let window_state = WindowState::new_layer( + event_loop_window_target.handle.clone(), + &event_loop_window_target.queue_handle, + &state, + size, + layer_surface.clone(), + attributes.preferred_theme, + ); + + let surface_size = size.to_logical::(1.0); + layer_surface.set_size(surface_size.width, surface_size.height); + if let Some(anchor) = attributes.platform_specific.wayland.anchor { + layer_surface.set_anchor(anchor); + } + if let Some(exclusive_zone) = attributes.platform_specific.wayland.exclusive_zone { + layer_surface.set_exclusive_zone(exclusive_zone) + } + if let Some((top, right, bottom, left)) = attributes.platform_specific.wayland.margin { + layer_surface.set_margin(top, right, bottom, left); + } + if let Some(keyboard_interactivity) = + attributes.platform_specific.wayland.keyboard_interactivity + { + layer_surface.set_keyboard_interactivity(keyboard_interactivity); + } + (WindowShell::WlrLayer { surface: layer_surface }, window_state) + } else { + let window = + state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle); + + let mut window_state = WindowState::new( + event_loop_window_target.handle.clone(), + &event_loop_window_target.queue_handle, + &state, + size, + window.clone(), + attributes.preferred_theme, + ); + + // Set the app_id. + if let Some(name) = attributes.platform_specific.name.map(|name| name.general) { + window.set_app_id(name); + } + + // Set the min and max sizes. + window_state.set_resizable(attributes.resizable); + let min_size = attributes.min_surface_size.map(|size| size.to_logical(1.)); + let max_size = attributes.max_surface_size.map(|size| size.to_logical(1.)); + window_state.set_min_surface_size(min_size); + window_state.set_max_surface_size(max_size); + + // Non-resizable implies that the min and max sizes are set to the same value. + window_state.set_resizable(attributes.resizable); + (WindowShell::Xdg { window }, window_state) + }; // Set transparency hint. window_state.set_transparent(attributes.transparent); @@ -119,11 +179,6 @@ impl Window { // Set the decorations hint. window_state.set_decorate(attributes.decorations); - // Set the app_id. - if let Some(name) = attributes.platform_specific.name.map(|name| name.general) { - window.set_app_id(name); - } - // Set the window title. window_state.set_title(attributes.title); @@ -152,7 +207,7 @@ impl Window { window.set_fullscreen(output.as_ref()) }, - _ if attributes.maximized => window.set_maximized(), + _ if attributes.maximized => window.set_maximized(true), _ => (), }; @@ -169,7 +224,7 @@ impl Window { } // XXX Do initial commit. - window.commit(); + window.wl_surface().commit(); // Add the window and window requests into the state. let window_state = Arc::new(Mutex::new(window_state)); @@ -239,6 +294,31 @@ impl Window { pub fn surface(&self) -> &WlSurface { self.window.wl_surface() } + + #[inline] + pub fn set_anchor(&self, anchor: Anchor) { + self.window.set_anchor(anchor); + } + + #[inline] + pub fn set_margin(&self, top: i32, right: i32, bottom: i32, left: i32) { + self.window.set_margin(top, right, bottom, left); + } + + #[inline] + pub fn set_exclusive_zone(&self, exclusive_zone: i32) { + self.window.set_exclusive_zone(exclusive_zone); + } + + #[inline] + pub fn set_keyboard_interactivity(&self, keyboard_interactivity: KeyboardInteractivity) { + self.window.set_keyboard_interactivity(keyboard_interactivity); + } + + #[inline] + pub fn set_layer(&self, layer: Layer) { + self.window.set_layer(layer); + } } impl Drop for Window { @@ -308,12 +388,13 @@ impl CoreWindow for Window { } fn outer_position(&self) -> Result, RequestError> { - Err(NotSupportedError::new("window position information is not available on Wayland") - .into()) + // XXX just for LayerShell + self.window_state.lock().unwrap().outer_position() } - fn set_outer_position(&self, _position: Position) { - // Not possible. + fn set_outer_position(&self, position: Position) { + // XXX just for LayerShell + self.window_state.lock().unwrap().set_outer_position(position); } fn surface_size(&self) -> PhysicalSize { @@ -419,21 +500,11 @@ impl CoreWindow for Window { } fn set_maximized(&self, maximized: bool) { - if maximized { - self.window.set_maximized() - } else { - self.window.unset_maximized() - } + self.window.set_maximized(maximized) } fn is_maximized(&self) -> bool { - self.window_state - .lock() - .unwrap() - .last_configure - .as_ref() - .map(|last_configure| last_configure.is_maximized()) - .unwrap_or_default() + self.window_state.lock().unwrap().is_maximized() } fn set_fullscreen(&self, fullscreen: Option) { @@ -456,14 +527,7 @@ impl CoreWindow for Window { } fn fullscreen(&self) -> Option { - let is_fullscreen = self - .window_state - .lock() - .unwrap() - .last_configure - .as_ref() - .map(|last_configure| last_configure.is_fullscreen()) - .unwrap_or_default(); + let is_fullscreen = self.window_state.lock().unwrap().is_fullscreen(); if is_fullscreen { let current_monitor = self.current_monitor(); @@ -493,7 +557,9 @@ impl CoreWindow for Window { self.window_state.lock().unwrap().is_decorated() } - fn set_window_level(&self, _level: WindowLevel) {} + fn set_window_level(&self, level: WindowLevel) { + self.window.set_layer(level.into()); + } fn set_window_icon(&self, _window_icon: Option) {} @@ -659,6 +725,95 @@ impl CoreWindow for Window { } } +enum WindowShell { + Xdg { window: SctkWindow }, + WlrLayer { surface: LayerSurface }, +} + +impl WindowShell { + pub fn set_maximized(&self, maximized: bool) { + match self { + WindowShell::Xdg { window } => { + if maximized { + window.set_maximized() + } else { + window.unset_maximized() + } + }, + WindowShell::WlrLayer { .. } => { + warn!("Maximizing is ignored for layer_shell windows") + }, + } + } + + pub fn set_minimized(&self) { + match self { + WindowShell::Xdg { window } => window.set_minimized(), + WindowShell::WlrLayer { .. } => warn!("Minimizing is ignored for layer_shell windows"), + } + } + + pub fn set_fullscreen(&self, output: Option<&WlOutput>) { + match self { + WindowShell::Xdg { window } => { + window.set_fullscreen(output); + }, + WindowShell::WlrLayer { .. } => warn!("Fullscreen is ignored for layer_shell windows"), + } + } + + pub fn unset_fullscreen(&self) { + match self { + WindowShell::Xdg { window } => window.unset_fullscreen(), + WindowShell::WlrLayer { .. } => warn!("Fullscreen is ignored for layer_shell windows"), + } + } + + pub fn wl_surface(&self) -> &WlSurface { + match self { + WindowShell::Xdg { window } => window.wl_surface(), + WindowShell::WlrLayer { surface } => surface.wl_surface(), + } + } + + pub fn set_anchor(&self, anchor: Anchor) { + match self { + WindowShell::WlrLayer { surface } => surface.set_anchor(anchor), + WindowShell::Xdg { .. } => warn!("Anchor is ignored for XDG windows"), + } + } + + pub fn set_margin(&self, top: i32, right: i32, bottom: i32, left: i32) { + match self { + WindowShell::WlrLayer { surface } => surface.set_margin(top, right, bottom, left), + WindowShell::Xdg { .. } => warn!("Margin is ignored for XDG windows"), + } + } + + pub fn set_exclusive_zone(&self, exclusive_zone: i32) { + match self { + WindowShell::WlrLayer { surface } => surface.set_exclusive_zone(exclusive_zone), + WindowShell::Xdg { .. } => warn!("Exclusive zone is ignored for XDG windows"), + } + } + + pub fn set_keyboard_interactivity(&self, keyboard_interactivity: KeyboardInteractivity) { + match self { + WindowShell::WlrLayer { surface } => { + surface.set_keyboard_interactivity(keyboard_interactivity) + }, + WindowShell::Xdg { .. } => warn!("Keyboard interactivity is ignored for XDG windows"), + } + } + + pub fn set_layer(&self, layer: Layer) { + match self { + WindowShell::WlrLayer { surface } => surface.set_layer(layer), + WindowShell::Xdg { .. } => warn!("Layer is ignored for XDG windows"), + } + } +} + /// The request from the window to the event loop. #[derive(Debug)] pub struct WindowRequests { diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index 69430d79b0..f5bf097de4 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -5,6 +5,7 @@ use std::sync::{Arc, Mutex, Weak}; use std::time::Duration; use ahash::HashSet; +use dpi::{PhysicalPosition, Position}; use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt}; use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::protocol::wl_seat::WlSeat; @@ -19,6 +20,7 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3:: 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::{PointerDataExt, ThemedPointer}; +use sctk::shell::wlr_layer::{LayerSurface, LayerSurfaceConfigure}; use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure}; use sctk::shell::xdg::XdgSurface; use sctk::shell::WaylandSurface; @@ -61,9 +63,6 @@ pub struct WindowState { // A shared pool where to allocate custom cursors. custom_cursor_pool: Arc>, - /// The last received configure. - pub last_configure: Option, - /// The pointers observed on the window. pub pointers: Vec>>, @@ -78,15 +77,15 @@ pub struct WindowState { /// Queue handle. pub queue_handle: QueueHandle, + /// State that differs based on being an XDG shell or a WLR layer shell + shell_specific: ShellSpecificState, + /// Theme variant. theme: Option, /// The current window title. title: String, - /// Whether the frame is resizable. - resizable: bool, - // NOTE: we can't use simple counter, since it's racy when seat getting destroyed and new // is created, since add/removed stuff could be delivered a bit out of order. /// Seats that has keyboard focus on that window. @@ -116,21 +115,9 @@ pub struct WindowState { /// The surface size of the window, as in without client side decorations. size: LogicalSize, - /// Whether the CSD fail to create, so we don't try to create them on each iteration. - csd_fails: bool, - /// Whether we should decorate the frame. decorate: bool, - /// Min size. - min_surface_size: LogicalSize, - max_surface_size: Option>, - - /// The size of the window when no states were applied to it. The primary use for it - /// is to fallback to original window size, before it was maximized, if the compositor - /// sends `None` for the new size in the configure. - stateless_size: LogicalSize, - /// Initial window size provided by the user. Removed on the first /// configure. initial_size: Option, @@ -138,8 +125,6 @@ pub struct WindowState { /// The state of the frame callback. frame_callback_state: FrameCallbackState, - viewport: Option, - fractional_scale: Option, blur: Option, blur_manager: Option, @@ -147,16 +132,46 @@ pub struct WindowState { /// /// The value is the serial of the event triggered moved. has_pending_move: Option, +} + +enum ShellSpecificState { + Xdg { + /// The underlying SCTK window. + window: Window, + + /// The last received configure. + last_configure: Option, + + /// Whether the frame is resizable. + resizable: bool, - /// The underlying SCTK window. - pub window: Window, + // 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 + // frame after the `window` is dropped. To achieve that we rely on rust's struct + // field drop order guarantees. + /// The window frame, which is created from the configure request. + frame: Option, - // 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 - // frame after the `window` is dropped. To achieve that we rely on rust's struct - // field drop order guarantees. - /// The window frame, which is created from the configure request. - frame: Option, + /// Whether the CSD fail to create, so we don't try to create them on each iteration. + csd_fails: bool, + + /// The size of the window when no states were applied to it. The primary use for it + /// is to fallback to original window size, before it was maximized, if the compositor + /// sends `None` for the new size in the configure. + stateless_size: LogicalSize, + + /// Min size. + min_surface_size: LogicalSize, + max_surface_size: Option>, + + viewport: Option, + fractional_scale: Option, + }, + WlrLayer { + surface: LayerSurface, + + last_configure: Option, + }, } impl WindowState { @@ -185,37 +200,83 @@ impl WindowState { blur_manager: winit_state.kwin_blur_manager.clone(), compositor, handle, - csd_fails: false, + shell_specific: ShellSpecificState::Xdg { + window, + last_configure: None, + resizable: true, + frame: None, + csd_fails: false, + stateless_size: initial_size.to_logical(1.), + max_surface_size: None, + min_surface_size: MIN_WINDOW_SIZE, + viewport, + fractional_scale, + }, cursor_grab_mode: GrabState::new(), selected_cursor: Default::default(), cursor_visible: true, decorate: true, - fractional_scale, - frame: None, frame_callback_state: FrameCallbackState::None, seat_focus: Default::default(), has_pending_move: None, ime_allowed: false, ime_purpose: ImePurpose::Normal, - last_configure: None, - max_surface_size: None, - min_surface_size: MIN_WINDOW_SIZE, pointer_constraints, pointers: Default::default(), queue_handle: queue_handle.clone(), - resizable: true, scale_factor: 1., shm: winit_state.shm.wl_shm().clone(), custom_cursor_pool: winit_state.custom_cursor_pool.clone(), size: initial_size.to_logical(1.), - stateless_size: initial_size.to_logical(1.), initial_size: Some(initial_size), text_inputs: Vec::new(), theme, title: String::default(), transparent: false, - viewport, - window, + } + } + + pub fn new_layer( + handle: Arc, + queue_handle: &QueueHandle, + winit_state: &WinitState, + initial_size: Size, + layer_surface: LayerSurface, + theme: Option, + ) -> Self { + let compositor = winit_state.compositor_state.clone(); + let pointer_constraints = winit_state.pointer_constraints.clone(); + + Self { + handle, + compositor, + theme, + cursor_grab_mode: GrabState::new(), + cursor_visible: true, + ime_allowed: false, + ime_purpose: ImePurpose::Normal, + pointer_constraints, + pointers: Default::default(), + queue_handle: queue_handle.clone(), + scale_factor: 1., + shm: winit_state.shm.wl_shm().clone(), + shell_specific: ShellSpecificState::WlrLayer { + surface: layer_surface, + last_configure: None, + }, + size: initial_size.to_logical(1.0), + selected_cursor: Default::default(), + decorate: false, + frame_callback_state: FrameCallbackState::None, + seat_focus: Default::default(), + has_pending_move: None, + custom_cursor_pool: winit_state.custom_cursor_pool.clone(), + initial_size: Some(initial_size), + text_inputs: Vec::new(), + title: String::default(), + transparent: false, + blur: None, + blur_manager: winit_state.kwin_blur_manager.clone(), } } @@ -247,37 +308,55 @@ impl WindowState { /// Request a frame callback if we don't have one for this window in flight. pub fn request_frame_callback(&mut self) { - let surface = self.window.wl_surface(); - match self.frame_callback_state { + match &self.frame_callback_state { FrameCallbackState::None | FrameCallbackState::Received => { self.frame_callback_state = FrameCallbackState::Requested; + let surface = self.wl_surface(); surface.frame(&self.queue_handle, surface.clone()); }, FrameCallbackState::Requested => (), } } + fn wl_surface(&self) -> &WlSurface { + match &self.shell_specific { + ShellSpecificState::Xdg { window, .. } => window.wl_surface(), + ShellSpecificState::WlrLayer { surface, .. } => surface.wl_surface(), + } + } + pub fn configure( &mut self, configure: WindowConfigure, shm: &Shm, subcompositor: &Option>, ) -> bool { + let scale_factor = self.scale_factor(); + let bounds = self.surface_size_bounds(&configure); + let ShellSpecificState::Xdg { + ref window, + ref mut last_configure, + ref mut frame, + ref mut csd_fails, + ref mut stateless_size, + .. + } = self.shell_specific + else { + unreachable!(); + }; // 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()); - self.stateless_size = self.size; + self.size = initial_size.to_logical(scale_factor); + *stateless_size = self.size; } if let Some(subcompositor) = subcompositor.as_ref().filter(|_| { - configure.decoration_mode == DecorationMode::Client - && self.frame.is_none() - && !self.csd_fails + configure.decoration_mode == DecorationMode::Client && frame.is_none() && !*csd_fails }) { match WinitFrame::new( - &self.window, + window, shm, #[cfg(feature = "sctk-adwaita")] self.compositor.clone(), @@ -286,26 +365,26 @@ impl WindowState { #[cfg(feature = "sctk-adwaita")] into_sctk_adwaita_config(self.theme), ) { - Ok(mut frame) => { - frame.set_title(&self.title); - frame.set_scaling_factor(self.scale_factor); + Ok(mut f) => { + f.set_title(&self.title); + f.set_scaling_factor(self.scale_factor); // Hide the frame if we were asked to not decorate. - frame.set_hidden(!self.decorate); - self.frame = Some(frame); + f.set_hidden(!self.decorate); + *frame = Some(f); }, Err(err) => { warn!("Failed to create client side decorations frame: {err}"); - self.csd_fails = true; + *csd_fails = true; }, } } else if configure.decoration_mode == DecorationMode::Server { // Drop the frame for server side decorations to save resources. - self.frame = None; + *frame = None; } let stateless = Self::is_stateless(&configure); - let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() { + let (mut new_size, constrain) = if let Some(frame) = frame { // Configure the window states. frame.update_state(configure.state); @@ -316,20 +395,19 @@ impl WindowState { let height = height.map(|h| h.get()).unwrap_or(1); ((width, height).into(), false) }, - (..) if stateless => (self.stateless_size, true), + (..) if stateless => (*stateless_size, true), _ => (self.size, true), } } else { match configure.new_size { (Some(width), Some(height)) => ((width.get(), height.get()).into(), false), - _ if stateless => (self.stateless_size, true), + _ if stateless => (*stateless_size, true), _ => (self.size, true), } }; // Apply configure bounds only when compositor let the user decide what size to pick. if constrain { - let bounds = self.surface_size_bounds(&configure); new_size.width = bounds.0.map(|bound_w| new_size.width.min(bound_w.get())).unwrap_or(new_size.width); new_size.height = bounds @@ -339,7 +417,7 @@ impl WindowState { } let new_state = configure.state; - let old_state = self.last_configure.as_ref().map(|configure| configure.state); + let old_state = last_configure.as_ref().map(|configure| configure.state); let state_change_requires_resize = old_state .map(|old_state| { @@ -352,7 +430,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); @@ -362,6 +440,25 @@ impl WindowState { } } + pub fn configure_layer(&mut self, configure: LayerSurfaceConfigure) { + let ShellSpecificState::WlrLayer { last_configure, .. } = &mut self.shell_specific else { + unreachable!(); + }; + // Configure the window states. + let new_size = match configure.new_size { + (0, 0) => self.size, + (0, height) => (self.size.width, height).into(), + (width, 0) => (width, self.size.height).into(), + (width, height) => (width, height).into(), + }; + + // XXX Set the configuration before doing a resize. + *last_configure = Some(configure); + + // XXX Set the configuration before doing a resize. + self.resize(new_size); + } + /// Compute the bounds for the surface size of the surface. fn surface_size_bounds( &self, @@ -372,14 +469,25 @@ impl WindowState { None => (None, None), }; - if let Some(frame) = self.frame.as_ref() { - let (width, height) = frame.subtract_borders( - configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()), - configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()), - ); - (configure_bounds.0.and(width), configure_bounds.1.and(height)) - } else { - configure_bounds + match &self.shell_specific { + ShellSpecificState::Xdg { frame, .. } => { + if let Some(frame) = frame.as_ref() { + let (width, height) = frame.subtract_borders( + configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()), + configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()), + ); + (configure_bounds.0.and(width), configure_bounds.1.and(height)) + } else { + configure_bounds + } + }, + ShellSpecificState::WlrLayer { surface, .. } => { + surface.set_size( + configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()).into(), + configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()).into(), + ); + configure_bounds + }, } } @@ -388,9 +496,34 @@ impl WindowState { !(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled()) } + #[inline] + pub fn is_maximized(&self) -> bool { + match &self.shell_specific { + ShellSpecificState::Xdg { last_configure, .. } => last_configure + .as_ref() + .map(|last_configure| last_configure.is_maximized()) + .unwrap_or_default(), + ShellSpecificState::WlrLayer { .. } => false, + } + } + + #[inline] + pub fn is_fullscreen(&self) -> bool { + match &self.shell_specific { + ShellSpecificState::Xdg { last_configure, .. } => last_configure + .as_ref() + .map(|last_configure| last_configure.is_fullscreen()) + .unwrap_or_default(), + ShellSpecificState::WlrLayer { .. } => false, + } + } + /// Start interacting drag resize. pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> { - let xdg_toplevel = self.window.xdg_toplevel(); + let ShellSpecificState::Xdg { window, .. } = &self.shell_specific else { + return Ok(()); + }; + let xdg_toplevel = window.xdg_toplevel(); // TODO(kchibisov) handle touch serials. self.apply_on_pointer(|_, data| { @@ -404,7 +537,11 @@ impl WindowState { /// Start the window drag. pub fn drag_window(&self) -> Result<(), RequestError> { - let xdg_toplevel = self.window.xdg_toplevel(); + let ShellSpecificState::Xdg { window, .. } = &self.shell_specific else { + return Ok(()); + }; + let xdg_toplevel = window.xdg_toplevel(); + // TODO(kchibisov) handle touch serials. self.apply_on_pointer(|_, data| { let serial = data.latest_button_serial(); @@ -427,10 +564,13 @@ impl WindowState { window_id: WindowId, updates: &mut Vec, ) -> Option { - 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(), + let ShellSpecificState::Xdg { window, frame, .. } = &mut self.shell_specific else { + return Some(false); + }; + match frame.as_mut()?.on_click(timestamp, click, pressed)? { + FrameAction::Minimize => window.set_minimized(), + FrameAction::Maximize => window.set_maximized(), + FrameAction::UnMaximize => window.unset_maximized(), FrameAction::Close => WinitState::queue_close(updates, window_id), FrameAction::Move => self.has_pending_move = Some(serial), FrameAction::Resize(edge) => { @@ -446,9 +586,9 @@ 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)), _ => (), }; @@ -456,7 +596,10 @@ impl WindowState { } pub fn frame_point_left(&mut self) { - if let Some(frame) = self.frame.as_mut() { + let ShellSpecificState::Xdg { ref mut frame, .. } = &mut self.shell_specific else { + return; + }; + if let Some(frame) = frame.as_mut() { frame.click_point_left(); } } @@ -470,15 +613,19 @@ impl WindowState { x: f64, y: f64, ) -> Option { + let ShellSpecificState::Xdg { window, ref mut frame, .. } = &mut self.shell_specific else { + return None; + }; + // Take the serial if we had any, so it doesn't stick around. let serial = self.has_pending_move.take(); - if let Some(frame) = self.frame.as_mut() { + if let Some(frame) = frame.as_mut() { let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y); // If we have a cursor change, that means that cursor is over the decorations, // so try to apply move. if let Some(serial) = cursor.is_some().then_some(serial).flatten() { - self.window.move_(seat, serial); + window.move_(seat, serial); None } else { cursor @@ -491,7 +638,10 @@ impl WindowState { /// Get the stored resizable state. #[inline] pub fn resizable(&self) -> bool { - self.resizable + match self.shell_specific { + ShellSpecificState::Xdg { resizable, .. } => resizable, + ShellSpecificState::WlrLayer { .. } => false, + } } /// Set the resizable state on the window. @@ -499,11 +649,22 @@ impl WindowState { /// Returns `true` when the state was applied. #[inline] pub fn set_resizable(&mut self, resizable: bool) -> bool { - if self.resizable == resizable { - return false; + match &mut self.shell_specific { + ShellSpecificState::Xdg { resizable: state_resizable, .. } => { + if *state_resizable == resizable { + return false; + } + + *state_resizable = resizable; + }, + ShellSpecificState::WlrLayer { .. } => { + if resizable { + warn!("Resizable is ignored for layer_shell windows"); + } + return false; + }, } - self.resizable = resizable; if resizable { // Restore min/max sizes of the window. self.reload_min_max_hints(); @@ -513,11 +674,14 @@ impl WindowState { } // Reload the state on the frame as well. - if let Some(frame) = self.frame.as_mut() { - frame.set_resizable(resizable); + match &mut self.shell_specific { + ShellSpecificState::Xdg { frame: Some(frame), .. } => { + frame.set_resizable(resizable); + true + }, + ShellSpecificState::Xdg { frame: None, .. } => false, + ShellSpecificState::WlrLayer { .. } => false, } - - true } /// Whether the window is focused by any seat. @@ -541,17 +705,23 @@ impl WindowState { /// Whether the window received initial configure event from the compositor. #[inline] pub fn is_configured(&self) -> bool { - self.last_configure.is_some() + match &self.shell_specific { + ShellSpecificState::Xdg { last_configure, .. } => last_configure.is_some(), + ShellSpecificState::WlrLayer { last_configure, .. } => last_configure.is_some(), + } } #[inline] pub fn is_decorated(&mut self) -> bool { - let csd = self - .last_configure + let ShellSpecificState::Xdg { ref last_configure, ref frame, .. } = self.shell_specific + else { + return false; + }; + let csd = last_configure .as_ref() .map(|configure| configure.decoration_mode == DecorationMode::Client) .unwrap_or(false); - if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() { + if let Some(frame) = csd.then_some(frame.as_ref()).flatten() { !frame.is_hidden() } else { // Server side decorations. @@ -559,13 +729,37 @@ impl WindowState { } } + #[inline] + pub fn outer_position(&self) -> Result, RequestError> { + Err(NotSupportedError::new("window position information is not available on xdg Wayland") + .into()) + } + + pub fn set_outer_position(&self, position: Position) { + let position = position.to_logical(self.scale_factor); + + match &self.shell_specific { + ShellSpecificState::Xdg { .. } => { + warn!("Change window position is not available on xdg Wayland",) + }, + // XXX just works for LayerShell + // Probably we can save this change to get in the `outer_position` function + ShellSpecificState::WlrLayer { surface, .. } => { + surface.set_margin(position.y, 0, 0, position.x) + }, + } + } + /// Get the outer size of the window. #[inline] pub fn outer_size(&self) -> LogicalSize { - self.frame - .as_ref() - .map(|frame| frame.add_borders(self.size.width, self.size.height).into()) - .unwrap_or(self.size) + match &self.shell_specific { + ShellSpecificState::Xdg { frame, .. } => frame + .as_ref() + .map(|frame| frame.add_borders(self.size.width, self.size.height).into()) + .unwrap_or(self.size), + ShellSpecificState::WlrLayer { .. } => self.size, + } } /// Register pointer on the top-level. @@ -593,7 +787,10 @@ impl WindowState { /// Refresh the decorations frame if it's present returning whether the client should redraw. pub fn refresh_frame(&mut self) -> bool { - if let Some(frame) = self.frame.as_mut() { + let ShellSpecificState::Xdg { ref mut frame, .. } = self.shell_specific else { + return false; + }; + if let Some(frame) = frame.as_mut() { if !frame.is_hidden() && frame.is_dirty() { return frame.draw(); } @@ -616,7 +813,7 @@ impl WindowState { /// Reissue the transparency hint to the compositor. pub fn reload_transparency_hint(&self) { - let surface = self.window.wl_surface(); + let surface = self.wl_surface(); if self.transparent { surface.set_opaque_region(None); @@ -630,9 +827,11 @@ impl WindowState { /// Try to resize the window when the user can do so. pub fn request_surface_size(&mut self, surface_size: Size) -> PhysicalSize { - if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) { - self.resize(surface_size.to_logical(self.scale_factor())) - } + if let ShellSpecificState::Xdg { last_configure, .. } = &self.shell_specific { + if last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) { + self.resize(surface_size.to_logical(self.scale_factor())) + } + }; logical_to_physical_rounded(self.surface_size(), self.scale_factor()) } @@ -642,40 +841,54 @@ impl WindowState { self.size = surface_size; // Update the stateless size. - if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) { - self.stateless_size = surface_size; + match &mut self.shell_specific { + ShellSpecificState::Xdg { last_configure, stateless_size, .. } => { + if Some(true) == last_configure.as_ref().map(Self::is_stateless) { + *stateless_size = surface_size; + } + }, + ShellSpecificState::WlrLayer { .. } => {}, } // Update the inner frame. - let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() { - // Resize only visible frame. - if !frame.is_hidden() { - frame.resize( - NonZeroU32::new(self.size.width).unwrap(), - NonZeroU32::new(self.size.height).unwrap(), - ); - } + let ((x, y), outer_size) = match self.shell_specific { + ShellSpecificState::Xdg { frame: Some(ref mut frame), .. } => { + // Resize only visible frame. + if !frame.is_hidden() { + frame.resize( + NonZeroU32::new(self.size.width).unwrap(), + NonZeroU32::new(self.size.height).unwrap(), + ); + } - (frame.location(), frame.add_borders(self.size.width, self.size.height).into()) - } else { - ((0, 0), self.size) + (frame.location(), frame.add_borders(self.size.width, self.size.height).into()) + }, + _ => ((0, 0), self.size), }; // Reload the hint. self.reload_transparency_hint(); // Set the window geometry. - self.window.xdg_surface().set_window_geometry( - x, - y, - outer_size.width as i32, - outer_size.height as i32, - ); - - // Update the target viewport, this is used if and only if fractional scaling is in use. - if let Some(viewport) = self.viewport.as_ref() { - // Set surface size without the borders. - viewport.set_destination(self.size.width as _, self.size.height as _); + match &self.shell_specific { + ShellSpecificState::Xdg { window, viewport, .. } => { + window.xdg_surface().set_window_geometry( + x, + y, + outer_size.width as i32, + outer_size.height as i32, + ); + + // Update the target viewport, this is used if and only if fractional scaling is in + // use. + if let Some(viewport) = viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination(self.size.width as _, self.size.height as _); + } + }, + ShellSpecificState::WlrLayer { surface, .. } => { + surface.set_size(outer_size.width, outer_size.height) + }, } } @@ -755,48 +968,73 @@ impl WindowState { /// Set maximum inner window size. pub fn set_min_surface_size(&mut self, size: Option>) { - // Ensure that the window has the right minimum size. - let mut size = size.unwrap_or(MIN_WINDOW_SIZE); - size.width = size.width.max(MIN_WINDOW_SIZE.width); - size.height = size.height.max(MIN_WINDOW_SIZE.height); - - // Add the borders. - let size = self - .frame - .as_ref() - .map(|frame| frame.add_borders(size.width, size.height).into()) - .unwrap_or(size); - - self.min_surface_size = size; - self.window.set_min_size(Some(size.into())); + match &mut self.shell_specific { + ShellSpecificState::Xdg { window, frame, min_surface_size, .. } => { + // Ensure that the window has the right minimum size. + let mut size = size.unwrap_or(MIN_WINDOW_SIZE); + size.width = size.width.max(MIN_WINDOW_SIZE.width); + size.height = size.height.max(MIN_WINDOW_SIZE.height); + + // Add the borders. + let size = frame + .as_ref() + .map(|frame| frame.add_borders(size.width, size.height).into()) + .unwrap_or(size); + + *min_surface_size = size; + window.set_min_size(Some(size.into())); + }, + ShellSpecificState::WlrLayer { .. } => { + warn!("Minimum size is ignored for layer_shell windows") + }, + } } /// Set maximum inner window size. pub fn set_max_surface_size(&mut self, size: Option>) { - let size = size.map(|size| { - self.frame - .as_ref() - .map(|frame| frame.add_borders(size.width, size.height).into()) - .unwrap_or(size) - }); - - self.max_surface_size = size; - self.window.set_max_size(size.map(Into::into)); + match &mut self.shell_specific { + ShellSpecificState::Xdg { window, frame, max_surface_size, .. } => { + let size = size.map(|size| { + frame + .as_ref() + .map(|frame| frame.add_borders(size.width, size.height).into()) + .unwrap_or(size) + }); + + *max_surface_size = size; + window.set_max_size(size.map(Into::into)); + }, + ShellSpecificState::WlrLayer { .. } => { + warn!("Maximum size is ignored for layer_shell windows") + }, + } } /// Set the CSD theme. pub fn set_theme(&mut self, theme: Option) { - self.theme = theme; - #[cfg(feature = "sctk-adwaita")] - if let Some(frame) = self.frame.as_mut() { - frame.set_config(into_sctk_adwaita_config(theme)) + match &mut self.shell_specific { + ShellSpecificState::Xdg { frame, .. } => { + self.theme = theme; + #[cfg(feature = "sctk-adwaita")] + if let Some(frame) = frame.as_mut() { + frame.set_config(into_sctk_adwaita_config(theme)) + } + }, + ShellSpecificState::WlrLayer { .. } => { + if theme.is_some() { + warn!("Theme is ignored for layer_shell windows") + } + }, } } /// The current theme for CSD decorations. #[inline] pub fn theme(&self) -> Option { - self.theme + match &self.shell_specific { + ShellSpecificState::Xdg { .. } => self.theme, + ShellSpecificState::WlrLayer { .. } => None, + } } /// Set the cursor grabbing state on the top-level. @@ -813,8 +1051,13 @@ impl WindowState { /// Reload the hints for minimum and maximum sizes. pub fn reload_min_max_hints(&mut self) { - self.set_min_surface_size(Some(self.min_surface_size)); - self.set_max_surface_size(self.max_surface_size); + match self.shell_specific { + ShellSpecificState::Xdg { min_surface_size, max_surface_size, .. } => { + self.set_min_surface_size(Some(min_surface_size)); + self.set_max_surface_size(max_surface_size); + }, + ShellSpecificState::WlrLayer { .. } => {}, + } } /// Set the grabbing state on the surface. @@ -842,7 +1085,7 @@ impl WindowState { }, } - let surface = self.window.wl_surface(); + let surface = self.wl_surface(); match mode { CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| { let pointer = pointer.pointer(); @@ -861,11 +1104,15 @@ impl WindowState { } pub fn show_window_menu(&self, position: LogicalPosition) { + let ShellSpecificState::Xdg { window, .. } = &self.shell_specific else { + return; + }; + // TODO(kchibisov) handle touch serials. self.apply_on_pointer(|_, data| { let 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()); }); } @@ -917,19 +1164,30 @@ impl WindowState { self.decorate = decorate; - match self.last_configure.as_ref().map(|configure| configure.decoration_mode) { - Some(DecorationMode::Server) if !self.decorate => { - // To disable decorations we should request client and hide the frame. - self.window.request_decoration_mode(Some(DecorationMode::Client)) - }, - _ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)), - _ => (), - } + match &mut self.shell_specific { + ShellSpecificState::Xdg { ref mut frame, window, last_configure, .. } => { + match last_configure.as_ref().map(|configure| configure.decoration_mode) { + Some(DecorationMode::Server) if !self.decorate => { + // To disable decorations we should request client and hide the frame. + window.request_decoration_mode(Some(DecorationMode::Client)) + }, + _ if self.decorate => { + window.request_decoration_mode(Some(DecorationMode::Server)) + }, + _ => (), + } - if let Some(frame) = self.frame.as_mut() { - frame.set_hidden(!decorate); - // Force the resize. - self.resize(self.size); + if let Some(frame) = frame.as_mut() { + frame.set_hidden(!decorate); + // Force the resize. + self.resize(self.size); + } + }, + ShellSpecificState::WlrLayer { .. } => { + if decorate { + warn!("Client-side decorations are ignored for layer_shell windows"); + } + }, } } @@ -997,29 +1255,34 @@ impl WindowState { pub fn set_scale_factor(&mut self, scale_factor: f64) { self.scale_factor = scale_factor; - // NOTE: When fractional scaling is not used update the buffer scale. - if self.fractional_scale.is_none() { - let _ = self.window.set_buffer_scale(self.scale_factor as _); - } - - if let Some(frame) = self.frame.as_mut() { + if let ShellSpecificState::Xdg { frame: Some(ref mut frame), fractional_scale, .. } = + &mut self.shell_specific + { frame.set_scaling_factor(scale_factor); + + // NOTE: When fractional scaling is not used update the buffer scale. + if fractional_scale.is_none() { + let _ = self.wl_surface().set_buffer_scale(scale_factor as _); + } } } /// Make window background blurred #[inline] pub fn set_blur(&mut self, blurred: bool) { + let ShellSpecificState::Xdg { window, .. } = &self.shell_specific else { + return; + }; if blurred && self.blur.is_none() { if let Some(blur_manager) = self.blur_manager.as_ref() { - let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle); + let blur = blur_manager.blur(self.wl_surface(), &self.queue_handle); blur.commit(); self.blur = Some(blur); } else { info!("Blur manager unavailable, unable to change blur") } } else if !blurred && self.blur.is_some() { - self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface()); + self.blur_manager.as_ref().unwrap().unset(window.wl_surface()); self.blur.take().unwrap().release(); } } @@ -1039,11 +1302,16 @@ impl WindowState { } // Update the CSD title. - if let Some(frame) = self.frame.as_mut() { - frame.set_title(&title); + match &mut self.shell_specific { + ShellSpecificState::Xdg { window, frame, .. } => { + // Update the CSD title. + if let Some(frame) = frame.as_mut() { + frame.set_title(&title); + } + window.set_title(&title); + }, + ShellSpecificState::WlrLayer { .. } => {}, } - - self.window.set_title(&title); self.title = title; } @@ -1083,12 +1351,15 @@ impl Drop for WindowState { blur.release(); } - if let Some(fs) = self.fractional_scale.take() { - fs.destroy(); - } + if let ShellSpecificState::Xdg { viewport, fractional_scale, .. } = &mut self.shell_specific + { + if let Some(fs) = fractional_scale.take() { + fs.destroy(); + } - if let Some(viewport) = self.viewport.take() { - viewport.destroy(); + if let Some(viewport) = viewport.take() { + viewport.destroy(); + } } // NOTE: the wl_surface used by the window is being cleaned up when diff --git a/src/window.rs b/src/window.rs index ff698af2c3..8e3075436b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -3,6 +3,8 @@ use std::fmt; #[doc(inline)] pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError}; +#[cfg(wayland_platform)] +use sctk::shell::wlr_layer::Layer; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -1511,6 +1513,17 @@ pub enum WindowLevel { AlwaysOnTop, } +#[cfg(wayland_platform)] +impl From for Layer { + fn from(value: WindowLevel) -> Self { + match value { + WindowLevel::AlwaysOnBottom => Layer::Bottom, + WindowLevel::Normal => Layer::Top, + WindowLevel::AlwaysOnTop => Layer::Overlay, + } + } +} + /// Generic IME purposes for use in [`Window::set_ime_purpose`]. /// /// The purpose may improve UX by optimizing the IME for the specific use case,