# Feature Landscape: Native DWM Window Capture

**Domain:** Windows DWM-based window capture APIs for flicker-free, occlusion-immune screenshots
**Researched:** 2026-04-12

## Current State

The project currently captures windows via monitor-crop fallback (`captureWindowViaMonitor` in `window-utils.ts`), which uses BitBlt to capture the entire monitor and crops to window bounds. This avoids PrintWindow flicker but captures overlapping windows. The v1.1 milestone aims to add a native C++ addon that reads directly from DWM composition surfaces, giving flicker-free capture that also ignores occlusion.

---

## API Comparison: Windows Window Capture Without PrintWindow

### Overview Matrix

| API | Type | Documented | Window-Specific | Ignores Occlusion | Ignores Minimized | Flicker-Free | Pixel Access | Complexity |
|-----|------|------------|-----------------|-------------------|-------------------|--------------|--------------|------------|
| **DwmGetDxSharedSurface** | Undocumented (user32) | NO | YES | YES | NO | YES | Via D3D shared resource | Medium |
| **Windows.Graphics.Capture (WGC)** | Official WinRT | YES | YES | YES | NO | YES | Via Direct3D11CaptureFramePool | High |
| **IDXGIOutputDuplication** | Official DXGI | YES | NO (monitor-level) | NO | N/A | YES | Via AcquireNextFrame | Medium |
| **DwmRegisterThumbnail** | Official (dwmapi) | YES | YES | YES | NO | YES | NO (render-only, no bitmap access) | Low |
| **BitBlt (current)** | Official GDI | YES | NO (screen-level) | NO | NO | YES | Direct bitmap | Low |
| **PrintWindow** | Official GDI | YES | YES | YES | NO | NO (flickers) | Direct bitmap | Low |

### Detailed Analysis

---

### 1. DwmGetDxSharedSurface -- RECOMMENDED

**What it is:** An undocumented function exported from `user32.dll` that returns a shared D3D surface handle for any DWM-composited window. DWM maintains offscreen textures for every window; this API exposes the handle to that texture.

**How it works:**
1. Obtain function pointer via `GetProcAddress(GetModuleHandle("USER32"), "DwmGetDxSharedSurface")`
2. Call with target HWND to get a shared `HANDLE`, adapter LUID, format, and update ID
3. Open the shared handle via `ID3D10Device::OpenSharedResource` or `ID3D11Device::OpenSharedResource`
4. Copy the GPU texture to a CPU-readable staging texture
5. Map the staging texture to read raw pixel data (BGRA format)

**Function signature** (reverse-engineered, used by OBS DWMCapture plugin):
```cpp
typedef BOOL (WINAPI *DwmGetDxSharedSurface_t)(
    HWND hwnd,
    HANDLE *phSurface,       // shared D3D surface handle
    LUID *pAdapterLuid,      // GPU adapter
    ULONG *pFmtWindow,       // surface format
    ULONG *pPresentFlags,    // presentation flags
    ULONGLONG *pWin32kUpdateId // frame update counter
);
```

**Strengths:**
- Reads directly from DWM's own composited texture -- true window isolation
- No flicker (no messages sent to target window)
- Ignores overlapping windows completely
- Fast (~5ms per capture on typical hardware)
- Works for all DWM-composited windows (GDI, DirectX, WPF, UWP, etc.)
- No yellow border indicator
- No user consent prompt required
- Works in unpackaged Win32 apps (our use case)
- Proven in production (OBS DWMCapture plugin)

**Weaknesses:**
- **Undocumented API** -- could break between Windows versions (HIGH risk)
- Parameters changed between Windows 7 and Windows 8+ (documented breakage)
- No Microsoft support or stability guarantee
- Requires D3D11 device initialization for texture access
- Does not work on minimized windows (DWM stops compositing them)
- Does not work inside VMs (returns "Invalid function")
- May not work for windows using DirectComposition or DXGI flip model (needs testing)

**Confidence:** MEDIUM -- Proven working in OBS plugin and AutoHotkey libraries on Windows 10/11, but undocumented status is a real stability risk. The update ID parameter can detect when the surface has actually changed (useful for idle compression).

