From 906821920b02c39aa2618306f062f67a644de487 Mon Sep 17 00:00:00 2001 From: JianFeeeee <109188060+JianFeeeee@users.noreply.github.com> Date: Sun, 4 Oct 2026 15:25:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(gui):=20=E6=B8=85=E7=90=86=E7=94=B1?= =?UTF-8?q?=E6=94=B9=E5=90=8D=E9=81=97=E7=95=99=E7=9A=84=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E5=BC=80=E6=9C=BA=E8=87=AA=E5=90=AF=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 现象 本机 HKCU\...\Run 里同时存在两条指向**同一个 exe** 的自启动项: electron.app.HomeAgent = "E:\program\homeagent\...\homeagent-gui.exe" electron.app.homeagent-gui = E:\program\homeagent\...\homeagent-gui.exe 表面看是两个自启动,实际是同一份程序的重复 ⇒ 开机启动两次。 ## 根因 Electron 的 app.setLoginItemSettings() 用「electron.app.」作为 注册表项名。本产物改过名(HomeAgent.exe → homeagent-gui.exe),于是写入 新项,而旧项 Electron 不会清理 ⇒ 每改一次名就多留一条。 原 applyAutoLaunch 既不去重、也不清理历史项。 ## 修法 新增 cleanupStaleLoginItems(): · 只删「指向当前 process.execPath」的 electron.app.* 项 —— 不误伤别的 Electron 应用(本机还有 electron.app.TencentDocs 同为该前缀) · 保留当前 exe 名对应的那条(Electron 期望的名字) · 在 app.whenReady() 里也跑一次:否则旧项要等用户手动开关自启才被清 ## 实测(非静态检查) 人为再造一条 electron.app.HomeAgent 指向同一 exe,启动 GUI 后: 启动前 2 条 → 启动后 1 条 日志: [autolaunch] removed stale login item: electron.app.HomeAgent 判据未改动,npm test 131 条全绿。 --- cmd/gui/main.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/cmd/gui/main.js b/cmd/gui/main.js index 7c98dee2..87a4c71e 100644 --- a/cmd/gui/main.js +++ b/cmd/gui/main.js @@ -3178,6 +3178,11 @@ function destroyTray() { app.whenReady().then(async () => { installAuthRule(); + // 清理历史遗留的重复开机自启项(产物改名会累积指向同一 exe 的旧项)。 + // 不依赖用户去设置里手动开关:否则旧项会一直留着、开机启动两次。 + try { + cleanupStaleLoginItems(); + } catch (e) {} const running = await isServerRunning(); if (running) { // 已探测到可用端点:服务端在别处正常运行(远程/WSL/另一台机器)。 @@ -3406,9 +3411,74 @@ function saveGuiPrefs(p) { return p; } +// Electron LoginItem 写入的注册表项名是「electron.app.」。 +// 产物改名过(HomeAgent.exe → homeagent-gui.exe)就会多出一条旧项, +// 且 setLoginItemSettings 不会清理它们 ⇒ 开机启动两次同一个 exe。 +// 实测本机 Run 键里确实同时存在: +// electron.app.HomeAgent = "E:\program\homeagent\...\homeagent-gui.exe" +// electron.app.homeagent-gui = E:\program\homeagent\...\homeagent-gui.exe +// 两条指向同一 exe,所以表面看是两个自启动,实际是同一份的重复。 +// +// 修法:每次写入前,删掉所有指向当前 exe 的 electron.app.* 旧项, +// 只保留当前名字对应的那一条。 +function cleanupStaleLoginItems() { + if (process.platform !== "win32") return 0; + let removed = 0; + try { + const { execFileSync } = require("child_process"); + const runKey = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"; + const execPath = String(process.execPath || "").toLowerCase(); + // 列出 Run 键下的所有值名 + const out = execFileSync( + "reg", + ["query", runKey], + { encoding: "utf8", timeout: 5000, windowsHide: true }, + ); + const names = []; + for (const line of String(out).split(/\r?\n/)) { + const m = line.match(/^\s{4}([A-Za-z0-9_.\-\\]+)\s+REG_/); + if (m) names.push(m[1]); + } + for (const name of names) { + if (!/^electron\.app\./i.test(name)) continue; + // 只删指向**当前 exe** 的;别的 Electron 应用不能误伤 + let val = ""; + try { + const q = execFileSync("reg", ["query", runKey, "/v", name], { + encoding: "utf8", + timeout: 5000, + windowsHide: true, + }); + val = String(q); + } catch (e) { + continue; + } + const sameExe = val.toLowerCase().includes(execPath); + if (!sameExe) continue; + // 保留当前 exe 名对应的那一条(Electron 期望的名字) + const currentName = "electron.app." + path.basename(process.execPath, ".exe"); + if (name === currentName) continue; + try { + execFileSync("reg", ["delete", runKey, "/v", name, "/f"], { + timeout: 5000, + windowsHide: true, + }); + removed++; + console.log("[autolaunch] removed stale login item: " + name); + } catch (e) { + console.error("[autolaunch] failed to remove " + name + ": " + e.message); + } + } + } catch (e) { + console.error("cleanupStaleLoginItems failed: " + e.message); + } + return removed; +} + // 应用开机自启设置(Electron LoginItem) function applyAutoLaunch(enabled) { try { + if (enabled) cleanupStaleLoginItems(); app.setLoginItemSettings({ openAtLogin: enabled, openAsHidden: true, // 开机自启时静默(Windows/macOS 支持)