Skip to content

Commit 5208294

Browse files
committed
Add multi-monitor / virtual-desktop geometry
1 parent 073782f commit 5208294

15 files changed

Lines changed: 447 additions & 2 deletions

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/v136_features_doc.rst`](../docs/source/Zh/doc/new_features/v136_features_doc.rst)
6+
7+
- **`enumerate_monitors` + `Monitor` / `virtual_bounds` / `monitor_at_point` / `monitor_for_window` / `to_local` / `to_virtual` / `remap_point`**(`AC_enumerate_monitors``AC_monitor_at_point`):`snap_window` / `arrange_grid` / 版面规划器都假设单一主屏 `(width, height)`——对多显示器无感,无法在第二台显示器铺排或处理负原点虚拟桌面。本功能补上实体层:并集虚拟边界、某点 / 某窗口属于哪台显示器、虚拟↔显示器区域坐标转换,以及跨分辨率 / DPI 的等效位置重映射。对 `Monitor` dataclass 的纯几何 → 完全无头可测;`enumerate_monitors` 具可注入 provider(默认 `mss`)。
8+
39
## 本次更新 (2026-06-23) — 图像预处理(供 OCR / 模板匹配)
410

511
在识别或匹配前先清理画面。完整参考:[`docs/source/Zh/doc/new_features/v135_features_doc.rst`](../docs/source/Zh/doc/new_features/v135_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/v136_features_doc.rst`](../docs/source/Zh/doc/new_features/v136_features_doc.rst)
6+
7+
- **`enumerate_monitors` + `Monitor` / `virtual_bounds` / `monitor_at_point` / `monitor_for_window` / `to_local` / `to_virtual` / `remap_point`**(`AC_enumerate_monitors``AC_monitor_at_point`):`snap_window` / `arrange_grid` / 版面規劃器都假設單一主螢幕 `(width, height)`——對多螢幕無感,無法在第二台顯示器鋪排或處理負原點虛擬桌面。本功能補上實體層:聯集虛擬邊界、某點 / 某視窗屬於哪台螢幕、虛擬↔螢幕區域座標轉換,以及跨解析度 / DPI 的等效位置重映射。對 `Monitor` dataclass 的純幾何 → 完全無頭可測;`enumerate_monitors` 具可注入 provider(預設 `mss`)。
8+
39
## 本次更新 (2026-06-23) — 影像前處理(供 OCR / 模板比對)
410

511
在辨識或比對前先清理畫面。完整參考:[`docs/source/Zh/doc/new_features/v135_features_doc.rst`](../docs/source/Zh/doc/new_features/v135_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) — Multi-Monitor / Virtual-Desktop Geometry
4+
5+
Place windows and points correctly across several displays. Full reference: [`docs/source/Eng/doc/new_features/v136_features_doc.rst`](docs/source/Eng/doc/new_features/v136_features_doc.rst).
6+
7+
- **`enumerate_monitors` + `Monitor` / `virtual_bounds` / `monitor_at_point` / `monitor_for_window` / `to_local` / `to_virtual` / `remap_point`** (`AC_enumerate_monitors`, `AC_monitor_at_point`): `snap_window` / `arrange_grid` / the layout planner all assumed a single primary `(width, height)` — monitor-blind, unable to tile on a second display or handle a negative-origin virtual desktop. This adds the physical layer: union virtual bounds, which-monitor-owns-this-point/window, virtual↔monitor-local conversion, and equivalent-spot remapping across resolutions/DPI. Pure geometry over `Monitor` dataclasses → fully headless-testable; `enumerate_monitors` has an injectable provider (default `mss`).
8+
39
## What's new (2026-06-23) — Image Pre-processing for OCR / Template Matching
410

