<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>Gabriel Ryder&#039;s Blog - Mica</title>
<link>https://www.dooper.top/index.php/tag/Mica/</link>
<atom:link href="https://www.dooper.top/index.php/feed/tag/Mica/" rel="self" type="application/rss+xml" />
<language>zh-CN</language>
<description></description>
<lastBuildDate>Sat, 09 Aug 2025 22:50:00 +0800</lastBuildDate>
<pubDate>Sat, 09 Aug 2025 22:50:00 +0800</pubDate>
<item>
<title>Tauri + Rust 实现 Windows 窗口材质动态切换：从底层 API 到前端交互</title>
<link>https://www.dooper.top/index.php/archives/4/</link>
<guid>https://www.dooper.top/index.php/archives/4/</guid>
<pubDate>Sat, 09 Aug 2025 22:50:00 +0800</pubDate>
<dc:creator>Gabriel Ryder</dc:creator>
<description><![CDATA[注意：这里是 Tauri2最近在做一个 Tauri 桌面应用时，想加个窗口材质切换功能 —— 就是 Windows 11 里的 Mica（云母）、Acrylic（亚克力） 类半透明效果。本来以为...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p>注意：这里是 Tauri2</p><p>最近在做一个 Tauri 桌面应用时，想加个窗口材质切换功能 —— 就是 Windows 11 里的 Mica（云母）、Acrylic（亚克力） 类半透明效果。本来以为 Tauri 自带相关 API，试了才发现：Tauri 确实能在初始化时设置窗口材质，但运行中动态切换是做不到的。没办法，只能自己动手用 Rust 调用系统 API 实现了，这里把过程分享出来，希望能帮到同样踩坑的朋友。</p><h2>先说说 Tauri 在材质设置的局限</h2><p>一开始我查 Tauri 文档，发现可以在 <code>tauri.conf.json</code> 里通过 <code>windows</code> 配置设置 <code>transparent</code> 后，能指定 <code>WindowEffect</code> 来实现材质的更改（还需要设置前端背景为 <code>transparent</code>）。但这些配置都是应用启动时生效的，一旦应用跑起来，就没法通过前端调用修改了。如果用户想在使用中切换不同的窗口效果，原生 API 根本满足不了，所以必须自己写 Rust 后端调用 Windows 的 DWM 接口。</p><h2>核心思路：用 Rust 对接 Windows DWM API</h2><p>Windows 的窗口效果是由<code>Desktop Window Manager（DWM）</code>管理的，它提供了 <code>DwmSetWindowAttribute</code> 和 <code>DwmGetWindowAttribute</code> 两个关键 API，分别用来设置和获取窗口属性。我们要做的就是用 Rust 调用这两个 API，实现材质的动态切换。</p><p>微软文档链接：</p><ol><li><a href="https://learn.microsoft.com/zh-cn/windows/win32/api/dwmapi/nf-dwmapi-dwmgetwindowattribute">DwmGetWindowAttribute 函数 （dwmapi.h）</a></li><li><a href="https://learn.microsoft.com/zh-cn/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute">DwmSetWindowAttribute 函数 （dwmapi.h）</a></li><li><a href="https://learn.microsoft.com/zh-cn/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute">DWMWINDOWATTRIBUTE 枚举 （dwmapi.h）</a></li></ol><h3>第一步：检测系统版本，确定支持的材质</h3><p>不同 Windows 版本支持的材质不一样，比如 Mica 材质只在 Windows 11（Build 22000+）才支持。所以第一步必须先判断系统版本，避免在不支持的系统上显示无效选项。​<br>我用 Rust 的 <code>winreg</code> 库（需要 <code>cargo add winreg</code> 使用）读取注册表来获取系统版本：</p><pre><code class="lang-rust">// 读取注册表中的系统版本号
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;)?;</code></pre><p>拿到build_number后，就能判断哪些材质可用了。比如 Build 22000 以上支持 Mica、Acrylic等，低版本可能只支持默认材质：</p><pre><code class="lang-rust">// 根据版本动态生成支持的材质列表
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; });
}</code></pre><h3>第二步：获取窗口句柄（HWND）​</h3><p>调用 DWM API 必须要有窗口句柄（HWND），Tauri 里可以通过 <code>window_handle()</code> 方法获取，但需要注意：Tauri 的窗口可能有多层父窗口，必须找到最顶层的那个句柄才有效。​<br>我用 Windows API 的 <code>GetParent函数</code> 循环向上查找顶层窗口：</p><pre><code class="lang-rust">// 从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
}</code></pre><p>这里的 <code>unsafe</code> 块是必须的，因为直接操作系统句柄涉及内存安全，Rust 无法在编译时验证，所以需要开发者自己保证逻辑正确。<br>​</p><h3>第三步：实现材质切换功能​</h3><p>有了窗口句柄和系统版本信息，就可以调用 <code>DwmSetWindowAttribute</code> 设置材质了。核心代码如下：</p><pre><code class="lang-rust">// 设置窗口材质
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()
}</code></pre><p>这里的 <code>material参数</code> 对应 <code>DWM</code> 的枚举值：<code>DWMSBT_AUTO（默认）</code>、<code>DWMSBT_MAINWINDOW（Mica）</code>等，前面定义的MaterialInfo结构体就是把这些值和友好名称对应起来，方便前端显示。​</p><pre><code class="lang-rust">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,
}</code></pre><p>对于样式样式枚举 DWM_SYSTEMBACKDROP_TYPE 的解释：</p><ul><li>DWMSBT_AUTO = 0 自动：默认</li><li>DWMSBT_NONE = 1 无背景样式</li><li>DWMSBT_MAINWINDOW = 2 云母(Mica)</li><li>DWMSBT_TRANSIENTWINDOW = 3 亚克力(Acrylic)</li><li>DWMSBT_TABBEDWINDOW = 4 云母（MicaAlt）</li></ul><h2>前端交互：Vue 3 如何调用后端功能​</h2><p>前端部分很简单，主要是用 Tauri 的 <code>invoke函数</code> 调用 Rust 写的命令，然后渲染材质列表和处理选择事件。​前端组件库则使用 <code>shadcn/vue</code></p><h3>初始化时加载材质列表​</h3><p>页面加载时，先调用后端获取支持的材质和当前材质：</p><pre><code class="lang-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];
    });
});</code></pre><h3>用下拉框实现切换交互​</h3><p>我用了一个 <code>Combobox 组件</code> 展示材质列表，用户选择后调用 <code>set_window_material</code> 命令：</p><pre><code class="lang-vue">&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);
        });
};</code></pre><h2>遇到的坑</h2><ul><li>窗口句柄获取错误：一开始直接用了 Tauri 返回的第一层窗口句柄，调用 DWM API 始终失败，后来才发现需要找顶层父窗口，用 <code>GetParent</code> 循环查找才解决，如果不找，则拿到的是 <code>webview</code> 的子窗口句柄。​</li></ul><h2>最后总结​</h2><p>虽然 Tauri 原生不支持动态切换窗口材质，但通过 Rust 调用 Windows 的 DWM API 完全可以实现。整个过程的核心就是：正确获取窗口句柄→根据系统版本提供合适的材质选项→用 DWM API 执行切换。​<br>这个功能写下来最大的感受是：Rust 虽然入门难，但处理系统级 API 时确实安全又高效；而 Tauri 的前后端通信机制也很方便，让前端能轻松调用底层功能。</p>
]]></content:encoded>
<slash:comments>15</slash:comments>
<comments>https://www.dooper.top/index.php/archives/4/#comments</comments>
<wfw:commentRss>https://www.dooper.top/index.php/feed/tag/Mica/</wfw:commentRss>
</item>
</channel>
</rss>