Fix Cap Windows window target discovery returning [] from cap targets windows --json
まだ誰も着手していません。
評価
調査の方向性
crates/scap-targets/src/platform/win.rs から始め、WindowImpl::list() と get_topmost_at_cursor_fallback() を比較し、その後 crates/recording/src/sources/screen_capture/mod.rs と apps/cli/src/targets.rs を通じて結果を追跡します。Windows で表示されている対象ウィンドウを用いて cap targets windows --json を再現し、既存のフィルターを維持したまま、使用可能な数値 ID が返され、クロップの挙動が分離されていることを確認します。
索引モデルが issue の本文から書いたものです。
説明
Summary
I am documenting a reproducible Windows limitation in the Cap CLI and intend to implement the resolution in a new pull request against the Cap repository after validating the fix locally. My production consumer is the Robotic Video Sales Letter agent in run.py, where I need Cap to record the Camoufox browser window rather than the entire desktop.
Environment
- Windows edition: Windows 11 Home Single Language
- Version: 24H2
- Installed: 4/6/2025
- OS build: 26100.4652
- Experience: Windows Feature Experience Pack 1000.26100.128.0
Exact observed problem
On Windows, the Cap CLI can be invoked successfully, but cap targets windows --json returns an empty JSON array ([]) even while a visible Camoufox top-level application window is open. Because no target is exposed, the result cannot be passed to cap record start --window <id> through the normal CLI workflow.
The CLI entrypoint is apps/cli/src/targets.rs. Its windows() function calls cap_recording::screen_capture::list_windows(), then serializes each returned window into the JSON target shape. An empty result at this boundary therefore becomes []; this is not a JSON formatting problem in the CLI.
The recording target abstraction is defined in crates/recording/src/sources/screen_capture/mod.rs. ScreenCaptureTarget::Window { id } expects a WindowId, resolves the target with Window::from_id(id), obtains the display and window bounds, and derives crop information from those bounds.
The Windows target implementation is crates/scap-targets/src/platform/win.rs. WindowImpl::list() defines an enumeration callback for windows, but currently invokes EnumChildWindows(Some(GetDesktopWindow()), ...). This is the suspicious failure boundary: EnumChildWindows enumerates child windows beneath a parent, whereas the application windows required by cap targets windows are top-level windows and are discoverable through EnumWindows.
The same win.rs file already uses EnumWindows in get_topmost_at_cursor_fallback(). This makes the list path internally inconsistent: the general window-list path begins with desktop-child enumeration, while another Windows path enumerates top-level windows directly.
Evidence from my repository
My consumer-side implementation is run.py. _find_camoufox_hwnd() uses win32gui.EnumWindows() to enumerate visible top-level windows, filters titles and process information, and returns the Camoufox window handle when it finds a match. This independently finds a valid HWND while Cap target discovery returns [].
The same run.py passes the discovered value to the Cap recording command when a window ID is available and retains a screen-recording fallback when it is not. The workaround is intentionally explicit because Cap target discovery is currently unavailable on my Windows setup.
The workaround is documented in README.md, which records that the Cap Windows target command returns an empty list and that pywin32 top-level enumeration is used as a consumer-side fallback. pywin32 is not doing the recording; it is only discovering an HWND and allowing my runner to identify the browser window.
The browser and cursor implementation in camoufox_capability.py is separate from this failure. It drives Camoufox and harvests browser events; it does not provide Cap target enumeration and is not the source of the empty Windows target array.
HWND and Cap identifier relationship
The Windows implementation in crates/scap-targets/src/platform/win.rs wraps the native HWND in WindowImpl and converts the handle value to WindowIdImpl with the underlying numeric value cast to u64. Therefore, a live numeric HWND returned by win32gui.EnumWindows() is conceptually the same native identifier that Cap uses for a Windows WindowId, provided the window is still valid when recording begins.
My current understanding is therefore:
win32gui.EnumWindows()discovers valid top-level HWNDs on Windows.- Cap'''s
WindowImpl::list()usesEnumChildWindowsfrom the desktop window, which enumerates children of the desktop rather than top-level application windows. - The fix should switch
WindowImpl::list()to useEnumWindowsdirectly, matching the pattern already present inget_topmost_at_cursor_fallback().
Scope of this issue
The primary scope is Windows target discovery for the Cap CLI, specifically the path from apps/cli/src/targets.rs through crates/recording/src/sources/screen_capture/mod.rs into crates/scap-targets/src/platform/win.rs.
This is distinct from a second possible limitation: a discovered window ID may still produce display-region cropping rather than true isolated window-content capture. The Windows capture implementation in crates/recording/src/sources/screen_capture/windows.rs configures Direct3D capture and applies crop_bounds, while the shared crop calculation is in crates/recording/src/sources/screen_capture/mod.rs. Fixing enumeration should restore target discoverability, but it must not be presented as automatically fixing capture semantics or crop correctness.
macOS caveat and platform-agnostic contract:
The macOS implementation is separate in crates/scap-targets/src/platform/macos.rs, where native Core Graphics window identifiers are used instead of Windows HWNDs. The macOS capture source is crates/recording/src/sources/screen_capture/macos.rs. The platform-specific discovery code must remain platform-appropriate.
However, the shared contract is the same across platforms: a listed window target must resolve to the native window, its ID must be passed unchanged through the CLI and recording layers, and --window should represent the window content rather than merely a rectangle on a display. Existing macOS reports such as Cap issue #1754 indicate that target discovery and window-content capture are separate concerns. I will keep the Windows discovery fix focused while adding or proposing cross-platform regression coverage at the shared target boundary where appropriate.
Intended resolution in a new PR
I intend to fork or branch the Cap repository, reproduce the failure on the Windows environment listed above, and submit a new PR that:
- Corrects the Windows window-list enumeration path in
crates/scap-targets/src/platform/win.rsso visible top-level application windows can be discovered by the CLI. - Preserves and verifies the existing validity filters, including visibility, child-window exclusion, tool-window exclusion, ignored executables, and exclusion of Cap-owned windows where those filters are intentional.
- Verifies that a returned native HWND becomes the expected
WindowIdand survives serialization throughapps/cli/src/targets.rs. - Adds a regression test or the strongest available platform test proving that a visible top-level Windows test window appears in
cap targets windows --jsonwith a usable ID. - Verifies the ID handoff into
cap record start --windowwithout claiming that target discovery alone resolves all window-cropping behavior. - Keeps macOS and other platform implementations unchanged unless shared tests expose a genuine contract problem; platform-specific native enumeration should not be replaced with a Windows-specific assumption.
Acceptance criteria
- On Windows, with a visible eligible application window open,
cap targets windows --jsonreturns at least one target instead of[]. - The returned target contains a numeric ID that corresponds to the native Windows window handle representation used by
crates/scap-targets/src/platform/win.rs. - The ID can be supplied to
cap record start --window <id>without requiringpywin32in the consuming application. - Invalid, hidden, child, tool, ignored, and Cap-owned windows remain filtered according to the intended behavior of the existing implementation.
- The existing screen fallback in
run.pyremains valid for consumers while the new PR is developed. - Window-content isolation and crop behavior are tested or explicitly tracked separately from the empty-target discovery bug.
I am opening this issue before implementing the new PR so the failure boundary, consumer workaround, Windows-specific evidence, cross-platform caveat, and intended acceptance criteria are explicit.
- 主要言語
- Rust
- スター
- 22.5k
- フォーク
- 1.9k
- 平均マージ
- 6時間 33分
- マージ済み PR(30日)
- 77
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
CapSoftware/Cap のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
CapSoftware/Cap#2305 · コメント 2 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
CapSoftware/Cap#1714 · コメント 3 件 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 58/100
CapSoftware/Cap#2329 · コメント 2 件 ·
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
CapSoftware/Cap#2328 · コメント 1 件 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 67/100
CapSoftware/Cap#2324 · コメント 1 件 ·
CapSoftware/Cap の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
-
state:needs triage
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
zed-industries/zed#64680 · コメント 2 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
RustPython/RustPython#8802 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
TheLarkInn/aipm#2390 ·