window-grabber
Monitor processes
from window_capture import Match, WindowMonitor, Target, recommended_capture_mode
with WindowMonitor(
[Target(process_name="example.exe"),
Target(process_name=Match.glob("agent*.exe", ignore_case=True))],
mode=recommended_capture_mode(),
) as monitor:
events = monitor.poll()
recommended_capture_mode() selects an initial policy without starting workers:
| Executing host | Recommended mode |
|---|---|
| Windows 7/8/8.1 and Windows 10 before build 19041 | controlled_desktop |
| Windows 10 build 19041 (2004) or later, including Windows 11 | isolated |
| Linux | isolated |
Other platforms raise RuntimeError. This is an OS recommendation, not a
capability probe or a guarantee that a particular window can be captured.
The Windows boundary includes the cursor-exclusion API required by default capture options.
Capture failures never change the selected mode. Omitting mode keeps the existing
default policy; using this helper explicitly permits desktop changes on older Windows.
For RPyC, evaluate the helper and construct objects through the remote module:
wc = conn.modules["window_capture"]
monitor = wc.WindowMonitor(
wc.Target(process_name="example.exe"),
mode=wc.recommended_capture_mode(),
)
try:
events = monitor.poll()
finally:
monitor.close()
Install the package on the remote machine and run the capture process in the target user's interactive Windows session. The helper evaluates that remote host; complete monitor-over-RPyC integration has not yet been qualified.
Use mode="isolated" for source-isolated capture without desktop changes.
Controlled desktop mode manages temporary visibility, topmost placement and Windows
popup separation, then restores the windows. It disables explicit foreground
activation to avoid cross-process input-state changes. Add allow_resize=True only if
layout-changing resizing is acceptable. Each popup has its own screenshot.
Target fields combine with AND, target lists with OR; matching windows are
captured once. Names, paths, classes and window_title accept literal strings,
Match.regex(...), or Match.glob(...). Patterns match the whole value; glob
supports * and ?. The original flags remain available for advanced callers.
See revision nine for matching,
mode conflicts, shared discovery deadlines and platform limits.
A typed Python API backed by precompiled, isolated Rust helpers. The coordinator owns desktop admission and recovery records. Native acquisition and potentially blocking window operations stay outside the caller's Python process.
from window_capture import CaptureSession, Target
with CaptureSession(mode="controlled_desktop") as session:
result = session.capture(Target(process_name="viewer.exe"))
if result.accepted:
result.require_image().save("viewer.png")
else:
print(result.reason, result.message, result.recovery)
for event in session.events.poll():
print(event.kind, event.reason)
All supplied selectors use AND semantics; ambiguity fails. The default total budget is 2,000 ms, including startup, queueing, fallback, encoding and restoration. Desktop changes require controlled mode or explicit advanced desktop permission. PNG encoding is timed; explicitly saving the returned image is separate file I/O. RGBA output contains immutable straight-alpha bytes and must be encoded explicitly before saving.
image.save(destination) accepts a path, BytesIO, or an open binary file:
from io import BytesIO
buffer = BytesIO()
event.require_image().save(buffer)
png_bytes = buffer.getvalue()
with open("capture.png", "wb") as output:
event.require_image().save(output)
Saving writes the existing PNG at the stream's current position. Caller-owned
streams are not closed, flushed or rewound; call buffer.seek(0) before reading
with read(). RGBA images still require explicit encoding. Write errors propagate
and can leave partial output.
With RPyC, a path is opened on the host executing save(). A stream proxy writes
to the host owning that stream. For example, if remote_event is a remote event:
buffer = BytesIO() # Lives on this client.
remote_event.require_image().save(buffer)
png_bytes = buffer.getvalue() # Screenshot transferred to this client.
The connection must permit the remote save call and callback access to the
stream's write method. A restricted service can expose a dedicated writer object
instead. Saving uses bounded byte chunks and supports short writes; it does not
change connection permissions or initiate another capture. RPyC remains optional.
Process monitoring
Each visible top-level window has its own capture and change history. Owned popups are discovered and captured separately at their full size; their pixels are never composited onto the owner's screenshot. Child controls remain part of their window.
from pathlib import Path
import time
from window_capture import ChangeOptions, WindowMonitor, Target
output = Path("captures")
output.mkdir(exist_ok=True)
with WindowMonitor(
Target(process_name="viewer.exe"),
changes=ChangeOptions(channel_tolerance=3, min_changed_pixels=1),
) as monitor:
while True:
started = time.monotonic()
for event in monitor.poll():
if event.image is not None:
handle = event.window.hwnd or event.window.x11_window_id
event.image.save(output / f"{handle}-{event.result.request_id}.png")
elif event.result is not None:
print(event.kind, event.result.reason)
time.sleep(max(0, 5 - (time.monotonic() - started)))
poll() returns opened, changed, geometry_changed, disappeared,
capture_failed, and capture_recovered events. Repeated failures with the same
reason are suppressed, while capture retries continue every poll. A changed
reason emits another failure. Recovery emits metadata even when pixels are
unchanged; event.image is None for recovery events. Changed images are delivered
separately. See revision ten.
An absent process produces no images; a newly visible popup gets its own opened
event. Disappearance includes hiding and no longer matching the selector. Failed
captures expose no image and preserve the last good baseline. Failed discovery
raises CaptureError and preserves the previous window set.
Comparison uses decoded RGBA pixels, independent of PNG compression. A pixel
counts as changed when any channel differs by more than 3 on the 0–255 scale.
The default reports even one such pixel to preserve small text and indicators.
Increase min_changed_pixels to suppress isolated speckles, accepting that equally
small real changes will also be ignored. Comparisons use the last reported
image, so cumulative drift is detected. Use poll(sink=...) to commit comparison history only after successful batch saving. It does not use a whole-image percentage rule.
Default monitoring enforces source_isolated acquisition and returns per-window
capture failures instead of raising them. Controlled desktop mode explicitly permits
observed-clear desktop captures. Each
window receives its own capture deadline; a full poll also includes discovery and
comparison. The caller schedules polls and file writes. Defaults limit discovery
to 64 windows and retained RGBA baselines to 128 MiB; transient captures, decoding
buffers and caller-held images require additional memory. Wayland portals do not
provide this process-enumeration API.
WGC can refuse a popup that Windows does not expose as a capturable item, including the non-Alt+Tab popup in our Server 2022 fixture. The default monitor then tries PrintWindow for that popup. A GPU-rendered popup that neither route can capture remains an explicit failure; the engine cannot promise coverage of every renderer.
Capture routes
| Platform | Implemented routes | Capability boundaries |
|---|---|---|
| Modern Windows | Windows Graphics Capture, PrintWindow, DXGI Desktop Duplication, GDI desktop | WGC isolates a window surface and supports cursor/alpha options. DXGI and GDI require permitted desktop control and report observed visibility. |
| Windows 7 SP1 build target | PrintWindow and GDI desktop, separate x86/x64 helpers | Modern WinRT/DXGI capture imports are excluded. Physical Windows 7 runtime and GPU qualification remain release gates. |
| Linux X11 | Composite named pixmap and controlled root-desktop capture | Local X11, opaque SDR TrueColor; client and child content. Reparented window-manager decorations are refused for whole-window capture. |
| Linux Wayland | Explicit portal window selection and a persistent PipeWire stream | Requires the packaged portal helper, system PipeWire and a ScreenCast portal. Complete selected window, hidden cursor, opaque BGRx; permission is prepared separately. |
Windows supports HWND/PID, executable name/path, class and title selectors. X11 supports XID/PID, executable name/path, class and title selectors where local process identity can be established. Wayland uses a source selected by the user:
from window_capture import CaptureSession, Target
with CaptureSession() as session:
source = session.select_window() # interactive; separate from capture's deadline
result = session.capture(Target(portal_source=source))
Prepared sources belong to that session and native process. Closure, worker death,
idle expiry or stream loss invalidates a source; capture never silently reconnects
or opens another selection dialog. select_window(timeout_ms=...) can bound the
interaction. prepare() probes capabilities and warms the coordinator process;
individual Windows acquisition processes still create fresh devices/frame pools.
Evidence and safety
CAPTURED means the candidate met the requested policy and required restoration
completed. source_isolated describes acquisition scope; it is not independent
pixel verification. Desktop routes report at most no_overlap_observed, because
window events and geometry checks cannot atomically lock out unrelated programs.
Suspicious uniform regions produce INDETERMINATE, including legitimate content
that a generic capture engine cannot explain. Rejected pixels are never exposed
as an accepted image.
The implementation separates configuration, IPC, process ownership, selection, mutation, pixel acquisition, validation and encoding. Resources have scoped owners; OS-specific unsafe code stays in native boundary modules. Pull-based diagnostic events have bounded capacity and overflow counts, with no user callbacks in the request path and no screenshots in diagnostic events or object representations. Process-monitor events deliberately expose accepted new/changed images to callers.
Capture admission currently serializes native acquisitions. Windows scopes it to the session/window station/desktop. Linux conservatively shares one local-host scope across X11, XWayland and Wayland, so environment aliases cannot create competing leases. Cross-account access fails unless a shared service is supplied; remote X11 capture is not coordinated by this package.
Unconfirmed mutation or restoration leaves a durable journal and quarantines new captures. An abandoned OS lock or elapsed timeout does not clear it. See recovery and limitations before operating a quarantined desktop.
Wheels and development
End-user installation needs no Rust, Cargo, C/C++ compiler or first-use downloads:
python -m pip install --no-index --find-links=dist --only-binary=:all: window-grabber
window-grabber --probe
Each Windows wheel includes architecture-matched modern and legacy executables; the Python facade selects the legacy binary on pre-Windows-10 systems. Linux wheels include separate X11 and optional portal executables, so absent PipeWire libraries do not prevent the X11 helper from starting. The current Linux CI build baseline is Ubuntu 24.04 x86_64; these are Linux platform wheels, not a claim of compatibility with every glibc baseline. PipeWire's shared library is an explicit system runtime dependency for the portal helper.
Maintainer builds use the pinned toolchains and Cargo.lock. The complete combined wheel commands live in .github/workflows/tests.yml. For local logic checks:
cargo test --locked --workspace
cargo fmt --all --check
cargo clippy --locked --workspace --all-targets -- -D warnings
python -m ruff check src tests scripts setup.py
python -m ruff format --check src tests scripts setup.py
PYTHONPATH=src python -m pytest tests -m "not windows and not linux"
CI tests installed wheels outside the source tree, with offline installation and Windows build tools removed from PATH. The desktop tests compare every pixel of independent GDI/child/OpenGL fixtures, test fallback/timeouts and concurrency, and verify restoration. Xvfb exercises installed X11 routes. Portal unit tests and installed-helper startup checks do not establish compositor interoperability.
Production qualification
Version 0.1.0 is the first production release of the documented capture routes. Unsupported operations continue to return explicit outcomes. Hosted CI does not establish physical Windows 7 GPU support, HDR/alpha accuracy, real compositor behavior, antivirus compatibility, or the proposed 99.5% success-within-two-seconds gate. Group movement of visible popups, Vulkan hooks and generic application adapters remain unavailable. No automatic privilege escalation or endpoint-policy changes are performed. Releases publish precompiled Windows wheels to PyPI; source distributions are disabled.
Opt-in renderer hooks
The experimental renderer_hook backend attaches to already-running, matching
x86/x64 processes. It supports OpenGL 1.x/2.x compatibility contexts with a static
executable SwapBuffers import, Direct3D 9 primary Present, and Direct3D 11 DXGI
Present/Present1, plus experimental Direct3D 12 readback using a queue observed
inside DXGI presentation. Each captures one client surface; select popups separately by
HWND. Direct3D formats and state are deliberately restricted, including no MSAA.
Direct3D 11 requires existing multithread protection and rejects partial updates.
Direct3D 12 requires D3D11On12, IDXGISwapChain3 and an unambiguous observed queue;
it does not guess from unrelated submissions. Vulkan readback remains unsupported.
result = capture(
Target(hwnd=hwnd),
options=CaptureOptions(
allow_injection=True,
backend_order=("renderer_hook", "print_window_full", "print_window"),
content=ContentOptions(area="client", include_children=False),
),
)
The precompiled DLL is packaged with the Windows wheel. The hook is never enabled by a preset. It remains loaded and its presentation interception remains installed until the target exits, but readback requires an active request. Readback executes on the target's render thread and can stall it; the caller deadline cannot preempt that driver call. Hook errors are recorded without assuming antivirus caused them. An exclusion for Python does not establish that the DLL is permitted.
See revision sixteen for the compatibility limits, lifetime rules and tests. Hardware GPU and Windows 7 runtime qualification remain outstanding.
Use the qualification guide to measure required deployments. The runner separates failures from accepted latency and requires an independent reference to pass a scenario. Exact commit results are recorded in GitHub Actions.
Revision five defines separate-window capture and process monitoring, superseding earlier popup-composition behavior. Revision four records the underlying implementation; revision two retains the remaining intended contract and production gates. Earlier proposals remain history.
Scheduled process recording
python -m window_capture --monitor --process-name example.exe --interval-seconds 5 --output captures
Each popup is saved independently. Complete batch-* directories contain PNGs
and a manifest; incomplete pending-* directories are not recordings. A failed
save does not advance comparison baselines. The interval is a wait after each
finished poll. Restarting records fresh baselines, so duplicate images are
possible. The default 1 GiB limit covers new output per recorder instance; arrange
archive retention separately. See revision six
for API usage, code-review scope, GPU evidence and remaining production gates.
Crowded desktops and popup foreground capture
Enable DesktopOptions(enabled=True, separate_owned_popups=True, resize_to_fit=True)
to permit temporary parent resizing when popups cannot be moved aside, and to
raise an owned popup independently for its own screenshot. The complete owner
group is journaled and restored. Resizing can change layout and hide content.
The monitor reports different image dimensions as geometry_changed, with a
fresh baseline, rather than treating them as pixel content changes.
CLI: --desktop --separate-owned-popups --resize-to-fit.
See design revision eight.
External VM screenshots after a capture failure
A Windows monitor event offers an explicit desktop transaction:
if event.result is not None and not event.result.accepted:
with event.bring_to_top(max_hold_ms=10000) as presented:
# Your own vCenter client call; obtain a fresh VM screenshot here.
screenshot = take_vm_screenshot()
# Only publish after exit confirms restoration and observation checks.
save_vm_screenshot(screenshot, guest_bounds=presented.bounds)
take_vm_screenshot and save_vm_screenshot are caller-provided functions.
The context manager uses the event's complete window identity, separates owned
popups, temporarily makes the target topmost, and restores journaled state. It
holds the same desktop lock as normal captures. allow_resize=True explicitly
permits layout changes when needed. A custom coordinator must match the monitor's
configuration. This operation explicitly permits desktop changes even when the
monitor uses isolated mode.
The hold limit is 100–60000 ms; setup and restoration have separate bounded budgets. Context exit, caller-pipe closure, and native hold expiry initiate restoration. A crashed/hung native transaction can leave a recovery journal and quarantine; restoration errors are raised, never silently treated as success. Expiry or observed window changes also raise, even if restoration completed.
presented.bounds uses guest desktop pixels. Map these to the vCenter image using
its resolution/display arrangement; do not assume a multi-monitor VM screenshot
shares that origin. Success reports observation and restoration, not external
image freshness, completeness, or atomic freedom from overlap. The external image
never changes the monitor baseline or automatically marks capture as recovered.
With RPyC, call bring_to_top() on the remote event and keep the context open while
the local caller requests the vCenter screenshot. The native hold timer remains
active if that connection is lost. Full vCenter/RPyC integration is not qualified.
Capture fallback and confidence
The default Windows order is WGC, print_window_full, print_window, desktop
DXGI, then desktop GDI. The default attempt limit is five; caller limits and the
single shared deadline still apply. Desktop backends are eligible only when the
selected policy permits them. Full-content PrintWindow uses PW_RENDERFULLCONTENT
on Windows 8.1 or newer; ordinary PrintWindow remains a separate fallback.
Neither method is a universal GPU-content guarantee.
WGC uses cursor control when the interface exists. Older WGC can retain its default
cursor inclusion when requested, but cannot satisfy cursor exclusion; that produces
CURSOR_CONTROL_UNAVAILABLE. The recommended mode therefore uses Windows 10 2004
as its boundary for default cursor-free capture.
Suspicious uniform regions trigger fallback. If later attempts also fail, the
suspicious result is preserved as CONTENT_UNCERTAIN, with the attempt history and
an optional diagnostic candidate image. The cause remains unknown; matching black
outputs do not prove that the application intended black content. Restoration
failures remain authoritative. content_evidence describes checks and suspicious
regions; completeness="not_established" explicitly distinguishes these checks
from proof that all application content was rendered. pixel_assurance="checked"
means the configured checks ran, not reference-image verification.
Monitor baselines retain their backend and occlusion evidence. A change in capture
source emits capture_source_changed with an image and starts a new baseline,
even if pixels are identical. Geometry changes retain their existing event type.
An uncertain result never replaces either baseline pixels or baseline source.
Release automation
Merges to main run final tests, create a version tag and GitHub release, then
publish the tested Windows wheels to PyPI. See the release guide
for version bumps, Trusted Publisher setup, Linux distribution, and failed-job retries.
Inspecting windows
Use the existing find_windows() API with or without a Target:
from window_capture import DiscoveryOptions, find_windows, Target
windows = find_windows(best_effort=True) # visible top-level windows
app_windows = find_windows(
Target(pid=1234),
discovery=DiscoveryOptions(include_children=True, include_hidden=True),
)
for window in app_windows:
print(window.hwnd, window.kind, window.process_path)
On Windows, each fully inspected record contains the existing stable identity
(HWND, PID, TID, process creation time, class, root and owner), plus title,
process_name, process_path, parent_hwnd, bounds, visible, minimized,
maximized, foreground, cloaked, is_child, is_popup, and kind.
kind is a structural classification: child, dialog (class #32770), owned
popup, or top-level. It does not identify an application's semantic UI role.
foreground means the foreground HWND; visibility does not establish that a
window is unobscured or capturable. Enumeration covers the caller's desktop,
not other sessions, secure desktops, or message-only windows.
DiscoveryOptions.include_children also searches descendants of enumerated top-level windows.
For an explicit HWND, inspection remains limited to that HWND. An explicit HWND
can be inspected even when hidden. include_hidden, disabling include_minimized, and best_effort currently
require Windows; Linux X11 retains its existing identity records and strict
selection. Wayland does not provide general desktop window enumeration.
Optional fields may be null with a reason in inspection_errors; for example,
cloaking information is unavailable on Windows 7. Strict discovery is the default.
With best_effort=True, inaccessible or disappearing candidates can instead
produce a partial record containing hwnd, inspection_error, and
matches_filter=None. Such records are not confirmed matches: missing
identity or process data must never be treated as proof of unique selection.
Deadlines and enumeration/response limits still fail the call, rather than
silently returning a truncated inventory. All returned records are immutable.
Window state is sampled, not an atomic desktop snapshot. A window can disappear immediately after discovery; capture independently resolves and revalidates its target. Discovery does not activate, move, resize, or capture windows.
Window context in capture events
On Windows, CaptureResult.window contains the same description sampled after
unique target resolution, before any capture-related desktop changes. It is
also available on CaptureEvent.window, including capture failures after
resolution. If inspection detects a changed identity, optional sampled fields
are discarded and inspection_errors.identity reports TARGET_CHANGED.
This context does not replace identity_evidence or prove capture succeeded.
result.requested_target and event.requested_target retain the requested
selectors even when no window could be resolved or the worker failed. In that
case window is None. Portal tokens are excluded. Discovery, capture and monitor events
include titles and full process paths whenever readable. Window details and requested selectors are omitted
from object representations and can be marked {"truncated": True} to preserve
the metadata budget. Events retain metadata only, without images.
Poll every window, capture selected windows
WindowMonitor separates discovery from capture through capture_when.
Omit the target to poll all visible top-level windows, or supply the usual
process selectors to narrow discovery:
from window_capture import DiscoveryOptions, WindowMonitor
with WindowMonitor(
discovery=DiscoveryOptions(include_hidden=True, include_children=True),
max_windows=512,
capture_when=lambda window: (window.process_name or "").lower() == "myapp.exe",
) as monitor:
for event in monitor.poll(): # schedule subsequent polls in your application
print(event.kind, event.window.hwnd, event.window.pid)
if event.image is not None:
event.image.save("capture-{}.png".format(event.window.hwnd))
The predicate receives an immutable window description and must return bool.
It runs once per discovered window per poll, on the caller's thread. All decisions
are evaluated before any capture starts; an exception or invalid return leaves
history unchanged and starts no captures. Predicate execution is outside native
capture deadlines. Title/path-based predicates receive readable metadata automatically.
A False decision performs no pixel acquisition for that window. It still emits
opened, metadata_changed, and disappeared events with result=None and
image=None. Unchanged windows produce no repeated event. Switching from capture
to observation emits capture_skipped and releases that window's pixel baseline;
selecting it again starts a fresh baseline. Predicate decisions and observation
history commit only after the sink succeeds, matching the existing retry contract.
max_windows bounds all discovered windows, including those excluded from capture.
Windows monitoring tolerates per-window inspection failures. Unconfirmed candidates
emit inspection_failed with window.inspection_error; they are not passed to the
capture predicate and are not captured. Previously observed identities and image
baselines remain intact while the same handle cannot be inspected. Successful
inspection emits inspection_recovered. Repeated identical inspection failures
are suppressed. An absent handle in a completed enumeration can then disappear.
Global discovery failures, deadlines and resource limits still fail the poll without
committing partial history. Direct find_windows() remains strict unless passed
best_effort=True. Linux discovery remains strict.
For an explicit fresh screenshot from an observation-only event:
result = event.capture(mode=recommended_capture_mode())
if result.accepted:
result.require_image().save("debug.png")
# Optional: requires Pillow on the machine executing this call.
result.require_image().to_pillow().show()
else:
print(result.reason, result.message)
event.capture() creates a separate capture session and leaves the event and monitor
history unchanged. It validates the sampled identity natively before acquisition
or desktop changes; stale identities fail with TARGET_CHANGED. Unconfirmed
inspection events fail with TARGET_UNVERIFIED until a fresh verified event exists.
Use CaptureSession.capture_window(event.window) to reuse your own session.
require_image() continues to return only existing pixels. to_pillow() returns
an independent loaded image; .show() explicitly opens a viewer on its executing
host, including when invoked through RPyC.
Minimized windows and owned popups are already included in Windows top-level
inventory when their visible flag is set. include_minimized=True is the default;
set it to False to exclude minimized windows. include_hidden=True includes
windows whose visible flag is unset, and include_children=True includes nested
controls/rendering windows. These same DiscoveryOptions apply to find_windows()
and WindowMonitor. Observing a minimized or hidden window does not establish
that a requested capture backend can acquire its pixels.
WindowMonitor accepts any native Target selector, a sequence of selectors, or
no selector. ProcessMonitor remains an alias for existing callers; new code can
use the clearer WindowMonitor name. Capture options still control how pixels are
acquired; discovery options only control which windows are observed.
Typed windows, events, and images
find_windows() returns immutable Pydantic Window models. WindowEvent,
CaptureEvent, CaptureResult, CapturedImage, CapabilityReport, and
CloseReport are Pydantic models too. Use attributes such as window.pid,
window.process_path, and window.bounds.left; unknown or unreadable
window fields are None. Constructors use named arguments. .schema() exposes
types and field descriptions; .dict() and .json() serialize models. Image
bytes are excluded from model serialization and object representations.
Window.metadata() serializes supplied fields only, preserving omitted fields.
For example, choose specific executables and save only available screenshots:
from window_capture import WindowMonitor
paths = {r"C:\Apps\First.exe", r"C:\Apps\Second.exe"}
with WindowMonitor(
capture_when=lambda window: window.process_path in paths,
) as monitor:
for event in monitor.poll():
if event.image is not None:
event.image.save("capture-{}.png".format(event.window.hwnd))
else:
print(event.kind, event.window.pid)
Membership above compares exact strings; normalize spelling/case explicitly if
that is your desired policy. Target(process_path=...) remains the native
file-identity selector when aliases/junctions must resolve to the same file.
event.require_image().save("capture.png") is the explicit alternative when an
image is required. It returns the existing screenshot and never starts another
capture. It raises CaptureNotRequested when event.result is None, CaptureError
(with .result) when capture failed, or ImageUnavailable for a metadata-only
recovery notification. Merely polling an excluded window raises nothing;
event.image is None. The metadata-only CaptureEvents buffer does not retain
images; use WindowMonitor image events or the returned CaptureResult.
Models use Pydantic's v1-compatible API (also provided by Pydantic 2). Python 3.8
installations require Pydantic 1.x, avoiding the newer pydantic-core binary's
Windows 7 import incompatibility.
Options such as Target, DiscoveryOptions, and CaptureOptions remain immutable
dataclasses. Model updates are revalidated by the library; Pydantic's ordinary
copy(update=...) is not a validation boundary.
HDR desktops and approximate colors
The default ImageOptions(hdr_policy="allow") permits GDI, PrintWindow, and
DXGI capture when HDR is enabled. Colors may be clipped or shifted; legacy
backend results describe this in result.diagnostics["color_evidence"].
WGC continues to tone map its floating-point HDR frames.
Use hdr_policy="tone_map" to require explicit HDR tone mapping, or "reject"
to reject active HDR. Color checks apply to displays overlapping the capture
rectangle. Unknown legacy display color state is reported without blocking the
default policy. Buffer validation, unwritten-pixel detection, blank-region checks,
window identity checks, and desktop obstruction checks still apply. Blank checks
are heuristics: they cannot prove that every nonuniform screenshot is correct.
Window display-affinity restrictions reject compositor/desktop attempts, which
could otherwise return blank pixels or the background behind an excluded window.
Fallback continues to configured application-rendered backends such as PrintWindow.
Those attempts must pass the same pixel validation; the library never changes the
window's display-affinity flag. A fallback can still fail if the application does
not provide usable pixels. Inspect result.attempts for each backend's outcome.
Presentation preparation has its own budget:
event.bring_to_top(prepare_timeout_ms=10000, max_hold_ms=10000). The first
limit covers queueing, helper startup, and window preparation; the second covers
the ready interval. If entry times out, release is requested and a bounded cleanup
wait retains the native failure and recovery details when available.
Hidden Windows input helpers (IME and MSCTFIME UI) may migrate between owners
on the same UI thread when a window is restored. Their transient ownership is
excluded from popup rollback comparisons; visible popups and ordinary hidden
application windows remain checked. Off-screen PrintWindow targets use an SDR DIB
and do not require an overlapping display for tone_map or reject. On-screen
color checks include every overlapping output, even a narrow overlap across monitors.
bring_to_top() acquires no pixels, so display-affinity exclusion does not block
its external observation interval. It still checks window identity and obstruction,
and leaves display affinity unchanged. This does not make an excluded window
available to GDI or other screen-capture APIs.
Windows desktop recovery
Desktop preparation applies to explicitly enabled capture backends, including
PrintWindow and PrintWindowFull. It restores minimized windows and may move,
resize, promote, or activate them according to DesktopOptions, then restores
recorded placement. Application-owned child layout is not rollback state.
If restoration cannot be confirmed, failures retain the native cause and the
journal location in result.diagnostics. The durable transaction records the
last stage and expected state. Further mutations first attempt bounded recovery
when a previous restoration worker completed. Source-isolated capture can still
attempt acquisition without preparation while a transaction remains pending.
with CaptureSession() as session:
report = session.recover_desktop(action="inspect")
print(report.dict())
report = session.recover_desktop() # Bounded, identity-checked recovery.
RecoveryReport.state is "clear" or "pending". Interrupted workers and old
intent-only journals cannot be safely replayed: while their application process
is alive they remain pending. Close the affected application completely, then
retry recovery. Recovery can retire the journal after positively verifying that
every affected process lifetime has ended. For activation transactions this also
includes the saved active and foreground windows' processes. Access denial is not proof of process exit. There
is no force-delete operation, and inspection never changes window state.
Do not terminate unrelated applications blindly; the report identifies the
processes involved. An OS sign-out ends those desktop application lifetimes.
Windows 7 build/import audits and hosted Windows tests do not establish behavior on every Chrome build, physical GPU, HDR configuration, or legacy OS desktop.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file window_grabber-0.2.2-py3-none-win_amd64.whl.
File metadata
- Download URL: window_grabber-0.2.2-py3-none-win_amd64.whl
- Upload date:
- Size: 2.6 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
104e8f49daed2a14bb3f6e884a749ca7754c0b369f67faf0e362b5fef1cc019c
|
|
| MD5 |
33189f625093aeb02fb08bfc3b14e23e
|
|
| BLAKE2b-256 |
f9dba6d307cc8ac338e28eafaa80a6c06e3601b0fae2895a8082af779906e914
|
Provenance
The following attestation bundles were made for window_grabber-0.2.2-py3-none-win_amd64.whl:
Publisher:
release.yml on Heknon/window-grabber
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
window_grabber-0.2.2-py3-none-win_amd64.whl -
Subject digest:
104e8f49daed2a14bb3f6e884a749ca7754c0b369f67faf0e362b5fef1cc019c - Sigstore transparency entry: 2818931971
- Sigstore integration time:
-
Permalink:
Heknon/window-grabber@ac9d32bd4909e6ce57f3aea6f3bf344c11d777df -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Heknon
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ac9d32bd4909e6ce57f3aea6f3bf344c11d777df -
Trigger Event:
push
-
Statement type:
File details
Details for the file window_grabber-0.2.2-py3-none-win32.whl.
File metadata
- Download URL: window_grabber-0.2.2-py3-none-win32.whl
- Upload date:
- Size: 2.4 MB
- Tags: Python 3, Windows x86
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c9724e431ffaa164327284cd14d34122ccda065edf5d95543705358175ca725
|
|
| MD5 |
b89d0086f86a40f1a434c9d683ed0afb
|
|
| BLAKE2b-256 |
d58d19dd2d965012bb7ccc7ec7f169e115c1b141b7fb9d1bea06088b3ad4d570
|
Provenance
The following attestation bundles were made for window_grabber-0.2.2-py3-none-win32.whl:
Publisher:
release.yml on Heknon/window-grabber
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
window_grabber-0.2.2-py3-none-win32.whl -
Subject digest:
5c9724e431ffaa164327284cd14d34122ccda065edf5d95543705358175ca725 - Sigstore transparency entry: 2818931853
- Sigstore integration time:
-
Permalink:
Heknon/window-grabber@ac9d32bd4909e6ce57f3aea6f3bf344c11d777df -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Heknon
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ac9d32bd4909e6ce57f3aea6f3bf344c11d777df -
Trigger Event:
push
-
Statement type: