From 09aa3dbec657494993c78de1635f62fd4826ee84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Scho=CC=88nwald?= Date: Thu, 14 May 2026 16:51:31 +0200 Subject: [PATCH] Add "follow main output" support for system (alert) device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new sub-command "follow" to -t system that restores the "Selected Sound Output Device" mode for alerts/sound effects: SwitchAudioSource -t system follow This performs the two writes that System Settings → Sound makes when the user picks "Selected Sound Output Device" from "Play sound effects through": 1. AlertsUseMainDevice = true in com.apple.soundpref (CurrentHost). 2. kAudioHardwarePropertyDefaultSystemOutputDevice ← current kAudioHardwarePropertyDefaultOutputDevice. In addition, when AlertsUseMainDevice is true, -t output now also mirrors the new device to system output, so alerts stay in sync with the main output automatically. When the flag is false, behavior is unchanged (pinned-mode preserved). System Settings caches com.apple.soundpref on launch — fully quit and reopen System Settings to see the popup label update. --- audio_switch.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++- audio_switch.h | 2 ++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/audio_switch.c b/audio_switch.c index 814edce..55b4487 100644 --- a/audio_switch.c +++ b/audio_switch.c @@ -40,7 +40,9 @@ void showUsage(const char * appName) { " -n : cycles the audio device to the next one\n" " -i device_id : sets the audio device to the given device by id\n" " -u device_uid : sets the audio device to the given device by uid or a substring of the uid\n" - " -s device_name : sets the audio device to the given device by name\n\n",appName); + " -s device_name : sets the audio device to the given device by name\n\n" + " -t system follow : restore alerts \"follow main output\" mode\n" + " (\"Selected sound output device\" in System Settings)\n\n",appName); } int runAudioSwitch(int argc, const char * argv[]) { @@ -170,6 +172,20 @@ int runAudioSwitch(int argc, const char * argv[]) { return 0; } + if (typeRequested == kAudioTypeSystemOutput && optind < argc && strcmp(argv[optind], "follow") == 0) { + AudioDeviceID mainOutput = getCurrentlySelectedDeviceID(kAudioTypeOutput); + if (mainOutput != kAudioDeviceUnknown) { + setOneDevice(mainOutput, kAudioTypeSystemOutput); + } + result = setAlertsFollowMain(true); + if (result == 0) { + char name[256]; + getDeviceName(mainOutput, name); + printf("alerts follow main output (\"%s\"); reopen System Settings to refresh UI\n", name); + } + return result; + } + if (typeRequested == kAudioTypeUnknown) typeRequested = kAudioTypeOutput; if (function == kFunctionCycleNext) { @@ -588,6 +604,15 @@ int setOneDevice(AudioDeviceID newDeviceID, ASDeviceType typeRequested) { printf("Failed to set %s", deviceTypeName(typeRequested)); } + if (typeRequested == kAudioTypeOutput && getAlertsFollowMain()) { + AudioObjectPropertyAddress sysAddr = { + kAudioHardwarePropertyDefaultSystemOutputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + AudioObjectSetPropertyData(kAudioObjectSystemObject, &sysAddr, 0, NULL, propertySize, &newDeviceID); + } + return 0; } @@ -796,3 +821,24 @@ void showAllDevices(ASDeviceType typeRequested, ASOutputType outputRequested) { } } } + +int setAlertsFollowMain(bool follow) { + CFStringRef domain = CFSTR("com.apple.soundpref"); + CFStringRef key = CFSTR("AlertsUseMainDevice"); + CFBooleanRef value = follow ? kCFBooleanTrue : kCFBooleanFalse; + CFPreferencesSetValue(key, value, domain, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost); + if (!CFPreferencesSynchronize(domain, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)) { + printf("Failed to write com.apple.soundpref preference\n"); + return 1; + } + return 0; +} + +bool getAlertsFollowMain(void) { + CFStringRef domain = CFSTR("com.apple.soundpref"); + CFStringRef key = CFSTR("AlertsUseMainDevice"); + Boolean valid = false; + Boolean result = CFPreferencesGetAppBooleanValue(key, domain, &valid); + if (!valid) return false; + return (bool)result; +} diff --git a/audio_switch.h b/audio_switch.h index 4d19cb9..49a51d3 100644 --- a/audio_switch.h +++ b/audio_switch.h @@ -84,6 +84,8 @@ AudioDeviceID getRequestedDeviceID(char * requestedDeviceName, ASDeviceType type AudioDeviceID getNextDeviceID(AudioDeviceID currentDeviceID, ASDeviceType typeRequested); int setDevice(AudioDeviceID newDeviceID, ASDeviceType typeRequested); int setOneDevice(AudioDeviceID newDeviceID, ASDeviceType typeRequested); +int setAlertsFollowMain(bool follow); +bool getAlertsFollowMain(void); int setAllDevicesByName(char * requestedDeviceName); int cycleNext(ASDeviceType typeRequested); int cycleNextForOneDevice(ASDeviceType typeRequested);