注意:这里是 Tauri2

最近在做一个 Tauri 桌面应用时,想加个窗口材质切换功能 —— 就是 Windows 11 里的 Mica(云母)、Acrylic(亚克力) 类半透明效果。本来以为 Tauri 自带相关 API,试了才发现:Tauri 确实能在初始化时设置窗口材质,但运行中动态切换是做不到的。没办法,只能自己动手用 Rust 调用系统 API 实现了,这里把过程分享出来,希望能帮到同样踩坑的朋友。

先说说 Tauri 在材质设置的局限

一开始我查 Tauri 文档,发现可以在 tauri.conf.json 里通过 windows 配置设置 transparent 后,能指定 WindowEffect 来实现材质的更改(还需要设置前端背景为 transparent)。但这些配置都是应用启动时生效的,一旦应用跑起来,就没法通过前端调用修改了。如果用户想在使用中切换不同的窗口效果,原生 API 根本满足不了,所以必须自己写 Rust 后端调用 Windows 的 DWM 接口。

核心思路:用 Rust 对接 Windows DWM API

Windows 的窗口效果是由Desktop Window Manager(DWM)管理的,它提供了 DwmSetWindowAttributeDwmGetWindowAttribute 两个关键 API,分别用来设置和获取窗口属性。我们要做的就是用 Rust 调用这两个 API,实现材质的动态切换。

微软文档链接:

  1. DwmGetWindowAttribute 函数 (dwmapi.h)
  2. DwmSetWindowAttribute 函数 (dwmapi.h)
  3. DWMWINDOWATTRIBUTE 枚举 (dwmapi.h)

第一步:检测系统版本,确定支持的材质

不同 Windows 版本支持的材质不一样,比如 Mica 材质只在 Windows 11(Build 22000+)才支持。所以第一步必须先判断系统版本,避免在不支持的系统上显示无效选项。​
我用 Rust 的 winreg 库(需要 cargo add winreg 使用)读取注册表来获取系统版本:

// 读取注册表中的系统版本号
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
let current_version = hklm.open_subkey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion")?;
let build_number: String = current_version.get_value("CurrentBuild")?;

拿到build_number后,就能判断哪些材质可用了。比如 Build 22000 以上支持 Mica、Acrylic等,低版本可能只支持默认材质:

// 根据版本动态生成支持的材质列表
if build_number >= 22000 {
    materials.push(MaterialInfo { id: DWMSBT_MAINWINDOW.0, name: "Mica" });
    materials.push(MaterialInfo { id: DWMSBT_TRANSIENTWINDOW.0, name: "Acrylic" });
    materials.push(MaterialInfo { id: DWMSBT_TABBEDWINDOW.0, name: "Tabbed (MicaAlt)" });
}

第二步:获取窗口句柄(HWND)​

调用 DWM API 必须要有窗口句柄(HWND),Tauri 里可以通过 window_handle() 方法获取,但需要注意:Tauri 的窗口可能有多层父窗口,必须找到最顶层的那个句柄才有效。​
我用 Windows API 的 GetParent函数 循环向上查找顶层窗口:

// 从Tauri窗口获取HWND
let window = app.get_webview_window("main")?;
let handle = window.window_handle()?;
// 转换为Windows原生句柄
match handle.as_raw() {
    RawWindowHandle::Win32(h) => {
        let mut current = HWND(isize::from(h.hwnd) as *mut c_void);
        // 循环找顶层窗口
        unsafe {
            loop {
                match GetParent(current) {
                    Ok(parent) if !parent.0.is_null() => current = parent,
                    _ => break,
                }
            }
        }
        Some(current.0 as isize) // 最终拿到可用的HWND
    }
    _ => None
}

这里的 unsafe 块是必须的,因为直接操作系统句柄涉及内存安全,Rust 无法在编译时验证,所以需要开发者自己保证逻辑正确。

第三步:实现材质切换功能​

有了窗口句柄和系统版本信息,就可以调用 DwmSetWindowAttribute 设置材质了。核心代码如下:

