VirtualDesktop 1.5.0__tar.gz → 1.6.0__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.
Files changed (64) hide show
  1. {virtualdesktop-1.5.0/VirtualDesktop.egg-info → virtualdesktop-1.6.0}/PKG-INFO +95 -3
  2. virtualdesktop-1.5.0/PKG-INFO → virtualdesktop-1.6.0/README.md +704 -669
  3. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/__init__.py +18 -1
  4. virtualdesktop-1.6.0/VirtualDesktop/record.py +605 -0
  5. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/workspace.py +141 -4
  6. virtualdesktop-1.5.0/README.md → virtualdesktop-1.6.0/VirtualDesktop.egg-info/PKG-INFO +761 -612
  7. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop.egg-info/SOURCES.txt +2 -0
  8. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/pyproject.toml +1 -1
  9. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_no_dialogs.py +28 -2
  10. virtualdesktop-1.6.0/tests/test_record.py +386 -0
  11. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/LICENSE +0 -0
  12. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/__main__.py +0 -0
  13. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_bundle.py +0 -0
  14. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_compat.py +0 -0
  15. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_desktop_runtime.py +0 -0
  16. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_devices.py +0 -0
  17. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_download.py +0 -0
  18. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_enumerate.py +0 -0
  19. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_helper.py +0 -0
  20. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_log.py +0 -0
  21. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_process.py +0 -0
  22. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_sigcheck.ps1 +0 -0
  23. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_sigcheck.py +0 -0
  24. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/_winapi.py +0 -0
  25. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/agent.py +0 -0
  26. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/capture.py +0 -0
  27. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/cli.py +0 -0
  28. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/config.py +0 -0
  29. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/desktop.py +0 -0
  30. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/desktop_capture.py +0 -0
  31. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/desktop_session.py +0 -0
  32. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/display_setup.py +0 -0
  33. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/driver.py +0 -0
  34. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/driver_manifest.json +0 -0
  35. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/errors.py +0 -0
  36. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/manifest.py +0 -0
  37. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/session.py +0 -0
  38. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop/window.py +0 -0
  39. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop.egg-info/dependency_links.txt +0 -0
  40. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop.egg-info/entry_points.txt +0 -0
  41. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop.egg-info/requires.txt +0 -0
  42. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/VirtualDesktop.egg-info/top_level.txt +0 -0
  43. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/setup.cfg +0 -0
  44. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_accelerators.py +0 -0
  45. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_agent.py +0 -0
  46. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_artifact_regression.py +0 -0
  47. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_capture.py +0 -0
  48. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_cli.py +0 -0
  49. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_compat.py +0 -0
  50. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_config_and_winapi.py +0 -0
  51. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_desktop_isolation.py +0 -0
  52. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_destructive_lifecycle.py +0 -0
  53. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_display_setup.py +0 -0
  54. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_download.py +0 -0
  55. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_driver.py +0 -0
  56. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_hide_modes.py +0 -0
  57. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_integration_windows.py +0 -0
  58. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_manifest.py +0 -0
  59. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_packaging.py +0 -0
  60. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_process.py +0 -0
  61. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_virtual_desktop.py +0 -0
  62. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_workspace.py +0 -0
  63. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_workspace_instances.py +0 -0
  64. {virtualdesktop-1.5.0 → virtualdesktop-1.6.0}/tests/test_workspace_move.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: VirtualDesktop
3
- Version: 1.5.0
3
+ Version: 1.6.0
4
4
  Summary: win10-win11 虚拟桌面
5
5
  Author: VirtualDesktop contributors
6
6
  License: MIT License
@@ -282,7 +282,8 @@ with VirtualWorkspace("lab") as space, space.agent() as agent:
282
282
  | 点击控件 / 送入字符 | **支持** | `agent.click(window, text=...)` / `agent.keys(window, "...")` |
283
283
  | 在桌面内跑任意 Python | **支持** | `agent.exec("...")`(可用 comtypes、pywinauto) |
284
284
  | 程序窗口截图 | **支持** | `agent.screenshot(window, "绝对路径")` |
285
- | 桌面截图 | **不支持** | 拿不到该桌面的屏幕 DC;需要拍屏幕请用虚拟显示器 |
285
+ | **窗口录制** | **支持** | `space.record_window(window, "out.mp4", fps=10)`(走代理,上限约 20 fps) |
286
+ | 桌面截图 / 桌面录制 | **不支持** | 拿不到该桌面的屏幕 DC;需要拍屏幕请用虚拟显示器 |
286
287
  | 跨桌面搬运窗口、移到主屏 | **不支持** | `move_to` 一律报 `cannot-cross-desktop`;只能在目标桌面重新 `launch` |