511
Clean up the screen before reading or matching it. Full reference: [`docs/source/Eng/doc/new_features/v135_features_doc.rst`](docs/source/Eng/doc/new_features/v135_features_doc.rst).
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
Multi-Monitor / Virtual-Desktop Geometry
2+
========================================
3+
4+
``snap_window``, ``arrange_grid`` and the layout planner all take a single primary
5+
``(width, height)`` — they are monitor-blind: they cannot tile on the second display
6+
or cope with a negative-origin virtual desktop, and ``coordinate_space`` only rescales
7+
a model grid. This adds the missing physical layer: enumerate the monitors, compute
8+
the union virtual bounds, ask which monitor contains a point or a window, convert
9+
between virtual and per-monitor-local coordinates, and remap a point to the equivalent
10+
spot on another display.
11+
12+
The geometry is pure arithmetic over plain ``Monitor`` dataclasses, so it is fully
13+
unit-testable; only ``enumerate_monitors``' default provider touches the OS (via
14+
``mss``) and it is injectable. Imports no ``PySide6``.
15+
16+
Headless API
17+
------------
18+
19+
.. code-block:: python
20+
21+
from je_auto_control import (enumerate_monitors, monitor_at_point,
22+
virtual_bounds, to_local, remap_point)
23+
24+
monitors = enumerate_monitors()
25+
print(virtual_bounds(monitors)) # (x, y, w, h) spanning all displays
26+
27+
here = monitor_at_point(monitors, x, y) # which monitor owns this point
28+
idx, lx, ly = to_local(monitors, x, y) # virtual -> (monitor, local x, local y)
29+
30+
# Move a point to the equivalent relative spot on another monitor.
31+
second = remap_point(monitors[0], monitors[1], 960, 540)
32+
33+
``Monitor`` carries ``index, x, y, width, height, scale, primary`` and a ``work``
34+
area (``.bounds`` / ``.contains(x, y)`` / ``.to_dict()``). ``virtual_bounds`` returns
35+
the union box (origin may be negative); ``primary_monitor`` picks the primary;
36+
``monitor_for_window(rect, monitors)`` returns the display a window mostly occupies
37+
(max overlap); ``to_virtual`` is the inverse of ``to_local``; ``remap_point``
38+
preserves the fractional position so it works across differing resolutions and DPI.
39+
40+
Executor commands
41+
-----------------
42+
43+
``AC_enumerate_monitors`` → ``{count, monitors, virtual_bounds}`` and
44+
``AC_monitor_at_point`` (``x`` / ``y``) → ``{found, monitor}``. They are exposed as
45+
the MCP tools ``ac_enumerate_monitors`` / ``ac_monitor_at_point`` and as Script
46+
Builder commands under **Window**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ Comprehensive guides for all AutoControl features.
158158
doc/new_features/v133_features_doc
159159
doc/new_features/v134_features_doc
160160
doc/new_features/v135_features_doc
161+
doc/new_features/v136_features_doc
161162
doc/ocr_backends/ocr_backends_doc
162163
doc/observability/observability_doc
163164
doc/operations_layer/operations_layer_doc
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
多螢幕 / 虛擬桌面幾何
2+
======================
3+
4+
``snap_window``、``arrange_grid`` 與版面規劃器都只取單一主螢幕 ``(width, height)``——它們對多螢幕無感:
5+
無法在第二台顯示器上鋪排、也無法處理負原點的虛擬桌面,而 ``coordinate_space`` 只縮放模型網格。本功能補上缺少
6+
的實體層:列舉各螢幕、計算聯集虛擬邊界、查詢某點或某視窗位於哪台螢幕、在虛擬座標與各螢幕區域座標間轉換,
7+
並把某點重映射到另一台螢幕上的等效位置。
8+
9+
幾何運算皆是對純 ``Monitor`` dataclass 的算術,因此完全可單元測試;只有 ``enumerate_monitors`` 的預設 provider
10+
會碰到 OS(透過 ``mss``),且可注入。不匯入 ``PySide6``。
11+
12+
無頭 API
13+
--------
14+
15+
.. code-block:: python
16+
17+
from je_auto_control import (enumerate_monitors, monitor_at_point,
18+
virtual_bounds, to_local, remap_point)
19+
20+
monitors = enumerate_monitors()
21+
print(virtual_bounds(monitors)) # 涵蓋所有顯示器的 (x, y, w, h)
22+
23+
here = monitor_at_point(monitors, x, y) # 此點屬於哪台螢幕
24+
idx, lx, ly = to_local(monitors, x, y) # 虛擬 -> (螢幕, 區域 x, 區域 y)
25+
26+
# 把某點移到另一台螢幕上的等效相對位置。
27+
second = remap_point(monitors[0], monitors[1], 960, 540)
28+
29+
``Monitor`` 帶有 ``index, x, y, width, height, scale, primary`` 與 ``work`` 區域(``.bounds`` /
30+
``.contains(x, y)`` / ``.to_dict()``)。``virtual_bounds`` 回傳聯集框(原點可能為負);``primary_monitor`` 取主螢幕;
31+
``monitor_for_window(rect, monitors)`` 回傳視窗主要佔據的顯示器(最大重疊);``to_virtual`` 是 ``to_local`` 的反向;
32+
``remap_point`` 保留分數位置,因此可跨不同解析度與 DPI 運作。
33+
34+
執行器命令
35+
----------
36+
37+
``AC_enumerate_monitors`` → ``{count, monitors, virtual_bounds}`` 與 ``AC_monitor_at_point``(``x`` / ``y``)→
38+
``{found, monitor}``。它們以 MCP 工具 ``ac_enumerate_monitors`` / ``ac_monitor_at_point`` 以及 Script Builder 中
39+
**Window** 分類下的命令提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ AutoControl 所有功能的完整使用指南。
158158
doc/new_features/v133_features_doc
159159
doc/new_features/v134_features_doc
160160
doc/new_features/v135_features_doc
161+
doc/new_features/v136_features_doc
161162
doc/ocr_backends/ocr_backends_doc
162163
doc/observability/observability_doc
163164
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,11 @@
303303
binarize, denoise, deskew, detect_skew_angle, enhance_contrast,
304304
preprocess_image, to_grayscale, upscale,
305305
)
306+
# Multi-monitor / virtual-desktop geometry (which monitor, where, remapping)
307+
from je_auto_control.utils.monitor_layout import (
308+
Monitor, enumerate_monitors, monitor_at_point, monitor_for_window,
309+
primary_monitor, remap_point, to_local, to_virtual, virtual_bounds,
310+
)
306311
# CI workflow annotations (GitHub Actions)
307312
from je_auto_control.utils.ci_annotations import (
308313
emit_annotations, format_annotation,
@@ -1119,6 +1124,15 @@ def start_autocontrol_gui(*args, **kwargs):
11191124
"deskew",
11201125
"detect_skew_angle",
11211126
"enhance_contrast",
1127+
"Monitor",
1128+
"enumerate_monitors",
1129+
"monitor_at_point",
1130+
"monitor_for_window",
1131+
"primary_monitor",
1132+
"remap_point",
1133+
"to_local",
1134+
"to_virtual",
1135+
"virtual_bounds",
11221136
"emit_annotations", "format_annotation",
11231137
"ClipboardHistory", "default_clipboard_history",
11241138
"analyze_heal_log", "heal_stats", "scan_secrets",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,18 @@ def _add_window_specs(specs: List[CommandSpec]) -> None:
622622
),
623623
description="Compute staggered, overlapping window rectangles.",
624624
))
625+
specs.append(CommandSpec(
626+
"AC_enumerate_monitors", "Window", "Enumerate Monitors",
627+
description="List monitors + virtual bounds (multi-display geometry).",
628+
))
629+
specs.append(CommandSpec(
630+
"AC_monitor_at_point", "Window", "Monitor at Point",
631+
fields=(
632+
FieldSpec("x", FieldType.INT),
633+
FieldSpec("y", FieldType.INT),
634+
),
635+
description="Report which monitor contains a virtual point.",
636+
))
625637

