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:
6 files changed
+192
-6
No files matched your search
+19
-2
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user