diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 173dbe2365088f9f736cf4bf845f44446e4cffb2..555a75879795d85bc20698b5a4c7cf76555f11ac 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -1268,6 +1268,9 @@ pub enum WindowKind { /// A window that appears above all other windows, usually used for alerts or popups /// use sparingly! PopUp, + + /// A floating window that appears on top of its parent window + Floating, } /// 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 f8672971ec51415ba93708f2b06be49678aa3738..1ebdda3a266af0f9e8d82dabd5b36372e0972438 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -695,6 +695,8 @@ impl LinuxClient for WaylandClient { ) -> anyhow::Result> { let mut state = self.0.borrow_mut(); + let parent = state.keyboard_focused_window.as_ref().map(|w| w.toplevel()); + let (window, surface_id) = WaylandWindow::new( handle, state.globals.clone(), @@ -702,6 +704,7 @@ impl LinuxClient for WaylandClient { WaylandClientStatePtr(Rc::downgrade(&self.0)), params, state.common.appearance, + parent, )?; state.windows.insert(surface_id, window.0.clone()); diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 76dd89c940c615d726af1cf5922be226d91dfd41..aa3b7141be77dbdc73893783620523c6c8d68e4e 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -14,14 +14,16 @@ use raw_window_handle as rwh; use wayland_backend::client::ObjectId; use wayland_client::WEnum; use wayland_client::{Proxy, protocol::wl_surface}; -use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1; use wayland_protocols::wp::viewporter::client::wp_viewport; 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::{ + wp::fractional_scale::v1::client::wp_fractional_scale_v1, + xdg::shell::client::xdg_toplevel::XdgToplevel, +}; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur; -use crate::scene::Scene; use crate::{ AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels, PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions, @@ -36,6 +38,7 @@ use crate::{ linux::wayland::{display::WaylandDisplay, serial::SerialKind}, }, }; +use crate::{WindowKind, scene::Scene}; #[derive(Default)] pub(crate) struct Callbacks { @@ -276,6 +279,7 @@ impl WaylandWindow { client: WaylandClientStatePtr, params: WindowParams, appearance: WindowAppearance, + parent: Option, ) -> anyhow::Result<(Self, ObjectId)> { let surface = globals.compositor.create_surface(&globals.qh, ()); let xdg_surface = globals @@ -283,6 +287,10 @@ impl WaylandWindow { .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); } @@ -337,6 +345,10 @@ impl WaylandWindowStatePtr { self.state.borrow().surface.clone() } + pub fn toplevel(&self) -> xdg_toplevel::XdgToplevel { + self.state.borrow().toplevel.clone() + } + pub fn ptr_eq(&self, other: &Self) -> bool { Rc::ptr_eq(&self.state, &other.state) } diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 497e3ff709e8cd2f853d50dc13e4f96f907e4008..fa9d0181c095819823553da9e7f6be27598aea78 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -1448,6 +1448,10 @@ impl LinuxClient for X11Client { params: WindowParams, ) -> anyhow::Result> { let mut state = self.0.borrow_mut(); + let parent_window = state + .keyboard_focused_window + .and_then(|focused_window| state.windows.get(&focused_window)) + .map(|window| window.window.x_window); let x_window = state .xcb_connection .generate_id() @@ -1466,6 +1470,7 @@ impl LinuxClient for X11Client { &state.atoms, state.scale_factor, state.common.appearance, + parent_window, )?; check_reply( || "Failed to set XdndAware property", diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 001b853afee1d1a2164f330f0bfdf81b77d8fc02..fe197a670177689ce776b6b55d439483c43921e0 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -57,6 +57,7 @@ x11rb::atom_manager! { WM_PROTOCOLS, WM_DELETE_WINDOW, WM_CHANGE_STATE, + WM_TRANSIENT_FOR, _NET_WM_PID, _NET_WM_NAME, _NET_WM_STATE, @@ -72,6 +73,7 @@ x11rb::atom_manager! { _NET_WM_MOVERESIZE, _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_NOTIFICATION, + _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_SYNC, _NET_SUPPORTED, _MOTIF_WM_HINTS, @@ -392,6 +394,7 @@ impl X11WindowState { atoms: &XcbAtoms, scale_factor: f32, appearance: WindowAppearance, + parent_window: Option, ) -> anyhow::Result { let x_screen_index = params .display_id @@ -529,6 +532,7 @@ impl X11WindowState { ), )?; } + if params.kind == WindowKind::PopUp { check_reply( || "X11 ChangeProperty32 setting window type for pop-up failed.", @@ -542,6 +546,38 @@ impl X11WindowState { )?; } + if params.kind == WindowKind::Floating { + if let Some(parent_window) = parent_window { + // WM_TRANSIENT_FOR hint indicating the main application window. For floating windows, we set + // a parent window (WM_TRANSIENT_FOR) such that the window manager knows where to + // place the floating window in relation to the main window. + // https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html + check_reply( + || "X11 ChangeProperty32 setting WM_TRANSIENT_FOR for floating window failed.", + xcb.change_property32( + xproto::PropMode::REPLACE, + x_window, + atoms.WM_TRANSIENT_FOR, + xproto::AtomEnum::WINDOW, + &[parent_window], + ), + )?; + } + + // _NET_WM_WINDOW_TYPE_DIALOG indicates that this is a dialog (floating) window + // https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html + check_reply( + || "X11 ChangeProperty32 setting window type for floating window failed.", + xcb.change_property32( + xproto::PropMode::REPLACE, + x_window, + atoms._NET_WM_WINDOW_TYPE, + xproto::AtomEnum::ATOM, + &[atoms._NET_WM_WINDOW_TYPE_DIALOG], + ), + )?; + } + check_reply( || "X11 ChangeProperty32 setting protocols failed.", xcb.change_property32( @@ -737,6 +773,7 @@ impl X11Window { atoms: &XcbAtoms, scale_factor: f32, appearance: WindowAppearance, + parent_window: Option, ) -> anyhow::Result { let ptr = X11WindowStatePtr { state: Rc::new(RefCell::new(X11WindowState::new( @@ -752,6 +789,7 @@ impl X11Window { atoms, scale_factor, appearance, + parent_window, )?)), callbacks: Rc::new(RefCell::new(Callbacks::default())), xcb: xcb.clone(), diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 899ac4498bec4728b214e2576b98bf7abaeafca4..95efffa3e77cdbeebf53acd47dd1aa9b33cb24ab 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -618,7 +618,7 @@ impl MacWindow { } let native_window: id = match kind { - WindowKind::Normal => msg_send![WINDOW_CLASS, alloc], + WindowKind::Normal | WindowKind::Floating => msg_send![WINDOW_CLASS, alloc], WindowKind::PopUp => { style_mask |= NSWindowStyleMaskNonactivatingPanel; msg_send![PANEL_CLASS, alloc] @@ -776,7 +776,7 @@ impl MacWindow { native_window.makeFirstResponder_(native_view); match kind { - WindowKind::Normal => { + WindowKind::Normal | WindowKind::Floating => { native_window.setLevel_(NSNormalWindowLevel); native_window.setAcceptsMouseMovedEvents_(YES); diff --git a/crates/rules_library/src/rules_library.rs b/crates/rules_library/src/rules_library.rs index 8357eb13857a642c56894263f676fca2bff30100..abb0b4e3a1a84cf7ecf40939b33aee19b874bcdf 100644 --- a/crates/rules_library/src/rules_library.rs +++ b/crates/rules_library/src/rules_library.rs @@ -136,6 +136,7 @@ pub fn open_rules_library( window_background: cx.theme().window_background_appearance(), window_decorations: Some(window_decorations), window_min_size: Some(size(px(800.), px(600.))), // 4:3 Aspect Ratio + kind: gpui::WindowKind::Floating, ..Default::default() }, |window, cx| { diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 5216e8a3adc696e265c0a0f14da881445f2f385a..bec3d1ed93a44cdaa3ff2d7e730d1710edcd2d29 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -484,7 +484,7 @@ pub fn open_settings_editor( }), focus: true, show: true, - kind: gpui::WindowKind::Normal, + kind: gpui::WindowKind::Floating, window_background: cx.theme().window_background_appearance(), window_min_size: Some(size(px(900.), px(750.))), // 4:3 Aspect Ratio window_bounds: Some(WindowBounds::centered(size(px(900.), px(750.)), cx)),