Skip to content

EthanShoe/GHubAudioSwitcher

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GHubAudioSwitcher

A lightweight Windows background service that automatically switches the default audio playback device when a Logitech G Pro X Wireless Gaming Headset powers on or off.

When the headset turns on, it becomes the default playback device. When it turns off, the previous device is restored. The switch happens in under a second.


Quick Start

Download: Latest Release

  1. Download the release zip, extract it, and place the folder somewhere permanent, e.g. C:\Program Files\GHubAudioSwitcher\.

  2. Make sure Logitech G Hub is installed and set to launch at startup — the service reads HID reports that G Hub's driver enables.

  3. Open PowerShell as Administrator and run Setup.ps1.

That's it. Toggle your headset off and on to confirm the default audio device switches.

Logs are written to a logs\ folder next to the exe. To uninstall, stop and remove the task:

Stop-ScheduledTask    -TaskName "GHubAudioSwitcher"
Unregister-ScheduledTask -TaskName "GHubAudioSwitcher" -Confirm:$false

AI Use

This repo was responsibly vide coded with Claude (Sonnet 4.6).


How It Works

Windows has no native power-state awareness for wireless headsets connected via USB receivers. The receiver keeps the headset permanently visible as an "Active" audio device regardless of whether the headset is actually powered on. No standard Windows API (PnP, CoreAudio device state, WMI) reports the physical power state.

The solution is to read raw HID++ reports directly from the headset's own HID interface.

Detection mechanism

The Logitech G Pro X Wireless (USB VID 046D, PID 0ABA) exposes a HID++ 2.0 long-message interface at MI_03. When the headset powers on or off, G Hub causes the firmware to emit a report on that interface:

Report ID  Device  Feature  Function  Payload...
   11        FF      06       00       <varies>
  • Payload non-zero (e.g. 0F 9C 01 ...) — headset has powered on
  • Payload all zero (00 00 00 ...) — headset has powered off

The service opens the headset's HID MI_03 collections in shared read mode (alongside G Hub, which runs concurrently), receives its own copy of each report, and filters for this exact pattern. End-to-end latency from physical power toggle to audio switch is under 100 ms.

What did not work

Every approach below was implemented and tested against a live G Hub installation before being abandoned.

Approach Why it failed
G Hub WebSocket (port 9010) Returns HTTP 400 for all connections. G Hub rejects external clients.
CoreAudio DeviceState polling Always reports Active for the headset regardless of power state. The USB receiver keeps the device node permanently present.
PnP device enumeration Device count and properties are identical whether the headset is on or off. Receiver prevents any node change.
G Hub named pipe — SUBSCRIBE /devices/state/changed Pipe accepts connections but sends no push events on headset toggle.
G Hub named pipe — SUBSCRIBE /battery/state/changed Same: no push events received on toggle.
G Hub named pipe — GET /devices/list Binary response is identical on and off; no field changes.
settings.db key presence The battery key battery/proxwirelessheadset/percentage remains in the SQLite database after the headset powers off. Key presence is not a reliable signal.
settings.db timestamp freshness Timestamp on the battery key does update, but only when G Hub polls the headset — intervals of 15–90 seconds. Rejected: did not meet the < 5 second latency requirement.
settings.db full JSON diff Four snapshots (off/on/off/on) diffed completely. Only two keys changed, both were "last write" timestamps updated continuously regardless of headset state. No binary on/off field exists anywhere in the database.
Logitech Unifying Receiver DJ protocol (MI_02) Opened the receiver's DJ interface in shared mode. Zero reports received on headset toggle. G Hub appears to hold the only report queue for that collection.

Audio switching

Audio switching uses AudioSwitcher.AudioApi.CoreAudio. On headset connect, the headset is set as the default playback device. On disconnect, the previous device is restored. A SemaphoreSlim guards against rapid back-to-back toggles.

Revert target tracking

