<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel rdf:about="https://www.dooper.top/index.php/feed/rss/tag/Mica/">
<title>Gabriel Ryder&#039;s Blog - Mica</title>
<link>https://www.dooper.top/index.php/tag/Mica/</link>
<description></description>
<items>
<rdf:Seq>
<rdf:li resource="https://www.dooper.top/index.php/archives/4/"/>
</rdf:Seq>
</items>
</channel>
<item rdf:about="https://www.dooper.top/index.php/archives/4/">
<title>Tauri + Rust 实现 Windows 窗口材质动态切换：从底层 API 到前端交互</title>
<link>https://www.dooper.top/index.php/archives/4/</link>
<dc:date>2025-08-09T22:50:00+08:00</dc:date>
<description>注意：这里是 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 APIWindows 的窗口效果是由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(&quot;SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion&quot;)?;
let build_number: String = current_version.get_value(&quot;CurrentBuild&quot;)?;拿到build_number后，就能判断哪些材质可用了。比如 Build 22000 以上支持 Mica、Acrylic等，低版本可能只支持默认材质：// 根据版本动态生成支持的材质列表
if build_number &gt;= 22000 {
    materials.push(MaterialInfo { id: DWMSBT_MAINWINDOW.0, name: &quot;Mica&quot; });
    materials.push(MaterialInfo { id: DWMSBT_TRANSIENTWINDOW.0, name: &quot;Acrylic&quot; });
    materials.push(MaterialInfo { id: DWMSBT_TABBEDWINDOW.0, name: &quot;Tabbed (MicaAlt)&quot; });
}第二步：获取窗口句柄（HWND）​调用 DWM API 必须要有窗口句柄（HWND），Tauri 里可以通过 window_handle() 方法获取，但需要注意：Tauri 的窗口可能有多层父窗口，必须找到最顶层的那个句柄才有效。​我用 Windows API 的 GetParent函数 循环向上查找顶层窗口：// 从Tauri窗口获取HWND
let window = app.get_webview_window(&quot;main&quot;)?;
let handle = window.window_handle()?;
// 转换为Windows原生句柄
match handle.as_raw() {
    RawWindowHandle::Win32(h) =&gt; {
        let mut current = HWND(isize::from(h.hwnd) as *mut c_void);
        // 循环找顶层窗口
        unsafe {
            loop {
                match GetParent(current) {
                    Ok(parent) if !parent.0.is_null() =&gt; current = parent,
                    _ =&gt; break,
                }
            }
        }
        Some(current.0 as isize) // 最终拿到可用的HWND
    }
    _ =&gt; None
}这里的 unsafe 块是必须的，因为直接操作系统句柄涉及内存安全，Rust 无法在编译时验证，所以需要开发者自己保证逻辑正确。​第三步：实现材质切换功能​有了窗口句柄和系统版本信息，就可以调用 DwmSetWindowAttribute 设置材质了。核心代码如下：// 设置窗口材质
pub fn set_window_material(app: &amp;tauri::AppHandle, material: i32) -&gt; 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, // 要设置的属性：系统背景类型
            (&amp;mut material_id as *mut i32).cast::&lt;c_void&gt;(), // 材质ID的指针
            std::mem::size_of::&lt;i32&gt;() 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: &amp;&#039;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(() =&gt; {
    // 获取支持的材质列表
    invoke(&#039;get_supported_window_materials&#039;).then(value =&gt; {
        materials.value = value as MaterialInfo[];
    });
    // 获取当前正在使用的材质
    invoke(&#039;get_current_window_materials&#039;).then(value =&gt; {
        selectedMaterial.value = (value as MaterialInfo[])[0];
    });
});用下拉框实现切换交互​我用了一个 Combobox 组件 展示材质列表，用户选择后调用 set_window_material 命令：&lt;!-- 前端交互组件 --&gt;
&lt;Combobox&gt;
  &lt;ComboboxTrigger as-child&gt;
    &lt;Button variant=&quot;outline&quot;&gt;
      {{ selectedMaterial?.name }}
      &lt;ChevronsUpDown class=&quot;ml-2 h-4 w-4&quot; /&gt;
    &lt;/Button&gt;
  &lt;/ComboboxTrigger&gt;
  &lt;ComboboxList&gt;
    &lt;ComboboxItem 
      v-for=&quot;material in materials&quot; 
      :key=&quot;material.id&quot;
      @select=&quot;() =&gt; setMaterial(material)&quot;
    &gt;
      {{ material.name }}
    &lt;/ComboboxItem&gt;
  &lt;/ComboboxList&gt;
&lt;/Combobox&gt;

// 切换材质的方法
const setMaterial = (material: MaterialInfo) =&gt; {
    invoke(&#039;set_window_material&#039;, { material: material.id })
        .then(() =&gt; {
            selectedMaterial.value = material; // 更新选中状态
        })
        .catch(error =&gt; {
            console.error(&#039;切换失败：&#039;, error);
        });
};遇到的坑窗口句柄获取错误：一开始直接用了 Tauri 返回的第一层窗口句柄，调用 DWM API 始终失败，后来才发现需要找顶层父窗口，用 GetParent 循环查找才解决，如果不找，则拿到的是 webview 的子窗口句柄。​最后总结​虽然 Tauri 原生不支持动态切换窗口材质，但通过 Rust 调用 Windows 的 DWM API 完全可以实现。整个过程的核心就是：正确获取窗口句柄→根据系统版本提供合适的材质选项→用 DWM API 执行切换。​这个功能写下来最大的感受是：Rust 虽然入门难，但处理系统级 API 时确实安全又高效；而 Tauri 的前后端通信机制也很方便，让前端能轻松调用底层功能。</description>
</item>
</rdf:RDF>