T65: on Windows 10 a blurred window's acrylic clears while it moves or sizes, which a user asked for in issue #3, where a drag trailed the pointer by seconds: below build 22000 the main window's procedure takes gpui's clear accent at WM_ENTERSIZEMOVE and its acrylic again at WM_EXITSIZEMOVE; Windows 11 keeps its acrylic

This commit is contained in:
ZacharyZhang-NY committed 2026-10-19 19:37:10 -04:00
1 parent d51823988e
commit 1384ef5dbe
6 files changed
+192 -6

No files matched your search

+19 -2
View File
@@ -6,6 +6,8 @@ use crate::settings::Settings;
mod macos;
#[cfg(target_os = "linux")]
mod wayland;
#[cfg(windows)]
mod windows;
#[cfg(target_os = "linux")]
mod x11;
@@ -27,6 +29,8 @@ pub struct Blur {
wayland: Option<wayland::Effect>,
#[cfg(target_os = "linux")]
x11: Option<x11::Effect>,
#[cfg(windows)]
moves: Option<windows::Moves>,
}
impl Blur {
@@ -37,14 +41,23 @@ impl Blur {
if shown == WindowBackgroundAppearance::Blurred {
macos::blur(window);
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
let _ = window;
#[cfg(windows)]
let moves = windows::Moves::on(window).unwrap_or_else(|error| {
log::error!("{error:#}");
None
});
#[cfg(windows)]
if let Some(moves) = &moves {
moves.blurred(shown == WindowBackgroundAppearance::Blurred);
}
Self {
shown,
#[cfg(target_os = "linux")]
wayland: wayland::Effect::on(window),
#[cfg(target_os = "linux")]
x11: x11::Effect::on(window),
#[cfg(windows)]
moves,
}
}
@@ -66,6 +79,10 @@ impl Blur {
if wanted == WindowBackgroundAppearance::Blurred {
macos::blur(window);
}
#[cfg(windows)]
if let Some(moves) = &self.moves {
moves.blurred(wanted == WindowBackgroundAppearance::Blurred);
}
}
/// Keeps a Linux compositor's blur on the window's pane while Blurred; each frame, sending only a change.
+167
View File
@@ -0,0 +1,167 @@
//! 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");
/// 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.
pub fn blurred(&self, on: bool) {
if on {
if unsafe { SetPropW(self.window, BLURRED, 1 as _) } == 0 {
log::error!("blur: the window's blurred mark does not set");
}
} else {
unsafe { RemovePropW(self.window, BLURRED) };
}
}
}
/// 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 blurred => match accent(window, CLEAR) {
Ok(()) => log::debug!("blur: the window moves clear"),
Err(error) => log::error!("{error:#}"),
},
WM_EXITSIZEMOVE 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 { 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)
}