# -*- coding: utf-8 -*- """两件事: 【1】修「弹通知」在静默模式下不显示 HeadlessUI.notify 之前只写日志,什么都不弹,所以手表点「弹通知」 电脑上一点反应都没有。 静默模式的意思是"开机不弹控制窗口",不代表手表**明确要求显示**的 东西也不显示 —— 投图、全屏大字、通知都属于这一类。 【2】把网络自检 / 修防火墙做进 EXE 原来的 网络自检.bat 会去找 python,没装 Python 的电脑用不了。 现在改成调用 LanRemote-Agent.exe --netcheck。 因为是 --windowed 程序没有控制台,所以要先 AllocConsole 自己开一个。 """ from __future__ import annotations import sys from pathlib import Path ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\windows") HEADLESS = ROOT / "lanremote" / "headless.py" RUN = ROOT / "run_agent.py" # ---------------- 1. notify 真的弹窗 ---------------- NOTIFY_OLD = ''' def notify(self, title: str, text: str) -> None: """静默模式下不弹气泡,只记日志。 真正的"提醒"由功能自己用 actions.notify 决定要不要显示窗口。 """ self.log(f"[通知] {title}: {text}")''' NOTIFY_NEW = ''' def notify(self, title: str, text: str) -> None: """弹一个小通知窗(屏幕右下角,几秒后自动消失)。 之前这里只写日志,所以手表点「弹通知」电脑上毫无反应。 静默模式的意思是"开机不弹控制窗口",而不是 "手表明确要求显示的东西也不显示" —— 投图、全屏大字、通知 都属于"明确要求",照常显示。 """ self.log(f"[通知] {title}: {text}") root = self._ensure_root() if root is None: return def build(): try: win = tk.Toplevel(root) win.title(f"通知 - {title}") win.overrideredirect(True) win.attributes("-topmost", True) win.configure(bg="#ffffff") box = tk.Frame(win, bg="#ffffff", highlightthickness=1, highlightbackground="#d0d7de") box.pack(fill="both", expand=True) tk.Frame(box, bg="#185ce8", height=4).pack(fill="x") tk.Label(box, text=title or "通知", bg="#ffffff", fg="#1f2328", font=("Microsoft YaHei UI", 11, "bold"), anchor="w").pack(fill="x", padx=14, pady=(10, 2)) tk.Label(box, text=text or "", bg="#ffffff", fg="#656d76", font=("Microsoft YaHei UI", 10), anchor="w", justify="left", wraplength=260).pack( fill="x", padx=14, pady=(0, 12)) win.update_idletasks() screen_w, screen_h = self.screen_size() width = max(240, win.winfo_reqwidth()) height = win.winfo_reqheight() win.geometry(f"+{screen_w - width - 24}+{screen_h - height - 72}") def close(_event=None): try: win.destroy() except Exception: # noqa: BLE001 pass win.bind("", close) win.after(6000, close) except Exception as exc: # noqa: BLE001 self.log(f"通知显示失败: {exc}") try: self._queue.put(build) except Exception: # noqa: BLE001 pass''' # ---------------- 2. run_agent 加 --netcheck / --fix-firewall ---------------- ARGS_OLD = ''' parser.add_argument("--setup", action="store_true", help="重新打开设置窗口")''' ARGS_NEW = ''' parser.add_argument("--setup", action="store_true", help="重新打开设置窗口") parser.add_argument("--netcheck", nargs="?", type=int, const=90, default=None, metavar="秒", help="网络自检(自开控制台,不需要装 Python)") parser.add_argument("--fix-firewall", action="store_true", help="提权修复防火墙")''' MAIN_OLD = ''' if args.autostart: return cli_autostart(args.autostart) if args.status: return cli_status()''' MAIN_NEW = ''' if args.autostart: return cli_autostart(args.autostart) if args.status: return cli_status() # 这两个是给 .bat 调的:EXE 自带功能,不需要系统装 Python if args.netcheck is not None: return cli_netcheck(args.netcheck) if args.fix_firewall: return cli_fix_firewall()''' CLI_ANCHOR = '''def cli_status() -> int:''' CLI_NEW = '''def attach_console() -> None: """给 --windowed 程序自己开一个控制台窗口。 打包成 --windowed 后进程没有控制台,直接 print 什么都看不到。 AllocConsole 可以临时开一个,并把 stdout/stderr 接过去。 """ if os.name != "nt": return try: import ctypes ctypes.windll.kernel32.AllocConsole() sys.stdout = open("CONOUT$", "w", encoding="utf-8", buffering=1) sys.stderr = open("CONOUT$", "w", encoding="utf-8", buffering=1) try: sys.stdin = open("CONIN$", "r", encoding="utf-8") except OSError: pass except Exception: # noqa: BLE001 pass def wait_key() -> None: try: print() input("按回车键关闭这个窗口…") except Exception: # noqa: BLE001 pass def cli_netcheck(seconds: int) -> int: """网络自检:监听端口,看手表的包到底有没有到这台电脑。""" attach_console() try: from lanremote import netcheck return netcheck.run_check(int(seconds or 90)) except Exception as exc: # noqa: BLE001 print(f"自检运行失败: {exc}") return 1 finally: wait_key() def cli_fix_firewall() -> int: """提权修复防火墙(会弹一次 UAC,然后黑终端一闪)。""" store = Store(data_dir()) ok, detail = fix_firewall_with_flash(store) attach_console() print(("已修复:" if ok else "未完成:") + str(detail)) wait_key() return 0 if ok else 1 def cli_status() -> int:''' def main() -> int: # 1 text = HEADLESS.read_text(encoding="utf-8") if NOTIFY_OLD not in text: print(" [失败] headless notify 锚点没找到") return 1 HEADLESS.write_text(text.replace(NOTIFY_OLD, NOTIFY_NEW, 1), encoding="utf-8") print(" headless.notify: 已改成真的弹通知窗") # 2 text = RUN.read_text(encoding="utf-8") for old, new, tag in ((ARGS_OLD, ARGS_NEW, "--netcheck/--fix-firewall 参数"), (MAIN_OLD, MAIN_NEW, "main 分发"), (CLI_ANCHOR, CLI_NEW, "cli_netcheck/cli_fix_firewall")): if old not in text: print(f" [跳过] {tag} —— 锚点没找到") continue text = text.replace(old, new, 1) print(f" {tag}: OK") RUN.write_text(text, encoding="utf-8") import ast ok = True for label, path in (("headless.py", HEADLESS), ("run_agent.py", RUN), ("netcheck.py", ROOT / "lanremote" / "netcheck.py")): try: ast.parse(path.read_text(encoding="utf-8")) print(f" {label}: 语法 OK") except SyntaxError as exc: print(f" {label}: 语法错误 {exc}") ok = False return 0 if ok else 1 if __name__ == "__main__": sys.exit(main())