Detailed changes
@@ -7263,6 +7263,7 @@ dependencies = [
"async-task",
"backtrace",
"bindgen 0.71.1",
+ "bitflags 2.9.4",
"blade-graphics",
"blade-macros",
"blade-util",
@@ -7342,6 +7343,7 @@ dependencies = [
"wayland-cursor",
"wayland-protocols 0.31.2",
"wayland-protocols-plasma",
+ "wayland-protocols-wlr",
"windows 0.61.3",
"windows-core 0.61.2",
"windows-numerics",
@@ -19490,6 +19492,19 @@ dependencies = [
"wayland-scanner",
]
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
+dependencies = [
+ "bitflags 2.9.4",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols 0.32.9",
+ "wayland-scanner",
+]
+
[[package]]
name = "wayland-scanner"
version = "0.31.7"
@@ -39,6 +39,7 @@ macos-blade = [
"objc2-metal",
]
wayland = [
+ "bitflags",
"blade-graphics",
"blade-macros",
"blade-util",
@@ -52,6 +53,7 @@ wayland = [
"wayland-cursor",
"wayland-protocols",
"wayland-protocols-plasma",
+ "wayland-protocols-wlr",
"filedescriptor",
"xkbcommon",
"open",
@@ -86,6 +88,7 @@ doctest = false
anyhow.workspace = true
async-task = "4.7"
backtrace = { workspace = true, optional = true }
+bitflags = { workspace = true, optional = true }
blade-graphics = { workspace = true, optional = true }
blade-macros = { workspace = true, optional = true }
blade-util = { workspace = true, optional = true }
@@ -202,6 +205,9 @@ wayland-protocols = { version = "0.31.2", features = [
wayland-protocols-plasma = { version = "0.2.0", features = [
"client",
], optional = true }
+wayland-protocols-wlr = { version = "0.3.9", features = [
+ "client",
+], optional = true }
# X11
as-raw-xcb-connection = { version = "1", optional = true }
@@ -0,0 +1,87 @@
+fn main() {
+ #[cfg(all(target_os = "linux", feature = "wayland"))]
+ example::main();
+
+ #[cfg(not(all(target_os = "linux", feature = "wayland")))]
+ panic!("This example requires the `wayland` feature and a linux system.");
+}
+
+#[cfg(all(target_os = "linux", feature = "wayland"))]
+mod example {
+ use std::time::{Duration, SystemTime, UNIX_EPOCH};
+
+ use gpui::{
+ App, Application, Bounds, Context, FontWeight, Size, Window, WindowBackgroundAppearance,
+ WindowBounds, WindowKind, WindowOptions, div, layer_shell::*, point, prelude::*, px, rems,
+ rgba, white,
+ };
+
+ struct LayerShellExample;
+
+ impl LayerShellExample {
+ fn new(cx: &mut Context<Self>) -> Self {
+ cx.spawn(async move |this, cx| {
+ loop {
+ let _ = this.update(cx, |_, cx| cx.notify());
+ cx.background_executor()
+ .timer(Duration::from_millis(500))
+ .await;
+ }
+ })
+ .detach();
+
+ LayerShellExample
+ }
+ }
+
+ impl Render for LayerShellExample {
+ fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_secs();
+
+ let hours = (now / 3600) % 24;
+ let minutes = (now / 60) % 60;
+ let seconds = now % 60;
+
+ div()
+ .size_full()
+ .flex()
+ .items_center()
+ .justify_center()
+ .text_size(rems(4.5))
+ .font_weight(FontWeight::EXTRA_BOLD)
+ .text_color(white())
+ .bg(rgba(0x0000044))
+ .rounded_xl()
+ .child(format!("{:02}:{:02}:{:02}", hours, minutes, seconds))
+ }
+ }
+
+ pub fn main() {
+ Application::new().run(|cx: &mut App| {
+ cx.open_window(
+ WindowOptions {
+ titlebar: None,
+ window_bounds: Some(WindowBounds::Windowed(Bounds {
+ origin: point(px(0.), px(0.)),
+ size: Size::new(px(500.), px(200.)),
+ })),
+ app_id: Some("gpui-layer-shell-example".to_string()),
+ window_background: WindowBackgroundAppearance::Transparent,
+ kind: WindowKind::LayerShell(LayerShellOptions {
+ namespace: "gpui".to_string(),
+ anchor: Anchor::LEFT | Anchor::RIGHT | Anchor::BOTTOM,
+ margin: Some((px(0.), px(0.), px(40.), px(0.))),
+ keyboard_interactivity: KeyboardInteractivity::None,
+ ..Default::default()
+ }),
+ ..Default::default()
+ },
+ |_, cx| cx.new(LayerShellExample::new),
+ )
+ .unwrap();
+ });
+ }
+}
@@ -82,6 +82,9 @@ pub(crate) use test::*;
#[cfg(target_os = "windows")]
pub(crate) use windows::*;
+#[cfg(all(target_os = "linux", feature = "wayland"))]
+pub use linux::layer_shell;
+
#[cfg(any(test, feature = "test-support"))]
pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
@@ -120,6 +123,15 @@ pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
}
}
+#[cfg(target_os = "windows")]
+pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
+ Rc::new(
+ WindowsPlatform::new()
+ .inspect_err(|err| show_error("Failed to launch", err.to_string()))
+ .unwrap(),
+ )
+}
+
/// Return which compositor we're guessing we'll use.
/// Does not attempt to connect to the given compositor
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
@@ -151,15 +163,6 @@ pub fn guess_compositor() -> &'static str {
}
}
-#[cfg(target_os = "windows")]
-pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
- Rc::new(
- WindowsPlatform::new()
- .inspect_err(|err| show_error("Failed to launch", err.to_string()))
- .unwrap(),
- )
-}
-
pub(crate) trait Platform: 'static {
fn background_executor(&self) -> BackgroundExecutor;
fn foreground_executor(&self) -> ForegroundExecutor;
@@ -1293,7 +1296,7 @@ pub struct TitlebarOptions {
}
/// The kind of window to create
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WindowKind {
/// A normal application window
Normal,
@@ -1304,6 +1307,11 @@ pub enum WindowKind {
/// A floating window that appears on top of its parent window
Floating,
+
+ /// A Wayland LayerShell window, used to draw overlays or backgrounds for applications such as
+ /// docks, notifications or wallpapers.
+ #[cfg(all(target_os = "linux", feature = "wayland"))]
+ LayerShell(layer_shell::LayerShellOptions),
}
/// The appearance of the window, as defined by the operating system.
@@ -27,3 +27,6 @@ pub(crate) use x11::*;
pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
#[cfg(not(all(feature = "screen-capture", any(feature = "wayland", feature = "x11"))))]
pub(crate) type PlatformScreenCaptureFrame = ();
+
+#[cfg(feature = "wayland")]
+pub use wayland::layer_shell;
@@ -5,6 +5,9 @@ mod display;
mod serial;
mod window;
+/// Contains Types for configuring layer_shell surfaces.
+pub mod layer_shell;
+
pub(crate) use client::*;
use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
@@ -62,6 +62,7 @@ use wayland_protocols::xdg::decoration::zv1::client::{
};
use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blur_manager};
+use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
use xkbcommon::xkb::{self, KEYMAP_COMPILE_NO_FLAGS, Keycode};
@@ -115,6 +116,7 @@ pub struct Globals {
pub fractional_scale_manager:
Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
pub decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
+ pub layer_shell: Option<zwlr_layer_shell_v1::ZwlrLayerShellV1>,
pub blur_manager: Option<org_kde_kwin_blur_manager::OrgKdeKwinBlurManager>,
pub text_input_manager: Option<zwp_text_input_manager_v3::ZwpTextInputManagerV3>,
pub executor: ForegroundExecutor,
@@ -152,6 +154,7 @@ impl Globals {
viewporter: globals.bind(&qh, 1..=1, ()).ok(),
fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
+ layer_shell: globals.bind(&qh, 1..=5, ()).ok(),
blur_manager: globals.bind(&qh, 1..=1, ()).ok(),
text_input_manager: globals.bind(&qh, 1..=1, ()).ok(),
executor,
@@ -695,7 +698,10 @@ impl LinuxClient for WaylandClient {
) -> anyhow::Result<Box<dyn PlatformWindow>> {
let mut state = self.0.borrow_mut();
- let parent = state.keyboard_focused_window.as_ref().map(|w| w.toplevel());
+ let parent = state
+ .keyboard_focused_window
+ .as_ref()
+ .and_then(|w| w.toplevel());
let (window, surface_id) = WaylandWindow::new(
handle,
@@ -945,6 +951,7 @@ delegate_noop!(WaylandClientStatePtr: ignore wl_buffer::WlBuffer);
delegate_noop!(WaylandClientStatePtr: ignore wl_region::WlRegion);
delegate_noop!(WaylandClientStatePtr: ignore wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);
delegate_noop!(WaylandClientStatePtr: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
+delegate_noop!(WaylandClientStatePtr: ignore zwlr_layer_shell_v1::ZwlrLayerShellV1);
delegate_noop!(WaylandClientStatePtr: ignore org_kde_kwin_blur_manager::OrgKdeKwinBlurManager);
delegate_noop!(WaylandClientStatePtr: ignore zwp_text_input_manager_v3::ZwpTextInputManagerV3);
delegate_noop!(WaylandClientStatePtr: ignore org_kde_kwin_blur::OrgKdeKwinBlur);
@@ -1087,6 +1094,31 @@ impl Dispatch<xdg_toplevel::XdgToplevel, ObjectId> for WaylandClientStatePtr {
}
}
+impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ObjectId> for WaylandClientStatePtr {
+ fn event(
+ this: &mut Self,
+ _: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
+ event: <zwlr_layer_surface_v1::ZwlrLayerSurfaceV1 as Proxy>::Event,
+ surface_id: &ObjectId,
+ _: &Connection,
+ _: &QueueHandle<Self>,
+ ) {
+ let client = this.get_client();
+ let mut state = client.borrow_mut();
+ let Some(window) = get_window(&mut state, surface_id) else {
+ return;
+ };
+
+ drop(state);
+ let should_close = window.handle_layersurface_event(event);
+
+ if should_close {
+ // The close logic will be handled in drop_window()
+ window.close();
+ }
+ }
+}
+
impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientStatePtr {
fn event(
_: &mut Self,
@@ -0,0 +1,111 @@
+use bitflags::bitflags;
+use thiserror::Error;
+use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
+
+use crate::Pixels;
+
+/// The layer the surface is rendered on. Multiple surfaces can share a layer, and ordering within
+/// a single layer is undefined.
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub enum Layer {
+ /// The background layer, typically used for wallpapers.
+ Background,
+
+ /// The bottom layer.
+ Bottom,
+
+ /// The top layer, typically used for fullscreen windows.
+ Top,
+
+ /// The overlay layer, used for surfaces that should always be on top.
+ #[default]
+ Overlay,
+}
+
+impl From<Layer> for zwlr_layer_shell_v1::Layer {
+ fn from(layer: Layer) -> Self {
+ match layer {
+ Layer::Background => Self::Background,
+ Layer::Bottom => Self::Bottom,
+ Layer::Top => Self::Top,
+ Layer::Overlay => Self::Overlay,
+ }
+ }
+}
+
+bitflags! {
+ /// Screen anchor point for layer_shell surfaces. These can be used in any combination, e.g.
+ /// specifying `Anchor::LEFT | Anchor::RIGHT` will stretch the surface across the width of the
+ /// screen.
+ #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+ pub struct Anchor: u32 {
+ /// Anchor to the top edge of the screen.
+ const TOP = 1;
+ /// Anchor to the bottom edge of the screen.
+ const BOTTOM = 2;
+ /// Anchor to the left edge of the screen.
+ const LEFT = 4;
+ /// Anchor to the right edge of the screen.
+ const RIGHT = 8;
+ }
+}
+
+impl From<Anchor> for zwlr_layer_surface_v1::Anchor {
+ fn from(anchor: Anchor) -> Self {
+ Self::from_bits_truncate(anchor.bits())
+ }
+}
+
+/// Keyboard interactivity mode for the layer_shell surfaces.
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub enum KeyboardInteractivity {
+ /// No keyboard inputs will be delivered to the surface and it won't be able to receive
+ /// keyboard focus.
+ None,
+
+ /// The surface will receive exclusive keyboard focus as long as it is above the shell surface
+ /// layer, and no other layer_shell surfaces are above it.
+ Exclusive,
+
+ /// The surface can be focused similarly to a normal window.
+ #[default]
+ OnDemand,
+}
+
+impl From<KeyboardInteractivity> for zwlr_layer_surface_v1::KeyboardInteractivity {
+ fn from(value: KeyboardInteractivity) -> Self {
+ match value {
+ KeyboardInteractivity::None => Self::None,
+ KeyboardInteractivity::Exclusive => Self::Exclusive,
+ KeyboardInteractivity::OnDemand => Self::OnDemand,
+ }
+ }
+}
+
+/// Options for creating a layer_shell window.
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct LayerShellOptions {
+ /// The namespace for the surface, mostly used by compositors to apply rules, can not be
+ /// changed after the surface is created.
+ pub namespace: String,
+ /// The layer the surface is rendered on.
+ pub layer: Layer,
+ /// The anchor point of the surface.
+ pub anchor: Anchor,
+ /// Requests that the compositor avoids occluding an area with other surfaces.
+ pub exclusive_zone: Option<Pixels>,
+ /// The anchor point of the exclusive zone, will be determined using the anchor if left
+ /// unspecified.
+ pub exclusive_edge: Option<Anchor>,
+ /// Margins between the surface and its anchor point(s).
+ /// Specified in CSS order: top, right, bottom, left.
+ pub margin: Option<(Pixels, Pixels, Pixels, Pixels)>,
+ /// How keyboard events should be delivered to the surface.
+ pub keyboard_interactivity: KeyboardInteractivity,
+}
+
+/// An error indicating that an action failed because the compositor doesn't support the required
+/// layer_shell protocol.
+#[derive(Debug, Error)]
+#[error("Compositor doesn't support zwlr_layer_shell_v1")]
+pub struct LayerShellNotSupportedError;
@@ -23,12 +23,14 @@ use wayland_protocols::{
xdg::shell::client::xdg_toplevel::XdgToplevel,
};
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
+use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1;
use crate::{
AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
ResizeEdge, Size, Tiling, WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance,
- WindowBounds, WindowControlArea, WindowControls, WindowDecorations, WindowParams, px, size,
+ WindowBounds, WindowControlArea, WindowControls, WindowDecorations, WindowParams,
+ layer_shell::LayerShellNotSupportedError, px, size,
};
use crate::{
Capslock,
@@ -83,14 +85,12 @@ struct InProgressConfigure {
}
pub struct WaylandWindowState {
- xdg_surface: xdg_surface::XdgSurface,
+ surface_state: WaylandSurfaceState,
acknowledged_first_configure: bool,
pub surface: wl_surface::WlSurface,
- decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
app_id: Option<String>,
appearance: WindowAppearance,
blur: Option<org_kde_kwin_blur::OrgKdeKwinBlur>,
- toplevel: xdg_toplevel::XdgToplevel,
viewport: Option<wp_viewport::WpViewport>,
outputs: HashMap<ObjectId, Output>,
display: Option<(ObjectId, Output)>,
@@ -116,6 +116,161 @@ pub struct WaylandWindowState {
client_inset: Option<Pixels>,
}
+pub enum WaylandSurfaceState {
+ Xdg(WaylandXdgSurfaceState),
+ LayerShell(WaylandLayerSurfaceState),
+}
+
+impl WaylandSurfaceState {
+ fn new(
+ surface: &wl_surface::WlSurface,
+ globals: &Globals,
+ params: &WindowParams,
+ parent: Option<XdgToplevel>,
+ ) -> anyhow::Result<Self> {
+ // For layer_shell windows, create a layer surface instead of an xdg surface
+ if let WindowKind::LayerShell(options) = ¶ms.kind {
+ let Some(layer_shell) = globals.layer_shell.as_ref() else {
+ return Err(LayerShellNotSupportedError.into());
+ };
+
+ let layer_surface = layer_shell.get_layer_surface(
+ &surface,
+ None,
+ options.layer.into(),
+ options.namespace.clone(),
+ &globals.qh,
+ surface.id(),
+ );
+
+ let width = params.bounds.size.width.0;
+ let height = params.bounds.size.height.0;
+ layer_surface.set_size(width as u32, height as u32);
+
+ layer_surface.set_anchor(options.anchor.into());
+ layer_surface.set_keyboard_interactivity(options.keyboard_interactivity.into());
+
+ if let Some(margin) = options.margin {
+ layer_surface.set_margin(
+ margin.0.0 as i32,
+ margin.1.0 as i32,
+ margin.2.0 as i32,
+ margin.3.0 as i32,
+ )
+ }
+
+ if let Some(exclusive_zone) = options.exclusive_zone {
+ layer_surface.set_exclusive_zone(exclusive_zone.0 as i32);
+ }
+
+ if let Some(exclusive_edge) = options.exclusive_edge {
+ layer_surface.set_exclusive_edge(exclusive_edge.into());
+ }
+
+ return Ok(WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState {
+ layer_surface,
+ }));
+ }
+
+ // All other WindowKinds result in a regular xdg surface
+ let xdg_surface = globals
+ .wm_base
+ .get_xdg_surface(&surface, &globals.qh, surface.id());
+
+ let toplevel = xdg_surface.get_toplevel(&globals.qh, surface.id());
+ if params.kind == WindowKind::Floating {
+ toplevel.set_parent(parent.as_ref());
+ }
+
+ if let Some(size) = params.window_min_size {
+ toplevel.set_min_size(size.width.0 as i32, size.height.0 as i32);
+ }
+
+ // Attempt to set up window decorations based on the requested configuration
+ let decoration = globals
+ .decoration_manager
+ .as_ref()
+ .map(|decoration_manager| {
+ decoration_manager.get_toplevel_decoration(&toplevel, &globals.qh, surface.id())
+ });
+
+ Ok(WaylandSurfaceState::Xdg(WaylandXdgSurfaceState {
+ xdg_surface,
+ toplevel,
+ decoration,
+ }))
+ }
+}
+
+pub struct WaylandXdgSurfaceState {
+ xdg_surface: xdg_surface::XdgSurface,
+ toplevel: xdg_toplevel::XdgToplevel,
+ decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
+}
+
+pub struct WaylandLayerSurfaceState {
+ layer_surface: zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
+}
+
+impl WaylandSurfaceState {
+ fn ack_configure(&self, serial: u32) {
+ match self {
+ WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { xdg_surface, .. }) => {
+ xdg_surface.ack_configure(serial);
+ }
+ WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface, .. }) => {
+ layer_surface.ack_configure(serial);
+ }
+ }
+ }
+
+ fn decoration(&self) -> Option<&zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1> {
+ if let WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { decoration, .. }) = self {
+ decoration.as_ref()
+ } else {
+ None
+ }
+ }
+
+ fn toplevel(&self) -> Option<&xdg_toplevel::XdgToplevel> {
+ if let WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { toplevel, .. }) = self {
+ Some(toplevel)
+ } else {
+ None
+ }
+ }
+
+ fn set_geometry(&self, x: i32, y: i32, width: i32, height: i32) {
+ match self {
+ WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { xdg_surface, .. }) => {
+ xdg_surface.set_window_geometry(x, y, width, height);
+ }
+ WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface, .. }) => {
+ // cannot set window position of a layer surface
+ layer_surface.set_size(width as u32, height as u32);
+ }
+ }
+ }
+
+ fn destroy(&mut self) {
+ match self {
+ WaylandSurfaceState::Xdg(WaylandXdgSurfaceState {
+ xdg_surface,
+ toplevel,
+ decoration: _decoration,
+ }) => {
+ // The role object (toplevel) must always be destroyed before the xdg_surface.
+ // See https://wayland.app/protocols/xdg-shell#xdg_surface:request:destroy
+ toplevel.destroy();
+ xdg_surface.destroy();
+ }
+ WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface }) => {
+ layer_surface.destroy();
+ }
+ }
+ }
+}
+
#[derive(Clone)]
pub struct WaylandWindowStatePtr {
state: Rc<RefCell<WaylandWindowState>>,
@@ -126,9 +281,7 @@ impl WaylandWindowState {
pub(crate) fn new(
handle: AnyWindowHandle,
surface: wl_surface::WlSurface,
- xdg_surface: xdg_surface::XdgSurface,
- toplevel: xdg_toplevel::XdgToplevel,
- decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
+ surface_state: WaylandSurfaceState,
appearance: WindowAppearance,
viewport: Option<wp_viewport::WpViewport>,
client: WaylandClientStatePtr,
@@ -157,20 +310,18 @@ impl WaylandWindowState {
BladeRenderer::new(gpu_context, &raw_window, config)?
};
- if let Some(titlebar) = options.titlebar {
- if let Some(title) = titlebar.title {
- toplevel.set_title(title.to_string());
+ if let WaylandSurfaceState::Xdg(ref xdg_state) = surface_state {
+ if let Some(title) = options.titlebar.and_then(|titlebar| titlebar.title) {
+ xdg_state.toplevel.set_title(title.to_string());
}
}
Ok(Self {
- xdg_surface,
+ surface_state,
acknowledged_first_configure: false,
surface,
- decoration,
app_id: None,
blur: None,
- toplevel,
viewport,
globals,
outputs: HashMap::default(),
@@ -243,17 +394,29 @@ impl Drop for WaylandWindow {
let client = state.client.clone();
state.renderer.destroy();
- if let Some(decoration) = &state.decoration {
- decoration.destroy();
- }
+
+ // Destroy blur first, this has no dependencies.
if let Some(blur) = &state.blur {
blur.release();
}
- state.toplevel.destroy();
+
+ // Decorations must be destroyed before the xdg state.
+ // See https://wayland.app/protocols/xdg-decoration-unstable-v1#zxdg_toplevel_decoration_v1
+ if let Some(decoration) = &state.surface_state.decoration() {
+ decoration.destroy();
+ }
+
+ // Surface state might contain xdg_toplevel/xdg_surface which can be destroyed now that
+ // decorations are gone. layer_surface has no dependencies.
+ state.surface_state.destroy();
+
+ // Viewport must be destroyed before the wl_surface.
+ // See https://wayland.app/protocols/viewporter#wp_viewport
if let Some(viewport) = &state.viewport {
viewport.destroy();
}
- state.xdg_surface.destroy();
+
+ // The wl_surface itself should always be destroyed last.
state.surface.destroy();
let state_ptr = self.0.clone();
@@ -288,31 +451,12 @@ impl WaylandWindow {
parent: Option<XdgToplevel>,
) -> anyhow::Result<(Self, ObjectId)> {
let surface = globals.compositor.create_surface(&globals.qh, ());
- let xdg_surface = globals
- .wm_base
- .get_xdg_surface(&surface, &globals.qh, surface.id());
- let toplevel = xdg_surface.get_toplevel(&globals.qh, surface.id());
-
- if params.kind == WindowKind::Floating {
- toplevel.set_parent(parent.as_ref());
- }
-
- if let Some(size) = params.window_min_size {
- toplevel.set_min_size(size.width.0 as i32, size.height.0 as i32);
- }
+ let surface_state = WaylandSurfaceState::new(&surface, &globals, ¶ms, parent)?;
if let Some(fractional_scale_manager) = globals.fractional_scale_manager.as_ref() {
fractional_scale_manager.get_fractional_scale(&surface, &globals.qh, surface.id());
}
- // Attempt to set up window decorations based on the requested configuration
- let decoration = globals
- .decoration_manager
- .as_ref()
- .map(|decoration_manager| {
- decoration_manager.get_toplevel_decoration(&toplevel, &globals.qh, surface.id())
- });
-
let viewport = globals
.viewporter
.as_ref()
@@ -322,9 +466,7 @@ impl WaylandWindow {
state: Rc::new(RefCell::new(WaylandWindowState::new(
handle,
surface.clone(),
- xdg_surface,
- toplevel,
- decoration,
+ surface_state,
appearance,
viewport,
client,
@@ -351,8 +493,8 @@ impl WaylandWindowStatePtr {
self.state.borrow().surface.clone()
}
- pub fn toplevel(&self) -> xdg_toplevel::XdgToplevel {
- self.state.borrow().toplevel.clone()
+ pub fn toplevel(&self) -> Option<xdg_toplevel::XdgToplevel> {
+ self.state.borrow().surface_state.toplevel().cloned()
}
pub fn ptr_eq(&self, other: &Self) -> bool {
@@ -419,7 +561,7 @@ impl WaylandWindowStatePtr {
}
}
let mut state = self.state.borrow_mut();
- state.xdg_surface.ack_configure(serial);
+ state.surface_state.ack_configure(serial);
let window_geometry = inset_by_tiling(
state.bounds.map_origin(|_| px(0.0)),
@@ -429,7 +571,7 @@ impl WaylandWindowStatePtr {
.map(|v| v.0 as i32)
.map_size(|v| if v <= 0 { 1 } else { v });
- state.xdg_surface.set_window_geometry(
+ state.surface_state.set_geometry(
window_geometry.origin.x,
window_geometry.origin.y,
window_geometry.size.width,
@@ -588,6 +730,42 @@ impl WaylandWindowStatePtr {
}
}
+ pub fn handle_layersurface_event(&self, event: zwlr_layer_surface_v1::Event) -> bool {
+ match event {
+ zwlr_layer_surface_v1::Event::Configure {
+ width,
+ height,
+ serial,
+ } => {
+ let mut size = if width == 0 || height == 0 {
+ None
+ } else {
+ Some(size(px(width as f32), px(height as f32)))
+ };
+
+ let mut state = self.state.borrow_mut();
+ state.in_progress_configure = Some(InProgressConfigure {
+ size,
+ fullscreen: false,
+ maximized: false,
+ resizing: false,
+ tiling: Tiling::default(),
+ });
+ drop(state);
+
+ // just do the same thing we'd do as an xdg_surface
+ self.handle_xdg_surface_event(xdg_surface::Event::Configure { serial });
+
+ false
+ }
+ zwlr_layer_surface_v1::Event::Closed => {
+ // unlike xdg, we don't have a choice here: the surface is closing.
+ true
+ }
+ _ => false,
+ }
+ }
+
#[allow(clippy::mutable_key_type)]
pub fn handle_surface_event(
&self,
@@ -849,7 +1027,7 @@ impl PlatformWindow for WaylandWindow {
let state_ptr = self.0.clone();
let dp_size = size.to_device_pixels(self.scale_factor());
- state.xdg_surface.set_window_geometry(
+ state.surface_state.set_geometry(
state.bounds.origin.x.0 as i32,
state.bounds.origin.y.0 as i32,
dp_size.width.0,
@@ -943,12 +1121,16 @@ impl PlatformWindow for WaylandWindow {
}
fn set_title(&mut self, title: &str) {
- self.borrow().toplevel.set_title(title.to_string());
+ if let Some(toplevel) = self.borrow().surface_state.toplevel() {
+ toplevel.set_title(title.to_string());
+ }
}
fn set_app_id(&mut self, app_id: &str) {
let mut state = self.borrow_mut();
- state.toplevel.set_app_id(app_id.to_owned());
+ if let Some(toplevel) = state.surface_state.toplevel() {
+ toplevel.set_app_id(app_id.to_owned());
+ }
state.app_id = Some(app_id.to_owned());
}
@@ -959,24 +1141,30 @@ impl PlatformWindow for WaylandWindow {
}
fn minimize(&self) {
- self.borrow().toplevel.set_minimized();
+ if let Some(toplevel) = self.borrow().surface_state.toplevel() {
+ toplevel.set_minimized();
+ }
}
fn zoom(&self) {
let state = self.borrow();
- if !state.maximized {
- state.toplevel.set_maximized();
- } else {
- state.toplevel.unset_maximized();
+ if let Some(toplevel) = state.surface_state.toplevel() {
+ if !state.maximized {
+ toplevel.set_maximized();
+ } else {
+ toplevel.unset_maximized();
+ }
}
}
fn toggle_fullscreen(&self) {
- let mut state = self.borrow_mut();
- if !state.fullscreen {
- state.toplevel.set_fullscreen(None);
- } else {
- state.toplevel.unset_fullscreen();
+ let mut state = self.borrow();
+ if let Some(toplevel) = state.surface_state.toplevel() {
+ if !state.fullscreen {
+ toplevel.set_fullscreen(None);
+ } else {
+ toplevel.unset_fullscreen();
+ }
}
}
@@ -1041,27 +1229,33 @@ impl PlatformWindow for WaylandWindow {
fn show_window_menu(&self, position: Point<Pixels>) {
let state = self.borrow();
let serial = state.client.get_serial(SerialKind::MousePress);
- state.toplevel.show_window_menu(
- &state.globals.seat,
- serial,
- position.x.0 as i32,
- position.y.0 as i32,
- );
+ if let Some(toplevel) = state.surface_state.toplevel() {
+ toplevel.show_window_menu(
+ &state.globals.seat,
+ serial,
+ position.x.0 as i32,
+ position.y.0 as i32,
+ );
+ }
}
fn start_window_move(&self) {
let state = self.borrow();
let serial = state.client.get_serial(SerialKind::MousePress);
- state.toplevel._move(&state.globals.seat, serial);
+ if let Some(toplevel) = state.surface_state.toplevel() {
+ toplevel._move(&state.globals.seat, serial);
+ }
}
fn start_window_resize(&self, edge: crate::ResizeEdge) {
let state = self.borrow();
- state.toplevel.resize(
- &state.globals.seat,
- state.client.get_serial(SerialKind::MousePress),
- edge.to_xdg(),
- )
+ if let Some(toplevel) = state.surface_state.toplevel() {
+ toplevel.resize(
+ &state.globals.seat,
+ state.client.get_serial(SerialKind::MousePress),
+ edge.to_xdg(),
+ )
+ }
}
fn window_decorations(&self) -> Decorations {
@@ -1077,7 +1271,7 @@ impl PlatformWindow for WaylandWindow {
fn request_decorations(&self, decorations: WindowDecorations) {
let mut state = self.borrow_mut();
state.decorations = decorations;
- if let Some(decoration) = state.decoration.as_ref() {
+ if let Some(decoration) = state.surface_state.decoration() {
decoration.set_mode(decorations.to_xdg());
update_window(state);
}