Home

xmn / bettertrumpet

publicxammen/BetterTrumpet· sync paused
Overview Code History Branches Pull requestsIssuesInsights
master
HomeOverview Code PRsIssues

feat: per-app volume rules, merged with hard mute (3.2.1)

2 months ago

59e1f11
Authored
xammen7/28/2026, 9:19:25 AM
Adds a persistent per-app volume rule alongside the existing hard mute,
with two modes:

- Launch: write the volume once per app launch, then leave it alone. The
  user stays free to change it for the rest of the session.
- Lock: hold the volume at the rule value, reverting changes from the app,
  the Windows mixer, or another tool.

The two lists are merged into a single AppRuleEntriesJson key, one entry
per app carrying both HardMuted and the volume rule, so the settings page
can show one editable row per app. HardMutedAppEntriesJson is migrated
once on load and left in place so downgrading still works; settings import
also accepts the old key and merges it rather than replacing the list.

Implementation notes:

- Rules are applied after the group merge in DeviceViewModel.AddSession,
  not before. The merge copies the existing group's volume and mute onto
  the new session and would otherwise overwrite them. This also fixes a
  latent bug where a hard mute could be cleared by that merge.
- Mute is re-asserted after the volume write: AudioDeviceSession's Volume
  setter clears IsMuted, so writing a launch volume would undo a hard mute.
- Launch keys on process id (LaunchVolumeTracker) because a session can
  re-enter the collection without the app restarting: a device move and
  back, an unhide, or a DeviceViewModel rebuild all call AddSession again.
  Applying on every AddSession would reset the volume mid-session. Tracker
  state is per-run, which is what lets a rule still apply after a reboot
  when the app wins the startup race and is already playing.
- Rule enforcement writes through a no-undo path so repeated Lock
  corrections don't flood the undo history.
- The watcher that Lock needs also re-asserts hard mute, closing a
  pre-existing gap where an app that unmuted itself stayed unmuted.

Verified at runtime: Launch applies on start and holds when raised
manually, then reapplies on the next launch; Lock reverts external changes
from both directions without oscillating; hard mute and a volume rule
coexist; export/import round-trips and legacy imports merge.

Parent7afe27a

21 files changed
  • EarTrumpet/App.xaml.cs+2−1
  • EarTrumpet/AppSettings.cs+285−68
  • EarTrumpet/DataModel/SettingsExportService.cs+6−4
  • EarTrumpet/Properties/Resources.Designer.cs+180−0
  • EarTrumpet/Properties/Resources.fr-FR.resx+90−0
  • EarTrumpet/Properties/Resources.resx+90−0
  • EarTrumpet/UI/ViewModels/AppItemViewModel.cs+15−0
  • EarTrumpet/UI/ViewModels/AppRuleItemViewModel.cs
+163
−0
  • EarTrumpet/UI/ViewModels/AudioSessionViewModel.cs+12−0
  • EarTrumpet/UI/ViewModels/DeviceCollectionViewModel.cs+33−4
  • EarTrumpet/UI/ViewModels/DeviceViewModel.cs+107−11
  • EarTrumpet/UI/ViewModels/EarTrumpetAppRulesSettingsPageViewModel.cs+226−0
  • EarTrumpet/UI/ViewModels/FocusedAppItemViewModel.cs+77−0
  • EarTrumpet/UI/ViewModels/IAppItemViewModel.cs+5−0
  • EarTrumpet/UI/ViewModels/LaunchVolumeTracker.cs+74−0
  • EarTrumpet/UI/ViewModels/SettingsAppItemViewModel.cs+1−0
  • EarTrumpet/UI/ViewModels/TemporaryAppItemViewModel.cs+2−0
  • EarTrumpet/UI/Views/AppItemView.xaml+34−0
  • EarTrumpet/UI/Views/SettingsWindow.xaml+175−0
  • EarTrumpet/UI/Views/SettingsWindow.xaml.cs+36−0
  • GitVersion.yml+1−1