fix(cli): enable systray2 on Windows for Norton-friendly tray (#8609)

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-07 18:08:17 -03:00
committed by GitHub
parent 6aac7b0c8f
commit cfeea5dc5b
6 changed files with 47 additions and 10 deletions

View File

@@ -17,7 +17,7 @@ export const SYSTRAY_VERSION = "2.1.4";
const SYSTRAY_SPEC = `${SYSTRAY_PACKAGE}@${SYSTRAY_VERSION}`;
export function resolveSystrayBinName(platform: NodeJS.Platform): string | null {
if (platform === "win32") return null;
if (platform === "win32") return "tray_windows_release.exe";
if (platform === "darwin") return "tray_darwin_release";
return "tray_linux_release";
}
@@ -45,7 +45,6 @@ export function chmodSystrayBinAt(runtimeRoot: string, platform: NodeJS.Platform
}
export async function loadSystray(): Promise<(new (...args: unknown[]) => unknown) | null> {
if (process.platform === "win32") return null; // Windows uses tray.ps1 instead
ensureRuntimeDir();
if (!isInstalled()) {
try {

View File

@@ -167,6 +167,10 @@ export function getAutostartStatus() {
linger: tryReadLingerEnabled(),
};
}
if (process.platform === "win32") {
const winMechanism = isAutostartEnabled() ? "vbs-startup" : null;
return { enabled: isAutostartEnabled(), mechanism: winMechanism };
}
return { enabled: isAutostartEnabled(), mechanism: null };
}

View File

@@ -1,5 +1,4 @@
import { isTraySupported, initSystrayUnix, killSystrayUnix } from "./traySystray.mjs";
import { initWinTray, killWinTray } from "./trayWindows.mjs";
let active = null;
@@ -10,15 +9,17 @@ export async function initTray({ port, onQuit, onOpenDashboard, onShowLogs }) {
const ctx = { port, onQuit, onOpenDashboard, onShowLogs };
// initSystrayUnix is async: it lazily installs/loads systray2 from the runtime
// dir (trayRuntime.ts) rather than from node_modules. (#4605)
active = process.platform === "win32" ? initWinTray(ctx) : await initSystrayUnix(ctx);
// Use systray2 on all platforms including Windows — the tarball ships
// tray_windows_release.exe, avoiding the Norton/AVG IDP.HELU.PSE85 heuristic
// that fires on temp-dir PowerShell scripts. (#8609)
active = await initSystrayUnix(ctx);
return active;
}
export function killTray() {
if (!active) return;
try {
if (process.platform === "win32") killWinTray(active);
else killSystrayUnix(active);
killSystrayUnix(active);
} catch {}
active = null;
}

View File

@@ -0,0 +1 @@
- fix(cli): enable systray2 on Windows for Norton-friendly tray (#8609)

View File

@@ -26,8 +26,8 @@ test("systray2 is pinned to a 2.x version (PR #1080 fix)", () => {
assert.match(SYSTRAY_VERSION, /^2\./, `expected systray2@2.x, got ${SYSTRAY_VERSION}`);
});
test("resolveSystrayBinName returns null on win32 and a *_release name elsewhere", () => {
assert.equal(resolveSystrayBinName("win32"), null);
test("resolveSystrayBinName returns *_release name on all platforms (#8609)", () => {
assert.equal(resolveSystrayBinName("win32"), "tray_windows_release.exe");
assert.equal(resolveSystrayBinName("darwin"), "tray_darwin_release");
assert.equal(resolveSystrayBinName("linux"), "tray_linux_release");
});
@@ -63,12 +63,12 @@ test("chmodSystrayBinAt is a no-op when the binary doesn't exist", () => {
}
});
test("chmodSystrayBinAt skips win32 (uses PowerShell tray, no Go binary)", () => {
test("chmodSystrayBinAt returns missing on win32 when binary is absent (#8609)", () => {
const root = mkdtempSync(join(tmpdir(), "omniroute-systray-bin-"));
try {
const result = chmodSystrayBinAt(root, "win32");
assert.equal(result.changed, false);
assert.equal(result.reason, "win32-skip");
assert.equal(result.reason, "missing");
} finally {
rmSync(root, { recursive: true, force: true });
}

View File

@@ -0,0 +1,32 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
test("characterize: trayWindows.mjs initWinTray writes a temp .ps1 (old behavior)", async () => {
const { initWinTray } = await import("../../bin/cli/tray/trayWindows.mjs");
const ORIG_PLATFORM = Object.getOwnPropertyDescriptor(process, "platform");
Object.defineProperty(process, "platform", { value: "win32", configurable: true });
const cleanup = () => {
if (ORIG_PLATFORM) Object.defineProperty(process, "platform", ORIG_PLATFORM);
};
try {
const proc = initWinTray({ port: 8609, onQuit() {}, onOpenDashboard() {}, onShowLogs() {} });
if (proc && typeof proc.on === "function") proc.on("error", () => {});
const scripts = readdirSync(tmpdir()).filter((f) => f.startsWith("omniroute-tray-") && f.endsWith(".ps1"));
assert.ok(scripts.length > 0, "initWinTray creates a temp .ps1 (expected — that is the Norton trigger)");
const content = readFileSync(join(tmpdir(), scripts[0]), "utf8");
assert.ok(content.includes("System.Windows.Forms.NotifyIcon"), "temp .ps1 uses WinForms tray");
} finally {
cleanup();
}
});
test("REGRESSION GUARD: index.mjs no longer imports or calls the PowerShell tray (#8609)", () => {
const source = readFileSync(join(process.cwd(), "bin/cli/tray/index.mjs"), "utf8");
assert.ok(!source.includes("trayWindows"), "index.mjs must not import trayWindows.mjs");
assert.ok(!source.includes("initWinTray"), "index.mjs must not reference initWinTray");
assert.ok(!source.includes("killWinTray"), "index.mjs must not reference killWinTray");
assert.ok(source.includes("initSystrayUnix"), "index.mjs must still import initSystrayUnix");
});