You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It is documented as deliberate: the out-string marshaling on the undocumented IAudioPolicyConfig vtable is
build-variant, and the author chose not to guess at it.
#1585 fixed the consequence — the picker no longer renders that null as the default device's name, and now
shows "Choose a device" instead. That is honest but still incomplete: Volume Control can set an app's output
device and cannot show it, so after a restart the user has no way to see from SysManager which app is routed
where. The override is in force in Windows; the app just cannot read it back.
Expected behavior
Implement the read using the same feature-detect-and-degrade contract that already governs the write path:
Add the GetPersistedDefaultAudioEndpoint vtable slot behind a try/catch that returns null on any
COM or marshaling failure — exactly the shape of TryCreate, which returns null when it cannot bind.
Pin the endpoint-id round trip with a unit test, the way ToPolicyEndpointId already is — that helper is
unit-tested precisely because the string format is easy to get wrong.
Once the read is real, also call SetOutputDeviceFromService for surviving rows, not only new ones
(MergeInto currently takes the ApplyUpdate branch for survivors, which never touches the selection). Do
it on tab activation or on a slow cadence, not on the 1 Hz reconcile pass: that is a COM call per app
per second, and the device list is already deliberately re-read on a slower cadence for the same reason.
Note the ordering constraint: doing that last step before the read works would be a regression, not
progress. With the read still stubbed it would overwrite whatever the user had just picked with null on
every pass.
Rationale
The tab's own header promises "Route an app to a specific output device (headset, speakers)". Half of that
promise is kept. Reading the route back is what turns it from a fire-and-forget action into something the user
can verify, which for the target persona is the difference between trusting the tab and re-picking the device
every time she opens it.
Verification constraint (why this was split out)
This cannot be verified by reading the source. It needs a machine that runs the application, with real
audio sessions and more than one render endpoint, because the failure mode is a marshaling mismatch that only
appears at the COM boundary. ARCHITECTURE.md already records the same constraint for the routing SET path:
"can only be runtime-verified on a real desktop, with real audio hardware."
So this issue is explicitly gated on running the built exe on real hardware and confirming that a
route set through SysManager reads back as the same endpoint id after a restart. A green unit suite is not
sufficient evidence here.
Evidence
Services/AudioPolicyConfig.cs — GetPersistedDefaultEndpoint body re-read verbatim, plus the doc comment
explaining why it is unimplemented and what the null now produces. Services/AudioMixerService.cs — GetSessionOutputDevice returns string? and returns null on every unknown path (post-#1585). ViewModels/AudioMixerViewModel.cs — MergeInto: SetOutputDeviceFromService is called only in the new-row
branch; survivors hit ApplyUpdate, which does not touch SelectedOutputDevice. ViewModels/ AudioSessionRowViewModel.cs — SetOutputDeviceFromService implements the three-state switch. SysManager.Tests/ArchitectureTests.cs — TheAudioRouteRead_ReportsUnknownRatherThanTheDefault guards the
contract that this work must not weaken.
Problem
AudioPolicyConfigFactory.GetPersistedDefaultEndpointis a stub. Its whole body is:It is documented as deliberate: the out-string marshaling on the undocumented
IAudioPolicyConfigvtable isbuild-variant, and the author chose not to guess at it.
#1585 fixed the consequence — the picker no longer renders that null as the default device's name, and now
shows "Choose a device" instead. That is honest but still incomplete: Volume Control can set an app's output
device and cannot show it, so after a restart the user has no way to see from SysManager which app is routed
where. The override is in force in Windows; the app just cannot read it back.
Expected behavior
Implement the read using the same feature-detect-and-degrade contract that already governs the write path:
GetPersistedDefaultAudioEndpointvtable slot behind atry/catchthat returnsnullon anyCOM or marshaling failure — exactly the shape of
TryCreate, which returns null when it cannot bind.nullmust continue to mean "could not read", so amarshaling failure degrades to the existing "Choose a device" placeholder rather than to a wrong device
name. The existing guard
TheAudioRouteRead_ReportsUnknownRatherThanTheDefaultalready enforces that theservice keeps the nullable return and does not collapse it.
ToPolicyEndpointIdalready is — that helper isunit-tested precisely because the string format is easy to get wrong.
SetOutputDeviceFromServicefor surviving rows, not only new ones(
MergeIntocurrently takes theApplyUpdatebranch for survivors, which never touches the selection). Doit on tab activation or on a slow cadence, not on the 1 Hz reconcile pass: that is a COM call per app
per second, and the device list is already deliberately re-read on a slower cadence for the same reason.
Note the ordering constraint: doing that last step before the read works would be a regression, not
progress. With the read still stubbed it would overwrite whatever the user had just picked with
nullonevery pass.
Rationale
The tab's own header promises "Route an app to a specific output device (headset, speakers)". Half of that
promise is kept. Reading the route back is what turns it from a fire-and-forget action into something the user
can verify, which for the target persona is the difference between trusting the tab and re-picking the device
every time she opens it.
Verification constraint (why this was split out)
This cannot be verified by reading the source. It needs a machine that runs the application, with real
audio sessions and more than one render endpoint, because the failure mode is a marshaling mismatch that only
appears at the COM boundary.
ARCHITECTURE.mdalready records the same constraint for the routing SET path:"can only be runtime-verified on a real desktop, with real audio hardware."
So this issue is explicitly gated on running the built exe on real hardware and confirming that a
route set through SysManager reads back as the same endpoint id after a restart. A green unit suite is not
sufficient evidence here.
Evidence
Services/AudioPolicyConfig.cs—GetPersistedDefaultEndpointbody re-read verbatim, plus the doc commentexplaining why it is unimplemented and what the null now produces.
Services/AudioMixerService.cs—GetSessionOutputDevicereturnsstring?and returns null on every unknown path (post-#1585).ViewModels/AudioMixerViewModel.cs—MergeInto:SetOutputDeviceFromServiceis called only in the new-rowbranch; survivors hit
ApplyUpdate, which does not touchSelectedOutputDevice.ViewModels/ AudioSessionRowViewModel.cs—SetOutputDeviceFromServiceimplements the three-state switch.SysManager.Tests/ArchitectureTests.cs—TheAudioRouteRead_ReportsUnknownRatherThanTheDefaultguards thecontract that this work must not weaken.
Affected area
Volume Control