feat(sdk): bring_to_front 用 Win32 API 激活并最大化主窗口
新增 _activate_and_maximize_windows 私有方法:用 ctypes 调 EnumWindows 找匠厂主窗口(标题含 匠厂/Jiangchang/ClawX/OpenClaw), ShowWindow(SW_MAXIMIZE) + SetForegroundWindow 让窗口铺满桌面。 bring_to_front 改 OS 分支: - Windows: 优先 ctypes 激活+最大化(dev / 安装版通吃,比 jiangchang:// 协议在 dev 模式下因 cwd 解析失败而报错可靠);失败时退回协议作为兜底 - macOS / Linux: 维持原协议激活路径(open / xdg-open) 适用场景:录屏 / 自动化测试需要 OS 级窗口前置。page.bring_to_front() 只切 Playwright tab 不影响 z-order,必须 OS 级 API 才能把 Electron 主窗口拉到桌面最前。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -218,6 +218,65 @@ class JiangchangDesktopClient:
|
||||
_logger.warning("[activate] 协议激活失败:%s", exc)
|
||||
return False
|
||||
|
||||
def _activate_and_maximize_windows(self) -> bool:
|
||||
"""Windows 专属:用 Win32 API 把主窗口拉到桌面最前并最大化。
|
||||
|
||||
EnumWindows 遍历所有顶层窗口,按标题模糊匹配(含"匠厂/Jiangchang/ClawX/OpenClaw"),
|
||||
找到后 ShowWindow(SW_MAXIMIZE) + SetForegroundWindow,让录屏看到铺满桌面的匠厂,
|
||||
避免任务栏边角和其他应用露出来。
|
||||
|
||||
为什么改用 ctypes(替代上一版 PowerShell AppActivate):
|
||||
1) AppActivate 只激活不最大化,依然能录到桌面边缘其他窗口;
|
||||
2) ctypes 不 fork powershell,启动更快(~0ms vs ~300-800ms);
|
||||
3) 直接拿到 hwnd 后续可扩展(如监控窗口尺寸)。
|
||||
"""
|
||||
if os.name != "nt":
|
||||
return False
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
except Exception as exc:
|
||||
_logger.debug("[bring_to_front] ctypes 不可用:%s", exc)
|
||||
return False
|
||||
|
||||
user32 = ctypes.windll.user32
|
||||
SW_MAXIMIZE = 3
|
||||
keywords = ("匠厂", "Jiangchang", "ClawX", "OpenClaw")
|
||||
matching: list = []
|
||||
|
||||
@ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM)
|
||||
def _enum_proc(hwnd, _lparam):
|
||||
if not user32.IsWindowVisible(hwnd):
|
||||
return True
|
||||
length = user32.GetWindowTextLengthW(hwnd)
|
||||
if length == 0:
|
||||
return True
|
||||
buf = ctypes.create_unicode_buffer(length + 1)
|
||||
user32.GetWindowTextW(hwnd, buf, length + 1)
|
||||
if any(kw in buf.value for kw in keywords):
|
||||
matching.append((hwnd, buf.value))
|
||||
return True
|
||||
|
||||
try:
|
||||
user32.EnumWindows(_enum_proc, 0)
|
||||
except Exception as exc:
|
||||
_logger.debug("[bring_to_front] EnumWindows 失败:%s", exc)
|
||||
return False
|
||||
|
||||
if not matching:
|
||||
_logger.debug("[bring_to_front] 未找到匠厂主窗口")
|
||||
return False
|
||||
|
||||
hwnd, title = matching[0]
|
||||
try:
|
||||
user32.ShowWindow(hwnd, SW_MAXIMIZE)
|
||||
user32.SetForegroundWindow(hwnd)
|
||||
_logger.debug("[bring_to_front] 已激活+最大化 hwnd=%s title=%s", hwnd, title)
|
||||
return True
|
||||
except Exception as exc:
|
||||
_logger.debug("[bring_to_front] ShowWindow/SetForegroundWindow 失败:%s", exc)
|
||||
return False
|
||||
|
||||
def _launch_via_executable(self, cdp_port: int) -> bool:
|
||||
"""回退:直接 spawn 可执行文件,带 --remote-debugging-port。"""
|
||||
exe = self._get_default_executable_path()
|
||||
@@ -270,12 +329,38 @@ class JiangchangDesktopClient:
|
||||
)
|
||||
|
||||
def bring_to_front(self) -> None:
|
||||
"""把主窗口聚焦到最前。"""
|
||||
"""把主窗口聚焦到桌面最前。
|
||||
|
||||
分两层:
|
||||
1) page.bring_to_front() —— Playwright 把 page 切到当前 context 内的最前
|
||||
tab。**不影响 OS 级窗口 z-order**。
|
||||
2) OS 级窗口前置(录屏 / 自动化必需):
|
||||
- Windows: 用 ctypes 调 Win32 EnumWindows 找匠厂主窗口 →
|
||||
ShowWindow(SW_MAXIMIZE) + SetForegroundWindow,让窗口铺满桌面
|
||||
(详见 _activate_and_maximize_windows)。失败时退回协议激活作为
|
||||
兜底,但 dev 模式(pnpm dev / electron .)下协议本身会因 cwd 解析
|
||||
失败而无效,只有安装版协议兜底才可能成功。
|
||||
- macOS / Linux: 走 jiangchang:// 协议(open / xdg-open,
|
||||
非 Windows 系统下协议处理器没有 cwd 解析问题)。
|
||||
"""
|
||||
try:
|
||||
page = self.get_page()
|
||||
page.bring_to_front()
|
||||
except Exception as exc:
|
||||
_logger.debug("[bring_to_front] 失败:%s", exc)
|
||||
_logger.debug("[bring_to_front] Playwright 切 page 失败:%s", exc)
|
||||
|
||||
if os.name == "nt":
|
||||
if self._activate_and_maximize_windows():
|
||||
return
|
||||
try:
|
||||
self._activate_via_protocol()
|
||||
except Exception as exc:
|
||||
_logger.debug("[bring_to_front] 协议激活失败:%s", exc)
|
||||
else:
|
||||
try:
|
||||
self._activate_via_protocol()
|
||||
except Exception as exc:
|
||||
_logger.debug("[bring_to_front] 协议激活失败:%s", exc)
|
||||
|
||||
# ─── 连接 / 断开 ──────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user