Merge branch 'worktree-agent-a5cf35a576dde87f5' into worktree-dep-currency-wave

This commit is contained in:
2026-08-13 14:19:16 +02:00
9 changed files with 44 additions and 31 deletions
Generated
+5 -5
View File
@@ -5429,9 +5429,9 @@ dependencies = [
[[package]]
name = "wasapi"
version = "0.23.0"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80c3aa5d6b0e7acc3ea10cb19c334df0c8d825060f14a30d9e3b03385e6e5175"
checksum = "ee144c215d8d31e94c1a599fbaf540ca82a7017e4f1786816b179f334fac751d"
dependencies = [
"log",
"num-integer",
@@ -5895,13 +5895,13 @@ dependencies = [
[[package]]
name = "windows-service"
version = "0.7.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24d6bcc7f734a4091ecf8d7a64c5f7d7066f45585c1861eba06449909609c8a"
checksum = "857224b3b211c6f3616921f081ee54721ee3ad2ace2fac6a6337e032f7b4dcf2"
dependencies = [
"bitflags 2.13.1",
"widestring",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
+1 -1
View File
@@ -150,7 +150,7 @@ libloading = "0.8"
x11rb = { version = "0.14", default-features = false }
[target.'cfg(windows)'.dependencies]
wasapi = "0.23"
wasapi = "0.24"
# Native D3D11VA decode (M5 of the native-decode program): the hand-declared DXVA buffer
# layouts and the AuPlan → picparams/qmatrix/slice-control conversion that video_d3d11_native
# submits. Windows-only because the rung is; the crate itself is cross-platform CPU code so
+8 -6
View File
@@ -100,12 +100,14 @@ pub fn devices() -> Result<(Vec<AudioDevice>, Vec<AudioDevice>)> {
/// audio keeps working, like the PipeWire twin's `target.object` behavior.
/// Resolve an active endpoint by id WITHOUT `DeviceEnumerator::get_device`.
///
/// That helper builds its argument as `PCWSTR::from_raw(HSTRING::from(id).as_ptr())` — the
/// `HSTRING` is a temporary, dropped at the end of that statement, so `GetDevice` reads freed
/// memory and misses ids that are perfectly valid. Scanning the active collection touches only
/// safe crate APIs, so it cannot regress the same way. (`punktfunk-host` fixes the same bug with
/// raw COM instead; this crate cannot, because it pins a different `windows` revision than
/// `wasapi` does, making the two `IMMDevice` types incompatible.)
/// Through `wasapi 0.23` that helper built its argument as
/// `PCWSTR::from_raw(HSTRING::from(id).as_ptr())` — the `HSTRING` was a temporary, dropped at the
/// end of that statement, so `GetDevice` read freed memory and missed ids that are perfectly valid.
/// `wasapi 0.24` fixed that upstream. Scanning the active collection touches only safe crate APIs,
/// so it cannot regress the same way, and it additionally filters to ACTIVE endpoints — which is
/// why it stays. (`punktfunk-host` routes around the same bug with raw COM instead; this crate
/// cannot, because it pins a different `windows` revision than `wasapi` does, making the two
/// `IMMDevice` types incompatible.)
pub(crate) fn device_by_id(
enumerator: &DeviceEnumerator,
direction: &Direction,
+4 -3
View File
@@ -920,9 +920,10 @@ fn pad_render_thread(
let res = (|| -> anyhow::Result<()> {
const BLOCK_ALIGN: usize = PAD_CHANNELS * 4; // f32 interleaved
let enumerator = wasapi::DeviceEnumerator::new().context("DeviceEnumerator")?;
// Not `get_device`: that helper resolves through a freed string — see
// [`crate::audio::device_by_id`] (audio_wasapi.rs, mounted as `crate::audio` on
// Windows by lib.rs's `#[path]` swap — there is no `audio_wasapi` module name).
// Not `get_device`: that helper resolved through a freed string through wasapi 0.23, and
// this path additionally wants the ACTIVE-only filter — see [`crate::audio::device_by_id`]
// (audio_wasapi.rs, mounted as `crate::audio` on Windows by lib.rs's `#[path]` swap —
// there is no `audio_wasapi` module name).
let device = crate::audio::device_by_id(&enumerator, &Direction::Render, endpoint_id)
.map_err(|e| anyhow!("correlated endpoint not found: {e:#}"))?;
let mut audio_client = device.get_iaudioclient().context("IAudioClient")?;
+5 -2
View File
@@ -278,7 +278,10 @@ windows = { version = "0.62", features = [
# The SCM plumbing for the `service` subcommand (define_windows_service! / dispatcher / control
# handler / ServiceManager install). Wraps the Win32 service API; the supervision loop itself uses
# the `windows` crate above.
windows-service = "0.7"
# ⭐ Keep this at 0.8+: 0.7 was the LAST crate in the tree pulling `windows-sys 0.52`, so it alone
# kept a fourth windows-sys major compiling. 0.8.1 moves to `windows-sys 0.61`, which the tree
# already builds, and that duplicate is gone. (0.8.0 is NOT enough — it lands on 0.59.)
windows-service = "0.8"
# Read the GOG.com install registry (HKLM\SOFTWARE\WOW6432Node\GOG.com\Games) for the GOG store
# provider — ergonomic + correct-by-construction vs. hand-rolled Reg* FFI for subkey enumeration.
winreg = "0.56"
@@ -286,7 +289,7 @@ winreg = "0.56"
# provider — a small read-only DOM is all we need (Identity/Executable/ShellVisuals/StoreId).
roxmltree = "0.21"
# WASAPI loopback audio capture (default render endpoint -> 48 kHz stereo f32 for the Opus path).
wasapi = "0.23"
wasapi = "0.24"
# Shared host<->driver wire contract for the pf-vdisplay IddCx virtual-display backend: the
# control-plane IOCTL codes + `#[repr(C)] Pod` request/reply structs, defined ONCE so host<->driver
# ABI drift is a compile error (used from `capture.rs`). The `bytemuck` that serializes those
@@ -509,9 +509,10 @@ pub(crate) fn restore_default_playback() {
/// Open a device by endpoint id, with a name for error context.
///
/// Resolves through [`super::pad_endpoint::open_wasapi_device`], NOT the `wasapi` crate's
/// `DeviceEnumerator::get_device` that one hands `GetDevice` a freed string (see the helper's
/// docs), so it fails at random on ids that are perfectly valid.
/// Resolves through [`super::pad_endpoint::open_wasapi_device`] rather than the `wasapi` crate's
/// `DeviceEnumerator::get_device`: that one handed `GetDevice` a freed string through 0.23, so it
/// failed at random on ids that are perfectly valid. `wasapi 0.24` fixed that, but we keep the one
/// resolution path — see the helper's docs.
pub(crate) fn open_endpoint(ep: &Endpoint) -> Result<wasapi::Device> {
super::pad_endpoint::open_wasapi_device(&ep.1)
.map_err(|e| anyhow!("open endpoint {:?}: {e:#}", ep.0))
@@ -911,13 +911,17 @@ fn open_mmdevice(endpoint_id: &str) -> Result<IMMDevice> {
/// Open a [`wasapi::Device`] for an endpoint id WITHOUT the crate's `DeviceEnumerator::get_device`.
///
/// `wasapi 0.23` builds that call's argument as
/// `PCWSTR::from_raw(HSTRING::from(device_id).as_ptr())`. The `HSTRING` is a temporary, so it is
/// dropped at the end of THAT statement and `IMMDeviceEnumerator::GetDevice` reads freed memory on
/// the next line. Whether the endpoint is found then depends on what the allocator happened to
/// Through `wasapi 0.23` that call built its argument as
/// `PCWSTR::from_raw(HSTRING::from(device_id).as_ptr())`. The `HSTRING` was a temporary, so it was
/// dropped at the end of THAT statement and `IMMDeviceEnumerator::GetDevice` read freed memory on
/// the next line. Whether the endpoint was found then depended on what the allocator happened to
/// leave behind — a heisenbug whose failure mode is `0x80070002` (ERROR_FILE_NOT_FOUND) for an id
/// that is perfectly valid. [`open_mmdevice`] keeps its wide buffer alive across the call, so
/// resolve there and only borrow the crate's wrapper around the resulting interface.
/// that is perfectly valid. **`wasapi 0.24` fixed this upstream** (the `HSTRING` is now bound to a
/// local that outlives the call), so this helper is no longer load-bearing for correctness.
///
/// We still resolve here, because the `IMMDevice` is wanted in its own right: [`probe_activation`]
/// and the property-store readers below need the raw interface, so routing every lookup through
/// [`open_mmdevice`] keeps ONE resolution path whose errors name the endpoint id.
pub(crate) fn open_wasapi_device(endpoint_id: &str) -> Result<wasapi::Device> {
let dev = open_mmdevice(endpoint_id)?;
wasapi::Device::from_immdevice(dev)
@@ -747,9 +747,10 @@ enum DefaultKind {
Unknown,
}
/// Resolves through [`super::pad_endpoint::open_wasapi_device`], NOT the `wasapi` crate's
/// `DeviceEnumerator::get_device` that one hands `GetDevice` a freed string (see the helper's
/// docs), and a spurious miss here silently downgrades a capturable default to `Unknown`.
/// Resolves through [`super::pad_endpoint::open_wasapi_device`] rather than the `wasapi` crate's
/// `DeviceEnumerator::get_device`: that one handed `GetDevice` a freed string through 0.23, and a
/// spurious miss here silently downgrades a capturable default to `Unknown`. `wasapi 0.24` fixed
/// that, but we keep the one resolution path — see the helper's docs.
fn judge_default(wiring: &wiring_plan::Wiring, id: &str) -> DefaultKind {
let Ok(dev) = super::pad_endpoint::open_wasapi_device(id) else {
return DefaultKind::Unknown;
+3 -2
View File
@@ -38,8 +38,9 @@ rustls = { version = "0.23", default-features = false, features = ["aws_lc_rs",
punktfunk-core = { path = "../punktfunk-core", default-features = false, features = ["tls", "ureq-tls"] }
[target.'cfg(windows)'.dependencies]
# SCM QUERY_STATUS works unprivileged — the service-state probe. Same crate the host service uses.
windows-service = "0.7"
# SCM QUERY_STATUS works unprivileged — the service-state probe. Same crate the host service uses,
# and it must stay in lockstep with it — see the windows-sys note on the host's declaration.
windows-service = "0.8"
windows = { version = "0.62", features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",