3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

デスクトップをアクリル素材のウィンドウで隠す

Posted at

成果物

↓デスクトップ全体がアクリル素材で隠されている様子
output.gif

環境

Windows 11 24H2
rustc 1.89.0

解説

EnumDisplayMonitors関数でデスクトップ領域のサイズを取得して、そのサイズのウィンドウを生成。SetWindowCompositionAttribute関数で生成したウィンドウにアクリル素材を適用しています。

EnumDisplayMonitors

EnumDisplayMonitors 関数ですべてのモニターのデスクトップ領域を取得します。

unsafe extern "system" fn enum_proc(_: HMONITOR, _: HDC, rect: *mut RECT, _: LPARAM) -> BOOL {
    // RECT 構造体へのポインタを Rust の参照に変換
    let rect = unsafe { &*rect };
    // 各モニターのデスクトップ領域ごとにウィンドウを生成(後述)
    create_window(rect).ok();
    // 処理を継続する場合は TRUE を返す
    true.into()
}

fn main() {
    _ = unsafe { EnumDisplayMonitors(None, None, Some(enum_proc), LPARAM::default()) };
}

CreateWindowExW

CreateWindowExW 関数でウィンドウを生成します。第 1 引数に WS_EX_NOREDIRECTIONBITMAP を渡して目に見えるコンテンツのないウィンドウスタイル(よく分からない:thinking:)を設定します。第 5 ~ 8 引数でデスクトップ領域の大きさと同じウィンドウサイズを設定します。さらに SetWindowLongW 関数ですべてのウィンドウスタイルを無効にしてタイトルバーやボーダーのないウィンドウスタイルを設定しています。

fn create_window(rect: &RECT) -> Result<()> {
    let hwnd = unsafe {
        CreateWindowExW(
            WS_EX_NOREDIRECTIONBITMAP, // 目に見えるコンテンツのないウィンドウ?
            CLASS_NAME,
            w!("Acrylic"),
            WS_BORDER,
            rect.left, // ウィンドウ左上の X 座標
            rect.top,  // ウィンドウ左上の Y 座標
            rect.right - rect.left, // ウィンドウの幅
            rect.bottom - rect.top, // ウィンドウの高さ
            None,
            None,
            None,
            None,
        )?
    };

    // ウィンドウにアクリル素材を適用する(後述)
    enable_blur_window(hwnd)?;

    // タイトルバーのないウィンドウ
    unsafe { SetWindowLongW(hwnd, GWL_STYLE, 0) };
    // ウィンドウの表示
    unsafe { ShowWindow(hwnd, SW_SHOW).ok()? };
    unsafe { UpdateWindow(hwnd).ok()? };
    Ok(())
}

SetWindowCompositionAttribute

SetWindowCompositionAttribute 関数でアクリル素材をウィンドウに適用します。SetWindowCompositionAttribute 関数はヘッダーファイルで宣言されていないので windows crate から呼び出せません。なので LoadLibraryW 関数と GetProcAddres 関数で SetWindowCompositionAttribute 関数へのポインタを取得する必要があります。

// SetWindowCompositionAttribute 関数の型を定義
type SetWindowCompositionAttribute = unsafe fn(HWND, *const WinCompAttrData) -> BOOL;

// DLL から動的にモジュールを読み込むための構造体
struct Lib {
    module: HMODULE,
}

impl Lib {
    fn new() -> Result<Self> {
        // user32.dll を読み込む
        let module = unsafe { LoadLibraryW(w!("user32"))? };
        Ok(Self { module })
    }

    fn set_window_composition_attribute(&self, hwnd: HWND, data: &WinCompAttrData) -> Result<()> {
        // user32.dll から SetWindowCompositionAttribute の関数ポインタを取得
        let p = unsafe {
            GetProcAddress(self.module, s!("SetWindowCompositionAttribute"))
                .context("no proc address")?
        };
        // SetWindowCompositionAttribute 型へ変換
        let set_window_composition_attribute =
            unsafe { std::mem::transmute::<_, SetWindowCompositionAttribute>(p) };
        // SetWindowCompositionAttribute を実行
        unsafe { set_window_composition_attribute(hwnd, data).ok()? };
        Ok(())
    }
}

// スコープを抜けるときに自動的にリソースを解放する 
impl Drop for Lib {
    fn drop(&mut self) {
        _ = unsafe { FreeLibrary(self.module) };
    }
}

fn enable_blur_window(hwnd: HWND) -> Result<()> {
    let lib = Lib::new()?;
    let data = WinCompAttrData::new(AccentPolicy::default());
    lib.set_window_composition_attribute(hwnd, &data)?;
    Ok(())
}

大事なことは大体説明したのでコードを全部載せます。

コピペ用サンプルコード
Cargo.toml
[package]
name = "acrylic"
version = "0.1.0"
edition = "2024"

[dependencies]
anyhow = "1.0"

