<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title><![CDATA[赵苦瓜のBlog~]]></title> 
<description><![CDATA[也是唧小b的blog哟~]]></description>
<link>https://blog.zhaokugua.com/</link>
<language>zh-cn</language>
<generator>www.emlog.net</generator>
<item>
	<title>小米平板 5（nabu）手写笔无线充电：从“吸上去没反应”到 6.14 内核自编驱动</title>
	<link>https://blog.zhaokugua.com/?post=122</link>
	<description><![CDATA[<blockquote>
<p>环境：小米平板 5（codename <strong>nabu</strong>，SM8150，三星 UFS <code>KLUFG8RHGB-B0E1</code>）· Ubuntu 26.04 ARM64
内核：<code>6.14.11-nabu-tmm</code>（日常）/ <code>6.17.0-xiaomi-nabu-tmm-UFS-slowdown+</code>（备用）
时间：2026-11-04
授权：文中驱动源码来自 <a href="https://gitlab.com/sm8150-mainline/linux">sm8150-mainline/linux</a>（GPL-2.0，作者 Nicola Guerrera）</p>
</blockquote>
<hr />
<h2>TL;DR</h2>
<ul>
<li>平板顶部的磁吸无线充电芯片是 <strong>IDT P9418</strong>（实际读出的 Chip ID 是 <code>0x9415</code>），挂在 <code>i2c-3 / 0x3b</code>。</li>
<li>6.14.11 内核<strong>根本没有编这个驱动</strong>，所以笔吸上去毫无反应——不是硬件坏了。</li>
<li>6.17 内核自带驱动，但那个内核对这台机器只有 <strong>5V 慢充</strong>（平板充电几乎不可用），且 UFS 被降频。</li>
<li>最终方案：<strong>把 6.17 的 <code>idtp9418</code> 驱动移植到 6.14.11</strong>，自己编译一个 out-of-tree 模块，
让“快充 + 手写笔充电”共存。驱动侧改了两处，另外给上游驱动补了一个 SoC 重试逻辑。</li>
<li>顺带打通：手写笔蓝牙配对、GATT 信息读取、桌面小组件与 GNOME 顶栏插件的电量展示。</li>
</ul>
<hr />
<h2>1. 背景与硬件</h2>
<p>小米平板 5 的 Smart Pen 通过磁吸吸附在平板顶部短边，由平板内的线圈无线供电。Linux 下这条链路涉及三部分：</p>
<table>
<thead>
<tr>
<th>部分</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>无线充芯片</td>
<td><strong>IDT P9418</strong>，i2c-3 地址 <code>0x3b</code>，设备树节点 <code>idtp9418@3b</code></td>
</tr>
<tr>
<td>霍尔传感器</td>
<td>检测笔是否吸附：<code>gpio101</code>（active-high）、<code>gpio134</code>（active-low）</td>
</tr>
<tr>
<td>驱动</td>
<td><code>idtp9418</code>（<code>drivers/power/supply/idtp9418.c</code>），<strong>2025-10-23</strong> 才进 sm8150-mainline</td>
</tr>
</tbody>
</table>
<p>相关 GPIO（TLMM，<code>/sys/kernel/debug/gpio</code> 可看）：</p>
<table>
<thead>
<tr>
<th>功能</th>
<th>GPIO</th>
</tr>
</thead>
<tbody>
<tr>
<td>IRQ</td>
<td>95</td>
</tr>
<tr>
<td>使能 enable</td>
<td>3</td>
</tr>
<tr>
<td>升压 boost-enable</td>
<td>47</td>
</tr>
<tr>
<td>霍尔 high / low</td>
<td>101 / 134</td>
</tr>
<tr>
<td>WPC-DET（旧 dtb 有，驱动没用）</td>
<td>94</td>
</tr>
</tbody>
</table>
<p>驱动暴露的节点：</p>
<ul>
<li><code>/sys/class/power_supply/idtp9418/</code> → <code>online</code>、<code>capacity</code>、<code>charge_control_end_threshold</code></li>
<li><code>/sys/bus/i2c/devices/3-003b/</code> → <code>chip_version</code>、<code>chip_status</code>（vout/iin/temp/soc）、<code>chip_powered</code></li>
</ul>
<hr />
<h2>2. 第一层：为什么“没反应”</h2>
<p>设备树里有节点，不代表有驱动。检查：</p>
<pre class="prettyprint lang-bash linenums"><code>ls /sys/bus/i2c/devices/3-003b/          # 设备存在
readlink /sys/bus/i2c/devices/3-003b/driver   # 悬空 → 没有驱动绑定
modinfo idtp9418                          # 6.14.11 上：Module not found
sudo i2cdetect -y 3                       # 0x3b 无应答（芯片没被使能，正常）</code></pre>
<p>结论：6.14.11 的模块目录里<strong>没有</strong> <code>idtp9418.ko</code>，内核也没编进去。设备树节点成了“孤儿”。</p>
<blockquote>
<p>小提示：芯片未上电时 i2c 不 ACK 是正常现象，不能据此判断硬件坏。</p>
</blockquote>
<hr />
<h2>3. 第二层：6.17 上跑通</h2>
<p>ESP 里已经装了 6.17 的 UKI（<code>EFI/ubuntu/6.17.0-UFS-slowdown+.efi</code>），rEFInd 选它启动，然后：</p>
<pre class="prettyprint lang-bash linenums"><code>sudo modprobe idtp9418</code></pre>
<p>笔吸上去（<strong>笔尖朝左</strong>），dmesg 立刻出现：</p>
<pre class="prettyprint linenums"><code>[idtp] Pen attached! Turning on reverse charging...
reverse charging start success
[charge-monitor] pen=1 soc=5</code></pre>
<p><code>/sys/class/power_supply/idtp9418</code> 出现，<code>online=1</code>，笔从 0% 一路充到 90%（约 18 分钟）。</p>
<p><strong>为什么 6.14.11 没有、6.17 有？</strong></p>
<ul>
<li>6.14.11 构建于 <strong>2025-08-14</strong>，驱动首个提交在 <strong>2025-10-23</strong> —— 时间上就不可能有。</li>
<li>6.17 构建于 <strong>2026-04-11</strong>，自然包含。</li>
<li>更麻烦的是<strong>设备树绑定也换了</strong>：6.14 的 dtb 用小米 BSP 风格属性名
（<code>idt,irq</code> / <code>hall,int3</code> / <code>hall,int4</code> / <code>idt,reverse-enable</code> / <code>idt,reverse-booset-enable</code>），
驱动读的是新绑定（<code>irq-gpios</code> / <code>pen-det-active-high-gpios</code> / … / <code>enable-gpios</code> / <code>boost-enable-gpios</code>）。
所以<strong>直接拷 <code>.ko</code> 也 probe 不起来</strong>。</li>
</ul>
<hr />
<h2>4. 笔的坑：SoC 0xFF、充电上限、电量口径</h2>
<h3>4.1 SoC 读到 0xFF 就停充</h3>
<p>驱动 <code>charge-monitor</code> 每 2 秒读一次笔的电量，原逻辑：</p>
<pre class="prettyprint lang-c linenums"><code>if (!attached || soc &lt; 0 || soc &gt;= di-&gt;charge_limit)
    /* 停充 */</code></pre>
<p>笔在<strong>刚上电的一小段时间内不响应</strong>电量查询（寄存器 <code>0x3a</code> 返回 <code>0xFF</code>），
于是驱动 2 秒内就判定失败并停充——表现就是“吸上去闪一下就没反应”。</p>
<h3>4.2 充电上限 85%</h3>
<p><code>charge_control_end_threshold</code> 默认 <strong>85</strong>，写值上限 <strong>95</strong>：</p>
<pre class="prettyprint lang-bash linenums"><code>echo 95 | sudo tee /sys/class/power_supply/idtp9418/charge_control_end_threshold</code></pre>
<p>笔的电量按 10% 台阶上报（80 → 90），会一步跨过 85，所以在 90 停下是正常的。</p>
<h3>4.3 停充后 capacity 归 0</h3>
<p>驱动的 <code>POWER_SUPPLY_PROP_CAPACITY</code> 在 <code>!is_charging</code> 时直接返回 0，所以停充后读 <code>capacity</code> 是 0，
不是电量真的掉了。</p>
<h3>4.4 两种电量的口径</h3>
<table>
<thead>
<tr>
<th>来源</th>
<th>什么时候有</th>
<th>特点</th>
</tr>
</thead>
<tbody>
<tr>
<td>WPC（<code>idtp9418/capacity</code>）</td>
<td>只在无线充电时</td>
<td>实时，但粒度粗、会跳（0→5→10→20→40）</td>
</tr>
<tr>
<td>BLE（GATT <code>0x2A19</code> / UPower）</td>
<td>只要蓝牙连着</td>
<td>细，但充电时可能长时间不刷新</td>
</tr>
</tbody>
</table>
<p>实测两者会差 10~20%，充满后（90%）又能对上——是<strong>刷新节奏不同</strong>，不是两套电量计。</p>
<hr />
<h2>5. 蓝牙：配对与能读到的信息</h2>
<p><strong>为什么 MIUI 吸上自动连，Linux 不会</strong>：MIUI 有小米笔系统服务，霍尔检测到吸附就主动唤醒并配对；
Linux/BlueZ 没有这个服务，笔平时不广播（省电），必须<strong>长按笔上的按键约 5 秒</strong>进入配对模式。</p>
<pre class="prettyprint lang-bash linenums"><code>bluetoothctl --timeout 120 scan on
# 长按笔按键，出现 Xiaomi Smart Pen 后：
bluetoothctl pair C0:2B:3D:70:C6:E9
bluetoothctl trust C0:2B:3D:70:C6:E9
bluetoothctl connect C0:2B:3D:70:C6:E9</code></pre>
<blockquote>
<p>笔的蓝牙地址是<strong>随机地址</strong>，重新配对会变，脚本里别硬编码，按名字匹配。</p>
</blockquote>
<p>能读到的 GATT 信息（GATT 读需要 root，BlueZ 的 D-Bus 策略）：</p>
<pre class="prettyprint lang-bash linenums"><code>sudo busctl --system call org.bluez \
  /org/bluez/hci0/dev_XX/serviceXXXX/charXXXX \
  org.bluez.GattCharacteristic1 ReadValue 'a{sv}' 0</code></pre>
<table>
<thead>
<tr>
<th>服务/特征</th>
<th>内容</th>
<th>实测值</th>
</tr>
</thead>
<tbody>
<tr>
<td>0x180F / 0x2A19</td>
<td>电量</td>
<td>20%</td>
</tr>
<tr>
<td>0x180A / 0x2A29</td>
<td>厂商</td>
<td><code>Xiaomi</code></td>
</tr>
<tr>
<td>0x180A / 0x2A24</td>
<td>型号</td>
<td><code>wPI07U81PC</code></td>
</tr>
<tr>
<td>0x180A / 0x2A26</td>
<td>固件版本</td>
<td><code>00.00.0703</code></td>
</tr>
<tr>
<td>0x180A / 0x2A28</td>
<td>软件版本</td>
<td><code>00.00.0703</code></td>
</tr>
<tr>
<td>0x180A / 0x2A50</td>
<td>PnP ID</td>
<td>vendor <code>0x1915</code>（Nordic）/ product <code>0xEAEA</code> / ver <code>0x0001</code></td>
</tr>
<tr>
<td>0x1812 HID</td>
<td>Report Map</td>
<td>Report 0x10：4 个按键位；0x21：1 字节 feature</td>
</tr>
<tr>
<td>0xFE59</td>
<td>Nordic Secure DFU</td>
<td>固件升级服务，<strong>别乱刷</strong></td>
</tr>
</tbody>
</table>
<p>没有序列号（0x2A25）和硬件版本（0x2A27）；压感/倾角不走蓝牙（走 NVT 触控）。</p>
<hr />
<h2>6. 第三层：给 6.14.11 编驱动</h2>
<h3>6.1 准备</h3>
<pre class="prettyprint lang-bash linenums"><code># 1) 保存当前 6.14.11 的 config（IKCONFIG）
zcat /proc/config.gz &gt; ~/nabu-build/config-6.14.11

# 2) 下载 6.14.11 源码（约 143MB）
curl -L -o linux-6.14.11.tar.xz https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.14.11.tar.xz
tar -xJf linux-6.14.11.tar.xz

# 3) 配置 + 准备模块构建（本机 arm64 原生编译）
cd linux-6.14.11
cp ../config-6.14.11 .config
make ARCH=arm64 olddefconfig
make -j8 ARCH=arm64 modules_prepare
make -s kernelrelease     # 6.14.11-nabu-tmm</code></pre>
<p>关键点：<strong><code>kernelrelease</code> 必须和运行内核一致</strong>（<code>6.14.11-nabu-tmm</code>），否则模块加载时 vermagic 不匹配。
本机 <code>CONFIG_MODVERSIONS=n</code>、<code>CONFIG_MODULE_SIG=n</code>，所以不需要 <code>Module.symvers</code> 的 CRC、也不校验签名。</p>
<h3>6.2 设备树绑定差异（改驱动，不改 dtb）</h3>
<p>6.14 的 dtb 用旧名字，所以把驱动的 GPIO 获取换成“按旧属性名查找”。
6.14 没有 <code>devm_gpiod_get_from_of_node</code>，改用 legacy API：</p>
<pre class="prettyprint lang-c linenums"><code>#include &lt;linux/gpio.h&gt;
#include &lt;linux/of_gpio.h&gt;

struct { const char *name; const char *prop; struct gpio_desc **gpio; unsigned long flags; }
gpio_map[] = {
    {"irq",                 "idt,irq",                 &amp;di-&gt;gpios.idt_irq,              GPIOF_IN},
    {"enable",              "idt,reverse-enable",      &amp;di-&gt;gpios.enable,               GPIOF_OUT_INIT_HIGH},
    {"boost-enable",        "idt,reverse-booset-enable",&amp;di-&gt;gpios.boost_enable,         GPIOF_OUT_INIT_HIGH},
    {"pen-det-active-high", "hall,int3",               &amp;di-&gt;gpios.pen_det_active_high,  GPIOF_IN},
    {"pen-det-active-low",  "hall,int4",               &amp;di-&gt;gpios.pen_det_active_low,   GPIOF_IN},
};

for (int i = 0; i &lt; ARRAY_SIZE(gpio_map); i++) {
    int gpio = of_get_named_gpio(di-&gt;dev-&gt;of_node, gpio_map[i].prop, 0);
    if (gpio &lt; 0) return gpio;
    ret = devm_gpio_request_one(di-&gt;dev, gpio, gpio_map[i].flags, gpio_map[i].name);
    if (ret) return ret;
    *gpio_map[i].gpio = gpio_to_desc(gpio);
}</code></pre>
<blockquote>
<p>注意 <code>idt,reverse-booset-enable</code> 里的 <strong>booset</strong> 是 dtb 里就有的拼写，别手滑改掉。</p>
</blockquote>
<h3>6.3 编译与加载</h3>
<p>源码树里没有 <code>Module.symvers</code> 时，modpost 会把所有符号报成 undefined。
<code>MODVERSIONS=n</code> 时这些符号在加载阶段由内核解析，所以可以直接：</p>
<pre class="prettyprint lang-bash linenums"><code>make -C ~/nabu-build/linux-6.14.11 M=$PWD ARCH=arm64 KBUILD_MODPOST_WARN=1 modules</code></pre>
<p>产物 <code>idtp9418.ko</code> 的 <code>vermagic</code> 必须是 <code>6.14.11-nabu-tmm SMP preempt mod_unload aarch64</code>。</p>
<p>安装并加载：</p>
<pre class="prettyprint lang-bash linenums"><code>sudo cp idtp9418.ko /lib/modules/6.14.11-nabu-tmm/extra/
sudo depmod -a 6.14.11-nabu-tmm
sudo insmod idtp9418.ko        # 或 modprobe</code></pre>
<p>加载后驱动自动绑定到 <code>3-003b</code>，<code>/sys/class/power_supply/idtp9418</code> 出现。
开机自动加载：<code>/etc/modules-load.d/charge-stylus.conf</code> 写一行 <code>idtp9418</code>。</p>
<h3>6.4 重试补丁 v2</h3>
<p>解决 4.1 的“0xFF 立刻停充”：</p>
<pre class="prettyprint lang-c linenums"><code>/* 笔刚上电时 SoC 可能暂时读不到（0xFF），先重试 ~30 秒再放弃 */
if (soc &gt;= 0)
    di-&gt;soc_retry = 0;
if (!attached || soc &gt;= di-&gt;charge_limit || (soc &lt; 0 &amp;&amp; ++di-&gt;soc_retry &gt; 15)) {
    /* 停充，soc_retry = 0 */
}</code></pre>
<p>实测：吸附后第 2 秒笔就报出 <code>soc=20</code>，充电继续。</p>
<hr />
<h2>7. 展示层：终端小组件与顶栏插件</h2>
<h3>7.1 终端小组件</h3>
<p><code>~/.local/bin/watch_battery.sh</code>（桌面上有启动器“电池实时电压”）：</p>
<ul>
<li>充电中：<code>● 充电中 10% 0.85 W 54 °C 上限 85%</code></li>
<li>未充电：<code>○ 未充电（蓝牙电量 80%）</code></li>
<li>没有驱动：<code>不可用（本内核无 idtp9418 驱动）</code></li>
</ul>
<p>功率 = <code>vout × iin</code>（从 <code>chip_status</code> 读，raw 值按 mV/mA 解释）。
注意：驱动每读一次 <code>chip_status</code> 会 <code>dev_info</code> 打日志，所以<strong>每 60 秒才读一次</strong>。</p>
<h3>7.2 GNOME 顶栏插件</h3>
<p>本地扩展 <code>batteryvitals@local</code>，在电池数值旁显示 <code>笔XX%</code>：</p>
<ul>
<li>充电中：绿色，用 WPC 值</li>
<li>未充电但蓝牙连着：灰色，用 BLE 值（<code>upower</code>，3 分钟刷新一次）</li>
<li>都没有：隐藏（原来的 <code>笔—</code> 容易被看成“笔一”，已去掉）</li>
</ul>
<blockquote>
<p><strong>大坑</strong>：GNOME Shell 对扩展 JS 有<strong>会话级模块缓存</strong>。
<code>gnome-extensions disable/enable</code>、重装 zip、改 <code>version</code> 都<strong>不会</strong>重新加载代码，
必须<strong>注销重登</strong>（或重启）才生效。</p>
</blockquote>
<hr />
<h2>8. 两个内核的对比与取舍</h2>
<table>
<thead>
<tr>
<th></th>
<th>6.14.11-nabu-tmm</th>
<th>6.17.0-UFS-slowdown+</th>
</tr>
</thead>
<tbody>
<tr>
<td>构建时间</td>
<td>2025-08-14</td>
<td>2026-04-11</td>
</tr>
<tr>
<td>充电栈</td>
<td>ln8000 电荷泵 + PD 9V/3A（快）</td>
<td>smb5，约 5V（慢，平板充满要 8~10 小时）</td>
</tr>
<tr>
<td>UFS 时钟</td>
<td>300MHz</td>
<td>201.5MHz（为三星 UFS 稳定性降频）</td>
</tr>
<tr>
<td>手写笔驱动</td>
<td>无（可自编模块补上）</td>
<td>自带</td>
</tr>
<tr>
<td>电流符号</td>
<td>负 = 充电</td>
<td>正 = 充电（相反）</td>
</tr>
<tr>
<td>模块数</td>
<td>594</td>
<td>656（+76 / −14）</td>
</tr>
</tbody>
</table>
<p><strong>结论</strong>：6.14.11 自编模块后是“快充 + 笔充电”两全，日常用它；6.17 退居备用。</p>
<hr />
<h2>9. 踩坑清单 / 给后来者</h2>
<ol>
<li><strong>设备树有节点 ≠ 有驱动</strong>：先看 <code>/sys/bus/i2c/devices/*/driver</code> 是否悬空。</li>
<li><strong>先确认内核版本与驱动提交时间的先后</strong>：6.14.11 比驱动早两个月，不可能有。</li>
<li><strong>DT 绑定在新旧树之间会漂移</strong>：BSP 属性名 vs mainline 属性名，移植时要么改驱动、要么重打 dtb/UKI。</li>
<li><strong><code>devm_gpiod_get_from_of_node</code> 在 6.14 已不存在</strong>：用 <code>of_get_named_gpio + devm_gpio_request_one + gpio_to_desc</code>。</li>
<li><strong>vermagic 必须逐字匹配</strong>；<code>CONFIG_MODVERSIONS=n</code> 时不需要 Module.symvers，<code>KBUILD_MODPOST_WARN=1</code> 可绕过 modpost。</li>
<li><strong>驱动可能在笔刚上电时读到 0xFF</strong>：给重试窗口，别第一次失败就放弃。</li>
<li><strong>无线充的 SoC 是粗粒度、会跳</strong>；BLE 电量更细但充电时可能不刷新。两者差 10~20% 正常。</li>
<li><strong>GNOME 扩展改完必须重新登录</strong>，disable/enable 不重载模块。</li>
<li><strong>启动额度</strong>：nabu 的 ABL 每启动一次扣 <code>slot-retry-count</code>，7 次上限；频繁切内核有风险，
能接 PC 时用 <code>fastboot set_active a</code> 补满。</li>
<li><strong>6.17 在三星 UFS 上要降频补丁</strong>（201.5MHz），否则可能黑屏/不稳。</li>
</ol>
<hr />
<h2>10. 附录</h2>
<h3>10.1 常用命令</h3>
<pre class="prettyprint lang-bash linenums"><code># 笔是否在充
cat /sys/class/power_supply/idtp9418/{online,capacity}

# 芯片遥测（vout/iin/temp/soc，raw）
cat /sys/bus/i2c/devices/3-003b/chip_status

# 芯片版本（OTP/APP 固件版本）
cat /sys/bus/i2c/devices/3-003b/chip_version

# 驱动日志
sudo dmesg | grep -i idtp | tail -20

# 蓝牙电量
upower -e | grep bluetooth
upower -i /org/freedesktop/UPower/devices/bluetooth_generic_dev_XX

# 模块
modinfo idtp9418
lsmod | grep idtp</code></pre>
<h3>10.2 关键文件</h3>
<table>
<thead>
<tr>
<th>文件</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/etc/modules-load.d/charge-stylus.conf</code></td>
<td>开机自动加载 <code>idtp9418</code></td>
</tr>
<tr>
<td><code>/lib/modules/6.14.11-nabu-tmm/extra/idtp9418.ko</code></td>
<td>自编模块</td>
</tr>
<tr>
<td><code>~/nabu-build/{idtp9418.c,idtp9418.h}</code></td>
<td>打补丁后的驱动源码</td>
</tr>
<tr>
<td><code>~/.local/bin/watch_battery.sh</code></td>
<td>终端小组件</td>
</tr>
<tr>
<td><code>~/.local/share/gnome-shell/extensions/batteryvitals@local/</code></td>
<td>顶栏插件</td>
</tr>
</tbody>
</table>
<h3>10.3 实测数据</h3>
<table>
<thead>
<tr>
<th>场景</th>
<th>数值</th>
</tr>
</thead>
<tbody>
<tr>
<td>无线充输出</td>
<td>0.85W（6.75V × 128mA）→ 2.3W（9.2V × 248mA）</td>
</tr>
<tr>
<td>笔充电</td>
<td>0% → 90% 约 18 分钟</td>
</tr>
<tr>
<td>芯片温度</td>
<td>48 ~ 57 °C（无线充工作范围）</td>
</tr>
<tr>
<td>平板快充（6.14）</td>
<td>9V/3A PD PPS，电池 −4.2A，约 2%/分钟</td>
</tr>
</tbody>
</table>
<hr />
<p><em>本文记录于 2026-11-04，所有命令均在小米平板 5（nabu）+ Ubuntu 26.04 上实测。</em>
<em>驱动源码来自 sm8150-mainline 社区，遵循 GPL-2.0。</em></p>]]></description>
	<pubDate>Wed, 04 Nov 2026 14:50:50 +0000</pubDate>
	<author>赵苦瓜</author>
	<guid>https://blog.zhaokugua.com/?post=122</guid>

</item>
<item>
	<title>小米平板5-Linux使用与排障手册</title>
	<link>https://blog.zhaokugua.com/?post=121</link>
	<description><![CDATA[<blockquote>
	<p>
		最后更新：2026-11-04 20:05
机器：Xiaomi Pad 5（nabu）· Ubuntu 26.04 ARM64 · UFS: <code>SAMSUNG KLUFG8RHGB-B0E1</code> 内核：<code>6.14.11-nabu-tmm</code>（快充 + 手写笔充电，自编 idtp9418 模块）/ <code>6.17.0-xiaomi-nabu-tmm-UFS-slowdown+</code>（自带笔驱动，但只有 5V 慢充）
覆盖：启动与 fastboot 自救 · 充电（含手写笔无线充/蓝牙）· Waydroid 与安卓应用 · 宿主音频 · 亮度音量同步 · 桌面工具链（Markdown 查看 / dsh）
	</p>
</blockquote>
<hr />
<h2>
	0. 速查总表
</h2>
<table>
	<thead>
		<tr>
			<th>
				场景
			</th>
			<th>
				症状
			</th>
			<th>
				处理
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				启动
			</td>
			<td>
				卡在 fastboot 出不来
			</td>
			<td>
				<code>fastboot set_active a</code> → <code>fastboot reboot</code>
			</td>
		</tr>
		<tr>
			<td>
				启动
			</td>
			<td>
				7 次启动额度快用完
			</td>
			<td>
				等桌面通知，补完跑 <code>nabu-boot-count reset</code>
			</td>
		</tr>
		<tr>
			<td>
				充电
			</td>
			<td>
				插着电却在掉电
			</td>
			<td>
				查 <code>v_offset</code>，坏了就<strong>拔插一次</strong>
			</td>
		</tr>
		<tr>
			<td>
				充电
			</td>
			<td>
				只有 5V、输入电流很小
			</td>
			<td>
				同上，多半是 <code>v_offset</code> 被写坏
			</td>
		</tr>
		<tr>
			<td>
				充电
			</td>
			<td>
				低电量插 33W/100W 就抖
			</td>
			<td>
				先用 18W 充到 40% 再换大功率头
			</td>
		</tr>
		<tr>
			<td>
				手写笔
			</td>
			<td>
				吸上去完全没反应
			</td>
			<td>
				6.14 需要 idtp9418 模块（已装，见 2.4）；确认已加载
			</td>
		</tr>
		<tr>
			<td>
				手写笔
			</td>
			<td>
				蓝牙搜不到 / 连不上
			</td>
			<td>
				长按笔按键 5 秒进配对模式，见 2.5
			</td>
		</tr>
		<tr>
			<td>
				Waydroid
			</td>
			<td>
				无声音 / 声音断续
			</td>
			<td>
				见 3.2 两处补丁
			</td>
		</tr>
		<tr>
			<td>
				Waydroid
			</td>
			<td>
				起来 40 秒后自己死
			</td>
			<td>
				完整重建，见 3.4
			</td>
		</tr>
		<tr>
			<td>
				宿主音频
			</td>
			<td>
				卡顿时电流声 / 机器人声
			</td>
			<td>
				量子钉 1024，见 4.1
			</td>
		</tr>
		<tr>
			<td>
				宿主音频
			</td>
			<td>
				开杜比后 50% 以上外放炸
			</td>
			<td>
				9 段拟合 + 零补偿，见 4.2
			</td>
		</tr>
		<tr>
			<td>
				亮度音量
			</td>
			<td>
				两端不同步
			</td>
			<td>
				见 5.1
			</td>
		</tr>
		<tr>
			<td>
				显示
			</td>
			<td>
				黑屏/点不亮 / 电源键唤不醒
			</td>
			<td>
				见附录 F
			</td>
		</tr>
		<tr>
			<td>
				游戏
			</td>
			<td>
				碧蓝航线点开闪退（EXIT_SELF 1）
			</td>
			<td>
				见 3.11，WebView 已修；仍卡 <code>BgcPlugin</code> NPE
			</td>
		</tr>
		<tr>
			<td>
				游戏
			</td>
			<td>
				异环黑屏（卡启动闪屏后 SIGSEGV）
			</td>
			<td>
				见 3.12，<code>libUnreal.so</code> 静态构造空写，容器里基本无解
			</td>
		</tr>
		<tr>
			<td>
				游戏
			</td>
			<td>
				任何 app 的 WebView 白屏/闪退
			</td>
			<td>
				<code>echo '--disable-gpu-compositing' &gt; /data/local/tmp/webview-command-line</code>
			</td>
		</tr>
		<tr>
			<td>
				文档
			</td>
			<td>
				双击 <code>.md</code> 只有纯文本、想要渲染
			</td>
			<td>
				用 dsh 的文档预览，见 6.1–6.2；<strong>别装 apostrophe</strong>
			</td>
		</tr>
		<tr>
			<td>
				文档
			</td>
			<td>
				要打开工作区以外的文件
			</td>
			<td>
				「文件」面板标题栏的「打开任意文件」按钮，见 6.3
			</td>
		</tr>
		<tr>
			<td>
				dsh
			</td>
			<td>
				装插件报 <code>spawn pnpm ENOENT</code>
			</td>
			<td>
				<code>sudo npm i -g pnpm@10</code>，见 6.4
			</td>
		</tr>
	</tbody>
</table>
<hr />
<h2>
	第 1 部分 · 系统与启动
</h2>
<h3>
	1.1 启动链与分区
</h3>
<pre class="prettyprint linenums"><code>ABL（打过 dualboot 补丁）→ boot_a 里的 bootmanager（TWRP-LINUX）
    → ESP(sda31) 上的 rEFInd（菜单 5 秒）→ EFI/ubuntu/*.efi
    → 内核 → root=PARTLABEL=linux (sda32)</code></pre>
<ul>
	<li>
		<strong>ESP</strong>：<code>/dev/disk/by-partlabel/esp</code>（334MB，sda31），含 <code>EFI/ubuntu/6.14.11.efi</code>、<code>6.17.0-UFS-slowdown+.efi</code>
	</li>
	<li>
		<strong>根分区</strong>：<code>linux</code>（sda32，458GB，挂 <code>/</code>）
	</li>
	<li>
		<strong>boot_a</strong>：TWRP-LINUX（dualboot bootmanager），<strong>不要随意刷</strong>
	</li>
	<li>
		<code>anti:1</code>（防回滚已开启，别刷旧固件）
	</li>
</ul>
<h3>
	1.2 A/B 槽：只有 7 次启动额度
</h3>
<p>
	实测事实（2026-11-02）：
</p>
<ul>
	<li>
		<strong>每次启动 <code>slot-retry-count:a</code> 减 1</strong>（实测 5 → 4，一次完整启动）
	</li>
	<li>
		<code>fastboot set_active a</code> 把它重置为 <strong>7</strong>，同时清掉 <code>slot-unbootable</code>
	</li>
	<li>
		<code>slot-successful:a</code> <strong>永远是 <code>no</code></strong>：Ubuntu 侧没有任何东西上报"启动成功"
	</li>
	<li>
		减到 0 → ABL 把该槽标记 <code>unbootable</code> → 没有可启动的槽 → <strong>停在 fastboot</strong> （11/02 那次事故就是这么来的）
	</li>
	<li>
		槽元数据<strong>不在 AP 可读分区</strong>：108 个 &lt;128MB 分区哈希对比，<code>set_active a</code> 前后只有 <code>logfs</code> 变化（UEFI 日志）；<code>misc</code> 恒为那 15 个字节、<code>devinfo</code> 全零。
结论：存在 AP 访问不到的保护区（UFS LUN / RPMB / ABL 自有存储），<strong>Linux 侧写不了"启动成功"</strong>
	</li>
	<li>
		<code>fastboot oem off-mode-charge 1</code> 能写入（连带把 <code>charger-screen-enabled</code> 置 1），
但<strong>挡不住插电自动开机</strong>，对保住计数没用
	</li>
</ul>
<p>
	建议节奏：
</p>
<ul>
	<li>
		计数到 4 会弹通知，此时还剩 3 次余量，找方便的时间补
	</li>
	<li>
		平时尽量用挂起代替关机，能显著减少启动次数
	</li>
	<li>
		<strong>别在没接 PC 的情况下重启</strong>
	</li>
</ul>
<h3>
	1.3 掉进 fastboot 怎么救
</h3>
<pre class="prettyprint lang-bash linenums"><code># 只读确认
fastboot getvar current-slot        # 期望 a
fastboot getvar slot-retry-count:a
fastboot getvar slot-unbootable:a
fastboot getvar slot-successful:a

# 恢复
fastboot set_active a
fastboot reboot</code></pre>
<p>
	补完计数：
</p>
<pre class="prettyprint lang-bash linenums"><code>nabu-boot-count reset     # 计数清零，重新开始算 7 次</code></pre>
<h3>
	1.4 内核怎么选
</h3>
<table>
	<thead>
		<tr>
			<th>
				<br />
			</th>
			<th>
				6.14.11-nabu-tmm
			</th>
			<th>
				6.17.0-UFS-slowdown+
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				充电栈
			</td>
			<td>
				ln8000 电荷泵
			</td>
			<td>
				qcom-smbx-charger（SMB5）
			</td>
		</tr>
		<tr>
			<td>
				充电
			</td>
			<td>
				9V/3A PD PPS，快（−3.8A）
			</td>
			<td>
				5V DCP，慢（输入约 595mA）
			</td>
		</tr>
		<tr>
			<td>
				电池电流符号
			</td>
			<td>
				负 = 充电
			</td>
			<td>
				<strong>正 = 充电（相反）</strong>
			</td>
		</tr>
		<tr>
			<td>
				UFS 时钟
			</td>
			<td>
				300MHz
			</td>
			<td>
				201.5MHz（为三星 UFS 降频）
			</td>
		</tr>
		<tr>
			<td>
				手写笔无线充
			</td>
			<td>
				✓ 需自编 <code>idtp9418</code> 模块（已装，见 2.4）
			</td>
			<td>
				✓ 自带驱动（笔尖朝左吸附）
			</td>
		</tr>
		<tr>
			<td>
				已知问题
			</td>
			<td>
				<code>v_offset</code> 可能被写坏
			</td>
			<td>
				充电慢，<code>status</code>/电流字段不可信
			</td>
		</tr>
	</tbody>
</table>
<ul>
	<li>
		日常推荐 <strong>6.14.11</strong>（快充可用），充电头用 18W
	</li>
	<li>
		<strong>要给手写笔充电只能切 6.17</strong>，代价是 UFS 限速、只能用 5V 慢充（见 2.4）
	</li>
	<li>
		rEFInd 菜单只有 5 秒，注意别选错
	</li>
</ul>
<h3>
	1.5 已装工具与数据文件
</h3>
<table>
	<thead>
		<tr>
			<th>
				工具
			</th>
			<th>
				类型
			</th>
			<th>
				作用
			</th>
			<th>
				开关
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>nabu-charge-guard</code>
			</td>
			<td>
				用户服务
			</td>
			<td>
				5 分钟滑窗净放电 &gt;250mA 时弹窗告警（按内核自动处理电流符号）
			</td>
			<td>
				<code>systemctl --user disable --now nabu-charge-guard</code>
			</td>
		</tr>
		<tr>
			<td>
				<code>nabu-charge-log</code>
			</td>
			<td>
				用户服务
			</td>
			<td>
				1Hz 记录充电状态到 CSV
			</td>
			<td>
				<code>systemctl --user disable --now nabu-charge-log</code>
			</td>
		</tr>
		<tr>
			<td>
				<code>nabu-boot-count</code>
			</td>
			<td>
				用户 oneshot
			</td>
			<td>
				启动计数（按 boot_id 去重，注销不会重复计数），到 4 弹窗提醒
			</td>
			<td>
				<code>systemctl --user disable --now nabu-boot-count</code>
			</td>
		</tr>
		<tr>
			<td>
				<code>fix-charger.sh</code>
			</td>
			<td>
				手动脚本
			</td>
			<td>
				只读诊断（打印 <code>v_offset</code>、电池状态）
			</td>
			<td>
				<code>sudo work/fix-charger.sh</code>
			</td>
		</tr>
	</tbody>
</table>
<p>
	数据位置：
</p>
<ul>
	<li>
		<code>~/.local/share/nabu-charge.csv</code> — 1Hz 充电日志（10MB 自动轮转）
	</li>
	<li>
		<code>~/.local/share/nabu/boot-count</code> — 启动计数
	</li>
	<li>
		<code>~/.local/share/nabu/part-hashes-before.txt</code> — 分区哈希基线（考古用，可删）
	</li>
</ul>
<hr />
<h2>
	第 2 部分 · 充电
</h2>
<h3>
	2.1 <code>v_offset</code> 坏掉是主要故障
</h3>
<table>
	<thead>
		<tr>
			<th>
				指标
			</th>
			<th>
				正常
			</th>
			<th>
				坏掉
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>v_offset</code>（dmesg）
			</td>
			<td>
				约 150 ~ 420（随输入电压浮动）
			</td>
			<td>
				<code>10</code> 或 <code>4294xxx</code>（负数溢出）
			</td>
		</tr>
		<tr>
			<td>
				<code>adc_vin</code>
			</td>
			<td>
				8200 ~ 8300
			</td>
			<td>
				5000 ~ 7700
			</td>
		</tr>
		<tr>
			<td>
				<code>adc_iin</code>
			</td>
			<td>
				2400 ~ 2700
			</td>
			<td>
				39（几乎不抽电流）
			</td>
		</tr>
		<tr>
			<td>
				电池电流
			</td>
			<td>
				−3.8A（充电）
			</td>
			<td>
				+1.2 ~ +2A（放电）
			</td>
		</tr>
	</tbody>
</table>
<p>
	坏掉后 <code>dmesg</code> 会刷：<code>FAULT_INT</code> / <code>volt_fault_detected (volt_qual=0)</code> / <code>vac_ov_control_work</code> / <code>stop charging</code> ↔ <code>start charging</code> 循环。
</p>
<p>
	<strong>原因</strong>：<code>v_offset</code> 是 ln8000 驱动内部算的 ADC 偏置。输入电压瞬时跌落时
（大功率 PD 头在 PPS 下调压、电荷泵换工作点），驱动会算出负值，后续所有电压换算全错
→ 误判"输入电压不合格" → 反复停充。坏值<strong>不会自己恢复</strong>，只在驱动初始化时重算。
</p>
<p>
	<strong>恢复</strong>：拔掉充电器重新插上（驱动重新初始化，<code>v_offset</code> 回到 ~390）。
</p>
<pre class="prettyprint lang-bash linenums"><code># 判断是否在坏状态
journalctl -k -n 20 | grep -o 'v_offset=[0-9]*' | tail -1
sudo dmesg | grep adc_vin | tail -2</code></pre>
<blockquote>
	<p>
		⚠️ <strong>绝对不要 unbind/rebind ln8000 驱动</strong> 实测 <code>echo 1-0051 &gt; /sys/bus/i2c/drivers/ln8000_charger/unbind</code> 触发内核 oops： <code>free_irq</code> → <code>power_supply_unregister</code> → <code>kernfs_remove_by_name_ns</code> 三个 WARNING，
接着 NULL pointer dereference，<strong>整机死机</strong>，只能长按电源重启。
	</p>
</blockquote>
<h3>
	2.2 充电头与线：低电量才是风险点
</h3>
<p>
	实测规律（2026-11-02 当天）：
</p>
<table>
	<thead>
		<tr>
			<th>
				电量
			</th>
			<th>
				充电头
			</th>
			<th>
				结果
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				16 ~ 25%
			</td>
			<td>
				33W
			</td>
			<td>
				反复 fault、<code>v_offset</code> 被写坏、净放电
			</td>
		</tr>
		<tr>
			<td>
				45 ~ 56%
			</td>
			<td>
				18W
			</td>
			<td>
				稳定 −3.8A，0 故障
			</td>
		</tr>
		<tr>
			<td>
				<strong>56 → 99%</strong>
			</td>
			<td>
				<strong>100W</strong>
			</td>
			<td>
				<strong>连续 45 分钟 0 故障、0 掉线</strong>，电流从 −5.1A 平滑回落到 −2.1A
			</td>
		</tr>
	</tbody>
</table>
<ul>
	<li>
		低电量时充电电流最大（实测冲到 −5.1A），电荷泵输入电流大、VBUS 更容易跌落，
一跌就是 PD hard reset → <code>v_offset</code> 被写坏 → fault 循环
	</li>
	<li>
		电量上来后电流自然回落（恒流转恒压），链路就稳了
	</li>
	<li>
		<strong>建议：低于 ~30% 用 18W 或 5V 头；充到 40% 以上再换大功率头</strong>，100W 也能长时间稳定
	</li>
	<li>
		18W 也不是绝对安全（出现过 5V 合约下同样写坏的情况），核心仍是"坏了就拔插"
	</li>
</ul>
<p>
	补充（11/03–11/04 实测）：
</p>
<ul>
	<li>
		<strong>拔线/插线的瞬间</strong>也会出现一次 <code>v_offset</code> 溢出（<code>adc_vin</code> 同时掉到 5~7V），
这是正常瞬变：重新插入时驱动会重新初始化，不影响充电
	</li>
	<li>
		真正危险的是<strong>插着电、充电过程中被写坏</strong>：此时会一直卡在 fault 循环，必须拔插一次
	</li>
	<li>
		26 小时里没有重启，A/B 计数没被消耗
	</li>
	<li>
		低电量（6~8%）那次的现场：<code>tcpm</code> 电压 0V、电池 +1.5A 放电，重插后立刻恢复 −3.6A
	</li>
	<li>
		<strong>开机前/休眠中线已插着 → 只协商到 5V</strong>（11/04 12:36 复现两次）：
tcpm <strong>只在"重新接入"时做 PD 协商</strong>。线如果先插着再开机（或休眠中插电），
它直接以 Type-C 5V 初始化，之后<strong>不会自己重试 PD，重启也无效</strong> （因为开机时线还插着，又按 5V 初始化一遍）。
实测：休眠插电 → 5V；<code>v_offset</code> 被写坏、不充；重启后仍是 5V； <strong>真正拔插一次 → 立刻 9V / −4.8A 快充</strong>，<code>v_offset</code> 回到 434
	</li>
	<li>
		判断有没有 PD：<code>cat /sys/class/power_supply/tcpm-source-psy-*/usb_type</code> 
		<ul>
			<li>
				<code>C [PD] PD_PPS</code> = 有 PD（9V）
			</li>
			<li>
				<code>[C] PD PD_PPS</code> = 只有 Type-C 5V，没 PD ← 需要拔插
			</li>
		</ul>
	</li>
	<li>
		5V 下不是完全不能充：PMIC 旁路能给约 0.6A（<code>ln8000 present=0</code>），慢但不断电
	</li>
	<li>
		软件侧改 <code>input_current_limit</code> 无效，已试过；<code>port_type</code> 切换也推不动 typec
	</li>
</ul>
<h3>
	2.3 怎么判断现在稳不稳
</h3>
<pre class="prettyprint lang-bash linenums"><code># 最近 1 分钟有没有 fault（0 = 稳）
journalctl -k --since '1 minute ago' | grep -c 'FAULT_INT has occurred'

# 20 秒内 Type-C 掉线次数（0~1 = 稳）
timeout 20 udevadm monitor --subsystem-match=typec 2&gt;/dev/null | grep -c port0-partner

# v_offset 是否在正常区间
journalctl -k -n 20 | grep -o 'v_offset=[0-9]*' | tail -1</code></pre>
<h3>
	2.4 手写笔无线充电（IDT P9418，2026-11-04 打通）
</h3>
<p>
	<strong>一句话</strong>：笔能无线充。6.17 自带驱动；6.14.11 原来没有，<strong>2026-11-04 已自编模块补上</strong>（见本节末尾更新）。
</p>
<table>
	<thead>
		<tr>
			<th>
				项目
			</th>
			<th>
				值
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				充电芯片
			</td>
			<td>
				IDT <strong>P9418</strong>（无线充电发射端），i2c-3 / <code>0x3b</code>，设备树节点 <code>idtp9418@3b</code>
			</td>
		</tr>
		<tr>
			<td>
				驱动
			</td>
			<td>
				<code>idtp9418</code>（<code>drivers/power/supply/idtp9418.c</code>，Nicola Guerrera，2025-10-23 进 sm8150-mainline）
			</td>
		</tr>
		<tr>
			<td>
				电源节点
			</td>
			<td>
				<code>/sys/class/power_supply/idtp9418</code>（<code>type=Wireless</code>，<code>online</code> / <code>capacity</code>）
			</td>
		</tr>
		<tr>
			<td>
				自动加载
			</td>
			<td>
				<code>/etc/modules-load.d/charge-stylus.conf</code> → <code>idtp9418</code>（本次新建）
			</td>
		</tr>
		<tr>
			<td>
				充电上限
			</td>
			<td>
				<code>charge_control_end_threshold</code>，默认 <strong>85%</strong>（<code>echo 100 &gt; ...</code> 可改）
			</td>
		</tr>
		<tr>
			<td>
				引脚
			</td>
			<td>
				霍尔 <code>gpio101</code>/<code>gpio134</code>，使能 <code>gpio3</code>，boost <code>gpio47</code>，IRQ <code>gpio95</code>，WPC-DET <code>gpio94</code>（TLMM）
			</td>
		</tr>
		<tr>
			<td>
				诊断属性
			</td>
			<td>
				<code>/sys/bus/i2c/devices/3-003b/{chip_version,chip_status,chip_powered}</code>
			</td>
		</tr>
	</tbody>
</table>
<p>
	<strong>用法</strong>
</p>
<ol>
	<li>
		用 6.17 内核启动（rEFInd 选 <code>6.17.0-UFS-slowdown+</code>）
	</li>
	<li>
		笔<strong>笔尖朝左</strong>吸到平板顶部短边（方向反了不充）
	</li>
	<li>
		看状态：
	</li>
</ol>
<pre class="prettyprint lang-bash linenums"><code>cat /sys/class/power_supply/idtp9418/{online,capacity}
sudo dmesg | grep idtp | tail -5
# 正常应看到：
#   [idtp] Pen attached! Turning on reverse charging...
#   reverse charging start success
#   [charge-monitor] pen=1 soc=5</code></pre>
<p>
	桌面「电池实时电压」小组件已加上手写笔一行（<code>~/.local/bin/watch_battery.sh</code>）： <code>● 充电中 12% 上限 85%</code> / <code>○ 未充电</code> / <code>不可用（本内核无驱动）</code>。
</p>
<p>
	<strong>实测（11/04 19:52）</strong>：笔放到完全没电（0%），吸上后 <strong>0% → 5% → 10%（约 5%/分钟）</strong>。
</p>
<p>
	<strong>已知坑</strong>
</p>
<ul>
	<li>
		<strong>SoC 读失败会永久停充</strong>：<code>charge-monitor</code> 读到 <code>soc=-1</code>（寄存器 <code>0x3a</code> 返回 <code>0xFF</code>）就打印 <code>stopping reverse charging</code> 并停掉；之后只有把笔<strong>取下再吸一次</strong>才会重新开始。
重新 <code>modprobe</code>/probe 都不会自动续充（probe 里没有启动充电的路径）。
	</li>
	<li>
		停充后 <code>online=0</code>、<code>capacity=0</code>、<code>chip_powered=0</code>，但霍尔仍显示"已吸附"，别被骗。
	</li>
	<li>
		电量为 0 的笔刚吸上时最容易触发；先充到 5~10% 再重新吸附基本就稳了。
	</li>
	<li>
		<strong>2026-11-04 20:37 新现象（待验证）</strong>：蓝牙配对之后，每次吸附都是 <code>reverse charging start success</code> → 连续 <code>soc is default 0xFF</code> → 约 6 秒内停充；
配对前同样的笔从 0% 一路充到 90% 完全正常。怀疑是<strong>蓝牙连接状态下笔不响应无线充握手</strong> （或笔发热保护）。验证方法：断开蓝牙再吸附，看是否恢复；如果恢复，就说明是 BLE 干扰。
	</li>
	<li>
		充电时笔的触控输入会被 NVT 驱动主动关掉（<code>pen_charge_state_notifier</code>），画不了是正常的。
	</li>
	<li>
		反向充电从平板电池取电（约 5W），长时间吸附会掉电。
	</li>
</ul>
<p>
	<strong>为什么只有 6.17 有 / 能不能移植到 6.14.11</strong>
</p>
<ul>
	<li>
		时间线：<code>6.14.11</code> 编于 <strong>2025-08-14</strong>；驱动首个提交在 <strong>2025-10-23</strong>（<code>sm8150-mainline/linux</code> 863ef95e）；<code>6.17</code> 编于 <strong>2026-04-11</strong>。所以 6.14.11 不是"没开配置"，是根本没有这份代码。
	</li>
	<li>
		设备树绑定也换了：6.14 的 dtb 用小米 BSP 属性名
（<code>idt,irq</code> / <code>hall,int3</code> / <code>hall,int4</code> / <code>idt,reverse-enable</code> / <code>idt,reverse-booset-enable</code>），
驱动读的是新绑定（<code>irq-gpios</code> / <code>pen-det-active-high-gpios</code> / <code>pen-det-active-low-gpios</code> / <code>enable-gpios</code> / <code>boost-enable-gpios</code>）。直接拷 <code>.ko</code> 也 probe 不起来。
	</li>
	<li>
		移植<strong>可行，但要动内核</strong>：
		<ol>
			<li>
				需要 6.14.11 的源码树（本机没有，PPA 只发二进制；<code>/lib/modules/6.14.11-nabu-tmm/build</code> 指向作者机器 <code>/home/themojoman/develop/nabu/linux</code>，已失效）；
			</li>
			<li>
				驱动 794 行，只用 gpiod / regmap / power_supply，6.14 的 API 够用；
			</li>
			<li>
				用同 config（<code>/proc/config.gz</code>）编模块，<code>vermagic</code> 要对上 <code>6.14.11-nabu-tmm</code>；
本机 <code>CONFIG_MODVERSIONS=n</code>、<code>CONFIG_MODULE_SIG=n</code>，所以编出来能直接加载；
			</li>
			<li>
				最省事：改驱动读旧属性名（不动 dtb/UKI）；否则要改 dtb 再用 <code>ukify</code> 重打 UKI；
			</li>
			<li>
				真正的风险在电源栈：6.14 是 ln8000 电荷泵 + PD 9V，6.17 是 smb5 5V，
p9418 的反向升压共享 VBUS，没在 6.14 上验证过。
			</li>
		</ol>
	</li>
	<li>
		取舍：留 6.17 → 能充笔，但只能 5V 慢充、UFS 限速；回 6.14.11 → 快充，但笔不能充。
	</li>
</ul>
<p>
	<strong>2026-11-04 21:20 更新：6.14.11 也能充笔了（自编模块）</strong>
</p>
<p>
	原来的结论“只有 6.17 有驱动”已过时——给 6.14.11 编了一个 out-of-tree 的 <code>idtp9418</code> 模块，
装上后 6.14.11 也能正常给笔无线充电，<strong>平板仍是 9V/3A 快充，两不耽误</strong>。
</p>
<ul>
	<li>
		模块位置：<code>/lib/modules/6.14.11-nabu-tmm/extra/idtp9418.ko</code>，开机由 <code>/etc/modules-load.d/charge-stylus.conf</code> 自动加载
	</li>
	<li>
		源码：<code>~/nabu-build/{idtp9418.c,idtp9418.h}</code>，改了两处：
		<ol>
			<li>
				DT 属性名换成 6.14 dtb 里的旧名字（<code>idt,irq</code> / <code>hall,int3</code> / <code>hall,int4</code> / <code>idt,reverse-enable</code> / <code>idt,reverse-booset-enable</code>）
			</li>
			<li>
				GPIO 用 legacy API（<code>of_get_named_gpio</code> + <code>devm_gpio_request_one</code> + <code>gpio_to_desc</code>），
因为 6.14 没有 <code>devm_gpiod_get_from_of_node</code>
			</li>
		</ol>
	</li>
	<li>
		编译：<code>linux-6.14.11</code> 源码树 + 本机 <code>/proc/config.gz</code>，<code>vermagic=6.14.11-nabu-tmm</code> 与原内核一致； <code>KBUILD_MODPOST_WARN=1</code> 跳过缺失的 <code>Module.symvers</code>（<code>MODVERSIONS=n</code>，加载时不校验）
	</li>
	<li>
		实测：笔 0% 吸上后稳定充电（<code>vout 6.75V / iin 128mA ≈ 0.85W / 52~54°C</code>），0%→5%→10%；
蓝牙同时连着也没再出现 <code>0xFF</code>停充
	</li>
	<li>
		结论：<strong>6.14.11 现在“快充 + 笔充电”两全</strong>，不用再为笔切 6.17
	</li>
</ul>
<h3>
	2.5 手写笔蓝牙（2026-11-04 配对成功）
</h3>
<p>
	<strong>为什么 MIUI 吸上就自动连，Linux 不会</strong>：MIUI 有小米笔系统服务，霍尔检测到吸附就主动唤醒笔并按私有流程配对；
Linux/BlueZ 没有这个服务，笔平时不广播（省电），必须<strong>长按笔上的按键</strong>让它进入配对模式。
</p>
<p>
	<strong>配对步骤</strong>
</p>
<ol>
	<li>
		<code>bluetoothctl</code> 里 <code>scan on</code>（或设置 → 蓝牙里保持扫描）
	</li>
	<li>
		长按笔上的按键约 5 秒
	</li>
	<li>
		出现 <code>Xiaomi Smart Pen</code> 后 <code>pair</code> → <code>trust</code> → <code>connect</code>
	</li>
</ol>
<pre class="prettyprint lang-bash linenums"><code>bluetoothctl --timeout 120 scan on     # 长按笔按键期间扫描
bluetoothctl devices | grep -i pen
bluetoothctl pair C0:2B:3D:70:C6:E9
bluetoothctl trust C0:2B:3D:70:C6:E9
bluetoothctl connect C0:2B:3D:70:C6:E9</code></pre>
<blockquote>
	<p>
		笔的蓝牙地址是<strong>随机地址</strong>（<code>random</code>），重新配对可能变，脚本里别硬编码 MAC，用名字匹配。
	</p>
</blockquote>
<p>
	<strong>能读到的信息（GATT）</strong>
</p>
<table>
	<thead>
		<tr>
			<th>
				服务/特征
			</th>
			<th>
				内容
			</th>
			<th>
				实测值
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				0x180F Battery / 0x2A19
			</td>
			<td>
				电量
			</td>
			<td>
				80%
			</td>
		</tr>
		<tr>
			<td>
				0x180A / 0x2A29
			</td>
			<td>
				厂商
			</td>
			<td>
				<code>Xiaomi</code>
			</td>
		</tr>
		<tr>
			<td>
				0x180A / 0x2A24
			</td>
			<td>
				型号
			</td>
			<td>
				<code>wPI07U81PC</code>
			</td>
		</tr>
		<tr>
			<td>
				0x180A / 0x2A26
			</td>
			<td>
				固件版本
			</td>
			<td>
				<code>00.00.0703</code>
			</td>
		</tr>
		<tr>
			<td>
				0x180A / 0x2A28
			</td>
			<td>
				软件版本
			</td>
			<td>
				<code>00.00.0703</code>
			</td>
		</tr>
		<tr>
			<td>
				0x180A / 0x2A50
			</td>
			<td>
				PnP ID
			</td>
			<td>
				vendor <code>0x1915</code>（Nordic）/ product <code>0xEAEA</code> / ver <code>0x0001</code>
			</td>
		</tr>
		<tr>
			<td>
				0x1812 HID
			</td>
			<td>
				Report Map
			</td>
			<td>
				Report 0x10：4 个按键位；0x21：1 字节 feature
			</td>
		</tr>
		<tr>
			<td>
				0xFE59
			</td>
			<td>
				Nordic Secure DFU
			</td>
			<td>
				固件升级服务，<strong>别乱刷</strong>
			</td>
		</tr>
	</tbody>
</table>
<ul>
	<li>
		<strong>没有</strong>序列号（0x2A25）和硬件版本（0x2A27）；压感/倾角不走蓝牙（走 NVT 触控）
	</li>
	<li>
		GATT 读取要 root（BlueZ D-Bus 策略）： <code>sudo busctl --system call org.bluez &lt;path&gt; org.bluez.GattCharacteristic1 ReadValue 'a{sv}' 0</code>
	</li>
	<li>
		电量普通用户可读：<code>upower -e | grep bluetooth</code>，再 <code>upower -i &lt;path&gt;</code>
	</li>
</ul>
<p>
	<strong>两种电量的区别</strong>
</p>
<table>
	<thead>
		<tr>
			<th>
				来源
			</th>
			<th>
				什么时候有
			</th>
			<th>
				可信度
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				WPC（<code>/sys/class/power_supply/idtp9418/capacity</code>）
			</td>
			<td>
				只在无线充电时；停充后读回 0
			</td>
			<td>
				粗（笔通过无线协议报，会 80→90 跳变）
			</td>
		</tr>
		<tr>
			<td>
				BLE（UPower 的 <code>bluetooth_generic_dev_*</code>）
			</td>
			<td>
				只要蓝牙连着
			</td>
			<td>
				细（笔自己的电量计）
			</td>
		</tr>
	</tbody>
</table>
<p>
	<strong>现在面板/小组件的显示逻辑</strong>
</p>
<ul>
	<li>
		充电中：<code>笔XX%</code>（绿，WPC 实时）+ 功率/温度
	</li>
	<li>
		未充电但蓝牙连着：<code>笔XX%</code>（灰，BLE，顶栏 3 分钟刷新一次）
	</li>
	<li>
		都没有：不显示（原来的 <code>笔—</code> 容易被看成“笔一”，已去掉）
	</li>
</ul>
<hr />
<h2>
	第 3 部分 · Waydroid 与安卓应用
</h2>
<h3>
	3.1 最终结论速查
</h3>
<table>
	<thead>
		<tr>
			<th>
				问题
			</th>
			<th>
				根因
			</th>
			<th>
				修复
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				Android 无声音
			</td>
			<td>
				<code>ro.hardware=unknown</code>，Android 加载了空壳 <code>audio.primary.default.so</code>
			</td>
			<td>
				<code>waydroid.cfg</code> 的 <code>[properties]</code> 里加 <code>ro.hardware.audio.primary = waydroid</code>
			</td>
		</tr>
		<tr>
			<td>
				Android 无声音（第二层）
			</td>
			<td>
				HAL 启动环境缺 <code>PULSE_RUNTIME_PATH</code>（upstream <a href="https://github.com/waydroid/waydroid/issues/2333">#2333</a>）
			</td>
			<td>
				overlay 里 <code>init.environ.rc</code> 加全局 <code>export PULSE_RUNTIME_PATH /run/xdg/pulse</code>
			</td>
		</tr>
		<tr>
			<td>
				声音断续 / 只有宿主也在放时才响几秒
			</td>
			<td>
				HAL 的 pulse 客户端缓冲不足，ALSA 持续 <code>XRUN</code>（缓冲欠载），设备拿不到连续数据
			</td>
			<td>
				<code>init.environ.rc</code> 再加 <code>export PULSE_LATENCY_MSEC 60</code>（不够可加到 120）
			</td>
		</tr>
		<tr>
			<td>
				容器起来 ~40 秒后自己死
			</td>
			<td>
				未重建 LXC 配置就 <code>systemctl restart</code>
			</td>
			<td>
				必须先 <code>rm -rf /var/lib/waydroid/lxc &amp;&amp; waydroid init</code> 再启动
			</td>
		</tr>
		<tr>
			<td>
				<code>Session start</code> 报 DBus NoReply
			</td>
			<td>
				用 <code>sudo</code> 启动了 session
			</td>
			<td>
				session 必须<strong>以普通用户身份</strong>启动
			</td>
		</tr>
		<tr>
			<td>
				Android 启动到「手机正在启动」后无限重启
			</td>
			<td>
				<code>/data</code> 属主被 <code>chown -R</code> 改坏，keystore2 打不开数据库
			</td>
			<td>
				只能重置 <code>/data</code>（见「事故复盘」）
			</td>
		</tr>
	</tbody>
</table>
<hr />
<h3>
	3.2 当前生效的配置（持久化）
</h3>
<h4>
	1. <code>/var/lib/waydroid/waydroid.cfg</code>
</h4>
<pre class="prettyprint lang-ini linenums"><code>[properties]
ro.hardware.audio.primary = waydroid</code></pre>
<p>
	作用：让 Android 加载 <code>/vendor/lib/hw/audio.primary.waydroid.so</code>，而不是 default 空壳。
</p>
<h4>
	2. <code>/var/lib/waydroid/overlay_rw/system/init.environ.rc</code>
</h4>
<p>
	内容是在原文件基础上，在 <code>on early-init</code> 块里插入两行：
</p>
<pre class="prettyprint linenums"><code>on early-init
    export PULSE_RUNTIME_PATH /run/xdg/pulse
    export PULSE_LATENCY_MSEC 60
    export ANDROID_BOOTLOGO 1
    ...</code></pre>
<p>
	作用：
</p>
<ul>
	<li>
		<code>PULSE_RUNTIME_PATH</code> —— 让所有 Android 服务（含 audio HAL）继承 pulse socket 路径
	</li>
	<li>
		<code>PULSE_LATENCY_MSEC</code> —— 给 libpulse 客户端设缓冲延迟。<strong>不设这一行，HAL 会持续欠载（XRUN），表现为声音断续或干脆没声</strong>，且症状很迷惑：宿主自己也播放时，设备被"带"起来，Android 的声音会顺带响几秒
	</li>
</ul>
<blockquote>
	<p>
		⚠️ 注意：这两个补丁放在 overlay 里。如果执行 <code>waydroid init -f</code> 且走到 preinstalled images 分支，overlay 会被清空，补丁需要重新加。
	</p>
</blockquote>
<hr />
<h3>
	3.3 验证音频是否正常（几条命令）
</h3>
<pre class="prettyprint lang-bash linenums"><code># 1. HAL 是否加载正确
adb shell getprop ro.hardware.audio.primary     # 应输出 waydroid
PID=$(adb shell pidof android.hardware.audio.service | tr -d '\r')
sudo waydroid shell -- sh -c "grep -oE '[^ ]*audio\.primary[^ ]*\.so' /proc/$PID/maps"
# 应输出 /vendor/lib/hw/audio.primary.waydroid.so

# 2. HAL 环境里有没有 pulse 路径和延迟参数
sudo waydroid shell -- sh -c "tr '\0' '\n' &lt; /proc/$PID/environ | grep PULSE"
# 应输出：
#   PULSE_RUNTIME_PATH=/run/xdg/pulse
#   PULSE_LATENCY_MSEC=60

# 3. 播放时看宿主 PipeWire 有没有 Waydroid 的音源
wpctl status | sed -n '/Streams:/,/Video/p'
# 应看到 "Waydroid" + output_FL/FR -&gt; Speaker playback [active]

# 4. 想确认送出去的不是静音，可以录 sink 监听
pw-record --target=61 /tmp/mon.wav      # 61 是 sink 节点 id，用 wpctl status 查
# 静音时峰值 0；有声时峰值明显大于 0

# 5. 【关键】播放时检查 ALSA 是否在欠载
cat /proc/asound/card1/pcm0p/sub0/status | head -1
# 正常：持续 state: RUNNING
# 异常：SETUP / XRUN 来回跳 —— 这就是断续无声的元凶，加 PULSE_LATENCY_MSEC 解决</code></pre>
<hr />
<h3>
	3.4 正确的启停方式（重要）
</h3>
<h4>
	启动（推荐）
</h4>
<pre class="prettyprint lang-bash linenums"><code># 普通用户身份，不要 sudo
export XDG_RUNTIME_DIR=/run/user/1000
export WAYLAND_DISPLAY=wayland-0
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus

sudo systemctl start waydroid-container
waydroid session start &amp;
waydroid show-full-ui</code></pre>
<p>
	或者直接点桌面「Waydroid 安卓」图标（启动脚本会处理这些）。
</p>
<h4>
	停止
</h4>
<pre class="prettyprint lang-bash linenums"><code>waydroid session stop        # 会连容器一起停</code></pre>
<h4>
	⚠️ 如果容器「起来了但几十秒后自己死」
</h4>
<p>
	不要用 <code>systemctl restart</code>，走完整重建：
</p>
<pre class="prettyprint lang-bash linenums"><code>waydroid session stop
sudo systemctl stop waydroid-container
sudo rm -rf /var/lib/waydroid/lxc
sudo waydroid init          # 重新生成 LXC 配置，不重新下镜像
sudo systemctl start waydroid-container
waydroid session start &amp;    # 普通用户</code></pre>
<blockquote>
	<p>
		这条是今晚反复踩坑总结出来的：<strong>单纯 restart 必挂，重建 lxc 目录后能稳定启动</strong>。
	</p>
</blockquote>
<hr />
<h3>
	3.5 事故复盘（别重蹈覆辙）
</h3>
<h4>
	事故 1：<code>chown -R</code> 毁掉 <code>/data</code>（最严重）
</h4>
<p>
	为了「修复权限」，对 <code>~/.local/share/waydroid/data</code> 执行了 <code>chown -R neko:neko</code>。
</p>
<p>
	后果：
</p>
<ul>
	<li>
		Android 的 <code>system</code> 用户恰好也是 <strong>uid 1000</strong>，所以 <code>/data/system</code> 等文件没事
	</li>
	<li>
		但 keystore 是 <strong>uid 1017</strong>，<code>/sdcard</code> 是 <strong>1023</strong>，各 app 是 <code>u10xxx</code> —— 全被改成 1000
	</li>
	<li>
		keystore2 打不开 <code>persistent.sqlite</code> → abort → system_server 崩 → init 重启 → 无限循环
	</li>
	<li>
		现象就是「能看到手机正在启动，几秒后重启」
	</li>
</ul>
<p>
	<strong>教训：永远不要对整个 <code>/data</code> 做递归 chown。</strong> 需要修单个目录时，先确认它应有的 uid：
</p>
<table>
	<thead>
		<tr>
			<th>
				目录
			</th>
			<th>
				应有 uid
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>/data/system</code>、<code>/data/misc/*</code>（多数）
			</td>
			<td>
				1000（system）
			</td>
		</tr>
		<tr>
			<td>
				<code>/data/misc/keystore</code>
			</td>
			<td>
				1017
			</td>
		</tr>
		<tr>
			<td>
				<code>/data/media</code>、<code>/sdcard</code>
			</td>
			<td>
				1023（media_rw）
			</td>
		</tr>
		<tr>
			<td>
				<code>/data/misc/adb</code>
			</td>
			<td>
				0（root）
			</td>
		</tr>
	</tbody>
</table>
<h4>
	事故 2：改 <code>init.waydroid.rc</code> 导致容器起不来
</h4>
<p>
	尝试在 <code>/system/etc/init/init.waydroid.rc</code> 的 <code>service vendor.audio-hal</code> 块里加 <code>setenv PULSE_RUNTIME_PATH ...</code>，结果容器直接起不来（LXC 运行 40 秒后退出）。
</p>
<p>
	正确做法是改 <strong><code>init.environ.rc</code></strong> 的全局 <code>export</code>（本记录 3.2）。
</p>
<h4>
	事故 3：<code>lxc.environment</code> 不生效
</h4>
<p>
	在 <code>config_base</code> 里加 <code>lxc.environment = PULSE_RUNTIME_PATH=...</code> 后容器能启动，但 Android init <strong>不会</strong>把 LXC 的环境传给它的服务，HAL 的 <code>/proc/&lt;pid&gt;/environ</code> 里依然没有。
</p>
<h4>
	事故 4：用 sudo 启动 session
</h4>
<p>
	<code>sudo waydroid session start</code> 会让 DBus 调用超时（<code>NoReply</code>），表现为「管理器根本没反应」，极容易误判成 Waydroid 坏了。
</p>
<hr />
<h3>
	3.6 网络相关
</h3>
<p>
	机器访问 SourceForge / GitHub Release 极慢（实测 SourceForge 单流 325 B/s，8 线程合计 3 KB/s）。
</p>
<p>
	用 Clash 代理（FlClash GUI，混合端口 7890）后正常：
</p>
<pre class="prettyprint lang-bash linenums"><code># 首次初始化镜像
sudo env https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 \
     no_proxy=ota.waydro.id waydroid init -f</code></pre>
<p>
	注意：
</p>
<ul>
	<li>
		<code>no_proxy=ota.waydro.id</code> 让元数据走直连（有时节点反而连不上 GitHub Pages）
	</li>
	<li>
		走代理实测 6–8 MB/s，912 MB 系统镜像几分钟能下完
	</li>
	<li>
		如果没有代理，可以用 <code>/etc/waydroid-extra/images</code> 预置 <code>system.img</code> + <code>vendor.img</code> 跳过下载
	</li>
</ul>
<hr />
<h3>
	3.7 adb / 文件传输
</h3>
<pre class="prettyprint lang-bash linenums"><code>sudo waydroid adb                           # 开启容器内 adb
adb connect 192.168.240.112:5555
adb devices                                 # 应显示 device</code></pre>
<p>
	如果显示 <code>unauthorized</code>，把宿主公钥灌进去：
</p>
<pre class="prettyprint lang-bash linenums"><code>D=~/.local/share/waydroid/data
sudo mkdir -p $D/misc/adb
sudo cp ~/.android/adbkey.pub $D/misc/adb/adb_keys
sudo waydroid shell -- setprop ctl.restart adbd
adb disconnect 192.168.240.112:5555; adb connect 192.168.240.112:5555</code></pre>
<p>
	日常传文件用桌面「安卓文件传输」工具（GTK4 写的，支持拖拽、双向、目录浏览），或：
</p>
<pre class="prettyprint lang-bash linenums"><code>adb push 文件 /sdcard/Download/
adb pull /sdcard/DCIM/xxx.jpg ~/图片/</code></pre>
<hr />
<h3>
	3.8 关键路径清单
</h3>
<table>
	<thead>
		<tr>
			<th>
				路径
			</th>
			<th>
				说明
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>/var/lib/waydroid/images/</code>
			</td>
			<td>
				system.img（1.9G）+ vendor.img（403M），只读挂载
			</td>
		</tr>
		<tr>
			<td>
				<code>/var/lib/waydroid/overlay_rw/</code>
			</td>
			<td>
				系统分区写入层（我们的 init.environ.rc 补丁在这）
			</td>
		</tr>
		<tr>
			<td>
				<code>/var/lib/waydroid/waydroid.cfg</code>
			</td>
			<td>
				主配置（含 <code>[properties]</code> 自定义属性）
			</td>
		</tr>
		<tr>
			<td>
				<code>/var/lib/waydroid/waydroid.prop</code>
			</td>
			<td>
				每次启动生成的属性文件
			</td>
		</tr>
		<tr>
			<td>
				<code>/var/lib/waydroid/lxc/waydroid/</code>
			</td>
			<td>
				LXC 配置（<code>config</code>、<code>config_session</code>、<code>config_nodes</code>）
			</td>
		</tr>
		<tr>
			<td>
				<code>~/.local/share/waydroid/data/</code>
			</td>
			<td>
				Android <code>/data</code>（用户数据、已装 app）
			</td>
		</tr>
		<tr>
			<td>
				<code>/usr/lib/waydroid/data/configs/config_base</code>
			</td>
			<td>
				LXC 配置模板
			</td>
		</tr>
		<tr>
			<td>
				<code>/usr/lib/waydroid/data/scripts/waydroid-net.sh</code>
			</td>
			<td>
				网桥脚本（<code>start</code>/<code>stop</code>）
			</td>
		</tr>
	</tbody>
</table>
<hr />
<h3>
	3.9 常见现象对照表
</h3>
<table>
	<thead>
		<tr>
			<th>
				现象
			</th>
			<th>
				大概率原因
			</th>
			<th>
				处理
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>Session: RUNNING, Container: STOPPED</code>
			</td>
			<td>
				容器启动失败
			</td>
			<td>
				走「完整重建」流程
			</td>
		</tr>
		<tr>
			<td>
				起来 40 秒后自己死
			</td>
			<td>
				未重建 lxc 配置
			</td>
			<td>
				同上
			</td>
		</tr>
		<tr>
			<td>
				<code>NoReply</code> / 管理器没反应
			</td>
			<td>
				用 sudo 起 session，或管理器卡死
			</td>
			<td>
				用普通用户；必要时 <code>systemctl restart</code>
			</td>
		</tr>
		<tr>
			<td>
				开机画面后无限重启
			</td>
			<td>
				<code>/data</code> 属主损坏（keystore）
			</td>
			<td>
				只能重置 <code>/data</code>
			</td>
		</tr>
		<tr>
			<td>
				无声音
			</td>
			<td>
				HAL 加载错 / 缺 PULSE 环境
			</td>
			<td>
				见 3.2 两处补丁
			</td>
		</tr>
		<tr>
			<td>
				声音断续，或只有别的程序放声音时才响几秒
			</td>
			<td>
				ALSA <code>XRUN</code> 欠载
			</td>
			<td>
				<code>init.environ.rc</code> 加 <code>PULSE_LATENCY_MSEC 60</code>，不够改 120
			</td>
		</tr>
		<tr>
			<td>
				声音突然变成"机器人/炸麦"，过一阵自己恢复
			</td>
			<td>
				音频图量子被压到 512，与 Waydroid 720 帧周期不对齐
			</td>
			<td>
				<code>force-quantum</code>/<code>min-quantum</code> 钉 1024，见 4.1
			</td>
		</tr>
		<tr>
			<td>
				界面卡顿时有电流声/爆音（跟 Android 无关）
			</td>
			<td>
				PipeWire 无实时优先级 + 缓冲偏小
			</td>
			<td>
				见 4.1
			</td>
		</tr>
		<tr>
			<td>
				开杜比音效后，音量 50% 以上外放"炸"
			</td>
			<td>
				20 段 EQ 级联比曲线深 5 dB，补偿增益把功放推进保护
			</td>
			<td>
				见 4.2：9 段拟合 + 零补偿 + −1 dBFS 保险
			</td>
		</tr>
		<tr>
			<td>
				开杜比后同音量明显变轻
			</td>
			<td>
				校正本身是减法，净衰减约 7 dB
			</td>
			<td>
				正常，靠音量旋钮补，见 4.2
			</td>
		</tr>
		<tr>
			<td>
				<code>adb unauthorized</code>
			</td>
			<td>
				容器 <code>/data</code> 里没有宿主公钥
			</td>
			<td>
				见 3.7
			</td>
		</tr>
		<tr>
			<td>
				首次 init 卡在下载
			</td>
			<td>
				SourceForge 被限速
			</td>
			<td>
				挂代理，或预置镜像
			</td>
		</tr>
		<tr>
			<td>
				桌面图标点了没反应
			</td>
			<td>
				容器没起 / session 没起
			</td>
			<td>
				先手动 <code>waydroid session start</code> 看报错
			</td>
		</tr>
	</tbody>
</table>
<hr />
<h3>
	3.10 一次性恢复脚本（保留数据）
</h3>
<pre class="prettyprint lang-bash linenums"><code>#!/bin/bash
# 保留 /data 和镜像，只重建运行环境
export XDG_RUNTIME_DIR=/run/user/1000
export WAYLAND_DISPLAY=wayland-0
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus

waydroid session stop 2&gt;/dev/null
sudo systemctl stop waydroid-container
sleep 2
sudo pkill -f /usr/bin/waydroid
for m in $(mount | grep waydroid | awk '{print $3}' | sort -r); do sudo umount -l "$m"; done
sudo rm -rf /run/waydroid-lxc /var/lib/waydroid/lxc

sudo waydroid init
sudo systemctl start waydroid-container
sleep 6
waydroid session start &amp;
sleep 40
waydroid status</code></pre>
<hr />
<h3>
	3.11 游戏闪退排查：碧蓝航线（2026-11-03，进行中）
</h3>
<h4>
	现象
</h4>
<p>
	点「碧蓝航线」图标 → 图标闪一下 → 黑屏 → 回桌面。启动后 1~5 秒必退，
Unity 会话计数一度累积到 11 次，全部失败。
</p>
<h4>
	结论（一句话）
</h4>
<p>
	<strong>不缺资源、不缺内存、不是网络、也不是安装包损坏。</strong> 是 <strong>app 自己的加密脚本包没被解密/加载出来</strong>，Unity 只能拿 APK 里那套"壳"元数据跑空场景，
脚本全部解析不到，于是进程主动 <code>exit(1)</code> 自杀。
</p>
<table>
	<thead>
		<tr>
			<th>
				环节
			</th>
			<th>
				证据
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				系统侧判定
			</td>
			<td>
				<code>dumpsys activity exit-info</code> 全部 <code>reason=1 (EXIT_SELF) status=1</code>，无 ANR、无 LMK
			</td>
		</tr>
		<tr>
			<td>
				崩点
			</td>
			<td>
				Unity 载 <code>level0</code> 时 <code>referenced script (...BLHX.Rendering...) is missing!</code> + <code>different serialization layout (Read 32 bytes but expected 56)</code>，随后 <code>exited cleanly (1)</code>
			</td>
		</tr>
		<tr>
			<td>
				代码不在 il2cpp
			</td>
			<td>
				<code>global-metadata.dat</code> 里 <code>BLHX</code> 只有 266 处，且都是 <code>BLHX.HotUpdate</code> / <code>BLHX.Rendering.HotUpdate</code> 这类基础设施；<code>ScriptingAssemblies.json</code> 里挂着 <code>Yongshi.BLHotUpdate.Runtime.dll</code>
			</td>
		</tr>
		<tr>
			<td>
				代码在加密包里
			</td>
			<td>
				<code>assets/AssetBundles/scripts64.ys</code>（38 MB）头部是 <code>52 AA 2A A5</code>，<strong>不是</strong>标准 <code>UnityFS</code>；同目录 <code>hotupdate.ys</code> / <code>custom_builtin.ys</code> 才是正常 <code>UnityFS</code>
			</td>
		</tr>
		<tr>
			<td>
				内容不缺
			</td>
			<td>
				<code>assets/AssetBundles/</code> 共 37536 个文件、未压缩 <strong>1644 MB</strong>，全在 APK 里；<code>Download/</code> 为空是"没走到下载"，不是"缺下载"
			</td>
		</tr>
		<tr>
			<td>
				APK 完整
			</td>
			<td>
				<code>libil2cpp.so</code> / <code>libcrypto.1.1.so</code> / <code>global-metadata.dat</code> 的 CRC 与 APK 内原件逐个比对<strong>完全一致</strong>；<code>splits=[base]</code>
			</td>
		</tr>
		<tr>
			<td>
				网络
			</td>
			<td>
				抓了启动全过程容器对外连接：只有 adb，没有任何 CDN 请求——它压根没尝试下载
			</td>
		</tr>
		<tr>
			<td>
				内存
			</td>
			<td>
				11.6 GB 总量 / 6.3 GB 可用
			</td>
		</tr>
	</tbody>
</table>
<h4>
	⚠️ 一个必须记住的坑：<code>ro.build.type</code> 别改 <code>user</code>
</h4>
<p>
	属性伪装第一版顺手把 <code>ro.build.type</code> 也改成 <code>user</code>，重启后开机弹
「<strong>Android 系统：您的设备内部出现了问题。请与您的设备制造商了解详情</strong>」：
</p>
<pre class="prettyprint linenums"><code>E/Build: Vendor interface is incompatible, error=1
E/ActivityTaskManager: Build fingerprint is not consistent, warning user</code></pre>
<p>
	原因：AOSP 的 <code>Build.isBuildConsistent()</code> 在 <code>IS_ENG || IS_USERDEBUG</code> 时<strong>直接返回 true（短路）</strong>。
改成 <code>user</code> 后不再短路，而这张镜像的 VINTF HAL 矩阵本来就不匹配， <code>VintfObject.verifyWithoutAvb()</code> 返回 1 → 必然判不一致 → 弹窗。
</p>
<p>
	<strong>结论：<code>ro.build.type</code> 必须保持 <code>userdebug</code>。</strong> 已撤回，弹窗消失。其余伪装可留。
</p>
<h4>
	当前生效的伪装（已验证系统无异常）
</h4>
<table>
	<thead>
		<tr>
			<th>
				文件
			</th>
			<th>
				作用
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>/var/lib/waydroid/waydroid.cfg</code> → <code>[properties]</code>
			</td>
			<td>
				持久化（下次 <code>waydroid init</code> 也会带上）
			</td>
		</tr>
		<tr>
			<td>
				<code>/var/lib/waydroid/waydroid_base.prop</code>
			</td>
			<td>
				立即生效（<code>session start</code> 只读这个，不回读 cfg）
			</td>
		</tr>
	</tbody>
</table>
<p>
	<strong>两边必须同步写</strong>——只改 cfg 不生效；跑 <code>waydroid init</code> 又会重写 LXC 配置（3.4 那条"必挂"路径）。
</p>
<pre class="prettyprint lang-ini linenums"><code>[properties]
ro.build.fingerprint = Xiaomi/nabu/nabu:13/TKQ1.220829.002/V14.0.5.0.TKXCNXM:user/release-keys
ro.build.tags = release-keys
ro.product.waydroid.brand = Xiaomi
ro.product.waydroid.manufacturer = Xiaomi
ro.product.waydroid.device = nabu
ro.product.waydroid.name = nabu
ro.serialno = 8H2X9K3LM</code></pre>
<p>
	实测变化：<code>brand</code> waydroid→<strong>Xiaomi</strong>、<code>manufacturer</code> Waydroid→<strong>Xiaomi</strong>、 <code>device</code>/<code>name</code> →<strong>nabu</strong>、<code>fingerprint</code> 112 字符→<strong>71 字符</strong>、<code>tags</code> test-keys→<strong>release-keys</strong>。
</p>
<blockquote>
	<p>
		顺带修掉一个真问题：原来 <code>ro.build.fingerprint</code> 有 112 字符 &gt; <code>PROP_VALUE_MAX</code>(92)， <strong>native 层 <code>__system_property_get("ro.build.fingerprint")</code> 一律拿到空串</strong> （日志里 <code>E libc: The property ... is too large for __system_property_get()</code>）。
现在是 71 字符，native 也能正常读到。
	</p>
</blockquote>
<p>
	备份：<code>/var/lib/waydroid/waydroid.{cfg,base.prop,prop}.bak-20261103-192221</code>
</p>
<p>
	回滚：
</p>
<pre class="prettyprint lang-bash linenums"><code>sudo cp -a /var/lib/waydroid/waydroid.cfg.bak-20261103-192221 /var/lib/waydroid/waydroid.cfg
sudo cp -a /var/lib/waydroid/waydroid_base.prop.bak-20261103-192221 /var/lib/waydroid/waydroid_base.prop
waydroid session stop; sudo systemctl start waydroid-container; waydroid session start &amp;</code></pre>
<h4>
	改完属性后：症状变了，但没修好
</h4>
<ul>
	<li>
		<code>BgcPluginManager ... onlyDownloadAndInstall()</code> 的 <code>NullPointerException</code> <strong>不再出现</strong>
	</li>
	<li>
		<strong>游戏仍在同一处 <code>exit(1)</code></strong>，<code>Download/</code> 依旧为空
	</li>
	<li>
		对照（19:40 更正，重要）：同机 <code>com.bilibili.priconne</code>（公主连结）<strong>能正常进主界面</strong>（实测 pid 在跑， <code>topResumedActivity=com.bilibili.priconne/.MainActivity</code>），而且它挂的是<strong>同一套反外挂</strong> （<code>libthemis.so</code> / <code>libcustom_rules.so</code> / <code>libbili.so</code> / <code>libmsaoaid*.so</code>），
B站 SDK 采到的设备档案同样是 <code>is_emu=001000</code>、<code>is_debug=1</code>。
它 exit-info 里的 <code>EXIT_SELF</code> 全部来自 <code>:GP7Service</code> / <code>:GP7Worker</code> <strong>子进程且 <code>status=0</code></strong>（正常退出），
和碧蓝航线"<strong>主进程</strong> <code>status=1</code> 自杀"完全不是一回事。
→ <strong>模拟器识别、反外挂拦截、环境本身，都不是本次闪退的原因。</strong>
	</li>
</ul>
<p>
	→ 属性伪装<strong>既不是必要条件也不是充分条件</strong>：公主连结在同样的 <code>is_emu=001000</code> + 同一套反外挂下能正常跑，
说明这些特征本身不致命。改属性没修好游戏，<strong>现在保留它只是为了顺手修掉
"fingerprint 112 字符导致 native 读不到"这个真实缺陷</strong>，随时可按上面的命令回滚。
</p>
<h4>
	要继续往下查可选的路
</h4>
<ol>
	<li>
		换 Android 11（LineageOS 18.1）镜像复测 —— 重装级操作，流程见 3.4 / 3.10；
	</li>
	<li>
		用 debuggable 重打包 APK 附加调试 —— 会毁签名、有封号风险，<strong>不建议</strong>；
	</li>
	<li>
		放弃在 waydroid 里跑这个游戏，用真机或官方 PC 端。
	</li>
</ol>
<h4>
	19:44 两处更正 + 最新进展
</h4>
<p>
	<strong>更正 1：公主连结的下载不在 <code>/sdcard</code>。</strong> 它把资源下到了<strong>内部目录</strong>，共 <strong>11 GB</strong>：
</p>
<pre class="prettyprint linenums"><code>/data/data/com.bilibili.priconne/files/   a/ 3.6G  v/ 2.9G  m/ 1.6G  s/ 1.5G  b/ 1.4G  manifest 55M</code></pre>
<p>
	之前只看 <code>/sdcard/Android/data/.../files/Download</code>（4 KB）就断定"它没下载也能跑"，<strong>结论作废</strong>。
查这类游戏的资源体积要直接 <code>du -sh /data/data/&lt;pkg&gt;</code>，别只看外部目录。
</p>
<p>
	<strong>更正 2：碧蓝航线并不是"点开必闪退"。</strong> 做<strong>完整清理</strong>（<code>pm clear</code> + <code>sudo rm -rf /sdcard/.../Android/data/com.bilibili.azurlane</code>）后，
它能正常弹出<strong>用户协议页</strong>并存活数十秒，<code>pss=315MB / rss=501MB</code>（Unity 确实起来了）：
</p>
<pre class="prettyprint linenums"><code>19:42:42  am_proc_start 29769  (首次启动，弹出协议页)
19:43:22  29769 EXIT_SELF status=1  (未点"同意"，exit)
19:43:30  am_proc_start 30990  (被重新拉起，再次弹协议页)
19:43:34  30990 EXIT_SELF status=1
19:43:37  31315 (仅 XprefProvider content provider)</code></pre>
<p>
	→ 之前"秒退"里有一部分是<strong>外部残留目录</strong>（<code>/sdcard/.../files/il2cpp</code> 还在）让启动器误判状态造成的。
清理干净后<strong>偶尔</strong>能走到协议页，但<strong>不稳定</strong>。
</p>
<p>
	<strong>19:47 实测：协议框抢不到。</strong> 清数据后连续 3 次尝试"启动 + 连点同意"（坐标 1314,990，
从截图量出来的按钮中心），三次都没点中：进程在 4~10 秒内就 <code>exit(1)</code>，截图取回来时已回到桌面。
协议框只在 19:42 那一次实例里稳定出现过（活了约 40 秒），之后的启动因为协议已记录，
直接进 Unity → 脚本缺失 → 4 秒死。
</p>
<p>
	稳定的失败循环（可复现）：
</p>
<pre class="prettyprint linenums"><code>启动 → Unity 载 level0 → BLHX.* 脚本全 missing + 序列化布局不匹配(32/56) → exit(1) → 被再次拉起 → ...</code></pre>
<p>
	<strong>结论：卡点不在"同意"这一步。</strong> 签协议只是把流程往后推了一格，真正的死点在 Unity 载入壳场景，
要过这一关只能让解密脚本包 <code>scripts64.ys</code> 成功加载——目前没有黑盒手段能做到。
</p>
<h4>
	19:52 用宿主 gdb / strace 取证（不用改 APK）
</h4>
<p>
	<strong>关键结论：<code>exit(1)</code> 是 Java 层从网络回调线程里调用的。</strong>
</p>
<p>
	容器里的进程在宿主上可见（<code>ps -eo pid,args | awk '$2=="com.bilibili.azurlane"'</code>），
有 sudo 就能直接 ptrace，<strong>完全不需要 debuggable 重打包</strong>：
</p>
<pre class="prettyprint lang-bash linenums"><code># 1) 找到宿主侧 PID
PID=$(ps -eo pid,args | awk '$2=="com.bilibili.azurlane"{print $1; exit}')

# 2) 在 exit_group 上断点，抓退出时的调用栈
sudo gdb -p $PID -batch -ex "catch syscall exit_group" -ex "continue" -ex "info threads" -ex "bt 25"

# 3) 看它打开了哪些文件（找缺失的资源）
sudo strace -f -p $PID -e trace=openat,openat2 -o /tmp/az.trace</code></pre>
<p>
	抓到的东西：
</p>
<table>
	<thead>
		<tr>
			<th>
				观察
			</th>
			<th>
				说明
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>exit_group</code> 的调用线程是 <strong><code>iligame.com/...</code></strong>，栈在 <code>libart.so</code> 的 <code>nterp_helper</code> 里
			</td>
			<td>
				是 <strong>Java 网络回调</strong>调用的 <code>System.exit(1)</code>，不是本地 native 崩溃
			</td>
		</tr>
		<tr>
			<td>
				<code>openat(".../files/il2cpp/il2cpp.usym") = ENOENT</code>、<code>/system/bin/il2cpp.usym = ENOENT</code>
			</td>
			<td>
				游戏在找 <code>il2cpp.usym</code>，<strong>APK 里没有这个文件</strong>（只有 3 个 <code>libil2cpp.so</code>），只能由热更下发
			</td>
		</tr>
		<tr>
			<td>
				<code>openat(".../files/il2cpp/Metadata/global-metadata.dat")</code> 成功
			</td>
			<td>
				元数据确实是从外部目录加载的
			</td>
		</tr>
		<tr>
			<td>
				<code>newfstatat(".../base.apk/assets/bin/Data/level0") = ENOTDIR</code>
			</td>
			<td>
				Unity 直接按 APK 内路径找壳场景（<code>level0</code> 只有 58 KB）
			</td>
		</tr>
	</tbody>
</table>
<p>
	抓包（<code>tcpdump -i waydroid0</code>）显示启动期间访问的全是 <strong>443/TLS</strong>，
SNI 里有 <code>p.biligame.com</code>（补丁服务器）、<code>api.biligame.net</code>、<code>line1-sdk-app-api.biligame.net</code>、 <code>game.bilibili.com</code>、<code>s1.hdslb.com</code>。<strong>没有任何资源下载流量</strong>，但补丁接口是访问过的。
</p>
<p>
	→ 合并起来看：<strong>大概率是补丁/SDK 接口的某个响应让客户端决定退出</strong>（版本、风控、实名或维护），
而不是本地解密失败。要看到响应内容必须 MITM（装 CA，且很可能撞证书固定）。
</p>
<h4>
	20:10 突破：协议页 WebView 渲染线程崩溃，用命令行开关修好了
</h4>
<p>
	之前"协议框只闪一下就消失"的真相不是 UI 抢不到，是 <strong>WebView 渲染线程把自己崩了</strong>：
</p>
<pre class="prettyprint linenums"><code>pid 2696, tid 2831, name: RenderThread &gt;&gt;&gt; com.bilibili.azurlane
signal 0 (SIGSEGV), code 1 (SEGV_MAPERR), Cause: null pointer dereference
#00 libwebviewchromium.so   (webview.apk!libwebviewchromium.so)</code></pre>
<p>
	对应更早的 <code>E libEGL: eglMakeCurrentImpl error 3008 (EGL_BAD_DISPLAY)</code> 和 <code>E/OpenGLRenderer: Device claims wide gamut support, cannot find matching config</code>。
WebView 在走 GPU 合成时崩在 Mesa/gbm 上。
</p>
<p>
	<strong>修复：给 WebView 一个命令行参数文件（全局生效，所有用到 WebView 的 app 都受益）</strong>
</p>
<pre class="prettyprint lang-bash linenums"><code>adb shell "echo '--disable-gpu-compositing' &gt; /data/local/tmp/webview-command-line"</code></pre>
<ul>
	<li>
		只写 <code>--disable-gpu-compositing</code>：<strong>协议页正常渲染、进程不崩</strong>（实测截图正常）
	</li>
	<li>
		写成 <code>--disable-gpu --disable-gpu-compositing</code>：不崩，但页面渲染成<strong>白块</strong>——不要用这个
	</li>
	<li>
		回滚：<code>adb shell rm /data/local/tmp/webview-command-line</code>
	</li>
</ul>
<p>
	修好之后，协议流程能完整跑通：
</p>
<pre class="prettyprint linenums"><code>agreement.start.api → agreement.page.view → agreement.page_load.api
→ agreement.agree.click → agreement.finish.api
→ patch_req_config → patch_config_net_success → bgc_plugin</code></pre>
<p>
	<strong>但随后仍卡在同一个老毛病</strong>（见下节）： <code>BgcPluginManager.onlyDownloadAndInstall() on a null object reference</code> → <code>exit(1)</code>。
所以 WebView 只是第一道坎，资源准备那道坎还在。
</p>
<h4>
	20:10 期间的两次容器宕机（教训）
</h4>
<p>
	为了装 MITM 证书改了 <code>overlay_rw/system/etc/security/cacerts/</code>，需要重启容器才生效。结果：
</p>
<ul>
	<li>
		<strong>普通 <code>systemctl restart</code> 开始随机失败</strong>，症状是 zygote 崩： <code>JNI FatalError: set_sched_policy(0, -1) failed: Operation not permitted</code> （cgroup 只读，和 3.9 表里那条一致）
	</li>
	<li>
		容器崩过一次之后，<strong>残留挂载会让后续启动全部失败</strong>，必须先清干净： <code>systemctl stop</code> → <code>umount</code> 所有 waydroid 挂载 → <code>rm -rf /run/waydroid-lxc /var/lib/waydroid/lxc</code> → <code>waydroid init</code> → <code>start</code>
	</li>
	<li>
		<strong><code>waydroid init</code> 会 <code>rmtree</code> 掉 <code>overlay_rw</code></strong>（源码 <code>images.py: remove_overlay</code>），
所以放在 overlay 里的证书每次重建都会丢——这就是 MITM 方案在这里走不通的根本原因
	</li>
	<li>
		另外 <code>pkill -f /usr/bin/waydroid</code> 会<strong>匹配到自己的命令行</strong>，把自己杀掉，别这么写
	</li>
</ul>
<p>
	结论：<strong>这个环境不适合做需要重启容器的实验</strong>。做之前先想清楚怎么恢复。
</p>
<h4>
	20:12 试过但无效：把缓存配置里的 bgc_plugin 关掉
</h4>
<p>
	思路：<code>files/cdn_s.0</code> 里 <code>sdk_switch.bgc_plugin = 1</code>，怀疑关掉能绕过那个 null manager。
</p>
<p>
	文件结构（已确认）：<strong>Java 序列化头（27 字节）+ 一段 6753 字节的 JSON 串，无尾部校验和</strong>，
所以可以等长替换 <code>"bgc_plugin": 1</code> → <code>"bgc_plugin": 0</code>，不破坏格式：
</p>
<pre class="prettyprint lang-bash linenums"><code># 备份
sudo cp -a .../files/cdn_s.0 /tmp/
# 等长替换（用 python 校验 JSON 仍可解析、长度不变）
# 再 chattr +i 防止被启动时的刷新覆盖
sudo chattr +i .../files/cdn_s.0</code></pre>
<p>
	结果：<strong>NPE 照样出现</strong>，<code>onlyDownloadAndInstall()</code> 依旧打在 null 上。
→ <code>sdk_switch.bgc_plugin</code> <strong>不是</strong>那个 manager 为 null 的原因。
已回滚（<code>chattr -i</code> + 还原备份，确认 <code>bgc_plugin=1</code>、大小 6780 未变）。
</p>
<h4>
	附：这次用到/可复用的取证命令
</h4>
<pre class="prettyprint lang-bash linenums"><code>adb connect 192.168.240.112:5555
adb logcat -b all -d -v threadtime | grep -E "CRASH|BgcPlugin|Unity|has died"
adb shell dumpsys activity exit-info com.bilibili.azurlane
# adb root 在 waydroid 不可用（会回 “not available in Waydroid”），直接走宿主文件系统
sudo ls ~/.local/share/waydroid/data/data/com.bilibili.azurlane/
sudo ls ~/.local/share/waydroid/data/data/com.bilibili.azurlane/shared_prefs/
sudo cat ~/.local/share/waydroid/data/data/com.bilibili.azurlane/shared_prefs/device_data.xml
# 看 il2cpp 元数据里到底有没有游戏类
sudo strings -a ~/.local/share/waydroid/data/media/0/Android/data/com.bilibili.azurlane/files/il2cpp/Metadata/global-metadata.dat | grep -c BLHX</code></pre>
<p>
	完整时间线与字段级证据另存： <code>~/文档/deepseek-harness/default-workspace/azurlane-crash-分析报告.md</code>
</p>
<hr />
<h3>
	3.12 异环（UE5）黑屏：库初始化阶段就崩了（2026-11-03）
</h3>
<h4>
	现场
</h4>
<ul>
	<li>
		包名 <strong><code>com.hottagames.yh.laohu</code></strong>（注意：<code>com.hypergryph.endfield</code> 是明日方舟终末地，别搞混），版本 1.4.0
	</li>
	<li>
		引擎：<strong>Unreal Engine 5</strong>（<code>com.epicgames.unreal.GameActivity</code>），<code>libUnreal.so</code> 有 <strong>241 MB</strong>
	</li>
	<li>
		症状：点开是「黑屏」——其实是停在 <strong>Android 12 启动闪屏</strong>（黑底 + 图标），
进程 10~20 秒后 SIGSEGV，<code>exit-info</code> 里是一长串 <code>reason=2 (SIGNALED) status=11</code>
	</li>
</ul>
<h4>
	取证（宿主 gdb 抓崩溃现场）
</h4>
<pre class="prettyprint lang-bash linenums"><code>PID=$(ps -eo pid,args | awk '$2=="com.hottagames.yh.laohu"{print $1; exit}')
sudo gdb -p $PID -batch -ex "continue" -ex "bt 18"</code></pre>
<pre class="prettyprint linenums"><code>Thread 1 "agames.yh.laohu" received signal SIGSEGV
#0..#14  libUnreal.so
#15  __dl__ZN6soinfo17call_constructorsEv ()        &lt;- 静态构造函数
#16  __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv ()
#17  __dl___loader_dlopen ()</code></pre>
<p>
	崩的那条指令（<code>pc - 库基址 = 0x289D97C</code>，用 <code>info proc mappings</code> 算的）：
</p>
<pre class="prettyprint linenums"><code>ldr  x10, [x10, #2408]     ; 取全局表
ldr  x0,  [x10]            ; x0 = 0   &lt;- 表项还没建
strb w10, [x9, #1192]      ; 置"已初始化"标志
str  x8,  [x0, #16]        ; 空指针写 -&gt; SIGSEGV</code></pre>
<p>
	<strong>结论：不是"渲染不出画面"，而是 <code>libUnreal.so</code> 被 <code>dlopen</code> 时，
静态构造函数往一个还没初始化的全局注册表里挂对象，直接空指针写死了——引擎 main 根本没进。</strong>
</p>
<h4>
	排除项
</h4>
<table>
	<thead>
		<tr>
			<th>
				怀疑
			</th>
			<th>
				结论
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				安装损坏
			</td>
			<td>
				否：<code>libUnreal.so</code> 与 APK 内原件 CRC 完全一致（251901120 / <code>370f2d23</code>）
			</td>
		</tr>
		<tr>
			<td>
				渲染/图形栈（Mesa、Vulkan）
			</td>
			<td>
				否：崩在 <code>dlopen</code> 构造函数里，与 EGL/Vulkan 无关（Vulkan ICD 也都在：freedreno / lvp / virtio）
			</td>
		</tr>
		<tr>
			<td>
				属性伪装 / WebView 修复的副作用
			</td>
			<td>
				否：这游戏 18:20 装的、19:03 就被清过数据，都早于我们的改动
			</td>
		</tr>
		<tr>
			<td>
				缺资源
			</td>
			<td>
				否（是结果不是原因）：内部数据 96 K、外部 16 K；<code>files/ano_tmp/</code> 里一堆 0 字节 <code>ms_*.dat</code>（腾讯 <strong>ANO</strong> 反外挂的内存扫描残留）
			</td>
		</tr>
	</tbody>
</table>
<h4>
	判断
</h4>
<p>
	<strong>库级初始化失败</strong>，在 waydroid 这种容器里基本无解：要么是 UE5 对这套 Android 13 + Mesa 的兼容问题，
要么是 ANO 反外挂注入让静态初始化炸了。
换 Android 11 镜像<strong>不一定</strong>能好（不是渲染问题，是加载问题），不建议为它重装。
</p>
<p>
	想要跑异环只能真机 / PC 端。
</p>
<h4>
	官方配置要求（2026-04-23 公测，<a href="https://yh.wanmei.com/m/news/gamebroad/20260421/261850.html">官网 FAQ</a>）
</h4>
<table>
	<thead>
		<tr>
			<th>
				项
			</th>
			<th>
				最低
			</th>
			<th>
				推荐
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				Android
			</td>
			<td>
				<strong>10.0+</strong>
			</td>
			<td>
				—
			</td>
		</tr>
		<tr>
			<td>
				SoC
			</td>
			<td>
				骁龙 855 / 天玑 8000 / 麒麟 990
			</td>
			<td>
				骁龙 8 Gen2 / 8 Gen3 / 天玑 9200 / 麒麟 9000S
			</td>
		</tr>
		<tr>
			<td>
				存储
			</td>
			<td>
				<strong>预留 20 GB</strong>
			</td>
			<td>
				—
			</td>
		</tr>
		<tr>
			<td>
				iOS
			</td>
			<td>
				iPhone 12 Pro Max、iOS 15
			</td>
			<td>
				iPhone 14 Pro、iOS 17
			</td>
		</tr>
	</tbody>
</table>
<p>
	我方设备：骁龙 860（≈855+）、Android 13、存储 483 GB → <strong>名义达标，但只到"最低"线</strong>，
即使能启动，UE5 开放世界在这颗 855 级 SoC 上也基本跑不动。
</p>
<p>
	官方对"移动端下载完打开就闪退"的建议是<strong>卸载重装并保持前台完整更新</strong>；
但本例崩在 <code>libUnreal.so</code> 静态构造（见上），APK 完整、无分包（1.4 GB 单包、<code>splits=[base]</code>）、
页大小 4 KB 正常，所以重装大概率无效。
</p>
<p>
	参考：雷电模拟器能跑（<a href="https://help.ldmnq.com/docs/yi-huan-you-hua-she-zhi">官方设置文档</a>，
要求 14.0.5.1+、6 GB 内存 4 核），但那跑的是 <strong>x86 Windows 模拟器</strong>，与 waydroid 的 arm64 路径不同。
</p>
<h4>
	20:27 按官方建议卸载重装 —— 实测无效
</h4>
<pre class="prettyprint lang-bash linenums"><code>sudo cp &lt;app&gt;/base.apk ~/yh_base.apk      # 先备份 1.4 GB 安装包
adb uninstall com.hottagames.yh.laohu
adb install -g -r ~/yh_base.apk           # 17 秒装回
adb shell am start -n com.hottagames.yh.laohu/com.epicgames.unreal.GameActivity</code></pre>
<p>
	结果：<strong>一模一样</strong>——停在 Android 启动闪屏（黑底+图标）→ 十几秒后 <code>reason=2 (SIGNALED) status=11</code>（SIGSEGV）→ 再次拉起 → 再崩。
</p>
<p>
	坐实了：不是安装/资源/配置问题，就是 <code>libUnreal.so</code> 在这套容器里静态初始化失败。 <strong>这条路到此为止。</strong>
</p>
<hr />
<h2>
	第 4 部分 · 宿主音频
</h2>
<h3>
	4.1 系统级音频杂音：卡顿时有电流声（与 Waydroid 无关）
</h3>
<h4>
	现象
</h4>
<ul>
	<li>
		界面卡顿（切大页面、加载重内容）时，声音里出现明显<strong>电流声 / 噼啪声</strong>
	</li>
	<li>
		退出卡顿页面、或暂停后重新播放，往往就恢复
	</li>
	<li>
		跟 Android 无关，宿主播放音乐视频同样会出现
	</li>
</ul>
<h4>
	排查（先排除「四扬声器不同步」）
</h4>
<p>
	Xiaomi Pad 5 的 4 颗 CS35L41 功放由<strong>同一条 TDM 链路、2 声道</strong>驱动：
</p>
<pre class="prettyprint linenums"><code># /usr/share/alsa/ucm2/Xiaomi/nabu/HiFi.conf
#  - Internal Speakers : CS35L41 driver : MultiMedia1 &lt;-&gt; QUAT_TDM_RX_0
SectionDevice."Speaker" {
    PlaybackChannels 2
}</code></pre>
<p>
	用户态只有一条 2 声道流，驱动/硬件层已经同步，<strong>不可能由软件造成四扬声器不同步</strong>。真不同步会表现为声像偏移、低频抵消发闷，而不是随负载出现的爆音。
</p>
<h4>
	真正原因（两层，第二层才是根因）
</h4>
<p>
	<strong>第一层：没有实时优先级</strong>
</p>
<pre class="prettyprint lang-bash linenums"><code>journalctl --user -u pipewire | grep -c 'out of buffers'     # 曾达 164 次
ps -o rtprio,comm -p $(pgrep -x pipewire)                    # RTPRIO  -  ← 没有 RT</code></pre>
<p>
	<strong>第二层（根因）：音频图量子被压到 512</strong>
</p>
<p>
	<code>default.clock.quantum</code> 只是<strong>偏好值</strong>，压不住客户端请求的低延迟。某个客户端（浏览器等）要 512 帧，整张图就掉到 512：
</p>
<pre class="prettyprint linenums"><code>R   48    512  48000  ...  ERR 3225    ← 扬声器 sink，错误持续累积
R  108    720  48000  ...  ERR 163     ← Waydroid 流，周期 720 帧
              ↑ 512 与 720 不成整数比 → 实时重采样 + 高错误率</code></pre>
<p>
	表现就是<strong>声音时好时坏、突然像"炸麦/机器人说话"</strong>，取决于当时连进来的是哪个客户端。这一点比单纯欠载更迷惑人：它不随负载变化，而是随"谁在放声音"变化。
</p>
<h4>
	修复
</h4>
<p>
	<strong>（1）实时优先级</strong>（辅助）—— <code>/etc/security/limits.d/25-pw-rlimits.conf</code> 已给 <code>@pipewire</code> 组 <code>rtprio 95</code>：
</p>
<pre class="prettyprint lang-bash linenums"><code>sudo usermod -aG pipewire neko     # 需要重新登录/重启才生效</code></pre>
<blockquote>
	<p>
		注意：实测这台机器上即使 <code>ulimit -r</code> 是 95，<code>chrt -f 80</code> 仍返回 EPERM，PipeWire 拿不到 SCHED_FIFO。所以不要指望这一项，主战场是下面的量子。
	</p>
</blockquote>
<p>
	<strong>（2）把量子钉死（关键）</strong> —— <code>~/.config/pipewire/pipewire.conf.d/10-stability.conf</code>：
</p>
<pre class="prettyprint linenums"><code>context.properties = {
    default.clock.quantum       = 1024
    default.clock.min-quantum   = 1024    # 关键：设成 512 等于没设
    default.clock.max-quantum   = 4096
    default.clock.force-quantum = 1024    # 关键：强制，忽略客户端请求
}</code></pre>
<p>
	<code>min-quantum</code> 和 <code>force-quantum</code> 两行缺一不可——只改 <code>quantum</code> 就是这次的弯路，客户端照样能把图压到 512。
</p>
<p>
	临时生效（不重启）：
</p>
<pre class="prettyprint lang-bash linenums"><code>pw-metadata -n settings 0 clock.force-quantum 1024</code></pre>
<h4>
	验证
</h4>
<pre class="prettyprint lang-bash linenums"><code># 看量子是否被钉住
pw-metadata -n settings | grep -E 'force-quantum|min-quantum'

# 看错误计数是否停止增长（隔 10 秒读两次对比）
timeout 8 pw-top -b -n 3 | grep -E 'alsa_output|Waydroid'
# ERR 列不再增长 = 稳定</code></pre>
<p>
	实测：修前 sink ERR 一路涨到 3225；固定 1024 后 10 秒、30 秒两次采样均<strong>零增长</strong>。
</p>
<h4>
	注意
</h4>
<ul>
	<li>
		<strong>不要单独重启 PipeWire</strong>：Waydroid 的 pulse socket 是绑定挂载进容器的，PipeWire 一重启容器里那个 socket 就变陈旧的 inode，Android 立刻没声音。要么重启整机，要么重启 PipeWire 后走3.4「完整重建」把容器重拉一遍。
	</li>
	<li>
		quantum 1024 ≈ 21 ms 延迟。看视频/听歌无感；打音游觉得延迟大可以降到 512，但那就回到"容易被客户端拖下去"的老问题。
	</li>
	<li>
		这台机器的延迟预算：<code>PULSE_LATENCY_MSEC 60</code>（Waydroid 侧）+ quantum 1024（系统侧）是两处独立缓冲，调一处不影响另一处。
	</li>
</ul>
<hr />
<h3>
	4.2 外放调音：把 MIUI 的杜比参数搬到 Linux
</h3>
<h4>
	结论
</h4>
<p>
	杜比的<strong>算法</strong>搬不过来（Android AudioEffect + 厂商 DSP 标定，且授权绑定），但<strong>调音参数</strong>是明文：<code>/vendor/etc/dolby/dax-default.xml</code>（DAX3 Android，format 3.x）。单位是 <strong>1/16 dB</strong>，<code>-192</code> 就是 −12 dB。
</p>
<table>
	<thead>
		<tr>
			<th>
				XML 节点
			</th>
			<th>
				含义
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>audio-optimizer-bands</code>
			</td>
			<td>
				逐机喇叭校正，20 段，L/R 独立
			</td>
		</tr>
		<tr>
			<td>
				<code>preset type="ieq"</code> + <code>ieq-amount</code>
			</td>
			<td>
				杜比音色曲线，按百分比混入（实测只有 5–11%）
			</td>
		</tr>
		<tr>
			<td>
				<code>regulator-tuning</code>
			</td>
			<td>
				每段限幅阈值（喇叭保护）
			</td>
		</tr>
		<tr>
			<td>
				<code>volmax-boost</code>
			</td>
			<td>
				静态响度增益
			</td>
		</tr>
		<tr>
			<td>
				<code>volume-leveler</code> / <code>dialog-enhancer</code> / <code>virtualizer</code>
			</td>
			<td>
				动态 / 对白 / 虚拟环绕，只能近似
			</td>
		</tr>
	</tbody>
</table>
<p>
	nabu 的曲线在 469 Hz–2.25 kHz 是一串 −7…−9 dB 的坑，本质是<strong>减法校正</strong>，不是"增强"。另外 <code>ieq_balanced/warm/detailed</code> 三条曲线在 agate/fuxi/marble/zircon 四台机上完全一致，说明它们是杜比 bundle 的通用音色，逐机差异在 <code>audio-optimizer-bands</code>（有的机型这一段全是 0，校准被放进 HAL/DSP 二进制）。
</p>
<h4>
	踩过的坑：滤波器级联 ≠ 曲线
</h4>
<p>
	第一版把 20 个频点直接映射成 20 个 Q=1.2 的 peaking 滤波器。频段只隔 1/3–1/2 倍频程，<strong>每个滤波器同时也在削邻居</strong>，级联后：
</p>
<ul>
	<li>
		曲线上 1 kHz = −7.6 dB
	</li>
	<li>
		实际级联 ≈ −20 dB
	</li>
	<li>
		整条链路平均 <strong>−12 dB</strong>（满幅粉噪逐样本跑 biquad 仿真验证）
	</li>
</ul>
<p>
	为了把响度找回来又加了 +4 dB 补偿增益——<strong>这就是"音量 50% 以上炸麦"的来源</strong>：补偿增益把信号重新推回功放（CS35L41）的保护边缘。
</p>
<blockquote>
	<p>
		教训：点值曲线要么用 FIR，要么拟合成少量宽滤波器再级联；不能把每个点当成独立 biquad 直接串起来。
	</p>
</blockquote>
<h4>
	修法：9 段宽滤波 + 零补偿 + 保险限幅
</h4>
<ul>
	<li>
		最小二乘把曲线拟合成 <strong>9 个 Q=1.0、一倍频程间距</strong>的 peaking（63/125/250/500/1k/2k/4k/8k/16k），拟合误差 ±2 dB
	</li>
	<li>
		<strong>不加补偿增益</strong>；EQ output-gain = −2 dB 留余量
	</li>
	<li>
		限幅器只当安全网：−1 dBFS、5 ms lookahead、20 ms release，正常播放不动作
	</li>
	<li>
		60 Hz 高通，限制小板喇叭的低频行程
	</li>
</ul>
<p>
	仿真（满幅粉噪）：peak −6.0 → <strong>−14.5 dBFS</strong>，rms −18.5 → −25.7 dBFS。100% 音量也不会超过直通路径，从原理上不会再炸。
</p>
<p>
	代价：同音量下比直通轻约 7 dB，靠音量旋钮补。
</p>
<h4>
	现状
</h4>
<table>
	<thead>
		<tr>
			<th>
				组件
			</th>
			<th>
				位置
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				预设
			</td>
			<td>
				<code>~/.local/share/easyeffects/output/MIUI-Dolby-Pad5-v2.json</code>
			</td>
		</tr>
		<tr>
			<td>
				开机自启
			</td>
			<td>
				<code>~/.config/systemd/user/easyeffects-dolby.service</code>（enabled）
			</td>
		</tr>
		<tr>
			<td>
				启动后加载预设 + 切默认 sink
			</td>
			<td>
				<code>~/.local/bin/ee-default-sink.sh</code>
			</td>
		</tr>
		<tr>
			<td>
				自动加载配置
			</td>
			<td>
				<code>~/.local/share/easyeffects/autoload/output/alsa_output.platform-sound.HiFi__Speaker__sink:Speaker playback.json</code>
			</td>
		</tr>
	</tbody>
</table>
<pre class="prettyprint lang-bash linenums"><code>systemctl --user status easyeffects-dolby.service
systemctl --user restart easyeffects-dolby.service
systemctl --user disable --now easyeffects-dolby.service   # 不要了

easyeffects -b 1    # 全局旁路，A/B 对比
easyeffects -b 2    # 恢复
wpctl set-default 48   # 临时把默认输出切回扬声器（绕过 EE）</code></pre>
<h4>
	想更响
</h4>
<p>
	在 EasyEffects 里加 <strong>Limiter → Input gain</strong>，每次 +2 dB，加到开始炸就退一格。不要靠改 EQ 抬增益——那等于把级联过深的问题再引回来。
</p>
<h2>
	第 5 部分 · 亮度与音量同步
</h2>
<h3>
	5.1 waydroid-sync：组件、主控与音量映射
</h3>
<p>
	让 Ubuntu 与 Android 的亮度、音量互相跟随。
</p>
<h4>
	组件
</h4>
<table>
	<thead>
		<tr>
			<th>
				项
			</th>
			<th>
				位置
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				守护进程
			</td>
			<td>
				<code>~/Apps/waydroid-sync.py</code>
			</td>
		</tr>
		<tr>
			<td>
				开机自启
			</td>
			<td>
				<code>~/.config/systemd/user/waydroid-sync.service</code>
			</td>
		</tr>
		<tr>
			<td>
				变更日志
			</td>
			<td>
				<code>~/.local/state/waydroid-sync.log</code>（同时进 journal）
			</td>
		</tr>
	</tbody>
</table>
<pre class="prettyprint lang-bash linenums"><code>systemctl --user status waydroid-sync
systemctl --user restart waydroid-sync
systemctl --user disable --now waydroid-sync     # 不要了就关掉
journalctl --user -u waydroid-sync -f            # 实时看同步事件</code></pre>
<h4>
	音量：不能 1:1 对齐
</h4>
<p>
	两端音量是<strong>相乘</strong>关系：
</p>
<pre class="prettyprint linenums"><code>实际听到 ≈ 宿主音量% × 安卓媒体音量%</code></pre>
<p>
	所以「宿主 50% + 安卓 50%」实际只有 <strong>25%</strong>。1:1 同步会让音量莫名其妙变小。
</p>
<p>
	解法是<strong>把非主控端钉住</strong>，主控端的数值就等于实际音量。
</p>
<p>
	主控端由脚本顶部常量选择：
</p>
<pre class="prettyprint lang-py linenums"><code>MASTER = "auto"      # 默认：谁被拖动谁生效，另一端归满——两边都能正常调
HOST_PIN = 100.0     # 固定主控模式下钉住的一端

# MASTER = "android"  # 安卓固定主控（宿主钉满）
# MASTER = "host"     # 宿主固定主控（安卓钉满）</code></pre>
<p>
	<strong>默认 <code>auto</code></strong>：你动哪边，哪边就保持住；另一边自动归满，保证"两端乘积 = 你这次调的数值"。
</p>
<table>
	<thead>
		<tr>
			<th>
				操作
			</th>
			<th>
				结果
			</th>
			<th>
				实际音量
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				宿主拖到 35%
			</td>
			<td>
				宿主停在 35%，安卓归 100%
			</td>
			<td>
				35%
			</td>
		</tr>
		<tr>
			<td>
				安卓拖到 5/15
			</td>
			<td>
				安卓停在 33%，宿主归 100%
			</td>
			<td>
				33%
			</td>
		</tr>
	</tbody>
</table>
<p>
	代价是<strong>没被调的那一侧会显示 100%</strong>。这在相乘结构下无法避免——两个滑块不可能同时显示真实值，总有一端要让位。
</p>
<p>
	如果希望某一端永远固定（例如只在安卓里调音量），可以切换成 <code>"android"</code> 或 <code>"host"</code>：
</p>
<table>
	<thead>
		<tr>
			<th>
				<br />
			</th>
			<th>
				<code>"auto"</code>（默认）
			</th>
			<th>
				<code>"android"</code>
			</th>
			<th>
				<code>"host"</code>
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				安卓滑块
			</td>
			<td>
				可用
			</td>
			<td>
				<strong>永远满格，动不了</strong>
			</td>
			<td>
				拖动后弹回
			</td>
		</tr>
		<tr>
			<td>
				宿主滑块
			</td>
			<td>
				可用
			</td>
			<td>
				拖动后弹回
			</td>
			<td>
				可用
			</td>
		</tr>
		<tr>
			<td>
				通知/闹钟突发
			</td>
			<td>
				哪端归满就哪端可能响
			</td>
			<td>
				可能很响
			</td>
			<td>
				被宿主压低，最安全
			</td>
		</tr>
	</tbody>
</table>
<blockquote>
	<p>
		实测教训：钉死某一端（不管哪一端）都会让那边的音量条失效。<code>"android"</code> 模式下的原话是"没法把音量调大了，默认就是满的"；<code>"host"</code> 模式下则是"宿主这边的应用都不能调音量了"。所以默认用 <code>auto</code>。
	</p>
</blockquote>
<h4>
	启动归一化
</h4>
<p>
	开机时两端可能都在半路（各 60%，实际 36%）。守护进程启动时把乘积算清并搬到主控端：
</p>
<pre class="prettyprint linenums"><code>[19:50:45] 启动归一化：实际音量 53% → 安卓 53% / 宿主钉 100%</code></pre>
<p>
	<strong>响度不变</strong>，只是把账本统一到主控端。
</p>
<h4>
	一个无法避免的副作用
</h4>
<p>
	在安卓里拖音量条，松手后会弹回满格（因为真正被调的是宿主音量）。这是相乘结构决定的：两个滑块不可能同时显示真实值。
</p>
<h4>
	亮度
</h4>
<table>
	<thead>
		<tr>
			<th>
				层级
			</th>
			<th>
				来源
			</th>
			<th>
				说明
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				1
			</td>
			<td>
				应用内窗口亮度
			</td>
			<td>
				读 SurfaceFlinger 的 <code>displayBrightness</code> 覆盖值，映射到宿主背光
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				Android 全局设置 <code>screen_brightness</code>（0–255）
			</td>
			<td>
				下拉快捷面板、设置里的亮度条
			</td>
		</tr>
		<tr>
			<td>
				3
			</td>
			<td>
				宿主 backlight（<code>ktz8866-backlight</code>，0–2047）
			</td>
			<td>
				GNOME 滑块、键盘快捷键
			</td>
		</tr>
	</tbody>
</table>
<p>
	应用内调亮度时，SurfaceFlinger 会设置覆盖值，守护进程读到后直接写宿主背光；退出应用后覆盖消失，自动回落到全局设置同步（并做基线重对齐，避免跳变）。
</p>
<pre class="prettyprint lang-bash linenums"><code># 查看某个 app 是否设置了窗口亮度
adb shell 'dumpsys SurfaceFlinger | grep -m1 displayBrightness'
#   displayBrightness=nullopt   → 没有 app 在控制
#   displayBrightness=0.35      → 有 app 设了 35%</code></pre>
<blockquote>
	<p>
		注意：不是所有播放器都用窗口亮度。有的（如部分 B 站版本）用半透明黑色遮罩"调暗"，那种在 SurfaceFlinger 里没有任何数值可读，<strong>无法桥接</strong>——因为它只是往画面上盖了一层黑，不改任何亮度参数。
	</p>
</blockquote>
<p>
	另外，Waydroid 没有虚拟背光，<strong>Android 的亮度设置本身不会让画面变暗</strong>；现在它是通过同步去驱动宿主 backlight，才产生实际效果。
</p>
<h4>
	关键实现细节
</h4>
<ul>
	<li>
		<strong>回读后存基线</strong>：Android 的媒体音量上限<strong>不是固定的</strong>——有时是 15，音量保护策略生效时会钳到 10。写完必须回读真实值存起来，否则会出现"设了又改回去"的振荡
	</li>
	<li>
		<strong>上限必须动态解析</strong>：<code>cmd media_session volume --stream 3 --get</code> 返回 <code>volume is N in range [0..MAX]</code>，要按返回的 MAX 换算百分比。写死 10 会把拖到顶算成 150%，宿主被钳到 100% 并莫名跳满（这个坑踩过一次）
	</li>
	<li>
		<strong>忽略"被应用恢复到满格"</strong>：安卓侧被钉在 100%，有些应用/系统会自己把音量恢复满格。这类变化要忽略，否则会被误判成用户调节，导致宿主突然跳到 100%
	</li>
	<li>
		<strong>按变化方向判断</strong>：只有一侧变了、另一侧没变才触发同步，两边都变就都不动
	</li>
	<li>
		<strong>adb 自愈</strong>：容器重启后 adbd 可能没开，守护进程每 30 秒最多尝试一次 <code>sudo waydroid adb</code>
	</li>
	<li>
		<strong>容器没起时不报错</strong>：只记一行日志，等待容器就绪
	</li>
</ul>
<hr />
<h2>
	第 6 部分 · 桌面工具链
</h2>
<h3>
	6.1 看 Markdown：现状与结论
</h3>
<table>
	<thead>
		<tr>
			<th>
				场景
			</th>
			<th>
				现状
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				双击 <code>.md</code>
			</td>
			<td>
				用 <code>org.gnome.TextEditor.desktop</code> 打开，<strong>纯文本</strong>，没有渲染
			</td>
		</tr>
		<tr>
			<td>
				查当前默认程序
			</td>
			<td>
				<code>gio mime text/markdown</code>
			</td>
		</tr>
		<tr>
			<td>
				改默认程序
			</td>
			<td>
				<code>xdg-mime default &lt;desktop-id&gt; text/markdown</code>
			</td>
		</tr>
		<tr>
			<td>
				要渲染着看
			</td>
			<td>
				用 dsh 的文档预览（见 6.2），工作区外的文件见 6.3
			</td>
		</tr>
	</tbody>
</table>
<p>
	<strong>不要装 apostrophe。</strong> apt 里是 3.4（2022 年的代码），在 Ubuntu 26.04 上直接坏：<code>ApostropheTextView</code> 的 <code>markup</code> 控制器没初始化成功，每次布局回调都抛异常，窗口永久「未响应」，正文永远不渲染，连 WebKit 子进程都没起来。日志特征：
</p>
<pre class="prettyprint lang-text linenums"><code>apostrophe/text_view.py:327 in _on_size_allocate
    self.markup.update_margins_indents()
AttributeError: 'ApostropheTextView' object has no attribute 'markup'</code></pre>
<p>
	2026-11-03 装过一次确认无解，已 <code>apt remove --purge</code> 卸载并把 mime 默认还原。真要个桌面编辑器再另找（Qt 系的 <code>ghostwriter</code> / <code>retext</code> 没这个问题），但日常看文档用 dsh 就够。
</p>
<h3>
	6.2 dsh Web GUI：文件树与文档预览
</h3>
<p>
	启动 <code>dsh web</code>，浏览器打开它打印的地址（本机是 <code>http://127.0.0.1:3080</code>）。
</p>
<ul>
	<li>
		右侧栏 →「文件」面板：树根是<strong>当前会话的工作目录</strong>（默认 <code>~/文档/deepseek-harness/default-workspace</code>），只能往下，不能向上浏览。这是设计限制：Host 的目录列举按会话根做包含检查，不是配置项。
	</li>
	<li>
		点任意文件 → 开一个「文档预览」标签，内容由浏览器渲染，不依赖本地编辑器。支持 markdown（表格、代码块、相对路径图片、YAML frontmatter）、代码、图片、PDF、docx/pptx（本地转 PDF）、xlsx/csv、HTML。
	</li>
	<li>
		预览按页懒加载、文件改动自动刷新，所以改文档时不用手动重载。
	</li>
	<li>
		注意：<strong>读取</strong>接口本身不限制工作区，限制的只有「列目录」。所以既有文件能被预览，只是文件树里走不到。
	</li>
</ul>
<h3>
	6.3 自写插件：<code>@local/dsh-open-file</code>
</h3>
<p>
	在「文件」面板标题栏加一个按钮（箭头进文件夹的图标），弹出系统文件选择框、默认定位 <code>~/桌面</code>，选中的文件直接用内置预览打开，<strong>不受工作区限制</strong>。
</p>
<table>
	<thead>
		<tr>
			<th>
				项
			</th>
			<th>
				值
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				源码
			</td>
			<td>
				<code>~/文档/deepseek-harness/default-workspace/plugins/dsh-open-file/</code>
			</td>
		</tr>
		<tr>
			<td>
				包名
			</td>
			<td>
				<code>@local/dsh-open-file</code>
			</td>
		</tr>
		<tr>
			<td>
				Host 半边
			</td>
			<td>
				<code>index.js</code>：注册 <code>GET /dsh-open-file/pick</code>，spawn <code>zenity --file-selection</code>
			</td>
		</tr>
		<tr>
			<td>
				Client 半边
			</td>
			<td>
				<code>client.js</code>：往 <code>sidebar.right.tab.files.actions</code> 注册按钮，拿到路径后 <code>openResource</code>
			</td>
		</tr>
		<tr>
			<td>
				起始目录
			</td>
			<td>
				读 <code>XDG_DESKTOP_DIR</code>（= <code>~/桌面</code>）；可用环境变量 <code>DSH_OPEN_FILE_START</code> 覆盖
			</td>
		</tr>
		<tr>
			<td>
				安装
			</td>
			<td>
				<code>plugin_manager</code> 的 <code>install_bundle</code>，target 传上面那个绝对路径，装完刷新页面
			</td>
		</tr>
		<tr>
			<td>
				卸载
			</td>
			<td>
				<code>plugin_manager</code> 的 <code>remove_bundle</code>，target 传 <code>@local/dsh-open-file</code>
			</td>
		</tr>
	</tbody>
</table>
<p>
	工作方式：Host 只负责「弹框、把你亲手选的那个绝对路径返回」，Client 把路径包成 <code>dsh-resource://file/session/&lt;会话id&gt;//home/neko/桌面/xxx.md</code> 再交给内置预览（首部 <code>//</code> 表示绝对路径）。安全边界：
</p>
<ul>
	<li>
		路由只接受 <code>GET</code>，只接受 <code>Sec-Fetch-Site: same-origin</code>（跨站 403），同一时刻只开一个对话框（409）。
	</li>
	<li>
		不接受任何路径参数，返回值只能来自对话框，伪造地址伪造不出文件。
	</li>
</ul>
<p>
	装进的是 profile <code>web</code>（<code>~/.dsh/profiles/web</code>），<code>pnpm add &lt;目录&gt;</code> 建的是软链接，所以改源码目录里的文件就是改生效文件；改完刷新页面即可，模块版本没换就重启 <code>dsh web</code>。
</p>
<h3>
	6.4 装 dsh 插件的前置：pnpm
</h3>
<p>
	<code>install_bundle</code> 内部跑 <code>pnpm add</code>，本机最初没有 pnpm，会直接失败：
</p>
<pre class="prettyprint lang-text linenums"><code>Command failed with ENOENT: pnpm add '...'
spawn pnpm ENOENT</code></pre>
<pre class="prettyprint lang-bash linenums"><code>sudo npm i -g pnpm@10   # 装到 /usr/local/bin/pnpm
pnpm --version          # 10.34.9</code></pre>
<p>
	另外两条和这次相关的清理经验：
</p>
<ul>
	<li>
		<strong>不要用 <code>apt autoremove</code> 清空间。</strong> 本机有一批被标成「自动安装」但你在用的包（deepin-wine、exagear、caribou 等），autoremove 会一起带走。卸载单个包用 <code>apt remove --purge &lt;pkg&gt;</code> 就够。
	</li>
	<li>
		卸 apostrophe 时留下了 pandoc 等 4 个包共约 210 MB（当时作为依赖被拉进来）。pandoc 本身是通用文档转换工具，留着有用；确实不要就 <code>apt remove pandoc pandoc-data python3-pypandoc python3-regex</code>。
	</li>
</ul>
<hr />
<h2>
	附录
</h2>
<h3>
	A. 事故时间线（2026-11-02）
</h3>
<table>
	<thead>
		<tr>
			<th>
				时间
			</th>
			<th>
				事件
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				19:54:30
			</td>
			<td>
				上一段 6h 会话干净关机
			</td>
		</tr>
		<tr>
			<td>
				19:54:31
			</td>
			<td>
				插电自动开机（<code>off-mode-charge=0</code>）
			</td>
		</tr>
		<tr>
			<td>
				19:55:03
			</td>
			<td>
				logind 收到电源键事件 → 干净关机
			</td>
		</tr>
		<tr>
			<td>
				19:55:47
			</td>
			<td>
				关机完成，1 秒后又自动上电
			</td>
		</tr>
		<tr>
			<td>
				19:56:19
			</td>
			<td>
				再次 <code>poweroff.target</code> 排队 → 19:56:57 关机
			</td>
		</tr>
		<tr>
			<td>
				19:56:57
			</td>
			<td>
				第三次启动时 <code>slot-unbootable:a=yes</code>、<code>retry=0</code> → 停 fastboot
			</td>
		</tr>
		<tr>
			<td>
				19:56:59
			</td>
			<td>
				<code>set_active a</code> 后恢复启动
			</td>
		</tr>
		<tr>
			<td>
				20:49:05
			</td>
			<td>
				33W 头下 <code>v_offset</code> 被写坏，充电进入 fault 循环
			</td>
		</tr>
		<tr>
			<td>
				21:11–21:13
			</td>
			<td>
				尝试 rebind ln8000 → 内核 oops（21:13:36）→ 死机 → 重启
			</td>
		</tr>
		<tr>
			<td>
				21:55
			</td>
			<td>
				fastboot 测试：确认计数 5→4、<code>set_active</code> 回到 7
			</td>
		</tr>
		<tr>
			<td>
				11/03 22:38
			</td>
			<td>
				6~8% 低电量下再次进入坏状态（<code>tcpm</code> 0V、不充电），重插后恢复 −3.6A
			</td>
		</tr>
		<tr>
			<td>
				11/04 00:33
			</td>
			<td>
				99% 时拔线，<code>v_offset</code> 瞬时溢出（属正常瞬变），此后待机
			</td>
		</tr>
	</tbody>
</table>
<h3>
	B. 不要做的事
</h3>
<table>
	<thead>
		<tr>
			<th>
				操作
			</th>
			<th>
				后果
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>fastboot set_active b</code>
			</td>
			<td>
				b 从未成功启动过，切过去大概率直接 fastboot
			</td>
		</tr>
		<tr>
			<td>
				<code>fastboot flash boot_a ...</code>
			</td>
			<td>
				覆盖 dualboot bootmanager，丢 Linux 引导
			</td>
		</tr>
		<tr>
			<td>
				<code>fastboot erase misc</code>
			</td>
			<td>
				已验证无效（槽信息不在这里）
			</td>
		</tr>
		<tr>
			<td>
				<code>fastboot oem lock</code> / <code>unlock</code>
			</td>
			<td>
				可能清数据
			</td>
		</tr>
		<tr>
			<td>
				unbind/rebind ln8000
			</td>
			<td>
				<strong>内核 oops 死机</strong>
			</td>
		</tr>
		<tr>
			<td>
				刷比当前旧的固件
			</td>
			<td>
				<code>anti:1</code>，可能硬砖
			</td>
		</tr>
		<tr>
			<td>
				<code>chown -R</code> 整个 Waydroid <code>/data</code>
			</td>
			<td>
				keystore(1017)/media(1023) 属主被改坏 → 无限重启（见 3.5）
			</td>
		</tr>
		<tr>
			<td>
				<code>sudo waydroid session start</code>
			</td>
			<td>
				DBus NoReply，管理器无响应（见 3.5）
			</td>
		</tr>
		<tr>
			<td>
				改 <code>init.waydroid.rc</code> 加 setenv
			</td>
			<td>
				容器起不来，必须改 <code>init.environ.rc</code>（见 3.5）
			</td>
		</tr>
		<tr>
			<td>
				单独重启 PipeWire
			</td>
			<td>
				容器里的 pulse socket 变陈旧 inode，Android 立刻没声音（见 4.1）
			</td>
		</tr>
		<tr>
			<td>
				<code>apt install apostrophe</code>
			</td>
			<td>
				3.4 在 26.04 上崩溃（<code>markup</code> AttributeError），窗口永久无响应（见 6.1）
			</td>
		</tr>
		<tr>
			<td>
				<code>apt autoremove</code>
			</td>
			<td>
				会带走 deepin-wine / exagear 等仍在使用的包（见 6.4）
			</td>
		</tr>
	</tbody>
</table>
<h3>
	C. 命令备忘
</h3>
<pre class="prettyprint lang-bash linenums"><code># ---- 启动 / 槽位 ----
fastboot set_active a &amp;&amp; fastboot reboot
cat ~/.local/share/nabu/boot-count
nabu-boot-count reset

# ---- 充电 ----
cat /sys/class/power_supply/qcom-battery/status
cat /sys/class/power_supply/qcom-battery/current_now   # 6.14: 负=充电  6.17: 正=充电
cat /sys/class/power_supply/qcom-battery/capacity
journalctl -k -n 20 | grep -o 'v_offset=[0-9]*' | tail -1
timeout 20 udevadm monitor --subsystem-match=typec 2&gt;/dev/null | grep -c port0-partner

# ---- Waydroid ----
waydroid session stop
sudo systemctl start waydroid-container
waydroid session start &amp;
waydroid show-full-ui
waydroid status

# ---- 音频 ----
pw-metadata -n settings | grep -E 'force-quantum|min-quantum'
timeout 8 pw-top -b -n 3 | grep -E 'alsa_output|Waydroid'
wpctl status | sed -n '/Streams:/,/Video/p'

# ---- 文档 / dsh ----
gio mime text/markdown                      # 当前用什么打开 .md
sudo npm i -g pnpm@10                       # dsh 装插件的前置（只需一次）
dsh web                                     # 启 Web GUI，浏览器访问它打印的地址</code></pre>
<h3>
	D. 参考
</h3>
<ul>
	<li>
		Waydroid 音频 HAL 无 PULSE_RUNTIME_PATH：<a href="https://github.com/waydroid/waydroid/issues/2333">https://github.com/waydroid/waydroid/issues/2333</a>
	</li>
	<li>
		Waydroid 官方文档：<a href="https://docs.waydro.id/">https://docs.waydro.id/</a>
	</li>
	<li>
		Waydroid 安装源：<code>https://repo.waydro.id</code>（已确认支持 Ubuntu 26.04 <code>resolute</code>）
	</li>
</ul>
<hr />
<h3>
	E. 局域网访问 DSH（黑屏救援用）
</h3>
<pre class="prettyprint lang-bash linenums"><code>systemctl --user status dsh-lan        # 绑定 192.168.31.41:3081 的实例
cat ~/桌面/dsh-局域网地址.txt          # 带 token 的最新 URL</code></pre>
<ul>
	<li>
		<strong>token 每次重启都会变</strong>，URL 以桌面那个 txt（或 <code>~/.local/share/nabu/dsh-lan-url.txt</code>）为准
	</li>
	<li>
		所有设备必须连<strong>同一个实例</strong>：DSH 的会话同一时间只能被一个进程驱动，
连到别的实例会报"当前会话已被占用"
	</li>
	<li>
		只监听局域网 IP，别在路由器上映射到公网（URL 等价于以 neko 身份执行命令）
	</li>
</ul>
<h3>
	F. 电源键与休眠显示
</h3>
<p>
	<strong>行为</strong>（2026-11-04 实测定稿）：
</p>
<table>
	<thead>
		<tr>
			<th>
				操作
			</th>
			<th>
				结果
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				亮屏时按电源键
			</td>
			<td>
				挂起（<code>systemctl suspend</code>）
			</td>
		</tr>
		<tr>
			<td>
				熄屏时按电源键
			</td>
			<td>
				点亮（<code>PowerSaveMode=0</code> + 解除背光 powerdown）
			</td>
		</tr>
		<tr>
			<td>
				从挂起恢复
			</td>
			<td>
				2 秒后强制点亮一次（兜底）
			</td>
		</tr>
	</tbody>
</table>
<p>
	<strong>为什么这么绕</strong>：这块屏有两个坑，单独用系统默认逻辑都会踩到——
</p>
<ol>
	<li>
		GNOME 的 <code>power-button-action</code> 默认是 <code>suspend</code>，按电源键唤醒时<strong>同一个按键事件又触发一次挂起</strong>，表现为"按了没反应、要多按几次"；
	</li>
	<li>
		<code>/sys/class/drm/card0-DSI-1/dpms</code> 是 <strong>只读</strong> 的（<code>-r--r--r--</code>），root 也写不进去；真正控制显示开关的是 <strong>mutter 的 <code>PowerSaveMode</code></strong>（0=亮，1=暗，3=关）。
	</li>
	<li>
		偶尔从挂起恢复后，mutter 认为屏亮着、但面板没重新初始化，屏幕实际是黑的。
	</li>
</ol>
<p>
	<strong>当前机制</strong>：
</p>
<table>
	<thead>
		<tr>
			<th>
				组件
			</th>
			<th>
				作用
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>gnome power-button-action = 'nothing'</code>
			</td>
			<td>
				让 GNOME 不抢电源键
			</td>
		</tr>
		<tr>
			<td>
				<code>nabu-wake-display</code>（system 服务）
			</td>
			<td>
				监听电源键 → 按 <code>dpms</code> 状态决定挂起还是点亮
			</td>
		</tr>
		<tr>
			<td>
				<code>nabu-display-on-resume</code>（system 服务）
			</td>
			<td>
				每次 resume 后 2 秒强制点亮
			</td>
		</tr>
		<tr>
			<td>
				<code>nabu-resume-probe</code>（system 服务）
			</td>
			<td>
				记录每次唤醒的 dpms/背光状态到 <code>/var/log/nabu-resume.log</code>
			</td>
		</tr>
	</tbody>
</table>
<p>
	<strong>手动操作</strong>：
</p>
<pre class="prettyprint lang-bash linenums"><code># 强制点亮（黑屏时最有用）
sudo /usr/local/bin/nabu-wake-display --resume

# 看电源键/点亮动作的日志
journalctl -t nabu-wake-display -n 20

# 看每次唤醒的显示状态
sudo tail -20 /var/log/nabu-resume.log</code></pre>
<p>
	<strong>撤销回系统默认</strong>：
</p>
<pre class="prettyprint lang-bash linenums"><code>sudo systemctl disable --now nabu-wake-display nabu-display-on-resume
gsettings set org.gnome.settings-daemon.plugins.power power-button-action 'suspend'</code></pre>
<blockquote>
	<p>
		注意：撤销后就会回到"按电源键可能唤不醒、要多按几次"的老状态。
	</p>
</blockquote>
<h3>
	G. 待分析：合上盖子偶尔不触发息屏（2026-11-04 记录）
</h3>
<p>
	<strong>现象</strong>：合上平板套时，有时正常挂起息屏，有时<strong>合好几次都没反应</strong>（用户原话："不太灵敏"）；打开盖子时唤醒倒是正常。
</p>
<p>
	<strong>已确认的事实</strong>：
</p>
<ul>
	<li>
		霍尔传感器工作正常，内核会报 <code>EV_SW / SW_LID</code>：
<pre class="prettyprint linenums"><code>14:04:04  gpio-keys type=5 code=0 value=1   ← 合上
14:04:10  gpio-keys type=5 code=0 value=0   ← 打开</code></pre>
	</li>
	<li>
		logind 也正常处理：
<pre class="prettyprint linenums"><code>14:04:04  systemd-logind: Lid closed.
14:04:04  systemd-logind: Suspending...
14:04:05  PM: suspend entry (s2idle)</code></pre>
	</li>
	<li>
		<strong>失败的几次完全没有 SW_LID 事件</strong>（60 秒抓取 0 条），说明不是 logind/GNOME 的问题，
而是磁铁→霍尔这一层偶尔没被触发。
	</li>
	<li>
		<code>/etc/systemd/logind.conf</code> 里 <code>HandleLidSwitch</code> 是注释状态（默认 <code>suspend</code>），未被改动。
	</li>
</ul>
<p>
	<strong>可能原因（待验证）</strong>：
</p>
<ol>
	<li>
		套子里的磁铁位置/强度不够，合上时霍尔电压没跨过阈值（换了保护套可能就正常）
	</li>
	<li>
		<code>gpio-keys</code> 的 debounce 或阈值配置
	</li>
	<li>
		合上速度太快/角度不对，磁铁没到位
	</li>
</ol>
<p>
	<strong>下次排查用的命令</strong>：
</p>
<pre class="prettyprint lang-bash linenums"><code># 实时看霍尔事件（合上/打开盖子）
sudo timeout 60 python3 ~/.local/share/nabu/lidcap.py

# 看 logind 有没有收到
journalctl -f | grep -iE 'Lid|suspend entry|suspend exit'

# 历史记录
journalctl | grep -c 'Lid closed'</code></pre>
<p>
	<strong>临时对策</strong>：合上后如果没息屏，按一次电源键手动挂起；或者把自动挂起时间调短。
</p>
<p>
	<strong>已保存的抓取数据</strong>：<code>~/.local/share/nabu/lid-capture-20261104.log</code>
</p>]]></description>
	<pubDate>Mon, 02 Nov 2026 04:25:55 +0000</pubDate>
	<author>赵苦瓜</author>
	<guid>https://blog.zhaokugua.com/?post=121</guid>

</item>
<item>
	<title>【域名更新】jixiaob.cn已全面更换为zhaokugua.com</title>
	<link>https://blog.zhaokugua.com/?post=120</link>
	<description><![CDATA[<p>
	<span style="font-size:16px;">原域名jixiaob.cn因恶意伪造证据举报导致被网信办封禁无法申诉</span> 
</p>
<p>
	<span style="font-size:16px;">现已全面更换为zhaokugua.com</span> 
</p>
<p>
	<span style="font-size:16px;"><br />
谨此通知</span> 
</p>
<p>
	<span style="font-size:16px;"><br />
</span>
</p>
<p>
	<span style="font-size:16px;"><a target="_blank" href="https://blog.zhaokugua.com/content/uploadfile/202601/c26c1768885364.gif" id="ematt:685"><img src="https://blog.zhaokugua.com/content/uploadfile/202601/c26c1768885364.gif" title="点击查看原图" alt="42f49777e88898f30f97c65b05e084d93493094766938960.gif" border="0" width="500" height="500" /></a><br />
</span>
</p>]]></description>
	<pubDate>Tue, 20 Jan 2026 04:55:36 +0000</pubDate>
	<author>赵苦瓜</author>
	<guid>https://blog.zhaokugua.com/?post=120</guid>

</item>
<item>
	<title>微信小程序获取其他小程序appid以及path以实现精准跳转功能</title>
	<link>https://blog.zhaokugua.com/?post=119</link>
	<description><![CDATA[<p>
	<span style="font-size:16px;">微信小程序官方文档里有写</span><span style="font-size:16px;">wx.navigateToMiniProgram的API，使用这个API可以精确跳转到另一个小程序的页面，甚至还可以携带参数，功能十分强大。</span> 
</p>
<p>
	<span style="font-size:16px;">一般只跳转单一固定页面的话，只需要传入</span><span style="white-space-collapse:collapse;font-size:16px;">shortLink参数即可精准跳转到某一个页面，但是这种shortlink是编码后的链接，看不出来参数等信息，还有很多是需要加参数才能精准跳转的页面用这种方式就做不到。因此还有使用<span style="font-size:16px;text-wrap-mode:wrap;">AppID</span>和path跳转的方式。</span> 
</p>
<p>
	<span style="white-space-collapse:collapse;font-size:16px;"><br />
</span> 
</p>
<p>
	<span style="white-space-collapse:collapse;font-size:16px;"> </span> 
</p>
<pre class="prettyprint lang-js linenums">    joinChannel(event) {
      wx.navigateToMiniProgram({
        "shortLink": "#小程序://卡拉彼丘手游/LvdUNdJ7mb8zayG"
      })
    },</pre>
<br />
<p>
	<br />
</p>
<p>
	<span style="white-space-collapse:collapse;font-size:16px;"><br />
</span> 
</p>
<p>
	<span style="white-space-collapse:collapse;font-size:16px;"><span style="font-size:16px;text-wrap-mode:wrap;">AppID</span>在小程序右上角三个点里查看详情就能看到，但是path去哪里看呢？</span> 
</p>
<p>
	<span style="white-space-collapse:collapse;font-size:16px;"><br />
</span> 
</p>
<p>
	<span style="white-space-collapse:collapse;"><span style="font-size:16px;">这有几个前提条件：</span><br />
</span> 
</p>
<p>
	<span style="white-space-collapse:collapse;"><span style="font-size:16px;">首先保证小程序可以被正常搜索到，未开放搜索能力或者搜索功能被封禁的小程序是看不到的。</span></span> 
</p>
<p>
	<span style="white-space-collapse:collapse;"><span style="font-size:16px;">准备好一个可以正常进入编辑公众号文章的公众号账号。</span></span> 
</p>
<p>
	<span style="white-space-collapse:collapse;"><span style="font-size:16px;"><br />
</span></span> 
</p>
<p>
	<span style="white-space-collapse:collapse;font-size:18px;"><span style="font-size:18px;"><strong>如何查找小程序的AppID</strong></span></span> 
</p>
<p>
	<span style="white-space-collapse:collapse;"><span style="font-size:16px;">1. 找到你要找path的小程序，点击右上角三个点，点击小程序名称。</span></span> 
</p>
<p>
	<span style="white-space-collapse:collapse;"><span style="font-size:16px;">2. 点击小程序的简介进入详细信息页，这里面就有小程序的AppID信息。</span></span> 
</p>
<p>
	<span style="white-space-collapse:collapse;"><span style="font-size:16px;"><a target="_blank" href="https://blog.zhaokugua.com/content/uploadfile/202511/12fb1763308490.png" id="ematt:675"><img src="https://blog.zhaokugua.com/content/uploadfile/202511/12fb1763308490.png" title="点击查看原图" alt="点击查看原图" border="0" width="1065" height="600" align="" /></a><br />
</span></span> 
</p>
<p>
	<span style="white-space-collapse:collapse;"><span style="font-size:16px;"><br />
</span></span> 
</p>
<p>
	<span style="font-size:18px;"><strong>如何查找小程序某个页面对应的Path</strong></span> 
</p>
<p>
	<span style="font-size:16px;">1. 打开微信公众平台（mp.weixin.qq.com），登录公众号的账号。</span> 
</p>
<p>
	<span style="font-size:16px;">2. 点击新的创作里的“文章”，进入文章创作页面。</span> 
</p>
<p>
	<span style="font-size:16px;">3. 点击上方的小程序按钮，点击“去搜索”，输入小程序名称或者appid进行搜索，搜到了点下一步。</span> 
</p>
<p>
	<span style="font-size:16px;"><a target="_blank" href="https://blog.zhaokugua.com/content/uploadfile/202511/1f051763308497.png" id="ematt:677"><img src="https://blog.zhaokugua.com/content/uploadfile/202511/1f051763308497.png" title="点击查看原图" alt="点击查看原图" border="0" width="853" height="600" align="" /></a><br />
</span> 
</p>
<p>
	<span style="font-size:16px;"><a target="_blank" href="https://blog.zhaokugua.com/content/uploadfile/202511/ee481763308502.png" id="ematt:679"><img src="https://blog.zhaokugua.com/content/uploadfile/202511/ee481763308502.png" title="点击查看原图" alt="点击查看原图" border="0" width="881" height="600" align="" /></a><br />
</span> 
</p>
<p>
	<span style="font-size:16px;">4. 点击“获取更多路径”，在输入框中输入你的微信账号，即可开启十分钟的可以复制小程序任意路径的功能。</span> 
</p>
<p>
	<span style="font-size:16px;"><a target="_blank" href="https://blog.zhaokugua.com/content/uploadfile/202511/05671763308506.png" id="ematt:681"><img src="https://blog.zhaokugua.com/content/uploadfile/202511/05671763308506.png" title="点击查看原图" alt="点击查看原图" border="0" width="1069" height="600" align="" /></a><br />
</span> 
</p>
<p>
	<span style="font-size:16px;">5. 进入小程序对应页面，点击右上角三个点就能看到复制页面路径的按钮，点击就可以复制路径了。如果有参数的话这里的参数也会一同复制到粘贴板，比如卡拉彼丘手游的扭蛋集卡页面就是pages/home/home<strong><span style="color:#E53333;">.html</span></strong>?tab=lottery</span> 
</p>
<p>
	<span style="font-size:16px;"><strong style="color:#E53333;font-size:16px;text-wrap-mode:wrap;">注意：直接复制的path是有.html的，要把这个去掉才能正常跳转！</strong><br />
</span> 
</p>
<p style="text-align:center;">
	<span style="font-size:16px;"><a target="_blank" href="https://blog.zhaokugua.com/content/uploadfile/202511/125c1763308510.jpg" id="ematt:683"><img src="https://blog.zhaokugua.com/content/uploadfile/202511/125c1763308510.jpg" title="点击查看原图" alt="点击查看原图" border="0" width="300" height="667" align="" /></a><br />
</span> 
</p>
<p>
	<span style="font-size:16px;"><br />
</span> 
</p>
<p>
	<span style="font-size:16px;">然后在自己的微信小程序里即可使用<span style="font-size:16px;text-wrap-mode:wrap;">wx.navigateToMiniProgram的API里填上对应的AppID以及path参数即可跳转到对应的页面。</span></span> 
</p>
<p>
	<span style="font-size:16px;"><span style="font-size:16px;text-wrap-mode:wrap;"><br />
</span></span> 
</p>
<p>
	<span style="font-size:16px;"><span style="font-size:16px;text-wrap-mode:wrap;"> </span></span> 
</p>
<pre class="prettyprint lang-js linenums">      // 需要变化参数跳转不同页面的需要AppID和path
      wx.navigateToMiniProgram({
        "appId": "wx8bdd206c6778298d",
        "path": "pages/home/home?tab=lottery"  // path里可以传参，比如此处的tab=lottery
      })</pre>
<br />
<p>
	<br />
</p>]]></description>
	<pubDate>Sun, 16 Nov 2025 14:12:44 +0000</pubDate>
	<author>赵苦瓜</author>
	<guid>https://blog.zhaokugua.com/?post=119</guid>

</item>
<item>
	<title>购买9800X3D+RTX5090有感</title>
	<link>https://blog.zhaokugua.com/?post=118</link>
	<description><![CDATA[<p>
	<span style="font-size:16px;">小时候自从08年家里买了第一台电脑开始就对计算机产生了浓厚的兴趣</span> 
</p>
<p>
	<span style="font-size:16px;">当时好像花了四五千块吧，记得是联想家悦系列的普通台式机&nbsp;联想 家悦 U2135A</span> 
</p>
<p>
	<span style="font-size:16px;">1440x900最高75hz的屏幕，1G的DDR2内存，160G的希捷硬盘，AMD速龙3600，还有128M显存的nVIDIA Geforce7100GS</span> 
</p>
<p>
	<span style="font-size:16px;">这显卡不仅是入门级性能菜鸡，关键还没有主动散热，仅靠鳍片被动散热，刚好碰到了烧显卡的通病，没多久就拆下来了</span> 
</p>
<p>
	<span style="font-size:16px;">然后这台电脑剩下的大部分时间都是64M显存的核显在工作（更菜鸡了</span> 
</p>
<p>
	<span style="font-size:16px;">最早预装的还是vista系统，当时加了太多动画特效导致在这台菜鸡电脑上贼卡，没几天就被换成了盗版winxp（</span> 
</p>
<p>
	<span style="font-size:16px;">后来xp停止维护又被我升级过win7和win10，当年还是ghost装机时代哈哈</span> 
</p>
<p>
	<span style="font-size:16px;"><br />
</span> 
</p>
<p>
	<span style="font-size:16px;">由于实在是太卡了，玩4399网页小游戏都要选一些小型的，不然也会卡</span> 
</p>
<p>
	<span style="font-size:16px;">后来考上市里的一中有了一千块钱拿来升级电脑，全靠咸鱼捡垃圾组了个GTX650战神，甚至还买了个全新的打印复印一体机和手机（</span> 
</p>
<p>
	<span style="font-size:16px;">当时很火的三人一狗终于也是能玩上了（别提有多激动了</span> 
</p>
<p>
	<span style="font-size:16px;">哈哈现在还在玩三人一狗（6怎么还没出！</span> 
</p>
<p>
	<span style="font-size:16px;"><br />
</span> 
</p>
<p>
	<span style="font-size:16px;">如今工作了，有点自己赚的钱也算是能实现儿时的梦想了</span> 
</p>
<p>
	<span style="font-size:16px;">RGB最强游戏主机，启动！</span> 
</p>
<p>
	<span style="font-size:16px;"><a target="_blank" href="https://blog.zhaokugua.com/content/uploadfile/202507/4df81753375300.jpg" id="ematt:671"><img src="https://blog.zhaokugua.com/content/uploadfile/202507/4df81753375300.jpg" title="点击查看原图" alt="点击查看原图" border="0" width="607" height="450" align="" /></a></span> 
</p>
<p>
	<span style="font-size:16px;">本来想去年十月左右就组一个的，大学的老笔记本还能凑活用就一直没动，刚好要出新一代50系显卡就想着买新不买旧不如等一等。</span> 
</p>
<p>
	<span style="font-size:16px;">然后等到1月发布，各种耍猴涨价之后实在忍不住了，2月装出来了，只不过显卡暂时拿4080s过渡。</span> 
</p>
<p>
	<span style="font-size:16px;">选的是水冷卡，所以就做了个CPU和显卡全水冷的方案（</span> 
</p>
<p>
	<span style="font-size:16px;">显卡是测出风的，当时刚好还是冬天比较冷还能烤烤腿（</span> 
</p>
<p>
	<br />
</p>
<p>
	<span style="font-size:16px;"><span style="font-size:16px;text-wrap-mode:wrap;">用着4080s看上去好像也比较高端了，但是配的是4K屏还总想着拉满画质开光追啥的就总感觉不够用要换90（</span><br />
</span> 
</p>
<p>
	<span style="font-size:16px;">再后来5090终于降价了一点然后实在憋不住了就换了（没想到后面还在猛降价，甚至比二手4090都便宜</span> 
</p>
<p>
	<span style="font-size:16px;">然后我终于获得了我的9800X3D+RTX5090神机（</span> 
</p>
<p>
	<span style="font-size:16px;"><a target="_blank" href="https://blog.zhaokugua.com/content/uploadfile/202507/13321753375297.jpg" id="ematt:669"><img src="https://blog.zhaokugua.com/content/uploadfile/202507/13321753375297.jpg" title="点击查看原图" alt="点击查看原图" border="0" width="686" height="500" align="" /></a></span> 
</p>
<p>
	<span style="font-size:16px;"><a target="_blank" href="https://blog.zhaokugua.com/content/uploadfile/202507/4f011753375291.jpg" id="ematt:667"><img src="https://blog.zhaokugua.com/content/uploadfile/202507/4f011753375291.jpg" title="点击查看原图" alt="点击查看原图" border="0" width="672" height="500" align="" /></a></span> 
</p>
<p>
	<span style="font-size:16px;"><a target="_blank" href="https://blog.zhaokugua.com/content/uploadfile/202507/fe291753375304.png" id="ematt:673"><img src="https://blog.zhaokugua.com/content/uploadfile/202507/fe291753375304.png" title="点击查看原图" alt="点击查看原图" border="0" width="696" height="500" align="" /></a></span> 
</p>
<p>
	<span style="font-size:16px;"><br />
</span> 
</p>
<p>
	<span style="font-size:16px;">9800X3D毕竟核心数少一些，多核处理方面还有生产力上总归是要差一点的，但是就看上了这个X3D的超大缓存带来的游戏帧数提升，小学微机课上就学过CPU里的缓存比金子都贵，速度是最快的。</span> 
</p>
<p>
	<span style="font-size:16px;">实际用下来感觉可能游戏编译着色器的时候回比其他U慢一些，进游戏之后的帧数确实高。</span> 
</p>
<p>
	<span style="font-size:16px;">不过编译着色器的时候感觉还是有点积热问题，拿360水冷压也有70度左右总感觉有点压不住（比起之前的电脑动不动100℃+好太多了</span> 
</p>
<p>
	<span style="font-size:16px;">然后就是5090，因为之前用过4080s所以还是能感觉到有些区别</span> 
</p>
<p>
	<span style="font-size:16px;">一是并不是买了5090所有画质就都能拉满，有些没优化的游戏5090全拉满也跑不了多高的帧数，还是得看厂商优化</span> 
</p>
<p>
	<span style="font-size:16px;">二是有些确实能跑满但是功耗实在是太高了，动不动四五百瓦功耗，已经赶上半个空调了，夏天太热了（还得降画质降点功耗玩</span> 
</p>
<p>
	<span style="font-size:16px;">三是待机功耗高，现在啥都不干，5090的待机功耗就能达到六七十瓦，之前4080s待机功耗也才二十出头！甚至跑一些游戏才七八十瓦功耗，不愧是电老虎（</span> 
</p>
<p>
	<span style="font-size:16px;">不过之前4080s跑不满的游戏能全拉满是真的爽啊（</span> 
</p>
<p>
	<span style="font-size:16px;">视频剪辑也有巨大提升，开了GPU加速编码，几分钟的4K视频渲染导出就要几秒钟，之前从没想过能这么快</span> 
</p>
<p>
	<span style="font-size:16px;">还有超大32G显存，目前还没找到有什么游戏能完全吃满，就算把怪猎荒野开全高4K200%渲染倍率和DLAA，只能跑30帧但是显存只能吃25G（</span> 
</p>
<p>
	<span style="font-size:16px;"><br />
</span> 
</p>
<p>
	<span style="font-size:16px;">现在画质都拉满了，游戏都能随便玩了，全都是顶级画质了</span> 
</p>
<p>
	<span style="font-size:16px;">总感觉还是少了点什么</span> 
</p>
<p>
	<span style="font-size:16px;">总是怀念之前大学宿舍一起开黑的时光...</span> 
</p>
<p>
	<span style="font-size:16px;"><br />
</span> 
</p>
<p>
	<span style="font-size:16px;">果然，游戏要求最高的配置还是朋友啊...</span> 
</p>
<p>
	<br />
</p>]]></description>
	<pubDate>Thu, 24 Jul 2025 16:34:24 +0000</pubDate>
	<author>赵苦瓜</author>
	<guid>https://blog.zhaokugua.com/?post=118</guid>

</item></channel>
</rss>