98 lines
3.6 KiB
Rust
98 lines
3.6 KiB
Rust
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;
|