Keeps Windows on a leash — a lightweight system tray app that stops Windows from switching your audio output or input without permission, and snaps it back when it tries.
- Windows 10 or 11
- .NET 10 SDK (to build) or .NET 10 Runtime (to run)
dotnet build AudioLeash.sln -c Release
AudioLeash\bin\Release\net10.0-windows\AudioLeash.exe
No window appears — the app lives entirely in the system notification area (tray).
Produces installer\Output\AudioLeash-Setup.exe — a single-file Windows installer that requires no admin rights.
Prerequisites (one-time setup):
Run from the repo root (PowerShell):
.\build-installer.ps1The installer:
- Checks for the .NET 10 Windows Desktop Runtime and warns if it is absent
- Installs to
%LOCALAPPDATA%\AudioLeash(no UAC prompt) - Adds a Start Menu entry
- Offers an optional "Start with Windows" checkbox (enabled by default), which pre-sets the same registry key that the tray menu's own toggle manages
| Package | Purpose |
|---|---|
NAudio.Wasapi 2.2.1 |
Device enumeration, change events (IMMNotificationClient) |
- The application runs headlessly — no main window is shown.
- An icon appears in the Windows notification area.
- Left-click or right-click the icon to open the device menu.
- On every menu open, the current list of active (enabled) playback and recording devices is fetched from the Windows Core Audio API.
- Devices are shown in two sections — Playback and Recording — each with a bold header.
- Devices are sorted alphabetically by their full display name within each section.
- If no active devices are found for a section, a disabled "No devices available" item is shown.
- Clicking a device in the menu:
- Sets it as the Windows default playback or recording device immediately.
- Stores its ID as the user-selected device for auto-restore purposes.
- Shows a balloon tip confirming the change.
- Refreshes the menu (checkmark moves to the newly selected device).
- A checkmark (✔) is shown next to the device the user has explicitly selected.
- If Windows has a different device set as default than the user's selection (e.g. after a plug event), that device is labelled with "(Windows default)" for clarity.
- The app subscribes to the Windows
AudioDeviceChangedevent stream. - If an external change sets a different device as the default (e.g. plugging in a USB headset causes Windows to auto-switch), the app detects this and automatically switches back to the user-selected device.
- The restore is accompanied by a balloon tip notification.
- If the user-selected device becomes unavailable (unplugged, disabled), the app retains the selection and suspends enforcement rather than clearing it.
- The tray menu shows the unavailable device as a grayed-out, checked entry with an (unavailable) label.
- The tray tooltip appends (waiting) to indicate the device is disconnected.
- When the device reconnects, AudioLeash automatically restores it as the default and shows a "Device Reconnected" balloon notification.
- On startup, if a saved device is not currently connected, the app keeps the selection and waits for the device to appear.
- An
isInternalChangeboolean guards against feedback loops where the app's own device switch triggers the change-monitoring handler, which would then try to switch again.
- The "Clear Selection" menu item removes both playback and recording device preferences.
- After clearing, the app will no longer attempt to restore any device when Windows changes the default.
- The item is greyed out when no device is selected.
IMMNotificationClientcallbacks arrive on a Windows COM audio thread.- All UI updates (balloon tips, menu refresh) are marshalled back to the UI thread via
Control.InvokeRequired/Control.Invoke.
- The "Exit" menu item hides the tray icon and terminates the application.
- All resources (
NotifyIcon,ContextMenuStrip,CoreAudioController) are properly disposed.
- A "Start with Windows" item in the tray menu registers or removes AudioLeash from the Windows
HKCU\...\Runregistry key. - A checkmark indicates it is currently registered.
- Clicking the item toggles registration on or off.
- The tray context menu automatically adapts its colour scheme to the Windows colour theme.
- When Windows is in dark mode the menu uses a dark background (
#1F1F1F) with light text, a subtle highlight on hover, and a muted separator. - When Windows is in light mode the menu reverts to the standard WinForms appearance.
- The renderer updates live — if the user switches theme in Windows Settings while AudioLeash is running, the next menu open reflects the new theme immediately; no restart required.
- The user-selected audio device is saved to
%AppData%\AudioLeash\settings.json. - On first launch (no settings file), a balloon tip prompts the user to select a device from the tray menu — the app is passive until a device is chosen explicitly.
- On subsequent launches, AudioLeash restores the saved selection automatically (if the device is still available); if the saved device is not currently connected, the selection is kept and enforcement resumes when the device reconnects.
- Clearing the selection also removes the saved preference.
- The tray menu shows two sections: Playback devices and Recording (microphone) devices, each with a bold section header.
- Users can independently lock a playback device and a recording device.
- When Windows changes either default device (e.g. due to a USB mic being plugged in), AudioLeash detects the change and restores the user's chosen device.
- Clear Selection resets both playback and recording selections.
- The tray tooltip shows both locked device names.
- Settings are persisted independently; existing settings from older versions are migrated automatically.
AudioLeash/
├── AudioLeash.sln
├── build-installer.ps1 ← Builds and packages the Inno Setup installer
├── installer/
│ └── AudioLeash.iss ← Inno Setup script
├── AudioLeash/
│ ├── AudioLeash.csproj
│ ├── Program.cs ← Entry point; STA thread, WinForms bootstrap
│ ├── AudioLeashContext.cs ← All application logic (tray, menu, device events)
│ ├── DeviceSelectionState.cs ← Pure selection state machine (unit-testable)
│ ├── PolicyConfigClient.cs ← COM interop: sets Windows default audio endpoint
│ ├── SettingsService.cs ← JSON settings persistence (%AppData%\AudioLeash\)
│ ├── StartupService.cs ← Windows Run-key startup registration
│ ├── DarkMenuRenderer.cs ← Dark mode context menu renderer
│ ├── WindowsTheme.cs ← Windows theme detection (light/dark)
│ └── Resources/
│ └── icon.ico ← tray icon
└── AudioLeash.Tests/
├── AudioLeash.Tests.csproj ← xUnit + NSubstitute
├── DeviceSelectionStateTests.cs
├── SettingsServiceTests.cs
├── StartupServiceTests.cs
└── WindowsThemeTests.cs
SelectedDeviceIdrace condition —OnDefaultDeviceChangedreadsSelectedDeviceIdtwice on the audio thread (once insideEvaluateDefaultChange, once to capturerestoreId) with no lock between them. If the user clicks "Clear Selection" on the UI thread in that gap,restoreIdwill benulldespite the!assertion. Extremely unlikely in practice and the innertry/catchhandles the resulting exception gracefully, but a proper fix would snapshotSelectedDeviceIdonce under a lock and pass the snapshot through.
Windows startup— ✔ Implemented (registryRunkey toggle in tray menu).- Hotkey cycling — Global keyboard shortcut to cycle to the next audio device.
- Communication device — Also set the "default communications device" alongside the default playback device.
Recording device support— ✔ Implemented (tray menu shows separate Playback and Recording sections; each can be locked independently).- Profiles — Named profiles that switch multiple devices (playback + recording) together. Could also address the boot-time race condition where a saved device hasn't finished initialising when the app starts — a profile-aware restore could defer until the target device comes online.
- Per-app routing — Use Windows 10+ per-application audio settings where supported.
Settings persistence— ✔ Implemented (JSON file in%AppData%\AudioLeash\).Tooltip on hover— ✔ Implemented (tray icon tooltip shows the selected device name, or "No device selected" when none is chosen).Dark mode menu— ✔ Implemented (context menu automatically uses a dark palette when Windows is in dark theme; updates live when the user changes the setting).- Volume indicator — Show or control master volume from the tray menu.
Single-instance enforcement— ✔ Implemented (namedMutexinProgram.cs; second instance exits silently).