T21: on X11 the window names its pane in _KDE_NET_WM_BLUR_BEHIND_REGION and drops it when Blurred goes; Ely's new ResizeBorder::glass leaves a client-drawn window's pane to the content, without ground or shadow, so a Linux window shows the desktop at all; T20's region and this property cover the pane alone, worked out each frame and sent only on a change; the Ely pin moves to 5dc4dd1; the X11 test runs its own Xvfb
CI / platforms (windows-latest) (push) Failing after 8s
CI / macos (push) Failing after 8m43s
CI / platforms (ubuntu-24.04) (push) Canceled after 0s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-15 01:09:47 -04:00
1 parent 8eeeedf12d
commit b5e4967071
11 files changed
+339 -45

No files matched your search

+34 -24
View File
@@ -17,15 +17,15 @@ use wayland_protocols::ext::background_effect::v1::client::{
ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1,
};
/// Wider and taller than any window: the compositor clips the region to the surface.
const ALL: i32 = 1 << 20;
/// The compositor's blur behind a window's surface, through `ext_background_effect_v1`.
pub struct Effect {
// Never dropped: gpui may close the display first, and a drop touches it.
connection: ManuallyDrop<Connection>,
queue: QueueHandle<Offers>,
compositor: WlCompositor,
effect: ExtBackgroundEffectSurfaceV1,
region: WlRegion,
/// The rect the compositor blurs behind, as last sent.
blurs: Option<[i32; 4]>,
}
/// What the compositor offers, as its events say.
@@ -35,8 +35,8 @@ struct Offers {
}
impl Effect {
/// The effect on `window`'s surface, blurring when `blurred`; none off Wayland or where the compositor lacks it.
pub fn on(window: &Window, blurred: bool) -> Option<Self> {
/// The effect on `window`'s surface, blurring nothing yet; none off Wayland or where the compositor lacks it.
pub fn on(window: &Window) -> Option<Self> {
let handles = (
HasWindowHandle::window_handle(window),
window.display_handle(),
@@ -53,12 +53,7 @@ impl Effect {
};
// SAFETY: gpui's live display, and the window's live surface on it.
match unsafe { Self::new(display.display, surface.surface) } {
Ok(effect) => {
if blurred && let Some(effect) = &effect {
effect.blur(true);
}
effect
}
Ok(effect) => effect,
Err(error) => {
log::error!("blur: ext_background_effect_v1 failed: {error:#}");
None
@@ -109,28 +104,43 @@ impl Effect {
let surface =
WlSurface::from_id(&connection, id).context("blur: the window's surface is gone")?;
let effect = manager.get_background_effect(&surface, &qh, ());
let region = compositor.create_region(&qh, ());
region.add(0, 0, ALL, ALL);
log::info!(
"blur: ext_background_effect_v1 on the window's surface; the compositor blurs: {}, org_kde_kwin_blur too: {kde}",
offers.blur
);
Ok(Some(Self {
connection: ManuallyDrop::new(connection),
queue: qh,
compositor,
effect,
region,
blurs: None,
}))
}
/// Blurs behind the whole surface, or nothing, from the surface's next commit.
pub fn blur(&self, on: bool) {
self.effect.set_blur_region(on.then_some(&self.region));
match self.connection.flush() {
Ok(()) if on => log::info!("blur: ext_background_effect_v1 blurs behind the window"),
Ok(()) => log::info!("blur: ext_background_effect_v1 blurs nothing"),
Err(error) => {
log::error!("blur: ext_background_effect_v1 did not reach the compositor: {error}")
}
/// Blurs behind `rect`, in surface pixels, or nothing, from the surface's next commit; sends only a change.
pub fn blur(&mut self, rect: Option<[i32; 4]>) {
if rect == self.blurs {
return;
}
let turned = rect.is_some() != self.blurs.is_some();
self.blurs = rect;
let region = rect.map(|[x, y, width, height]| {
let region = self.compositor.create_region(&self.queue, ());
region.add(x, y, width, height);
region
});
self.effect.set_blur_region(region.as_ref());
// The effect copies the region.
if let Some(region) = region {
region.destroy();
}
let what = rect.map_or("nothing".into(), |rect| format!("{rect:?}"));
if let Err(error) = self.connection.flush() {
log::error!("blur: ext_background_effect_v1 did not reach the compositor: {error}");
} else if turned {
log::info!("blur: ext_background_effect_v1 blurs {what}");
} else {
log::debug!("blur: ext_background_effect_v1 blurs {what}");
}
}
}
+8 -6
View File
@@ -233,17 +233,19 @@ fn heard(offered: bool, ask: impl FnOnce(Option<Effect>)) -> (Vec<String>, u32)
#[test]
fn the_window_s_surface_blurs_while_asked() {
let (notes, surface) = heard(true, |effect| {
let effect = effect.expect("the compositor offers the protocol");
effect.blur(true);
effect.blur(false);
let mut effect = effect.expect("the compositor offers the protocol");
effect.blur(Some([10, 10, 300, 200]));
effect.blur(Some([10, 10, 300, 200]));
effect.blur(Some([0, 0, 320, 220]));
effect.blur(None);
});
let all = format!("blur [[0, 0, {ALL}, {ALL}]]");
let expected = [
format!("effect on surface {surface}"),
all,
"blur [[10, 10, 300, 200]]".into(),
"blur [[0, 0, 320, 220]]".into(),
"blur nothing".into(),
];
assert_eq!(notes, expected);
assert_eq!(notes, expected, "a repeat sends nothing");
assert!(crate::testlog::logged(
"blur: ext_background_effect_v1 on the window's surface; the compositor blurs: true, org_kde_kwin_blur too: false"
));
+97
View File
@@ -0,0 +1,97 @@
use anyhow::Context as _;
use gpui::Window;
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use x11rb::{
protocol::xproto::{Atom, AtomEnum, ConnectionExt as _, PropMode},
rust_connection::RustConnection,
wrapper::ConnectionExt as _,
};
/// KWin blurs behind the rects a window names here, in its own pixels.
const BLUR_BEHIND: &str = "_KDE_NET_WM_BLUR_BEHIND_REGION";
/// The compositor's blur behind an X11 window, through `_KDE_NET_WM_BLUR_BEHIND_REGION`.
pub struct Effect {
connection: RustConnection,
window: u32,
atom: Atom,
/// The rect the property names, as last set.
blurs: Option<[i32; 4]>,
}
impl Effect {
/// The effect on `window`'s X11 window, blurring nothing yet; none off X11.
pub fn on(window: &Window) -> Option<Self> {
let Ok(handle) = HasWindowHandle::window_handle(window) else {
log::error!("blur: gpui gave no handle to the window");
return None;
};
let RawWindowHandle::Xcb(handle) = handle.as_raw() else {
log::info!("blur: no X11 window, so no {BLUR_BEHIND}");
return None;
};
// gpui opens $DISPLAY too; a property belongs to the window, whoever sets it.
match Self::new(None, handle.window.get()) {
Ok(effect) => Some(effect),
Err(error) => {
log::error!("blur: {BLUR_BEHIND} failed: {error:#}");
None
}
}
}
/// An effect on `window` of the X server `display` names, or `$DISPLAY`'s.
fn new(display: Option<&str>, window: u32) -> anyhow::Result<Self> {
let (connection, _) = x11rb::connect(display).context("blur: no X server")?;
let atom = (connection.intern_atom(false, BLUR_BEHIND.as_bytes()))
.context("blur: the atom did not go out")?
.reply()
.context("blur: the atom")?
.atom;
log::info!(
"blur: {BLUR_BEHIND} on X11 window {window}; KWin blurs behind it, other compositors pass it by"
);
Ok(Self {
connection,
window,
atom,
blurs: None,
})
}
/// Names `rect`, in the window's pixels, or removes the property; the X server has it on return; sends only a change.
pub fn blur(&mut self, rect: Option<[i32; 4]>) {
if rect == self.blurs {
return;
}
let turned = rect.is_some() != self.blurs.is_some();
self.blurs = rect;
let asked = match rect {
Some(rect) => {
let cardinals = rect.map(|side| side.max(0) as u32);
(self.connection)
.change_property32(
PropMode::REPLACE,
self.window,
self.atom,
AtomEnum::CARDINAL,
&cardinals,
)
.map(|cookie| cookie.check())
}
None => (self.connection)
.delete_property(self.window, self.atom)
.map(|cookie| cookie.check()),
};
let what = rect.map_or("nothing".into(), |rect| format!("{rect:?}"));
match asked {
Ok(Ok(())) if turned => log::info!("blur: {BLUR_BEHIND} names {what}"),
Ok(Ok(())) => log::debug!("blur: {BLUR_BEHIND} names {what}"),
Ok(Err(error)) => log::error!("blur: the X server refused {BLUR_BEHIND}: {error}"),
Err(error) => log::error!("blur: {BLUR_BEHIND} did not reach the X server: {error}"),
}
}
}
#[cfg(test)]
mod tests;
+91
View File
@@ -0,0 +1,91 @@
use std::{
io::{BufRead, BufReader},
process::{Child, Command, Stdio},
};
use x11rb::{
COPY_DEPTH_FROM_PARENT,
connection::Connection as _,
protocol::xproto::{CreateWindowAux, WindowClass},
};
use super::*;
/// An X server of the test's own, gone with it.
struct Xvfb {
child: Child,
display: String,
}
impl Xvfb {
fn start() -> Self {
let mut child = Command::new("Xvfb")
.args(["-displayfd", "1", "-nolisten", "tcp"])
.stdout(Stdio::piped())
.spawn()
.expect("Xvfb starts: the X11 tests need it");
let mut number = String::new();
let stdout = child.stdout.take().expect("Xvfb's stdout");
BufReader::new(stdout)
.read_line(&mut number)
.expect("Xvfb names its display");
let display = format!(":{}", number.trim());
Self { child, display }
}
}
impl Drop for Xvfb {
fn drop(&mut self) {
if let Err(error) = self.child.kill().and_then(|()| self.child.wait().map(drop)) {
eprintln!("Xvfb {} did not stop: {error}", self.display);
}
}
}
#[test]
fn the_window_names_its_pane_while_blurred() {
crate::testlog::init();
let xvfb = Xvfb::start();
let (connection, screen) = x11rb::connect(Some(&xvfb.display)).expect("the test connects");
let root = connection.setup().roots[screen].root;
let window = connection.generate_id().expect("a window id");
let aux = CreateWindowAux::new();
let class = WindowClass::INPUT_OUTPUT;
(connection.create_window(
COPY_DEPTH_FROM_PARENT,
window,
root,
0,
0,
640,
400,
0,
class,
0,
&aux,
))
.expect("the request")
.check()
.expect("a window");
let mut effect = Effect::new(Some(&xvfb.display), window).expect("an effect");
let atom = effect.atom;
let worn = || {
let property = (connection.get_property(false, window, atom, AtomEnum::ANY, 0, 16))
.expect("the request")
.reply()
.expect("the property");
let cardinal = property.type_ == u32::from(AtomEnum::CARDINAL);
let values = property.value32().map(Iterator::collect::<Vec<_>>);
(property.type_ != 0).then_some((cardinal, values))
};
assert_eq!(worn(), None, "none at first");
effect.blur(Some([20, 20, 1200, 760]));
assert_eq!(worn(), Some((true, Some(vec![20, 20, 1200, 760]))));
effect.blur(Some([0, 0, 1240, 800]));
assert_eq!(worn(), Some((true, Some(vec![0, 0, 1240, 800]))));
effect.blur(None);
assert_eq!(worn(), None, "removed");
assert!(crate::testlog::logged(&format!(
"blur: {BLUR_BEHIND} on X11 window {window}; KWin blurs behind it, other compositors pass it by"
)));
}