Skip to content

Commit 6f06aa9

Browse files
authored
Merge pull request #361 from Integration-Automation/feat/window-geometry-batch
Add window client-area geometry (frame insets, client-relative point)
2 parents 79f3bc7 + e55f66c commit 6f06aa9

15 files changed

Lines changed: 309 additions & 1 deletion

File tree

README/WHATS_NEW_zh-CN.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-23) — 窗口客户区几何
4+
5+
不论标题栏 / 边框,点击窗口*内部*。完整参考:[`docs/source/Zh/doc/new_features/v150_features_doc.rst`](../docs/source/Zh/doc/new_features/v150_features_doc.rst)
6+
7+
- **`get_client_rect` / `client_point` / `frame_insets` / `client_to_screen`**(`AC_get_client_rect``AC_client_point`):`get_window_geometry` 只返回*外框*——没有客户区矩形、框边内缩运算或客户区→屏幕对应。`client_point("App", x, y)` 把内容相对点对应到屏幕,让点击不论外框都落在窗口内;`frame_insets` 报告边框 / 标题栏厚度。`frame_insets`/`client_to_screen` 是纯几何(可无头测试);`get_client_rect` 使用可注入的 Win32 读取器(`GetClientRect`+`ClientToScreen`)。
8+
39
## 本次更新 (2026-06-23) — 感知式(YIQ)图像比对含反锯齿抑制
410

511
会忽略反锯齿边缘的视觉回归比对。完整参考:[`docs/source/Zh/doc/new_features/v149_features_doc.rst`](../docs/source/Zh/doc/new_features/v149_features_doc.rst)

README/WHATS_NEW_zh-TW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-23) — 視窗客戶區幾何
4+
5+
不論標題列 / 邊框,點擊視窗*內部*。完整參考:[`docs/source/Zh/doc/new_features/v150_features_doc.rst`](../docs/source/Zh/doc/new_features/v150_features_doc.rst)
6+
7+
- **`get_client_rect` / `client_point` / `frame_insets` / `client_to_screen`**(`AC_get_client_rect``AC_client_point`):`get_window_geometry` 只回傳*外框*——沒有客戶區矩形、框邊內縮運算或客戶區→螢幕對應。`client_point("App", x, y)` 把內容相對點對應到螢幕,讓點擊不論外框都落在視窗內;`frame_insets` 回報邊框 / 標題列厚度。`frame_insets`/`client_to_screen` 是純幾何(可無頭測試);`get_client_rect` 使用可注入的 Win32 讀取器(`GetClientRect`+`ClientToScreen`)。
8+
39
## 本次更新 (2026-06-23) — 感知式(YIQ)影像比對含反鋸齒抑制
410

511
會忽略反鋸齒邊緣的視覺回歸比對。完整參考:[`docs/source/Zh/doc/new_features/v149_features_doc.rst`](../docs/source/Zh/doc/new_features/v149_features_doc.rst)

WHATS_NEW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# What's New — AutoControl
22

3+
## What's new (2026-06-23) — Window Client-Area Geometry
4+
5+
Click *inside* a window regardless of its title bar / borders. Full reference: [`docs/source/Eng/doc/new_features/v150_features_doc.rst`](docs/source/Eng/doc/new_features/v150_features_doc.rst).
6+
7+
- **`get_client_rect` / `client_point` / `frame_insets` / `client_to_screen`** (`AC_get_client_rect`, `AC_client_point`): `get_window_geometry` returns only the *outer* bbox — there was no client-area rect, frame-inset math, or client→screen mapping. `client_point("App", x, y)` maps a content-relative point to the screen so a click lands inside the window regardless of chrome; `frame_insets` reports border/title-bar thickness. `frame_insets`/`client_to_screen` are pure geometry (headless-testable); `get_client_rect` uses an injectable Win32 reader (`GetClientRect`+`ClientToScreen`).
8+
39
## What's new (2026-06-23) — Perceptual (YIQ) Image Diff with Anti-Alias Suppression
410

