注意:这里是 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)
管理的,它提供了 DwmSetWindowAttribute
和 DwmGetWindowAttribute
两个关键 API,分别用来设置和获取窗口属性。我们要做的就是用 Rust 调用这两个 API,实现材质的动态切换。
微软文档链接:
- DwmGetWindowAttribute 函数 (dwmapi.h)
- DwmSetWindowAttribute 函数 (dwmapi.h)
- 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 的前后端通信机制也很方便,让前端能轻松调用底层功能。