287
288
 
288
289
  `agent.uia_tree()` 在**桌面内**遍历 UIA,这是私有桌面唯一可用的取树方式:
@@ -498,6 +499,91 @@ configure_virtual_display(size="1920x1080", dpi="125%") # 传 None 表示跟
498
499
  print(current_primary_size()) # (1920, 1080)
499
500
  ```
500
501
 
502
+ ## 录制
503
+
504
+ 录制**只作用于本实例自己的桌面或窗口**,从不拍主屏、也不会去拍"所有屏幕"——需要那种行为的
505
+ 请直接用系统截图工具。
506
+
507
+ 两种录制目标,用同一个 `Recorder`:
508
+
509
+ ```python
510
+ from VirtualDesktop import VirtualWorkspace
511
+
512
+ with VirtualWorkspace(VirtualWorkspace.SHARED) as space: # hide_mode 默认 virtual_screen
513
+ space.launch(["charmap.exe"])
514
+
515
+ # ① 录整个虚拟桌面(工作区自己的那块虚拟屏)
516
+ with space.record_desktop("desktop.mp4", fps=15, duration=10) as rec:
517
+ ... # 这期间发生的一切都被录下
518
+ print(rec.report().describe())
519
+
520
+ # ② 只录指定窗口(任何 hide_mode 都可以,包括私有桌面)
521
+ window = space.windows()[0]
522
+ with space.record_window(window, "window.gif", fps=10, duration=5) as rec2:
523
+ ...
524
+ ```
525
+
526
+ 窗口录制还有**私有桌面**版本(走代理进程,帧率上限约 20 fps):
527
+
528
+ ```python
529
+ with VirtualWorkspace("lab") as space, space.agent() as agent:
530
+ window = agent.launch(["charmap.exe"])
531
+ with space.record_window(window, r"C:\绝对路径\window.mp4", fps=10, duration=5):
532
+ ...
533
+ ```
534
+
535
+ ### 输出格式
536
+
537
+ 扩展名决定格式。**视频需要 ffmpeg 在 PATH 上**;其余只依赖 Pillow,没装 ffmpeg 也能用:
538
+
539
+ | 扩展名 | 写入方 | 说明 |
540
+ |---|---|---|
541
+ | `.mp4` `.mkv` `.mov` `.avi` `.webm` | ffmpeg | H.264 + `yuv420p`;`quality` 映射到 CRF(0–51,默认 23) |
542
+ | `.gif` | Pillow | 动画 GIF;`quality` 映射到调色板颜色数(8–256) |
543
+ | `.png` `.jpg` `.webp` … | Pillow | 写成**目录下的编号帧**,另附 `frames.json` |
544
+
545
+ 查当前环境支持情况:
546
+
547
+ ```python
548
+ from VirtualDesktop import supported_formats
549
+ print(supported_formats()) # {'.mp4': 'ffmpeg', '.gif': 'pillow', ...}
550
+ ```
551
+
552
+ > 内容完全相同的连续帧会被 GIF 优化合并成一个条目(延迟累加),所以 GIF 的**帧数可能少于
553
+ > 录制帧数**,但总时长不变。`report().frames` 报的是实际**采集**帧数。
554
+
555
+ ### 流式输出
556
+
557
+ 需要边录边处理(送编码器、推到 socket、实时 OCR)时用 `record_stream()`:文件照常写,
558
+ 同时逐帧交给你:
559
+
560
+ ```python
561
+ with space.record_stream("run.gif", target=window, fps=8, duration=5) as stream:
562
+ for frame in stream:
563
+ ... # frame.image 是刚采集到的 PIL 图像
564
+ ```
565
+
566
+ 它是个**有界**队列(默认最多缓存 240 帧):消费端跟不上时丢最旧的帧,不会无限吃内存。
567
+ 录制到时限后迭代会**自动结束**,不会挂起。
568
+
569
+ ### 参数
570
+
571
+ `record_desktop()` / `record_window()` 都接受:
572
+
573
+ | 参数 | 含义 |
574
+ |---|---|
575
+ | `fps` | 目标帧率(必填,默认 10)。低于目标会被丢弃并计入 `report().dropped` |
576
+ | `duration` | 秒;到点自动停止,`stopped_by="duration"` |
577
+ | `max_frames` | 帧数上限;到点自动停止,`stopped_by="max_frames"` |
578
+ | `quality` | 见上方格式表;不同后端含义不同 |
579
+ | `format` | 覆盖扩展名推断 |
580
+ | `on_frame` | `(frame, index)` 回调,每帧一次;回调抛异常不会中断录制 |
581
+ | `ffmpeg` | 指定 ffmpeg 可执行文件路径 |
582
+
583
+ `report()` 返回 `RecordingReport`,含 `frames` / `fps` / `duration` / `size` / `bytes`
584
+ / `dropped` / `stopped_by` / `error`。**在 `with` 块内调用也是安全的**——它会先收尾再统计,
585
+ 所以报的大小是成品文件的大小。
586
+
501
587
  ## 能力边界
502
588
 
503
589
  | 想做的事 | 虚拟显示器 | offscreen |
@@ -508,6 +594,8 @@ print(current_primary_size()) # (1920, 1080)
508
594
  | 送入字符(窗口消息) | 能 | 能 |
509
595
  | **程序窗口截图** | **能** | **能** |
510
596
  | **桌面截图** | **能**(拍到窗口) | **不能**(窗口在所有显示器之外) |
597
+ | **窗口录制** | **能** | **能** |
598
+ | **桌面录制** | **能**(录工作区自己的虚拟屏) | **不能**(没有属于自己的屏幕区域) |
511
599
  | 外部 pywinauto / UIA 直接连上 | 能 | 能 |
512
600
  | 移动真实鼠标来点击 | 能 | **不能**(窗口在屏幕之外) |
513
601
  | 滚轮 | 能 | **不能** |
@@ -528,6 +616,8 @@ print(current_primary_size()) # (1920, 1080)
528
616
  | 完整 UIA 树 | **支持** | 外部 UIA,或 `--ui-tree DIR` |
529
617
  | 程序窗口截图 | **支持** | `ws.capture(hwnd, save_to=...)` / `CaptureEngine.capture(hwnd)` |
530
618
  | 桌面截图 | **支持**(`virtual_screen`) | `ws.capture_desktop()` / `capture_monitor()` / `capture_all_screens()` |
619
+ | **窗口录制** | **支持** | `ws.record_window(hwnd, "out.mp4", fps=15, duration=10)` |
620
+ | **桌面录制** | **支持**(`virtual_screen`) | `ws.record_desktop("out.mp4", fps=15)` |
531
621
  | 真实鼠标 / 滚轮 / 键盘 | **支持**(`virtual_screen`) | pywinauto `click_input()` / `type_keys()`,或 pyautogui |
532
622
  | 在显示器之间移动窗口 | **支持** | `move_to("\\\\.\\DISPLAY2")` / `move_all_to(...)` / `move_to_primary()` |
533
623
  | 退出时还原窗口 | **支持** | `with` 退出自动还原;异常退出同样还原 |
@@ -583,7 +673,9 @@ VIRTUALDESKTOP_LOG_FILE=<path>
583
673
  | 窗口截图是黑帧 | 应用为硬件加速 / 受保护内容 | 系统限制;换 `bitblt` 试试,或确认窗口有内容 |
584
674
  | 桌面截图少了右下角 | 进程不感知 DPI,矩形按逻辑坐标算 | 先调 `CaptureEngine().ensure_dpi_aware()` |
585
675
  | 桌面截图拍不到工作区窗口 | 用了 `offscreen`,窗口在所有显示器之外 | 改用 `hide_mode="virtual_screen"` |
586
- | `capture_desktop()` 报"workspace is not open" | offscreen 模式下没有绑定显示器 | 改用 `capture_all_screens()`,或用 `virtual_screen` |
676
+ | `capture_desktop()` 报"workspace has no monitor" | `offscreen` 模式没有属于自己的屏幕区域 | 用 `hide_mode="virtual_screen"`,或改用 `capture(hwnd)` / `record_window(hwnd)` |
677
+ | 录制报 `record-would-capture-user-screen` | 工作区用的是物理屏 | 改用虚拟显示器;确实要录主屏请显式传 `monitor="primary"` |
678
+ | 录制报 `ffmpeg-missing` | 目标格式是视频但 PATH 上没有 ffmpeg | 装 ffmpeg、传 `ffmpeg=<路径>`,或改录 `.gif` / 帧序列 |
587
679
  | 真实鼠标点击无效 | 窗口在屏幕之外,或拿不到前台焦点 | 用 `virtual_screen`,或改用消息驱动 |
588
680
 
589
681
  **离线 / 内网部署**:把安装包按清单里的文件名(`VirtualDesktop/driver_manifest.json` 的