use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32Handle};
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::shared::ntdef::{NTSTATUS, PVOID, ULONG, WCHAR};
use winapi::shared::windef::{HWINEVENTHOOK, HWND};
use winapi::shared::winerror::S_OK;
use winapi::um::dwmapi::DwmExtendFrameIntoClientArea;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryA};
use winapi::um::uxtheme::MARGINS;
use cstr::cstr;
use winapi::um::winbase::{FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageW, LocalFree};
use winapi::um::winnt::{LANG_NEUTRAL, LONG, LPWSTR, MAKELANGID, SUBLANG_NEUTRAL};
use crate::Blur;
use std::collections::HashMap;
use std::sync::Mutex;
use winapi::um::processthreadsapi::GetCurrentProcessId;
use winapi::um::winuser::{CHILDID_SELF, EVENT_MAX, EVENT_MIN, EVENT_OBJECT_DESTROY, EVENT_SYSTEM_MOVESIZEEND, EVENT_SYSTEM_MOVESIZESTART, OBJID_WINDOW, SetWinEventHook};
const WINDOWS_10_1805: DWORD = 18362;
const WINDOWS_10_1903: DWORD = 18362;
type __SetWindowCompositionAttribute =
unsafe extern "system" fn(
HWND,
*mut WindowCompositionAttributeData) -> BOOL;
type __RtlGetVersion =
unsafe extern "system" fn(*mut OperatingSystemVersionInfoWide) -> NTSTATUS;
lazy_static::lazy_static! {
static ref MEMORY: Mutex<HashMap<usize, WindowState>> = Default::default();
static ref EVENT_HOOK_INSTALLED: Mutex<usize> = Default::default();
static ref VERSION: Option<OperatingSystemVersionInfoWide> = unsafe {
let byte_size = std::mem::size_of::<OperatingSystemVersionInfoWide>();
let byte_size = ULONG::try_from(byte_size)
.expect("The size of an OperatingSystemVersionInfoWide is larger \
than what can fit in a ULONG. This structure has a very \
well-defined size in Windows and in this library, and it \
should never naturally end up as bigger than a ULONG. This can \
either point towards a broken build environment or a compiler \
bug.");
let mut version = OperatingSystemVersionInfoWide {
byte_size,
major_version: 0,
minor_version: 0,
build_number: 0,
platform_id: 0,
service_pack: [0; 128]
};
let library = LoadLibraryA(cstr!("ntdll.dll").as_ptr());
if library.is_null() {
log::warn!(
"Ntdll.dll is not available. Selected blur method may be \
wrong.");
return None
}
let address = GetProcAddress(
library,
cstr!("RtlGetVersion").as_ptr());
if address.is_null() {
log::warn!(
"RtlGetVersion is not available. Selected blur method may be \
wrong.");
return None
}
let result = {
let function = std::mem
::transmute
::<_, __RtlGetVersion>(address);
function(&mut version as *mut _)
};
if result != 0 {
log::error!(
"RtlGetVersion returned {} when it should have been an \
infallible function, whose only valid return value is \
STATUS_SUCCESS. This may be indicative of a bug.",
result);
return None
}
FreeLibrary(library);
Some(version)
};
}
pub fn set_blur<H: HasRawWindowHandle>(window: &H, kind: Blur) -> Result<(), Error> {
let win7 = match kind {
Blur::Disabled => AccentPolicyState::Disabled,
_ => AccentPolicyState::BlurBehind,
};
let win10 = match kind {
Blur::Disabled => AccentPolicyState::Disabled,
Blur::Quality => AccentPolicyState::AcrylicBlurBehind,
Blur::Performance => AccentPolicyState::BlurBehind
};
match window.raw_window_handle() {
RawWindowHandle::Win32(Win32Handle { hwnd, .. }) => unsafe {
let policy = match *VERSION {
Some(OperatingSystemVersionInfoWide {
major_version,
build_number,
.. }) => {
if major_version < 6 {
return Err(Error::BlurNotSupported)
} else if major_version < 10 {
AccentPolicy {
state: win7,
accent_flags: 0,
gradient_color: 0,
animation_id: 0
}
} else {
if build_number < WINDOWS_10_1805 {
AccentPolicy {
state: win7,
accent_flags: 0,
gradient_color: 0,
animation_id: 0
}
} else {
AccentPolicy {
state: win10,
accent_flags: 0,
gradient_color: 0x00101010,
animation_id: 0
}
}
}
},
None =>
return Err(Error::BlurNotSupported)
};
let mut margins = MARGINS {
cxLeftWidth: 0,
cxRightWidth: 0,
cyTopHeight: 1,
cyBottomHeight: 0
};
let result = DwmExtendFrameIntoClientArea(
hwnd as *mut _,
&mut margins as *mut _);
if result != S_OK {
return Err(Error::FailedInvocation {
call: "DwmExtendFrameIntoClientArea",
description: "extend window frame into the client area",
error: WinError(result as DWORD)
})
}
let mut old = None;
let mut memory = MEMORY.lock().unwrap();
let state = memory
.entry(hwnd as _)
.and_modify(|current| {
old = Some(current.accent);
current.accent = policy;
})
.or_insert(WindowState {
transform_hold: false,
accent: policy
});
if !state.transform_hold {
if let Some(current) = old {
match (current.state, policy.state) {
(AccentPolicyState::AcrylicBlurBehind, target)
if target != AccentPolicyState::Disabled =>
transition(hwnd as _, AccentPolicy {
state: AccentPolicyState::Disabled,
accent_flags: 0,
gradient_color: 0,
animation_id: 0
}),
(source, AccentPolicyState::AcrylicBlurBehind)
if source != AccentPolicyState::Disabled =>
transition(hwnd as _, AccentPolicy {
state: AccentPolicyState::Disabled,
accent_flags: 0,
gradient_color: 0,
animation_id: 0
}),
_ => Ok(())
}?;
}
transition(hwnd as _, policy)?;
}
let mut installed = EVENT_HOOK_INSTALLED.lock().unwrap();
if *installed == 0 {
*installed = SetWinEventHook(
EVENT_MIN,
EVENT_MAX,
std::ptr::null_mut(),
Some(handle_window_events),
GetCurrentProcessId(),
0,
0) as usize;
if *installed == 0 {
return Err(Error::FailedInvocation {
call: "SetWinEventHook",
description: "register hook for state upkeep",
error: WinError(GetLastError())
})
}
}
Ok(())
},
RawWindowHandle::WinRt(_) =>
Err(Error::BlurNotSupported),
_ =>
Err(Error::BlurNotSupported)
}
}
struct WindowState {
transform_hold: bool,
accent: AccentPolicy
}
unsafe fn transition(hwnd: HWND, mut policy: AccentPolicy) -> Result<(), Error> {
let mut data = WindowCompositionAttributeData {
attribute: WindowCompositionAttribute::AccentPolicy,
data: &mut policy as *mut AccentPolicy as *mut _,
data_size: std::mem::size_of::<AccentPolicy>() as _
};
let lib = LoadLibraryA(cstr!("user32.dll").as_ptr());
if lib.is_null() {
return Err(Error::MissingFunctionality {
name: "user32.dll",
error: WinError(GetLastError())
})
}
let address_set = GetProcAddress(
lib,
cstr!("SetWindowCompositionAttribute").as_ptr());
if address_set.is_null() {
return Err(Error::MissingFunctionality {
name: "user32.dll\\SetWindowCompositionAttribute",
error: WinError(GetLastError())
})
}
let result = {
let address_set = std::mem
::transmute
::<_, __SetWindowCompositionAttribute>(address_set);
address_set(
hwnd as *mut _,
&mut data as *mut _)
};
if result == 0 {
return Err(Error::FailedInvocation {
call: "SetWindowCompositionAttribute",
description: "set the composition attribute of the \
window",
error: WinError(GetLastError())
})
}
FreeLibrary(lib);
Ok(())
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Blur is not supported for this window type")]
BlurNotSupported,
#[error("Could not find \"{name}\": {error}")]
MissingFunctionality {
name: &'static str,
error: WinError
},
#[error("Could not {description}: call to \"{call}\" failed: {error}")]
FailedInvocation {
call: &'static str,
description: &'static str,
error: WinError
}
}
unsafe extern "system" fn handle_window_events(
_this: HWINEVENTHOOK,
event: DWORD,
hwnd: HWND,
object_id: LONG,
child_id: LONG,
_is_event_thread: DWORD,
_event_time: DWORD) {
if object_id != OBJID_WINDOW || child_id != CHILDID_SELF {
return
}
match event {
EVENT_OBJECT_DESTROY => {
let mut memory = MEMORY.lock().unwrap();
memory.remove(&(hwnd as _));
},
EVENT_SYSTEM_MOVESIZESTART => {
let mut memory = MEMORY.lock().unwrap();
let state = match memory.get_mut(&(hwnd as _)) {
Some(state) => state,
None => return
};
let cheap_transform_required =
VERSION.as_ref().map(|info| {
info.major_version >= 10 && info.build_number >= WINDOWS_10_1903
}).unwrap_or(false)
&& state.accent.state == AccentPolicyState::AcrylicBlurBehind;
if cheap_transform_required {
state.transform_hold = true;
let _ = transition(hwnd, AccentPolicy {
state: AccentPolicyState::Disabled,
accent_flags: 0,
gradient_color: 0,
animation_id: 0
});
let _ = transition(hwnd, AccentPolicy {
state: AccentPolicyState::BlurBehind,
accent_flags: 0,
gradient_color: 0,
animation_id: 0
});
}
},
EVENT_SYSTEM_MOVESIZEEND => {
let mut memory = MEMORY.lock().unwrap();
let state = match memory.get_mut(&(hwnd as _)) {
Some(state) => state,
None => return
};
if state.transform_hold {
let _ = transition(hwnd, state.accent);
state.transform_hold = false;
}
},
_ => {}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct WinError(DWORD);
impl WinError {
pub fn message(&self) -> Option<String> {
let mut buffer: LPWSTR = std::ptr::null_mut();
let size = unsafe {
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS,
std::ptr::null_mut(),
self.0,
MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) as DWORD,
&mut buffer as *mut LPWSTR as *mut _,
0,
std::ptr::null_mut())
};
if size == 0 {
return None
}
let slice = unsafe {
std::slice::from_raw_parts(buffer, size as usize)
};
let message = std::char::decode_utf16(slice.iter().cloned())
.map(|c| c.unwrap_or('_'))
.collect::<String>()
.trim()
.to_string();
unsafe {
LocalFree(buffer as *mut _);
}
Some(message)
}
}
impl std::fmt::Display for WinError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.message() {
Some(message) =>
write!(f, "{} (0x{:08x})", message, self.0),
None =>
write!(f, "0x{:08x}", self.0),
}
}
}
impl std::error::Error for WinError {}
#[repr(C)]
struct OperatingSystemVersionInfoWide {
byte_size: ULONG,
major_version: ULONG,
minor_version: ULONG,
build_number: ULONG,
platform_id: ULONG,
service_pack: [WCHAR; 128],
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
struct WindowCompositionAttributeData {
attribute: WindowCompositionAttribute,
data: PVOID,
data_size: ULONG
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
enum WindowCompositionAttribute {
AccentPolicy = 19
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
struct AccentPolicy {
state: AccentPolicyState,
accent_flags: i32,
gradient_color: i32,
animation_id: i32,
}
#[repr(i32)]
#[allow(dead_code)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
enum AccentPolicyState {
Disabled = 0,
Gradient = 1,
TransparentGradient = 2,
BlurBehind = 3,
AcrylicBlurBehind = 4,
}