win_acrylic_sys/
lib.rs

1use std::{
2    mem::size_of,
3    sync::atomic::{AtomicUsize, Ordering},
4};
5
6use once_cell::sync::Lazy;
7use windows_sys::Win32::{
8    Foundation::*,
9    Graphics::Dwm::*,
10    System::LibraryLoader::{GetModuleHandleA, GetProcAddress},
11    UI::Controls::MARGINS,
12};
13
14#[link(name = "ntdll")]
15extern "system" {
16    fn RtlGetVersion(os_ver_info: *mut OsVersionInfo) -> u32;
17}
18
19#[derive(Debug, Clone, Copy)]
20#[repr(C)]
21struct OsVersionInfo {
22    os_version_info_size: u32,
23    major_version: u32,
24    minor_version: u32,
25    build_number: u32,
26    platform_id: u32,
27    csd_version: [u16; 128],
28}
29
30fn build_no() -> u32 {
31    let mut os_ver_info: OsVersionInfo = unsafe { std::mem::zeroed() };
32    os_ver_info.os_version_info_size = size_of::<OsVersionInfo>() as _;
33    unsafe { RtlGetVersion(&mut os_ver_info) };
34    os_ver_info.build_number
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38#[repr(C)]
39#[allow(dead_code)]
40enum WindowCompositionAttribute {
41    Undefined = 0,
42    NcrenderingEnabled = 1,
43    NcrenderingPolicy = 2,
44    TransitionsForcedisabled = 3,
45    AllowNcpaint = 4,
46    CaptionButtonBounds = 5,
47    NonclientRtlLayout = 6,
48    ForceIconicRepresentation = 7,
49    ExtendedFrameBounds = 8,
50    HasIconicBitmap = 9,
51    ThemeAttributes = 10,
52    NcrenderingExiled = 11,
53    Ncadornmentinfo = 12,
54    ExcludedFromLivepreview = 13,
55    VideoOverlayActive = 14,
56    ForceActivewindowAppearance = 15,
57    DisallowPeek = 16,
58    Cloak = 17,
59    Cloaked = 18,
60    AccentPolicy = 19,
61    FreezeRepresentation = 20,
62    EverUncloaked = 21,
63    VisualOwner = 22,
64    Holographic = 23,
65    ExcludedFromDda = 24,
66    Passiveupdatemode = 25,
67    Usedarkmodecolors = 26,
68    Last = 27,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72#[repr(C)]
73struct WindowCompositionAttributeData {
74    attrib: WindowCompositionAttribute,
75    pv_data: *mut std::ffi::c_void,
76    cb_data: usize,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80#[allow(dead_code)]
81#[repr(C)]
82enum AccentState {
83    Disabled = 0,
84    EnableGradient = 1,
85    EnableTransparentgradient = 2,
86    EnableBlurbehind = 3,
87    EnableAcrylicblurbehind = 4,
88    EnableHostbackdrop = 5,
89    InvalidState = 6,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[repr(C)]
94struct AccentPolicy {
95    accent_state: AccentState,
96    accent_flags: u32,
97    gradient_color: u32,
98    animation_id: u32,
99}
100
101static SET_WINDOW_COMPOSITION_ATTRIBUTE: Lazy<
102    extern "system" fn(
103        hwnd: HWND,
104        wnd_comp_attrib_data: *const WindowCompositionAttributeData,
105    ) -> BOOL,
106> = Lazy::new(|| unsafe {
107    let user32 = GetModuleHandleA(b"user32.dll\0".as_ptr() as _);
108    std::mem::transmute(GetProcAddress(
109        user32,
110        b"SetWindowCompositionAttribute\0".as_ptr() as _,
111    ))
112});
113
114unsafe fn set_window_composition_attribute<T>(
115    hwnd: HWND,
116    attrib: WindowCompositionAttribute,
117    data: T,
118) -> bool {
119    let window_composition_attribute = WindowCompositionAttributeData {
120        attrib,
121        pv_data: &data as *const _ as _,
122        cb_data: size_of::<T>(),
123    };
124    (*SET_WINDOW_COMPOSITION_ATTRIBUTE)(hwnd, &window_composition_attribute) != 0
125}
126unsafe fn dwm_set_window_attribute<T>(hwnd: HWND, attrib: DWMWINDOWATTRIBUTE, data: T) -> bool {
127    DwmSetWindowAttribute(hwnd, attrib, &data as *const _ as _, size_of::<T>() as _) == 0
128}
129
130static LAST_EFFECT: AtomicUsize = AtomicUsize::new(Effect::None as _);
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
133pub enum Effect {
134    None,
135    Solid,
136    Transparent,
137    Aero,
138    Acrylic,
139    Mica,
140    Tabbed,
141}
142
143impl From<Effect> for AccentState {
144    fn from(effect: Effect) -> Self {
145        match effect {
146            Effect::None => AccentState::Disabled,
147            Effect::Solid => AccentState::EnableGradient,
148            Effect::Transparent => AccentState::EnableTransparentgradient,
149            Effect::Aero => AccentState::EnableBlurbehind,
150            Effect::Acrylic => AccentState::EnableAcrylicblurbehind,
151            Effect::Mica => AccentState::EnableHostbackdrop,
152            Effect::Tabbed => AccentState::InvalidState,
153        }
154    }
155}
156
157const NEGATIVE_ONE_MARGIN: MARGINS = MARGINS {
158    cxLeftWidth: -1,
159    cxRightWidth: -1,
160    cyBottomHeight: -1,
161    cyTopHeight: -1,
162};
163
164const RESTORATIVE_MARGIN: MARGINS = MARGINS {
165    cxLeftWidth: 0,
166    cxRightWidth: 0,
167    cyBottomHeight: 0,
168    cyTopHeight: 1,
169};
170
171/// Freshly introduced in Windows 11 build 22523.
172///
173/// Allows *Tabbed* style.
174///
175/// Acrylic => 3i32
176/// Mica => 2i32
177/// Tabbed => 4i32
178const DWMWA_NEW_BACKDROP_MODE: DWMWINDOWATTRIBUTE = 38;
179
180/// Introduced in Windows 11 22000.
181///
182/// Takes [BOOL].
183const DWMWA_USE_MICA_BACKDROP: DWMWINDOWATTRIBUTE = 1029;
184
185/// Set window backdrop effect to the specified handle.
186///
187/// Color is in (R,G,B,A) format.
188///
189/// # Panics
190///
191/// Panics if non-mica effect is requested with no color specified.
192///
193/// # Safety
194///
195/// The handle `hwnd` must be valid.
196pub unsafe fn set_effect(
197    hwnd: HWND,
198    effect: Effect,
199    dark_mode: bool,
200    color: Option<(u8, u8, u8, u8)>,
201) {
202    // Set [ACCENT_DISABLED] as [ACCENT_POLICY] in
203    // [SetWindowCompositionAttribute] to apply styles properly.
204    set_window_composition_attribute(
205        hwnd,
206        WindowCompositionAttribute::AccentPolicy,
207        AccentPolicy {
208            accent_state: AccentState::Disabled,
209            accent_flags: 2,
210            gradient_color: 0,
211            animation_id: 0,
212        },
213    );
214
215    let windows_build_no = build_no();
216
217    // Only on later Windows 11 versions and if effect is WindowEffect.mica,
218    // WindowEffect.acrylic or WindowEffect.tabbed, otherwise fallback to old
219    // approach.
220    if windows_build_no >= 22523 && effect > Effect::Aero {
221        DwmExtendFrameIntoClientArea(hwnd, &NEGATIVE_ONE_MARGIN);
222        dwm_set_window_attribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, BOOL::from(dark_mode));
223        dwm_set_window_attribute(
224            hwnd,
225            DWMWA_NEW_BACKDROP_MODE,
226            match effect {
227                Effect::Acrylic => 3i32,
228                Effect::Mica => 2,
229                Effect::Tabbed => 4,
230                _ => unreachable!(),
231            },
232        );
233    } else if effect == Effect::Mica {
234        // Check for Windows 11.
235        if windows_build_no >= 22000 {
236            // Mica effect requires [DwmExtendFrameIntoClientArea & "sheet of
237            // glass"
238            // effect with negative margins.
239            DwmExtendFrameIntoClientArea(hwnd, &NEGATIVE_ONE_MARGIN);
240            dwm_set_window_attribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, BOOL::from(dark_mode));
241            dwm_set_window_attribute(hwnd, DWMWA_USE_MICA_BACKDROP, BOOL::from(true));
242        }
243    } else {
244        // Restore original window style & [DwmExtendFrameIntoClientArea] margin
245        // if the last set effect was [WindowEffect.mica], since it sets
246        // negative margins to the window.
247        if (windows_build_no >= 22000
248            && LAST_EFFECT.load(Ordering::Relaxed) == Effect::Mica as usize)
249            || (windows_build_no >= 22523
250                && LAST_EFFECT.load(Ordering::Relaxed) > Effect::Aero as usize)
251        {
252            // Atleast one margin should be non-negative in order to show the DWM
253            // window shadow created by handling [WM_NCCALCSIZE].
254            //
255            // Matching value with bitsdojo_window.
256            // https://github.com/bitsdojo/bitsdojo_window/blob/adad0cd40be3d3e12df11d864f18a96a2d0fb4fb/bitsdojo_window_windows/windows/bitsdojo_window.cpp#L149
257            DwmExtendFrameIntoClientArea(hwnd, &RESTORATIVE_MARGIN);
258            dwm_set_window_attribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, BOOL::from(false));
259            dwm_set_window_attribute(hwnd, DWMWA_USE_MICA_BACKDROP, BOOL::from(false));
260        }
261        set_window_composition_attribute(
262            hwnd,
263            WindowCompositionAttribute::AccentPolicy,
264            AccentPolicy {
265                accent_state: AccentState::from(effect),
266                accent_flags: 2,
267                gradient_color: color
268                    .map(|(r, g, b, a)| {
269                        (a as u32) << 24 | (b as u32) << 16 | (g as u32) << 8 | r as u32
270                    })
271                    .unwrap(),
272                animation_id: 0,
273            },
274        );
275    }
276
277    LAST_EFFECT.store(effect as _, Ordering::Relaxed);
278}