**Compatibility evidence:**
- OBS DWMCapture plugin: works on Windows 8+ through Windows 10
- AutoHotkey wincapture library (2022): works on Windows 10/11
- Multiple community reports of continued functionality on Windows 11 22H2/23H2
- No confirmed reports of breakage on Windows 11 24H2 (LOW confidence -- could not verify)

---

### 2. Windows.Graphics.Capture (WGC)

**What it is:** Microsoft's official modern screen capture API, part of WinRT. Supports per-window and per-monitor capture via `IGraphicsCaptureItemInterop::CreateForWindow`.

**How it works:**
1. Initialize WinRT and create a D3D11 device
2. Get `IGraphicsCaptureItemInterop` activation factory
3. Call `CreateForWindow(hwnd, ...)` to get a `GraphicsCaptureItem`
4. Create a `Direct3D11CaptureFramePool` with the device
5. Start a `GraphicsCaptureSession`
6. Receive frames via `FrameArrived` callback or poll
7. Copy D3D11 texture to staging texture for CPU readback

**Strengths:**
- Officially documented and supported by Microsoft
- Will continue to work across Windows versions
- Ignores overlapping windows
- No flicker
- Handles DPI scaling correctly
- Supports both window and monitor capture

**Weaknesses:**
- **Yellow border indicator** shown around captured window by default
- Removing yellow border requires `GraphicsCaptureAccess.RequestAccessAsync(Borderless)` which:
  - Requires `graphicsCaptureWithoutBorder` capability in package manifest
  - Displays a **user consent prompt** (unacceptable for automated agent use)
  - For unpackaged Win32 apps, manifest integration is awkward
- Requires WinRT activation and COM infrastructure in the native addon
- Requires a message pump (`DispatcherQueue`) on the capture thread
- More complex C++ implementation (WinRT/C++ interop, cppwinrt headers)
- Minimum Windows 10 1903 (acceptable for our Windows 11 target)
- Higher CPU overhead than DwmGetDxSharedSurface (~200-800us vs ~5ms total including copy)

**Yellow border verdict:** The yellow border is a **dealbreaker for agent use**. An AI agent capturing windows to debug UI issues cannot have a yellow border modifying the very UI it is trying to observe. The borderless path requires user interaction (consent prompt), which defeats the purpose of automated capture. On recent Windows 11 builds (24H2+), OBS reportedly no longer shows the border, but this appears to be an OS-level change, not an API-level guarantee.

**Confidence:** HIGH for API stability, LOW for borderless capture without user interaction in an unpackaged context.

---

### 3. IDXGIOutputDuplication (Desktop Duplication API)

**What it is:** Official DXGI API that duplicates an entire monitor's output. Captures the full desktop composited by DWM.

**How it works:**
1. Create D3D11 device and get DXGI adapter/output
2. Call `IDXGIOutput1::DuplicateOutput` to create duplication
3. Call `AcquireNextFrame` to get the current desktop texture
4. Crop to window coordinates

**Strengths:**
- Officially documented
- Very fast (~3x faster than BitBlt for full screen)
- No flicker, no messages to target window
- No yellow border

**Weaknesses:**
- **Captures entire monitor, not individual windows** -- same limitation as current BitBlt approach
- Overlapping windows WILL appear in capture (same problem we have now)
- Must manually crop to window bounds (we already do this)
- Cannot capture exclusive fullscreen apps
- Only marginal improvement over current monitor-crop approach

**Verdict:** Does not solve the occlusion problem. This is essentially a faster version of what we already do with BitBlt monitor capture + crop. Not worth the added D3D complexity for our use case.

**Confidence:** HIGH -- well documented, but does not meet requirements.

---

### 4. DwmRegisterThumbnail

**What it is:** Official DWM API that creates a live thumbnail of a source window rendered into a destination window.

**How it works:**
1. Call `DwmRegisterThumbnail(hwndDest, hwndSrc, &hthumbnail)` to establish a relationship
2. Call `DwmUpdateThumbnailProperties` to set size/position mapping
3. DWM renders source window content into destination window's client area

**Strengths:**
- Officially documented
- Ignores overlapping windows
- No flicker
- Simple API

