diff --git a/README.md b/README.md index e0ef3a5..fa41657 100755 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Build `antipopd` can be built in a terminal using the following command: - clang -framework AppKit -framework IOKit -arch i386 -arch x86_64 -o antipopd antipopd.m + clang -framework AppKit -framework IOKit -framework CoreAudio -arch i386 -arch x86_64 -o antipopd antipopd.m A built version (`i386` and `x86_64`) of `antipopd` is included in the repository. @@ -42,6 +42,10 @@ at `/usr/local/share/antipop/ac_only`. If the first byte of the configuration file is a `1` the audio system will only be kept alive when on AC power. +If you want to allow `antipopd` to work only when the default output device is set to built-in +you can create a configuration file at `/usr/local/share/antipop/built_in_only` as described above. +This may be helpful when you have a bluetooth headset connected and don't want to drain it's battery. + The configuration file is only read once when `antipopd` launches. Changing the configuration file will not take effect until `antipopd` is restarted. diff --git a/antipopd b/antipopd index 79c86c4..1890d77 100755 Binary files a/antipopd and b/antipopd differ diff --git a/antipopd.m b/antipopd.m index d92417f..0c293a9 100755 --- a/antipopd.m +++ b/antipopd.m @@ -19,49 +19,99 @@ #import #import #import +#import #import -#define ANTIPOPD_CONFIG "/usr/local/share/antipop/ac_only" +#define ANTIPOPD_AC_ONLY_CONFIG "/usr/local/share/antipop/ac_only" +#define ANTIPOPD_BUILT_IN_ONLY_CONFIG "/usr/local/share/antipop/built_in_only" + #define BATTERY_STATE CFSTR("State:/IOKit/PowerSources/InternalBattery-0") #define POWER_SOURCE CFSTR("Power Source State") #define INTERVAL 10 // seconds static BOOL runOnACOnly = NO; +static BOOL runOnBuiltInOnly = NO; void banner() { printf("antipopd\n\n"); - - printf("Copyright (c) Matthew Robinson 2010, 2018\n"); + + printf("Copyright (c) Matthew Robinson 2010, 2018\n"); printf("Email: matt@blendedcocoa.com\n\n"); - + printf("antipopd is a drop in replacement for Robert Tomsick's antipopd 1.0.2 bash\n"); printf("script which is available at http://www.tomsick.net/projects/antipop\n\n"); - + printf("antipopd is a utility program which keeps the audio system active to stop\n"); printf("the popping sound that can occur when OS X puts the audio system to sleep.\n"); printf("This is achieved by using the Speech Synthesizer system to speak a space,\n"); printf("which results in no audio output but keeps the audio system awake.\n\n"); - + printf("The benefit of this compiled version over the bash script is a reduction\n"); printf("in resource overheads. The bash script executes two expensive processes \n"); printf("(pmset and say) every ten seconds (one process if ac_only is set to 0).\n\n"); - + printf("This version of antipopd is released, like Robert Tomsick's version, under\n"); printf("a Creative Commons Attribution Noncommercial Share Alike License 3.0,\n"); printf("http://creativecommons.org/licenses/by-nc-sa/3.0/us\n\n"); - + } NSSpeechSynthesizer *speech = nil; +void *preparePropertyAddress(AudioObjectPropertyAddress *propertyAddress, UInt32 prop) { + propertyAddress->mSelector = prop; + propertyAddress->mScope = kAudioObjectPropertyScopeGlobal; + propertyAddress->mElement = kAudioObjectPropertyElementMaster; + + return propertyAddress; +} + +OSStatus getDeviceTransportType(AudioDeviceID deviceId, UInt32 *transportType) { + AudioObjectPropertyAddress propertyAddress; + preparePropertyAddress(&propertyAddress, kAudioDevicePropertyTransportType); + UInt32 dataSize = sizeof(UInt32); + + return AudioObjectGetPropertyData(deviceId, &propertyAddress, 0, NULL, &dataSize, transportType); +} + +OSStatus getDefaultOutputDevice(AudioDeviceID *deviceId) { + AudioObjectPropertyAddress propertyAddress; + preparePropertyAddress(&propertyAddress, kAudioHardwarePropertyDefaultOutputDevice); + UInt32 dataSize = sizeof(AudioDeviceID); + + return AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, deviceId); +} + +OSStatus isDefaultOutputDeviceBuiltIn(BOOL *result) { + AudioDeviceID defaultOutputId; + OSStatus error = getDefaultOutputDevice(&defaultOutputId); + + if (error) { + NSLog(@"Error getting default output device (code: %d)", (int) error); + return error; + } + + UInt32 transportType; + error = getDeviceTransportType(defaultOutputId, &transportType); + + if (error) { + NSLog(@"Error getting transport type for device ID: %d (code: %d)", (int) defaultOutputId, (int) error); + return error; + } + + *result = transportType == kAudioDeviceTransportTypeBuiltIn; + + return kAudioServicesNoError; +} + // Timer callback that actually speaks the space void speak(CFRunLoopTimerRef timer, void *info) { if (!speech) { speech = [[NSSpeechSynthesizer alloc] initWithVoice:nil]; } - + // If we are only supposed to run on AC power if (runOnACOnly) { // and we don't have unlimited power remaining @@ -70,33 +120,48 @@ void speak(CFRunLoopTimerRef timer, void *info) { return; } } - + + if (runOnBuiltInOnly) { + BOOL defaultOutputIsBuiltIn; + OSStatus error = isDefaultOutputDeviceBuiltIn(&defaultOutputIsBuiltIn); + + if (error) { + NSLog(@"Error while trying to determine default output device. Code: %d", (int) error); + exit(error); + } + + if (!defaultOutputIsBuiltIn) { + return; + } + } + [speech startSpeakingString:@" "]; } -// Check for the existance of the ac_only file, check the contents -// and set runOnACOnly as appropriate -void loadACOnlyConfig() { - // Try to open the ac_only config file - int fd = open(ANTIPOPD_CONFIG, O_RDONLY); - - // If succesful look inside, otherwise proceed with runOnACOnly default +// Check for the existence of the config file, check the contents +// and set parameter as appropriate +void loadConfigAndSetParameter(BOOL *parameter, char *config) { + // Try to open the config file + int fd = open(config, O_RDONLY); + + // If succesful look inside, otherwise proceed with default parameter if (fd != -1) { char buffer; - + ssize_t result = read(fd, &buffer, 1); - + // ...the first byte of the file is 1 if (result == 1 && buffer == '1') { - runOnACOnly = YES; + *parameter = YES; } - + close(fd); } } int main(int argc, char *argv[]) { - loadACOnlyConfig(); + loadConfigAndSetParameter(&runOnACOnly, ANTIPOPD_AC_ONLY_CONFIG); + loadConfigAndSetParameter(&runOnBuiltInOnly, ANTIPOPD_BUILT_IN_ONLY_CONFIG); // Put an AutoreleasePool in place in case NSSpeechSynthesizer expects it @autoreleasepool { @@ -104,11 +169,11 @@ int main(int argc, char *argv[]) { banner(); exit(EXIT_SUCCESS); } - + CFRunLoopTimerContext context = { 0, NULL, NULL, NULL, NULL, }; - + CFRunLoopTimerRef timer = CFRunLoopTimerCreate( NULL, 0, @@ -118,9 +183,9 @@ int main(int argc, char *argv[]) { speak, &context ); - + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode); - + CFRunLoopRun(); }