foreground-tracking
游戏启动后的前台窗口监听/聚焦计时的平台实现。 涉及 Windows Job Object、Linux systemd scope / cgroup、 X11 EWMH、AT-SPI D-Bus 等机制。
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
游戏启动后的前台窗口监听/聚焦计时的平台实现。 涉及 Windows Job Object、Linux systemd scope / cgroup、 X11 EWMH、AT-SPI D-Bus 等机制。
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | foreground-tracking |
| description | 游戏启动后的前台窗口监听/聚焦计时的平台实现。 涉及 Windows Job Object、Linux systemd scope / cgroup、 X11 EWMH、AT-SPI D-Bus 等机制。 |
settings.launch.precision_mode 开启时,只有游戏窗口位于前台才会计时。
轮询间隔 1 秒,持久化间隔 60 秒,均在 game_loop() 中完成。
文件: src-tauri/src/exec/windows.rs
| 步骤 | 说明 |
|---|---|
| 创建 Job Object | CreateJobObjectW,未命名 |
| 绑定启动器 | AssignProcessToJobObject,子/孙进程自动继承 |
| 前台检测 | 每秒 GetForegroundWindow() → GetWindowThreadProcessId() → IsProcessInJob(foreground_pid, job_handle) |
关键:Windows Job 的继承语义保证即使经过 Locale Emulator 等包装层,最终的游戏进程也在 Job 内,is_focused() 始终能匹配。
enum GameTracker {
Systemd { procs_path: PathBuf }, // 优先
Child { child: tokio::process::Child }, // systemd 不可用时回退
}
has_systemd_user() 检查:
systemd-run 在 $PATH 中$XDG_RUNTIME_DIR 已设置/run/user/$UID/systemd/private 存在文件: src-tauri/src/exec/linux/spawn.rs
systemd-run --user --scope --no-block --unit=galgame-manager-{game_id}-{pid}.scopesystemctl --user show --property=ControlGroup --value 获取 scope 的 cgroup 子路径/sys/fs/cgroup/{subpath}/cgroup.procs 来判断进程树存活性存活性检测:has_active_processes() 读取 cgroup.procs,只要还有 PID 就认为活着。
聚焦检测:is_focused() 检查前台 PID 是否在 cgroup.procs 列表中。
仅支持 cgroup v2(Arch / Fedora / Ubuntu 21.10+ / Debian 11+ 默认使用)。
cgroup v1 下查询会失败 →has_active_processes()返回空 → 游戏循环结束。
has_active_processes() 仅调用 tokio::process::Child::try_wait()is_focused() 仅比较前台 PID 与 child.id()(启动器自身的 PID)局限性:
is_focused() 几乎永不匹配(前台窗口的 PID ≠ 启动器 PID)文件: src-tauri/src/exec/linux/foreground/mod.rs
pub trait ForegroundDetector {
fn focused_pid(&self) -> Option<u32>;
}
CompositeDetector 依次尝试每个子检测器,返回第一个 Some(pid)。
src-tauri/src/exec/linux/foreground/x11.rs| 步骤 | 说明 |
|---|---|
| 1 | _NET_ACTIVE_WINDOW → 聚焦窗口的 XID |
| 2 | _NET_WM_PID → 该窗口所属 PID |
x11rb 连接,每次 focused_pid() 调用时同步查询(约 1 个 X11 往返)try_init() 检查 $DISPLAY,无 DISPLAY 时返回 Nonesrc-tauri/src/exec/linux/foreground/atspi.rsnew_current_thread tokio runtimeorg.a11y.atspi.Event.Object.StateChanged:focused,arg0="focused",detail1=1 表示获得焦点GetConnectionUnixProcessID 解析为 PID,缓存到 HashMap<String, u32>NameOwnerChanged 监听用于淘汰死进程的缓存FOCUSED_PID 通过 AtomicU32 共享,查询不阻塞try_init() 检查 $DBUS_SESSION_BUS_ADDRESS,无 D-Bus 时返回 NoneX11Detector 优先 (廉价同步查询)
↓ 返回 None
AtspiDetector 兜底 (需 AT-SPI 桌面)
↓ 两者都 None
NoopDetector (永不匹配,记录警告日志)
| 场景 | 前台检测 | 存活性检测 |
|---|---|---|
| Windows | ✅ GetForegroundWindow + IsProcessInJob | ✅ Job Object ActiveProcesses |
| Linux + systemd + X11/XWayland | ✅ X11Detector | ✅ cgroup.procs |
| Linux + systemd + Wayland + AT-SPI | ✅ AtspiDetector | ✅ cgroup.procs |
| Linux + systemd + Wayland + 无 AT-SPI | ❌ 永不匹配 | ✅ cgroup.procs |
| Linux + Child 回退 | ⚠️ 仅匹配启动器自身 PID | ⚠️ 仅追踪直接子进程 |
额外边界:
systemd-run 调用失败(权限、资源不足)→ 退化为 Child 路径FOCUSED_PID 永远为 0$DISPLAY 或连接拒绝)→ X11Detector 不初始化