// 设置窗口材质
pub fn set_window_material(app: &tauri::AppHandle, material: i32) -> u32 {
    let hwnd = get_window_hwnd(app.clone()).unwrap(); // 获取前面拿到的HWND
    let mut material_id = material; // 材质ID(对应DWM的枚举值)
    unsafe {
        // 调用DWM API设置属性
        DwmSetWindowAttribute(
            HWND(hwnd as *mut c_void), 
            DWMWA_SYSTEMBACKDROP_TYPE, // 要设置的属性:系统背景类型
            (&mut material_id as *mut i32).cast::<c_void>(), // 材质ID的指针
            std::mem::size_of::<i32>() as u32 // 参数大小
        );
    }
    material_id.try_into().unwrap()
}

这里的 material参数 对应 DWM 的枚举值:DWMSBT_AUTO(默认)DWMSBT_MAINWINDOW(Mica)等,前面定义的MaterialInfo结构体就是把这些值和友好名称对应起来,方便前端显示。​

use windows::Win32::Graphics::Dwm::{
    DWMSBT_AUTO,
    DWMSBT_NONE,
    DWMSBT_MAINWINDOW,
    DWMSBT_TRANSIENTWINDOW,
    DWMSBT_TABBEDWINDOW,
    DWMWA_SYSTEMBACKDROP_TYPE,
    DwmGetWindowAttribute,
    DwmSetWindowAttribute
};

#[derive(Serialize)]
pub struct MaterialInfo {
    pub id: i32,
    pub name: &'static str,
}

对于样式样式枚举 DWM_SYSTEMBACKDROP_TYPE 的解释:

  • DWMSBT_AUTO = 0 自动:默认
  • DWMSBT_NONE = 1 无背景样式
  • DWMSBT_MAINWINDOW = 2 云母(Mica)
  • DWMSBT_TRANSIENTWINDOW = 3 亚克力(Acrylic)
  • DWMSBT_TABBEDWINDOW = 4 云母(MicaAlt)

前端交互:Vue 3 如何调用后端功能​

前端部分很简单,主要是用 Tauri 的 invoke函数 调用 Rust 写的命令,然后渲染材质列表和处理选择事件。​前端组件库则使用 shadcn/vue

初始化时加载材质列表​

页面加载时,先调用后端获取支持的材质和当前材质:

// 前端初始化代码
onMounted(() => {
    // 获取支持的材质列表
    invoke('get_supported_window_materials').then(value => {
        materials.value = value as MaterialInfo[];
    });
    // 获取当前正在使用的材质
    invoke('get_current_window_materials').then(value => {
        selectedMaterial.value = (value as MaterialInfo[])[0];
    });
});

用下拉框实现切换交互​

我用了一个 Combobox 组件 展示材质列表,用户选择后调用 set_window_material 命令:

<!-- 前端交互组件 -->
<Combobox>
  <ComboboxTrigger as-child>
    <Button variant="outline">
      {{ selectedMaterial?.name }}
      <ChevronsUpDown class="ml-2 h-4 w-4" />
    </Button>
  </ComboboxTrigger>
  <ComboboxList>
    <ComboboxItem 
      v-for="material in materials" 
      :key="material.id"
      @select="() => setMaterial(material)"
    >
      {{ material.name }}
    </ComboboxItem>
  </ComboboxList>
</Combobox>

// 切换材质的方法
const setMaterial = (material: MaterialInfo) => {
    invoke('set_window_material', { material: material.id })
        .then(() => {
            selectedMaterial.value = material; // 更新选中状态
        })
        .catch(error => {
            console.error('切换失败:', error);
        });
};

遇到的坑

  • 窗口句柄获取错误:一开始直接用了 Tauri 返回的第一层窗口句柄,调用 DWM API 始终失败,后来才发现需要找顶层父窗口,用 GetParent 循环查找才解决,如果不找,则拿到的是 webview 的子窗口句柄。​

最后总结​

虽然 Tauri 原生不支持动态切换窗口材质,但通过 Rust 调用 Windows 的 DWM API 完全可以实现。整个过程的核心就是:正确获取窗口句柄→根据系统版本提供合适的材质选项→用 DWM API 执行切换。​
这个功能写下来最大的感受是:Rust 虽然入门难,但处理系统级 API 时确实安全又高效;而 Tauri 的前后端通信机制也很方便,让前端能轻松调用底层功能。

评论区(暂无评论)

我要评论

昵称
邮箱
网址
0/200
没有评论
可按 ESC 键退出搜索

0 篇文章已搜寻到~