# 设计说明:把「刷新 / 重启」放进 DSH 桌面端的「应用」菜单 > **插件只是载体,补丁才是本体。** 这个包不带任何 UI,它带的是: > 1. 一个能直接干活的工具 `dsh_desktop_menu`(status / apply / revert); > 2. 完整的推导过程(本文)与给 AI 的照抄步骤([RECIPE.md](RECIPE.md)); > 3. 等价的独立 Python 脚本([standalone/](standalone/)),不装插件也能照做。 > > 换一台装了 DSH 桌面端的电脑,AI 读这两页 + 调工具,就能把那两项写进「应用」菜单。 ## 1. 事实:菜单在哪、为什么默认没有这两项 DSH 主界面左上角的「应用」按钮**不是 Web 插件渲染的**,而是 Electron 桌面壳的原生菜单: | 层 | 位置 | 说明 | | --- | --- | --- | | 按钮条(应用 / 编辑) | `app.asar:lib/preload-app.cjs` → `installWindowsMenu()` | 往页面里挂一个 `[data-windows-menu]` 影子 DOM,点击时发 IPC | | 菜单内容 | `app.asar:lib/main.js` → `applicationItems()` | 主进程用 `Menu.buildFromTemplate(...).popup()` 现场构建 | | 菜单项里的两项 | 同上 | `刷新页面` (`role: "reload"`)、`重启应用与 Host` (`app.relaunch() + quitWithoutConfirmation()`) | 这两项**代码本来就有**,但包在开发构建开关里: ```js ...development ? [ { type: "separator" }, { label: ...reloadPageMenu, role: "reload" }, { label: ...restartAppHostMenu, click: () => { app.relaunch(); quitWithoutConfirmation() } }, ] : [], ``` `development = !app.isPackaged`,正式安装版恒为 `false`,所以永远不显示。 ## 2. 补丁:11 字节等长替换 ```diff - ...development ? [ + ...!(0 === 1) ? [ ``` - `development` 与 `!(0 === 1)` **都是 11 字节** ⇒ asar 头的 `json_len`、每个文件的 `offset`、归档总大小全不变,**不需要重新打包**; - 语义不变:`!(0 === 1)` 恒为 `true`,数组照常展开; - 还需要同步一件事:asar 头里 `/lib/main.js` 的 `integrity.hash`(同文件 `blocks[0]` 共 2 处,都是 64 位十六进制)。改完内容后用新 SHA256 **等长**替换即可。 (Electron 的 `EnableEmbeddedAsarIntegrityValidation` 只在 macOS 强制;Windows 上同步哈希是保险,不影响运行。) - 幂等:脚本按内容定位 `...development ? [ `,找不到或出现多次就拒绝写入。 改完之后,`main.js` 只在**主进程启动时**加载一次 ⇒ 必须重启桌面端才生效。 ## 3. AI 在一台新电脑上的标准流程 1. 装本插件(`plugin_manager` → `install_bundle`,target 为本包绝对目录),重启一次 DSH; 2. 调 `dsh_desktop_menu`: - `{ "action": "status" }` → 看 `asar` 路径、`patched`、`hashConsistent`; - `{ "action": "apply" }` → 写入补丁(返回 `verified.patched === true`); - 让用户从托盘退出再打开桌面端 → 生效; 3. 之后「应用」菜单里就有 `刷新页面` / `重启应用与 Host`; 4. 桌面端自动更新会换掉 `app.asar`,补丁消失 ⇒ 更新后再 `apply` 一次; 5. 不想要了:`{ "action": "revert" }`。 `app.asar` 自动探测顺序:参数 `asar` → 环境变量 `DSH_DESKTOP_ASAR` → 从运行中的可执行文件逐级向上找 `