The service needs to know which device to restore when the headset turns off. It tracks this at three levels, in order of preference:

  1. Event-driven (normal operation). The service subscribes to CoreAudio's default-device-changed notifications. Any time Windows switches the default playback device to a non-headset device — whether by G Hub, the user, or anything else — the service records that device's ID and writes it to state.json next to the exe. This is the most accurate source: it reflects exactly what was last active before the headset.

  2. Persisted state (startup with headset already on). If the service starts and the headset is already the default (e.g. the PC was shut down with the headset on), no change event fires. The service reads state.json from the previous session to recover the last known non-headset default. This means the correct device is restored even across reboots.

  3. Heuristic fallback (first ever run). If state.json does not exist yet or the saved device is no longer available, the service picks the first non-headset, non-virtual playback device as a best-effort revert target. Virtual and mirroring devices (such as those created by Nahimic or Sonic Studio audio software) are automatically skipped.

state.json is updated whenever the revert target changes, so after the first headset toggle the persisted state is always current.


Requirements

  • Windows 10/11
  • Logitech G Hub installed and running
  • Logitech G Pro X Wireless headset (USB VID 046D, PID 0ABA)
  • .NET 10 SDK (build only; the published exe is self-contained)

Configuration

appsettings.json sits next to the executable and is read at startup. Changes take effect after restarting the scheduled task.

Key Default Description
GHub:HeadsetNameFilter PRO X Wireless Substring matched against CoreAudioDevice.FullName to identify the headset audio endpoint.
GHub:FallbackNameFilter (empty) Optional. Pins the heuristic fallback to a specific device. Only used on the very first run (before state.json exists) or if the saved device is unavailable. Leave empty to auto-select the first non-virtual, non-headset device. Set this to a unique substring of your speakers' full name (e.g. Realtek) if the heuristic picks the wrong device. The full device name is visible in Windows Sound settings or in the service log at startup.
Serilog:MinimumLevel Information Log verbosity. Set to Debug to see every raw HID report and every default-device change event.

state.json

state.json is written next to the exe automatically — you do not create or edit it. It stores the ID of the last non-headset default playback device so the correct revert target survives reboots. Delete it to reset the persisted state (the heuristic will be used on next startup instead).


Build and publish

# Debug run (console output visible)
dotnet run

# Publish self-contained single executable
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o ./publish

The published exe and appsettings.json are in ./publish. The log file is written to ./publish/logs/log-YYYYMMDD.txt.


Task Scheduler setup

Setup.ps1 (included in the release) handles registration automatically. Run it in an administrator PowerShell session from the folder where you placed the files. It detects the exe path from its own location, so no editing is required.

The task runs as your interactive user account at logon, which is required for both audio session access and HID device access. It restarts automatically up to 3 times if it crashes.

To manage the task manually:

# Start immediately without logging off
Start-ScheduledTask -TaskName "GHubAudioSwitcher"

# Stop
Stop-ScheduledTask -TaskName "GHubAudioSwitcher"

# Remove
Unregister-ScheduledTask -TaskName "GHubAudioSwitcher" -Confirm:$false

Diagnostics

The HidProbe.ps1 script in the repository root can be used to inspect raw HID reports from the headset. It opens all MI_03 and MI_02 collections in shared mode and prints every report received in real time. Useful for verifying the detection signal if something stops working after a firmware or driver update.

.\HidProbe.ps1 -Seconds 90

Toggle the headset on and off while it runs. You should see bursts of 11 FF 06 00 ... reports from hset-col02.

To enable verbose logging in the running service, set "MinimumLevel": "Debug" in appsettings.json and restart the scheduled task. Every HID report byte sequence will be written to the log.


Project structure

GHubAudioSwitcher/
├── Program.cs               # Host bootstrap, Serilog setup
├── Worker.cs                # BackgroundService entry point
├── GHubHidMonitor.cs        # HID++ report reader + power-state detection
├── AudioSwitchService.cs    # CoreAudio default device switching + state persistence
├── appsettings.json         # Runtime configuration
├── Setup.ps1                # Scheduled Task registration script (included in release)
├── HidProbe.ps1             # Raw HID report diagnostic tool
└── GHubAudioSwitcher.csproj

# Written at runtime, next to the exe:
├── state.json               # Persisted revert target (last non-headset default device)
└── logs/log-YYYYMMDD.txt

About

A lightweight Windows background service that automatically switches the default audio playback device when a Logitech G Pro X Wireless Gaming Headset powers on or off.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors