diff --git a/Cargo.lock b/Cargo.lock index 5e35202e90..7f09342879 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7354,8 +7354,9 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-cursor", - "wayland-protocols", + "wayland-protocols 0.31.2", "wayland-protocols-plasma", + "wayland-protocols-wlr", "windows 0.61.1", "windows-core 0.61.0", "windows-numerics", @@ -18369,9 +18370,9 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", "downcast-rs", @@ -18383,9 +18384,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ "bitflags 2.9.0", "rustix 0.38.44", @@ -18416,6 +18417,18 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-protocols" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +dependencies = [ + "bitflags 2.9.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + [[package]] name = "wayland-protocols-plasma" version = "0.2.0" @@ -18425,7 +18438,20 @@ dependencies = [ "bitflags 2.9.0", "wayland-backend", "wayland-client", - "wayland-protocols", + "wayland-protocols 0.31.2", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +dependencies = [ + "bitflags 2.9.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.8", "wayland-scanner", ] diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 680111a6ce..4023ddf2dc 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -47,6 +47,7 @@ wayland = [ "wayland-cursor", "wayland-protocols", "wayland-protocols-plasma", + "wayland-protocols-wlr", "filedescriptor", "xkbcommon", "open", @@ -193,6 +194,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.8", features = [ + "client" +], optional = true} # X11 as-raw-xcb-connection = { version = "1", optional = true } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 1e72d23868..febf294e48 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -1216,6 +1216,10 @@ pub enum WindowKind { /// A window that appears above all other windows, usually used for alerts or popups /// use sparingly! PopUp, + /// An overlay such as a notification window, a launcher, ... + /// + /// Only supported on wayland + Overlay, } /// The appearance of the window, as defined by the operating system. diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 72e4477ecf..33b22e7ce5 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -61,6 +61,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}; @@ -114,6 +115,7 @@ pub struct Globals { pub fractional_scale_manager: Option, pub decoration_manager: Option, + pub layer_shell: Option, pub blur_manager: Option, pub text_input_manager: Option, pub executor: ForegroundExecutor, @@ -151,6 +153,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..=1, ()).ok(), blur_manager: globals.bind(&qh, 1..=1, ()).ok(), text_input_manager: globals.bind(&qh, 1..=1, ()).ok(), executor, @@ -929,6 +932,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); @@ -1074,6 +1078,31 @@ impl Dispatch for WaylandClientStatePtr { } } +impl Dispatch for WaylandClientStatePtr { + fn event( + this: &mut Self, + _: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + event: ::Event, + surface_id: &ObjectId, + _: &Connection, + _: &QueueHandle, + ) { + 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 for WaylandClientStatePtr { fn event( _: &mut Self, diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 2b2207e22c..33c908d1b2 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -1,3 +1,6 @@ +use blade_graphics as gpu; +use collections::HashMap; +use futures::channel::oneshot::Receiver; use std::{ cell::{Ref, RefCell, RefMut}, ffi::c_void, @@ -6,9 +9,14 @@ use std::{ sync::Arc, }; -use blade_graphics as gpu; -use collections::HashMap; -use futures::channel::oneshot::Receiver; +use crate::{ + Capslock, + platform::{ + PlatformAtlas, PlatformInputHandler, PlatformWindow, + blade::{BladeContext, BladeRenderer, BladeSurfaceConfig}, + linux::wayland::{display::WaylandDisplay, serial::SerialKind}, + }, +}; use raw_window_handle as rwh; use wayland_backend::client::ObjectId; @@ -20,6 +28,8 @@ use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1 use wayland_protocols::xdg::shell::client::xdg_surface; use wayland_protocols::xdg::shell::client::xdg_toplevel::{self}; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur; +use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::Layer; +use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1; use crate::scene::Scene; use crate::{ @@ -27,15 +37,7 @@ use crate::{ PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Size, Tiling, WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowControls, WindowDecorations, - WindowParams, px, size, -}; -use crate::{ - Capslock, - platform::{ - PlatformAtlas, PlatformInputHandler, PlatformWindow, - blade::{BladeContext, BladeRenderer, BladeSurfaceConfig}, - linux::wayland::{display::WaylandDisplay, serial::SerialKind}, - }, + WindowKind, WindowParams, px, size, }; #[derive(Default)] @@ -81,14 +83,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, app_id: Option, appearance: WindowAppearance, blur: Option, - toplevel: xdg_toplevel::XdgToplevel, viewport: Option, outputs: HashMap, display: Option<(ObjectId, Output)>, @@ -114,6 +114,78 @@ pub struct WaylandWindowState { client_inset: Option, } +pub enum WaylandSurfaceState { + Xdg(WaylandXdgSurfaceState), + LayerShell(WaylandLayerSurfaceState), +} + +pub struct WaylandXdgSurfaceState { + xdg_surface: xdg_surface::XdgSurface, + toplevel: xdg_toplevel::XdgToplevel, + decoration: Option, +} + +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, + }) => { + toplevel.destroy(); + xdg_surface.destroy(); + } + WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_surface }) => { + layer_surface.destroy(); + } + } + } +} + #[derive(Clone)] pub struct WaylandWindowStatePtr { state: Rc>, @@ -124,9 +196,7 @@ impl WaylandWindowState { pub(crate) fn new( handle: AnyWindowHandle, surface: wl_surface::WlSurface, - xdg_surface: xdg_surface::XdgSurface, - toplevel: xdg_toplevel::XdgToplevel, - decoration: Option, + surface_state: WaylandSurfaceState, appearance: WindowAppearance, viewport: Option, client: WaylandClientStatePtr, @@ -156,13 +226,11 @@ impl WaylandWindowState { }; Ok(Self { - xdg_surface, + surface_state, acknowledged_first_configure: false, surface, - decoration, app_id: None, blur: None, - toplevel, viewport, globals, outputs: HashMap::default(), @@ -235,17 +303,16 @@ impl Drop for WaylandWindow { let client = state.client.clone(); state.renderer.destroy(); - if let Some(decoration) = &state.decoration { + if let Some(decoration) = &state.surface_state.decoration() { decoration.destroy(); } if let Some(blur) = &state.blur { blur.release(); } - state.toplevel.destroy(); + state.surface_state.destroy(); if let Some(viewport) = &state.viewport { viewport.destroy(); } - state.xdg_surface.destroy(); state.surface.destroy(); let state_ptr = self.0.clone(); @@ -279,27 +346,65 @@ impl WaylandWindow { appearance: WindowAppearance, ) -> 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 let Some(size) = params.window_min_size { - toplevel.set_min_size(size.width.0 as i32, size.height.0 as i32); - } + let surface_state = match (params.kind, globals.layer_shell.as_ref()) { + // Matching on layer_shell here means that if kind is Overlay, but the compositor doesn't support layer_shell, + // we end up defaulting to xdg_surface anyway + (WindowKind::Overlay, Some(layer_shell)) => { + let layer_surface = layer_shell.get_layer_surface( + &surface, + None, + Layer::Overlay, + "".to_string(), + &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_keyboard_interactivity( + zwlr_layer_surface_v1::KeyboardInteractivity::OnDemand, + ); + + WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState { layer_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 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(), + ) + }); + + WaylandSurfaceState::Xdg(WaylandXdgSurfaceState { + xdg_surface, + toplevel, + decoration, + }) + } + }; 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() @@ -309,9 +414,7 @@ impl WaylandWindow { state: Rc::new(RefCell::new(WaylandWindowState::new( handle, surface.clone(), - xdg_surface, - toplevel, - decoration, + surface_state, appearance, viewport, client, @@ -403,7 +506,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)), @@ -413,7 +516,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, @@ -578,6 +681,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, @@ -840,7 +979,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, @@ -934,12 +1073,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) = self.borrow().surface_state.toplevel() { + toplevel.set_app_id(app_id.to_owned()); + } state.app_id = Some(app_id.to_owned()); } @@ -950,24 +1093,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(); + } } } @@ -1032,27 +1181,33 @@ impl PlatformWindow for WaylandWindow { fn show_window_menu(&self, position: Point) { 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 { @@ -1068,7 +1223,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); } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index aedf131909..f01d33147b 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -559,7 +559,7 @@ impl MacWindow { } let native_window: id = match kind { - WindowKind::Normal => msg_send![WINDOW_CLASS, alloc], + WindowKind::Normal | WindowKind::Overlay => msg_send![WINDOW_CLASS, alloc], WindowKind::PopUp => { style_mask |= NSWindowStyleMaskNonactivatingPanel; msg_send![PANEL_CLASS, alloc] @@ -711,7 +711,7 @@ impl MacWindow { native_window.makeFirstResponder_(native_view); match kind { - WindowKind::Normal => { + WindowKind::Normal | WindowKind::Overlay => { native_window.setLevel_(NSNormalWindowLevel); native_window.setAcceptsMouseMovedEvents_(YES); }