windows.rs

crates/veilvoice-watch/src/windows.rs

veilvoice-watch · 606 lines · read the source here · or on GitHub

Windows detection, via the Capability Access Manager.

Where the answer lives

Windows records every application's use of the microphone and camera under HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ ConsentStore\{microphone,webcam}. Each application gets a subkey holding LastUsedTimeStart and LastUsedTimeStop as FILETIME values.

The rule that makes this a live view rather than a history: an application is using the device right now when its start time is non-zero and its stop time is zero. Windows clears the stop value on acquisition and writes it on release. This is the same bookkeeping that drives the taskbar privacy indicator, so what this reports is exactly what the OS itself believes.

Desktop programs live under a NonPackaged subkey, with their path encoded using # in place of each separator. Store apps appear directly, keyed by package family name.

One subprocess per capability, and why that had to be fixed

This originally asked reg.exe for the subkeys of the store, then spawned reg.exe twice more for every application it found to read the two timestamps. The count is 2 + 2n per capability, where n is how many applications have ever asked for that device.

Measured on the machine this was found on: 7 packaged and 19 desktop applications for the microphone, 6 packaged for the camera, which is 68 process creations per scan. One reg.exe spawn there costs 6.6 ms at its fastest, so a scan cost at least 449 ms, and that is the warm-cache best case rather than the typical one. The desktop application called scan on the user-interface thread every two seconds.

The result was a window that froze repeatedly, which is what "runs extremely slow and freezes every couple of seconds" meant in the report. Nothing was leaking and nothing was deadlocked: it was doing a great deal of work in the worst possible place.

reg query <key> /s prints the whole subtree, keys and values together, in one go. So the scan is now two spawns, one per capability, and parse_consent_dump does the rest in memory. Measured on the same machine: 45 ms for the whole scan, against 449 ms, and it no longer grows with the number of applications installed. The parser is a pure function over text, so it is tested against a captured dump on every platform rather than only on the one that can produce it.

The front end was fixed too, and separately: a scan that is fast is still not something to do on the thread that paints.

What it cannot give you

A PID. Windows tracks this per application, not per process, so DeviceUse::pid is None here. The trade is worth it: this sees packaged apps, background services and anything else the OS accounts for, which enumerating process handles would miss.

In plain words

Finds out which applications are using the microphone or camera on Windows.

Windows keeps that in its own records of what has been granted access and when, and this reads them. It reports per application rather than per running program, because that is how Windows stores it, and the difference is stated rather than papered over.

WHAT THIS FILE CONTAINS

606 lines defining 10 functions (1 public), 0 types and 2 constants. Everything below is read out of the source, so it cannot disagree with the code.

What happens when it runs. These are the ways in: public, and nothing else in this file calls them, so they are what an outside caller reaches first.

  • scan line 136
    reaches collect, decode_path, filetime_to_system, friendly_name, parse_consent_dump, query_tree, hex_value, no_window, reg_exe

WHAT CALLS WHAT

no_window line 88 reg_exe line 120 scan line 136 collect line 149 query_tree line 177 parse_consent_dump line 211 hex_value line 266 decode_path line 277 friendly_name line 282 filetime_to_system line 292 entry: a way in: public, and nothing in this file calls it helper: private to this file dashed: a call that goes back up, or across a wrapped rank The functions this file defines, and the calls between them. An edge means the callee's name appears, called, inside the caller's body. This is a syntactic reading, not a type-resolved one.

The functions this file defines, and the calls between them. An edge means the callee's name appears, called, inside the caller's body. This is a syntactic reading, not a type-resolved one.

The same graph as Mermaid source
%%{init: {"theme":"base","themeVariables":{"background":"#1a1b26","primaryColor":"#1f2335","primaryTextColor":"#c0caf5","primaryBorderColor":"#7aa2f7","secondaryColor":"#16161e","tertiaryColor":"#16161e","lineColor":"#737aa2","textColor":"#c0caf5","mainBkg":"#1f2335","nodeBorder":"#7aa2f7","clusterBkg":"#16161e","clusterBorder":"#2f3549","fontFamily":"ui-monospace, SFMono-Regular, Consolas, monospace","fontSize":"14px"}}}%%
flowchart TD
    n_no_window["no_window<br/>line 88"]
    n_reg_exe["reg_exe<br/>line 120"]
    n_scan(["scan<br/>line 136"])
    n_collect["collect<br/>line 149"]
    n_query_tree["query_tree<br/>line 177"]
    n_parse_consent_dump["parse_consent_dump<br/>line 211"]
    n_hex_value["hex_value<br/>line 266"]
    n_decode_path["decode_path<br/>line 277"]
    n_friendly_name["friendly_name<br/>line 282"]
    n_filetime_to_system["filetime_to_system<br/>line 292"]
    n_collect --> n_decode_path
    n_collect --> n_filetime_to_system
    n_collect --> n_friendly_name
    n_collect --> n_parse_consent_dump
    n_collect --> n_query_tree
    n_parse_consent_dump --> n_hex_value
    n_query_tree --> n_no_window
    n_query_tree --> n_reg_exe
    n_scan --> n_collect
    click n_no_window href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-watch/src/windows.rs#L88" "open the source"
    click n_reg_exe href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-watch/src/windows.rs#L120" "open the source"
    click n_scan href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-watch/src/windows.rs#L136" "open the source"
    click n_collect href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-watch/src/windows.rs#L149" "open the source"
    click n_query_tree href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-watch/src/windows.rs#L177" "open the source"
    click n_parse_consent_dump href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-watch/src/windows.rs#L211" "open the source"
    click n_hex_value href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-watch/src/windows.rs#L266" "open the source"
    click n_decode_path href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-watch/src/windows.rs#L277" "open the source"
    click n_friendly_name href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-watch/src/windows.rs#L282" "open the source"
    click n_filetime_to_system href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-watch/src/windows.rs#L292" "open the source"
    classDef entry fill:#1f2335,stroke:#7aa2f7,color:#c0caf5
    class n_scan entry
    classDef helper fill:#1f2335,stroke:#bb9af7,color:#c0caf5
    class n_no_window,n_reg_exe,n_collect,n_query_tree,n_parse_consent_dump,n_hex_value,n_decode_path,n_friendly_name,n_filetime_to_system helper

This site loads no third-party script, so it cannot run Mermaid; the diagram above is the same nodes and edges drawn by the generator instead. GitHub renders the source below directly.

ITEMS

ItemLineDocumentation
no_window fn88Spawn without a console window.
CONSENT_STORE const101The full hive name, not the HKCU abbreviation: reg query echoes subkey paths back in long form, and the reply has to be matched against what was asked for.
FILETIME_TO_UNIX_SECS const105FILETIME counts 100-nanosecond intervals from 1601-01-01; Unix time starts at 1970-01-01.
reg_exe fn120The absolute path of reg.exe, or None if it is not where it should be.
scan pub fn136
collect fn149Walk one capability's whole subtree, from a single reg query /s.
query_tree fn177One reg query <key> /s, printing the whole subtree.
parse_consent_dump pub(crate) fn211Pull (key, LastUsedTimeStart, LastUsedTimeStop) out of a /s dump.
hex_value fn266Name REG_QWORD 0x...
decode_path fn277Registry keys encode a path with # where a separator belongs.
friendly_name fn282The executable name, or the package family name for a Store app.
filetime_to_system fn292