511
Visual-regression diffing that ignores anti-aliased edges. Full reference: [`docs/source/Eng/doc/new_features/v149_features_doc.rst`](docs/source/Eng/doc/new_features/v149_features_doc.rst).
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Window Client-Area Geometry
2+
===========================
3+
4+
``window_capture.get_window_geometry`` returns a window's *outer* bounding box (for
5+
screenshotting), but there is no *client*-area rect, no frame-inset math, and no
6+
client→screen point mapping. RPA needs "click at ``(x, y)`` *inside* this window's
7+
client area regardless of title-bar height / borders" — the building block for
8+
window-relative clicking. This adds the client rect, the pure frame-inset and
9+
client-to-screen helpers, and a one-call ``client_point``.
10+
11+
``frame_insets`` / ``client_to_screen`` are pure geometry (headless-testable); only
12+
``get_client_rect``'s default reader touches Win32 (``GetClientRect`` +
13+
``ClientToScreen``), and it is injectable. Imports no ``PySide6``.
14+
15+
Headless API
16+
------------
17+
18+
.. code-block:: python
19+
20+
from je_auto_control import (get_client_rect, client_point, frame_insets,
21+
client_to_screen)
22+
23+
# Click 20px in, 30px down from the window's content origin (not its title bar).
24+
point = client_point("Calculator", 20, 30)
25+
if point:
26+
click(*point)
27+
28+
rect = get_client_rect("Calculator") # (x, y, width, height)
29+
insets = frame_insets(get_window_geometry("Calculator"), rect) # border sizes
30+
31+
``get_client_rect`` returns the client area as ``(x, y, width, height)`` with a
32+
screen-coordinate origin (or ``None``); ``client_point`` maps a client-local point to
33+
the screen so a click lands inside the content regardless of chrome. ``frame_insets``
34+
returns the ``{left, top, right, bottom}`` border/title-bar thickness from the outer
35+
and client rects, and ``client_to_screen`` is the underlying pure offset.
36+
37+
Executor commands
38+
-----------------
39+
40+
``AC_get_client_rect`` (``title`` → ``{found, rect}``) and ``AC_client_point``
41+
(``title`` / ``x`` / ``y`` → ``{found, point}``). They are exposed as the MCP tools
42+
``ac_get_client_rect`` / ``ac_client_point`` and as Script Builder commands under
43+
**Window**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ Comprehensive guides for all AutoControl features.
172172
doc/new_features/v147_features_doc
173173
doc/new_features/v148_features_doc
174174
doc/new_features/v149_features_doc
175+
doc/new_features/v150_features_doc
175176
doc/ocr_backends/ocr_backends_doc
176177
doc/observability/observability_doc
177178
doc/operations_layer/operations_layer_doc
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
視窗客戶區幾何
2+
==============
3+
4+
``window_capture.get_window_geometry`` 回傳視窗的*外框*邊界框(供截圖),但沒有*客戶區*矩形、沒有框邊內縮運算、
5+
也沒有客戶區→螢幕的點對應。RPA 需要「不論標題列高度 / 邊框,在此視窗客戶區的 ``(x, y)`` 點擊」——這是視窗相對
6+
點擊的基礎。本功能加入客戶區矩形、純框邊內縮與客戶區轉螢幕輔助函式,以及一次呼叫的 ``client_point``。
7+
8+
``frame_insets`` / ``client_to_screen`` 是純幾何(可無頭測試);只有 ``get_client_rect`` 的預設讀取器觸及 Win32
9+
(``GetClientRect`` + ``ClientToScreen``),且可注入。不匯入 ``PySide6``。
10+
11+
無頭 API
12+
--------
13+
14+
.. code-block:: python
15+
16+
from je_auto_control import (get_client_rect, client_point, frame_insets,
17+
client_to_screen)
18+
19+
# 從視窗內容原點(非標題列)往內 20px、往下 30px 點擊。
20+
point = client_point("Calculator", 20, 30)
21+
if point:
22+
click(*point)
23+
24+
rect = get_client_rect("Calculator") # (x, y, width, height)
25+
insets = frame_insets(get_window_geometry("Calculator"), rect) # 邊框大小
26+
27+
``get_client_rect`` 以螢幕座標原點回傳客戶區的 ``(x, y, width, height)``(或 ``None``);``client_point`` 把客戶區
28+
區域內的點對應到螢幕,讓點擊不論視窗外框都落在內容上。``frame_insets`` 由外框與客戶區矩形回傳
29+
``{left, top, right, bottom}`` 邊框 / 標題列厚度,``client_to_screen`` 則是底層的純位移。
30+
31+
執行器命令
32+
----------
33+
34+
``AC_get_client_rect``(``title`` → ``{found, rect}``)與 ``AC_client_point``(``title`` / ``x`` / ``y`` →
35+
``{found, point}``)。它們以 MCP 工具 ``ac_get_client_rect`` / ``ac_client_point`` 以及 Script Builder 中 **Window**
36+
分類下的命令提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ AutoControl 所有功能的完整使用指南。
172172
doc/new_features/v147_features_doc
173173
doc/new_features/v148_features_doc
174174
doc/new_features/v149_features_doc
175+
doc/new_features/v150_features_doc
175176
doc/ocr_backends/ocr_backends_doc
176177
doc/observability/observability_doc
177178
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,10 @@
357357
from je_auto_control.utils.perceptual_diff import (
358358
PerceptualDiffResult, assert_perceptual, perceptual_diff,
359359
)
360+
# Window client-area geometry (frame insets, client-to-screen mapping)
361+
from je_auto_control.utils.window_geometry import (
362+
client_point, client_to_screen, frame_insets, get_client_rect,
363+
)
360364
# CI workflow annotations (GitHub Actions)
361365
from je_auto_control.utils.ci_annotations import (
362366
emit_annotations, format_annotation,
@@ -1227,6 +1231,10 @@ def start_autocontrol_gui(*args, **kwargs):
12271231
"perceptual_diff",
12281232
"assert_perceptual",
12291233
"PerceptualDiffResult",
1234+
"frame_insets",
1235+
"client_to_screen",
1236+
"get_client_rect",
1237+
"client_point",
12301238
"emit_annotations", "format_annotation",
12311239
"ClipboardHistory", "default_clipboard_history",
12321240
"analyze_heal_log", "heal_stats", "scan_secrets",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,20 @@ def _add_window_specs(specs: List[CommandSpec]) -> None:
707707
fields=(FieldSpec("title", FieldType.STRING),),
708708
description="Send a window to the bottom of the z-order.",
709709
))
710+
specs.append(CommandSpec(
711+
"AC_get_client_rect", "Window", "Get Client Rect",
712+
fields=(FieldSpec("title", FieldType.STRING),),
713+
description="A window's client-area rect (excludes title bar / borders).",
714+
))
715+
specs.append(CommandSpec(
716+
"AC_client_point", "Window", "Client-Relative Point",
717+
fields=(
718+
FieldSpec("title", FieldType.STRING),
719+
FieldSpec("x", FieldType.INT),
720+
FieldSpec("y", FieldType.INT),
721+
),
722+
description="Screen point for an (x, y) inside a window's client area.",
723+
))
710724
specs.append(CommandSpec(
711725
"AC_wait_window_closed", "Window", "Wait for Window to Close",
712726
fields=(

je_auto_control/utils/executor/action_executor.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3798,6 +3798,22 @@ def _perceptual_diff(actual: str, expected: str, threshold: Any = 0.1,
37983798
"diff_ratio": result.diff_ratio, "regions": result.regions}
37993799

38003800

3801+
def _get_client_rect(title: str) -> Dict[str, Any]:
3802+
"""Adapter: a window's client-area rect in screen coordinates."""
3803+
from je_auto_control.utils.window_geometry import get_client_rect
3804+
rect = get_client_rect(title)
3805+
return {"found": rect is not None,
3806+
"rect": list(rect) if rect is not None else None}
3807+
3808+
3809+
def _client_point(title: str, x: Any, y: Any) -> Dict[str, Any]:
3810+
"""Adapter: screen point for a client-area-local (x, y) inside a window."""
3811+
from je_auto_control.utils.window_geometry import client_point
3812+
point = client_point(title, int(x), int(y))
3813+
return {"found": point is not None,
3814+
"point": list(point) if point is not None else None}
3815+
3816+
38013817
def _with_modifiers(modifiers: Any, actions: Any) -> Dict[str, Any]:
38023818
"""Adapter: run nested actions while modifier keys are held down."""
38033819
import json
@@ -5550,6 +5566,8 @@ def __init__(self):
55505566
"AC_send_to_back": _send_to_back,
55515567
"AC_soft_assert": _soft_assert,
55525568
"AC_perceptual_diff": _perceptual_diff,
5569+
"AC_get_client_rect": _get_client_rect,
5570+
"AC_client_point": _client_point,
55535571
"AC_tile_rect": _tile_rect,
55545572
"AC_grid_rects": _grid_rects,
55555573
"AC_cascade_rects": _cascade_rects,

0 commit comments

Comments
 (0)