<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Fee Space</title>
    <link>https://luanyufei.cn/</link>
    <description>乱与狒的Blog</description>
    <language>zh-CN</language>
    <copyright>All rights reserved 2026, 乱与狒</copyright>
    <lastBuildDate>Sun, 04 Oct 2026 12:49:35 GMT</lastBuildDate>
    <generator>Hexo</generator>
    <image>
      <url>https://www.gravatar.com/avatar/c290a591cab174a68b6fe48e2da30018</url>
      <title>Fee Space</title>
      <link>https://luanyufei.cn/</link>
    </image>
    <atom:link href="https://luanyufei.cn/rss2.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>给 IINA 加上长按倍速：左右键加速慢放，短按快进退</title>
      <link>https://luanyufei.cn/2026/10/04/iina-arrow-key-speed-control-plugin/</link>
      <description>
        <![CDATA[<p>平时在手机或网页上看视频，长按倍速、松开恢复原速用得很顺手。但在 Mac 上用 IINA 时，左右方向键默认只能固定快进&#x2F;快退 5 秒。想快速过一下无聊片段时狂按快进容易跳过关键内容，按住不放又完全没反应。GitHub issue 里早有人提过这类需求（比如 #4437、#4929），但官方一直没有内置。</p>
<p>其实写一个 IINA 原生 JavaScript 插件就能解决：<strong>按住右键 2.0× 加速，按住左键 0.5× 慢放，松手恢复原速；轻敲短按一下依然是快进&#x2F;快退 5 秒</strong>。</p>
<p>把插件代码和几个踩坑点记在这里，换机时直接复制就能用。</p>
<h2 id="为什么不用-mpv-Lua-脚本？"><a href="#为什么不用-mpv-Lua-脚本？" class="headerlink" title="为什么不用 mpv Lua 脚本？"></a>为什么不用 mpv Lua 脚本？</h2><p>IINA 底层是 mpv，写 <code>~/.config/mpv/scripts/</code> 下的 Lua 脚本看起来最省事。但实际测试下来问题不少：</p>
<ul>
<li><strong>松手事件经常丢失</strong>：IINA 的 Cocoa 界面会拦截输入，mpv 动态绑定的按键经常收不到 <code>up</code>（按键松开）事件，导致手都松开了视频还在 2 倍速狂飙；</li>
<li><strong>系统报警音</strong>：长按方向键触发系统键盘连发时，IINA 没法干净地消费底层事件，系统会一直发出“咚咚咚”的无效按键提示音；</li>
<li><strong>调不到原生 UI</strong>：没法使用 IINA 自带的 OSD 提示和偏好设置面板。</li>
</ul>
<p>而从 IINA 1.4.0 开始支持的原生 JavaScript 插件系统，提供了 <code>iina.input.onKeyDown</code> 和 <code>iina.input.onKeyUp</code>，官方文档本身就提到这两个接口适用于“hold to speed &#x2F; hold to seek”，能很好地处理长按拦截和松手还原。</p>
<h2 id="实现细节与踩坑点"><a href="#实现细节与踩坑点" class="headerlink" title="实现细节与踩坑点"></a>实现细节与踩坑点</h2><p>开发过程中有几个值得注意的细节：</p>
<h3 id="1-过滤系统按键自动重复（isRepeat）"><a href="#1-过滤系统按键自动重复（isRepeat）" class="headerlink" title="1. 过滤系统按键自动重复（isRepeat）"></a>1. 过滤系统按键自动重复（isRepeat）</h3><p>在 macOS 里长按一个键，系统会在短暂延迟后以很高的频率连续发送 <code>KeyDown</code>。如果不处理，长按过程中会频繁重置计时器或重复触发命令。</p>
<p>IINA 插件 API 的回调参数里自带了 <code>data.isRepeat</code>，只要判断为重复事件就直接拦截：</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">input.<span class="title function_">onKeyDown</span>(<span class="string">&quot;RIGHT&quot;</span>, <span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (data &amp;&amp; data.<span class="property">isRepeat</span>) <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 直接拦截，忽略后续连发</span></span><br><span class="line">  <span class="keyword">if</span> (rightDown) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  rightDown = <span class="literal">true</span>;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;, input.<span class="property">PRIORITY_HIGH</span>);</span><br></pre></td></tr></table></figure>

<p>通过返回 <code>true</code> 并声明 <code>input.PRIORITY_HIGH</code>，不仅可以过滤自身状态，还能彻底消除系统的“咚咚”报警音。</p>
<h3 id="2-短按快进与长按倍速兼得"><a href="#2-短按快进与长按倍速兼得" class="headerlink" title="2. 短按快进与长按倍速兼得"></a>2. 短按快进与长按倍速兼得</h3><p>日常看视频时，轻敲一下快进几秒微调进度的需求很常见，不能为了长按加速把短按快进搞丢了。</p>
<p>这里加了一个 250ms 的定时器：</p>
<ul>
<li>按下时启动计时；</li>
<li>如果在 250ms 内松开（轻按短敲），清除定时器，正常执行快进&#x2F;快退 5 秒；</li>
<li>如果按住超过 250ms（长按），进入倍速播放，松手时恢复。</li>
</ul>
<h3 id="3-记住原本速度与暂停状态"><a href="#3-记住原本速度与暂停状态" class="headerlink" title="3. 记住原本速度与暂停状态"></a>3. 记住原本速度与暂停状态</h3><ul>
<li><strong>原本速度</strong>：如果视频原本就在 1.25× 播放，松手后应该回到 1.25×，而不是死板地重置为 1.0×。在长按前读一下 <code>mpv.getNumber(&quot;speed&quot;)</code> 存起来即可。</li>
<li><strong>暂停状态</strong>：暂停时长按右键，一般是想快进看看画面。这时候顺便把 <code>pause</code> 设为 <code>false</code>，松手时恢复倍速的同时也自动恢复暂停。</li>
</ul>
<h3 id="4-mpv-command-的参数必须是字符串数组"><a href="#4-mpv-command-的参数必须是字符串数组" class="headerlink" title="4. mpv.command 的参数必须是字符串数组"></a>4. mpv.command 的参数必须是字符串数组</h3><p>写短按快进时，顺手写了 <code>mpv.command(&quot;seek&quot;, 5)</code>，结果长按加减速正常，短按快进却毫无反应。</p>
<p>翻了 API 文档才发现，IINA 插件封装的 <code>mpv.command</code> 参数类型严格要求是 <code>string[]</code>：</p>
<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line">mpv.<span class="title function_">command</span>(<span class="attr">name</span>: <span class="built_in">string</span>, <span class="attr">args</span>: <span class="built_in">string</span>[]): <span class="built_in">void</span></span><br></pre></td></tr></table></figure>

<p>传数字会在 Swift 桥接层直接报错退出。正确的写法必须把秒数转为字符串，并带上相对寻轨参数：</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 快进 5 秒</span></span><br><span class="line">mpv.<span class="title function_">command</span>(<span class="string">&quot;seek&quot;</span>, [<span class="title class_">String</span>(step), <span class="string">&quot;relative&quot;</span>]);</span><br><span class="line"><span class="comment">// 快退 5 秒</span></span><br><span class="line">mpv.<span class="title function_">command</span>(<span class="string">&quot;seek&quot;</span>, [<span class="string">`-<span class="subst">$&#123;step&#125;</span>`</span>, <span class="string">&quot;relative&quot;</span>]);</span><br></pre></td></tr></table></figure>

<h3 id="5-解绑默认的左右键快捷键"><a href="#5-解绑默认的左右键快捷键" class="headerlink" title="5. 解绑默认的左右键快捷键"></a>5. 解绑默认的左右键快捷键</h3><p>IINA 自带的快捷键预设优先级很高，会抢在插件前把左右键给拦截了，导致插件收不到松手事件。</p>
<p>解决方法很简单：在 <code>~/Library/Application Support/com.colliderli.iina/input_conf/</code> 里放一份配置文件（比如 <code>ArrowSpeed.conf</code>），保留其他所有默认按键，只把 <code>RIGHT seek 5</code> 和 <code>LEFT seek -5</code> 注释掉，让左右键完全交给插件处理。</p>
<h2 id="完整源码"><a href="#完整源码" class="headerlink" title="完整源码"></a>完整源码</h2><p>插件由 3 个文件构成，存放在 <code>~/Library/Application Support/com.colliderli.iina/plugins/com.antigravity.iina-arrow-speed.iinaplugin/</code> 目录下。</p>
<h3 id="1-Info-json（清单配置）"><a href="#1-Info-json（清单配置）" class="headerlink" title="1. Info.json（清单配置）"></a>1. Info.json（清单配置）</h3><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Arrow Key Speed Control&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;identifier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;com.antigravity.iina-arrow-speed&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Hold Right Arrow to speed up, Left Arrow to slow down, release to restore original speed. Tap for seek.&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Antigravity&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;entry&quot;</span><span class="punctuation">:</span> <span class="string">&quot;main.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;minApiVersion&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;minIINAVersion&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.4.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;show-osd&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;preferencesPage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;preferences.html&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;preferenceDefaults&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;fastSpeed&quot;</span><span class="punctuation">:</span> <span class="number">2.0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;slowSpeed&quot;</span><span class="punctuation">:</span> <span class="number">0.5</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;holdDelay&quot;</span><span class="punctuation">:</span> <span class="number">250</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;seekStep&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;enableTapSeek&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<h3 id="2-main-js（核心业务脚本）"><a href="#2-main-js（核心业务脚本）" class="headerlink" title="2. main.js（核心业务脚本）"></a>2. main.js（核心业务脚本）</h3><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Arrow Key Speed Control — IINA Plugin</span></span><br><span class="line"><span class="comment">// Hold Rig]]>
      </description>
      <author>noonyjufee@gmail.com (乱与狒)</author>
      <category domain="https://luanyufei.cn/categories/%E5%AE%9E%E7%94%A8%E6%8A%80%E5%B7%A7/">实用技巧</category>
      <category domain="https://luanyufei.cn/tags/macOS/">macOS</category>
      <category domain="https://luanyufei.cn/tags/%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/">折腾记录</category>
      <category domain="https://luanyufei.cn/tags/%E7%94%9F%E4%BA%A7%E5%8A%9B/">生产力</category>
      <pubDate>Sun, 04 Oct 2026 20:25:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>平时在手机或网页上看视频，长按倍速、松开恢复原速用得很顺手。但在 Mac 上用 IINA 时，左右方向键默认只能固定快进&#x2F;快退 5 秒。想快速过一下无聊片段时狂按快进容易跳过关键内容，按住不放又完全没反应。GitHub issue 里早有人提过这类需求（比如 #4437、#4929），但官方一直没有内置。</p><p>其实写一个 IINA 原生 JavaScript 插件就能解决：<strong>按住右键 2.0× 加速，按住左键 0.5× 慢放，松手恢复原速；轻敲短按一下依然是快进&#x2F;快退 5 秒</strong>。</p><p>把插件代码和几个踩坑点记在这里，换机时直接复制就能用。</p><h2 id="为什么不用-mpv-Lua-脚本？"><a href="#为什么不用-mpv-Lua-脚本？" class="headerlink" title="为什么不用 mpv Lua 脚本？"></a>为什么不用 mpv Lua 脚本？</h2><p>IINA 底层是 mpv，写 <code>~/.config/mpv/scripts/</code> 下的 Lua 脚本看起来最省事。但实际测试下来问题不少：</p><ul><li><strong>松手事件经常丢失</strong>：IINA 的 Cocoa 界面会拦截输入，mpv 动态绑定的按键经常收不到 <code>up</code>（按键松开）事件，导致手都松开了视频还在 2 倍速狂飙；</li><li><strong>系统报警音</strong>：长按方向键触发系统键盘连发时，IINA 没法干净地消费底层事件，系统会一直发出“咚咚咚”的无效按键提示音；</li><li><strong>调不到原生 UI</strong>：没法使用 IINA 自带的 OSD 提示和偏好设置面板。</li></ul><p>而从 IINA 1.4.0 开始支持的原生 JavaScript 插件系统，提供了 <code>iina.input.onKeyDown</code> 和 <code>iina.input.onKeyUp</code>，官方文档本身就提到这两个接口适用于“hold to speed &#x2F; hold to seek”，能很好地处理长按拦截和松手还原。</p><h2 id="实现细节与踩坑点"><a href="#实现细节与踩坑点" class="headerlink" title="实现细节与踩坑点"></a>实现细节与踩坑点</h2><p>开发过程中有几个值得注意的细节：</p><h3 id="1-过滤系统按键自动重复（isRepeat）"><a href="#1-过滤系统按键自动重复（isRepeat）" class="headerlink" title="1. 过滤系统按键自动重复（isRepeat）"></a>1. 过滤系统按键自动重复（isRepeat）</h3><p>在 macOS 里长按一个键，系统会在短暂延迟后以很高的频率连续发送 <code>KeyDown</code>。如果不处理，长按过程中会频繁重置计时器或重复触发命令。</p><p>IINA 插件 API 的回调参数里自带了 <code>data.isRepeat</code>，只要判断为重复事件就直接拦截：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">input.<span class="title function_">onKeyDown</span>(<span class="string">&quot;RIGHT&quot;</span>, <span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (data &amp;&amp; data.<span class="property">isRepeat</span>) <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 直接拦截，忽略后续连发</span></span><br><span class="line">  <span class="keyword">if</span> (rightDown) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  rightDown = <span class="literal">true</span>;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;, input.<span class="property">PRIORITY_HIGH</span>);</span><br></pre></td></tr></table></figure><p>通过返回 <code>true</code> 并声明 <code>input.PRIORITY_HIGH</code>，不仅可以过滤自身状态，还能彻底消除系统的“咚咚”报警音。</p><h3 id="2-短按快进与长按倍速兼得"><a href="#2-短按快进与长按倍速兼得" class="headerlink" title="2. 短按快进与长按倍速兼得"></a>2. 短按快进与长按倍速兼得</h3><p>日常看视频时，轻敲一下快进几秒微调进度的需求很常见，不能为了长按加速把短按快进搞丢了。</p><p>这里加了一个 250ms 的定时器：</p><ul><li>按下时启动计时；</li><li>如果在 250ms 内松开（轻按短敲），清除定时器，正常执行快进&#x2F;快退 5 秒；</li><li>如果按住超过 250ms（长按），进入倍速播放，松手时恢复。</li></ul><h3 id="3-记住原本速度与暂停状态"><a href="#3-记住原本速度与暂停状态" class="headerlink" title="3. 记住原本速度与暂停状态"></a>3. 记住原本速度与暂停状态</h3><ul><li><strong>原本速度</strong>：如果视频原本就在 1.25× 播放，松手后应该回到 1.25×，而不是死板地重置为 1.0×。在长按前读一下 <code>mpv.getNumber(&quot;speed&quot;)</code> 存起来即可。</li><li><strong>暂停状态</strong>：暂停时长按右键，一般是想快进看看画面。这时候顺便把 <code>pause</code> 设为 <code>false</code>，松手时恢复倍速的同时也自动恢复暂停。</li></ul><h3 id="4-mpv-command-的参数必须是字符串数组"><a href="#4-mpv-command-的参数必须是字符串数组" class="headerlink" title="4. mpv.command 的参数必须是字符串数组"></a>4. mpv.command 的参数必须是字符串数组</h3><p>写短按快进时，顺手写了 <code>mpv.command(&quot;seek&quot;, 5)</code>，结果长按加减速正常，短按快进却毫无反应。</p><p>翻了 API 文档才发现，IINA 插件封装的 <code>mpv.command</code> 参数类型严格要求是 <code>string[]</code>：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line">mpv.<span class="title function_">command</span>(<span class="attr">name</span>: <span class="built_in">string</span>, <span class="attr">args</span>: <span class="built_in">string</span>[]): <span class="built_in">void</span></span><br></pre></td></tr></table></figure><p>传数字会在 Swift 桥接层直接报错退出。正确的写法必须把秒数转为字符串，并带上相对寻轨参数：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 快进 5 秒</span></span><br><span class="line">mpv.<span class="title function_">command</span>(<span class="string">&quot;seek&quot;</span>, [<span class="title class_">String</span>(step), <span class="string">&quot;relative&quot;</span>]);</span><br><span class="line"><span class="comment">// 快退 5 秒</span></span><br><span class="line">mpv.<span class="title function_">command</span>(<span class="string">&quot;seek&quot;</span>, [<span class="string">`-<span class="subst">$&#123;step&#125;</span>`</span>, <span class="string">&quot;relative&quot;</span>]);</span><br></pre></td></tr></table></figure><h3 id="5-解绑默认的左右键快捷键"><a href="#5-解绑默认的左右键快捷键" class="headerlink" title="5. 解绑默认的左右键快捷键"></a>5. 解绑默认的左右键快捷键</h3><p>IINA 自带的快捷键预设优先级很高，会抢在插件前把左右键给拦截了，导致插件收不到松手事件。</p><p>解决方法很简单：在 <code>~/Library/Application Support/com.colliderli.iina/input_conf/</code> 里放一份配置文件（比如 <code>ArrowSpeed.conf</code>），保留其他所有默认按键，只把 <code>RIGHT seek 5</code> 和 <code>LEFT seek -5</code> 注释掉，让左右键完全交给插件处理。</p><h2 id="完整源码"><a href="#完整源码" class="headerlink" title="完整源码"></a>完整源码</h2><p>插件由 3 个文件构成，存放在 <code>~/Library/Application Support/com.colliderli.iina/plugins/com.antigravity.iina-arrow-speed.iinaplugin/</code> 目录下。</p><h3 id="1-Info-json（清单配置）"><a href="#1-Info-json（清单配置）" class="headerlink" title="1. Info.json（清单配置）"></a>1. Info.json（清单配置）</h3><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Arrow Key Speed Control&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;identifier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;com.antigravity.iina-arrow-speed&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Hold Right Arrow to speed up, Left Arrow to slow down, release to restore original speed. Tap for seek.&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Antigravity&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;entry&quot;</span><span class="punctuation">:</span> <span class="string">&quot;main.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;minApiVersion&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;minIINAVersion&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.4.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;show-osd&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;preferencesPage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;preferences.html&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;preferenceDefaults&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;fastSpeed&quot;</span><span class="punctuation">:</span> <span class="number">2.0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;slowSpeed&quot;</span><span class="punctuation">:</span> <span class="number">0.5</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;holdDelay&quot;</span><span class="punctuation">:</span> <span class="number">250</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;seekStep&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;enableTapSeek&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="2-main-js（核心业务脚本）"><a href="#2-main-js（核心业务脚本）" class="headerlink" title="2. main.js（核心业务脚本）"></a>2. main.js（核心业务脚本）</h3><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Arrow Key Speed Control — IINA Plugin</span></span><br><span class="line"><span class="comment">// Hold Right = Speed Up, Hold Left = Slow Down. Release = Restore Speed. Tap = Seek.</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; core, input, mpv, event, menu, <span class="attr">console</span>: log &#125; = iina;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 读取用户偏好配置（含严格兜底）</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getFastSpeed</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> val = iina.<span class="property">preferences</span>.<span class="title function_">get</span>(<span class="string">&quot;fastSpeed&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> val != <span class="literal">null</span> &amp;&amp; !<span class="built_in">isNaN</span>(val) ? <span class="title class_">Number</span>(val) : <span class="number">2.0</span>;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getSlowSpeed</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> val = iina.<span class="property">preferences</span>.<span class="title function_">get</span>(<span class="string">&quot;slowSpeed&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> val != <span class="literal">null</span> &amp;&amp; !<span class="built_in">isNaN</span>(val) ? <span class="title class_">Number</span>(val) : <span class="number">0.5</span>;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getHoldDelay</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> val = iina.<span class="property">preferences</span>.<span class="title function_">get</span>(<span class="string">&quot;holdDelay&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> val != <span class="literal">null</span> &amp;&amp; !<span class="built_in">isNaN</span>(val) ? <span class="title class_">Number</span>(val) : <span class="number">250</span>;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getSeekStep</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> val = iina.<span class="property">preferences</span>.<span class="title function_">get</span>(<span class="string">&quot;seekStep&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> val != <span class="literal">null</span> &amp;&amp; !<span class="built_in">isNaN</span>(val) ? <span class="title class_">Number</span>(val) : <span class="number">5</span>;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getTapSeek</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> val = iina.<span class="property">preferences</span>.<span class="title function_">get</span>(<span class="string">&quot;enableTapSeek&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> val != <span class="literal">null</span> ? <span class="title class_">Boolean</span>(val) : <span class="literal">true</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 运行时状态</span></span><br><span class="line"><span class="keyword">let</span> rightTimer = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">let</span> leftTimer = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">let</span> isHoldingRight = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">let</span> isHoldingLeft = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">let</span> rightDown = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">let</span> leftDown = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">let</span> savedSpeed = <span class="number">1.0</span>;</span><br><span class="line"><span class="keyword">let</span> wasPausedRight = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">let</span> wasPausedLeft = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 跟踪文件加载与播放基础速度</span></span><br><span class="line">event.<span class="title function_">on</span>(<span class="string">&quot;iina.file-loaded&quot;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  savedSpeed = mpv.<span class="title function_">getNumber</span>(<span class="string">&quot;speed&quot;</span>) || <span class="number">1.0</span>;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">restoreSpeed</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    mpv.<span class="title function_">set</span>(<span class="string">&quot;speed&quot;</span>, savedSpeed);</span><br><span class="line">    core.<span class="title function_">osd</span>(<span class="string">`▶  恢复 <span class="subst">$&#123;savedSpeed&#125;</span>× 正常速度`</span>);</span><br><span class="line">    log.<span class="title function_">log</span>(<span class="string">`[arrow-speed] restored speed to <span class="subst">$&#123;savedSpeed&#125;</span>×`</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    log.<span class="title function_">log</span>(<span class="string">&quot;[arrow-speed] restoreSpeed error: &quot;</span> + e);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ──────────────────────────────────────────────</span></span><br><span class="line"><span class="comment">// 右方向键 (Right Arrow) -&gt; 加速 / 快进</span></span><br><span class="line"><span class="comment">// ──────────────────────────────────────────────</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">handleRightDown</span>(<span class="params">data</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (data &amp;&amp; data.<span class="property">isRepeat</span>) <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// 拦截系统重复触发，防止抖动</span></span><br><span class="line">  <span class="keyword">if</span> (rightDown) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">  rightDown = <span class="literal">true</span>;</span><br><span class="line">  isHoldingRight = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 记录按下前的真实速度（若未处于左键慢放中）</span></span><br><span class="line">  <span class="keyword">if</span> (!isHoldingLeft) &#123;</span><br><span class="line">    savedSpeed = mpv.<span class="title function_">getNumber</span>(<span class="string">&quot;speed&quot;</span>) || <span class="number">1.0</span>;</span><br><span class="line">    wasPausedRight = mpv.<span class="title function_">getFlag</span>(<span class="string">&quot;pause&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> delay = <span class="title function_">getHoldDelay</span>();</span><br><span class="line">  <span class="keyword">if</span> (delay &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">// 零延迟模式：立即加速</span></span><br><span class="line">    isHoldingRight = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">const</span> speed = <span class="title function_">getFastSpeed</span>();</span><br><span class="line">    <span class="keyword">if</span> (wasPausedRight) mpv.<span class="title function_">set</span>(<span class="string">&quot;pause&quot;</span>, <span class="literal">false</span>);</span><br><span class="line">    mpv.<span class="title function_">set</span>(<span class="string">&quot;speed&quot;</span>, speed);</span><br><span class="line">    core.<span class="title function_">osd</span>(<span class="string">`▶▶  <span class="subst">$&#123;speed&#125;</span>× 加速播放`</span>);</span><br><span class="line">    log.<span class="title function_">log</span>(<span class="string">`[arrow-speed] boost to <span class="subst">$&#123;speed&#125;</span>× (zero delay)`</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 延迟判定：超过阈值视为长按加速</span></span><br><span class="line">    rightTimer = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (!rightDown) <span class="keyword">return</span>;</span><br><span class="line">      isHoldingRight = <span class="literal">true</span>;</span><br><span class="line">      <span class="keyword">const</span> speed = <span class="title function_">getFastSpeed</span>();</span><br><span class="line">      <span class="keyword">if</span> (wasPausedRight) mpv.<span class="title function_">set</span>(<span class="string">&quot;pause&quot;</span>, <span class="literal">false</span>);</span><br><span class="line">      mpv.<span class="title function_">set</span>(<span class="string">&quot;speed&quot;</span>, speed);</span><br><span class="line">      core.<span class="title function_">osd</span>(<span class="string">`▶▶  <span class="subst">$&#123;speed&#125;</span>× 加速播放`</span>);</span><br><span class="line">      log.<span class="title function_">log</span>(<span class="string">`[arrow-speed] hold detected -&gt; boost to <span class="subst">$&#123;speed&#125;</span>×`</span>);</span><br><span class="line">    &#125;, delay);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">handleRightUp</span>(<span class="params"></span>) &#123;</span><br><span class="line">  rightDown = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (rightTimer !== <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="built_in">clearTimeout</span>(rightTimer);</span><br><span class="line">    rightTimer = <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (isHoldingRight) &#123;</span><br><span class="line">    <span class="comment">// 结束长按，恢复原本速度与暂停状态</span></span><br><span class="line">    <span class="title function_">restoreSpeed</span>();</span><br><span class="line">    <span class="keyword">if</span> (wasPausedRight) &#123;</span><br><span class="line">      mpv.<span class="title function_">set</span>(<span class="string">&quot;pause&quot;</span>, <span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    isHoldingRight = <span class="literal">false</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="title function_">getTapSeek</span>()) &#123;</span><br><span class="line">    <span class="comment">// 短按：快进对应步长（严格传递字符串数组）</span></span><br><span class="line">    <span class="keyword">const</span> step = <span class="title function_">getSeekStep</span>();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      mpv.<span class="title function_">command</span>(<span class="string">&quot;seek&quot;</span>, [<span class="title class_">String</span>(step), <span class="string">&quot;relative&quot;</span>]);</span><br><span class="line">      core.<span class="title function_">osd</span>(<span class="string">`▶▶ 快进 +<span class="subst">$&#123;step&#125;</span>s`</span>);</span><br><span class="line">      log.<span class="title function_">log</span>(<span class="string">`[arrow-speed] tap seek executed: +<span class="subst">$&#123;step&#125;</span>s`</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      log.<span class="title function_">log</span>(<span class="string">`[arrow-speed] tap seek failed: `</span> + e);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ──────────────────────────────────────────────</span></span><br><span class="line"><span class="comment">// 左方向键 (Left Arrow) -&gt; 慢放 / 快退</span></span><br><span class="line"><span class="comment">// ──────────────────────────────────────────────</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">handleLeftDown</span>(<span class="params">data</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (data &amp;&amp; data.<span class="property">isRepeat</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  <span class="keyword">if</span> (leftDown) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">  leftDown = <span class="literal">true</span>;</span><br><span class="line">  isHoldingLeft = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!isHoldingRight) &#123;</span><br><span class="line">    savedSpeed = mpv.<span class="title function_">getNumber</span>(<span class="string">&quot;speed&quot;</span>) || <span class="number">1.0</span>;</span><br><span class="line">    wasPausedLeft = mpv.<span class="title function_">getFlag</span>(<span class="string">&quot;pause&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> delay = <span class="title function_">getHoldDelay</span>();</span><br><span class="line">  <span class="keyword">if</span> (delay &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">    isHoldingLeft = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">const</span> speed = <span class="title function_">getSlowSpeed</span>();</span><br><span class="line">    <span class="keyword">if</span> (wasPausedLeft) mpv.<span class="title function_">set</span>(<span class="string">&quot;pause&quot;</span>, <span class="literal">false</span>);</span><br><span class="line">    mpv.<span class="title function_">set</span>(<span class="string">&quot;speed&quot;</span>, speed);</span><br><span class="line">    core.<span class="title function_">osd</span>(<span class="string">`◀◀  <span class="subst">$&#123;speed&#125;</span>× 慢放播放`</span>);</span><br><span class="line">    log.<span class="title function_">log</span>(<span class="string">`[arrow-speed] slow down to <span class="subst">$&#123;speed&#125;</span>× (zero delay)`</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    leftTimer = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (!leftDown) <span class="keyword">return</span>;</span><br><span class="line">      isHoldingLeft = <span class="literal">true</span>;</span><br><span class="line">      <span class="keyword">const</span> speed = <span class="title function_">getSlowSpeed</span>();</span><br><span class="line">      <span class="keyword">if</span> (wasPausedLeft) mpv.<span class="title function_">set</span>(<span class="string">&quot;pause&quot;</span>, <span class="literal">false</span>);</span><br><span class="line">      mpv.<span class="title function_">set</span>(<span class="string">&quot;speed&quot;</span>, speed);</span><br><span class="line">      core.<span class="title function_">osd</span>(<span class="string">`◀◀  <span class="subst">$&#123;speed&#125;</span>× 慢放播放`</span>);</span><br><span class="line">      log.<span class="title function_">log</span>(<span class="string">`[arrow-speed] hold detected -&gt; slow down to <span class="subst">$&#123;speed&#125;</span>×`</span>);</span><br><span class="line">    &#125;, delay);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">handleLeftUp</span>(<span class="params"></span>) &#123;</span><br><span class="line">  leftDown = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (leftTimer !== <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="built_in">clearTimeout</span>(leftTimer);</span><br><span class="line">    leftTimer = <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (isHoldingLeft) &#123;</span><br><span class="line">    <span class="title function_">restoreSpeed</span>();</span><br><span class="line">    <span class="keyword">if</span> (wasPausedLeft) &#123;</span><br><span class="line">      mpv.<span class="title function_">set</span>(<span class="string">&quot;pause&quot;</span>, <span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    isHoldingLeft = <span class="literal">false</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="title function_">getTapSeek</span>()) &#123;</span><br><span class="line">    <span class="keyword">const</span> step = <span class="title function_">getSeekStep</span>();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      mpv.<span class="title function_">command</span>(<span class="string">&quot;seek&quot;</span>, [<span class="string">`-<span class="subst">$&#123;step&#125;</span>`</span>, <span class="string">&quot;relative&quot;</span>]);</span><br><span class="line">      core.<span class="title function_">osd</span>(<span class="string">`◀◀ 快退 -<span class="subst">$&#123;step&#125;</span>s`</span>);</span><br><span class="line">      log.<span class="title function_">log</span>(<span class="string">`[arrow-speed] tap seek executed: -<span class="subst">$&#123;step&#125;</span>s`</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      log.<span class="title function_">log</span>(<span class="string">`[arrow-speed] tap seek failed: `</span> + e);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注册监听器（大写 mpv key code，高优先级拦截）</span></span><br><span class="line">input.<span class="title function_">onKeyDown</span>(<span class="string">&quot;RIGHT&quot;</span>, handleRightDown, input.<span class="property">PRIORITY_HIGH</span>);</span><br><span class="line">input.<span class="title function_">onKeyUp</span>(<span class="string">&quot;RIGHT&quot;</span>, handleRightUp, input.<span class="property">PRIORITY_HIGH</span>);</span><br><span class="line"></span><br><span class="line">input.<span class="title function_">onKeyDown</span>(<span class="string">&quot;LEFT&quot;</span>, handleLeftDown, input.<span class="property">PRIORITY_HIGH</span>);</span><br><span class="line">input.<span class="title function_">onKeyUp</span>(<span class="string">&quot;LEFT&quot;</span>, handleLeftUp, input.<span class="property">PRIORITY_HIGH</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ──────────────────────────────────────────────</span></span><br><span class="line"><span class="comment">// 快捷菜单栏支持（在播放中方便随时切换常用倍速）</span></span><br><span class="line"><span class="comment">// ──────────────────────────────────────────────</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">FAST_SPEED_OPTIONS</span> = [<span class="number">1.5</span>, <span class="number">2.0</span>, <span class="number">2.5</span>, <span class="number">3.0</span>, <span class="number">4.0</span>];</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">SLOW_SPEED_OPTIONS</span> = [<span class="number">0.25</span>, <span class="number">0.5</span>, <span class="number">0.75</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">buildPluginMenu</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    menu.<span class="title function_">removeAllItems</span>();</span><br><span class="line">    <span class="keyword">const</span> currentFast = <span class="title function_">getFastSpeed</span>();</span><br><span class="line">    <span class="keyword">const</span> currentSlow = <span class="title function_">getSlowSpeed</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> fastMenu = menu.<span class="title function_">item</span>(<span class="string">`右键加速 (<span class="subst">$&#123;currentFast&#125;</span>×)`</span>, <span class="literal">null</span>);</span><br><span class="line">    <span class="variable constant_">FAST_SPEED_OPTIONS</span>.<span class="title function_">forEach</span>(<span class="function">(<span class="params">spd</span>) =&gt;</span> &#123;</span><br><span class="line">      fastMenu.<span class="title function_">addSubMenuItem</span>(</span><br><span class="line">        menu.<span class="title function_">item</span>(<span class="string">`<span class="subst">$&#123;spd&#125;</span>×`</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">          iina.<span class="property">preferences</span>.<span class="title function_">set</span>(<span class="string">&quot;fastSpeed&quot;</span>, spd);</span><br><span class="line">          iina.<span class="property">preferences</span>.<span class="title function_">persist</span>();</span><br><span class="line">          core.<span class="title function_">osd</span>(<span class="string">`右键加速已设为: <span class="subst">$&#123;spd&#125;</span>×`</span>);</span><br><span class="line">          <span class="title function_">buildPluginMenu</span>();</span><br><span class="line">        &#125;, &#123; <span class="attr">selected</span>: spd === currentFast &#125;)</span><br><span class="line">      );</span><br><span class="line">    &#125;);</span><br><span class="line">    menu.<span class="title function_">addItem</span>(fastMenu);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> slowMenu = menu.<span class="title function_">item</span>(<span class="string">`左键慢放 (<span class="subst">$&#123;currentSlow&#125;</span>×)`</span>, <span class="literal">null</span>);</span><br><span class="line">    <span class="variable constant_">SLOW_SPEED_OPTIONS</span>.<span class="title function_">forEach</span>(<span class="function">(<span class="params">spd</span>) =&gt;</span> &#123;</span><br><span class="line">      slowMenu.<span class="title function_">addSubMenuItem</span>(</span><br><span class="line">        menu.<span class="title function_">item</span>(<span class="string">`<span class="subst">$&#123;spd&#125;</span>×`</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">          iina.<span class="property">preferences</span>.<span class="title function_">set</span>(<span class="string">&quot;slowSpeed&quot;</span>, spd);</span><br><span class="line">          iina.<span class="property">preferences</span>.<span class="title function_">persist</span>();</span><br><span class="line">          core.<span class="title function_">osd</span>(<span class="string">`左键慢放已设为: <span class="subst">$&#123;spd&#125;</span>×`</span>);</span><br><span class="line">          <span class="title function_">buildPluginMenu</span>();</span><br><span class="line">        &#125;, &#123; <span class="attr">selected</span>: spd === currentSlow &#125;)</span><br><span class="line">      );</span><br><span class="line">    &#125;);</span><br><span class="line">    menu.<span class="title function_">addItem</span>(slowMenu);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    log.<span class="title function_">log</span>(<span class="string">&quot;[arrow-speed] build menu error: &quot;</span> + err);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">buildPluginMenu</span>();</span><br><span class="line">log.<span class="title function_">log</span>(<span class="string">&quot;[arrow-speed] plugin loaded and listeners initialized&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="3-preferences-html（偏好设置面板）"><a href="#3-preferences-html（偏好设置面板）" class="headerlink" title="3. preferences.html（偏好设置面板）"></a>3. preferences.html（偏好设置面板）</h3><p>可以在 IINA 偏好设置中以图形化滑块调节数值，适配系统的暗色与浅色外观：</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">    <span class="selector-pseudo">:root</span> &#123;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">color-scheme</span>: light dark;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">body</span> &#123;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">padding</span>: <span class="number">18px</span> <span class="number">24px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">font-size</span>: <span class="number">13px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">font-family</span>: -apple-system, BlinkMacSystemFont, <span class="string">&quot;Segoe UI&quot;</span>, Roboto, sans-serif;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">color</span>: -apple-system-label;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">user-select</span>: none;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.group-title</span> &#123;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">font-weight</span>: <span class="number">600</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">margin-bottom</span>: <span class="number">12px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">font-size</span>: <span class="number">12px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">text-transform</span>: uppercase;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">letter-spacing</span>: <span class="number">0.5px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">opacity</span>: <span class="number">0.7</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.row</span> &#123;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">justify-content</span>: space-between;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">margin-bottom</span>: <span class="number">14px</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">label</span> &#123;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">width</span>: <span class="number">170px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">flex-shrink</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-tag">input</span><span class="selector-attr">[type=<span class="string">&quot;range&quot;</span>]</span> &#123;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">flex</span>: <span class="number">1</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">margin</span>: <span class="number">0</span> <span class="number">12px</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.val</span> &#123;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">width</span>: <span class="number">60px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">text-align</span>: right;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">font-weight</span>: <span class="number">600</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">font-variant-numeric</span>: tabular-nums;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.check-row</span> &#123;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">margin</span>: <span class="number">12px</span> <span class="number">0</span> <span class="number">14px</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.check-row</span> <span class="selector-tag">input</span> &#123;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">margin-right</span>: <span class="number">8px</span>;</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.hint</span> &#123;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">font-size</span>: <span class="number">11px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">opacity</span>: <span class="number">0.65</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">line-height</span>: <span class="number">1.4</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">margin-top</span>: <span class="number">14px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">padding-top</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">border-top</span>: <span class="number">1px</span> solid <span class="built_in">rgba</span>(<span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">0.2</span>);</span></span><br><span class="line"><span class="language-css">    &#125;</span></span><br><span class="line"><span class="language-css">  </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;group-title&quot;</span>&gt;</span>长按速度控制<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;row&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;fastSpeed&quot;</span>&gt;</span>右键长按加速倍速：<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;range&quot;</span> <span class="attr">id</span>=<span class="string">&quot;fastSpeed&quot;</span> <span class="attr">min</span>=<span class="string">&quot;1.25&quot;</span> <span class="attr">max</span>=<span class="string">&quot;5.0&quot;</span> <span class="attr">step</span>=<span class="string">&quot;0.25&quot;</span> <span class="attr">value</span>=<span class="string">&quot;2.0&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">&quot;val&quot;</span> <span class="attr">id</span>=<span class="string">&quot;fastSpeedDisplay&quot;</span>&gt;</span>2.0×<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;row&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;slowSpeed&quot;</span>&gt;</span>左键长按慢放倍速：<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;range&quot;</span> <span class="attr">id</span>=<span class="string">&quot;slowSpeed&quot;</span> <span class="attr">min</span>=<span class="string">&quot;0.1&quot;</span> <span class="attr">max</span>=<span class="string">&quot;0.9&quot;</span> <span class="attr">step</span>=<span class="string">&quot;0.05&quot;</span> <span class="attr">value</span>=<span class="string">&quot;0.5&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">&quot;val&quot;</span> <span class="attr">id</span>=<span class="string">&quot;slowSpeedDisplay&quot;</span>&gt;</span>0.50×<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;group-title&quot;</span> <span class="attr">style</span>=<span class="string">&quot;margin-top: 18px;&quot;</span>&gt;</span>触发与短按行为<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;row&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;holdDelay&quot;</span>&gt;</span>长按判定延迟 (ms)：<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;range&quot;</span> <span class="attr">id</span>=<span class="string">&quot;holdDelay&quot;</span> <span class="attr">min</span>=<span class="string">&quot;0&quot;</span> <span class="attr">max</span>=<span class="string">&quot;600&quot;</span> <span class="attr">step</span>=<span class="string">&quot;25&quot;</span> <span class="attr">value</span>=<span class="string">&quot;250&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">&quot;val&quot;</span> <span class="attr">id</span>=<span class="string">&quot;holdDelayDisplay&quot;</span>&gt;</span>250ms<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;row&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;seekStep&quot;</span>&gt;</span>短按快进/快退步长 (秒)：<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;range&quot;</span> <span class="attr">id</span>=<span class="string">&quot;seekStep&quot;</span> <span class="attr">min</span>=<span class="string">&quot;1&quot;</span> <span class="attr">max</span>=<span class="string">&quot;30&quot;</span> <span class="attr">step</span>=<span class="string">&quot;1&quot;</span> <span class="attr">value</span>=<span class="string">&quot;5&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">&quot;val&quot;</span> <span class="attr">id</span>=<span class="string">&quot;seekStepDisplay&quot;</span>&gt;</span>5s<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;check-row&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;checkbox&quot;</span> <span class="attr">id</span>=<span class="string">&quot;enableTapSeek&quot;</span> <span class="attr">checked</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;enableTapSeek&quot;</span> <span class="attr">style</span>=<span class="string">&quot;width: auto;&quot;</span>&gt;</span>短按轻点保留快进/快退功能<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;hint&quot;</span>&gt;</span></span><br><span class="line">    💡 提示：按住右键加速、按住左键慢放，松手立即恢复原速。轻敲方向键则快进/快退 5 秒。若想纯按住加速（无快进延迟），可将判定延迟拉至 0ms。</span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> prefs = (<span class="variable language_">window</span>.<span class="property">iina</span> || iina)?.<span class="property">preferences</span>;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> fastSpeed = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;fastSpeed&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> fastDisp = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;fastSpeedDisplay&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> slowSpeed = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;slowSpeed&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> slowDisp = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;slowSpeedDisplay&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> holdDelay = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;holdDelay&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> holdDisp = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;holdDelayDisplay&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> seekStep = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;seekStep&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> seekDisp = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;seekStepDisplay&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> enableTapSeek = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;enableTapSeek&quot;</span>);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">function</span> <span class="title function_">update</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      fastDisp.<span class="property">textContent</span> = <span class="built_in">parseFloat</span>(fastSpeed.<span class="property">value</span>).<span class="title function_">toFixed</span>(<span class="number">2</span>).<span class="title function_">replace</span>(<span class="regexp">/\.?0+$/</span>, <span class="string">&quot;&quot;</span>) + <span class="string">&quot;×&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">      slowDisp.<span class="property">textContent</span> = <span class="built_in">parseFloat</span>(slowSpeed.<span class="property">value</span>).<span class="title function_">toFixed</span>(<span class="number">2</span>) + <span class="string">&quot;×&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">      holdDisp.<span class="property">textContent</span> = <span class="built_in">parseInt</span>(holdDelay.<span class="property">value</span>) + <span class="string">&quot;ms&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">      seekDisp.<span class="property">textContent</span> = <span class="built_in">parseInt</span>(seekStep.<span class="property">value</span>) + <span class="string">&quot;s&quot;</span>;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">try</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (prefs) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> fs = prefs.<span class="title function_">get</span>(<span class="string">&quot;fastSpeed&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> ss = prefs.<span class="title function_">get</span>(<span class="string">&quot;slowSpeed&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> hd = prefs.<span class="title function_">get</span>(<span class="string">&quot;holdDelay&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> st = prefs.<span class="title function_">get</span>(<span class="string">&quot;seekStep&quot;</span>);</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> et = prefs.<span class="title function_">get</span>(<span class="string">&quot;enableTapSeek&quot;</span>);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">if</span> (fs != <span class="literal">null</span>) fastSpeed.<span class="property">value</span> = fs;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">if</span> (ss != <span class="literal">null</span>) slowSpeed.<span class="property">value</span> = ss;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">if</span> (hd != <span class="literal">null</span>) holdDelay.<span class="property">value</span> = hd;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">if</span> (st != <span class="literal">null</span>) seekStep.<span class="property">value</span> = st;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">if</span> (et != <span class="literal">null</span>) enableTapSeek.<span class="property">checked</span> = <span class="title class_">Boolean</span>(et);</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125; <span class="keyword">catch</span> (e) &#123;&#125;</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">update</span>();</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">function</span> <span class="title function_">save</span>(<span class="params">key, val</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">try</span> &#123;</span></span><br><span class="line"><span class="language-javascript">        prefs?.<span class="title function_">set</span>(key, val);</span></span><br><span class="line"><span class="language-javascript">        prefs?.<span class="title function_">persist</span>();</span></span><br><span class="line"><span class="language-javascript">      &#125; <span class="keyword">catch</span> (e) &#123;&#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    fastSpeed.<span class="title function_">addEventListener</span>(<span class="string">&quot;input&quot;</span>, <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">update</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">save</span>(<span class="string">&quot;fastSpeed&quot;</span>, <span class="built_in">parseFloat</span>(fastSpeed.<span class="property">value</span>));</span></span><br><span class="line"><span class="language-javascript">    &#125;);</span></span><br><span class="line"><span class="language-javascript">    slowSpeed.<span class="title function_">addEventListener</span>(<span class="string">&quot;input&quot;</span>, <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">update</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">save</span>(<span class="string">&quot;slowSpeed&quot;</span>, <span class="built_in">parseFloat</span>(slowSpeed.<span class="property">value</span>));</span></span><br><span class="line"><span class="language-javascript">    &#125;);</span></span><br><span class="line"><span class="language-javascript">    holdDelay.<span class="title function_">addEventListener</span>(<span class="string">&quot;input&quot;</span>, <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">update</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">save</span>(<span class="string">&quot;holdDelay&quot;</span>, <span class="built_in">parseInt</span>(holdDelay.<span class="property">value</span>));</span></span><br><span class="line"><span class="language-javascript">    &#125;);</span></span><br><span class="line"><span class="language-javascript">    seekStep.<span class="title function_">addEventListener</span>(<span class="string">&quot;input&quot;</span>, <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">update</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">save</span>(<span class="string">&quot;seekStep&quot;</span>, <span class="built_in">parseInt</span>(seekStep.<span class="property">value</span>));</span></span><br><span class="line"><span class="language-javascript">    &#125;);</span></span><br><span class="line"><span class="language-javascript">    enableTapSeek.<span class="title function_">addEventListener</span>(<span class="string">&quot;change&quot;</span>, <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">save</span>(<span class="string">&quot;enableTapSeek&quot;</span>, enableTapSeek.<span class="property">checked</span>);</span></span><br><span class="line"><span class="language-javascript">    &#125;);</span></span><br><span class="line"><span class="language-javascript">  </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="一键配置与生效"><a href="#一键配置与生效" class="headerlink" title="一键配置与生效"></a>一键配置与生效</h2><p>为确保 IINA 的默认快捷键不抢占事件，我们在 <code>input_conf</code> 目录下生成专属配置，并通过 <code>defaults</code> 激活插件与配置文件：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 复制官方默认快捷键，并解绑左右方向键</span></span><br><span class="line">CONF_DIR=<span class="string">&quot;<span class="variable">$HOME</span>/Library/Application Support/com.colliderli.iina/input_conf&quot;</span></span><br><span class="line"><span class="built_in">mkdir</span> -p <span class="string">&quot;<span class="variable">$CONF_DIR</span>&quot;</span></span><br><span class="line">sed -e <span class="string">&#x27;s/^RIGHT seek  5/# RIGHT seek  5/&#x27;</span> \</span><br><span class="line">    -e <span class="string">&#x27;s/^LEFT  seek -5/# LEFT  seek -5/&#x27;</span> \</span><br><span class="line">    /Applications/IINA.app/Contents/Resources/config/iina-default-input.conf \</span><br><span class="line">    &gt; <span class="string">&quot;<span class="variable">$CONF_DIR</span>/ArrowSpeed.conf&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 启用该快捷键预设与插件</span></span><br><span class="line">defaults write com.colliderli.iina currentInputConfigName <span class="string">&quot;ArrowSpeed&quot;</span></span><br><span class="line">defaults write com.colliderli.iina <span class="string">&quot;PluginEnabled.com.antigravity.iina-arrow-speed&quot;</span> -bool <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>配置完成后重启 IINA（<code>Cmd + Q</code> 彻底退出后再打开），长按倍速与短按快进即可正常工作。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Ubuntu 26.04 (Apple Silicon / Parallels) 配置与优化记录</title>
      <link>https://luanyufei.cn/2026/08/26/ubuntu-26-04-parallels-optimization/</link>
      <description>
        <![CDATA[<p>很久以前我装过一次 Ubuntu，当时各种依赖、字体和驱动配置实在太折腾，没用多久就放弃了。现在有了 AI Agent，很多底层的调试和脚本一句话就能搞定，整个折腾流程顺畅了许多。</p>
<p>这篇文档是我在 Apple Silicon Mac 的 Parallels 虚拟机里调教 Ubuntu 26.04 的完整记录。内容涵盖了屏幕高刷锁定、GNOME 现代主题架构与深色模式避坑、全透明悬浮 Dock、输入法迁移、终端与浏览器调优、系统深度瘦身与去遥测（彻底剥离 Snap、接入 Flatpak）、常用原生工具及系统底层服务，留作以后换机或在实体机上装 Linux 时的速查参考。</p>
<h2 id="1-屏幕分辨率与高刷锁定（2560×1440-180Hz）"><a href="#1-屏幕分辨率与高刷锁定（2560×1440-180Hz）" class="headerlink" title="1. 屏幕分辨率与高刷锁定（2560×1440 @ 180Hz）"></a>1. 屏幕分辨率与高刷锁定（2560×1440 @ 180Hz）</h2><h3 id="遇到问题"><a href="#遇到问题" class="headerlink" title="遇到问题"></a>遇到问题</h3><ol>
<li><strong>分辨率与刷新率缺失</strong>：系统初始缺少 2K 16:9（2560×1440）及 180Hz 高刷新率选项。</li>
<li><strong>分辨率被动态重置</strong>：Parallels Tools 后台进程在拖动或缩放 Mac 窗口时会触发动态分辨率检测，将虚拟机强行改回当前窗口尺寸并将刷新率重置为 60Hz。</li>
</ol>
<h3 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h3><ul>
<li><strong>内核 DRM 注入</strong>：在 <code>/etc/default/grub.d/99_resolution.cfg</code> 中加入参数 <code>video=Virtual-1:2560x1440@180 video=2560x1440@180</code>，执行 <code>sudo update-grub</code>，让内核在引导时生成对应的显示模式。</li>
<li><strong>阻止动态分辨率篡改</strong>：将系统二进制 <code>/usr/bin/prlcc</code> 替换为包装脚本，移除 DynRes 分辨率监听逻辑，同时保留剪贴板同步（<code>prlcp</code>）、文件拖放（<code>prldnd</code>）和共享目录（<code>prl_fsd</code>）。</li>
<li><strong>同步显示管理器配置</strong>：将显示配置文件保存到 <code>~/.config/monitors.xml</code>，并同步复制至 <code>/var/lib/gdm3/.config/monitors.xml</code>，确保从 GDM3 登录界面到进入桌面全程锁定该配置。</li>
</ul>
<h2 id="2-GNOME-42-主题架构与深色模式避坑"><a href="#2-GNOME-42-主题架构与深色模式避坑" class="headerlink" title="2. GNOME 42+ 主题架构与深色模式避坑"></a>2. GNOME 42+ 主题架构与深色模式避坑</h2><p>在现代 GNOME（GNOME 42+）下给 Ubuntu 换主题，很容易遇到各种莫名其妙的 bug：比如切了深色模式设置窗口依然是白色、换了第三方图标只要按一下深色按钮就自动变回默认图标、Brave 里的 Dark Reader 变黑两秒后又自己跳回白色等。</p>
<p>这些坑的根源在于现代 GNOME 的底层架构分层。</p>
<h3 id="2-1-现代-GNOME-的四层界面架构"><a href="#2-1-现代-GNOME-的四层界面架构" class="headerlink" title="2.1 现代 GNOME 的四层界面架构"></a>2.1 现代 GNOME 的四层界面架构</h3><p>目前的 Linux 桌面由四个相互独立的系统组成：</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">               ┌────────────────────────────────────────┐</span><br><span class="line">               │         你的应用界面 (Applications)       │</span><br><span class="line">               └───────────────────┬────────────────────┘</span><br><span class="line">                                   │</span><br><span class="line">         ┌─────────────────────────┴─────────────────────────┐</span><br><span class="line">         ▼                                                   ▼</span><br><span class="line">┌─────────────────────────────────┐         ┌─────────────────────────────────┐</span><br><span class="line">│     现代应用 (GTK4 + Libadwaita)   │         │     传统应用 (GTK3 / Legacy)     │</span><br><span class="line">│   (如: 系统设置、文件管理器、文本编辑器)   │         │    (如: GIMP、各种第三方老应用)     │</span><br><span class="line">└────────────────┬────────────────┘         └────────────────┬────────────────┘</span><br><span class="line">                 │                                           │</span><br><span class="line">   ┌─────────────┴─────────────┐                             │</span><br><span class="line">   ▼                           ▼                             ▼</span><br><span class="line">【Libadwaita 原生引擎】   【~/.config/gtk-4.0/】       【~/.themes/ 或 /usr/share/】</span><br><span class="line"> • 内置深浅色样式         • 用户强制覆盖层              • 读取 gsettings gtk-theme</span><br><span class="line"> • 官方标准架构           (第三方主题强行注入点)        (Yaru / Orchis 在此生效)</span><br><span class="line"> • 0ms 实时深浅热重载     • 静态文件，无法热重载</span><br></pre></td></tr></table></figure>

<ul>
<li><strong>Libadwaita (GTK4)</strong>：GNOME 官方为了防止第三方主题破坏控件结构，完全废弃了从 <code>~/.themes/</code> 读取外部主题的做法。深浅色样式直接以内置资源形式编译在底层，支持毫秒级的动态切换。</li>
<li><strong>第三方主题（如 Orchis）</strong>：为了强行修改 Libadwaita 应用，安装脚本通常会把生成的静态 CSS 复制到 <code>~/.config/gtk-4.0/gtk.css</code>。这是 GTK4 留给用户的最高优先级覆盖层，一旦写入，就会直接废掉 Libadwaita 原生的动态深浅色切换能力。</li>
<li><strong>GNOME Shell UI</strong>：独立于应用窗口，专门管顶部状态栏、右上角快捷控制面板（Quick Settings）、通知中心和日历，由 <code>user-theme</code> 扩展独立控制。</li>
<li><strong>Electron 与 Chromium &#x2F; Brave</strong>：依赖 Linux 的 XDG Desktop Portal（通过 D-Bus <code>org.freedesktop.portal.Settings</code> 广播）来接收操作系统的 <code>color-scheme</code> 切换信号。</li>
</ul>
<h3 id="2-2-五大常见故障与深度原理复盘"><a href="#2-2-五大常见故障与深度原理复盘" class="headerlink" title="2.2 五大常见故障与深度原理复盘"></a>2.2 五大常见故障与深度原理复盘</h3><h4 id="1-系统设了-Dark-Mode，系统设置窗口依然是白色？"><a href="#1-系统设了-Dark-Mode，系统设置窗口依然是白色？" class="headerlink" title="1. 系统设了 Dark Mode，系统设置窗口依然是白色？"></a>1. 系统设了 Dark Mode，系统设置窗口依然是白色？</h4><ul>
<li><strong>根本原因</strong>：之前安装 Orchis 等第三方主题时，浅色样式的 CSS 文件被写入了 <code>~/.config/gtk-4.0/gtk.css</code>。GTK4 启动时无条件优先加载这个静态文件里的背景定义，导致设置窗口被强行涂白。</li>
</ul>
<h4 id="2-为什么-Electron-能秒切深浅色，原生设置和浏览器却不行？"><a href="#2-为什么-Electron-能秒切深浅色，原生设置和浏览器却不行？" class="headerlink" title="2. 为什么 Electron 能秒切深浅色，原生设置和浏览器却不行？"></a>2. 为什么 Electron 能秒切深浅色，原生设置和浏览器却不行？</h4><ul>
<li><strong>根本原因</strong>：Electron 实时监听 D-Bus Portal 广播信号，收到后在内存里即时重算 CSS <code>@media (prefers-color-scheme: dark)</code>。而 GTK4 外部覆盖文件只在启动时读取一次磁盘，无法实时热重绘，必须关闭窗口重开才能生效。</li>
</ul>
<h4 id="3-Brave-里的-Dark-Reader-变黑-2-秒后，自动弹回浅色？"><a href="#3-Brave-里的-Dark-Reader-变黑-2-秒后，自动弹回浅色？" class="headerlink" title="3. Brave 里的 Dark Reader 变黑 2 秒后，自动弹回浅色？"></a>3. Brave 里的 Dark Reader 变黑 2 秒后，自动弹回浅色？</h4><ul>
<li><strong>根本原因（信号竞争冲突）</strong>：<ol>
<li>点击系统深色模式时，GNOME 先通过 D-Bus 广播 <code>color-scheme = 1 (Dark)</code>，Dark Reader 瞬间生效变黑；</li>
<li>如果 Brave 开启了 <code>Use GTK</code>，Chromium 底层模块在 2 秒后完成 GTK3 主题色彩重算，误判为浅色，强行推翻了之前的 Portal 信号，向页面发送 <code>prefers-color-scheme: light</code>；</li>
<li>Dark Reader 收到错误的覆盖信号，误以为系统切回了浅色，于是自动关闭了深色渲染。</li>
</ol>
</li>
</ul>
<h4 id="4-右上角控制中心与系统弹窗依然是白色？"><a href="#4-右上角控制中心与系统弹窗依然是白色？" class="headerlink" title="4. 右上角控制中心与系统弹窗依然是白色？"></a>4. 右上角控制中心与系统弹窗依然是白色？</h4><ul>
<li><strong>根本原因</strong>：桌面的系统弹窗属于 GNOME Shell UI，由 <code>user-theme</code> 扩展管理。如果之前 Shell 主题被固定指定为了浅色主题（如 <code>Orchis-Light</code>），桌面弹窗就会被强制固定为浅色。</li>
</ul>
<h4 id="5-换了第三方图标（如-Tela），一按深色模式又变回官方图标？"><a href="#5-换了第三方图标（如-Tela），一按深色模式又变回官方图标？" class="headerlink" title="5. 换了第三方图标（如 Tela），一按深色模式又变回官方图标？"></a>5. 换了第三方图标（如 Tela），一按深色模式又变回官方图标？</h4><ul>
<li><strong>根本原因</strong>：Ubuntu 的「系统设置 ➔ 外观」面板内置了绑定逻辑。只要在图形界面里点击了 Dark &#x2F; Default 或切换了颜色圆圈，Ubuntu 就会自动触发 <code>gsettings set icon-theme Yaru-&lt;color&gt;-dark</code>，强行重置图标。</li>
</ul>
<h3 id="2-3-终极推荐配置：纯净热重载与图标防篡改"><a href="#2-3-终极推荐配置：纯净热重载与图标防篡改" class="headerlink" title="2.3 终极推荐配置：纯净热重载与图标防篡改"></a>2.3 终极推荐配置：纯净热重载与图标防篡改</h3><p>要实现<strong>零 Bug、免重启、毫秒级平滑热重载、图标不被系统重置</strong>，按照以下四步配置：</p>
<h4 id="1-净化-GTK4-Libadwaita（恢复原生热重载）"><a href="#1-净化-GTK4-Libadwaita（恢复原生热重载）" class="headerlink" title="1. 净化 GTK4 &#x2F; Libadwaita（恢复原生热重载）"></a>1. 净化 GTK4 &#x2F; Libadwaita（恢复原生热重载）</h4><p>清理用户目录下的暴力覆盖层：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 备份并清空 gtk-4.0 下的所有覆盖文件</span></span><br><span class="line"><span class="built_in">mkdir</span> -p ~/.config/gtk-4.0.bak</span><br><span class="line"><span class="built_in">mv</span> ~/.config/gtk-4.0/* ~/.config/gtk-4.0.bak/ 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line"><span class="built_in">rm</span> -rf ~/.config/gtk-4.0/*</span><br></pre></td></tr></table></figure>

<h4 id="2-统一官方系统主题与-Shell-主题"><a href="#2-统一官方系统主题与-Shell-主题" class="headerlink" title="2. 统一官方系统主题与 Shell 主题"></a>2. 统一官方系统主题与 Shell 主题</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 设置 GTK 传统主题为官方 Yaru-purple</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface gtk-theme <span class="string">&#x27;Yaru-purple&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 重置 GNOME Shell 主题为官方原生（解决右上角弹窗保持白色的问题）</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.user-theme name <span class="string">&#x27;&#x27;</span></span><br></pre></td></tr></table></figure>

<h4 id="3-利用-XDG-优先级锁定-Tela-图标"><a href="#3-利用-XDG-优先级锁定-Tela-图标" class="headerlink" title="3. 利用 XDG 优先级锁定 Tela 图标"></a>3. 利用 XDG 优先级锁定 Tela 图标</h4><p>Linux 标准规定：<code>~/.local/share/icons/</code> 的优先级高于 <code>/usr/share/icons/</code>。建立软链接后，就算 Ubuntu 设置面板强制写入 Yaru，系统读取到的依然是 Tela 图标：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">ln</span> -sfn ~/.local/share/icons/Tela-purple-dark ~/.local/share/icons/Yaru-purple-dark</span><br><span class="line"><span class="built_in">ln</span> -sfn ~/.local/share/icons/Tela-purple ~/.local/share/icons/Yaru-purple</span><br><span class="line"><span class="built_in">ln</span> -sfn ~/.local/share/icons/Tela-dark ~/.local/share/icons/Yaru-dark</span><br><span class="line"><span class="built_in">ln</span> -sfn ~/.local/share/icons/Tela ~/.local/share/icons/Yaru</span><br><span class="line"></span><br><span class="line"><span class="comment"># 立即刷新当前图标</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface icon-theme <span class="string">&#x27;Tela-purple-dark&#x27;</span></span><br></pre></td></tr></table></figure>

<h3 id="2-4-深浅色自动切换（扩展方案与纯命令行定时）"><a href="#2-4-深浅色自动切换（扩展方案与纯命令行定时）" class="headerlink" title="2.4 深浅色自动切换（扩展方案与纯命令行定时）"></a>2.4 深浅色自动切换（扩展方案与纯命令行定时）</h3><p>如果希望桌面能像 macOS 一样根据日落日出或指定时间自动在深浅色之间无感切换，有两种实现方式：</p>
<h4 id="方案-A：Night-Theme-Switcher-扩展（体验最佳）"><a href="#方案-A：Night-Theme-Switcher-扩展（体验最佳）" class="headerlink" title="方案 A：Night Theme Switcher 扩展（体验最佳）"></a>方案 A：Night Theme Switcher 扩展（体验最佳）</h4><p>社区里成熟的自动切换扩展是 <strong>Night Theme Switcher</strong>，支持根据地理位置的日落日出时间、系统夜灯状态或指定时钟定时自动切换。在没有浏览器扩展商店或想免 Sudo 纯命令行部署时，可以直接用 Python 脚本下载并启用：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 自动下载并安装扩展包</span></span><br><span class="line">python3 -c <span class="string">&quot;</span></span><br><span class="line"><span class="str]]>
      </description>
      <author>noonyjufee@gmail.com (乱与狒)</author>
      <category domain="https://luanyufei.cn/categories/%E5%AE%9E%E7%94%A8%E6%8A%80%E5%B7%A7/">实用技巧</category>
      <category domain="https://luanyufei.cn/tags/Linux/">Linux</category>
      <category domain="https://luanyufei.cn/tags/Ubuntu/">Ubuntu</category>
      <category domain="https://luanyufei.cn/tags/Parallels/">Parallels</category>
      <category domain="https://luanyufei.cn/tags/%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/">折腾记录</category>
      <category domain="https://luanyufei.cn/tags/%E7%94%9F%E4%BA%A7%E5%8A%9B/">生产力</category>
      <category domain="https://luanyufei.cn/tags/%E6%A1%8C%E9%9D%A2%E7%BE%8E%E5%8C%96/">桌面美化</category>
      <pubDate>Wed, 26 Aug 2026 17:20:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>很久以前我装过一次 Ubuntu，当时各种依赖、字体和驱动配置实在太折腾，没用多久就放弃了。现在有了 AI Agent，很多底层的调试和脚本一句话就能搞定，整个折腾流程顺畅了许多。</p><p>这篇文档是我在 Apple Silicon Mac 的 Parallels 虚拟机里调教 Ubuntu 26.04 的完整记录。内容涵盖了屏幕高刷锁定、GNOME 现代主题架构与深色模式避坑、全透明悬浮 Dock、输入法迁移、终端与浏览器调优、系统深度瘦身与去遥测（彻底剥离 Snap、接入 Flatpak）、常用原生工具及系统底层服务，留作以后换机或在实体机上装 Linux 时的速查参考。</p><h2 id="1-屏幕分辨率与高刷锁定（2560×1440-180Hz）"><a href="#1-屏幕分辨率与高刷锁定（2560×1440-180Hz）" class="headerlink" title="1. 屏幕分辨率与高刷锁定（2560×1440 @ 180Hz）"></a>1. 屏幕分辨率与高刷锁定（2560×1440 @ 180Hz）</h2><h3 id="遇到问题"><a href="#遇到问题" class="headerlink" title="遇到问题"></a>遇到问题</h3><ol><li><strong>分辨率与刷新率缺失</strong>：系统初始缺少 2K 16:9（2560×1440）及 180Hz 高刷新率选项。</li><li><strong>分辨率被动态重置</strong>：Parallels Tools 后台进程在拖动或缩放 Mac 窗口时会触发动态分辨率检测，将虚拟机强行改回当前窗口尺寸并将刷新率重置为 60Hz。</li></ol><h3 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h3><ul><li><strong>内核 DRM 注入</strong>：在 <code>/etc/default/grub.d/99_resolution.cfg</code> 中加入参数 <code>video=Virtual-1:2560x1440@180 video=2560x1440@180</code>，执行 <code>sudo update-grub</code>，让内核在引导时生成对应的显示模式。</li><li><strong>阻止动态分辨率篡改</strong>：将系统二进制 <code>/usr/bin/prlcc</code> 替换为包装脚本，移除 DynRes 分辨率监听逻辑，同时保留剪贴板同步（<code>prlcp</code>）、文件拖放（<code>prldnd</code>）和共享目录（<code>prl_fsd</code>）。</li><li><strong>同步显示管理器配置</strong>：将显示配置文件保存到 <code>~/.config/monitors.xml</code>，并同步复制至 <code>/var/lib/gdm3/.config/monitors.xml</code>，确保从 GDM3 登录界面到进入桌面全程锁定该配置。</li></ul><h2 id="2-GNOME-42-主题架构与深色模式避坑"><a href="#2-GNOME-42-主题架构与深色模式避坑" class="headerlink" title="2. GNOME 42+ 主题架构与深色模式避坑"></a>2. GNOME 42+ 主题架构与深色模式避坑</h2><p>在现代 GNOME（GNOME 42+）下给 Ubuntu 换主题，很容易遇到各种莫名其妙的 bug：比如切了深色模式设置窗口依然是白色、换了第三方图标只要按一下深色按钮就自动变回默认图标、Brave 里的 Dark Reader 变黑两秒后又自己跳回白色等。</p><p>这些坑的根源在于现代 GNOME 的底层架构分层。</p><h3 id="2-1-现代-GNOME-的四层界面架构"><a href="#2-1-现代-GNOME-的四层界面架构" class="headerlink" title="2.1 现代 GNOME 的四层界面架构"></a>2.1 现代 GNOME 的四层界面架构</h3><p>目前的 Linux 桌面由四个相互独立的系统组成：</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">               ┌────────────────────────────────────────┐</span><br><span class="line">               │         你的应用界面 (Applications)       │</span><br><span class="line">               └───────────────────┬────────────────────┘</span><br><span class="line">                                   │</span><br><span class="line">         ┌─────────────────────────┴─────────────────────────┐</span><br><span class="line">         ▼                                                   ▼</span><br><span class="line">┌─────────────────────────────────┐         ┌─────────────────────────────────┐</span><br><span class="line">│     现代应用 (GTK4 + Libadwaita)   │         │     传统应用 (GTK3 / Legacy)     │</span><br><span class="line">│   (如: 系统设置、文件管理器、文本编辑器)   │         │    (如: GIMP、各种第三方老应用)     │</span><br><span class="line">└────────────────┬────────────────┘         └────────────────┬────────────────┘</span><br><span class="line">                 │                                           │</span><br><span class="line">   ┌─────────────┴─────────────┐                             │</span><br><span class="line">   ▼                           ▼                             ▼</span><br><span class="line">【Libadwaita 原生引擎】   【~/.config/gtk-4.0/】       【~/.themes/ 或 /usr/share/】</span><br><span class="line"> • 内置深浅色样式         • 用户强制覆盖层              • 读取 gsettings gtk-theme</span><br><span class="line"> • 官方标准架构           (第三方主题强行注入点)        (Yaru / Orchis 在此生效)</span><br><span class="line"> • 0ms 实时深浅热重载     • 静态文件，无法热重载</span><br></pre></td></tr></table></figure><ul><li><strong>Libadwaita (GTK4)</strong>：GNOME 官方为了防止第三方主题破坏控件结构，完全废弃了从 <code>~/.themes/</code> 读取外部主题的做法。深浅色样式直接以内置资源形式编译在底层，支持毫秒级的动态切换。</li><li><strong>第三方主题（如 Orchis）</strong>：为了强行修改 Libadwaita 应用，安装脚本通常会把生成的静态 CSS 复制到 <code>~/.config/gtk-4.0/gtk.css</code>。这是 GTK4 留给用户的最高优先级覆盖层，一旦写入，就会直接废掉 Libadwaita 原生的动态深浅色切换能力。</li><li><strong>GNOME Shell UI</strong>：独立于应用窗口，专门管顶部状态栏、右上角快捷控制面板（Quick Settings）、通知中心和日历，由 <code>user-theme</code> 扩展独立控制。</li><li><strong>Electron 与 Chromium &#x2F; Brave</strong>：依赖 Linux 的 XDG Desktop Portal（通过 D-Bus <code>org.freedesktop.portal.Settings</code> 广播）来接收操作系统的 <code>color-scheme</code> 切换信号。</li></ul><h3 id="2-2-五大常见故障与深度原理复盘"><a href="#2-2-五大常见故障与深度原理复盘" class="headerlink" title="2.2 五大常见故障与深度原理复盘"></a>2.2 五大常见故障与深度原理复盘</h3><h4 id="1-系统设了-Dark-Mode，系统设置窗口依然是白色？"><a href="#1-系统设了-Dark-Mode，系统设置窗口依然是白色？" class="headerlink" title="1. 系统设了 Dark Mode，系统设置窗口依然是白色？"></a>1. 系统设了 Dark Mode，系统设置窗口依然是白色？</h4><ul><li><strong>根本原因</strong>：之前安装 Orchis 等第三方主题时，浅色样式的 CSS 文件被写入了 <code>~/.config/gtk-4.0/gtk.css</code>。GTK4 启动时无条件优先加载这个静态文件里的背景定义，导致设置窗口被强行涂白。</li></ul><h4 id="2-为什么-Electron-能秒切深浅色，原生设置和浏览器却不行？"><a href="#2-为什么-Electron-能秒切深浅色，原生设置和浏览器却不行？" class="headerlink" title="2. 为什么 Electron 能秒切深浅色，原生设置和浏览器却不行？"></a>2. 为什么 Electron 能秒切深浅色，原生设置和浏览器却不行？</h4><ul><li><strong>根本原因</strong>：Electron 实时监听 D-Bus Portal 广播信号，收到后在内存里即时重算 CSS <code>@media (prefers-color-scheme: dark)</code>。而 GTK4 外部覆盖文件只在启动时读取一次磁盘，无法实时热重绘，必须关闭窗口重开才能生效。</li></ul><h4 id="3-Brave-里的-Dark-Reader-变黑-2-秒后，自动弹回浅色？"><a href="#3-Brave-里的-Dark-Reader-变黑-2-秒后，自动弹回浅色？" class="headerlink" title="3. Brave 里的 Dark Reader 变黑 2 秒后，自动弹回浅色？"></a>3. Brave 里的 Dark Reader 变黑 2 秒后，自动弹回浅色？</h4><ul><li><strong>根本原因（信号竞争冲突）</strong>：<ol><li>点击系统深色模式时，GNOME 先通过 D-Bus 广播 <code>color-scheme = 1 (Dark)</code>，Dark Reader 瞬间生效变黑；</li><li>如果 Brave 开启了 <code>Use GTK</code>，Chromium 底层模块在 2 秒后完成 GTK3 主题色彩重算，误判为浅色，强行推翻了之前的 Portal 信号，向页面发送 <code>prefers-color-scheme: light</code>；</li><li>Dark Reader 收到错误的覆盖信号，误以为系统切回了浅色，于是自动关闭了深色渲染。</li></ol></li></ul><h4 id="4-右上角控制中心与系统弹窗依然是白色？"><a href="#4-右上角控制中心与系统弹窗依然是白色？" class="headerlink" title="4. 右上角控制中心与系统弹窗依然是白色？"></a>4. 右上角控制中心与系统弹窗依然是白色？</h4><ul><li><strong>根本原因</strong>：桌面的系统弹窗属于 GNOME Shell UI，由 <code>user-theme</code> 扩展管理。如果之前 Shell 主题被固定指定为了浅色主题（如 <code>Orchis-Light</code>），桌面弹窗就会被强制固定为浅色。</li></ul><h4 id="5-换了第三方图标（如-Tela），一按深色模式又变回官方图标？"><a href="#5-换了第三方图标（如-Tela），一按深色模式又变回官方图标？" class="headerlink" title="5. 换了第三方图标（如 Tela），一按深色模式又变回官方图标？"></a>5. 换了第三方图标（如 Tela），一按深色模式又变回官方图标？</h4><ul><li><strong>根本原因</strong>：Ubuntu 的「系统设置 ➔ 外观」面板内置了绑定逻辑。只要在图形界面里点击了 Dark &#x2F; Default 或切换了颜色圆圈，Ubuntu 就会自动触发 <code>gsettings set icon-theme Yaru-&lt;color&gt;-dark</code>，强行重置图标。</li></ul><h3 id="2-3-终极推荐配置：纯净热重载与图标防篡改"><a href="#2-3-终极推荐配置：纯净热重载与图标防篡改" class="headerlink" title="2.3 终极推荐配置：纯净热重载与图标防篡改"></a>2.3 终极推荐配置：纯净热重载与图标防篡改</h3><p>要实现<strong>零 Bug、免重启、毫秒级平滑热重载、图标不被系统重置</strong>，按照以下四步配置：</p><h4 id="1-净化-GTK4-Libadwaita（恢复原生热重载）"><a href="#1-净化-GTK4-Libadwaita（恢复原生热重载）" class="headerlink" title="1. 净化 GTK4 &#x2F; Libadwaita（恢复原生热重载）"></a>1. 净化 GTK4 &#x2F; Libadwaita（恢复原生热重载）</h4><p>清理用户目录下的暴力覆盖层：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 备份并清空 gtk-4.0 下的所有覆盖文件</span></span><br><span class="line"><span class="built_in">mkdir</span> -p ~/.config/gtk-4.0.bak</span><br><span class="line"><span class="built_in">mv</span> ~/.config/gtk-4.0/* ~/.config/gtk-4.0.bak/ 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line"><span class="built_in">rm</span> -rf ~/.config/gtk-4.0/*</span><br></pre></td></tr></table></figure><h4 id="2-统一官方系统主题与-Shell-主题"><a href="#2-统一官方系统主题与-Shell-主题" class="headerlink" title="2. 统一官方系统主题与 Shell 主题"></a>2. 统一官方系统主题与 Shell 主题</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 设置 GTK 传统主题为官方 Yaru-purple</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface gtk-theme <span class="string">&#x27;Yaru-purple&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 重置 GNOME Shell 主题为官方原生（解决右上角弹窗保持白色的问题）</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.user-theme name <span class="string">&#x27;&#x27;</span></span><br></pre></td></tr></table></figure><h4 id="3-利用-XDG-优先级锁定-Tela-图标"><a href="#3-利用-XDG-优先级锁定-Tela-图标" class="headerlink" title="3. 利用 XDG 优先级锁定 Tela 图标"></a>3. 利用 XDG 优先级锁定 Tela 图标</h4><p>Linux 标准规定：<code>~/.local/share/icons/</code> 的优先级高于 <code>/usr/share/icons/</code>。建立软链接后，就算 Ubuntu 设置面板强制写入 Yaru，系统读取到的依然是 Tela 图标：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">ln</span> -sfn ~/.local/share/icons/Tela-purple-dark ~/.local/share/icons/Yaru-purple-dark</span><br><span class="line"><span class="built_in">ln</span> -sfn ~/.local/share/icons/Tela-purple ~/.local/share/icons/Yaru-purple</span><br><span class="line"><span class="built_in">ln</span> -sfn ~/.local/share/icons/Tela-dark ~/.local/share/icons/Yaru-dark</span><br><span class="line"><span class="built_in">ln</span> -sfn ~/.local/share/icons/Tela ~/.local/share/icons/Yaru</span><br><span class="line"></span><br><span class="line"><span class="comment"># 立即刷新当前图标</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface icon-theme <span class="string">&#x27;Tela-purple-dark&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="2-4-深浅色自动切换（扩展方案与纯命令行定时）"><a href="#2-4-深浅色自动切换（扩展方案与纯命令行定时）" class="headerlink" title="2.4 深浅色自动切换（扩展方案与纯命令行定时）"></a>2.4 深浅色自动切换（扩展方案与纯命令行定时）</h3><p>如果希望桌面能像 macOS 一样根据日落日出或指定时间自动在深浅色之间无感切换，有两种实现方式：</p><h4 id="方案-A：Night-Theme-Switcher-扩展（体验最佳）"><a href="#方案-A：Night-Theme-Switcher-扩展（体验最佳）" class="headerlink" title="方案 A：Night Theme Switcher 扩展（体验最佳）"></a>方案 A：Night Theme Switcher 扩展（体验最佳）</h4><p>社区里成熟的自动切换扩展是 <strong>Night Theme Switcher</strong>，支持根据地理位置的日落日出时间、系统夜灯状态或指定时钟定时自动切换。在没有浏览器扩展商店或想免 Sudo 纯命令行部署时，可以直接用 Python 脚本下载并启用：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 自动下载并安装扩展包</span></span><br><span class="line">python3 -c <span class="string">&quot;</span></span><br><span class="line"><span class="string">import urllib.request, tempfile, subprocess, os</span></span><br><span class="line"><span class="string">url = &#x27;https://extensions.gnome.org/download-extension/nightthemeswitcher@romainvigier.fr.shell-extension.zip?version_tag=73047&#x27;</span></span><br><span class="line"><span class="string">with tempfile.NamedTemporaryFile(suffix=&#x27;.zip&#x27;, delete=False) as f:</span></span><br><span class="line"><span class="string">    zip_path = f.name</span></span><br><span class="line"><span class="string">    with urllib.request.urlopen(urllib.request.Request(url, headers=&#123;&#x27;User-Agent&#x27;: &#x27;Mozilla/5.0&#x27;&#125;)) as resp:</span></span><br><span class="line"><span class="string">        f.write(resp.read())</span></span><br><span class="line"><span class="string">subprocess.run([&#x27;gnome-extensions&#x27;, &#x27;install&#x27;, &#x27;--force&#x27;, zip_path])</span></span><br><span class="line"><span class="string">os.unlink(zip_path)</span></span><br><span class="line"><span class="string">&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 编译 schema 规则文件</span></span><br><span class="line">glib-compile-schemas ~/.local/share/gnome-shell/extensions/nightthemeswitcher@romainvigier.fr/schemas</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 启用扩展</span></span><br><span class="line">gnome-extensions <span class="built_in">enable</span> nightthemeswitcher@romainvigier.fr</span><br></pre></td></tr></table></figure><ul><li><strong>快速手动切主题快捷键</strong>：<kbd>Shift</kbd> + <kbd>Super</kbd> + <kbd>T</kbd></li><li><strong>打开图形设置面板</strong>：<code>gnome-extensions prefs nightthemeswitcher@romainvigier.fr</code></li><li><strong>命令行设定固定切换时间</strong>（如 07:00 浅色、19:00 深色）：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">SCH_DIR=~/.local/share/gnome-shell/extensions/nightthemeswitcher@romainvigier.fr/schemas</span><br><span class="line">gsettings --schemadir <span class="string">&quot;<span class="variable">$SCH_DIR</span>&quot;</span> <span class="built_in">set</span> org.gnome.shell.extensions.nightthemeswitcher.time sunrise 7.0</span><br><span class="line">gsettings --schemadir <span class="string">&quot;<span class="variable">$SCH_DIR</span>&quot;</span> <span class="built_in">set</span> org.gnome.shell.extensions.nightthemeswitcher.time sunset 19.0</span><br></pre></td></tr></table></figure></li></ul><h4 id="方案-B：纯-Crontab-定时任务（免装扩展）"><a href="#方案-B：纯-Crontab-定时任务（免装扩展）" class="headerlink" title="方案 B：纯 Crontab 定时任务（免装扩展）"></a>方案 B：纯 Crontab 定时任务（免装扩展）</h4><p>如果不打算额外安装扩展，直接用系统内置的 <code>gsettings</code> 配合定时任务即可：</p><ul><li><strong>深色切换</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface color-scheme <span class="string">&#x27;prefer-dark&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface gtk-theme <span class="string">&#x27;Yaru-dark&#x27;</span></span><br></pre></td></tr></table></figure></li><li><strong>浅色切换</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface color-scheme <span class="string">&#x27;default&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface gtk-theme <span class="string">&#x27;Yaru&#x27;</span></span><br></pre></td></tr></table></figure></li></ul><p>执行 <code>crontab -e</code> 写入两行定时规则：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 每天 07:00 切浅色</span><br><span class="line">0 7 * * * gsettings set org.gnome.desktop.interface color-scheme &#x27;default&#x27; &amp;&amp; gsettings set org.gnome.desktop.interface gtk-theme &#x27;Yaru&#x27;</span><br><span class="line"></span><br><span class="line"># 每天 19:00 切深色</span><br><span class="line">0 19 * * * gsettings set org.gnome.desktop.interface color-scheme &#x27;prefer-dark&#x27; &amp;&amp; gsettings set org.gnome.desktop.interface gtk-theme &#x27;Yaru-dark&#x27;</span><br></pre></td></tr></table></figure><h2 id="3-桌面深度美化与透明组件调优"><a href="#3-桌面深度美化与透明组件调优" class="headerlink" title="3. 桌面深度美化与透明组件调优"></a>3. 桌面深度美化与透明组件调优</h2><h3 id="3-1-字体安装与渲染配置"><a href="#3-1-字体安装与渲染配置" class="headerlink" title="3.1 字体安装与渲染配置"></a>3.1 字体安装与渲染配置</h3><ul><li><strong>安装常用字体</strong>：<ul><li>中文字体：苹方全字重（PingFang SC，存放于 <code>/usr/local/share/fonts/PingFang/</code>）及思源黑体（Noto Sans CJK SC）。</li><li>等宽编程字体：JetBrains Mono、Fira Code。</li></ul></li><li><strong>全局字体渲染设置</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface font-name <span class="string">&quot;PingFang SC 11&quot;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface document-font-name <span class="string">&quot;PingFang SC 11&quot;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface monospace-font-name <span class="string">&quot;JetBrains Mono 10&quot;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.wm.preferences titlebar-font <span class="string">&quot;PingFang SC Bold 11&quot;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface font-antialiasing <span class="string">&quot;rgba&quot;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface font-hinting <span class="string">&quot;slight&quot;</span></span><br></pre></td></tr></table></figure></li><li><strong>网页字体映射修复</strong>：在 <code>/etc/fonts/local.conf</code> 中将 <code>SimSun</code>、<code>宋体</code>、<code>Microsoft YaHei</code> 和 <code>微软雅黑</code> 映射至苹方，解决部分国内网页将中文字形错误回退到衬线宋体的问题。</li></ul><p><figure class="article-image-figure"><img src="/image/ubuntu-app-drawer-blur.webp" alt="Tela 图标主题与 Blur my Shell 应用抽屉磨砂背景" title="Tela 图标主题与 Blur my Shell 应用抽屉磨砂背景效果" loading="lazy" decoding="async"><figcaption class="image-caption">Tela 图标主题与 Blur my Shell 应用抽屉磨砂背景效果</figcaption></figure></p><h3 id="3-2-顶部状态栏-100-透明与托盘图标居中"><a href="#3-2-顶部状态栏-100-透明与托盘图标居中" class="headerlink" title="3.2 顶部状态栏 100% 透明与托盘图标居中"></a>3.2 顶部状态栏 100% 透明与托盘图标居中</h3><ul><li><strong>顶部状态栏（Top Bar）100% 全透明</strong>：<br>GNOME 原生顶栏由底层 C&#x2F;St 固化渲染并自带纯黑底色。安装专用扩展 <code>Transparent Top Bar (Adjustable transparency)</code>（UUID: <code>transparent-top-bar@ftpix.com</code>），并关闭 Blur my Shell 对顶栏的暗色磨砂，即可实现通透的全透明悬浮顶栏：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.blur-my-shell.panel blur <span class="literal">false</span></span><br><span class="line">gsettings --schemadir ~/.local/share/glib-2.0/schemas/ <span class="built_in">set</span> com.ftpix.transparentbar transparency 0</span><br><span class="line">gsettings --schemadir ~/.local/share/glib-2.0/schemas/ <span class="built_in">set</span> com.ftpix.transparentbar dark-full-screen <span class="literal">false</span></span><br></pre></td></tr></table></figure></li><li><strong>后台托盘图标居中</strong>：<br>通过 <code>Ubuntu AppIndicators</code>（<code>ubuntu-appindicators@ubuntu.com</code>），可将原本挤在右上角的托盘图标移至顶栏中央（紧随在时钟日历后方）：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.appindicator tray-pos <span class="string">&#x27;center&#x27;</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="3-3-【核心避坑】Dock-栏-100-全透明与悬停预览调优"><a href="#3-3-【核心避坑】Dock-栏-100-全透明与悬停预览调优" class="headerlink" title="3.3 【核心避坑】Dock 栏 100% 全透明与悬停预览调优"></a>3.3 【核心避坑】Dock 栏 100% 全透明与悬停预览调优</h3><p>很多用户将 Dock 栏调为底部居中小胶囊后，发现即使设置 <code>background-opacity = 0.0</code>，底栏依然存在灰色半透明底、磨砂气泡或直接变成纯黑一块。</p><h4 id="为什么普通方法会失效？"><a href="#为什么普通方法会失效？" class="headerlink" title="为什么普通方法会失效？"></a>为什么普通方法会失效？</h4><ol><li><strong>Ubuntu Dock 源码逻辑缺陷（<code>theming.js</code>）</strong>：在 Ubuntu Dock 内部，如果 <code>custom-background-color</code> 为 <code>false</code>，计算透明度的 Alpha 逻辑<strong>不会被执行</strong>，而是直接回退为系统默认的半透明深色底。单纯开启 <code>custom-background-color</code> 给 <code>#000000</code> 时，若未正确注入 Alpha，系统会渲染为纯黑块。<strong>必须同时满足 <code>custom-background-color = true</code>、<code>background-color = &#39;#000000&#39;</code>、<code>transparency-mode = &#39;FIXED&#39;</code> 和 <code>background-opacity = 0.0</code></strong>，底层才会生成 <code>rgba(0, 0, 0, 0)</code> 的完全透明内联样式。</li><li><strong>Blur my Shell 图层叠加冲突</strong>：<code>Blur my Shell</code> 默认对 Dock 启用了毛玻璃模糊，即使透明度为 0 也会在背后叠加一层灰白色磨砂层。需显式关闭其针对 Dash-to-Dock 的模糊层。</li></ol><h4 id="100-真正全透明-Dock-标准配置"><a href="#100-真正全透明-Dock-标准配置" class="headerlink" title="100% 真正全透明 Dock 标准配置"></a>100% 真正全透明 Dock 标准配置</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 底部居中小胶囊</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock dock-position <span class="string">&#x27;BOTTOM&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock extend-height <span class="literal">false</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock custom-theme-shrink <span class="literal">true</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock dash-max-icon-size 48</span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock running-indicator-style <span class="string">&#x27;DOTS&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock autohide <span class="literal">true</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock intellihide <span class="literal">true</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock show-mounts <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 触发底层 0 不透明度注入（关键避坑）</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock apply-custom-theme <span class="literal">false</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock custom-background-color <span class="literal">true</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock background-color <span class="string">&#x27;#000000&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock transparency-mode <span class="string">&#x27;FIXED&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock background-opacity 0.0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 移除多余的毛玻璃图层</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.blur-my-shell.dash-to-dock blur <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 热重载 Dock 扩展即时生效</span></span><br><span class="line">gnome-extensions <span class="built_in">disable</span> ubuntu-dock@ubuntu.com &amp;&amp; <span class="built_in">sleep</span> 0.2 &amp;&amp; gnome-extensions <span class="built_in">enable</span> ubuntu-dock@ubuntu.com</span><br></pre></td></tr></table></figure><p><figure class="article-image-figure"><img src="/image/ubuntu-desktop-transparent-dock.webp" alt="Ubuntu 全透明顶栏与居中悬浮 Dock 最终效果" title="Ubuntu 全透明顶栏与居中悬浮 Dock 最终效果" loading="lazy" decoding="async"><figcaption class="image-caption">Ubuntu 全透明顶栏与居中悬浮 Dock 最终效果</figcaption></figure></p><h4 id="Dock-鼠标悬停实时窗口缩略图（零延迟即消）"><a href="#Dock-鼠标悬停实时窗口缩略图（零延迟即消）" class="headerlink" title="Dock 鼠标悬停实时窗口缩略图（零延迟即消）"></a>Dock 鼠标悬停实时窗口缩略图（零延迟即消）</h4><p>安装 <strong><code>Dock Window Preview</code></strong>（UUID: <code>dock-window-preview@quivio</code>），可实现类似 Windows &#x2F; macOS 的悬停缩略图：</p><ol><li><strong>水平卡片与灵敏度</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">gsettings --schemadir ~/.local/share/glib-2.0/schemas/ <span class="built_in">set</span> org.gnome.shell.extensions.dock-window-preview preview-layout <span class="string">&#x27;horizontal&#x27;</span></span><br><span class="line">gsettings --schemadir ~/.local/share/glib-2.0/schemas/ <span class="built_in">set</span> org.gnome.shell.extensions.dock-window-preview show-close-button <span class="literal">true</span></span><br><span class="line">gsettings --schemadir ~/.local/share/glib-2.0/schemas/ <span class="built_in">set</span> org.gnome.shell.extensions.dock-window-preview hover-delay-ms 150</span><br></pre></td></tr></table></figure></li><li><strong>零延迟即时消失调优</strong>：修改扩展目录中的 <code>extension.js</code>，将 <code>HIDE_DELAY_MS</code> 从 <code>240ms</code> 改为 <code>0ms</code>，将鼠标离开扫描周期 <code>POINTER_POLL_MS</code> 改为 <code>40ms</code>，实现鼠标悬停 0.15 秒弹出、移开瞬间 0 延迟消失。</li></ol><h3 id="3-4-GNOME-扩展清单"><a href="#3-4-GNOME-扩展清单" class="headerlink" title="3.4 GNOME 扩展清单"></a>3.4 GNOME 扩展清单</h3><p>管理工具通过 <code>sudo apt install gnome-shell-extension-manager</code> 安装，目前启用的扩展包括：</p><ul><li><strong>User Themes</strong> (<code>user-theme@gnome-shell-extensions.gcampax.github.com</code>)：解锁自定义 Shell 主题。</li><li><strong>Blur my Shell</strong> (<code>blur-my-shell@aunetx</code>)：应用抽屉与概览背景半透明毛玻璃效果。</li><li><strong>Transparent Top Bar</strong> (<code>transparent-top-bar@ftpix.com</code>)：顶栏 100% 全透明。</li><li><strong>Dock Window Preview</strong> (<code>dock-window-preview@quivio</code>)：Dock 栏悬停实时窗口预览卡片。</li><li><strong>Logo Menu</strong> (<code>logomenu@aryan_k</code>)：顶栏左侧系统菜单入口。</li><li><strong>Vitals</strong> (<code>Vitals@CoreCoding.com</code>)：顶栏显示 CPU、内存、实时网速与存储状态。</li><li><strong>Compiz Magic Lamp</strong> (<code>compiz-alike-magic-lamp-effect@hermes83.github.com</code>)：窗口最小化神奇缩放动效。</li><li><strong>Ubuntu AppIndicators</strong> (<code>ubuntu-appindicators@ubuntu.com</code>)：系统托盘图标支持与居中对齐。</li></ul><h3 id="3-5-一键美化与恢复脚本速查"><a href="#3-5-一键美化与恢复脚本速查" class="headerlink" title="3.5 一键美化与恢复脚本速查"></a>3.5 一键美化与恢复脚本速查</h3><p>重装系统或新机配置时，可直接保存执行以下脚本一键还原整套美化：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"><span class="comment"># 一键应用桌面美化与透明配置</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 应用主题</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface cursor-theme <span class="string">&#x27;WhiteSur-cursors&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface icon-theme <span class="string">&#x27;Tela-purple-dark&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.desktop.interface gtk-theme <span class="string">&#x27;Yaru-purple&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.user-theme name <span class="string">&#x27;&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 托盘居中</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.appindicator tray-pos <span class="string">&#x27;center&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. Dock 真正全透明</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock dock-position <span class="string">&#x27;BOTTOM&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock extend-height <span class="literal">false</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock custom-theme-shrink <span class="literal">true</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock apply-custom-theme <span class="literal">false</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock custom-background-color <span class="literal">true</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock background-color <span class="string">&#x27;#000000&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock transparency-mode <span class="string">&#x27;FIXED&#x27;</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.dash-to-dock background-opacity 0.0</span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.blur-my-shell.dash-to-dock blur <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 顶栏 100% 纯透明</span></span><br><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.extensions.blur-my-shell.panel blur <span class="literal">false</span></span><br><span class="line">gsettings --schemadir ~/.local/share/glib-2.0/schemas/ <span class="built_in">set</span> com.ftpix.transparentbar transparency 0 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line">gsettings --schemadir ~/.local/share/glib-2.0/schemas/ <span class="built_in">set</span> com.ftpix.transparentbar dark-full-screen <span class="literal">false</span> 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. Dock 悬停预览参数</span></span><br><span class="line">gsettings --schemadir ~/.local/share/glib-2.0/schemas/ <span class="built_in">set</span> org.gnome.shell.extensions.dock-window-preview preview-layout <span class="string">&#x27;horizontal&#x27;</span> 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line">gsettings --schemadir ~/.local/share/glib-2.0/schemas/ <span class="built_in">set</span> org.gnome.shell.extensions.dock-window-preview show-close-button <span class="literal">true</span> 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line">gsettings --schemadir ~/.local/share/glib-2.0/schemas/ <span class="built_in">set</span> org.gnome.shell.extensions.dock-window-preview hover-delay-ms 150 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 6. 重启扩展生效</span></span><br><span class="line">gnome-extensions <span class="built_in">disable</span> ubuntu-dock@ubuntu.com &amp;&amp; <span class="built_in">sleep</span> 0.2 &amp;&amp; gnome-extensions <span class="built_in">enable</span> ubuntu-dock@ubuntu.com</span><br><span class="line">gnome-extensions <span class="built_in">disable</span> blur-my-shell@aunetx &amp;&amp; <span class="built_in">sleep</span> 0.2 &amp;&amp; gnome-extensions <span class="built_in">enable</span> blur-my-shell@aunetx</span><br><span class="line">nautilus -q 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;✨ 美化与透明配置已全部应用成功！&quot;</span></span><br></pre></td></tr></table></figure><h2 id="4-Rime-输入法与小鹤双拼"><a href="#4-Rime-输入法与小鹤双拼" class="headerlink" title="4. Rime 输入法与小鹤双拼"></a>4. Rime 输入法与小鹤双拼</h2><h3 id="实施细节"><a href="#实施细节" class="headerlink" title="实施细节"></a>实施细节</h3><ul><li><strong>安装依赖</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install -y ibus-rime librime-bin librime-plugin-lua</span><br></pre></td></tr></table></figure></li><li><strong>同步 Mac 词库与配置</strong>：从 Mac 共享路径 <code>/media/psf/Home/Library/Rime</code> 同步雾凇拼音（<code>rime_ice</code>）、双拼方案、词库文件（<code>cn_dicts/</code>、<code>en_dicts/</code>）、自定义短语（<code>custom_phrase.txt</code>）、Lua 插件及 OpenCC 规则到 <code>~/.config/ibus/rime/</code>。</li><li><strong>设定默认方案</strong>：在 <code>default.custom.yaml</code> 中将 <code>double_pinyin_flypy</code>（小鹤双拼）置于首位。</li><li><strong>行内拼音显示</strong>：在 <code>ibus_rime.custom.yaml</code> 中配置 <code>style/preedit_style: composition</code>，使输入框内直接显示键入的拼音字母（如 <code>na xie</code>），按空格确认后汉字上屏。</li><li><strong>快捷键</strong>：输入法切换键设为 <code>Ctrl + Space</code>（避免与 macOS 的 Spotlight 冲突），中英文在 Rime 内通过 <code>Shift</code> 切换。</li></ul><h2 id="5-终端环境（Ghostty）"><a href="#5-终端环境（Ghostty）" class="headerlink" title="5. 终端环境（Ghostty）"></a>5. 终端环境（Ghostty）</h2><h3 id="5-1-安装与默认终端切换"><a href="#5-1-安装与默认终端切换" class="headerlink" title="5.1 安装与默认终端切换"></a>5.1 安装与默认终端切换</h3><ol><li>通过 <code>sudo apt install -y ghostty</code> 安装官方 ARM64 版本。</li><li>注册为默认终端：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> update-alternatives --install /usr/bin/x-terminal-emulator x-terminal-emulator /usr/bin/ghostty 50</span><br><span class="line"><span class="built_in">sudo</span> update-alternatives --<span class="built_in">set</span> x-terminal-emulator /usr/bin/ghostty</span><br></pre></td></tr></table></figure></li><li>绑定快捷键 <code>Ctrl + Alt + T</code> 唤起 Ghostty，并将 Dock 栏中的终端图标替换为 Ghostty。</li></ol><h3 id="5-2-渲染问题修复"><a href="#5-2-渲染问题修复" class="headerlink" title="5.2 渲染问题修复"></a>5.2 渲染问题修复</h3><ul><li><strong>遇到问题</strong>：Ghostty 依赖 OpenGL 4.3 的片元着色器 SSBO 特性，而 Parallels 虚拟显卡仅支持 OpenGL 3.3，导致直接启动时崩溃或无响应。</li><li><strong>修复方法</strong>：将二进制文件移动为 <code>/usr/bin/ghostty.bin</code>，并创建 <code>/usr/bin/ghostty</code> 包装脚本，指定 Mesa 的 <code>llvmpipe</code> 软件渲染引擎：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/sh</span></span><br><span class="line"><span class="built_in">export</span> LIBGL_ALWAYS_SOFTWARE=1</span><br><span class="line"><span class="built_in">export</span> MESA_LOADER_DRIVER_OVERRIDE=llvmpipe</span><br><span class="line"><span class="built_in">exec</span> /usr/bin/ghostty.bin <span class="string">&quot;<span class="variable">$@</span>&quot;</span></span><br></pre></td></tr></table></figure></li><li><strong>主题配置</strong>：在 <code>~/.config/ghostty/config</code> 中配置标准主题：<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="attr">font-size</span> = <span class="number">13</span></span><br><span class="line"><span class="attr">theme</span> = dark:Catppuccin Mocha,light:Catppuccin Latte</span><br><span class="line"><span class="attr">window-padding-x</span> = <span class="number">8</span></span><br><span class="line"><span class="attr">window-padding-y</span> = <span class="number">8</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="6-浏览器环境（Brave）"><a href="#6-浏览器环境（Brave）" class="headerlink" title="6. 浏览器环境（Brave）"></a>6. 浏览器环境（Brave）</h2><h3 id="6-1-移除-Firefox"><a href="#6-1-移除-Firefox" class="headerlink" title="6.1 移除 Firefox"></a>6.1 移除 Firefox</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> snap remove --purge firefox</span><br><span class="line"><span class="built_in">sudo</span> apt remove --purge -y firefox</span><br><span class="line"><span class="built_in">rm</span> -rf ~/snap/firefox ~/.mozilla</span><br></pre></td></tr></table></figure><h3 id="6-2-安装-Brave-并配置企业策略"><a href="#6-2-安装-Brave-并配置企业策略" class="headerlink" title="6.2 安装 Brave 并配置企业策略"></a>6.2 安装 Brave 并配置企业策略</h3><ol><li><strong>添加官方仓库并安装</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg</span><br><span class="line"><span class="built_in">sudo</span> curl -fsSLo /etc/apt/sources.list.d/brave-browser-release.sources https://brave-browser-apt-release.s3.brave.com/brave-browser.sources</span><br><span class="line"><span class="built_in">sudo</span> apt update &amp;&amp; <span class="built_in">sudo</span> apt install -y brave-browser</span><br></pre></td></tr></table></figure></li><li><strong>写入企业策略配置文件</strong>：创建 <code>/etc/brave/policies/managed/brave_debloat.json</code>，关闭加密钱包、内置 AI、VPN、推广奖励与遥测，并将字体统一指定为苹方与 JetBrains Mono：<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;BraveWalletDisabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;BraveAIChatEnabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;BraveVPNDisabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;BraveRewardsDisabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;BraveNewsDisabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;BraveP3AEnabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;BraveStatsPingEnabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;MetricsReportingEnabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;BraveWebDiscoveryEnabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;UrlKeyedAnonymizedDataCollectionEnabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;UserFeedbackAllowed&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;StandardFontFamily&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PingFang SC&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;SansserifFontFamily&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PingFang SC&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;FixedFontFamily&quot;</span><span class="punctuation">:</span> <span class="string">&quot;JetBrains Mono&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><strong>设为默认浏览器</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">xdg-settings <span class="built_in">set</span> default-web-browser brave-browser.desktop</span><br></pre></td></tr></table></figure></li></ol><h3 id="6-3-外观与深浅色跟随避坑"><a href="#6-3-外观与深浅色跟随避坑" class="headerlink" title="6.3 外观与深浅色跟随避坑"></a>6.3 外观与深浅色跟随避坑</h3><p>打开 Brave 进入 <code>brave://settings/appearance</code>：</p><ul><li><strong>Linux 主题选项</strong>：选择 <strong>Classic（经典主题）</strong>。如果当前显示 <code>Reset to Classic</code>，点击重置（<strong>切勿点击 Use GTK 或 Use QT</strong>，否则会引发前面提到的色彩信号竞争 bug）。</li><li><strong>Brave colors</strong>：选择 <strong>Same as Linux</strong>（跟随系统）。</li><li><strong>Dark Reader 扩展</strong>：移除了 GTK 的冲突信号后，Dark Reader 选择“跟随系统”即可正常工作，不再出现 2 秒后弹回浅色的问题。</li></ul><h2 id="7-系统瘦身、去遥测与包管理（剥离-Snap-接入-Flatpak）"><a href="#7-系统瘦身、去遥测与包管理（剥离-Snap-接入-Flatpak）" class="headerlink" title="7. 系统瘦身、去遥测与包管理（剥离 Snap &amp; 接入 Flatpak）"></a>7. 系统瘦身、去遥测与包管理（剥离 Snap &amp; 接入 Flatpak）</h2><p>Ubuntu 这几年把不少常用软件甚至底层运行时都硬塞进了 Snap。启动慢、吃磁盘不说，每次敲 <code>lsblk</code> 还会被一堆虚拟 loop 回环挂载点刷屏。把 Snap 彻底剥离并锁定 APT 规则，换用轻量规范的原生 GNOME 软件中心与 Flatpak，再顺手干掉系统报错弹窗与商业广告，系统会清爽很多。</p><h3 id="7-1-彻底卸载-Snap-并锁定-APT-规则（防止静默带回）"><a href="#7-1-彻底卸载-Snap-并锁定-APT-规则（防止静默带回）" class="headerlink" title="7.1 彻底卸载 Snap 并锁定 APT 规则（防止静默带回）"></a>7.1 彻底卸载 Snap 并锁定 APT 规则（防止静默带回）</h3><ol><li><strong>按依赖顺序卸载 Snap 应用与底层运行时</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> snap remove --purge snap-store gnome-46-2404 gtk-common-themes mesa-2404 core24 bare snapd</span><br></pre></td></tr></table></figure></li><li><strong>彻底清除 <code>snapd</code> 守护进程与残留缓存目录</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get purge -y snapd</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -rf ~/snap /var/cache/snapd /var/lib/snapd /snap /root/snap</span><br></pre></td></tr></table></figure></li><li><strong>配置 APT Pinning 锁定规则</strong>：<br>Ubuntu 的部分 APT 命令（如 <code>apt install firefox</code>）默认会自动唤起 Snap。写入一条负优先级规则，彻底断掉系统自动把 Snap 装回来的后路：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> bash -c <span class="string">&#x27;cat &lt;&lt;EOF &gt; /etc/apt/preferences.d/nosnap.pref</span></span><br><span class="line"><span class="string">Package: snapd</span></span><br><span class="line"><span class="string">Pin: release a=*</span></span><br><span class="line"><span class="string">Pin-Priority: -10</span></span><br><span class="line"><span class="string">EOF&#x27;</span></span><br></pre></td></tr></table></figure></li></ol><h3 id="7-2-原生-GNOME-软件中心与-Flatpak-Flathub-生态"><a href="#7-2-原生-GNOME-软件中心与-Flatpak-Flathub-生态" class="headerlink" title="7.2 原生 GNOME 软件中心与 Flatpak &#x2F; Flathub 生态"></a>7.2 原生 GNOME 软件中心与 Flatpak &#x2F; Flathub 生态</h3><p>移除 Snap 之后，换上原生轻量的 GNOME 软件中心，并接入 Flatpak 生态管理跨分发版应用：</p><ol><li><strong>安装原生软件中心与 Flatpak 插件</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install -y flatpak gnome-software gnome-software-plugin-flatpak</span><br></pre></td></tr></table></figure></li><li><strong>添加 Flathub 官方软件源</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo</span><br></pre></td></tr></table></figure></li></ol><h3 id="7-3-关闭崩溃报错弹窗与遥测数据收集"><a href="#7-3-关闭崩溃报错弹窗与遥测数据收集" class="headerlink" title="7.3 关闭崩溃报错弹窗与遥测数据收集"></a>7.3 关闭崩溃报错弹窗与遥测数据收集</h3><p>平时用 Ubuntu 最烦时不时跳出“系统遇到内部错误”的弹窗。后台的 Apport、Whoopsie 和 Ubuntu Report 遥测还在默默吃 I&#x2F;O 记日志。关停服务并彻底清理：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 停止并禁用报错守护进程</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl stop apport whoopsie 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">disable</span> apport whoopsie 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 彻底卸载遥测与报错组件</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get purge -y apport apport-gtk whoopsie ubuntu-report popularity-contest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 清空历史崩溃转储文件</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -rf /var/crash/*</span><br></pre></td></tr></table></figure><h3 id="7-4-屏蔽-Ubuntu-Pro-商业推广与-MOTD-登录新闻"><a href="#7-4-屏蔽-Ubuntu-Pro-商业推广与-MOTD-登录新闻" class="headerlink" title="7.4 屏蔽 Ubuntu Pro 商业推广与 MOTD 登录新闻"></a>7.4 屏蔽 Ubuntu Pro 商业推广与 MOTD 登录新闻</h3><p>每次在终端执行 <code>apt upgrade</code> 都会带出一段 Ubuntu Pro &#x2F; ESM 商业订阅提示，登录终端时还会有动态联网新闻轮播：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 移除 APT 中的 Ubuntu Pro 广告注入钩子</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> -f /etc/apt/apt.conf.d/20apt-esm-hook.conf</span><br><span class="line"></span><br><span class="line"><span class="comment"># 禁用登录终端时的 MOTD 联网新闻轮播</span></span><br><span class="line"><span class="built_in">sudo</span> sed -i <span class="string">&quot;s/ENABLED=1/ENABLED=0/&quot;</span> /etc/default/motd-news</span><br></pre></td></tr></table></figure><h3 id="7-5-卸载预装游戏与清理孤立缓存"><a href="#7-5-卸载预装游戏与清理孤立缓存" class="headerlink" title="7.5 卸载预装游戏与清理孤立缓存"></a>7.5 卸载预装游戏与清理孤立缓存</h3><p>扫雷、数独、麻将、纸牌这些系统预装小游戏平时用不上，直接 purge 掉，再清理孤立依赖和包缓存：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 卸载预装小游戏</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get purge -y gnome-mines gnome-sudoku gnome-mahjongg aisleriot 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 自动清理孤立依赖库与残留配置</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get autoremove -y --purge</span><br><span class="line"></span><br><span class="line"><span class="comment"># 清空 apt 安装包下载缓存释放空间</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get clean</span><br></pre></td></tr></table></figure><h2 id="8-系统底层与外设优化"><a href="#8-系统底层与外设优化" class="headerlink" title="8. 系统底层与外设优化"></a>8. 系统底层与外设优化</h2><h3 id="8-1-ZRAM-内存实时压缩"><a href="#8-1-ZRAM-内存实时压缩" class="headerlink" title="8.1 ZRAM 内存实时压缩"></a>8.1 ZRAM 内存实时压缩</h3><ul><li>安装 <code>zram-tools</code> 并启用 <code>zramswap.service</code>。</li><li>在 <code>/etc/default/zramswap</code> 中将压缩算法设为 <code>zstd</code>，容量上限设为 100% 物理内存（5.3GB），优先级设为 100。</li><li><strong>效果</strong>：系统可用 Swap 扩充至 9.1GB，优先在内存中压缩暂存不活跃数据，避免因内存不足触发磁盘 I&#x2F;O 卡顿。</li></ul><h3 id="8-2-截图快捷键"><a href="#8-2-截图快捷键" class="headerlink" title="8.2 截图快捷键"></a>8.2 截图快捷键</h3><ul><li>将交互式截图工具快捷键绑定为 <code>Ctrl + Shift + S</code>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">gsettings <span class="built_in">set</span> org.gnome.shell.keybindings show-screenshot-ui <span class="string">&quot;[&#x27;Print&#x27;, &#x27;&lt;Control&gt;&lt;Shift&gt;s&#x27;, &#x27;&lt;Super&gt;&lt;Shift&gt;s&#x27;]&quot;</span></span><br></pre></td></tr></table></figure></li><li>支持区域框选、窗口截图与屏幕录制，截图自动存入剪贴板并保存在 <code>~/Pictures/Screenshots</code>。</li></ul><h3 id="8-3-剪贴板与拖拽服务"><a href="#8-3-剪贴板与拖拽服务" class="headerlink" title="8.3 剪贴板与拖拽服务"></a>8.3 剪贴板与拖拽服务</h3><p>创建 Systemd 用户守护服务 <code>~/.config/systemd/user/parallels-tools-user.service</code> 并安装 <code>wl-clipboard</code> 与 <code>xclip</code>，确保登录后自动恢复与 Mac 宿主机的双向剪贴板和文件拖放支持。</p><h3 id="8-4-系统监控与任务管理（Mission-Center）"><a href="#8-4-系统监控与任务管理（Mission-Center）" class="headerlink" title="8.4 系统监控与任务管理（Mission Center）"></a>8.4 系统监控与任务管理（Mission Center）</h3><ul><li>安装了 <strong>Mission Center</strong>，界面风格和交互逻辑几乎是 Windows 任务管理器的翻版，支持直观监控 CPU、内存、磁盘 I&#x2F;O、网络利用率及细粒度进程管理，相比原生系统监视器更加直观好用。</li></ul><h3 id="8-5-Nautilus-一级右键菜单扩展（复制文件路径）"><a href="#8-5-Nautilus-一级右键菜单扩展（复制文件路径）" class="headerlink" title="8.5 Nautilus 一级右键菜单扩展（复制文件路径）"></a>8.5 Nautilus 一级右键菜单扩展（复制文件路径）</h3><p>为了彻底摆脱二级 <code>Scripts</code> 子菜单的繁琐操作，直接在文件管理器一级右键菜单中调用复制路径：</p><ol><li><strong>安装 Python 扩展支持库</strong>：<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install -y python3-nautilus</span><br></pre></td></tr></table></figure></li><li><strong>安装 <code>nautilus-copy-path</code> 插件</strong>：<ul><li>源码仓库：<a href="https://github.com/chr314/nautilus-copy-path">chr314&#x2F;nautilus-copy-path</a>；</li><li>将插件置于 <code>~/.local/share/nautilus-python/extensions/</code>，执行 <code>nautilus -q</code> 刷新后台。</li></ul></li><li><strong>使用效果</strong>：<ul><li>在文件管理器中右键任意文件，一级菜单直接提供 <strong><code>Copy Path</code></strong>（复制完整路径）与 <strong><code>Copy Name</code></strong>（复制文件名）；</li><li>支持快捷键：选中文件直接按 <strong><code>Ctrl + Shift + C</code></strong> 瞬间复制完整路径。</li></ul></li></ol><h2 id="9-常用软件与-Antigravity-开发环境"><a href="#9-常用软件与-Antigravity-开发环境" class="headerlink" title="9. 常用软件与 Antigravity 开发环境"></a>9. 常用软件与 Antigravity 开发环境</h2><h3 id="9-1-原生-ARM64-常用生产力软件（LocalSend-PeaZip）"><a href="#9-1-原生-ARM64-常用生产力软件（LocalSend-PeaZip）" class="headerlink" title="9.1 原生 ARM64 常用生产力软件（LocalSend &amp; PeaZip）"></a>9.1 原生 ARM64 常用生产力软件（LocalSend &amp; PeaZip）</h3><ul><li><strong>LocalSend（局域网跨平台隔空互传）</strong>：<ul><li>官方 Linux aarch64 原生 Flutter 构建，安装于 <code>~/.local/share/localsend</code>，支持桌面图标点击与终端 <code>localsend</code> 命令启动。</li></ul></li><li><strong>PeaZip（7-Zip 核心专业图形化压缩&#x2F;解压管理器）</strong>：<ul><li>官方 7-Zip 原生 Linux 版仅有 CLI 命令行工具（<code>7zz</code>），PeaZip 是 Linux 下成熟强大的 7-Zip 图形化管理工具，支持多标签页、密码加密、分卷压缩，并已关联为系统中 <code>.7z</code>、<code>.zip</code>、<code>.rar</code>、<code>.tar.gz</code> 等所有常见压缩格式的默认打开程序。</li></ul></li></ul><h3 id="9-2-Antigravity-部署、补丁与深浅模式自动同步"><a href="#9-2-Antigravity-部署、补丁与深浅模式自动同步" class="headerlink" title="9.2 Antigravity 部署、补丁与深浅模式自动同步"></a>9.2 Antigravity 部署、补丁与深浅模式自动同步</h3><ul><li><strong>部署与权限</strong>：安装于 <code>/opt/antigravity</code>，修复 <code>chrome-sandbox</code> 权限，生成 <code>/usr/local/bin/antigravity</code> 全局命令与桌面图标。</li><li><strong>标题栏色差与窗口唤醒修复</strong>：<ul><li>修改 <code>dist/ipcHandlers.js</code>，将主题同步平台判断放宽至非 macOS 系统，使 Linux 下窗口按钮区域颜色与主题实时匹配。</li><li>修改 <code>dist/main.js</code> 中的 <code>second-instance</code> 事件逻辑，修复在 <code>Ctrl + W</code> 关闭窗口后点击 Dock 无法重新呼出界面的问题。</li></ul></li><li><strong>自动打补丁脚本</strong>：编写 <code>/opt/antigravity/patch-linux-fixes.js</code> 并在启动脚本中调用，确保应用自动更新后补丁依然生效。</li><li><strong>跟随系统自动切换深浅主题</strong>：<br>在 Antigravity &#x2F; VS Code 中按 <kbd>Ctrl</kbd> + <kbd>,</kbd> 打开设置，搜索并勾选 <code>Auto Detect Color Scheme</code>，或在 <code>settings.json</code> 中配置：<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;window.autoDetectColorScheme&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;workbench.preferredLightColorTheme&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Default Light Modern&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;workbench.preferredDarkColorTheme&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Default Dark Modern&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="9-3-现代开发工具"><a href="#9-3-现代开发工具" class="headerlink" title="9.3 现代开发工具"></a>9.3 现代开发工具</h3><ul><li><strong>uv</strong>：安装于 <code>/usr/local/bin/uv</code>，用于 Python 环境隔离和包管理。</li><li><strong>OpenCLI</strong>：全局安装 <code>@jackwener/opencli</code>，并部署相关 skills 到 <code>~/.gemini/config/skills/</code>。</li></ul><h3 id="9-4-Web-开发中的深浅模式落地（防闪白与工程实践）"><a href="#9-4-Web-开发中的深浅模式落地（防闪白与工程实践）" class="headerlink" title="9.4 Web 开发中的深浅模式落地（防闪白与工程实践）"></a>9.4 Web 开发中的深浅模式落地（防闪白与工程实践）</h3><p>在开发网站或前端项目时，实现一个体验完善的深浅模式切换需要兼顾系统偏好、手动覆盖和防白屏闪烁（Anti-FOUC）。</p><h4 id="1-核心架构：CSS-变量与-Class-切换"><a href="#1-核心架构：CSS-变量与-Class-切换" class="headerlink" title="1. 核心架构：CSS 变量与 Class 切换"></a>1. 核心架构：CSS 变量与 Class 切换</h4><p>生产环境通常在 <code>&lt;html&gt;</code> 标签上挂载 <code>.dark</code> 类，通过 CSS 变量统一管理色彩：</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-pseudo">:root</span> &#123;</span><br><span class="line">  <span class="attr">--bg</span>: <span class="number">#ffffff</span>;</span><br><span class="line">  <span class="attr">--text</span>: <span class="number">#111111</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">html</span><span class="selector-class">.dark</span> &#123;</span><br><span class="line">  <span class="attr">--bg</span>: <span class="number">#121212</span>;</span><br><span class="line">  <span class="attr">--text</span>: <span class="number">#f5f5f5</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">body</span> &#123;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="built_in">var</span>(--bg);</span><br><span class="line">  <span class="attribute">color</span>: <span class="built_in">var</span>(--text);</span><br><span class="line">  <span class="attribute">transition</span>: background-color <span class="number">0.2s</span> ease, color <span class="number">0.2s</span> ease;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-防白屏闪烁（Anti-FOUC）"><a href="#2-防白屏闪烁（Anti-FOUC）" class="headerlink" title="2. 防白屏闪烁（Anti-FOUC）"></a>2. 防白屏闪烁（Anti-FOUC）</h4><p>很多网站在深色模式下刷新页面时，会先闪一下刺眼的白光再变黑。这是因为外部 JS 通常在 DOM 解析完毕后才执行。</p><p>解决办法是在 <code>&lt;head&gt;</code> 标签的最顶部放一段轻量的阻塞式内联 JS，在浏览器开始绘制首帧之前就计算并挂载好类名：</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    (<span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> saved = <span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;theme-preference&#x27;</span>) || <span class="string">&#x27;system&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> systemDark = <span class="variable language_">window</span>.<span class="title function_">matchMedia</span>(<span class="string">&#x27;(prefers-color-scheme: dark)&#x27;</span>).<span class="property">matches</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (saved === <span class="string">&#x27;dark&#x27;</span> || (saved === <span class="string">&#x27;system&#x27;</span> &amp;&amp; systemDark)) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&#x27;dark&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">      &#125; <span class="keyword">else</span> &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">&#x27;dark&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;)();</span></span><br><span class="line"><span class="language-javascript">  </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="3-完整的状态管理逻辑"><a href="#3-完整的状态管理逻辑" class="headerlink" title="3. 完整的状态管理逻辑"></a>3. 完整的状态管理逻辑</h4><p>支持 <code>light</code>、<code>dark</code>、<code>system</code> 三态，并在用户选择“跟随系统”时实时监听系统偏好广播：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">STORAGE_KEY</span> = <span class="string">&#x27;theme-preference&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getEffectiveTheme</span>(<span class="params">pref</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (pref === <span class="string">&#x27;dark&#x27;</span> || pref === <span class="string">&#x27;light&#x27;</span>) <span class="keyword">return</span> pref;</span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">window</span>.<span class="title function_">matchMedia</span>(<span class="string">&#x27;(prefers-color-scheme: dark)&#x27;</span>).<span class="property">matches</span> ? <span class="string">&#x27;dark&#x27;</span> : <span class="string">&#x27;light&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">applyTheme</span>(<span class="params">pref</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> effective = <span class="title function_">getEffectiveTheme</span>(pref);</span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">classList</span>.<span class="title function_">toggle</span>(<span class="string">&#x27;dark&#x27;</span>, effective === <span class="string">&#x27;dark&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setTheme</span>(<span class="params">pref</span>) &#123;</span><br><span class="line">  <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="variable constant_">STORAGE_KEY</span>, pref);</span><br><span class="line">  <span class="title function_">applyTheme</span>(pref);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听操作系统深浅模式变化</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">matchMedia</span>(<span class="string">&#x27;(prefers-color-scheme: dark)&#x27;</span>).<span class="title function_">addEventListener</span>(<span class="string">&#x27;change&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> current = <span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="variable constant_">STORAGE_KEY</span>) || <span class="string">&#x27;system&#x27;</span>;</span><br><span class="line">  <span class="keyword">if</span> (current === <span class="string">&#x27;system&#x27;</span>) &#123;</span><br><span class="line">    <span class="title function_">applyTheme</span>(<span class="string">&#x27;system&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="4-常见框架生态方案"><a href="#4-常见框架生态方案" class="headerlink" title="4. 常见框架生态方案"></a>4. 常见框架生态方案</h4><p>写具体业务时通常不需要重复造轮子：</p><ul><li><strong>Tailwind CSS</strong>：在配置中设置 <code>darkMode: &#39;class&#39;</code>。</li><li><strong>React &#x2F; Next.js</strong>：直接使用社区标配 <a href="https://github.com/pacocoursey/next-themes"><code>next-themes</code></a>，内置了 SSR 防闪烁与三态管理。</li><li><strong>Vue &#x2F; Nuxt</strong>：使用 <a href="https://vueuse.org/core/useDark/"><code>@vueuse/core</code></a> 的 <code>useDark()</code> 与 <code>useColorMode()</code>。</li></ul><h2 id="10-核心维护与排错命令速查"><a href="#10-核心维护与排错命令速查" class="headerlink" title="10. 核心维护与排错命令速查"></a>10. 核心维护与排错命令速查</h2><p>平时调试深浅色、主题状态或包管理时，可以直接用这些命令查询：</p><table><thead><tr><th align="left">查询 &#x2F; 操作目的</th><th align="left">终端命令</th></tr></thead><tbody><tr><td align="left"><strong>查看当前深浅色状态</strong></td><td align="left"><code>gsettings get org.gnome.desktop.interface color-scheme</code></td></tr><tr><td align="left"><strong>查看当前 GTK 主题</strong></td><td align="left"><code>gsettings get org.gnome.desktop.interface gtk-theme</code></td></tr><tr><td align="left"><strong>查看当前图标主题</strong></td><td align="left"><code>gsettings get org.gnome.desktop.interface icon-theme</code></td></tr><tr><td align="left"><strong>查看当前 GNOME Shell 主题</strong></td><td align="left"><code>gsettings get org.gnome.shell.extensions.user-theme name</code></td></tr><tr><td align="left"><strong>切换为深色模式</strong></td><td align="left"><code>gsettings set org.gnome.desktop.interface color-scheme &#39;prefer-dark&#39;</code></td></tr><tr><td align="left"><strong>切换为浅色模式</strong></td><td align="left"><code>gsettings set org.gnome.desktop.interface color-scheme &#39;default&#39;</code></td></tr><tr><td align="left"><strong>查询 D-Bus Portal 真实广播值</strong></td><td align="left"><code>dbus-send --session --print-reply=literal --dest=org.freedesktop.portal.Desktop /org/freedesktop/portal/desktop org.freedesktop.portal.Settings.Read string:org.freedesktop.appearance string:color-scheme</code></td></tr><tr><td align="left"><strong>更新所有 Flatpak 应用</strong></td><td align="left"><code>flatpak update</code></td></tr><tr><td align="left"><strong>清理未使用的 Flatpak 运行时</strong></td><td align="left"><code>flatpak uninstall --unused</code></td></tr></tbody></table>]]>
      </content:encoded>
    </item>
    <item>
      <title>用几个不同的大模型接力，我写了个原生的 macOS 版 WO Mic</title>
      <link>https://luanyufei.cn/2026/08/20/native-macos-womic-client-macmic/</link>
      <description>
        <![CDATA[<p>Mac 平台一直缺少官方的 WO Mic 客户端，市面上也很难找到足够轻量、好用的替代方案。平时如果临时想把手机当成 Mac 的无线麦克风，往往需要复杂的配置或忍受高延迟的网页中转。</p>
<p>既然没有现成的工具，不如自己动手写一个。我用纯 Swift 和 SwiftUI 开发了原生客户端 <a href="https://github.com/luanyufei/MacMic">MacMic</a>，体积小、冷启动快、无须冗余的后台驻留服务。</p>
<p>原本以为这只是个简单的网络接收与音频解码工具——SwiftUI 界面和 Opus 解码器很快就搭建完毕。但随后在虚拟音频路由和闭源私有通信协议上踩到的深坑，远超最初的预期。整个排查与解决过程，经历了几个不同大模型的接力才得以彻底破局。</p>
<h2 id="第一轮：Gemini-3-7-Flash-的驱动弯路与协议假死"><a href="#第一轮：Gemini-3-7-Flash-的驱动弯路与协议假死" class="headerlink" title="第一轮：Gemini 3.7 Flash 的驱动弯路与协议假死"></a>第一轮：Gemini 3.7 Flash 的驱动弯路与协议假死</h2><p>最初我使用 Gemini 3.7 Flash 来搭建骨架、实现基础网络通信和音频输入管线。</p>
<p>在音频输入这一步，Gemini 3.7 Flash 首先建议自研一套虚拟音频 HAL（Hardware Abstraction Layer）驱动，并生成了对应的 C&#x2F;C++ 驱动源码。然而在 macOS Sequoia 与 Apple Silicon 架构下，系统对内核扩展及 DriverKit &#x2F; CoreAudio 插件的签名与公证有着极其严格的要求。未签名的驱动会被 <code>coreaudiod</code> 直接拒载，甚至可能导致系统音频服务假死。为了避免陷入驱动签名的泥潭，我果断放弃了自研驱动路线，改为调用 macOS 开源社区成熟且拥有官方公证的环回音频驱动 <strong>BlackHole 2ch</strong>。</p>
<p>解决了音频路由后，真正的硬骨头落在了网络协议上。</p>
<p>WO Mic 官方从未公开过通信协议规范。按照常规网络编程逻辑，Gemini 3.7 Flash 编写了标准的 TCP Socket 通信代码。然而每次连接 iPhone 时，程序都会卡死在握手阶段，或者刚发出数据就收到手机端的 <code>Connection reset by peer</code>。</p>
<p>通过 Wireshark 抓包确认，手机端的 8125 端口处于正常监听状态，但只要客户端发包，连接就会立刻被对端重置。调整重试机制、超时时间与包头格式均无济于事。面对无任何公开文档的非标准私有协议，常规的参数推演容易陷入反复试错的循环。</p>
<h2 id="第二轮：Opus-4-6-逆向-Windows-二进制与发现分段时序"><a href="#第二轮：Opus-4-6-逆向-Windows-二进制与发现分段时序" class="headerlink" title="第二轮：Opus 4.6 逆向 Windows 二进制与发现分段时序"></a>第二轮：Opus 4.6 逆向 Windows 二进制与发现分段时序</h2><p>为了突破瓶颈，我提取了官方 Windows 客户端 <code>WOMicClient.exe</code>，转由擅长逆向分析的 Opus 4.6 处理，借助 Python 的 <code>pefile</code> 与 <code>capstone</code> 库反汇编其二进制文件。</p>
<p>通过逆向分析，摸清了 Windows 客户端的基础握手帧格式：</p>
<ul>
<li><strong>握手包结构</strong>：<code>[1 字节 cmd] [4 字节大端 length] [payload 内容]</code></li>
</ul>
<p>但直接将完整数据包（例如 11 字节的 <code>0x65</code> 版本校验包）一次性发送至 iPhone 时，iOS 端依然会立即切断连接。Opus 在反复比对汇编逻辑与抓包数据后，捕捉到了一个关键细节：<strong>iOS 端的底层 Socket 读取采用分步阻塞机制</strong>。其逻辑是先 <code>read(1)</code> 读取 1 字节命令，再 <code>read(4)</code> 读取 4 字节长度，最后根据长度读取对应 Payload。</p>
<p>在 TCP 传输层面，若客户端调用单次 <code>sendall()</code> 将整包直接推入缓冲区，iOS 端的读取时序就会错乱，进而触发 RST 错误。</p>
<p>针对这一特性的解决方案十分明确：必须将单个控制包强制拆分为三次独立发送。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">s.sendall(<span class="built_in">bytes</span>([cmd_byte]))           <span class="comment"># 1. 发送 1 字节命令</span></span><br><span class="line">s.sendall(struct.pack(<span class="string">&quot;&gt;I&quot;</span>, length))   <span class="comment"># 2. 发送 4 字节载荷长度（大端序）</span></span><br><span class="line"><span class="keyword">if</span> payload:</span><br><span class="line">    s.sendall(payload)                 <span class="comment"># 3. 发送载荷内容</span></span><br></pre></td></tr></table></figure>

<p>改用分段发送后，握手流程顺利走通：</p>
<ul>
<li><code>0x65</code>（CheckVersion）返回版本校验通过。</li>
<li><code>0x66</code>（SetCodec）返回 <code>0x00</code> 确认。</li>
<li><code>0x67</code>（StartCapture）返回 <code>0x00</code> 确认。</li>
</ul>
<p>然而握手虽然通过，控制通道上却迟迟没有连续的音频流传入。此时第一天的调用额度耗尽，Opus 将所有反汇编笔记与端口分析结果整理后交付。</p>
<h2 id="第三轮：Gemini-3-1-Pro-的推论假象与排查误区"><a href="#第三轮：Gemini-3-1-Pro-的推论假象与排查误区" class="headerlink" title="第三轮：Gemini 3.1 Pro 的推论假象与排查误区"></a>第三轮：Gemini 3.1 Pro 的推论假象与排查误区</h2><p>我将已有的逆向笔记整理后交由 Gemini 3.1 Pro 接力分析。</p>
<p>Gemini 3.1 Pro 分析后给出了一个推断：iOS 端在处理完 <code>0x67 StartCapture</code> 后会主动关闭 TCP，认为 TCP 仅为一次性信令通道，音频流必定由 UDP 传输；而 UDP 未收到数据，则是由于 macOS 系统防火墙静默拦截了终端 Python 脚本的网络访问。它建议将代码迁移进 Swift 并打包为 <code>.app</code> 应用，以触发系统的本地网络权限弹窗。</p>
<p>按照该思路完成 Swift 移植并打包运行后，应用确实弹出了系统网络权限请求，但实际测试中暴露出更多底层问题：</p>
<ul>
<li>iOS 端建立连接后，准时在第 5 秒发生断联。</li>
<li>Android 端始终显示已连接，但 Mac 客户端完全接收不到音频数据。</li>
<li>手机端主动断开后，Mac 端界面依然停留在“已连接”状态。</li>
</ul>
<p>事实表明，“一次性信令通道”和“防火墙拦截”的猜测并未触及核心问题。</p>
<h2 id="第四轮：Opus-4-6-反编译-APK-源码彻底破局"><a href="#第四轮：Opus-4-6-反编译-APK-源码彻底破局" class="headerlink" title="第四轮：Opus 4.6 反编译 APK 源码彻底破局"></a>第四轮：Opus 4.6 反编译 APK 源码彻底破局</h2><p>推测无法替代事实。为了彻底搞清服务端逻辑，我将 Windows 安装包与 Android 官方 APK（<code>app-release-4_8.apk</code>）一同导入项目，交由恢复额度的 Opus 4.6 展开深度反编译。</p>
<p>借助 <code>jadx</code> 反编译出服务端的 Java 源码后，所有谜团迎刃而解：</p>
<h3 id="1-音频无声之谜：被误传的-UDP-端口"><a href="#1-音频无声之谜：被误传的-UDP-端口" class="headerlink" title="1. 音频无声之谜：被误传的 UDP 端口"></a>1. 音频无声之谜：被误传的 UDP 端口</h3><p>在反编译文件 <code>d2/d.java</code> 中，处理 <code>0x66</code>（配置编码）的逻辑揭示了协议真实定义：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="built_in">this</span>.f12091q = dataInputStream.readByte();  <span class="comment">// 编码类型 (0x02 为 Opus)</span></span><br><span class="line"><span class="built_in">this</span>.f12092r = dataInputStream.readByte();  <span class="comment">// 采样率档位 (0=8k, 1=16k, 2=48k)</span></span><br><span class="line"><span class="built_in">this</span>.f12093s = dataInputStream.readInt();   <span class="comment">// 客户端用于接收音频的 UDP 端口号</span></span><br></pre></td></tr></table></figure>

<p>此前根据常规认知，最后 4 字节被当成了采样率数值 <code>48000</code>（<code>0x0000BB80</code>）发送。手机收到后，误认为 Mac 客户端的 UDP 接收端口为 48000，便将所有音频包发往 Mac 的 48000 端口，而客户端一直在 8125 端口监听等待。</p>
<p>将最后 4 字节修正为客户端真实的 UDP 监听端口（<code>8125</code>，十六进制 <code>0x00001FBD</code>）后，UDP 音频数据包瞬间恢复正常接收。</p>
<p>同时，反编译源码进一步确认了 UDP 音频包的 11 字节头部格式：<br><code>[协议版本 (2 字节大端 0x0400)] [长度 (2 字节)] [序号 (2 字节)] [时间戳 (4 字节)] [标志 (1 字节)]</code>，第 11 字节之后即为标准的 Opus 裸音频流。</p>
<h3 id="2-五秒断联真相：指令混淆与保活机制"><a href="#2-五秒断联真相：指令混淆与保活机制" class="headerlink" title="2. 五秒断联真相：指令混淆与保活机制"></a>2. 五秒断联真相：指令混淆与保活机制</h3><p>在 <code>V1/l.java</code> 与 <code>A/g.java</code> 中，服务端在收到 <code>0x67</code> 开始采集指令后，会在后台启动一个 5000ms 的超时定时器 <code>TimerTask</code>（<code>V1/k.java</code>）：若 5 秒内未收到心跳保活包，服务端将判定客户端断开，并主动销毁音频采集线程。</p>
<p>更关键的是指令定义的混淆：</p>
<ul>
<li><code>0x68</code>（104）：在源码中实际对应 <code>CMD_STOP_CAPTURE</code>（停止采集）。</li>
<li><code>0x69</code>（105）：才是真正的 <code>CMD_HEARTBEAT</code>（重置 5 秒倒计时的保活心跳）。</li>
</ul>
<p>此前代码在轮询线程中每 3 秒发送一次 <code>0x68</code>，无异于持续要求手机停止采集；而在 Swift 客户端中仅发送了 UDP ping，未在 TCP 控制通道发送 <code>0x69</code> 心跳，导致服务端在第 5 秒准时触发超时自毁。</p>
<p>源码同时表明，服务端不会对 <code>0x69</code> 心跳包返回任何应答，客户端只需每隔 2 秒单向发送一次空的 <code>0x69</code> 控制帧即可保持连接。</p>
<h3 id="3-连接状态与断联同步"><a href="#3-连接状态与断联同步" class="headerlink" title="3. 连接状态与断联同步"></a>3. 连接状态与断联同步</h3><p>在捕获到 TCP 连接断开及心跳异常事件后，完善状态机的通知流转机制。一旦手机端挂断或异常离线，Mac 客户端能够即时感知并自动切换回待连接状态。</p>
<h2 id="落地效果与验证"><a href="#落地效果与验证" class="headerlink" title="落地效果与验证"></a>落地效果与验证</h2><p>将上述协议逻辑完整实现于 Swift 原生客户端后，整体通信链路恢复稳定：</p>
<ul>
<li><strong>音频传输</strong>：音频流以 50 包&#x2F;秒（每帧 20ms）稳定接收并解码，长时间运行无断流与卡顿。</li>
<li><strong>实时监测</strong>：SwiftUI 界面上的 VU 电平表随输入声音实时动态响应，感知延迟极低。</li>
<li><strong>增益控制</strong>：支持 0% 至 300% 的音量平滑增益调节与一键静音切换。</li>
<li><strong>系统集成</strong>：音频输出目标设为 BlackHole 2ch 后，微信、飞书、腾讯会议等各类通讯软件均可直接将其选为系统默认输入麦克风。</li>
</ul>
<h2 id="总结与项目地址"><a href="#总结与项目地址" class="headerlink" title="总结与项目地址"></a>总结与项目地址</h2><p>完整项目源码与构建说明已开源在 GitHub：<br>👉 <a href="https://github.com/luanyufei/MacMic">https://github.com/luanyufei/MacMic</a></p>
<p>在缺乏文档的私有协议逆向过程中，推断往往伴随着盲区。无论是对防火墙拦截的猜想，还是对参数含义的惯性假设，都会耗费大量的调试成本，而实际逻辑始终隐藏在服务端的二进制字节中。</p>
<p>在多模型协作的分工上：</p>
<ul>
<li><strong>Gemini 3.7 Flash</strong> 擅长快速搭建 UI 框架、组织模块架构与编写通用胶水代码；</li>
<li><strong>Opus 4.6</strong> 则在二进制反汇编、反编译源码逻辑推导及协议逆向分析中表现出色，能够精准定位异常时序与字段定义。</li>
</ul>
<p>当提供真实的代码上下文与反编译目标后，大模型基于代码语义与时序逻辑的精细排查，能够大幅提升逆向工程与协议还原的效率。</p>]]>
      </description>
      <author>noonyjufee@gmail.com (乱与狒)</author>
      <category domain="https://luanyufei.cn/categories/%E5%BC%80%E5%8F%91%E8%AE%B0%E5%BD%95/">开发记录</category>
      <category domain="https://luanyufei.cn/tags/macOS/">macOS</category>
      <category domain="https://luanyufei.cn/tags/Swift/">Swift</category>
      <category domain="https://luanyufei.cn/tags/%E9%80%86%E5%90%91%E5%B7%A5%E7%A8%8B/">逆向工程</category>
      <category domain="https://luanyufei.cn/tags/%E5%A4%A7%E6%A8%A1%E5%9E%8B/">大模型</category>
      <category domain="https://luanyufei.cn/tags/WO-Mic/">WO Mic</category>
      <pubDate>Thu, 20 Aug 2026 14:45:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Mac 平台一直缺少官方的 WO Mic 客户端，市面上也很难找到足够轻量、好用的替代方案。平时如果临时想把手机当成 Mac 的无线麦克风，往往需要复杂的配置或忍受高延迟的网页中转。</p><p>既然没有现成的工具，不如自己动手写一个。我用纯 Swift 和 SwiftUI 开发了原生客户端 <a href="https://github.com/luanyufei/MacMic">MacMic</a>，体积小、冷启动快、无须冗余的后台驻留服务。</p><p>原本以为这只是个简单的网络接收与音频解码工具——SwiftUI 界面和 Opus 解码器很快就搭建完毕。但随后在虚拟音频路由和闭源私有通信协议上踩到的深坑，远超最初的预期。整个排查与解决过程，经历了几个不同大模型的接力才得以彻底破局。</p><h2 id="第一轮：Gemini-3-7-Flash-的驱动弯路与协议假死"><a href="#第一轮：Gemini-3-7-Flash-的驱动弯路与协议假死" class="headerlink" title="第一轮：Gemini 3.7 Flash 的驱动弯路与协议假死"></a>第一轮：Gemini 3.7 Flash 的驱动弯路与协议假死</h2><p>最初我使用 Gemini 3.7 Flash 来搭建骨架、实现基础网络通信和音频输入管线。</p><p>在音频输入这一步，Gemini 3.7 Flash 首先建议自研一套虚拟音频 HAL（Hardware Abstraction Layer）驱动，并生成了对应的 C&#x2F;C++ 驱动源码。然而在 macOS Sequoia 与 Apple Silicon 架构下，系统对内核扩展及 DriverKit &#x2F; CoreAudio 插件的签名与公证有着极其严格的要求。未签名的驱动会被 <code>coreaudiod</code> 直接拒载，甚至可能导致系统音频服务假死。为了避免陷入驱动签名的泥潭，我果断放弃了自研驱动路线，改为调用 macOS 开源社区成熟且拥有官方公证的环回音频驱动 <strong>BlackHole 2ch</strong>。</p><p>解决了音频路由后，真正的硬骨头落在了网络协议上。</p><p>WO Mic 官方从未公开过通信协议规范。按照常规网络编程逻辑，Gemini 3.7 Flash 编写了标准的 TCP Socket 通信代码。然而每次连接 iPhone 时，程序都会卡死在握手阶段，或者刚发出数据就收到手机端的 <code>Connection reset by peer</code>。</p><p>通过 Wireshark 抓包确认，手机端的 8125 端口处于正常监听状态，但只要客户端发包，连接就会立刻被对端重置。调整重试机制、超时时间与包头格式均无济于事。面对无任何公开文档的非标准私有协议，常规的参数推演容易陷入反复试错的循环。</p><h2 id="第二轮：Opus-4-6-逆向-Windows-二进制与发现分段时序"><a href="#第二轮：Opus-4-6-逆向-Windows-二进制与发现分段时序" class="headerlink" title="第二轮：Opus 4.6 逆向 Windows 二进制与发现分段时序"></a>第二轮：Opus 4.6 逆向 Windows 二进制与发现分段时序</h2><p>为了突破瓶颈，我提取了官方 Windows 客户端 <code>WOMicClient.exe</code>，转由擅长逆向分析的 Opus 4.6 处理，借助 Python 的 <code>pefile</code> 与 <code>capstone</code> 库反汇编其二进制文件。</p><p>通过逆向分析，摸清了 Windows 客户端的基础握手帧格式：</p><ul><li><strong>握手包结构</strong>：<code>[1 字节 cmd] [4 字节大端 length] [payload 内容]</code></li></ul><p>但直接将完整数据包（例如 11 字节的 <code>0x65</code> 版本校验包）一次性发送至 iPhone 时，iOS 端依然会立即切断连接。Opus 在反复比对汇编逻辑与抓包数据后，捕捉到了一个关键细节：<strong>iOS 端的底层 Socket 读取采用分步阻塞机制</strong>。其逻辑是先 <code>read(1)</code> 读取 1 字节命令，再 <code>read(4)</code> 读取 4 字节长度，最后根据长度读取对应 Payload。</p><p>在 TCP 传输层面，若客户端调用单次 <code>sendall()</code> 将整包直接推入缓冲区，iOS 端的读取时序就会错乱，进而触发 RST 错误。</p><p>针对这一特性的解决方案十分明确：必须将单个控制包强制拆分为三次独立发送。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">s.sendall(<span class="built_in">bytes</span>([cmd_byte]))           <span class="comment"># 1. 发送 1 字节命令</span></span><br><span class="line">s.sendall(struct.pack(<span class="string">&quot;&gt;I&quot;</span>, length))   <span class="comment"># 2. 发送 4 字节载荷长度（大端序）</span></span><br><span class="line"><span class="keyword">if</span> payload:</span><br><span class="line">    s.sendall(payload)                 <span class="comment"># 3. 发送载荷内容</span></span><br></pre></td></tr></table></figure><p>改用分段发送后，握手流程顺利走通：</p><ul><li><code>0x65</code>（CheckVersion）返回版本校验通过。</li><li><code>0x66</code>（SetCodec）返回 <code>0x00</code> 确认。</li><li><code>0x67</code>（StartCapture）返回 <code>0x00</code> 确认。</li></ul><p>然而握手虽然通过，控制通道上却迟迟没有连续的音频流传入。此时第一天的调用额度耗尽，Opus 将所有反汇编笔记与端口分析结果整理后交付。</p><h2 id="第三轮：Gemini-3-1-Pro-的推论假象与排查误区"><a href="#第三轮：Gemini-3-1-Pro-的推论假象与排查误区" class="headerlink" title="第三轮：Gemini 3.1 Pro 的推论假象与排查误区"></a>第三轮：Gemini 3.1 Pro 的推论假象与排查误区</h2><p>我将已有的逆向笔记整理后交由 Gemini 3.1 Pro 接力分析。</p><p>Gemini 3.1 Pro 分析后给出了一个推断：iOS 端在处理完 <code>0x67 StartCapture</code> 后会主动关闭 TCP，认为 TCP 仅为一次性信令通道，音频流必定由 UDP 传输；而 UDP 未收到数据，则是由于 macOS 系统防火墙静默拦截了终端 Python 脚本的网络访问。它建议将代码迁移进 Swift 并打包为 <code>.app</code> 应用，以触发系统的本地网络权限弹窗。</p><p>按照该思路完成 Swift 移植并打包运行后，应用确实弹出了系统网络权限请求，但实际测试中暴露出更多底层问题：</p><ul><li>iOS 端建立连接后，准时在第 5 秒发生断联。</li><li>Android 端始终显示已连接，但 Mac 客户端完全接收不到音频数据。</li><li>手机端主动断开后，Mac 端界面依然停留在“已连接”状态。</li></ul><p>事实表明，“一次性信令通道”和“防火墙拦截”的猜测并未触及核心问题。</p><h2 id="第四轮：Opus-4-6-反编译-APK-源码彻底破局"><a href="#第四轮：Opus-4-6-反编译-APK-源码彻底破局" class="headerlink" title="第四轮：Opus 4.6 反编译 APK 源码彻底破局"></a>第四轮：Opus 4.6 反编译 APK 源码彻底破局</h2><p>推测无法替代事实。为了彻底搞清服务端逻辑，我将 Windows 安装包与 Android 官方 APK（<code>app-release-4_8.apk</code>）一同导入项目，交由恢复额度的 Opus 4.6 展开深度反编译。</p><p>借助 <code>jadx</code> 反编译出服务端的 Java 源码后，所有谜团迎刃而解：</p><h3 id="1-音频无声之谜：被误传的-UDP-端口"><a href="#1-音频无声之谜：被误传的-UDP-端口" class="headerlink" title="1. 音频无声之谜：被误传的 UDP 端口"></a>1. 音频无声之谜：被误传的 UDP 端口</h3><p>在反编译文件 <code>d2/d.java</code> 中，处理 <code>0x66</code>（配置编码）的逻辑揭示了协议真实定义：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="built_in">this</span>.f12091q = dataInputStream.readByte();  <span class="comment">// 编码类型 (0x02 为 Opus)</span></span><br><span class="line"><span class="built_in">this</span>.f12092r = dataInputStream.readByte();  <span class="comment">// 采样率档位 (0=8k, 1=16k, 2=48k)</span></span><br><span class="line"><span class="built_in">this</span>.f12093s = dataInputStream.readInt();   <span class="comment">// 客户端用于接收音频的 UDP 端口号</span></span><br></pre></td></tr></table></figure><p>此前根据常规认知，最后 4 字节被当成了采样率数值 <code>48000</code>（<code>0x0000BB80</code>）发送。手机收到后，误认为 Mac 客户端的 UDP 接收端口为 48000，便将所有音频包发往 Mac 的 48000 端口，而客户端一直在 8125 端口监听等待。</p><p>将最后 4 字节修正为客户端真实的 UDP 监听端口（<code>8125</code>，十六进制 <code>0x00001FBD</code>）后，UDP 音频数据包瞬间恢复正常接收。</p><p>同时，反编译源码进一步确认了 UDP 音频包的 11 字节头部格式：<br><code>[协议版本 (2 字节大端 0x0400)] [长度 (2 字节)] [序号 (2 字节)] [时间戳 (4 字节)] [标志 (1 字节)]</code>，第 11 字节之后即为标准的 Opus 裸音频流。</p><h3 id="2-五秒断联真相：指令混淆与保活机制"><a href="#2-五秒断联真相：指令混淆与保活机制" class="headerlink" title="2. 五秒断联真相：指令混淆与保活机制"></a>2. 五秒断联真相：指令混淆与保活机制</h3><p>在 <code>V1/l.java</code> 与 <code>A/g.java</code> 中，服务端在收到 <code>0x67</code> 开始采集指令后，会在后台启动一个 5000ms 的超时定时器 <code>TimerTask</code>（<code>V1/k.java</code>）：若 5 秒内未收到心跳保活包，服务端将判定客户端断开，并主动销毁音频采集线程。</p><p>更关键的是指令定义的混淆：</p><ul><li><code>0x68</code>（104）：在源码中实际对应 <code>CMD_STOP_CAPTURE</code>（停止采集）。</li><li><code>0x69</code>（105）：才是真正的 <code>CMD_HEARTBEAT</code>（重置 5 秒倒计时的保活心跳）。</li></ul><p>此前代码在轮询线程中每 3 秒发送一次 <code>0x68</code>，无异于持续要求手机停止采集；而在 Swift 客户端中仅发送了 UDP ping，未在 TCP 控制通道发送 <code>0x69</code> 心跳，导致服务端在第 5 秒准时触发超时自毁。</p><p>源码同时表明，服务端不会对 <code>0x69</code> 心跳包返回任何应答，客户端只需每隔 2 秒单向发送一次空的 <code>0x69</code> 控制帧即可保持连接。</p><h3 id="3-连接状态与断联同步"><a href="#3-连接状态与断联同步" class="headerlink" title="3. 连接状态与断联同步"></a>3. 连接状态与断联同步</h3><p>在捕获到 TCP 连接断开及心跳异常事件后，完善状态机的通知流转机制。一旦手机端挂断或异常离线，Mac 客户端能够即时感知并自动切换回待连接状态。</p><h2 id="落地效果与验证"><a href="#落地效果与验证" class="headerlink" title="落地效果与验证"></a>落地效果与验证</h2><p>将上述协议逻辑完整实现于 Swift 原生客户端后，整体通信链路恢复稳定：</p><ul><li><strong>音频传输</strong>：音频流以 50 包&#x2F;秒（每帧 20ms）稳定接收并解码，长时间运行无断流与卡顿。</li><li><strong>实时监测</strong>：SwiftUI 界面上的 VU 电平表随输入声音实时动态响应，感知延迟极低。</li><li><strong>增益控制</strong>：支持 0% 至 300% 的音量平滑增益调节与一键静音切换。</li><li><strong>系统集成</strong>：音频输出目标设为 BlackHole 2ch 后，微信、飞书、腾讯会议等各类通讯软件均可直接将其选为系统默认输入麦克风。</li></ul><h2 id="总结与项目地址"><a href="#总结与项目地址" class="headerlink" title="总结与项目地址"></a>总结与项目地址</h2><p>完整项目源码与构建说明已开源在 GitHub：<br>👉 <a href="https://github.com/luanyufei/MacMic">https://github.com/luanyufei/MacMic</a></p><p>在缺乏文档的私有协议逆向过程中，推断往往伴随着盲区。无论是对防火墙拦截的猜想，还是对参数含义的惯性假设，都会耗费大量的调试成本，而实际逻辑始终隐藏在服务端的二进制字节中。</p><p>在多模型协作的分工上：</p><ul><li><strong>Gemini 3.7 Flash</strong> 擅长快速搭建 UI 框架、组织模块架构与编写通用胶水代码；</li><li><strong>Opus 4.6</strong> 则在二进制反汇编、反编译源码逻辑推导及协议逆向分析中表现出色，能够精准定位异常时序与字段定义。</li></ul><p>当提供真实的代码上下文与反编译目标后，大模型基于代码语义与时序逻辑的精细排查，能够大幅提升逆向工程与协议还原的效率。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>
        <![CDATA[Shadowrocket & Clash 进阶优化指南]]>
      </title>
      <link>https://luanyufei.cn/2026/08/19/shadowrocket-clash-advanced-guide/</link>
      <description>
        <![CDATA[<p>机场默认提供的订阅配置通常只做最基础的粗粒度分流，直接拿来当主力用经常会碰到各种瓶颈：大流量机场看视频流畅，但出口多为机房 IP，访问 Gemini、Claude 极易触发风控限制；专线节点纯净度高，但价格贵流量少，拿来跑日常下载太浪费。同时，移动端的客户端如果没有配置好，自己辛辛苦苦改好的分流规则一刷新就会被机场原始配置全量覆盖。</p>
<p>本文整理了一套通用的进阶优化经验，并提供 iOS、电脑端以及安卓端的完整落地配置参考模板。</p>
<h2 id="优化经验"><a href="#优化经验" class="headerlink" title="优化经验"></a>优化经验</h2><h3 id="1-协议降级：阻断-QUIC-UDP-443-规避丢包卡顿"><a href="#1-协议降级：阻断-QUIC-UDP-443-规避丢包卡顿" class="headerlink" title="1. 协议降级：阻断 QUIC (UDP 443) 规避丢包卡顿"></a>1. 协议降级：阻断 QUIC (UDP 443) 规避丢包卡顿</h3><p>Chromium 内核浏览器与移动端 App 访问 Google 资产时，默认优先走基于 UDP 443 端口的 QUIC 协议。大多数代理节点对 UDP 转发的优化有限，极易发生握手挂起或高丢包，导致页面白屏转圈。</p>
<p>在规则链顶层显式拦截目标域名的 UDP 443 请求，迫使浏览器在超时前收到拒绝响应，从而毫秒级平滑降级到稳定的 TCP (HTTPS) 通道。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line"># Shadowrocket 写法</span><br><span class="line">AND,((DEST-PORT,443),(PROTOCOL,UDP),(DOMAIN-SUFFIX,google.com)),REJECT</span><br><span class="line">AND,((DEST-PORT,443),(PROTOCOL,UDP),(DOMAIN-SUFFIX,googleapis.com)),REJECT</span><br><span class="line"></span><br><span class="line"># Clash 写法</span><br><span class="line">- AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,google.com)),REJECT</span><br><span class="line">- AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,googleapis.com)),REJECT</span><br></pre></td></tr></table></figure>

<h3 id="2-DNS-防污染与-CDN-本地化：防止国内流量误入代理"><a href="#2-DNS-防污染与-CDN-本地化：防止国内流量误入代理" class="headerlink" title="2. DNS 防污染与 CDN 本地化：防止国内流量误入代理"></a>2. DNS 防污染与 CDN 本地化：防止国内流量误入代理</h3><p>很多网上的配置为了防污染，把客户端 DNS 强行指向 <code>8.8.8.8</code> 并关闭系统 DNS。在国内环境下，向境外公共 DNS 发起纯文本 UDP 53 查询极易被劫持；且境外的 Anycast DNS 在解析微信、淘宝等国内服务时，会分配海外 CDN 节点，导致下游的 <code>GEOIP,CN</code> 规则误判失效，让国内流量白白绕道代理。</p>
<p>应保留国内公共 DNS 或系统 DNS 负责国内域名解析，海外域名交由代理节点远程解析。</p>
<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Shadowrocket 配置示例</span></span><br><span class="line"><span class="attr">dns-server</span> = system, <span class="number">223.5</span>.<span class="number">5.5</span>, <span class="number">119.29</span>.<span class="number">29.29</span></span><br><span class="line"><span class="attr">fallback-dns-server</span> = <span class="number">8.8</span>.<span class="number">8.8</span>, <span class="number">1.1</span>.<span class="number">1.1</span></span><br><span class="line"><span class="attr">dns-direct-system</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure>

<h3 id="3-多订阅协同：视频大流量前置，剥离专线消耗"><a href="#3-多订阅协同：视频大流量前置，剥离专线消耗" class="headerlink" title="3. 多订阅协同：视频大流量前置，剥离专线消耗"></a>3. 多订阅协同：视频大流量前置，剥离专线消耗</h3><p>设定两套订阅角色：订阅 A（主力日常）承载大文件下载与 YouTube 4K 视频；订阅 B（专线 &#x2F; 解锁）仅用于 AI 请求。</p>
<p>为了防止看视频把昂贵的专线流量耗尽，由于代理规则是从上往下匹配的，必须把 <code>googlevideo.com</code> 等视频域名写在前面绑定主力机场，将剩余的 Google 服务收拢到专线。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line"># 1. 视频请求提前被拦截走主力机场</span><br><span class="line">DOMAIN-SUFFIX,googlevideo.com,主力机场</span><br><span class="line">DOMAIN-SUFFIX,youtube.com,主力机场</span><br><span class="line"></span><br><span class="line"># 2. 剩余的 AI 请求再进专线策略组</span><br><span class="line">DOMAIN-KEYWORD,gemini,AI专线</span><br></pre></td></tr></table></figure>

<h3 id="4-AI-会话闭环：底层鉴权与推理接口全量收拢"><a href="#4-AI-会话闭环：底层鉴权与推理接口全量收拢" class="headerlink" title="4. AI 会话闭环：底层鉴权与推理接口全量收拢"></a>4. AI 会话闭环：底层鉴权与推理接口全量收拢</h3><p>Gemini、Claude 等服务由多组后端分布式接口协同工作。如果规则只写了前端域名 <code>gemini.google.com</code>，页面通信时发起的后台鉴权 API （如 <code>clients6</code>）就会滑落到兜底规则走主力机场。服务端检测到同一会话的请求 IP 撕裂，就会判定会话异常并直接中断。</p>
<p>必须对底层 API 进行全域收拢，确保所有通信都在同一节点 IP 下进行。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">DOMAIN-SUFFIX,gemini.google.com,AI专线</span><br><span class="line">DOMAIN-SUFFIX,generativelanguage.googleapis.com,AI专线</span><br><span class="line">DOMAIN-SUFFIX,clients6.google.com,AI专线</span><br><span class="line">DOMAIN-SUFFIX,oauth2.googleapis.com,AI专线</span><br><span class="line">DOMAIN-SUFFIX,googleusercontent.com,AI专线</span><br></pre></td></tr></table></figure>

<h3 id="5-节点池净化：剔除机场虚拟提示节点"><a href="#5-节点池净化：剔除机场虚拟提示节点" class="headerlink" title="5. 节点池净化：剔除机场虚拟提示节点"></a>5. 节点池净化：剔除机场虚拟提示节点</h3><p>很多机场会在节点列表中插入 <code>剩余流量：985.3 GB</code>、<code>距离下次重置：27 天</code> 的虚拟节点。如果直接把节点列表全量写进静态的 <code>自动选择</code> 或 <code>故障转移</code> 策略组，用户消耗了流量或跨越了日期，机场服务端下发的节点名称就会变化。策略组在本地找不到写死的旧名称，内核就会抛出 <code>&#39;剩余流量：xxx&#39; not found</code> 错误并直接拒绝启动。</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Clash 策略组中，严禁混入虚拟节点，仅填写真实的服务器节点名称</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">自动选择</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">url-test</span></span><br><span class="line">  <span class="attr">proxies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">🇯🇵日本-01</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">🇺🇸美国-01</span></span><br><span class="line">    <span class="comment"># 绝对不能填入 - 剩余流量：985.3 GB</span></span><br></pre></td></tr></table></figure>

<h3 id="6-移动端本地化持久：防止订阅更新覆盖自建分流"><a href="#6-移动端本地化持久：防止订阅更新覆盖自建分流" class="headerlink" title="6. 移动端本地化持久：防止订阅更新覆盖自建分流"></a>6. 移动端本地化持久：防止订阅更新覆盖自建分流</h3><p>安卓端（如 Clash Meta for Android）默认没有桌面端的脚本注入功能。如果在客户端把主订阅作为在线 URL 导入，然后手动去改本地的 <code>config.yaml</code> 加上分流规则，一旦触发定时刷新，客户端就会重新下载机场的原始裸配置，把你写好的规则全量覆盖冲掉。</p>
<p>需要将移动端配置转存为本地文件（<code>File</code> 模式），切断客户端对该文件的自动下载覆盖。副订阅（AI 专线）的节点更新需求，直接通过配置文件里的 <code>proxy-providers</code> 交给内核去独立后台静默拉取。</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 在 File 模式下的本地 config.yaml 中配置独立节点池</span></span><br><span class="line"><span class="attr">proxy-providers:</span></span><br><span class="line">  <span class="attr">ai-provider:</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">http</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">&quot;https://sub-b.com/link&quot;</span></span><br><span class="line">    <span class="attr">interval:</span> <span class="number">86400</span> <span class="comment"># 独立控制后台拉取频率，不依赖客户端 UI 的订阅刷新</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">./proxy_providers/ai_nodes.yaml</span></span><br><span class="line">    <span class="attr">filter:</span> <span class="string">&quot;新加坡|🇸🇬|SG&quot;</span></span><br></pre></td></tr></table></figure>

<h3 id="7-规则库加速：使用国内-CDN-镜像防静默失效"><a href="#7-规则库加速：使用国内-CDN-镜像防静默失效" class="headerlink" title="7. 规则库加速：使用国内 CDN 镜像防静默失效"></a>7. 规则库加速：使用国内 CDN 镜像防静默失效</h3><p>拉取远程规则集失败时（如规则源路径写错，或国内网络直连 GitHub 遇到阻断），Shadowrocket 与 Clash 通常不会报错打断，而是直接将该规则集静默置空。这意味着数万条分流白名单根本没有加载进内存。引用开源规则库时，必须将原版的 GitHub 原生链接替换为 jsDelivr 等国内可稳定直连的 CDN 镜像源。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line"># 错误写法（极易超时丢包）</span><br><span class="line">RULE-SET,https://raw.githubusercontent.com/Loyalsoldier/surge-rules/release/apple.txt,PROXY</span><br><span class="line"></span><br><span class="line"># 正确写法（CDN 稳定直连）</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/apple.txt,PROXY</span><br></pre></td></tr></table></figure>

<h2 id="配置文件参考模板"><a href="#配置文件参考模板" class="headerlink" title="配置文件参考模板"></a>配置文件参考模板</h2><p>以下模板包含了上文提到的所有排坑措施。请根据实际需求替换其中的节点和订阅链接。</p>
<h3 id="iOS-iPadOS-Shadowrocket"><a href="#iOS-iPadOS-Shadowrocket" class="headerlink" title="iOS &#x2F; iPadOS (Shadowrocket)"></a>iOS &#x2F; iPadOS (Shadowrocket)</h3><ol>
<li>在 Shadowrocket 右上角 <code>+</code> 分别导入订阅 A 和订阅 B；</li>
<li>进入底栏 <strong>“配置”</strong>，点击当前配置文件右侧的 <code>(i)</code> -&gt; 点击 <strong>“编辑纯文本”</strong>；</li>
<li>清空原有内容，粘贴下方配置并保存；</li>
<li>回到首页，全局路由选择 <strong>“配置”</strong>，节点列表选中订阅 A 的主力节点。</li>
</ol>
<figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[General]</span></span><br><span class="line"><span class="attr">bypass-system</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">skip-proxy</span> = <span class="number">192.168</span>.<span class="number">0.0</span>/<span class="number">16</span>, <span class="number">10.0</span>.<span class="number">0.0</span>/<span class="number">8</span>, <span class="number">172.16</span>.<span class="number">0.0</span>/<span class="number">12</span>, localhost, *.local, captive.apple.com</span><br><span class="line"><span class="attr">tun-excluded-routes</span> = <span class="number">10.0</span>.<span class="number">0.0</span>/<span class="number">8</span>, <span class="number">100.64</span>.<span class="number">0.0</span>/<span class="number">10</span>, <span class="number">127.0</span>.<span class="number">0.0</span>/<span class="number">8</span>, <span class="number">169.254</span>.<span class="number">0.0</span>/<span class="number">16</span>, <span class="number">172.16</span>.<span class="number">0.0</span>/<span class="number">12</span>, <span class="number">192.0</span>.<span class="number">0.0</span>/<span class="number">24</span>, <span class="number">192.0</span>.<span class="number">2.0</span>/<span class="number">24</span>, <span class="number">192.88</span>.<span class="number">99.0</span>/<span class="number">24</span>, <span class="number">192.168</span>.<span class="number">0.0</span>/<span class="number">16</span>, <span class="number">198.51</span>.<span class="number">100.0</span>/<span class="number">24</span>, <span class="number">203.0</span>.<span class="number">113.0</span>/<span class="number">24</span>, <span class="number">224.0</span>.<span class="number">0.0</span>/<span class="number">4</span>, <span class="number">255.255</span>.<span class="number">255.255</span>/<span class="number">32</span>, <span class="number">239.255</span>.<span class="number">255.250</span>/<span class="number]]>
      </description>
      <author>noonyjufee@gmail.com (乱与狒)</author>
      <category domain="https://luanyufei.cn/categories/%E5%AE%9E%E7%94%A8%E6%8A%80%E5%B7%A7/">实用技巧</category>
      <category domain="https://luanyufei.cn/tags/Shadowrocket/">Shadowrocket</category>
      <category domain="https://luanyufei.cn/tags/Clash/">Clash</category>
      <category domain="https://luanyufei.cn/tags/Mihomo/">Mihomo</category>
      <category domain="https://luanyufei.cn/tags/%E4%BB%A3%E7%90%86%E5%88%86%E6%B5%81/">代理分流</category>
      <category domain="https://luanyufei.cn/tags/AI/">AI</category>
      <category domain="https://luanyufei.cn/tags/Android/">Android</category>
      <category domain="https://luanyufei.cn/tags/iOS/">iOS</category>
      <category domain="https://luanyufei.cn/tags/macOS/">macOS</category>
      <pubDate>Wed, 19 Aug 2026 09:30:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>机场默认提供的订阅配置通常只做最基础的粗粒度分流，直接拿来当主力用经常会碰到各种瓶颈：大流量机场看视频流畅，但出口多为机房 IP，访问 Gemini、Claude 极易触发风控限制；专线节点纯净度高，但价格贵流量少，拿来跑日常下载太浪费。同时，移动端的客户端如果没有配置好，自己辛辛苦苦改好的分流规则一刷新就会被机场原始配置全量覆盖。</p><p>本文整理了一套通用的进阶优化经验，并提供 iOS、电脑端以及安卓端的完整落地配置参考模板。</p><h2 id="优化经验"><a href="#优化经验" class="headerlink" title="优化经验"></a>优化经验</h2><h3 id="1-协议降级：阻断-QUIC-UDP-443-规避丢包卡顿"><a href="#1-协议降级：阻断-QUIC-UDP-443-规避丢包卡顿" class="headerlink" title="1. 协议降级：阻断 QUIC (UDP 443) 规避丢包卡顿"></a>1. 协议降级：阻断 QUIC (UDP 443) 规避丢包卡顿</h3><p>Chromium 内核浏览器与移动端 App 访问 Google 资产时，默认优先走基于 UDP 443 端口的 QUIC 协议。大多数代理节点对 UDP 转发的优化有限，极易发生握手挂起或高丢包，导致页面白屏转圈。</p><p>在规则链顶层显式拦截目标域名的 UDP 443 请求，迫使浏览器在超时前收到拒绝响应，从而毫秒级平滑降级到稳定的 TCP (HTTPS) 通道。</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line"># Shadowrocket 写法</span><br><span class="line">AND,((DEST-PORT,443),(PROTOCOL,UDP),(DOMAIN-SUFFIX,google.com)),REJECT</span><br><span class="line">AND,((DEST-PORT,443),(PROTOCOL,UDP),(DOMAIN-SUFFIX,googleapis.com)),REJECT</span><br><span class="line"></span><br><span class="line"># Clash 写法</span><br><span class="line">- AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,google.com)),REJECT</span><br><span class="line">- AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,googleapis.com)),REJECT</span><br></pre></td></tr></table></figure><h3 id="2-DNS-防污染与-CDN-本地化：防止国内流量误入代理"><a href="#2-DNS-防污染与-CDN-本地化：防止国内流量误入代理" class="headerlink" title="2. DNS 防污染与 CDN 本地化：防止国内流量误入代理"></a>2. DNS 防污染与 CDN 本地化：防止国内流量误入代理</h3><p>很多网上的配置为了防污染，把客户端 DNS 强行指向 <code>8.8.8.8</code> 并关闭系统 DNS。在国内环境下，向境外公共 DNS 发起纯文本 UDP 53 查询极易被劫持；且境外的 Anycast DNS 在解析微信、淘宝等国内服务时，会分配海外 CDN 节点，导致下游的 <code>GEOIP,CN</code> 规则误判失效，让国内流量白白绕道代理。</p><p>应保留国内公共 DNS 或系统 DNS 负责国内域名解析，海外域名交由代理节点远程解析。</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Shadowrocket 配置示例</span></span><br><span class="line"><span class="attr">dns-server</span> = system, <span class="number">223.5</span>.<span class="number">5.5</span>, <span class="number">119.29</span>.<span class="number">29.29</span></span><br><span class="line"><span class="attr">fallback-dns-server</span> = <span class="number">8.8</span>.<span class="number">8.8</span>, <span class="number">1.1</span>.<span class="number">1.1</span></span><br><span class="line"><span class="attr">dns-direct-system</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><h3 id="3-多订阅协同：视频大流量前置，剥离专线消耗"><a href="#3-多订阅协同：视频大流量前置，剥离专线消耗" class="headerlink" title="3. 多订阅协同：视频大流量前置，剥离专线消耗"></a>3. 多订阅协同：视频大流量前置，剥离专线消耗</h3><p>设定两套订阅角色：订阅 A（主力日常）承载大文件下载与 YouTube 4K 视频；订阅 B（专线 &#x2F; 解锁）仅用于 AI 请求。</p><p>为了防止看视频把昂贵的专线流量耗尽，由于代理规则是从上往下匹配的，必须把 <code>googlevideo.com</code> 等视频域名写在前面绑定主力机场，将剩余的 Google 服务收拢到专线。</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line"># 1. 视频请求提前被拦截走主力机场</span><br><span class="line">DOMAIN-SUFFIX,googlevideo.com,主力机场</span><br><span class="line">DOMAIN-SUFFIX,youtube.com,主力机场</span><br><span class="line"></span><br><span class="line"># 2. 剩余的 AI 请求再进专线策略组</span><br><span class="line">DOMAIN-KEYWORD,gemini,AI专线</span><br></pre></td></tr></table></figure><h3 id="4-AI-会话闭环：底层鉴权与推理接口全量收拢"><a href="#4-AI-会话闭环：底层鉴权与推理接口全量收拢" class="headerlink" title="4. AI 会话闭环：底层鉴权与推理接口全量收拢"></a>4. AI 会话闭环：底层鉴权与推理接口全量收拢</h3><p>Gemini、Claude 等服务由多组后端分布式接口协同工作。如果规则只写了前端域名 <code>gemini.google.com</code>，页面通信时发起的后台鉴权 API （如 <code>clients6</code>）就会滑落到兜底规则走主力机场。服务端检测到同一会话的请求 IP 撕裂，就会判定会话异常并直接中断。</p><p>必须对底层 API 进行全域收拢，确保所有通信都在同一节点 IP 下进行。</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">DOMAIN-SUFFIX,gemini.google.com,AI专线</span><br><span class="line">DOMAIN-SUFFIX,generativelanguage.googleapis.com,AI专线</span><br><span class="line">DOMAIN-SUFFIX,clients6.google.com,AI专线</span><br><span class="line">DOMAIN-SUFFIX,oauth2.googleapis.com,AI专线</span><br><span class="line">DOMAIN-SUFFIX,googleusercontent.com,AI专线</span><br></pre></td></tr></table></figure><h3 id="5-节点池净化：剔除机场虚拟提示节点"><a href="#5-节点池净化：剔除机场虚拟提示节点" class="headerlink" title="5. 节点池净化：剔除机场虚拟提示节点"></a>5. 节点池净化：剔除机场虚拟提示节点</h3><p>很多机场会在节点列表中插入 <code>剩余流量：985.3 GB</code>、<code>距离下次重置：27 天</code> 的虚拟节点。如果直接把节点列表全量写进静态的 <code>自动选择</code> 或 <code>故障转移</code> 策略组，用户消耗了流量或跨越了日期，机场服务端下发的节点名称就会变化。策略组在本地找不到写死的旧名称，内核就会抛出 <code>&#39;剩余流量：xxx&#39; not found</code> 错误并直接拒绝启动。</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Clash 策略组中，严禁混入虚拟节点，仅填写真实的服务器节点名称</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">自动选择</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">url-test</span></span><br><span class="line">  <span class="attr">proxies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">🇯🇵日本-01</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">🇺🇸美国-01</span></span><br><span class="line">    <span class="comment"># 绝对不能填入 - 剩余流量：985.3 GB</span></span><br></pre></td></tr></table></figure><h3 id="6-移动端本地化持久：防止订阅更新覆盖自建分流"><a href="#6-移动端本地化持久：防止订阅更新覆盖自建分流" class="headerlink" title="6. 移动端本地化持久：防止订阅更新覆盖自建分流"></a>6. 移动端本地化持久：防止订阅更新覆盖自建分流</h3><p>安卓端（如 Clash Meta for Android）默认没有桌面端的脚本注入功能。如果在客户端把主订阅作为在线 URL 导入，然后手动去改本地的 <code>config.yaml</code> 加上分流规则，一旦触发定时刷新，客户端就会重新下载机场的原始裸配置，把你写好的规则全量覆盖冲掉。</p><p>需要将移动端配置转存为本地文件（<code>File</code> 模式），切断客户端对该文件的自动下载覆盖。副订阅（AI 专线）的节点更新需求，直接通过配置文件里的 <code>proxy-providers</code> 交给内核去独立后台静默拉取。</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 在 File 模式下的本地 config.yaml 中配置独立节点池</span></span><br><span class="line"><span class="attr">proxy-providers:</span></span><br><span class="line">  <span class="attr">ai-provider:</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">http</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">&quot;https://sub-b.com/link&quot;</span></span><br><span class="line">    <span class="attr">interval:</span> <span class="number">86400</span> <span class="comment"># 独立控制后台拉取频率，不依赖客户端 UI 的订阅刷新</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">./proxy_providers/ai_nodes.yaml</span></span><br><span class="line">    <span class="attr">filter:</span> <span class="string">&quot;新加坡|🇸🇬|SG&quot;</span></span><br></pre></td></tr></table></figure><h3 id="7-规则库加速：使用国内-CDN-镜像防静默失效"><a href="#7-规则库加速：使用国内-CDN-镜像防静默失效" class="headerlink" title="7. 规则库加速：使用国内 CDN 镜像防静默失效"></a>7. 规则库加速：使用国内 CDN 镜像防静默失效</h3><p>拉取远程规则集失败时（如规则源路径写错，或国内网络直连 GitHub 遇到阻断），Shadowrocket 与 Clash 通常不会报错打断，而是直接将该规则集静默置空。这意味着数万条分流白名单根本没有加载进内存。引用开源规则库时，必须将原版的 GitHub 原生链接替换为 jsDelivr 等国内可稳定直连的 CDN 镜像源。</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line"># 错误写法（极易超时丢包）</span><br><span class="line">RULE-SET,https://raw.githubusercontent.com/Loyalsoldier/surge-rules/release/apple.txt,PROXY</span><br><span class="line"></span><br><span class="line"># 正确写法（CDN 稳定直连）</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/apple.txt,PROXY</span><br></pre></td></tr></table></figure><h2 id="配置文件参考模板"><a href="#配置文件参考模板" class="headerlink" title="配置文件参考模板"></a>配置文件参考模板</h2><p>以下模板包含了上文提到的所有排坑措施。请根据实际需求替换其中的节点和订阅链接。</p><h3 id="iOS-iPadOS-Shadowrocket"><a href="#iOS-iPadOS-Shadowrocket" class="headerlink" title="iOS &#x2F; iPadOS (Shadowrocket)"></a>iOS &#x2F; iPadOS (Shadowrocket)</h3><ol><li>在 Shadowrocket 右上角 <code>+</code> 分别导入订阅 A 和订阅 B；</li><li>进入底栏 <strong>“配置”</strong>，点击当前配置文件右侧的 <code>(i)</code> -&gt; 点击 <strong>“编辑纯文本”</strong>；</li><li>清空原有内容，粘贴下方配置并保存；</li><li>回到首页，全局路由选择 <strong>“配置”</strong>，节点列表选中订阅 A 的主力节点。</li></ol><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[General]</span></span><br><span class="line"><span class="attr">bypass-system</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">skip-proxy</span> = <span class="number">192.168</span>.<span class="number">0.0</span>/<span class="number">16</span>, <span class="number">10.0</span>.<span class="number">0.0</span>/<span class="number">8</span>, <span class="number">172.16</span>.<span class="number">0.0</span>/<span class="number">12</span>, localhost, *.local, captive.apple.com</span><br><span class="line"><span class="attr">tun-excluded-routes</span> = <span class="number">10.0</span>.<span class="number">0.0</span>/<span class="number">8</span>, <span class="number">100.64</span>.<span class="number">0.0</span>/<span class="number">10</span>, <span class="number">127.0</span>.<span class="number">0.0</span>/<span class="number">8</span>, <span class="number">169.254</span>.<span class="number">0.0</span>/<span class="number">16</span>, <span class="number">172.16</span>.<span class="number">0.0</span>/<span class="number">12</span>, <span class="number">192.0</span>.<span class="number">0.0</span>/<span class="number">24</span>, <span class="number">192.0</span>.<span class="number">2.0</span>/<span class="number">24</span>, <span class="number">192.88</span>.<span class="number">99.0</span>/<span class="number">24</span>, <span class="number">192.168</span>.<span class="number">0.0</span>/<span class="number">16</span>, <span class="number">198.51</span>.<span class="number">100.0</span>/<span class="number">24</span>, <span class="number">203.0</span>.<span class="number">113.0</span>/<span class="number">24</span>, <span class="number">224.0</span>.<span class="number">0.0</span>/<span class="number">4</span>, <span class="number">255.255</span>.<span class="number">255.255</span>/<span class="number">32</span>, <span class="number">239.255</span>.<span class="number">255.250</span>/<span class="number">32</span></span><br><span class="line"><span class="attr">dns-server</span> = system, <span class="number">223.5</span>.<span class="number">5.5</span>, <span class="number">119.29</span>.<span class="number">29.29</span></span><br><span class="line"><span class="attr">fallback-dns-server</span> = <span class="number">8.8</span>.<span class="number">8.8</span>, <span class="number">1.1</span>.<span class="number">1.1</span></span><br><span class="line"><span class="attr">ipv6</span> = <span class="literal">false</span></span><br><span class="line"><span class="attr">prefer-ipv6</span> = <span class="literal">false</span></span><br><span class="line"><span class="attr">dns-fallback-system</span> = <span class="literal">false</span></span><br><span class="line"><span class="attr">dns-direct-system</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">icmp-auto-reply</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">always-reject-url-rewrite</span> = <span class="literal">false</span></span><br><span class="line"><span class="attr">private-ip-answer</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">dns-direct-fallback-proxy</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">udp-policy-not-supported-behaviour</span> = REJECT</span><br><span class="line"></span><br><span class="line"><span class="section">[Proxy Group]</span></span><br><span class="line"><span class="comment"># 筛选订阅 B 中包含“新加坡”的可用节点，每 5 分钟自动测速优选</span></span><br><span class="line"><span class="attr">AI-Services</span> = url-test, url = http://www.gstatic.com/generate_204, interval = <span class="number">300</span>, tolerance = <span class="number">50</span>, policy-regex-filter=新加坡|🇸🇬|SG</span><br><span class="line"></span><br><span class="line"><span class="section">[Rule]</span></span><br><span class="line"><span class="comment"># 1. 协议阻断：拦截 Google UDP 443，强制回退 TCP</span></span><br><span class="line">AND,((DEST-PORT,443),(PROTOCOL,UDP),(DOMAIN-SUFFIX,google.com)),REJECT</span><br><span class="line">AND,((DEST-PORT,443),(PROTOCOL,UDP),(DOMAIN-SUFFIX,googleapis.com)),REJECT</span><br><span class="line">AND,((DEST-PORT,443),(PROTOCOL,UDP),(DOMAIN-SUFFIX,gstatic.com)),REJECT</span><br><span class="line">AND,((DEST-PORT,443),(PROTOCOL,UDP),(DOMAIN-SUFFIX,googleusercontent.com)),REJECT</span><br><span class="line">AND,((DEST-PORT,443),(PROTOCOL,UDP),(DOMAIN-SUFFIX,googlevideo.com)),REJECT</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. YouTube 视频大流量前置（走主力机场 PROXY，不耗专线）</span></span><br><span class="line">DOMAIN-SUFFIX,googlevideo.com,PROXY</span><br><span class="line">DOMAIN-SUFFIX,youtube.com,PROXY</span><br><span class="line">DOMAIN-SUFFIX,youtu.be,PROXY</span><br><span class="line">DOMAIN-SUFFIX,ytimg.com,PROXY</span><br><span class="line">DOMAIN-SUFFIX,youtubei.googleapis.com,PROXY</span><br><span class="line">DOMAIN-SUFFIX,yt3.ggpht.com,PROXY</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. AI 资产与底层 API 闭环（路由至 AI-Services 策略组）</span></span><br><span class="line">DOMAIN-KEYWORD,antigravity,AI-Services</span><br><span class="line">DOMAIN-KEYWORD,gemini,AI-Services</span><br><span class="line">DOMAIN-KEYWORD,colab,AI-Services</span><br><span class="line">DOMAIN-KEYWORD,deepmind,AI-Services</span><br><span class="line">DOMAIN-SUFFIX,gemini.google.com,AI-Services</span><br><span class="line">DOMAIN-SUFFIX,bard.google.com,AI-Services</span><br><span class="line">DOMAIN-SUFFIX,aistudio.google.com,AI-Services</span><br><span class="line">DOMAIN-SUFFIX,ai.google.dev,AI-Services</span><br><span class="line">DOMAIN-SUFFIX,makersuite.google.com,AI-Services</span><br><span class="line">DOMAIN-SUFFIX,deepmind.google,AI-Services</span><br><span class="line">DOMAIN-SUFFIX,deepmind.com,AI-Services</span><br><span class="line">DOMAIN-SUFFIX,clients6.google.com,AI-Services</span><br><span class="line">DOMAIN-SUFFIX,googleusercontent.com,AI-Services</span><br><span class="line">DOMAIN-SUFFIX,googleapis.com,AI-Services</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. Loyalsoldier 远程精细分流规则库（jsDelivr CDN 加速）</span></span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/private.txt,DIRECT</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/reject.txt,REJECT</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/icloud.txt,DIRECT</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/apple.txt,PROXY</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/proxy.txt,PROXY</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/gfw.txt,PROXY</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/greatfire.txt,PROXY</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/telegramcidr.txt,PROXY,no-resolve</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/direct.txt,DIRECT</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/cncidr.txt,DIRECT,no-resolve</span><br><span class="line">GEOIP,CN,DIRECT</span><br><span class="line">RULE-SET,https://cdn.jsdelivr.net/gh/Loyalsoldier/surge-rules@release/tld-not-cn.txt,PROXY</span><br><span class="line">FINAL,PROXY</span><br></pre></td></tr></table></figure><h3 id="电脑端-以-Clash-Verge-Rev-为例"><a href="#电脑端-以-Clash-Verge-Rev-为例" class="headerlink" title="电脑端 (以 Clash Verge Rev 为例)"></a>电脑端 (以 Clash Verge Rev 为例)</h3><p>桌面端通过<strong>扩展脚本 (Script)</strong> 可以实现配置与规则解耦：更新机场订阅时，Verge 会重新执行脚本把自定义规则注入进去，无需手动反复修改配置文件。</p><ol><li>在客户端中正常导入订阅 A（设为主力激活配置）和订阅 B；</li><li>进入 <strong>“订阅 (Profiles)”</strong> -&gt; 右键主力订阅 A -&gt; 选择 <strong>“脚本 (Script)”</strong>；</li><li>粘贴下方代码，将 <code>UNLOCK_SUB_URL</code> 替换为真实的订阅 B 链接；</li><li>保存后右键点击订阅 A 选择 <strong>“启用”</strong> 即可。</li></ol><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">main</span>(<span class="params">config, profileName</span>) &#123;</span><br><span class="line">  <span class="comment">// 防泄漏与 DNS 规则遵从</span></span><br><span class="line">  config[<span class="string">&quot;ipv6&quot;</span>] = <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">if</span> (config[<span class="string">&quot;dns&quot;</span>]) &#123;</span><br><span class="line">    config[<span class="string">&quot;dns&quot;</span>][<span class="string">&quot;ipv6&quot;</span>] = <span class="literal">false</span>;</span><br><span class="line">    config[<span class="string">&quot;dns&quot;</span>][<span class="string">&quot;respect-rules&quot;</span>] = <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 自定义参数（替换为真实的专线订阅链接）</span></span><br><span class="line">  <span class="keyword">const</span> <span class="variable constant_">UNLOCK_SUB_URL</span> = <span class="string">&quot;https://sub-b.com/link&quot;</span>;</span><br><span class="line">  <span class="keyword">const</span> <span class="variable constant_">AI_GROUP_NAME</span> = <span class="string">&quot;AI-Services&quot;</span>;</span><br><span class="line">  <span class="keyword">const</span> <span class="variable constant_">NODE_FILTER</span> = <span class="string">&quot;新加坡|🇸🇬|SG&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 注入 proxy-providers 专线节点池</span></span><br><span class="line">  config[<span class="string">&quot;proxy-providers&quot;</span>] = config[<span class="string">&quot;proxy-providers&quot;</span>] || &#123;&#125;;</span><br><span class="line">  config[<span class="string">&quot;proxy-providers&quot;</span>][<span class="string">&quot;ai-provider&quot;</span>] = &#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>,</span><br><span class="line">    <span class="attr">url</span>: <span class="variable constant_">UNLOCK_SUB_URL</span>,</span><br><span class="line">    <span class="attr">interval</span>: <span class="number">86400</span>,</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;./proxy_providers/ai_nodes.yaml&quot;</span>,</span><br><span class="line">    <span class="attr">filter</span>: <span class="variable constant_">NODE_FILTER</span>,</span><br><span class="line">    <span class="string">&quot;health-check&quot;</span>: &#123;</span><br><span class="line">      <span class="attr">enable</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">interval</span>: <span class="number">300</span>,</span><br><span class="line">      <span class="attr">url</span>: <span class="string">&quot;http://www.gstatic.com/generate_204&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  config[<span class="string">&quot;proxy-groups&quot;</span>] = config[<span class="string">&quot;proxy-groups&quot;</span>] || [];</span><br><span class="line">  config[<span class="string">&quot;proxy-groups&quot;</span>] = config[<span class="string">&quot;proxy-groups&quot;</span>].<span class="title function_">filter</span>(<span class="function"><span class="params">g</span> =&gt;</span> g.<span class="property">name</span> !== <span class="variable constant_">AI_GROUP_NAME</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 自动探测主力机场的代理策略组名称</span></span><br><span class="line">  <span class="keyword">let</span> mainProxyGroupName = <span class="string">&quot;PROXY&quot;</span>;</span><br><span class="line">  <span class="keyword">const</span> detectedGroup = config[<span class="string">&quot;proxy-groups&quot;</span>].<span class="title function_">find</span>(</span><br><span class="line">    <span class="function"><span class="params">g</span> =&gt;</span> g.<span class="property">type</span> === <span class="string">&quot;select&quot;</span> &amp;&amp; g.<span class="property">name</span> !== <span class="string">&quot;GLOBAL&quot;</span> &amp;&amp; g.<span class="property">name</span> !== <span class="variable constant_">AI_GROUP_NAME</span></span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">if</span> (detectedGroup) &#123;</span><br><span class="line">    mainProxyGroupName = detectedGroup.<span class="property">name</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 插入 AI 专属 URL-Test 自动测速策略组</span></span><br><span class="line">  config[<span class="string">&quot;proxy-groups&quot;</span>].<span class="title function_">unshift</span>(&#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="variable constant_">AI_GROUP_NAME</span>,</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&quot;url-test&quot;</span>,</span><br><span class="line">    <span class="attr">url</span>: <span class="string">&quot;http://www.gstatic.com/generate_204&quot;</span>,</span><br><span class="line">    <span class="attr">interval</span>: <span class="number">300</span>,</span><br><span class="line">    <span class="attr">tolerance</span>: <span class="number">50</span>,</span><br><span class="line">    <span class="attr">use</span>: [<span class="string">&quot;ai-provider&quot;</span>]</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 引入 Loyalsoldier 远程精细规则库 (jsDelivr CDN)</span></span><br><span class="line">  config[<span class="string">&quot;rule-providers&quot;</span>] = config[<span class="string">&quot;rule-providers&quot;</span>] || &#123;&#125;;</span><br><span class="line">  <span class="keyword">const</span> loyalProviders = &#123;</span><br><span class="line">    <span class="attr">reject</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;domain&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/reject.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/reject.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">icloud</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;domain&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/icloud.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/icloud.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">apple</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;domain&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/apple.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/apple.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">proxy</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;domain&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/proxy.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/proxy.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">direct</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;domain&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/direct.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/direct.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">private</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;domain&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/private.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/private.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">gfw</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;domain&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/gfw.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/gfw.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">greatfire</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;domain&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/greatfire.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/greatfire.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="string">&quot;tld-not-cn&quot;</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;domain&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/tld-not-cn.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/tld-not-cn.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">telegramcidr</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;ipcidr&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/telegramcidr.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/telegramcidr.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">cncidr</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;ipcidr&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/cncidr.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/cncidr.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">lancidr</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;ipcidr&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/lancidr.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/lancidr.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;,</span><br><span class="line">    <span class="attr">applications</span>: &#123; <span class="attr">type</span>: <span class="string">&quot;http&quot;</span>, <span class="attr">behavior</span>: <span class="string">&quot;classical&quot;</span>, <span class="attr">url</span>: <span class="string">&quot;https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/applications.txt&quot;</span>, <span class="attr">path</span>: <span class="string">&quot;./ruleset/loyalsoldier/applications.yaml&quot;</span>, <span class="attr">interval</span>: <span class="number">86400</span> &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">assign</span>(config[<span class="string">&quot;rule-providers&quot;</span>], loyalProviders);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 顶层 QUIC 拦截</span></span><br><span class="line">  <span class="keyword">const</span> quicBlockRules = [</span><br><span class="line">    <span class="string">&quot;AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,google.com)),REJECT&quot;</span>,</span><br><span class="line">    <span class="string">&quot;AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,googleapis.com)),REJECT&quot;</span>,</span><br><span class="line">    <span class="string">&quot;AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,gstatic.com)),REJECT&quot;</span>,</span><br><span class="line">    <span class="string">&quot;AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,googleusercontent.com)),REJECT&quot;</span>,</span><br><span class="line">    <span class="string">&quot;AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,googlevideo.com)),REJECT&quot;</span></span><br><span class="line">  ];</span><br><span class="line"></span><br><span class="line">  <span class="comment">// YouTube 视频前置</span></span><br><span class="line">  <span class="keyword">const</span> youtubeRules = [</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,googlevideo.com,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,youtube.com,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,youtu.be,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,ytimg.com,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,youtubei.googleapis.com,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,yt3.ggpht.com,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span></span><br><span class="line">  ];</span><br><span class="line"></span><br><span class="line">  <span class="comment">// AI 底层 API 闭环</span></span><br><span class="line">  <span class="keyword">const</span> aiRules = [</span><br><span class="line">    <span class="string">`DOMAIN-KEYWORD,antigravity,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-KEYWORD,gemini,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-KEYWORD,colab,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-KEYWORD,deepmind,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,gemini.google.com,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,bard.google.com,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,ai.google.dev,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,aistudio.google.com,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,makersuite.google.com,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,deepmind.google,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,deepmind.com,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`GEOSITE,google-gemini,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`GEOSITE,google-deepmind,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,clients6.google.com,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,googleusercontent.com,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`DOMAIN-SUFFIX,googleapis.com,<span class="subst">$&#123;AI_GROUP_NAME&#125;</span>`</span></span><br><span class="line">  ];</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 日常流量兜底</span></span><br><span class="line">  <span class="keyword">const</span> loyalRules = [</span><br><span class="line">    <span class="string">&quot;RULE-SET,applications,DIRECT&quot;</span>,</span><br><span class="line">    <span class="string">&quot;RULE-SET,private,DIRECT&quot;</span>,</span><br><span class="line">    <span class="string">&quot;RULE-SET,reject,REJECT&quot;</span>,</span><br><span class="line">    <span class="string">&quot;RULE-SET,icloud,DIRECT&quot;</span>,</span><br><span class="line">    <span class="string">`RULE-SET,apple,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`RULE-SET,proxy,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`RULE-SET,gfw,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`RULE-SET,greatfire,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`RULE-SET,telegramcidr,<span class="subst">$&#123;mainProxyGroupName&#125;</span>,no-resolve`</span>,</span><br><span class="line">    <span class="string">&quot;RULE-SET,direct,DIRECT&quot;</span>,</span><br><span class="line">    <span class="string">&quot;RULE-SET,lancidr,DIRECT,no-resolve&quot;</span>,</span><br><span class="line">    <span class="string">&quot;RULE-SET,cncidr,DIRECT,no-resolve&quot;</span>,</span><br><span class="line">    <span class="string">&quot;GEOIP,LAN,DIRECT,no-resolve&quot;</span>,</span><br><span class="line">    <span class="string">&quot;GEOIP,CN,DIRECT&quot;</span>,</span><br><span class="line">    <span class="string">`RULE-SET,tld-not-cn,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span>,</span><br><span class="line">    <span class="string">`MATCH,<span class="subst">$&#123;mainProxyGroupName&#125;</span>`</span></span><br><span class="line">  ];</span><br><span class="line"></span><br><span class="line">  config.<span class="property">rules</span> = [...quicBlockRules, ...youtubeRules, ...aiRules, ...loyalRules];</span><br><span class="line">  <span class="keyword">return</span> config;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="安卓端-Clash-Meta"><a href="#安卓端-Clash-Meta" class="headerlink" title="安卓端 (Clash Meta)"></a>安卓端 (Clash Meta)</h3><p>由于安卓客户端（如 Clash Meta for Android、Flclash）没有扩展脚本功能，为避免更新订阅时丢掉分流规则，必须采用 <strong>本地文件 (File) 模式 + 内核 proxy-providers 后台更新</strong>。</p><ol><li>在客户端中新建配置，类型选择 <strong>“本地文件 (File)”</strong>；</li><li>剔除主力节点列表中包含 <code>剩余流量</code> 等字眼的虚拟节点；</li><li>将副订阅写入 <code>proxy-providers</code> 交给内核后台独立更新；</li><li>保存后激活该本地配置。</li></ol><p>以下为关键片段的结构模板（在完整的本地 yaml 文件中修改这三块即可）：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 专线节点池：交由内核在后台独立拉取</span></span><br><span class="line"><span class="attr">proxy-providers:</span></span><br><span class="line">  <span class="attr">ai-provider:</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">http</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">&quot;https://sub-b.com/link&quot;</span></span><br><span class="line">    <span class="attr">interval:</span> <span class="number">86400</span></span><br><span class="line">    <span class="attr">path:</span> <span class="string">./proxy_providers/ai_nodes.yaml</span></span><br><span class="line">    <span class="attr">filter:</span> <span class="string">&quot;新加坡|🇸🇬|SG&quot;</span></span><br><span class="line">    <span class="attr">health-check:</span></span><br><span class="line">      <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">interval:</span> <span class="number">300</span></span><br><span class="line">      <span class="attr">url:</span> <span class="string">http://www.gstatic.com/generate_204</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 策略组结构（严禁混入虚拟节点名）</span></span><br><span class="line"><span class="attr">proxy-groups:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">AI-Services</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">url-test</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">http://www.gstatic.com/generate_204</span></span><br><span class="line">    <span class="attr">interval:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">tolerance:</span> <span class="number">50</span></span><br><span class="line">    <span class="attr">use:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ai-provider</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">主力代理</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">select</span></span><br><span class="line">    <span class="attr">proxies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">自动选择</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">故障转移</span></span><br><span class="line">      <span class="comment"># 此处填入真实的节点名称</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">🇯🇵日本-01</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">🇺🇸美国-01</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">自动选择</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">url-test</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">http://www.gstatic.com/generate_204</span></span><br><span class="line">    <span class="attr">interval:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">tolerance:</span> <span class="number">50</span></span><br><span class="line">    <span class="attr">proxies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">🇯🇵日本-01</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">🇺🇸美国-01</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 分流规则注入</span></span><br><span class="line"><span class="attr">rules:</span></span><br><span class="line">  <span class="comment"># QUIC 阻断</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,google.com)),REJECT</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,googleapis.com)),REJECT</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,gstatic.com)),REJECT</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,googleusercontent.com)),REJECT</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">AND,((DST-PORT,443),(NETWORK,UDP),(DOMAIN-SUFFIX,googlevideo.com)),REJECT</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment"># YouTube 剥离专线消耗</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,googlevideo.com,主力代理</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,youtube.com,主力代理</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,youtu.be,主力代理</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ytimg.com,主力代理</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment"># AI 闭环走专线</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-KEYWORD,antigravity,AI-Services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-KEYWORD,gemini,AI-Services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-KEYWORD,colab,AI-Services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-KEYWORD,deepmind,AI-Services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,gemini.google.com,AI-Services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,bard.google.com,AI-Services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,ai.google.dev,AI-Services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,aistudio.google.com,AI-Services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,clients6.google.com,AI-Services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,googleusercontent.com,AI-Services</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">DOMAIN-SUFFIX,googleapis.com,AI-Services</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment"># Loyalsoldier 国内直连与兜底</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">RULE-SET,applications,DIRECT</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">RULE-SET,private,DIRECT</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">RULE-SET,reject,REJECT</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">RULE-SET,icloud,DIRECT</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">RULE-SET,apple,主力代理</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">RULE-SET,proxy,主力代理</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">RULE-SET,gfw,主力代理</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">RULE-SET,direct,DIRECT</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">GEOIP,LAN,DIRECT,no-resolve</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">GEOIP,CN,DIRECT</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">MATCH,主力代理</span></span><br></pre></td></tr></table></figure><h2 id="规则扩展与多模型适配"><a href="#规则扩展与多模型适配" class="headerlink" title="规则扩展与多模型适配"></a>规则扩展与多模型适配</h2><p>若后续需要分流其他平台，只需在对应的 AI 规则区追加域名：</p><h3 id="1-Claude-Anthropic"><a href="#1-Claude-Anthropic" class="headerlink" title="1. Claude (Anthropic)"></a>1. Claude (Anthropic)</h3><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">DOMAIN-SUFFIX,claude.ai</span><br><span class="line">DOMAIN-SUFFIX,anthropic.com</span><br></pre></td></tr></table></figure><h3 id="2-OpenAI-ChatGPT"><a href="#2-OpenAI-ChatGPT" class="headerlink" title="2. OpenAI (ChatGPT)"></a>2. OpenAI (ChatGPT)</h3><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">DOMAIN-SUFFIX,chatgpt.com</span><br><span class="line">DOMAIN-SUFFIX,openai.com</span><br><span class="line">DOMAIN-SUFFIX,oaistatic.com</span><br><span class="line">DOMAIN-SUFFIX,oaiusercontent.com</span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>Brave 浏览器去除无用功能</title>
      <link>https://luanyufei.cn/2026/07/18/brave-browser-debloat/</link>
      <description>
        <![CDATA[<p>由于 Chrome 浏览器对去广告插件有反制机制——检测到去广告扩展后会让 YouTube 变得卡顿——所以我决定转用 Brave。然而 Brave 也自带了不少臃肿功能，比如 AI、钱包、VPN 等等。下面分别介绍 macOS 和 Windows 上的一键精简方法。</p>
<h2 id="macOS"><a href="#macOS" class="headerlink" title="macOS"></a>macOS</h2><h3 id="Step-1：生成配置描述文件"><a href="#Step-1：生成配置描述文件" class="headerlink" title="Step 1：生成配置描述文件"></a>Step 1：生成配置描述文件</h3><p>把下面这段命令<strong>整段</strong>粘贴到终端里执行：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt; <span class="string">&#x27;EOF&#x27;</span> &gt; ~/Desktop/BraveDebloat.mobileconfig</span><br><span class="line">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="line">&lt;!DOCTYPE plist PUBLIC <span class="string">&quot;-//Apple//DTD PLIST 1.0//EN&quot;</span> <span class="string">&quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;</span>&gt;</span><br><span class="line">&lt;plist version=<span class="string">&quot;1.0&quot;</span>&gt;</span><br><span class="line">&lt;dict&gt;</span><br><span class="line">    &lt;key&gt;PayloadContent&lt;/key&gt;</span><br><span class="line">    &lt;array&gt;</span><br><span class="line">        &lt;dict&gt;</span><br><span class="line">            &lt;key&gt;PayloadContent&lt;/key&gt;</span><br><span class="line">            &lt;dict&gt;</span><br><span class="line">                &lt;key&gt;com.brave.Browser&lt;/key&gt;</span><br><span class="line">                &lt;dict&gt;</span><br><span class="line">                    &lt;key&gt;Forced&lt;/key&gt;</span><br><span class="line">                    &lt;array&gt;</span><br><span class="line">                        &lt;dict&gt;</span><br><span class="line">                            &lt;key&gt;mcx_preference_settings&lt;/key&gt;</span><br><span class="line">                            &lt;dict&gt;</span><br><span class="line">                                &lt;key&gt;BraveWalletDisabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">true</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveAIChatEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveVPNDisabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">true</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveRewardsDisabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">true</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveNewsDisabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">true</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveP3AEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveStatsPingEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;MetricsReportingEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveWebDiscoveryEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;UrlKeyedAnonymizedDataCollectionEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;UserFeedbackAllowed&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                            &lt;/dict&gt;</span><br><span class="line">                        &lt;/dict&gt;</span><br><span class="line">                    &lt;/array&gt;</span><br><span class="line">                &lt;/dict&gt;</span><br><span class="line">            &lt;/dict&gt;</span><br><span class="line">            &lt;key&gt;PayloadEnabled&lt;/key&gt;</span><br><span class="line">            &lt;<span class="literal">true</span>/&gt;</span><br><span class="line">            &lt;key&gt;PayloadIdentifier&lt;/key&gt;</span><br><span class="line">            &lt;string&gt;com.apple.ManagedClient.preferences.Brave&lt;/string&gt;</span><br><span class="line">            &lt;key&gt;PayloadType&lt;/key&gt;</span><br><span class="line">            &lt;string&gt;com.apple.ManagedClient.preferences&lt;/string&gt;</span><br><span class="line">            &lt;key&gt;PayloadUUID&lt;/key&gt;</span><br><span class="line">            &lt;string&gt;98765432-ABCD-EF01-2345-6789ABCDEF01&lt;/string&gt;</span><br><span class="line">            &lt;key&gt;PayloadVersion&lt;/key&gt;</span><br><span class="line">            &lt;<span class="built_in">integer</span>&gt;1&lt;/integer&gt;</span><br><span class="line">        &lt;/dict&gt;</span><br><span class="line">    &lt;/array&gt;</span><br><span class="line">    &lt;key&gt;PayloadDescription&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;Brave Debloat&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadDisplayName&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;Brave Debloat&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadIdentifier&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;com.brave.debloat.profile&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadOrganization&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;LocalAdmin&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadRemovalDisallowed&lt;/key&gt;</span><br><span class="line">    &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">    &lt;key&gt;PayloadScope&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;System&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadType&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;Configuration&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadUUID&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;12345678-ABCD-EF01-2345-6789ABCDEF01&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadVersion&lt;/key&gt;</span><br><span class="line">    &lt;<span class="built_in">integer</span>&gt;1&lt;/integer&gt;</span><br><span class="line">&lt;/dict&gt;</span><br><span class="line">&lt;/plist&gt;</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure>

<h3 id="Step-2：安装配置描述文件"><a href="#Step-2：安装配置描述文件" class="headerlink" title="Step 2：安装配置描述文件"></a>Step 2：安装配置描述文件</h3><p>执行完成后，桌面会生成一个名为 <code>BraveDebloat.mobileconfig</code> 的文件。<strong>双击它</strong>，弹窗不用管，点 OK 即可。</p>
<h3 id="Step-3：在系统设置中确认安装"><a href="#Step-3：在系统设置中确认安装" class="headerlink" title="Step 3：在系统设置中确认安装"></a>Step 3：在系统设置中确认安装</h3><p>打开 <strong>系统设置</strong>，搜索 <code>profiles</code>，进入 <strong>Device Management</strong> 界面。双击 <strong>Brave Debloat</strong>，然后点 <strong>Install</strong> 安装。</p>
<p><figure class="article-image-figure"><img src="/image/brave-debloat-profiles.webp" alt="系统设置中的 Brave Debloat 描述文件" title="在 Device Management 中安装 Brave Debloat 描述文件" loading="lazy" decoding="async"><figcaption class="image-caption">在 Device Management 中安装 Brave Debloat 描述文件</figcaption></figure></p>
<h3 id="Step-4：重启-Brave"><a href="#Step-4：重启-Brave" class="headerlink" title="Step 4：重启 Brave"></a>Step 4：重启 Brave</h3><p>重启 Brave 浏览器，此时那些没用的功能就全部消失了。</p>
<h2 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h2><h3 id="Step-1：创建注册表文件"><a href="#Step-1：创建注册表文件" class="headerlink" title="Step 1：创建注册表文件"></a>Step 1：创建注册表文件</h3><p>在桌面新建一个<strong>文本文档</strong>，将以下内容粘贴进去：</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Windows Registry Editor Version 5.00</span><br><span class="line"></span><br><span class="line">[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\BraveSoftware\Brave]</span><br><span class="line">&quot;BraveWalletDisabled&quot;=dword:00000001</span><br><span class="line">&quot;BraveAIChatEnabled&quot;=dword:00000000</span><br><span class="line">&quot;BraveVPNDisabled&quot;=dword:00000001</span><br><span class="line">&quot;BraveRewardsDisabled&quot;=dword:00000001</span><br><span class="line">&quot;BraveNewsDisabled&quot;=dword:00000001</span><br><span class="line">&quot;BraveP3AEnabled&quot;=dword:00000000</span><br><span class="line">&quot;BraveStatsPingEnabled&quot;=dword:00000000</span><br><span class="line">&quot;MetricsReportingEnabled&quot;=dword:00000000</span><br><span class="line">&quot;BraveWebDiscoveryEnabled&quot;=dword:00000000</span><br><span class="line">&quot;UrlKeyedAnonymizedDataCollectionEnabled&quot;=dword:00000000</span><br><span class="line">&quot;UserFeedbackAllowed&quot;=dword:00000000</span><br></pre></td></tr></table></figure>

<h3 id="Step-2：修改后缀名并运行"><a href="#Step-2：修改后缀名并运行" class="headerlink" title="Step 2：修改后缀名并运行"></a>Step 2：修改后缀名并运行</h3><p>保存文件后，将后缀名从 <code>.txt</code> 改为 <code>.reg</code>（例如 <code>BraveDebloat.reg</code>）。<strong>双击运行</strong>，弹出的提示一路点「是」，允许写入注册表。</p>
<h3 id="Step-3：重启-Brave"><a href="#Step-3：重启-Brave" class="headerlink" title="Step 3：重启 Brave"></a>Step 3：重启 Brave</h3><p>重启 Brave 浏览器，完成瘦身。</p>]]>
      </description>
      <author>noonyjufee@gmail.com (乱与狒)</author>
      <category domain="https://luanyufei.cn/categories/%E5%AE%9E%E7%94%A8%E6%8A%80%E5%B7%A7/">实用技巧</category>
      <pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>由于 Chrome 浏览器对去广告插件有反制机制——检测到去广告扩展后会让 YouTube 变得卡顿——所以我决定转用 Brave。然而 Brave 也自带了不少臃肿功能，比如 AI、钱包、VPN 等等。下面分别介绍 macOS 和 Windows 上的一键精简方法。</p><h2 id="macOS"><a href="#macOS" class="headerlink" title="macOS"></a>macOS</h2><h3 id="Step-1：生成配置描述文件"><a href="#Step-1：生成配置描述文件" class="headerlink" title="Step 1：生成配置描述文件"></a>Step 1：生成配置描述文件</h3><p>把下面这段命令<strong>整段</strong>粘贴到终端里执行：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt; <span class="string">&#x27;EOF&#x27;</span> &gt; ~/Desktop/BraveDebloat.mobileconfig</span><br><span class="line">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="line">&lt;!DOCTYPE plist PUBLIC <span class="string">&quot;-//Apple//DTD PLIST 1.0//EN&quot;</span> <span class="string">&quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;</span>&gt;</span><br><span class="line">&lt;plist version=<span class="string">&quot;1.0&quot;</span>&gt;</span><br><span class="line">&lt;dict&gt;</span><br><span class="line">    &lt;key&gt;PayloadContent&lt;/key&gt;</span><br><span class="line">    &lt;array&gt;</span><br><span class="line">        &lt;dict&gt;</span><br><span class="line">            &lt;key&gt;PayloadContent&lt;/key&gt;</span><br><span class="line">            &lt;dict&gt;</span><br><span class="line">                &lt;key&gt;com.brave.Browser&lt;/key&gt;</span><br><span class="line">                &lt;dict&gt;</span><br><span class="line">                    &lt;key&gt;Forced&lt;/key&gt;</span><br><span class="line">                    &lt;array&gt;</span><br><span class="line">                        &lt;dict&gt;</span><br><span class="line">                            &lt;key&gt;mcx_preference_settings&lt;/key&gt;</span><br><span class="line">                            &lt;dict&gt;</span><br><span class="line">                                &lt;key&gt;BraveWalletDisabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">true</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveAIChatEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveVPNDisabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">true</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveRewardsDisabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">true</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveNewsDisabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">true</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveP3AEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveStatsPingEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;MetricsReportingEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;BraveWebDiscoveryEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;UrlKeyedAnonymizedDataCollectionEnabled&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                                &lt;key&gt;UserFeedbackAllowed&lt;/key&gt;</span><br><span class="line">                                &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">                            &lt;/dict&gt;</span><br><span class="line">                        &lt;/dict&gt;</span><br><span class="line">                    &lt;/array&gt;</span><br><span class="line">                &lt;/dict&gt;</span><br><span class="line">            &lt;/dict&gt;</span><br><span class="line">            &lt;key&gt;PayloadEnabled&lt;/key&gt;</span><br><span class="line">            &lt;<span class="literal">true</span>/&gt;</span><br><span class="line">            &lt;key&gt;PayloadIdentifier&lt;/key&gt;</span><br><span class="line">            &lt;string&gt;com.apple.ManagedClient.preferences.Brave&lt;/string&gt;</span><br><span class="line">            &lt;key&gt;PayloadType&lt;/key&gt;</span><br><span class="line">            &lt;string&gt;com.apple.ManagedClient.preferences&lt;/string&gt;</span><br><span class="line">            &lt;key&gt;PayloadUUID&lt;/key&gt;</span><br><span class="line">            &lt;string&gt;98765432-ABCD-EF01-2345-6789ABCDEF01&lt;/string&gt;</span><br><span class="line">            &lt;key&gt;PayloadVersion&lt;/key&gt;</span><br><span class="line">            &lt;<span class="built_in">integer</span>&gt;1&lt;/integer&gt;</span><br><span class="line">        &lt;/dict&gt;</span><br><span class="line">    &lt;/array&gt;</span><br><span class="line">    &lt;key&gt;PayloadDescription&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;Brave Debloat&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadDisplayName&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;Brave Debloat&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadIdentifier&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;com.brave.debloat.profile&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadOrganization&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;LocalAdmin&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadRemovalDisallowed&lt;/key&gt;</span><br><span class="line">    &lt;<span class="literal">false</span>/&gt;</span><br><span class="line">    &lt;key&gt;PayloadScope&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;System&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadType&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;Configuration&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadUUID&lt;/key&gt;</span><br><span class="line">    &lt;string&gt;12345678-ABCD-EF01-2345-6789ABCDEF01&lt;/string&gt;</span><br><span class="line">    &lt;key&gt;PayloadVersion&lt;/key&gt;</span><br><span class="line">    &lt;<span class="built_in">integer</span>&gt;1&lt;/integer&gt;</span><br><span class="line">&lt;/dict&gt;</span><br><span class="line">&lt;/plist&gt;</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><h3 id="Step-2：安装配置描述文件"><a href="#Step-2：安装配置描述文件" class="headerlink" title="Step 2：安装配置描述文件"></a>Step 2：安装配置描述文件</h3><p>执行完成后，桌面会生成一个名为 <code>BraveDebloat.mobileconfig</code> 的文件。<strong>双击它</strong>，弹窗不用管，点 OK 即可。</p><h3 id="Step-3：在系统设置中确认安装"><a href="#Step-3：在系统设置中确认安装" class="headerlink" title="Step 3：在系统设置中确认安装"></a>Step 3：在系统设置中确认安装</h3><p>打开 <strong>系统设置</strong>，搜索 <code>profiles</code>，进入 <strong>Device Management</strong> 界面。双击 <strong>Brave Debloat</strong>，然后点 <strong>Install</strong> 安装。</p><p><figure class="article-image-figure"><img src="/image/brave-debloat-profiles.webp" alt="系统设置中的 Brave Debloat 描述文件" title="在 Device Management 中安装 Brave Debloat 描述文件" loading="lazy" decoding="async"><figcaption class="image-caption">在 Device Management 中安装 Brave Debloat 描述文件</figcaption></figure></p><h3 id="Step-4：重启-Brave"><a href="#Step-4：重启-Brave" class="headerlink" title="Step 4：重启 Brave"></a>Step 4：重启 Brave</h3><p>重启 Brave 浏览器，此时那些没用的功能就全部消失了。</p><h2 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h2><h3 id="Step-1：创建注册表文件"><a href="#Step-1：创建注册表文件" class="headerlink" title="Step 1：创建注册表文件"></a>Step 1：创建注册表文件</h3><p>在桌面新建一个<strong>文本文档</strong>，将以下内容粘贴进去：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Windows Registry Editor Version 5.00</span><br><span class="line"></span><br><span class="line">[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\BraveSoftware\Brave]</span><br><span class="line">&quot;BraveWalletDisabled&quot;=dword:00000001</span><br><span class="line">&quot;BraveAIChatEnabled&quot;=dword:00000000</span><br><span class="line">&quot;BraveVPNDisabled&quot;=dword:00000001</span><br><span class="line">&quot;BraveRewardsDisabled&quot;=dword:00000001</span><br><span class="line">&quot;BraveNewsDisabled&quot;=dword:00000001</span><br><span class="line">&quot;BraveP3AEnabled&quot;=dword:00000000</span><br><span class="line">&quot;BraveStatsPingEnabled&quot;=dword:00000000</span><br><span class="line">&quot;MetricsReportingEnabled&quot;=dword:00000000</span><br><span class="line">&quot;BraveWebDiscoveryEnabled&quot;=dword:00000000</span><br><span class="line">&quot;UrlKeyedAnonymizedDataCollectionEnabled&quot;=dword:00000000</span><br><span class="line">&quot;UserFeedbackAllowed&quot;=dword:00000000</span><br></pre></td></tr></table></figure><h3 id="Step-2：修改后缀名并运行"><a href="#Step-2：修改后缀名并运行" class="headerlink" title="Step 2：修改后缀名并运行"></a>Step 2：修改后缀名并运行</h3><p>保存文件后，将后缀名从 <code>.txt</code> 改为 <code>.reg</code>（例如 <code>BraveDebloat.reg</code>）。<strong>双击运行</strong>，弹出的提示一路点「是」，允许写入注册表。</p><h3 id="Step-3：重启-Brave"><a href="#Step-3：重启-Brave" class="headerlink" title="Step 3：重启 Brave"></a>Step 3：重启 Brave</h3><p>重启 Brave 浏览器，完成瘦身。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Windows 11 优化方案</title>
      <link>https://luanyufei.cn/2026/07/18/windows-11-optimization-guide/</link>
      <description>
        <![CDATA[<p>Windows 11 自带了大量臃肿功能和恼人的 UI 变动。以下是一套完整的优化方案，涵盖精简系统、还原经典界面、激活、禁用更新等。</p>
<h2 id="精简系统镜像"><a href="#精简系统镜像" class="headerlink" title="精简系统镜像"></a>精简系统镜像</h2><p>与其装完再删，不如一开始就用精简版镜像。GitHub 上有两个知名项目：</p>
<ul>
<li><strong>Tiny11</strong> — 删除了大部分无用系统组件，保留核心功能，适合大多数人</li>
<li><strong>nano11</strong> — 比 Tiny11 更激进的精简方案，适合追求极致精简的用户</li>
</ul>
<p>在 GitHub 搜索 <code>Tiny11</code> 或 <code>nano11</code> 即可找到。配合 <strong>Rufus</strong>（开源刷机工具）制作启动 U 盘进行安装。</p>
<h2 id="还原经典开始菜单和任务栏"><a href="#还原经典开始菜单和任务栏" class="headerlink" title="还原经典开始菜单和任务栏"></a>还原经典开始菜单和任务栏</h2><p>Win11 的开始菜单和任务栏改动很大，如果你更习惯 Win10 的风格，可以使用 <strong>StartAllBack</strong> 一键还原。</p>
<p>下载地址：<a href="https://www.ghxi.com/startallback.html">https://www.ghxi.com/startallback.html</a></p>
<h2 id="还原经典右键菜单"><a href="#还原经典右键菜单" class="headerlink" title="还原经典右键菜单"></a>还原经典右键菜单</h2><p>Win11 的右键菜单默认只显示几个选项，完整菜单被折叠了。用以下方法可以恢复 Win10 样式的完整右键菜单。</p>
<p>以<strong>管理员身份</strong>打开 CMD 或 PowerShell，输入：</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">reg add <span class="string">&quot;HKCU\Software\Classes\CLSID\&#123;86ca1aa0-34aa-4e8b-a509-50c905bae2a2&#125;\InprocServer32&quot;</span> /f /ve</span><br></pre></td></tr></table></figure>

<p>然后重启资源管理器即可生效。</p>
<p>如果想恢复回 Win11 默认右键菜单：</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">reg delete <span class="string">&quot;HKCU\Software\Classes\CLSID\&#123;86ca1aa0-34aa-4e8b-a509-50c905bae2a2&#125;&quot;</span> /f</span><br></pre></td></tr></table></figure>

<h2 id="关闭动画和半透明效果"><a href="#关闭动画和半透明效果" class="headerlink" title="关闭动画和半透明效果"></a>关闭动画和半透明效果</h2><p>打开 <strong>设置 → 辅助功能 → 视觉效果</strong>，关闭以下两项：</p>
<ul>
<li><strong>动画效果</strong> — 关闭</li>
<li><strong>透明效果</strong> — 关闭</li>
</ul>
<p>关闭后系统响应会更跟手，尤其对低配机器提升明显。</p>
<h2 id="激活-Windows-和-Office"><a href="#激活-Windows-和-Office" class="headerlink" title="激活 Windows 和 Office"></a>激活 Windows 和 Office</h2><p>以<strong>管理员身份</strong>打开 CMD 或 PowerShell，输入：</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">irm</span> https://get.activated.win | <span class="built_in">iex</span></span><br></pre></td></tr></table></figure>

<p>在弹出的菜单中选择：</p>
<ul>
<li><code>[1] HWID</code> — 永久激活 Windows（硬件数字许可证）</li>
<li><code>[2] Ohook</code> — 永久激活 Office</li>
</ul>
<h2 id="永久禁止-Windows-更新"><a href="#永久禁止-Windows-更新" class="headerlink" title="永久禁止 Windows 更新"></a>永久禁止 Windows 更新</h2><p>使用 <strong>Windows Update Blocker</strong> 工具，一键永久禁止自动更新：</p>
<p>下载地址：<a href="https://www.sordum.org/downloads/?st-windows-update-blocker">https://www.sordum.org/downloads/?st-windows-update-blocker</a></p>
<h2 id="删除-禁用-Win11-无用功能"><a href="#删除-禁用-Win11-无用功能" class="headerlink" title="删除 &#x2F; 禁用 Win11 无用功能"></a>删除 &#x2F; 禁用 Win11 无用功能</h2><p><strong>Chris Titus Tech</strong> 的 Windows 工具箱，提供图形界面，一键删除或禁用各种臃肿功能。</p>
<p>以<strong>管理员身份</strong>打开 CMD 或 PowerShell，输入：</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">irm</span> https://christitus.com/win | <span class="built_in">iex</span></span><br></pre></td></tr></table></figure>

<p>GitHub 项目地址：<a href="https://github.com/ChrisTitusTech/winutil">https://github.com/ChrisTitusTech/winutil</a></p>
<h2 id="系统汉化"><a href="#系统汉化" class="headerlink" title="系统汉化"></a>系统汉化</h2><p>如果安装的是英文版 Windows，参考以下教程进行汉化：</p>
<p><a href="https://www.cnblogs.com/ACDIV/p/19156603">https://www.cnblogs.com/ACDIV/p/19156603</a></p>
<h2 id="推荐软件"><a href="#推荐软件" class="headerlink" title="推荐软件"></a>推荐软件</h2><ul>
<li><strong>Flow Launcher</strong> — 开源的快速启动器，类似 macOS 的 Spotlight，按快捷键即可搜索应用、文件、网页等</li>
</ul>]]>
      </description>
      <author>noonyjufee@gmail.com (乱与狒)</author>
      <category domain="https://luanyufei.cn/categories/%E5%AE%9E%E7%94%A8%E6%8A%80%E5%B7%A7/">实用技巧</category>
      <pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Windows 11 自带了大量臃肿功能和恼人的 UI 变动。以下是一套完整的优化方案，涵盖精简系统、还原经典界面、激活、禁用更新等。</p><h2 id="精简系统镜像"><a href="#精简系统镜像" class="headerlink" title="精简系统镜像"></a>精简系统镜像</h2><p>与其装完再删，不如一开始就用精简版镜像。GitHub 上有两个知名项目：</p><ul><li><strong>Tiny11</strong> — 删除了大部分无用系统组件，保留核心功能，适合大多数人</li><li><strong>nano11</strong> — 比 Tiny11 更激进的精简方案，适合追求极致精简的用户</li></ul><p>在 GitHub 搜索 <code>Tiny11</code> 或 <code>nano11</code> 即可找到。配合 <strong>Rufus</strong>（开源刷机工具）制作启动 U 盘进行安装。</p><h2 id="还原经典开始菜单和任务栏"><a href="#还原经典开始菜单和任务栏" class="headerlink" title="还原经典开始菜单和任务栏"></a>还原经典开始菜单和任务栏</h2><p>Win11 的开始菜单和任务栏改动很大，如果你更习惯 Win10 的风格，可以使用 <strong>StartAllBack</strong> 一键还原。</p><p>下载地址：<a href="https://www.ghxi.com/startallback.html">https://www.ghxi.com/startallback.html</a></p><h2 id="还原经典右键菜单"><a href="#还原经典右键菜单" class="headerlink" title="还原经典右键菜单"></a>还原经典右键菜单</h2><p>Win11 的右键菜单默认只显示几个选项，完整菜单被折叠了。用以下方法可以恢复 Win10 样式的完整右键菜单。</p><p>以<strong>管理员身份</strong>打开 CMD 或 PowerShell，输入：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">reg add <span class="string">&quot;HKCU\Software\Classes\CLSID\&#123;86ca1aa0-34aa-4e8b-a509-50c905bae2a2&#125;\InprocServer32&quot;</span> /f /ve</span><br></pre></td></tr></table></figure><p>然后重启资源管理器即可生效。</p><p>如果想恢复回 Win11 默认右键菜单：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line">reg delete <span class="string">&quot;HKCU\Software\Classes\CLSID\&#123;86ca1aa0-34aa-4e8b-a509-50c905bae2a2&#125;&quot;</span> /f</span><br></pre></td></tr></table></figure><h2 id="关闭动画和半透明效果"><a href="#关闭动画和半透明效果" class="headerlink" title="关闭动画和半透明效果"></a>关闭动画和半透明效果</h2><p>打开 <strong>设置 → 辅助功能 → 视觉效果</strong>，关闭以下两项：</p><ul><li><strong>动画效果</strong> — 关闭</li><li><strong>透明效果</strong> — 关闭</li></ul><p>关闭后系统响应会更跟手，尤其对低配机器提升明显。</p><h2 id="激活-Windows-和-Office"><a href="#激活-Windows-和-Office" class="headerlink" title="激活 Windows 和 Office"></a>激活 Windows 和 Office</h2><p>以<strong>管理员身份</strong>打开 CMD 或 PowerShell，输入：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">irm</span> https://get.activated.win | <span class="built_in">iex</span></span><br></pre></td></tr></table></figure><p>在弹出的菜单中选择：</p><ul><li><code>[1] HWID</code> — 永久激活 Windows（硬件数字许可证）</li><li><code>[2] Ohook</code> — 永久激活 Office</li></ul><h2 id="永久禁止-Windows-更新"><a href="#永久禁止-Windows-更新" class="headerlink" title="永久禁止 Windows 更新"></a>永久禁止 Windows 更新</h2><p>使用 <strong>Windows Update Blocker</strong> 工具，一键永久禁止自动更新：</p><p>下载地址：<a href="https://www.sordum.org/downloads/?st-windows-update-blocker">https://www.sordum.org/downloads/?st-windows-update-blocker</a></p><h2 id="删除-禁用-Win11-无用功能"><a href="#删除-禁用-Win11-无用功能" class="headerlink" title="删除 &#x2F; 禁用 Win11 无用功能"></a>删除 &#x2F; 禁用 Win11 无用功能</h2><p><strong>Chris Titus Tech</strong> 的 Windows 工具箱，提供图形界面，一键删除或禁用各种臃肿功能。</p><p>以<strong>管理员身份</strong>打开 CMD 或 PowerShell，输入：</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="built_in">irm</span> https://christitus.com/win | <span class="built_in">iex</span></span><br></pre></td></tr></table></figure><p>GitHub 项目地址：<a href="https://github.com/ChrisTitusTech/winutil">https://github.com/ChrisTitusTech/winutil</a></p><h2 id="系统汉化"><a href="#系统汉化" class="headerlink" title="系统汉化"></a>系统汉化</h2><p>如果安装的是英文版 Windows，参考以下教程进行汉化：</p><p><a href="https://www.cnblogs.com/ACDIV/p/19156603">https://www.cnblogs.com/ACDIV/p/19156603</a></p><h2 id="推荐软件"><a href="#推荐软件" class="headerlink" title="推荐软件"></a>推荐软件</h2><ul><li><strong>Flow Launcher</strong> — 开源的快速启动器，类似 macOS 的 Spotlight，按快捷键即可搜索应用、文件、网页等</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>电子扫盲｜超分与补帧</title>
      <link>https://luanyufei.cn/2025/03/13/tech-basics-upscaling-frame-generation/</link>
      <description>
        <![CDATA[<p>我们在[[分辨率]]这篇文章里提到过，如果我们玩游戏时想获得清晰的画面，就要拉高分辨率，而拉高分辨率的后果就是帧数降低。那有没有什么办法，能让我们在<strong>不手动拉高分辨率</strong>的情况下，画面也能变清晰呢？</p>
<p>答案是有的。这就是<strong>超分辨率技术（super resolution）</strong>，俗称超分。超分就是利用一定的算法或者是AI技术，来将原本低分辨率的画面变成高分辨率的画面。比如你用DLSS将1K的画面超分到4K，此时你看到的是4K的画面，但是电脑实际上只渲染的是1K，所以压力大大减小，帧数因此也就提高了。</p>
<p>另外一种技术是补帧，也叫<strong>帧生成（frame generation）</strong>，或者叫插帧也行。从名字来看就很好理解，就是用算法或者ai来添加额外的帧，从而使画面变得流畅。但是由于插帧一般的原理是<strong>根据两个帧补出它们中间的帧</strong>，所以需要等待下一帧出现才能补出上一帧，从而造成了延迟。原始帧越低，那么补帧延迟越高，效果越差。比如你将30帧补成120帧就没有将60帧补成120帧的效果好。</p>
<p>在游戏画面越来越爆炸的今天，显卡的性能已经逐渐跟不上游戏的发展，因此超分与补帧几乎成为了刚需。各家都有各自的超分与补帧技术。下面简单的介绍一下。</p>
<p><strong>DLSS（Deep Learning Super Sampling，深度学习超级采样）</strong>，俗称“大力水手”，是n卡（英伟达的显卡）才有的功能，包含了超分和插帧（插帧只有DLSS3才有，而DLSS3只有40系n卡才能开）。目前最新版本是DLSS4。</p>
<p><strong>FSR（FidelityFX Super Resolution）</strong></p>]]>
      </description>
      <author>noonyjufee@gmail.com (乱与狒)</author>
      <category domain="https://luanyufei.cn/categories/FeeFee%E7%94%B5%E5%AD%90%E6%89%AB%E7%9B%B2/">FeeFee电子扫盲</category>
      <pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>我们在[[分辨率]]这篇文章里提到过，如果我们玩游戏时想获得清晰的画面，就要拉高分辨率，而拉高分辨率的后果就是帧数降低。那有没有什么办法，能让我们在<strong>不手动拉高分辨率</strong>的情况下，画面也能变清晰呢？</p><p>答案是有的。这就是<strong>超分辨率技术（super resolution）</strong>，俗称超分。超分就是利用一定的算法或者是AI技术，来将原本低分辨率的画面变成高分辨率的画面。比如你用DLSS将1K的画面超分到4K，此时你看到的是4K的画面，但是电脑实际上只渲染的是1K，所以压力大大减小，帧数因此也就提高了。</p><p>另外一种技术是补帧，也叫<strong>帧生成（frame generation）</strong>，或者叫插帧也行。从名字来看就很好理解，就是用算法或者ai来添加额外的帧，从而使画面变得流畅。但是由于插帧一般的原理是<strong>根据两个帧补出它们中间的帧</strong>，所以需要等待下一帧出现才能补出上一帧，从而造成了延迟。原始帧越低，那么补帧延迟越高，效果越差。比如你将30帧补成120帧就没有将60帧补成120帧的效果好。</p><p>在游戏画面越来越爆炸的今天，显卡的性能已经逐渐跟不上游戏的发展，因此超分与补帧几乎成为了刚需。各家都有各自的超分与补帧技术。下面简单的介绍一下。</p><p><strong>DLSS（Deep Learning Super Sampling，深度学习超级采样）</strong>，俗称“大力水手”，是n卡（英伟达的显卡）才有的功能，包含了超分和插帧（插帧只有DLSS3才有，而DLSS3只有40系n卡才能开）。目前最新版本是DLSS4。</p><p><strong>FSR（FidelityFX Super Resolution）</strong></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>电子扫盲｜操作系统</title>
      <link>https://luanyufei.cn/2025/02/20/tech-basics-operating-systems/</link>
      <description>
        <![CDATA[<p>电脑、手机都有操作系统（Operating System，简称OS），常见的电脑操作系统有<strong>Windows，Linux和macOS</strong>，常见的手机操作系统有<strong>安卓（Android）和iOS</strong>。<br>不同的操作系统之间是不能互通的！比如，当你发现老师发来的软件是.exe格式，而你正在使用macOS，那你就该知道这个软件我装不了，因为.exe是windows上才有的格式。</p>
<p>先来说说电脑系统：<br>1、Windows是最常见的电脑操作系统，它是微软公司（Microsoft）开发的，<strong>跟安卓系统半毛钱关系没有！！</strong> 总有人会有类似这样的发言：“你的电脑是安卓还是苹果？”这是严重的笑话。<br>2、macOS是苹果的电脑系统，不要叫它iOS！！iOS指的是苹果的<strong>手机</strong>系统。<br>3、Linux是著名的开源系统（开源的意思是源代码公开），一般只有程序员会使用。<br>4、其他系统，如ChromeOS、Unix等，不需要了解。</p>]]>
      </description>
      <author>noonyjufee@gmail.com (乱与狒)</author>
      <category domain="https://luanyufei.cn/categories/FeeFee%E7%94%B5%E5%AD%90%E6%89%AB%E7%9B%B2/">FeeFee电子扫盲</category>
      <pubDate>Thu, 20 Feb 2025 00:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>电脑、手机都有操作系统（Operating System，简称OS），常见的电脑操作系统有<strong>Windows，Linux和macOS</strong>，常见的手机操作系统有<strong>安卓（Android）和iOS</strong>。<br>不同的操作系统之间是不能互通的！比如，当你发现老师发来的软件是.exe格式，而你正在使用macOS，那你就该知道这个软件我装不了，因为.exe是windows上才有的格式。</p><p>先来说说电脑系统：<br>1、Windows是最常见的电脑操作系统，它是微软公司（Microsoft）开发的，<strong>跟安卓系统半毛钱关系没有！！</strong> 总有人会有类似这样的发言：“你的电脑是安卓还是苹果？”这是严重的笑话。<br>2、macOS是苹果的电脑系统，不要叫它iOS！！iOS指的是苹果的<strong>手机</strong>系统。<br>3、Linux是著名的开源系统（开源的意思是源代码公开），一般只有程序员会使用。<br>4、其他系统，如ChromeOS、Unix等，不需要了解。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>电子扫盲｜写在前面</title>
      <link>https://luanyufei.cn/2025/02/13/tech-basics-foreword/</link>
      <description>
        <![CDATA[<p>《FEEFEE电子扫盲》是为那些对电脑<strong>完全一窍不通</strong>的人群而撰写的，所以许多细节之处有所省略。</p>
<h1 id="为什么想到写这样一个系列"><a href="#为什么想到写这样一个系列" class="headerlink" title="为什么想到写这样一个系列"></a>为什么想到写这样一个系列</h1><p>当我在机房看到20岁的同班同学打开了Chrome浏览器，直接在Google上搜索却没有反应，还举手说电脑坏了时，我沉默了。。。</p>
<p>当我听到有人Windows系统卡死，抱怨道“安卓系统真垃圾”时，我沉默了。。。</p>
<p>当有人给我发来一个.exe文件，问我这个文件怎么在他的mac上打不开时，我沉默了。。。</p>
<p>当长辈问我明明有一个电脑（指显示器）为什么还买个笔记本时，我沉默了。。。</p>
<p>随着手机的逐渐普及，越来越多人已经逐渐抛弃了电脑，甚至有人妄言：电脑已经没用了，手机能解决一切。如此的后果就是越来越多的年轻人正逐渐称为「赛博文盲」。</p>
<p><a href="https://zhuanlan.zhihu.com/p/706499217">https://zhuanlan.zhihu.com/p/706499217</a></p>
<h1 id="后面的内容还没想好，过几天写"><a href="#后面的内容还没想好，过几天写" class="headerlink" title="后面的内容还没想好，过几天写"></a>后面的内容还没想好，过几天写</h1>]]>
      </description>
      <author>noonyjufee@gmail.com (乱与狒)</author>
      <category domain="https://luanyufei.cn/categories/FeeFee%E7%94%B5%E5%AD%90%E6%89%AB%E7%9B%B2/">FeeFee电子扫盲</category>
      <pubDate>Thu, 13 Feb 2025 00:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>《FEEFEE电子扫盲》是为那些对电脑<strong>完全一窍不通</strong>的人群而撰写的，所以许多细节之处有所省略。</p><h1 id="为什么想到写这样一个系列"><a href="#为什么想到写这样一个系列" class="headerlink" title="为什么想到写这样一个系列"></a>为什么想到写这样一个系列</h1><p>当我在机房看到20岁的同班同学打开了Chrome浏览器，直接在Google上搜索却没有反应，还举手说电脑坏了时，我沉默了。。。</p><p>当我听到有人Windows系统卡死，抱怨道“安卓系统真垃圾”时，我沉默了。。。</p><p>当有人给我发来一个.exe文件，问我这个文件怎么在他的mac上打不开时，我沉默了。。。</p><p>当长辈问我明明有一个电脑（指显示器）为什么还买个笔记本时，我沉默了。。。</p><p>随着手机的逐渐普及，越来越多人已经逐渐抛弃了电脑，甚至有人妄言：电脑已经没用了，手机能解决一切。如此的后果就是越来越多的年轻人正逐渐称为「赛博文盲」。</p><p><a href="https://zhuanlan.zhihu.com/p/706499217">https://zhuanlan.zhihu.com/p/706499217</a></p><h1 id="后面的内容还没想好，过几天写"><a href="#后面的内容还没想好，过几天写" class="headerlink" title="后面的内容还没想好，过几天写"></a>后面的内容还没想好，过几天写</h1>]]>
      </content:encoded>
    </item>
    <item>
      <title>电子扫盲｜显示器</title>
      <link>https://luanyufei.cn/2025/02/12/tech-basics-display-technologies/</link>
      <description>
        <![CDATA[<p>先讲讲LCD显示器的基本原理。首先是一层液晶材料用来显示图像，但由于液晶材料本身不发光，所以再在它的底部加上一层背光板（发光的板子），这就构成了LCD屏。</p>
<p>LCD的优点是延迟很低，价格也很便宜，很适合打fps游戏（first player shooting，第一人称射击游戏），因此大部分游戏笔记本都采用LCD屏。但是LCD屏有个缺点，就是对比度（明暗差异，对比度越高，色彩表现越好，画面越真实）很低。什么意思呢？比如你在LCD屏上打开一张纯黑的图片，然后你再把房间里的灯关掉，你就会发现屏幕居然在发光！这是因为LCD屏在打开的时候，下面的背光板是一直亮着的，所以即便黑色依然会发光。由此我们也可以推断出LCD屏会很耗电。<em>就我个人而言，我见过的对比度控制的比较好的LCD屏是iPad Pro 2020。</em> 而且LCD屏还有个缺点就是屏幕边缘会漏光。（如下图）</p>
<p><figure class="article-image-figure"><img src="/image/2.webp" alt="一张图片" title="LCD漏光现象" loading="lazy" decoding="async"><figcaption class="image-caption">LCD漏光现象</figcaption></figure></p>
<p>这时候有人就诞生了一个想法，如果说我把LCD的背光板切成很多小份，这些背光板上方如果显示的是黑色，我就把这个背光板关掉；如果显示的不是黑色，那就把背光板打开。这样不就实现了高对比度吗？这种<strong>分区背光</strong>的LCD屏就叫做<strong>miniLED</strong>，miniLED本质上讲依旧属于LCD屏，但显示效果要远远好于LCD。而且，miniLED的显示效果跟分区数（即背光板被切成的份数）是正相关的，分区数量越多，那明暗的差异自然越大，显示效果自然越好。</p>
<p>但miniLED也有缺点。首先是光晕现象。比如一张纯黑的图片，正中间有一个杯子（如下图），那么杯子的周围就会形成一圈光晕。当然，如果分区数量够多，那光晕现象自然也会减小。</p>
<p><figure class="article-image-figure"><img src="/image/1.webp" alt="一张图片" title="miniLED光晕现象" loading="lazy" decoding="async"><figcaption class="image-caption">miniLED光晕现象</figcaption></figure></p>]]>
      </description>
      <author>noonyjufee@gmail.com (乱与狒)</author>
      <category domain="https://luanyufei.cn/categories/FeeFee%E7%94%B5%E5%AD%90%E6%89%AB%E7%9B%B2/">FeeFee电子扫盲</category>
      <pubDate>Wed, 12 Feb 2025 00:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>先讲讲LCD显示器的基本原理。首先是一层液晶材料用来显示图像，但由于液晶材料本身不发光，所以再在它的底部加上一层背光板（发光的板子），这就构成了LCD屏。</p><p>LCD的优点是延迟很低，价格也很便宜，很适合打fps游戏（first player shooting，第一人称射击游戏），因此大部分游戏笔记本都采用LCD屏。但是LCD屏有个缺点，就是对比度（明暗差异，对比度越高，色彩表现越好，画面越真实）很低。什么意思呢？比如你在LCD屏上打开一张纯黑的图片，然后你再把房间里的灯关掉，你就会发现屏幕居然在发光！这是因为LCD屏在打开的时候，下面的背光板是一直亮着的，所以即便黑色依然会发光。由此我们也可以推断出LCD屏会很耗电。<em>就我个人而言，我见过的对比度控制的比较好的LCD屏是iPad Pro 2020。</em> 而且LCD屏还有个缺点就是屏幕边缘会漏光。（如下图）</p><p><figure class="article-image-figure"><img src="/image/2.webp" alt="一张图片" title="LCD漏光现象" loading="lazy" decoding="async"><figcaption class="image-caption">LCD漏光现象</figcaption></figure></p><p>这时候有人就诞生了一个想法，如果说我把LCD的背光板切成很多小份，这些背光板上方如果显示的是黑色，我就把这个背光板关掉；如果显示的不是黑色，那就把背光板打开。这样不就实现了高对比度吗？这种<strong>分区背光</strong>的LCD屏就叫做<strong>miniLED</strong>，miniLED本质上讲依旧属于LCD屏，但显示效果要远远好于LCD。而且，miniLED的显示效果跟分区数（即背光板被切成的份数）是正相关的，分区数量越多，那明暗的差异自然越大，显示效果自然越好。</p><p>但miniLED也有缺点。首先是光晕现象。比如一张纯黑的图片，正中间有一个杯子（如下图），那么杯子的周围就会形成一圈光晕。当然，如果分区数量够多，那光晕现象自然也会减小。</p><p><figure class="article-image-figure"><img src="/image/1.webp" alt="一张图片" title="miniLED光晕现象" loading="lazy" decoding="async"><figcaption class="image-caption">miniLED光晕现象</figcaption></figure></p>]]>
      </content:encoded>
    </item>
  </channel>
</rss>
