# -*- coding: utf-8 -*- """修三组问题。 【A】toggle 永远往同一个方向走(一个 bug 解释两条报障) 手表点开关时用的是**缓存里的旧状态**取反: boolean on = Json.bool(feature, "on", false); runFeature(name, Json.obj("on", !on), ...); 翻转成功后缓存没更新,于是: 任务栏:初始 false -> 发 true -> 隐藏。缓存还是 false -> 再点还是发 true -> 永远隐藏,恢复不了 开机自启:初始 true -> 发 false -> 关闭。缓存还是 true -> 再点还是发 false -> 永远打不开 修法:翻转成功后把缓存里的 on 一起改掉。 【B】_taskbar_hidden 读不准 用字符串 "03,00,00,00" 去匹配 reg query 的输出, 大小写和位置都不稳,状态永远读成 False。 改成把 REG_BINARY 解析成字节数组,看下标 8 的最低位。 【C】手表开的画面关不掉 close_windows 是关**别的程序**的窗口; 真正常关闭全屏的 big_text_clear 藏在「教学」组里、名字也不直观, 而且管不到投图 / 通知 / 屏幕共享提示。 新增一个「关掉电脑上的画面」,把所有本程序开的可见东西一次收掉, 并放到「屏幕」页最上面 —— 那是用户最先看到的地方。 """ from __future__ import annotations import sys from pathlib import Path ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") MAIN = ROOT / "android" / "src" / "com" / "lanremote" / "watch" / "MainActivity.java" FEATURES = ROOT / "windows" / "lanremote" / "features.py" HEADLESS = ROOT / "windows" / "lanremote" / "headless.py" # ---------------- A. 手表 toggle 缓存 ---------------- A_OLD = ''' if ("toggle".equals(kind)) { boolean on = Json.bool(feature, "on", false); runFeature(name, Json.obj("on", !on), (on ? "关闭 " : "开启 ") + label); return; }''' A_NEW = ''' if ("toggle".equals(kind)) { final boolean on = Json.bool(feature, "on", false); final boolean next = !on; runFeature(name, Json.obj("on", next), (on ? "关闭 " : "开启 ") + label); // 关键:本地缓存必须跟着翻转。 // 不翻转的话,下次点还是拿旧状态取反,永远往同一个方向走 —— // 表现就是"能隐藏但恢复不了"、"显示已关闭但打不开"。 feature.put("on", Boolean.valueOf(next)); return; }''' # ---------------- B. 任务栏状态读取 ---------------- B_OLD = ''' def _taskbar_hidden(self) -> bool: code, out = _run(["reg", "query", r"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects3", "/v", "Settings"]) if code != 0 or not out: return False return " 03 00 00 00 " in out.lower() or "03,00,00,00" in out''' B_NEW = ''' def _taskbar_hidden(self) -> bool: """任务栏是不是自动隐藏。 reg query 把 REG_BINARY 输出成逗号分隔的十六进制: Settings REG_BINARY 30,00,00,00,00,00,00,00,03,00,00,00,... 下标 8 那个字节的最低位就是「自动隐藏」开关。 以前是拿字符串 "03,00,00,00" 去整段匹配 —— 大小写和位置都不稳, 状态永远读成 False,于是手表每次点都发"隐藏", 表现成"能隐藏但恢复不了"。这里老老实实按字节解析。 """ code, out = _run(["reg", "query", r"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion" r"\\Explorer\\StuckRects3", "/v", "Settings"]) if code != 0 or not out: return False match = re.search(r"REG_BINARY\\s+([0-9a-fA-F,\\s]+)", out) if not match: return False values = [v for v in re.split(r"[,\\s]+", match.group(1).strip()) if v] if len(values) <= 8: return False try: return bool(int(values[8], 16) & 0x01) except ValueError: return False''' # ---------------- C. 关掉电脑上的画面 ---------------- C_DEFS_OLD = ''' add("big_text_clear", "关掉全屏内容", "action", "教学")''' C_DEFS_NEW = ''' add("big_text_clear", "关掉全屏内容", "action", "教学") add("close_visuals", "关掉电脑上的画面", "action", "显示")''' C_IMPL_OLD = ''' def _do_big_text_clear(self, params) -> str: closed = self._close_fullscreen_windows() return f"已关掉 {closed} 个全屏窗口" if closed else "当前没有全屏窗口"''' C_IMPL_NEW = ''' def _do_big_text_clear(self, params) -> str: closed = self._close_fullscreen_windows() return f"已关掉 {closed} 个全屏窗口" if closed else "当前没有全屏窗口" def _do_close_visuals(self, params) -> str: """把本程序在电脑上开出来的画面全部收掉。 覆盖:全屏大字 / 倒计时 / 请安静 / 下课铃 / 投图 / 通知 / 屏幕共享提示。 手表能打开的东西,必须也能关掉。 """ done = [] closed = self._close_fullscreen_windows() if closed: done.append(f"全屏窗口 {closed} 个") for label, method in (("投图", "close_image"), ("通知", "close_notifications")): func = getattr(self.actions.ui, method, None) if func is None: continue try: if func(): done.append(label) except Exception: # noqa: BLE001 pass # 屏幕镜像提示条也一起收掉(镜像本身由手表那个开关控制) try: hide = getattr(self.actions.ui, "_hide_notice", None) if hide is not None and getattr(self.actions.ui, "_notice", None): hide() done.append("共享提示") except Exception: # noqa: BLE001 pass return "已关掉:" + "、".join(done) if done else "电脑上现在没有本程序开的画面" def _do_quit_agent(self, params) -> str: """退出被控端(延迟一点点,好让回复先发出去)。""" import threading import time as _t def die(): _t.sleep(1.0) os._exit(0) threading.Thread(target=die, daemon=True, name="quit").start() return "被控端正在退出"''' # ---------------- C2. headless 支持关闭通知 ---------------- C2_OLD = ''' try: self._queue.put(build) except Exception: # noqa: BLE001 pass''' C2_NEW = ''' self._notices.append(None) # 先占位,build 里回填 index = len(self._notices) - 1 def build(): try: win = tk.Toplevel(root) win.title(f"通知 - {title}") win.overrideredirect(True) win.attributes("-topmost", True) win.configure(bg="#ffffff") self._notices[index] = win 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 try: self._notices[index] = None except (IndexError, AttributeError): 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 def close_notifications(self) -> bool: """关掉所有还在显示的通知窗。返回是否关掉了至少一个。""" handled = False for index, win in enumerate(list(getattr(self, "_notices", []))): if win is None: continue handled = True def build(target=win, slot=index): try: target.destroy() except Exception: # noqa: BLE001 pass try: self._notices[slot] = None except (IndexError, AttributeError): pass try: self._queue.put(build) except Exception: # noqa: BLE001 pass return handled''' def main() -> int: ok = True # A text = MAIN.read_text(encoding="utf-8") if A_OLD not in text: print(" [失败] A: 手表 toggle 锚点没找到") ok = False else: MAIN.write_text(text.replace(A_OLD, A_NEW, 1), encoding="utf-8") print(" A: 手表 toggle 缓存现在会翻转") # B + C text = FEATURES.read_text(encoding="utf-8") for old, new, tag in ((B_OLD, B_NEW, "B: _taskbar_hidden 按字节解析"), (C_DEFS_OLD, C_DEFS_NEW, "C: 新功能声明"), (C_IMPL_OLD, C_IMPL_NEW, "C: close_visuals + quit_agent")): if old not in text: print(f" [失败] {tag} —— 锚点没找到") ok = False continue text = text.replace(old, new, 1) print(f" {tag}") FEATURES.write_text(text, encoding="utf-8") # C2 text = HEADLESS.read_text(encoding="utf-8") if C2_OLD not in text: print(" [失败] C2: headless notify 锚点没找到") ok = False else: HEADLESS.write_text(text.replace(C2_OLD, C2_NEW, 1), encoding="utf-8") print(" C2: headless 支持关闭通知") # _notices 初始化 text = HEADLESS.read_text(encoding="utf-8") if "self._notices = []" not in text: old = " self._notice = None" if old in text: text = text.replace(old, " self._notices = []\n" + old, 1) HEADLESS.write_text(text, encoding="utf-8") print(" C2b: 加了 _notices 列表") else: print(" [跳过] C2b: 找不到 _notice 初始化位置(放到 __init__ 末尾)") # 退而求其次:用 getattr 兜底,close_notifications 里已经用了 else: print(" C2b: _notices 已存在") import ast for label, path in (("features.py", FEATURES), ("headless.py", HEADLESS)): 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())