Files
ELYTerminal/src/blur/windows.rs
T

188 lines
6.8 KiB
Rust

//! On Windows 10 a blurred window's acrylic clears while it moves or sizes: that DWM samples the blur again at each step, and the window trails the pointer by seconds.
use std::sync::atomic::{AtomicIsize, Ordering};
use anyhow::{Context as _, bail};
use gpui::Window;
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use windows_sys::{
Wdk::System::SystemServices::RtlGetVersion,
Win32::{
Foundation::{HWND, LPARAM, LRESULT, WPARAM},
System::{
LibraryLoader::{GetModuleHandleW, GetProcAddress},
SystemInformation::OSVERSIONINFOW,
},
UI::WindowsAndMessaging::{
CallWindowProcW, GWLP_WNDPROC, GetPropW, RemovePropW, SWP_FRAMECHANGED, SWP_NOACTIVATE,
SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SetPropW, SetWindowLongPtrW, SetWindowPos,
WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_NCDESTROY, WNDPROC,
},
},
core::{BOOL, PCWSTR},
s, w,
};
/// Windows 11's first build, whose DWM moves an acrylic window as fast as a clear one.
const ELEVEN: u32 = 22000;
/// gpui's window procedure, which the main windows' pass every message on to.
static GPUI: AtomicIsize = AtomicIsize::new(0);
/// A window's property while it is blurred, which its moves read.
const BLURRED: PCWSTR = w!("ElyTerminalBlurred");
/// A window's property while it moves or sizes, which a change of its blur reads.
const MOVING: PCWSTR = w!("ElyTerminalMoving");
/// gpui's accents: acrylic for Blurred, a clear gradient for Transparent.
const ACRYLIC: u32 = 4;
const CLEAR: u32 = 2;
/// A main window on Windows 10, whose moves clear its acrylic.
pub struct Moves {
window: HWND,
}
impl Moves {
/// Takes the window's moves on Windows 10; none on 11 and later.
pub fn on(window: &Window) -> anyhow::Result<Option<Self>> {
let build = build()?;
if build >= ELEVEN {
log::info!("blur: Windows build {build} moves acrylic as it is");
return Ok(None);
}
let window = native(window)?;
let gpui = unsafe { SetWindowLongPtrW(window, GWLP_WNDPROC, moving as *const () as isize) };
anyhow::ensure!(gpui != 0, "blur: the window's procedure does not read");
let kept = GPUI.swap(gpui, Ordering::Relaxed);
anyhow::ensure!(
kept == 0 || kept == gpui,
"blur: gpui's windows have two procedures"
);
log::info!("blur: on Windows build {build} acrylic clears while the window moves");
Ok(Some(Self { window }))
}
/// Notes whether the window is blurred, as its moves read it; one blurred mid-move stays clear until the move ends.
pub fn blurred(&self, on: bool) {
if !on {
unsafe { RemovePropW(self.window, BLURRED) };
return;
}
if unsafe { SetPropW(self.window, BLURRED, 1 as _) } == 0 {
log::error!("blur: the window's blurred mark does not set");
}
if !unsafe { GetPropW(self.window, MOVING) }.is_null() {
match accent(self.window, CLEAR) {
Ok(()) => log::debug!("blur: blurred mid-move, the window moves clear"),
Err(error) => log::error!("{error:#}"),
}
}
}
}
/// The window's procedure: a blurred window clears its acrylic while it moves; all else is gpui's.
unsafe extern "system" fn moving(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
let gpui: WNDPROC = unsafe { std::mem::transmute(GPUI.load(Ordering::Relaxed)) };
let blurred = !unsafe { GetPropW(window, BLURRED) }.is_null();
match message {
WM_ENTERSIZEMOVE => {
if unsafe { SetPropW(window, MOVING, 1 as _) } == 0 {
log::error!("blur: the window's moving mark does not set");
}
if blurred {
match accent(window, CLEAR) {
Ok(()) => log::debug!("blur: the window moves clear"),
Err(error) => log::error!("{error:#}"),
}
}
}
WM_EXITSIZEMOVE => {
unsafe { RemovePropW(window, MOVING) };
if blurred {
match accent(window, ACRYLIC) {
Ok(()) => log::debug!("blur: the window rests blurred again"),
Err(error) => log::error!("{error:#}"),
}
let flags =
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED;
if unsafe { SetWindowPos(window, std::ptr::null_mut(), 0, 0, 0, 0, flags) } == 0 {
log::error!("blur: DWM hears of no new frame after the move");
}
}
}
WM_NCDESTROY => {
unsafe { RemovePropW(window, BLURRED) };
unsafe { RemovePropW(window, MOVING) };
}
_ => {}
}
unsafe { CallWindowProcW(gpui, window, message, wparam, lparam) }
}
#[repr(C)]
struct AccentPolicy {
state: u32,
flags: u32,
color: u32,
animation: u32,
}
#[repr(C)]
struct CompositionData {
attribute: u32,
data: *mut AccentPolicy,
size: usize,
}
/// Sets the window's accent as gpui's `set_window_composition_attribute` sets it.
fn accent(window: HWND, state: u32) -> anyhow::Result<()> {
type Set = unsafe extern "system" fn(HWND, *mut CompositionData) -> BOOL;
let user32 = unsafe { GetModuleHandleW(w!("user32.dll")) };
anyhow::ensure!(!user32.is_null(), "blur: user32.dll is not loaded");
let found = unsafe { GetProcAddress(user32, s!("SetWindowCompositionAttribute")) }
.context("blur: user32.dll has no SetWindowCompositionAttribute")?;
let set: Set = unsafe { std::mem::transmute(found) };
let acrylic = state == ACRYLIC;
let mut policy = AccentPolicy {
state,
flags: if acrylic { 0 } else { 2 },
color: if acrylic { 1 << 24 } else { 0 },
animation: 0,
};
let mut data = CompositionData {
attribute: 0x13,
data: &mut policy,
size: size_of::<AccentPolicy>(),
};
anyhow::ensure!(
unsafe { set(window, &mut data) } != 0,
"blur: the window's accent {state} does not set"
);
Ok(())
}
/// The running Windows' build.
fn build() -> anyhow::Result<u32> {
let mut version: OSVERSIONINFOW = unsafe { std::mem::zeroed() };
version.dwOSVersionInfoSize = size_of::<OSVERSIONINFOW>() as u32;
let status = unsafe { RtlGetVersion(&mut version) };
anyhow::ensure!(status == 0, "blur: RtlGetVersion fails with {status:#x}");
Ok(version.dwBuildNumber)
}
/// The HWND gpui made for `window`.
fn native(window: &Window) -> anyhow::Result<HWND> {
let handle = HasWindowHandle::window_handle(window)
.map_err(|error| anyhow::anyhow!("blur: no native handle: {error:?}"))?;
let RawWindowHandle::Win32(win32) = handle.as_raw() else {
bail!("blur: not a Win32 window");
};
Ok(win32.hwnd.get() as HWND)
}