[dependencies.windows]
version = "0.62"
features = [
  "Win32_Graphics_Gdi",
  "Win32_UI_WindowsAndMessaging",
  "Win32_System_LibraryLoader",
]
src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use anyhow::{Context, Result};
use windows::{
    Win32::{
        Foundation::{FreeLibrary, HMODULE, HWND, LPARAM, LRESULT, RECT, WPARAM},
        Graphics::Gdi::{EnumDisplayMonitors, HDC, HMONITOR, UpdateWindow},
        System::LibraryLoader::{GetProcAddress, LoadLibraryW},
        UI::WindowsAndMessaging::{
            CreateWindowExW, DefWindowProcW, DispatchMessageW, GWL_STYLE, GetMessageW, MSG,
            PostQuitMessage, RegisterClassW, SW_SHOW, SetWindowLongW, ShowWindow, TranslateMessage,
            WM_DESTROY, WNDCLASSW, WS_BORDER, WS_EX_NOREDIRECTIONBITMAP,
        },
    },
    core::{BOOL, PCWSTR, s, w},
};

const CLASS_NAME: PCWSTR = w!("acylic_window_class");

#[repr(i32)]
#[derive(Debug, Clone, Copy)]
enum WindowCompositionAttrib {
    AccentPolicy = 0x13,
}

impl Default for WindowCompositionAttrib {
    fn default() -> Self {
        Self::AccentPolicy
    }
}

#[allow(dead_code)]
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
enum AccentState {
    Disabled = 0,
    BlurBehind = 3,
}

impl Default for AccentState {
    fn default() -> Self {
        Self::BlurBehind
    }
}

#[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
struct AccentPolicy {
    accent_state: AccentState,
    flgas: i32,
    color: i32,
    animation_id: i32,
}

#[repr(C)]
#[derive(Debug, Default)]
struct WinCompAttrData {
    attribute: WindowCompositionAttrib,
    data: *const u8,
    data_size: usize,
}

impl WinCompAttrData {
    fn new(policy: AccentPolicy) -> Self {
        Self {
            data: &policy as *const AccentPolicy as *const _,
            data_size: size_of::<AccentPolicy>(),
            ..Default::default()
        }
    }
}

type SetWindowCompositionAttribute = unsafe fn(HWND, *const WinCompAttrData) -> BOOL;

struct Lib {
    module: HMODULE,
}

impl Lib {
    fn new() -> Result<Self> {
        let module = unsafe { LoadLibraryW(w!("user32"))? };
        Ok(Self { module })
    }

    fn set_window_composition_attribute(&self, hwnd: HWND, data: &WinCompAttrData) -> Result<()> {
        let p = unsafe {
            GetProcAddress(self.module, s!("SetWindowCompositionAttribute"))
                .context("no proc address")?
        };
        let set_window_composition_attribute =
            unsafe { std::mem::transmute::<_, SetWindowCompositionAttribute>(p) };
        unsafe { set_window_composition_attribute(hwnd, data).ok()? };
        Ok(())
    }
}

impl Drop for Lib {
    fn drop(&mut self) {
        _ = unsafe { FreeLibrary(self.module) };
    }
}

fn enable_blur_window(hwnd: HWND) -> Result<()> {
    let lib = Lib::new()?;
    let data = WinCompAttrData::new(AccentPolicy::default());
    lib.set_window_composition_attribute(hwnd, &data)?;
    Ok(())
}

unsafe extern "system" fn wnd_proc(
    hwnd: HWND,
    msg: u32,
    wparam: WPARAM,
    lparam: LPARAM,
) -> LRESULT {
    match msg {
        WM_DESTROY => unsafe {
            PostQuitMessage(0);
        },
        _ => return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) },
    }
    LRESULT::default()
}

fn create_window(rect: &RECT) -> Result<()> {
    let hwnd = unsafe {
        CreateWindowExW(
            WS_EX_NOREDIRECTIONBITMAP,
            CLASS_NAME,
            w!("Acrylic"),
            WS_BORDER,
            rect.left,
            rect.top,
            rect.right - rect.left,
            rect.bottom - rect.top,
            None,
            None,
            None,
            None,
        )?
    };

    enable_blur_window(hwnd)?;

    unsafe { SetWindowLongW(hwnd, GWL_STYLE, 0) };
    unsafe { ShowWindow(hwnd, SW_SHOW).ok()? };
    unsafe { UpdateWindow(hwnd).ok()? };
    Ok(())
}

unsafe extern "system" fn enum_proc(_: HMONITOR, _: HDC, rect: *mut RECT, _: LPARAM) -> BOOL {
    let rect = unsafe { &*rect };
    create_window(rect).ok();
    true.into()
}

fn main() -> Result<()> {
    let wc = WNDCLASSW {
        lpfnWndProc: Some(wnd_proc),
        lpszClassName: CLASS_NAME,
        ..Default::default()
    };
    unsafe { RegisterClassW(&wc) };

    _ = unsafe { EnumDisplayMonitors(None, None, Some(enum_proc), LPARAM::default()) };

    let mut msg = MSG::default();
    loop {
        if !unsafe { GetMessageW(&mut msg, None, 0, 0) }.as_bool() {
            break;
        }
        unsafe {
            _ = TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }
    Ok(())
}

まとめ

終了するときはAlt + F4でウィンドウを閉じてください :bow:

参考

https://github.com/selastingeorge/Win32-Acrylic-Effect
https://stackoverflow.com/questions/7442939/opening-a-window-that-has-no-title-bar-with-win32

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?