VirtualDesktop 1.3.1__tar.gz → 1.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/PKG-INFO +106 -13
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/README.md +104 -12
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/__init__.py +135 -135
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_desktop_runtime.py +88 -0
- virtualdesktop-1.4.1/VirtualDesktop/_enumerate.py +97 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/agent.py +45 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/desktop_session.py +230 -20
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/workspace.py +45 -1
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop.egg-info/PKG-INFO +106 -13
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop.egg-info/SOURCES.txt +1 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop.egg-info/requires.txt +1 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/pyproject.toml +69 -64
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_packaging.py +3 -2
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/LICENSE +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/__main__.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_bundle.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_compat.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_devices.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_download.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_helper.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_log.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_process.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_sigcheck.ps1 +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_sigcheck.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/_winapi.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/capture.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/cli.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/config.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/desktop.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/desktop_capture.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/display_setup.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/driver.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/driver_manifest.json +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/errors.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/manifest.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/session.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop/window.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop.egg-info/dependency_links.txt +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop.egg-info/entry_points.txt +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/VirtualDesktop.egg-info/top_level.txt +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/setup.cfg +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_accelerators.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_agent.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_artifact_regression.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_capture.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_cli.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_compat.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_config_and_winapi.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_desktop_isolation.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_destructive_lifecycle.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_display_setup.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_download.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_driver.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_hide_modes.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_integration_windows.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_manifest.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_no_dialogs.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_process.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_virtual_desktop.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_workspace.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_workspace_instances.py +0 -0
- {virtualdesktop-1.3.1 → virtualdesktop-1.4.1}/tests/test_workspace_move.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: VirtualDesktop
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: Multi-window virtual-display workspaces on Windows: one pip install bootstraps the virtual display driver, hosts programs on a virtual or private desktop, keeps them off the physical screens, and hands out real window handles for automation and PrintWindow capture.
|
|
5
5
|
Author: VirtualDesktop contributors
|
|
6
6
|
License: MIT License
|
|
@@ -47,6 +47,7 @@ Requires-Python: >=3.9
|
|
|
47
47
|
Description-Content-Type: text/markdown
|
|
48
48
|
License-File: LICENSE
|
|
49
49
|
Requires-Dist: Pillow>=9.0
|
|
50
|
+
Requires-Dist: comtypes>=1.2.0
|
|
50
51
|
Provides-Extra: test
|
|
51
52
|
Requires-Dist: pytest>=7.0; extra == "test"
|
|
52
53
|
Provides-Extra: build
|
|
@@ -78,20 +79,59 @@ pip install VirtualDesktop # pip
|
|
|
78
79
|
uv add VirtualDesktop # uv(写入 pyproject.toml 并锁定)
|
|
79
80
|
```
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
运行时依赖:**Pillow**(帧图像)与 **comtypes**(UI Automation 是 COM API)。不需要 pywin32——
|
|
83
|
+
库访问 Win32 全部走 ctypes。若国内镜像同步滞后导致找不到最新版,加
|
|
82
84
|
`--default-index https://pypi.org/simple`(uv)或 `-i https://pypi.org/simple`(pip)。
|
|
83
85
|
|
|
84
86
|
| | 私有桌面 | 虚拟显示器 |
|
|
85
87
|
|---|---|---|
|
|
86
88
|
| 一句话 | 另开一个桌面,窗口与你的桌面完全隔离 | 多一块屏幕,窗口仍在你的桌面上、只是画在看不见的地方 |
|
|
87
|
-
|
|
|
89
|
+
| 底层 | 内核**对象**(`CreateDesktopW`),独立窗口命名空间 + DACL | 显示**设备**(IddCx 驱动),系统多出一块 `\\.\DISPLAYn` |
|
|
88
90
|
| 需要权限 | **零**:不需要驱动、不弹 UAC | 装一次驱动,弹**一次 UAC** |
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
91
|
+
| 系统要求 | Win10 / 11 任意 | Win10 1809 (17763) 起 |
|
|
92
|
+
| 程序窗口截图 | **不能** | **能** |
|
|
93
|
+
| 桌面截图 | **不能** | **能** |
|
|
94
|
+
| 读窗口属性 / 枚举子控件 | 能(桌面内代理) | 能(外部进程直接) |
|
|
95
|
+
| 点击控件 / 送入字符 | 能(窗口消息) | 能(消息 + 真实输入) |
|
|
96
|
+
| 真实鼠标 / 滚轮 / 键盘 | **不能** | **能** |
|
|
97
|
+
| 外部 pywinauto / pyautogui 直接驱动 | **不能** | **能** |
|
|
98
|
+
| 窗口跨桌面搬运 | **不能**(Windows 限制) | 能(在显示器之间移动) |
|
|
99
|
+
| 推荐场景 | 多实例并行、强隔离、不要截图 | UI 自动化、截图 OCR、无人值守抓界面 |
|
|
92
100
|
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
### 隔离强度分级
|
|
102
|
+
|
|
103
|
+
两套方案的隔离**不是"强/弱"两档,而是三个层面各不相同**,按你需要防住什么来选:
|
|
104
|
+
|
|
105
|
+
| 隔离层面 | 私有桌面 | 虚拟显示器 | 说明 |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| 窗口**发现**(能否枚举到) | **隔离** | 不隔离 | 私有桌面:`EnumWindows`/`FindWindow` 从外部看不到 |
|
|
108
|
+
| 窗口**输入**(真实鼠标键盘) | **隔离** | 不隔离 | 私有桌面:没有活动指针与键盘焦点 |
|
|
109
|
+
| 窗口**截图**(像素可读性) | **隔离** | 不隔离 | 私有桌面:从不参与系统合成,没有像素 |
|
|
110
|
+
| 窗口**消息**(`SendMessage` 等) | **不隔离** | 不隔离 | 拿到句柄即可投递消息,DACL 保护的是桌面对象而不是窗口消息 |
|
|
111
|
+
| 进程**生命周期** | **隔离** | 不隔离 | 私有桌面关闭时其上进程一并结束;虚拟显示器上的程序由你负责 |
|
|
112
|
+
|
|
113
|
+
> 关键提醒:私有桌面**不等于**"完全无法访问"。它挡的是**发现**、**输入**和**截图**,
|
|
114
|
+
> 挡不住"已知句柄后投递消息"。所以不要把私有桌面当作进程级安全沙箱使用——它没有降低
|
|
115
|
+
> 目标进程的权限,只是把它挪到了另一个窗口命名空间。
|
|
116
|
+
|
|
117
|
+
### 推荐怎么选
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
要截图(窗口截图或桌面截图) → 虚拟显示器
|
|
121
|
+
要用真实鼠标 / 滚轮 / 键盘,或 pyautogui → 虚拟显示器
|
|
122
|
+
要用 pywinauto / UIA 从外部直接驱动窗口 → 虚拟显示器
|
|
123
|
+
只处理一个窗口、要截图,越省事越好 → OffscreenSession(虚拟显示器侧)
|
|
124
|
+
─────────────────────────────────────────────────────────────
|
|
125
|
+
要多个实例互不干扰、且不需要截图 → 私有桌面
|
|
126
|
+
不能弹 UAC、不能装驱动 → 私有桌面
|
|
127
|
+
要"退出后什么都不留"(进程随桌面一起结束) → 私有桌面
|
|
128
|
+
需要窗口完全不出现在任何可见位置 → 私有桌面
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
两条常用组合:
|
|
132
|
+
|
|
133
|
+
- **只是想安静地跑一个窗口并抓图** → `OffscreenSession` + `virtual_screen`
|
|
134
|
+
- **想并行跑很多互不干扰的实例** → 每个实例一个 `VirtualWorkspace("名字")`
|
|
95
135
|
|
|
96
136
|
---
|
|
97
137
|
|
|
@@ -107,7 +147,7 @@ uv add VirtualDesktop # uv(写入 pyproject.toml 并锁定)
|
|
|
107
147
|
| 系统 | Windows 10 / 11(无 IddCx 版本要求) |
|
|
108
148
|
| 权限 | 当前用户即可,**不提权** |
|
|
109
149
|
| 驱动 | 不需要 |
|
|
110
|
-
| 依赖 |
|
|
150
|
+
| 依赖 | Pillow + comtypes |
|
|
111
151
|
|
|
112
152
|
## 基本用法
|
|
113
153
|
|
|
@@ -169,13 +209,14 @@ with VirtualWorkspace("lab") as space, space.agent() as agent:
|
|
|
169
209
|
| `agent.adopt(pid)` | 接上桌面内已在运行的程序 |
|
|
170
210
|
| `agent.info(window)` | 标题、类名、矩形、`child_count`、`buttons` |
|
|
171
211
|
| `agent.text(window)` | 窗口标题 |
|
|
212
|
+
| `agent.uia_tree(window)` | 完整 UIA 树(在桌面内遍历) |
|
|
172
213
|
| `agent.click(window, text=/index=)` | 点击控件(走窗口消息,见下) |
|
|
173
214
|
| `agent.keys(window, "...")` | 送入字符(走窗口消息) |
|
|
174
215
|
| `agent.windows()` | 该桌面上的窗口清单 |
|
|
175
216
|
| `agent.exec("python 代码")` | 在桌面内跑任意 Python,返回打印内容 |
|
|
176
217
|
|
|
177
218
|
`exec` 是逃生舱:里面的代码能 `import VirtualDesktop`、能用 `list_windows()`、`CaptureEngine`,
|
|
178
|
-
也能用你自己装的任何库。代理本身只依赖 ctypes
|
|
219
|
+
也能用你自己装的任何库。代理本身只依赖 ctypes 与 comtypes。
|
|
179
220
|
|
|
180
221
|
### 私有桌面不能截图
|
|
181
222
|
|
|
@@ -210,6 +251,38 @@ with VirtualWorkspace("lab") as space, space.agent() as agent:
|
|
|
210
251
|
要点:**能用消息驱动的都能用,需要真实输入设备的都不能用,两种截图都不能用**。需要截图或需要
|
|
211
252
|
真实鼠标/滚轮的应用,请改用下面的虚拟显示器方案。
|
|
212
253
|
|
|
254
|
+
## 支持的能力与调用方式
|
|
255
|
+
|
|
256
|
+
| 能力 | 是否支持 | 怎么调 |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| 在一个桌面里跑多个程序 | **支持** | 连续 `agent.launch([...])`,各自拿到不同句柄 |
|
|
259
|
+
| 多个独立桌面同时在线 | **支持** | 多个 `VirtualWorkspace("名字")` 并存,句柄与名字互不相同 |
|
|
260
|
+
| 拿到窗口句柄 | **支持** | `agent.launch([...]).hwnd` / `agent.adopt(pid).hwnd` / `desktop.find_window(pid)` |
|
|
261
|
+
| 读标题 / 类名 / 矩形 / 尺寸 | **支持** | `agent.info(window)` |
|
|
262
|
+
| 枚举子控件与按钮文字 | **支持** | `agent.info(window)["child_count"]` / `["buttons"]` |
|
|
263
|
+
| 完整 UIA 树 | **支持** | `agent.uia_tree(window)` |
|
|
264
|
+
| 点击控件 / 送入字符 | **支持** | `agent.click(window, text=...)` / `agent.keys(window, "...")` |
|
|
265
|
+
| 在桌面内跑任意 Python | **支持** | `agent.exec("...")`(可用 comtypes、pywinauto) |
|
|
266
|
+
| 程序窗口截图 / 桌面截图 | **不支持** | 见上方原因;改用虚拟显示器 |
|
|
267
|
+
| 跨桌面搬运窗口、移到主屏 | **不支持** | `move_to` 一律报 `cannot-cross-desktop`;只能在目标桌面重新 `launch` |
|
|
268
|
+
|
|
269
|
+
`agent.uia_tree()` 在**桌面内**遍历 UIA,这是私有桌面唯一可用的取树方式:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
with VirtualWorkspace("lab") as space, space.agent() as agent:
|
|
273
|
+
window = agent.launch(["charmap.exe"])
|
|
274
|
+
tree = agent.uia_tree(window) # view="RawView" 默认;也可 ControlView / ContentView
|
|
275
|
+
print(tree["count"])
|
|
276
|
+
for node in tree["nodes"][:5]:
|
|
277
|
+
print(node["control"], node["name"], node["enabled"])
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
> 不要用 `GetRootElement()`:它属于**输入桌面**,在私有桌面里走它拿不到任何目标窗口。
|
|
281
|
+
> 库内部用的是 `ElementFromHandle(hwnd)`。
|
|
282
|
+
|
|
283
|
+
> 在里面用 comtypes / pywinauto 是安全的:窗口枚举已不再绑定调用者线程,所以
|
|
284
|
+
> `from pywinauto import Application` 放在文件顶部也不会影响私有桌面。
|
|
285
|
+
|
|
213
286
|
---
|
|
214
287
|
|
|
215
288
|
# 第二部分:虚拟显示器
|
|
@@ -225,7 +298,7 @@ with VirtualWorkspace("lab") as space, space.agent() as agent:
|
|
|
225
298
|
| 系统 | **Windows 10 1809 (build 17763) 或更高**(更低版本没有 IddCx 框架) |
|
|
226
299
|
| 权限 | 安装/卸载驱动需要管理员,弹**一次 UAC** |
|
|
227
300
|
| 驱动 | 首次使用时自动下载安装(也可预先装好) |
|
|
228
|
-
| 依赖 |
|
|
301
|
+
| 依赖 | Pillow + comtypes |
|
|
229
302
|
|
|
230
303
|
## 装驱动
|
|
231
304
|
|
|
@@ -424,6 +497,26 @@ print(current_primary_size()) # (1920, 1080)
|
|
|
424
497
|
> 用真实输入时,窗口所在显示器必须是活动桌面的一部分;`pyautogui` 这类库还需要窗口能取得前台
|
|
425
498
|
> 焦点。若目标窗口拿不到焦点,请改用消息驱动(`agent.click()` / `WM_CHAR`)。
|
|
426
499
|
|
|
500
|
+
## 支持的能力与调用方式
|
|
501
|
+
|
|
502
|
+
| 能力 | 是否支持 | 怎么调 |
|
|
503
|
+
|---|---|---|
|
|
504
|
+
| 在一个工作区里跑多个程序 | **支持** | 连续 `ws.launch([...])`,运行中可随时新增 |
|
|
505
|
+
| 多个工作区同时在线 | **支持** | 多个 `VirtualWorkspace(SHARED)` 并存,各自独立停靠位 |
|
|
506
|
+
| 拿到窗口句柄给外部用 | **支持** | `ws.describe()["windows"]`(真实 HWND,无包装) |
|
|
507
|
+
| 读标题 / 类名 / 矩形 | **支持** | 外部 pywinauto 直接读,或 `ws.windows()` |
|
|
508
|
+
| 枚举子控件 | **支持** | 外部 pywinauto `children(visible_only=False)` |
|
|
509
|
+
| 完整 UIA 树 | **支持** | 外部 UIA,或 `--ui-tree DIR` |
|
|
510
|
+
| 程序窗口截图 | **支持** | `ws.capture(hwnd, save_to=...)` / `CaptureEngine.capture(hwnd)` |
|
|
511
|
+
| 桌面截图 | **支持**(`virtual_screen`) | `ws.capture_desktop()` / `capture_monitor()` / `capture_all_screens()` |
|
|
512
|
+
| 真实鼠标 / 滚轮 / 键盘 | **支持**(`virtual_screen`) | pywinauto `click_input()` / `type_keys()`,或 pyautogui |
|
|
513
|
+
| 在显示器之间移动窗口 | **支持** | `move_to("\\\\.\\DISPLAY2")` / `move_all_to(...)` / `move_to_primary()` |
|
|
514
|
+
| 退出时还原窗口 | **支持** | `with` 退出自动还原;异常退出同样还原 |
|
|
515
|
+
| 退出时卸载驱动 | **支持** | `uninstall_on_exit=True` 或 `close(uninstall=True)` |
|
|
516
|
+
| 桌面截图(`offscreen` 模式) | **不支持** | 窗口在所有显示器之外,拍不到;改用 `virtual_screen` |
|
|
517
|
+
| 真实鼠标 / 滚轮 / 键盘(`offscreen`) | **不支持** | 同上:指针被钳制回屏幕边缘 |
|
|
518
|
+
| 跨私有桌面搬运窗口 | **不支持** | Windows 不允许;`move_to` 会明确报错 |
|
|
519
|
+
|
|
427
520
|
---
|
|
428
521
|
|
|
429
522
|
# 通用功能
|
|
@@ -464,7 +557,8 @@ VIRTUALDESKTOP_LOG_FILE=<path>
|
|
|
464
557
|
| `ElevationDenied` | UAC 被取消或被策略阻止 | 在管理员终端执行一次 `VirtualDesktop install-driver` |
|
|
465
558
|
| `unsupported-windows-build` | 系统低于 Win10 1809,没有 IddCx | 换系统,或用私有桌面(不需要驱动) |
|
|
466
559
|
| `capture-needs-shared-workspace` | 想截私有桌面里的窗口 | 私有桌面不支持截图,改用虚拟显示器 |
|
|
467
|
-
| `SetThreadDesktop ... error 170` |
|
|
560
|
+
| `SetThreadDesktop ... error 170` | 你自己在绑定线程,而该线程已有窗口或已初始化 COM | 直接调用 `bind_thread()` 时才会遇到;库内部的枚举不再绑定调用者线程 |
|
|
561
|
+
| 某程序在私有桌面上找不到窗口 | 控制台类程序(`cmd.exe`、`ping.exe`)的窗口属于 `conhost.exe`,不在自己 pid 下 | 报错会提示;用 `agent.exec()` 在桌面内驱动它,或换 GUI 程序 |
|
|
468
562
|
| 安装后没出现新的虚拟屏 | 驱动已注册但显示器未启用 | 设备管理器启用;`report.requires_reboot` 为真时重启 |
|
|
469
563
|
| 驱动下载失败或极慢 | github.com 不可达 | 设 `VIRTUALDESKTOP_GITHUB_MIRRORS`,或预置离线缓存 |
|
|
470
564
|
| 窗口截图是黑帧 | 应用为硬件加速 / 受保护内容 | 系统限制;换 `bitblt` 试试,或确认窗口有内容 |
|
|
@@ -472,7 +566,6 @@ VIRTUALDESKTOP_LOG_FILE=<path>
|
|
|
472
566
|
| 桌面截图拍不到工作区窗口 | 用了 `offscreen`,窗口在所有显示器之外 | 改用 `hide_mode="virtual_screen"` |
|
|
473
567
|
| `capture_desktop()` 报"workspace is not open" | offscreen 模式下没有绑定显示器 | 改用 `capture_all_screens()`,或用 `virtual_screen` |
|
|
474
568
|
| 真实鼠标点击无效 | 窗口在屏幕之外,或拿不到前台焦点 | 用 `virtual_screen`,或改用消息驱动 |
|
|
475
|
-
| 某程序在私有桌面上找不到窗口 | 控制台类(cmd)等窗口不参与常规枚举 | 换程序,或用 `agent.exec()` 在桌面内直接操作 |
|
|
476
569
|
|
|
477
570
|
**离线 / 内网部署**:把安装包按清单里的文件名(`VirtualDesktop/driver_manifest.json` 的
|
|
478
571
|
`artifact.filename`)放进 `VIRTUALDESKTOP_CACHE_DIR` 即可,不会联网;也可用
|
|
@@ -22,20 +22,59 @@ pip install VirtualDesktop # pip
|
|
|
22
22
|
uv add VirtualDesktop # uv(写入 pyproject.toml 并锁定)
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
运行时依赖:**Pillow**(帧图像)与 **comtypes**(UI Automation 是 COM API)。不需要 pywin32——
|
|
26
|
+
库访问 Win32 全部走 ctypes。若国内镜像同步滞后导致找不到最新版,加
|
|
26
27
|
`--default-index https://pypi.org/simple`(uv)或 `-i https://pypi.org/simple`(pip)。
|
|
27
28
|
|
|
28
29
|
| | 私有桌面 | 虚拟显示器 |
|
|
29
30
|
|---|---|---|
|
|
30
31
|
| 一句话 | 另开一个桌面,窗口与你的桌面完全隔离 | 多一块屏幕,窗口仍在你的桌面上、只是画在看不见的地方 |
|
|
31
|
-
|
|
|
32
|
+
| 底层 | 内核**对象**(`CreateDesktopW`),独立窗口命名空间 + DACL | 显示**设备**(IddCx 驱动),系统多出一块 `\\.\DISPLAYn` |
|
|
32
33
|
| 需要权限 | **零**:不需要驱动、不弹 UAC | 装一次驱动,弹**一次 UAC** |
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
34
|
+
| 系统要求 | Win10 / 11 任意 | Win10 1809 (17763) 起 |
|
|
35
|
+
| 程序窗口截图 | **不能** | **能** |
|
|
36
|
+
| 桌面截图 | **不能** | **能** |
|
|
37
|
+
| 读窗口属性 / 枚举子控件 | 能(桌面内代理) | 能(外部进程直接) |
|
|
38
|
+
| 点击控件 / 送入字符 | 能(窗口消息) | 能(消息 + 真实输入) |
|
|
39
|
+
| 真实鼠标 / 滚轮 / 键盘 | **不能** | **能** |
|
|
40
|
+
| 外部 pywinauto / pyautogui 直接驱动 | **不能** | **能** |
|
|
41
|
+
| 窗口跨桌面搬运 | **不能**(Windows 限制) | 能(在显示器之间移动) |
|
|
42
|
+
| 推荐场景 | 多实例并行、强隔离、不要截图 | UI 自动化、截图 OCR、无人值守抓界面 |
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
### 隔离强度分级
|
|
45
|
+
|
|
46
|
+
两套方案的隔离**不是"强/弱"两档,而是三个层面各不相同**,按你需要防住什么来选:
|
|
47
|
+
|
|
48
|
+
| 隔离层面 | 私有桌面 | 虚拟显示器 | 说明 |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| 窗口**发现**(能否枚举到) | **隔离** | 不隔离 | 私有桌面:`EnumWindows`/`FindWindow` 从外部看不到 |
|
|
51
|
+
| 窗口**输入**(真实鼠标键盘) | **隔离** | 不隔离 | 私有桌面:没有活动指针与键盘焦点 |
|
|
52
|
+
| 窗口**截图**(像素可读性) | **隔离** | 不隔离 | 私有桌面:从不参与系统合成,没有像素 |
|
|
53
|
+
| 窗口**消息**(`SendMessage` 等) | **不隔离** | 不隔离 | 拿到句柄即可投递消息,DACL 保护的是桌面对象而不是窗口消息 |
|
|
54
|
+
| 进程**生命周期** | **隔离** | 不隔离 | 私有桌面关闭时其上进程一并结束;虚拟显示器上的程序由你负责 |
|
|
55
|
+
|
|
56
|
+
> 关键提醒:私有桌面**不等于**"完全无法访问"。它挡的是**发现**、**输入**和**截图**,
|
|
57
|
+
> 挡不住"已知句柄后投递消息"。所以不要把私有桌面当作进程级安全沙箱使用——它没有降低
|
|
58
|
+
> 目标进程的权限,只是把它挪到了另一个窗口命名空间。
|
|
59
|
+
|
|
60
|
+
### 推荐怎么选
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
要截图(窗口截图或桌面截图) → 虚拟显示器
|
|
64
|
+
要用真实鼠标 / 滚轮 / 键盘,或 pyautogui → 虚拟显示器
|
|
65
|
+
要用 pywinauto / UIA 从外部直接驱动窗口 → 虚拟显示器
|
|
66
|
+
只处理一个窗口、要截图,越省事越好 → OffscreenSession(虚拟显示器侧)
|
|
67
|
+
─────────────────────────────────────────────────────────────
|
|
68
|
+
要多个实例互不干扰、且不需要截图 → 私有桌面
|
|
69
|
+
不能弹 UAC、不能装驱动 → 私有桌面
|
|
70
|
+
要"退出后什么都不留"(进程随桌面一起结束) → 私有桌面
|
|
71
|
+
需要窗口完全不出现在任何可见位置 → 私有桌面
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
两条常用组合:
|
|
75
|
+
|
|
76
|
+
- **只是想安静地跑一个窗口并抓图** → `OffscreenSession` + `virtual_screen`
|
|
77
|
+
- **想并行跑很多互不干扰的实例** → 每个实例一个 `VirtualWorkspace("名字")`
|
|
39
78
|
|
|
40
79
|
---
|
|
41
80
|
|
|
@@ -51,7 +90,7 @@ uv add VirtualDesktop # uv(写入 pyproject.toml 并锁定)
|
|
|
51
90
|
| 系统 | Windows 10 / 11(无 IddCx 版本要求) |
|
|
52
91
|
| 权限 | 当前用户即可,**不提权** |
|
|
53
92
|
| 驱动 | 不需要 |
|
|
54
|
-
| 依赖 |
|
|
93
|
+
| 依赖 | Pillow + comtypes |
|
|
55
94
|
|
|
56
95
|
## 基本用法
|
|
57
96
|
|
|
@@ -113,13 +152,14 @@ with VirtualWorkspace("lab") as space, space.agent() as agent:
|
|
|
113
152
|
| `agent.adopt(pid)` | 接上桌面内已在运行的程序 |
|
|
114
153
|
| `agent.info(window)` | 标题、类名、矩形、`child_count`、`buttons` |
|
|
115
154
|
| `agent.text(window)` | 窗口标题 |
|
|
155
|
+
| `agent.uia_tree(window)` | 完整 UIA 树(在桌面内遍历) |
|
|
116
156
|
| `agent.click(window, text=/index=)` | 点击控件(走窗口消息,见下) |
|
|
117
157
|
| `agent.keys(window, "...")` | 送入字符(走窗口消息) |
|
|
118
158
|
| `agent.windows()` | 该桌面上的窗口清单 |
|
|
119
159
|
| `agent.exec("python 代码")` | 在桌面内跑任意 Python,返回打印内容 |
|
|
120
160
|
|
|
121
161
|
`exec` 是逃生舱:里面的代码能 `import VirtualDesktop`、能用 `list_windows()`、`CaptureEngine`,
|
|
122
|
-
也能用你自己装的任何库。代理本身只依赖 ctypes
|
|
162
|
+
也能用你自己装的任何库。代理本身只依赖 ctypes 与 comtypes。
|
|
123
163
|
|
|
124
164
|
### 私有桌面不能截图
|
|
125
165
|
|
|
@@ -154,6 +194,38 @@ with VirtualWorkspace("lab") as space, space.agent() as agent:
|
|
|
154
194
|
要点:**能用消息驱动的都能用,需要真实输入设备的都不能用,两种截图都不能用**。需要截图或需要
|
|
155
195
|
真实鼠标/滚轮的应用,请改用下面的虚拟显示器方案。
|
|
156
196
|
|
|
197
|
+
## 支持的能力与调用方式
|
|
198
|
+
|
|
199
|
+
| 能力 | 是否支持 | 怎么调 |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| 在一个桌面里跑多个程序 | **支持** | 连续 `agent.launch([...])`,各自拿到不同句柄 |
|
|
202
|
+
| 多个独立桌面同时在线 | **支持** | 多个 `VirtualWorkspace("名字")` 并存,句柄与名字互不相同 |
|
|
203
|
+
| 拿到窗口句柄 | **支持** | `agent.launch([...]).hwnd` / `agent.adopt(pid).hwnd` / `desktop.find_window(pid)` |
|
|
204
|
+
| 读标题 / 类名 / 矩形 / 尺寸 | **支持** | `agent.info(window)` |
|
|
205
|
+
| 枚举子控件与按钮文字 | **支持** | `agent.info(window)["child_count"]` / `["buttons"]` |
|
|
206
|
+
| 完整 UIA 树 | **支持** | `agent.uia_tree(window)` |
|
|
207
|
+
| 点击控件 / 送入字符 | **支持** | `agent.click(window, text=...)` / `agent.keys(window, "...")` |
|
|
208
|
+
| 在桌面内跑任意 Python | **支持** | `agent.exec("...")`(可用 comtypes、pywinauto) |
|
|
209
|
+
| 程序窗口截图 / 桌面截图 | **不支持** | 见上方原因;改用虚拟显示器 |
|
|
210
|
+
| 跨桌面搬运窗口、移到主屏 | **不支持** | `move_to` 一律报 `cannot-cross-desktop`;只能在目标桌面重新 `launch` |
|
|
211
|
+
|
|
212
|
+
`agent.uia_tree()` 在**桌面内**遍历 UIA,这是私有桌面唯一可用的取树方式:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
with VirtualWorkspace("lab") as space, space.agent() as agent:
|
|
216
|
+
window = agent.launch(["charmap.exe"])
|
|
217
|
+
tree = agent.uia_tree(window) # view="RawView" 默认;也可 ControlView / ContentView
|
|
218
|
+
print(tree["count"])
|
|
219
|
+
for node in tree["nodes"][:5]:
|
|
220
|
+
print(node["control"], node["name"], node["enabled"])
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
> 不要用 `GetRootElement()`:它属于**输入桌面**,在私有桌面里走它拿不到任何目标窗口。
|
|
224
|
+
> 库内部用的是 `ElementFromHandle(hwnd)`。
|
|
225
|
+
|
|
226
|
+
> 在里面用 comtypes / pywinauto 是安全的:窗口枚举已不再绑定调用者线程,所以
|
|
227
|
+
> `from pywinauto import Application` 放在文件顶部也不会影响私有桌面。
|
|
228
|
+
|
|
157
229
|
---
|
|
158
230
|
|
|
159
231
|
# 第二部分:虚拟显示器
|
|
@@ -169,7 +241,7 @@ with VirtualWorkspace("lab") as space, space.agent() as agent:
|
|
|
169
241
|
| 系统 | **Windows 10 1809 (build 17763) 或更高**(更低版本没有 IddCx 框架) |
|
|
170
242
|
| 权限 | 安装/卸载驱动需要管理员,弹**一次 UAC** |
|
|
171
243
|
| 驱动 | 首次使用时自动下载安装(也可预先装好) |
|
|
172
|
-
| 依赖 |
|
|
244
|
+
| 依赖 | Pillow + comtypes |
|
|
173
245
|
|
|
174
246
|
## 装驱动
|
|
175
247
|
|
|
@@ -368,6 +440,26 @@ print(current_primary_size()) # (1920, 1080)
|
|
|
368
440
|
> 用真实输入时,窗口所在显示器必须是活动桌面的一部分;`pyautogui` 这类库还需要窗口能取得前台
|
|
369
441
|
> 焦点。若目标窗口拿不到焦点,请改用消息驱动(`agent.click()` / `WM_CHAR`)。
|
|
370
442
|
|
|
443
|
+
## 支持的能力与调用方式
|
|
444
|
+
|
|
445
|
+
| 能力 | 是否支持 | 怎么调 |
|
|
446
|
+
|---|---|---|
|
|
447
|
+
| 在一个工作区里跑多个程序 | **支持** | 连续 `ws.launch([...])`,运行中可随时新增 |
|
|
448
|
+
| 多个工作区同时在线 | **支持** | 多个 `VirtualWorkspace(SHARED)` 并存,各自独立停靠位 |
|
|
449
|
+
| 拿到窗口句柄给外部用 | **支持** | `ws.describe()["windows"]`(真实 HWND,无包装) |
|
|
450
|
+
| 读标题 / 类名 / 矩形 | **支持** | 外部 pywinauto 直接读,或 `ws.windows()` |
|
|
451
|
+
| 枚举子控件 | **支持** | 外部 pywinauto `children(visible_only=False)` |
|
|
452
|
+
| 完整 UIA 树 | **支持** | 外部 UIA,或 `--ui-tree DIR` |
|
|
453
|
+
| 程序窗口截图 | **支持** | `ws.capture(hwnd, save_to=...)` / `CaptureEngine.capture(hwnd)` |
|
|
454
|
+
| 桌面截图 | **支持**(`virtual_screen`) | `ws.capture_desktop()` / `capture_monitor()` / `capture_all_screens()` |
|
|
455
|
+
| 真实鼠标 / 滚轮 / 键盘 | **支持**(`virtual_screen`) | pywinauto `click_input()` / `type_keys()`,或 pyautogui |
|
|
456
|
+
| 在显示器之间移动窗口 | **支持** | `move_to("\\\\.\\DISPLAY2")` / `move_all_to(...)` / `move_to_primary()` |
|
|
457
|
+
| 退出时还原窗口 | **支持** | `with` 退出自动还原;异常退出同样还原 |
|
|
458
|
+
| 退出时卸载驱动 | **支持** | `uninstall_on_exit=True` 或 `close(uninstall=True)` |
|
|
459
|
+
| 桌面截图(`offscreen` 模式) | **不支持** | 窗口在所有显示器之外,拍不到;改用 `virtual_screen` |
|
|
460
|
+
| 真实鼠标 / 滚轮 / 键盘(`offscreen`) | **不支持** | 同上:指针被钳制回屏幕边缘 |
|
|
461
|
+
| 跨私有桌面搬运窗口 | **不支持** | Windows 不允许;`move_to` 会明确报错 |
|
|
462
|
+
|
|
371
463
|
---
|
|
372
464
|
|
|
373
465
|
# 通用功能
|
|
@@ -408,7 +500,8 @@ VIRTUALDESKTOP_LOG_FILE=<path>
|
|
|
408
500
|
| `ElevationDenied` | UAC 被取消或被策略阻止 | 在管理员终端执行一次 `VirtualDesktop install-driver` |
|
|
409
501
|
| `unsupported-windows-build` | 系统低于 Win10 1809,没有 IddCx | 换系统,或用私有桌面(不需要驱动) |
|
|
410
502
|
| `capture-needs-shared-workspace` | 想截私有桌面里的窗口 | 私有桌面不支持截图,改用虚拟显示器 |
|
|
411
|
-
| `SetThreadDesktop ... error 170` |
|
|
503
|
+
| `SetThreadDesktop ... error 170` | 你自己在绑定线程,而该线程已有窗口或已初始化 COM | 直接调用 `bind_thread()` 时才会遇到;库内部的枚举不再绑定调用者线程 |
|
|
504
|
+
| 某程序在私有桌面上找不到窗口 | 控制台类程序(`cmd.exe`、`ping.exe`)的窗口属于 `conhost.exe`,不在自己 pid 下 | 报错会提示;用 `agent.exec()` 在桌面内驱动它,或换 GUI 程序 |
|
|
412
505
|
| 安装后没出现新的虚拟屏 | 驱动已注册但显示器未启用 | 设备管理器启用;`report.requires_reboot` 为真时重启 |
|
|
413
506
|
| 驱动下载失败或极慢 | github.com 不可达 | 设 `VIRTUALDESKTOP_GITHUB_MIRRORS`,或预置离线缓存 |
|
|
414
507
|
| 窗口截图是黑帧 | 应用为硬件加速 / 受保护内容 | 系统限制;换 `bitblt` 试试,或确认窗口有内容 |
|
|
@@ -416,7 +509,6 @@ VIRTUALDESKTOP_LOG_FILE=<path>
|
|
|
416
509
|
| 桌面截图拍不到工作区窗口 | 用了 `offscreen`,窗口在所有显示器之外 | 改用 `hide_mode="virtual_screen"` |
|
|
417
510
|
| `capture_desktop()` 报"workspace is not open" | offscreen 模式下没有绑定显示器 | 改用 `capture_all_screens()`,或用 `virtual_screen` |
|
|
418
511
|
| 真实鼠标点击无效 | 窗口在屏幕之外,或拿不到前台焦点 | 用 `virtual_screen`,或改用消息驱动 |
|
|
419
|
-
| 某程序在私有桌面上找不到窗口 | 控制台类(cmd)等窗口不参与常规枚举 | 换程序,或用 `agent.exec()` 在桌面内直接操作 |
|
|
420
512
|
|
|
421
513
|
**离线 / 内网部署**:把安装包按清单里的文件名(`VirtualDesktop/driver_manifest.json` 的
|
|
422
514
|
`artifact.filename`)放进 `VIRTUALDESKTOP_CACHE_DIR` 即可,不会联网;也可用
|
|
@@ -1,135 +1,135 @@
|
|
|
1
|
-
"""``VirtualDesktop``
|
|
2
|
-
|
|
3
|
-
The public surface is deliberately small:
|
|
4
|
-
|
|
5
|
-
* :class:`~VirtualDesktop.session.OffscreenSession`
|
|
6
|
-
* :func:`~VirtualDesktop.session.probe_environment`
|
|
7
|
-
* :class:`~VirtualDesktop.driver.DriverInstaller`
|
|
8
|
-
* :mod:`VirtualDesktop.errors`
|
|
9
|
-
|
|
10
|
-
Typical use::
|
|
11
|
-
|
|
12
|
-
from VirtualDesktop import OffscreenSession
|
|
13
|
-
|
|
14
|
-
with OffscreenSession(target_window_title="WeChat") as session:
|
|
15
|
-
session.mount()
|
|
16
|
-
frame = session.capture() # PIL image
|
|
17
|
-
frame.image.save("wechat.png")
|
|
18
|
-
|
|
19
|
-
Nothing is installed until ``mount()`` actually needs a virtual display, and nothing is
|
|
20
|
-
moved until a target window has been found, so a failed call leaves the machine as it was.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
from __future__ import annotations
|
|
24
|
-
|
|
25
|
-
from ._log import enable_debug_logging, get_logger
|
|
26
|
-
from ._winapi import (
|
|
27
|
-
IS_WINDOWS,
|
|
28
|
-
MonitorInfo,
|
|
29
|
-
enumerate_monitors,
|
|
30
|
-
is_admin,
|
|
31
|
-
list_windows,
|
|
32
|
-
select_virtual_monitor,
|
|
33
|
-
)
|
|
34
|
-
from .capture import DEFAULT_METHODS, CaptureEngine, Frame, analyze_image
|
|
35
|
-
from .config import DriverConfig, SessionConfig
|
|
36
|
-
from .desktop import DesktopBinding, current_desktop_name, ensure_default_desktop
|
|
37
|
-
from .driver import DriverInstaller, InstallReport, virtual_display_present
|
|
38
|
-
from .errors import (
|
|
39
|
-
AmbiguousWindowError,
|
|
40
|
-
CaptureError,
|
|
41
|
-
ConfigError,
|
|
42
|
-
DesktopBindError,
|
|
43
|
-
DownloadError,
|
|
44
|
-
DriverInstallError,
|
|
45
|
-
DriverNotFoundError,
|
|
46
|
-
DriverVerificationError,
|
|
47
|
-
ElevationDenied,
|
|
48
|
-
ElevationRequired,
|
|
49
|
-
ExtractionError,
|
|
50
|
-
IntegrityError,
|
|
51
|
-
MissingDependencyError,
|
|
52
|
-
OffscreenMountError,
|
|
53
|
-
SessionStateError,
|
|
54
|
-
UnsupportedPlatformError,
|
|
55
|
-
WindowMountError,
|
|
56
|
-
WindowNotFoundError,
|
|
57
|
-
)
|
|
58
|
-
from .manifest import load_manifest
|
|
59
|
-
from .session import OffscreenSession, probe_environment
|
|
60
|
-
from .desktop_session import DesktopProcess, VirtualDesktop, current_desktop_name
|
|
61
|
-
from .window import WindowMounter, WindowRecord, find_window, window_diagnostics
|
|
62
|
-
from .workspace import SHARED, VirtualWorkspace, WorkspaceWindow
|
|
63
|
-
from .agent import AgentProcess, AgentSession
|
|
64
|
-
from .display_setup import configure_virtual_display, current_primary_dpi, current_primary_size
|
|
65
|
-
|
|
66
|
-
__version__ = "1.
|
|
67
|
-
|
|
68
|
-
__all__ = [
|
|
69
|
-
"__version__",
|
|
70
|
-
# session
|
|
71
|
-
"OffscreenSession",
|
|
72
|
-
"probe_environment",
|
|
73
|
-
# drivers
|
|
74
|
-
"DriverInstaller",
|
|
75
|
-
"InstallReport",
|
|
76
|
-
"virtual_display_present",
|
|
77
|
-
"load_manifest",
|
|
78
|
-
# windows and displays
|
|
79
|
-
"WindowMounter",
|
|
80
|
-
"WindowRecord",
|
|
81
|
-
"find_window",
|
|
82
|
-
"window_diagnostics",
|
|
83
|
-
"enumerate_monitors",
|
|
84
|
-
"select_virtual_monitor",
|
|
85
|
-
"MonitorInfo",
|
|
86
|
-
"list_windows",
|
|
87
|
-
"ensure_default_desktop",
|
|
88
|
-
"current_desktop_name",
|
|
89
|
-
"DesktopBinding",
|
|
90
|
-
# private virtual desktop (no admin, no driver)
|
|
91
|
-
"VirtualDesktop",
|
|
92
|
-
"DesktopProcess",
|
|
93
|
-
# multi-window virtual-display workspace (externally controllable)
|
|
94
|
-
"VirtualWorkspace",
|
|
95
|
-
"WorkspaceWindow",
|
|
96
|
-
"SHARED",
|
|
97
|
-
# in-desktop agent (for isolated workspaces / private desktops)
|
|
98
|
-
"AgentSession",
|
|
99
|
-
"AgentProcess",
|
|
100
|
-
# virtual display geometry
|
|
101
|
-
"configure_virtual_display",
|
|
102
|
-
"current_primary_size",
|
|
103
|
-
"current_primary_dpi",
|
|
104
|
-
# capture
|
|
105
|
-
"CaptureEngine",
|
|
106
|
-
"Frame",
|
|
107
|
-
"analyze_image",
|
|
108
|
-
"DEFAULT_METHODS",
|
|
109
|
-
# configuration
|
|
110
|
-
"DriverConfig",
|
|
111
|
-
"SessionConfig",
|
|
112
|
-
# platform helpers
|
|
113
|
-
"IS_WINDOWS",
|
|
114
|
-
"is_admin",
|
|
115
|
-
"enable_debug_logging",
|
|
116
|
-
# errors
|
|
117
|
-
"OffscreenMountError",
|
|
118
|
-
"UnsupportedPlatformError",
|
|
119
|
-
"MissingDependencyError",
|
|
120
|
-
"ConfigError",
|
|
121
|
-
"IntegrityError",
|
|
122
|
-
"DownloadError",
|
|
123
|
-
"ExtractionError",
|
|
124
|
-
"ElevationRequired",
|
|
125
|
-
"ElevationDenied",
|
|
126
|
-
"DriverInstallError",
|
|
127
|
-
"DriverNotFoundError",
|
|
128
|
-
"DriverVerificationError",
|
|
129
|
-
"DesktopBindError",
|
|
130
|
-
"WindowNotFoundError",
|
|
131
|
-
"AmbiguousWindowError",
|
|
132
|
-
"WindowMountError",
|
|
133
|
-
"CaptureError",
|
|
134
|
-
"SessionStateError",
|
|
135
|
-
]
|
|
1
|
+
"""``VirtualDesktop`` 鈥?an offscreen window workstation for Windows.
|
|
2
|
+
|
|
3
|
+
The public surface is deliberately small:
|
|
4
|
+
|
|
5
|
+
* :class:`~VirtualDesktop.session.OffscreenSession` 鈥?mount, capture, restore.
|
|
6
|
+
* :func:`~VirtualDesktop.session.probe_environment` 鈥?diagnostics without side effects.
|
|
7
|
+
* :class:`~VirtualDesktop.driver.DriverInstaller` 鈥?explicit driver control.
|
|
8
|
+
* :mod:`VirtualDesktop.errors` 鈥?every exception, each with a ``code`` and a ``hint``.
|
|
9
|
+
|
|
10
|
+
Typical use::
|
|
11
|
+
|
|
12
|
+
from VirtualDesktop import OffscreenSession
|
|
13
|
+
|
|
14
|
+
with OffscreenSession(target_window_title="WeChat") as session:
|
|
15
|
+
session.mount()
|
|
16
|
+
frame = session.capture() # PIL image
|
|
17
|
+
frame.image.save("wechat.png")
|
|
18
|
+
|
|
19
|
+
Nothing is installed until ``mount()`` actually needs a virtual display, and nothing is
|
|
20
|
+
moved until a target window has been found, so a failed call leaves the machine as it was.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from ._log import enable_debug_logging, get_logger
|
|
26
|
+
from ._winapi import (
|
|
27
|
+
IS_WINDOWS,
|
|
28
|
+
MonitorInfo,
|
|
29
|
+
enumerate_monitors,
|
|
30
|
+
is_admin,
|
|
31
|
+
list_windows,
|
|
32
|
+
select_virtual_monitor,
|
|
33
|
+
)
|
|
34
|
+
from .capture import DEFAULT_METHODS, CaptureEngine, Frame, analyze_image
|
|
35
|
+
from .config import DriverConfig, SessionConfig
|
|
36
|
+
from .desktop import DesktopBinding, current_desktop_name, ensure_default_desktop
|
|
37
|
+
from .driver import DriverInstaller, InstallReport, virtual_display_present
|
|
38
|
+
from .errors import (
|
|
39
|
+
AmbiguousWindowError,
|
|
40
|
+
CaptureError,
|
|
41
|
+
ConfigError,
|
|
42
|
+
DesktopBindError,
|
|
43
|
+
DownloadError,
|
|
44
|
+
DriverInstallError,
|
|
45
|
+
DriverNotFoundError,
|
|
46
|
+
DriverVerificationError,
|
|
47
|
+
ElevationDenied,
|
|
48
|
+
ElevationRequired,
|
|
49
|
+
ExtractionError,
|
|
50
|
+
IntegrityError,
|
|
51
|
+
MissingDependencyError,
|
|
52
|
+
OffscreenMountError,
|
|
53
|
+
SessionStateError,
|
|
54
|
+
UnsupportedPlatformError,
|
|
55
|
+
WindowMountError,
|
|
56
|
+
WindowNotFoundError,
|
|
57
|
+
)
|
|
58
|
+
from .manifest import load_manifest
|
|
59
|
+
from .session import OffscreenSession, probe_environment
|
|
60
|
+
from .desktop_session import DesktopProcess, VirtualDesktop, current_desktop_name
|
|
61
|
+
from .window import WindowMounter, WindowRecord, find_window, window_diagnostics
|
|
62
|
+
from .workspace import SHARED, VirtualWorkspace, WorkspaceWindow
|
|
63
|
+
from .agent import AgentProcess, AgentSession
|
|
64
|
+
from .display_setup import configure_virtual_display, current_primary_dpi, current_primary_size
|
|
65
|
+
|
|
66
|
+
__version__ = "1.4.1"
|
|
67
|
+
|
|
68
|
+
__all__ = [
|
|
69
|
+
"__version__",
|
|
70
|
+
# session
|
|
71
|
+
"OffscreenSession",
|
|
72
|
+
"probe_environment",
|
|
73
|
+
# drivers
|
|
74
|
+
"DriverInstaller",
|
|
75
|
+
"InstallReport",
|
|
76
|
+
"virtual_display_present",
|
|
77
|
+
"load_manifest",
|
|
78
|
+
# windows and displays
|
|
79
|
+
"WindowMounter",
|
|
80
|
+
"WindowRecord",
|
|
81
|
+
"find_window",
|
|
82
|
+
"window_diagnostics",
|
|
83
|
+
"enumerate_monitors",
|
|
84
|
+
"select_virtual_monitor",
|
|
85
|
+
"MonitorInfo",
|
|
86
|
+
"list_windows",
|
|
87
|
+
"ensure_default_desktop",
|
|
88
|
+
"current_desktop_name",
|
|
89
|
+
"DesktopBinding",
|
|
90
|
+
# private virtual desktop (no admin, no driver)
|
|
91
|
+
"VirtualDesktop",
|
|
92
|
+
"DesktopProcess",
|
|
93
|
+
# multi-window virtual-display workspace (externally controllable)
|
|
94
|
+
"VirtualWorkspace",
|
|
95
|
+
"WorkspaceWindow",
|
|
96
|
+
"SHARED",
|
|
97
|
+
# in-desktop agent (for isolated workspaces / private desktops)
|
|
98
|
+
"AgentSession",
|
|
99
|
+
"AgentProcess",
|
|
100
|
+
# virtual display geometry
|
|
101
|
+
"configure_virtual_display",
|
|
102
|
+
"current_primary_size",
|
|
103
|
+
"current_primary_dpi",
|
|
104
|
+
# capture
|
|
105
|
+
"CaptureEngine",
|
|
106
|
+
"Frame",
|
|
107
|
+
"analyze_image",
|
|
108
|
+
"DEFAULT_METHODS",
|
|
109
|
+
# configuration
|
|
110
|
+
"DriverConfig",
|
|
111
|
+
"SessionConfig",
|
|
112
|
+
# platform helpers
|
|
113
|
+
"IS_WINDOWS",
|
|
114
|
+
"is_admin",
|
|
115
|
+
"enable_debug_logging",
|
|
116
|
+
# errors
|
|
117
|
+
"OffscreenMountError",
|
|
118
|
+
"UnsupportedPlatformError",
|
|
119
|
+
"MissingDependencyError",
|
|
120
|
+
"ConfigError",
|
|
121
|
+
"IntegrityError",
|
|
122
|
+
"DownloadError",
|
|
123
|
+
"ExtractionError",
|
|
124
|
+
"ElevationRequired",
|
|
125
|
+
"ElevationDenied",
|
|
126
|
+
"DriverInstallError",
|
|
127
|
+
"DriverNotFoundError",
|
|
128
|
+
"DriverVerificationError",
|
|
129
|
+
"DesktopBindError",
|
|
130
|
+
"WindowNotFoundError",
|
|
131
|
+
"AmbiguousWindowError",
|
|
132
|
+
"WindowMountError",
|
|
133
|
+
"CaptureError",
|
|
134
|
+
"SessionStateError",
|
|
135
|
+
]
|