feat(companion): auto-switch Windows audio output on controller headset jack - #147
Open
gunCannoli wants to merge 1 commit into
Open
feat(companion): auto-switch Windows audio output on controller headset jack#147gunCannoli wants to merge 1 commit into
gunCannoli wants to merge 1 commit into
Conversation
…et jack The DualSense's audio endpoint stays present in Windows whether or not anything is plugged into the controller's 3.5 mm jack, and Windows often promotes it to the default output on connect. Windows cannot detect the jack state itself -- the USB audio device does not disconnect when the headset is unplugged -- so it never switches back. The firmware already reports the jack state: the DualSense input report's PluggedHeadphones bit reaches the companion as audioStatus.headsetPlugged. No firmware change is needed. When enabled, the companion drives the Windows default render endpoint: headset in the jack routes to the controller, jack empty routes to a fallback device. With exactly one non-controller output the fallback is resolved automatically and no dropdown is shown; with two or more, a dropdown lists them. This also covers Windows promoting the controller while the jack is empty. Details: - Debounced. The firmware bit is an unfiltered pass-through of the input report with no debouncing, so a marginal plug can chatter; a changed state must hold across two consecutive audio-status polls before acting. - Gates on bit 0 (headsetPlugged), not bit 1 (headsetAudioRoute) -- the latter reflects firmware-internal speaker routing and is false whenever nothing is playing. - Dormant during RDP sessions (Windows redirects audio to Remote Audio) and during host-persona transitions. - Off by default. AudioHelper gains --set-default-render (set the default render endpoint by name, reusing the existing IPolicyConfig path) and --list-render-endpoints (active endpoints as JSON, flagging the bridge's own endpoint). No COMMAND_ID, no PROTOCOL_MINOR bump, no firmware change. Six tests cover the plug/unplug transitions with debounce, the disabled case, single-output auto-resolution, the ambiguous multi-output case, the empty-jack correction, and RDP dormancy; one covers the IPC contract. The settings modal gains one row. The preferences modal is already the tightest variant; upstream may want to give it more height. This fork's suggestion is to let it fill the app window, but that is a UI-layout call for the maintainer and is not included here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The DualSense's audio endpoint stays present in Windows whether or not
anything is plugged into the controller's 3.5 mm jack. Windows frequently
promotes it to the default output device when the controller connects, so
system audio goes to the controller's speaker even with no headset attached.
Windows cannot detect the jack state itself -- the USB audio device does not
disconnect when the headset is unplugged -- so it cannot switch back on its
own. Users have to change the default output manually every time.
Change
Companion-app only. The firmware already reports the jack state: the
DualSense input report's
PluggedHeadphonesbit reaches the companion asaudioStatus.headsetPlugged. No firmware change is required.When enabled, the companion drives the Windows default render endpoint:
The fallback is chosen in Bridge Settings. When exactly one non-controller
output exists it is used automatically and no dropdown is shown; with two or
more, a dropdown lists them. This also covers the case where Windows promotes
the controller to default while the jack is empty -- the companion routes
back to the fallback.
Details
report with no debouncing, so a marginal plug can chatter. A changed jack
state must hold across two consecutive audio-status polls (~1 s) before the
companion acts.
headsetAudioRoute(bit 1) reflectsfirmware-internal speaker routing and is false whenever nothing is playing.
headsetPlugged(bit 0) is the actual jack state.during a remote session; the feature stays inactive rather than fighting
it.
default-render restore is in flight.
fallback device can be resolved.
Implementation
companion/native/AudioHelper-- two new verbs:--set-default-render --device-name "A;B;C"(set the default renderendpoint to the first active match; reuses the existing
IPolicyConfig::SetDefaultEndpointpath) and--list-render-endpoints(active render endpoints as JSON, flagging the bridge's own endpoint).
bridge-service.ts--syncHeadsetAudioAutoSwitch(), called once per pollafter
syncControllerPowerSavingState().headsetAudioAutoSwitchEnabled(bool) andheadsetAudioFallbackDevice(string; empty means auto-resolve).No
COMMAND_ID, noPROTOCOL_MINORbump, no firmware change.Settings modal size
This adds one row to Bridge Settings. The preferences modal is already the
tightest variant, and on a fresh checkout the Power & Controller column can
get crowded. This is a UI-layout call for the maintainer and is not
changed in this PR. Our suggestion, if a change is wanted, is to let the
preferences modal fill the app window (the two columns then fit without an
internal scrollbar); a smaller height bump would also work.
Testing
npm run typecheckandnpx vitest run srcpass. Seven new tests cover theplug/unplug transitions with debounce, the disabled case, auto-resolution of
a single output, the ambiguous multi-output case, the empty-jack correction,
RDP dormancy, and the IPC contract.
npm run build:audio-helperbuildsclean.
Hardware (Waveshare RP2350B-Plus-W, Windows 11):
output is routed back to the fallback device.
selected fallback persists, and a saved-but-absent device shows
"(not connected)".