gpui: Add support for floating windows (#39702)
Closes #ISSUE This allows new windows like the Rules library or the Settings UI window to appear floating on window managers like hyprland: https://github.com/user-attachments/assets/628db7f9-4459-4601-85f1-789923831182 Left is with `WindowKind::Floating` and right is with `WindowKind::Normal` Release Notes: - Added support for floating windows on x11 and wayland
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -695,6 +695,8 @@ 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 (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());
|
||||
|
||||
|
||||
@@ -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<XdgToplevel>,
|
||||
) -> 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)
|
||||
}
|
||||
|
||||
@@ -1448,6 +1448,10 @@ impl LinuxClient for X11Client {
|
||||
params: WindowParams,
|
||||
) -> anyhow::Result<Box<dyn PlatformWindow>> {
|
||||
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",
|
||||
|
||||
@@ -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<xproto::Window>,
|
||||
) -> anyhow::Result<Self> {
|
||||
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<xproto::Window>,
|
||||
) -> anyhow::Result<Self> {
|
||||
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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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)),
|
||||
|
||||
Reference in New Issue
Block a user