**Weaknesses:**
- **No bitmap/pixel data access** -- DWM renders directly to screen; you cannot read the pixels back as a buffer
- You would need to create a hidden window, render the thumbnail into it, then use BitBlt/PrintWindow to capture THAT window -- circular problem
- Designed for taskbar previews and Aero Peek, not screenshot capture
- Top-level windows only

**Verdict:** Fundamentally unsuitable. The API renders thumbnails visually but provides no mechanism to extract pixel data. You would need a secondary capture of the rendered thumbnail, reintroducing the original problems.

**Confidence:** HIGH -- well understood limitation, confirmed by documentation.

---

## Table Stakes

Features that the DWM capture addon MUST have for v1.1.

| Feature | Why Expected | Complexity | Notes |
|---------|--------------|------------|-------|
| Flicker-free window capture | Primary motivation for the milestone; PrintWindow causes visible flicker | Medium | DwmGetDxSharedSurface reads composited surface, no WM_PRINT sent |
| Occlusion-immune capture | Second motivation; current monitor-crop captures overlapping windows | Medium | DWM surface is per-window, ignoring z-order |
| Drop-in CaptureTarget integration | Must work with existing session-manager, scheduler, grid-compiler pipeline | Low | Implement CaptureTarget interface, return PNG Buffer |
| Automatic fallback to monitor-crop | Not all windows may support DWM surface; minimized windows cannot be captured | Low | Try DWM first, catch failure, fall back to existing captureWindowViaMonitor |
| Prebuilt binary for Windows x64 | Users should not need C++ build tools to install | Medium | Use prebuildify or prebuild to ship .node binary |
| Window handle (HWND) input | Must accept the same handle/title lookup the existing window-utils uses | Low | findWindow returns node-screenshots Window with .id() = HWND |

## Differentiators

Features that add extra value beyond the core requirement.

| Feature | Value Proposition | Complexity | Notes |
|---------|-------------------|------------|-------|
| Frame change detection via updateId | DwmGetDxSharedSurface returns a Win32kUpdateId that increments when the surface changes; can skip unchanged frames without pixel comparison | Low | Replaces or augments existing idle-compressor pixel-compare |
| DPI-aware capture | Read actual surface dimensions from D3D texture desc rather than relying on window coordinate math with scale factors | Low | More accurate than current manual DPI scaling in window-utils |
| Cursor compositing option | Optionally overlay cursor position onto captured frame | Medium | Useful for agent debugging of mouse-interaction issues |
| ARM64 prebuilt binary | Support Windows on ARM devices | Medium | Additional prebuild target; same C++ code should compile |

## Anti-Features

Features to explicitly NOT build.

| Anti-Feature | Why Avoid | What to Do Instead |
|--------------|-----------|-------------------|
| Windows.Graphics.Capture (WGC) backend | Yellow border modifies captured UI; borderless requires user consent prompt incompatible with automated agents; complex WinRT/COM infrastructure for a periodic screenshot tool | Use DwmGetDxSharedSurface for DWM capture, fall back to monitor-crop |
| IDXGIOutputDuplication backend | Does not solve occlusion problem; same limitation as current BitBlt monitor-crop | Keep existing monitor-crop as fallback |
| DwmRegisterThumbnail backend | No pixel data access; would need secondary capture creating circular dependency | Not viable for screenshot extraction |
| Continuous/streaming capture | Overengineered for periodic screenshot use case; DwmGetDxSharedSurface is fast enough for per-frame polling | Stick with timer-based scheduler calling capture() |
| Cross-platform native addon | Milestone targets Windows 11 only; macOS/Linux have different capture APIs | Platform-specific addon, keep node-screenshots for other platforms |
| Video recording from DWM surface | Out of scope per PROJECT.md; grid image is the deliverable format | Use existing GIF exporter for animation needs |

## Feature Dependencies

