Files
Scoreboard/console/main/autostart.js
T
AskaEth efa049c827 feat(platform): Console 支持 Linux(arm64),Windows 行为逐字不变;版本 dev_0.5.10
需求:让积分榜能在飞腾 D2000(ARM64)的 Linux 上跑起来,**同时不破坏 Windows**。

## 一、Go 服务端:交叉编译即可(**已实测**)
- SQLite 用的是 `modernc.org/sqlite`(纯 Go 驱动),全项目**没有一处 `import "C"`** → `CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build` 直接出;
- 产物是**静态链接**(`ELF 64-bit LSB executable, ARM aarch64, statically linked`)→ **不依赖 glibc 版本**,对国产系统友好;
- 本机(arm64)**实际启动验证**:数据目录、局域网地址探测、数据库迁移(0001/0002/0003)全部正常;
- 产物:`debug/服务端-arm64/scoreboard`(29 MB,`-s -w`)。

## 二、Console(Electron 宿主):平台差异收口到一个新文件
- **新增 `console/main/platform.js`**:平台差异集中在这 45 行里,其余代码不写平台判断。
  ⚠️ **`platform` 参数缺省按 Windows 解析** —— 漏传参数走的就是改动前那条老路(这比"改了再小心"更保守:**默认行为不变,新行为必须显式传参**)。
- `userpaths.js` / `datadir.js`:Windows 分支 `%LOCALAPPDATA%\ScoreboardConsole\` **逐字保留**;Linux 分支按 XDG(`$XDG_DATA_HOME`,缺省 `~/.local/share`)。两平台各自的"拿不到用户目录"提示文案分开写(免得现场去设一个本平台不存在的环境变量)。
- `config.js`:服务端文件名按平台取(Windows `scoreboard.exe` / Linux `scoreboard`)。
- **新增 `console/main/autostart.js`**:Linux 用 `~/.config/autostart/*.desktop`(开=写文件、关=删文件;Linux 没有"已注册但未启用"这个中间态)。
- 托盘与窗口行为:Linux 的通知区多数不派发 double-click,因此按平台分支成单击。

## 三、怎么保证没弄坏 Windows
1. **设计上**:平台差异集中 + 缺省值就是 Windows(见上);
2. **逐行核对 diff**:Windows 分支那几行原样未动;
3. **测试**:Console 由 **68 → 96 例**,新增全是平台分支用例,**既有断言一条没改**;
4. **真机验证**:构建机重打 Windows 包并跑(这一道才算收口)。

## 四、自检
全量自检两轮**全绿、`EXIT=0`**:服务端 14 包 / 前端 **944 例** / Console **96 例**。

## 版本
`dev_0.5.9` → **`dev_0.5.10`**;安装器素材同步重生成(照例只动 `brand.bmp` 两张)。
2026-09-15 00:53:16 +08:00

86 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict'
const fs = require('fs')
const path = require('path')
const { xdgConfigHome } = require('./platform')
// Linux 的「开机自启」只能靠 XDG 自启目录里的一个 .desktop:Electron 的
// setLoginItemSettings 在 Linux 上是空实现,桌面环境(GNOME / UKUI / 麒麟 / KDE)
// 读的都是 $XDG_CONFIG_HOME/autostart 或 ~/.config/autostart。
const AUTOSTART_FILE = 'scoreboard-console.desktop'
// 返回 .desktop 的绝对路径;非 Linux(Windows/macOS 走系统登录项)或拿不到 ~/.config 时
// 返回 null,由调用方回滚配置并记日志,绝不静默当成功。
function resolveAutostartFile({ platform = 'linux', env = process.env } = {}) {
if (platform !== 'linux') return null
const base = xdgConfigHome(env)
return base ? path.join(base, 'autostart', AUTOSTART_FILE) : null
}
// Exec 字段不是 shell 命令行,路径带空格必须自己加引号,否则自启时会被拆成两个参数、
// 找不到程序(现场装到「Program Files」那种带空格的目录下必然遇到)。
function quoteExec(exePath) {
return `"${String(exePath).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
}
function desktopEntry(exePath) {
return [
'[Desktop Entry]',
'Type=Application',
'Version=1.0',
'Name=Scoreboard Console',
'Name[zh_CN]=赛事积分榜',
'Comment=守护服务端并把大屏投到扩展屏',
`Exec=${quoteExec(exePath)}`,
'Terminal=false',
// 有些桌面环境只认这一个键;写 true 表明这份文件是「启用」状态(关掉是直接删文件)
'X-GNOME-Autostart-enabled=true',
'Hidden=false',
''
].join('\n')
}
// 「开」与「关」以文件实际状态为准(与 Windows 读登录项同一口径):
// 文件在就是开。Hidden=true 是 XDG 规范里「忽略这个文件」的写法 —— 现场从桌面环境的
// 「启动应用程序」面板里关掉自启时会这么标,所以必须认出来,否则界面会显示成还开着。
function isAutostartEnabled({ platform = 'linux', env = process.env } = {}) {
const file = resolveAutostartFile({ platform, env })
if (!file) return false
let text
try {
text = fs.readFileSync(file, 'utf8')
} catch {
return false
}
return !/^\s*Hidden\s*=\s*true\s*$/im.test(text)
}
// 开 → 写文件(覆盖旧的,每次启动 Console 按配置重写一遍是幂等的);
// 关 → 删文件。与 Windows 的「登录项一直保留、只切 enabled」不同:各桌面环境对
// X-GNOME-Autostart-enabled / Hidden 的支持不一致,写了标记却被忽略等于开关失灵,
// 删文件是唯一在各地都生效的做法(差异见回报)。
// 任何失败(含拿不到路径、平台不对)都抛出去,由调用方回滚配置,界面才不会说反。
function setAutostart({ platform = 'linux', env = process.env, exePath, enabled } = {}) {
const file = resolveAutostartFile({ platform, env })
if (!file) {
if (platform !== 'linux') throw new Error('开机自启在 Linux 上走 XDG 自启目录,本平台请用系统登录项')
throw new Error('拿不到 $XDG_CONFIG_HOME 与 $HOME,无法写自启文件')
}
if (enabled !== true) {
// force: 文件本来就不在(目录也不在)时不算失败
fs.rmSync(file, { force: true })
return { file }
}
if (typeof exePath !== 'string' || exePath === '') {
throw new Error('拿不到 Console 自身的可执行文件路径,无法写自启文件')
}
fs.mkdirSync(path.dirname(file), { recursive: true })
fs.writeFileSync(file, desktopEntry(exePath), 'utf8')
return { file }
}
module.exports = { AUTOSTART_FILE, resolveAutostartFile, isAutostartEnabled, setAutostart }