626638

627639
def _add_flow_specs(specs: List[CommandSpec]) -> None:

je_auto_control/utils/executor/action_executor.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3431,6 +3431,26 @@ def _preprocess_image(output_path: str, source: Any = None, steps: Any = None,
34313431
"height": int(result.shape[0])}
34323432

34333433

3434+
def _enumerate_monitors() -> Dict[str, Any]:
3435+
"""Adapter: list connected monitors with virtual-desktop geometry."""
3436+
from je_auto_control.utils.monitor_layout import (
3437+
enumerate_monitors, virtual_bounds)
3438+
monitors = enumerate_monitors()
3439+
bounds = virtual_bounds(monitors) if monitors else (0, 0, 0, 0)
3440+
return {"count": len(monitors),
3441+
"monitors": [monitor.to_dict() for monitor in monitors],
3442+
"virtual_bounds": list(bounds)}
3443+
3444+
3445+
def _monitor_at_point(x: Any, y: Any) -> Dict[str, Any]:
3446+
"""Adapter: report which monitor contains a virtual point."""
3447+
from je_auto_control.utils.monitor_layout import (
3448+
enumerate_monitors, monitor_at_point)
3449+
monitor = monitor_at_point(enumerate_monitors(), int(x), int(y))
3450+
return {"found": monitor is not None,
3451+
"monitor": monitor.to_dict() if monitor else None}
3452+
3453+
34343454
def _with_modifiers(modifiers: Any, actions: Any) -> Dict[str, Any]:
34353455
"""Adapter: run nested actions while modifier keys are held down."""
34363456
import json
@@ -5158,6 +5178,8 @@ def __init__(self):
51585178
"AC_find_shapes": _find_shapes,
51595179
"AC_find_rectangles": _find_rectangles,
51605180
"AC_preprocess_image": _preprocess_image,
5181+
"AC_enumerate_monitors": _enumerate_monitors,
5182+
"AC_monitor_at_point": _monitor_at_point,
51615183
"AC_tile_rect": _tile_rect,
51625184
"AC_grid_rects": _grid_rects,
51635185
"AC_cascade_rects": _cascade_rects,

0 commit comments

Comments
 (0)