```
DwmGetDxSharedSurface addon (native C++)
  -> D3D11 device initialization
  -> Shared surface handle opening
  -> GPU-to-CPU texture copy (staging texture)
  -> BGRA pixel buffer extraction
  -> PNG encoding (via sharp in JS, or stb_image_write in C++)

CaptureTarget integration
  -> DwmGetDxSharedSurface addon
  -> Existing findWindow (window-utils.ts)
  -> Fallback to captureWindowViaMonitor

Prebuilt binaries
  -> DwmGetDxSharedSurface addon compiles
  -> prebuildify or node-pre-gyp configuration
  -> CI build on Windows x64
```

## MVP Recommendation

Prioritize:
1. **DwmGetDxSharedSurface native addon** -- Core C++ NAPI module that takes an HWND, opens the DWM shared surface via D3D11, copies to staging texture, returns raw BGRA pixel buffer to JavaScript
2. **DwmWindowTarget class** -- New CaptureTarget implementation that calls the addon, converts BGRA buffer to PNG via sharp, with automatic fallback to monitor-crop on failure
3. **Auto-detection logic** -- Try DWM capture first; on failure (unsupported window, VM environment, API unavailable), transparently fall back to existing monitor-crop
4. **Prebuilt x64 binary** -- Ship compiled .node file so end users do not need build tools

Defer:
- **ARM64 binary**: Very few Windows ARM users currently; add when demand exists
- **Cursor compositing**: Nice-to-have but not core to the flicker/occlusion fix
- **Frame change detection via updateId**: Optimization; existing idle-compressor works

## Risk Assessment

| Risk | Severity | Mitigation |
|------|----------|------------|
| DwmGetDxSharedSurface removed/changed in future Windows update | HIGH | Automatic fallback to monitor-crop ensures the tool never breaks; monitor for Windows Insider builds |
| Surface handle fails for certain window types (e.g., UWP, DirectComposition) | MEDIUM | Test against diverse window types; fall back gracefully per-window |
| D3D11 device creation fails (no GPU, remote desktop, VM) | MEDIUM | Detect and fall back to monitor-crop; log warning |
| Prebuilt binary ABI compatibility across Node.js versions | LOW | Use NAPI (version-agnostic ABI); test against Node 18/20/22 |

## Sources

- [OBS DWMCapture plugin source (DWMCaptureSource.cpp)](https://github.com/notr1ch/DWMCapture/blob/master/DWMCaptureSource.cpp) -- Reference implementation of DwmGetDxSharedSurface capture
- [DwmGetDxSharedSurface undocumented API reference](https://undoc.airesoft.co.uk/user32.dll/DwmGetDxSharedSurface.php) -- Function signature and parameter documentation
- [AutoHotkey wincapture library](https://github.com/thqby/ahk2_lib/tree/master/wincapture) -- Alternative implementation showing DWM, DXGI, and WGC capture methods
- [IGraphicsCaptureItemInterop::CreateForWindow (Microsoft)](https://learn.microsoft.com/en-us/windows/win32/api/windows.graphics.capture.interop/nf-windows-graphics-capture-interop-igraphicscaptureiteminterop-createforwindow) -- Official WGC Win32 interop docs
- [GraphicsCaptureSession.IsBorderRequired (Microsoft)](https://learn.microsoft.com/en-us/uwp/api/windows.graphics.capture.graphicscapturesession.isborderrequired?view=winrt-26100) -- Yellow border control requirements
- [Desktop Duplication API (Microsoft)](https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api) -- IDXGIOutputDuplication documentation
- [DwmRegisterThumbnail (Microsoft)](https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmregisterthumbnail) -- Thumbnail API documentation
- [Win32CaptureSample (Microsoft)](https://github.com/robmikh/Win32CaptureSample) -- Official WGC sample for Win32 apps
- [OBS capture method comparison (community)](https://obsproject.com/forum/threads/for-capture-method-whats-the-difference-between-bitblt-and-windows-graphics-capture.127687/) -- BitBlt vs WGC performance and behavior
- [DwmDxGetWindowSharedSurface (Microsoft)](https://learn.microsoft.com/en-us/windows/win32/dwm/dwmdxgetwindowsharedsurface) -- Related documented API (driver-level)
- [xcap/node-screenshots (GitHub)](https://github.com/nashaofu/xcap) -- Underlying library uses PrintWindow for window capture on Windows
