|
| 1 | +"""AFA(明日方舟帧操小助手)进程管理:检测 + 拉起自带 AFA.exe。 |
| 2 | +
|
| 3 | +AFA 是独立常驻 AutoHotkey 工具,注册全局热键完成暂停/步进/技能/撤退时序。 |
| 4 | +executor.afa_hotkey 通过 SendInput 模拟按键触发 AFA 热键——故 AFA 必须先运行。 |
| 5 | +
|
| 6 | +本模块: |
| 7 | +- is_afa_running():查 AFA.exe 进程是否在 |
| 8 | +- ensure_afa():没在则拉起自带的 AFA.exe(打包在 exe 同级 afa/AFA.exe) |
| 9 | +
|
| 10 | +AFA 用默认热键配置(F/Space/R/W/S/A),与 afa_hotkey 假设一致。 |
| 11 | +AFA 是 GPL-3.0(CloudTracey/arknights-frame-assistant),自带分发见 README 许可声明。 |
| 12 | +""" |
| 13 | + |
| 14 | +from __future__ import annotations |
| 15 | + |
| 16 | +import subprocess |
| 17 | +import sys |
| 18 | +from pathlib import Path |
| 19 | + |
| 20 | +from aao.utils.logger import logger |
| 21 | +from aao.utils.runtime_paths import project_root |
| 22 | + |
| 23 | +_AFA_PROCESS = "AFA.exe" # AFA 进程名(编译后) |
| 24 | +_AFA_REL_PATH = "afa" / Path(_AFA_PROCESS) # 相对项目根/exe 同级 |
| 25 | + |
| 26 | + |
| 27 | +def _afa_exe_path() -> Path: |
| 28 | + """自带 AFA.exe 路径(项目根 或 exe 同级的 afa/AFA.exe)。""" |
| 29 | + return project_root() / _AFA_REL_PATH |
| 30 | + |
| 31 | + |
| 32 | +def is_afa_running() -> bool: |
| 33 | + """AFA.exe 进程是否在运行。非 Windows 返回 False。""" |
| 34 | + if sys.platform != "win32": |
| 35 | + return False |
| 36 | + try: |
| 37 | + import ctypes |
| 38 | + |
| 39 | + # 用 CreateToolhelp32Snapshot 枚举进程,避免依赖 tasklist/PowerShell |
| 40 | + # 简单起见用 tasklist(Windows 自带) |
| 41 | + out = subprocess.run( |
| 42 | + ["tasklist", "/FI", f"IMAGENAME eq {_AFA_PROCESS}", "/NH", "/FO", "CSV"], |
| 43 | + capture_output=True, |
| 44 | + text=True, |
| 45 | + timeout=5, |
| 46 | + check=False, |
| 47 | + ) |
| 48 | + running = _AFA_PROCESS.lower() in out.stdout.lower() |
| 49 | + # 引用 ctypes 防止 pyright 报未用(保留 Windows 判断接口) |
| 50 | + _ = ctypes |
| 51 | + return running |
| 52 | + except (OSError, subprocess.SubprocessError): |
| 53 | + logger.warning("无法检测 AFA 进程,假定未运行") |
| 54 | + return False |
| 55 | + |
| 56 | + |
| 57 | +def ensure_afa() -> bool: |
| 58 | + """确保 AFA 在运行:没在则拉起自带 AFA.exe(后台、无窗口阻塞)。 |
| 59 | +
|
| 60 | + Returns: |
| 61 | + True 若 AFA 已在运行 或 成功拉起;False 若拉起失败。 |
| 62 | + """ |
| 63 | + if is_afa_running(): |
| 64 | + logger.info("AFA 已在运行") |
| 65 | + return True |
| 66 | + |
| 67 | + exe = _afa_exe_path() |
| 68 | + if not exe.exists(): |
| 69 | + logger.warning("未找到自带 AFA.exe(%s),请手动启动 AFA 或重新安装", exe) |
| 70 | + return False |
| 71 | + |
| 72 | + try: |
| 73 | + # 后台启动 AFA( detached,不随本进程退出而终止) |
| 74 | + # CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS(Windows) |
| 75 | + flags = 0x00000008 | 0x00000200 if sys.platform == "win32" else 0 # noqa: S107 |
| 76 | + subprocess.Popen( |
| 77 | + [str(exe)], |
| 78 | + cwd=str(exe.parent), |
| 79 | + stdin=subprocess.DEVNULL, |
| 80 | + stdout=subprocess.DEVNULL, |
| 81 | + stderr=subprocess.DEVNULL, |
| 82 | + close_fds=True, |
| 83 | + creationflags=flags, |
| 84 | + ) |
| 85 | + logger.info("已拉起自带 AFA: %s", exe) |
| 86 | + return True |
| 87 | + except (OSError, subprocess.SubprocessError): |
| 88 | + logger.exception("拉起 AFA 失败") |
| 89 | + return False |
0 commit comments