把 _activate_and_maximize_windows 内嵌的 50+ 行 ctypes 实现抽到独立的 SDK 子模块 src/jiangchang_desktop_sdk/window_win32.py,暴露公开函数 activate_and_maximize_jiangchang_window,给 screencast 工具也复用(见后续 commit)。 client.py 的 _activate_and_maximize_windows 退化为 6 行薄壳,调用新模块。 设计: - window_win32 是 SDK 子模块(放 src/jiangchang_desktop_sdk/ 下),不放 tools/, 避免 SDK 反向依赖工具层 - ctypes EnumWindows + ShowWindow(SW_MAXIMIZE) + SetForegroundWindow 逻辑不变 - 跨平台行为不变:Windows 真执行,其他系统返回 False - bring_to_front 调用方完全不感知变化(向后兼容) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
86 lines
2.7 KiB
Python
86 lines
2.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
Win32 窗口工具:跨 SDK / screencast 共享的"激活并最大化匠厂主窗口"实现。
|
||
|
||
为什么单独抽出来:
|
||
- SDK 的 bring_to_front 需要 OS 级窗口前置(page.bring_to_front 只切 tab)
|
||
- screencast 的 run_screencast 录屏前也要把匠厂浮到前面并铺满屏幕
|
||
- 两者用同一份 EnumWindows + ShowWindow(SW_MAXIMIZE) + SetForegroundWindow 实现,
|
||
避免代码重复维护
|
||
|
||
跨平台行为:
|
||
- Windows: 真正执行
|
||
- 其他系统: 直接返回 False(调用方按需 fallback 到协议激活)
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import os
|
||
from typing import Optional, Tuple
|
||
|
||
_logger = logging.getLogger("jiangchang_desktop_sdk.window_win32")
|
||
|
||
_DEFAULT_KEYWORDS: Tuple[str, ...] = ("匠厂", "Jiangchang", "ClawX", "OpenClaw")
|
||
SW_MAXIMIZE = 3
|
||
|
||
|
||
def activate_and_maximize_jiangchang_window(
|
||
keywords: Optional[Tuple[str, ...]] = None,
|
||
) -> bool:
|
||
"""Windows: EnumWindows 找匠厂主窗口 → ShowWindow(SW_MAXIMIZE) + SetForegroundWindow。
|
||
|
||
Args:
|
||
keywords: 标题模糊匹配的关键词;不传则用默认
|
||
("匠厂", "Jiangchang", "ClawX", "OpenClaw")
|
||
|
||
Returns:
|
||
True: 找到窗口并执行了 ShowWindow + SetForegroundWindow
|
||
False: 非 Windows / ctypes 不可用 / 找不到匹配窗口 / 任何异常
|
||
"""
|
||
if os.name != "nt":
|
||
return False
|
||
|
||
try:
|
||
import ctypes
|
||
from ctypes import wintypes
|
||
except Exception as exc:
|
||
_logger.debug("ctypes 不可用:%s", exc)
|
||
return False
|
||
|
||
user32 = ctypes.windll.user32
|
||
kw = keywords or _DEFAULT_KEYWORDS
|
||
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(k in buf.value for k in kw):
|
||
matching.append((hwnd, buf.value))
|
||
return True
|
||
|
||
try:
|
||
user32.EnumWindows(_enum_proc, 0)
|
||
except Exception as exc:
|
||
_logger.debug("EnumWindows 失败:%s", exc)
|
||
return False
|
||
|
||
if not matching:
|
||
_logger.debug("未找到匠厂主窗口(keywords=%s)", kw)
|
||
return False
|
||
|
||
hwnd, title = matching[0]
|
||
try:
|
||
user32.ShowWindow(hwnd, SW_MAXIMIZE)
|
||
user32.SetForegroundWindow(hwnd)
|
||
_logger.debug("已激活+最大化 hwnd=%s title=%s", hwnd, title)
|
||
return True
|
||
except Exception as exc:
|
||
_logger.debug("ShowWindow/SetForegroundWindow 失败:%s", exc)
|
||
return False
|