Skip to content

feat(presets): add per-preset output device association#294

Open
dennislemennace wants to merge 2 commits into
Audio4Linux:masterfrom
dennislemennace:preset-output-device
Open

feat(presets): add per-preset output device association#294
dennislemennace wants to merge 2 commits into
Audio4Linux:masterfrom
dennislemennace:preset-output-device

Conversation

@dennislemennace
Copy link
Copy Markdown

@dennislemennace dennislemennace commented May 21, 2026

What this does

Adds an opt-in "Remember output device" checkbox to the Presets panel. When checked at save time, the currently selected output device is stored alongside the preset. When that preset is later loaded — from the panel or the tray icon — the app automatically switches to that device, making profile switching a single action.

Default behaviour is completely unchanged: presets saved without the checkbox never affect the output device.

Implementation details

Storage — a new preset_devices.json sidecar file managed by PresetManager, structured as a JSON object mapping preset name → {deviceId, useDefault}. This mirrors the existing preset_rules.json pattern and deliberately keeps device names out of the portable .conf preset files, since they are machine-specific.

Apply pathapplyPresetDevice() sets AppConfig::AudioOutputDevice + AppConfig::AudioOutputUseDefault, which triggers a live device switch via PipewireAudioService::onAppConfigUpdated — the same path SettingsFragment uses when the user picks a device manually.

Import safety — the association is applied only when loading a managed preset from the presets directory. Files opened via Load custom audio.conf (MainWindow::loadExternalFile(), which also flows through loadFromPath()) never switch the device, even if the imported file's base name happens to collide with a saved preset.

Feedback-loop guard — the existing onOutputDeviceChanged rule system loads presets in response to device changes. A _suppressDeviceApply flag prevents the loaded preset from firing another device change in that code path.

Rename/delete syncPresetManager::rename() and remove() keep _presetDevices in sync so stale entries don't accumulate.

PulseAudio — the new checkbox is hidden under #ifdef USE_PULSEAUDIO alongside the existing Rules button, since the PulseAudio device-switching path is stubbed (PulsePipelineManager hardcodes use_default_sink = true).

Incidental fix

PresetFragment::onAddClicked() previously cleared the name field with ui->presetName->text() = "";. Because QLineEdit::text() returns a QString by value, this assigned to a throwaway temporary and the field was never actually cleared after saving. Replaced with ui->presetName->clear().

Files changed

  • src/data/PresetManager.h — new map, path helper, load/save/set/clear/has/apply declarations
  • src/data/PresetManager.cpp — full implementation + hooks in loadFromPath (scoped to managed presets), rename, remove, onOutputDeviceChanged
  • src/interface/fragment/PresetFragment.uirememberDevice QCheckBox (added to tab order)
  • src/interface/fragment/PresetFragment.cpp — checkbox wired to save/reflect state; hidden on PulseAudio; incidental clear() fix

Lets a preset optionally remember which output device was active when it
was saved, and switch to that device automatically when the preset is
loaded — making profile switching a single action instead of two.

- PresetManager: new _presetDevices map (name → {deviceId, useDefault}),
  persisted in preset_devices.json alongside the existing preset_rules.json.
  setPresetDevice/clearPresetDevice/hasPresetDevice/applyPresetDevice API.
  applyPresetDevice() sets AppConfig::AudioOutputDevice +
  AudioOutputUseDefault, which triggers a live switch via
  PipewireAudioService::onAppConfigUpdated — the same path SettingsFragment
  uses. A _suppressDeviceApply guard prevents a feedback loop when a
  device-change rule fires and loads a preset via onOutputDeviceChanged.
  rename() and remove() keep the map in sync.
- PresetFragment: "Remember output device" checkbox (opt-in; default
  behaviour unchanged). Hidden on PulseAudio builds alongside the existing
  rules button, since the PulseAudio device-switch path is stubbed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dennislemennace
Copy link
Copy Markdown
Author

dennislemennace commented May 21, 2026

Hi, I am new to github and this is my first PR using LLM. This is a feature which is super convenient for my use case, which is having different profile presets for each device. This allows the output device to be saved with the preset if the checkbox is enabled. I have built and tested on Bazzite and seems to work well. Please consider approving this, and let me know if I can improve this PR in anyway! Thanks for the great software :)

…tab order

loadFromPath() is also reached by MainWindow::loadExternalFile(), which can
open any .conf on disk. Guard applyPresetDevice() so it only runs for files in
the presets directory, preventing an imported file from switching the output
device merely because its base name collides with a saved preset's name.

Also add the new "Remember output device" checkbox to the .ui tab order so it
is reachable in a defined order via keyboard navigation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant