Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.

Expand Down
Binary file modified antipopd
Binary file not shown.
117 changes: 91 additions & 26 deletions antipopd.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,49 +19,99 @@
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <IOKit/ps/IOPowerSources.h>
#import <AudioToolbox/AudioToolbox.h>

#import <unistd.h>

#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
Expand All @@ -70,45 +120,60 @@ 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 {
if (argc >= 2) { // if we have any parameter show the banner
banner();
exit(EXIT_SUCCESS);
}

CFRunLoopTimerContext context = {
0, NULL, NULL, NULL, NULL,
};

CFRunLoopTimerRef timer = CFRunLoopTimerCreate(
NULL,
0,
Expand All @@ -118,9 +183,9 @@ int main(int argc, char *argv[]) {
speak,
&context
);

CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);

CFRunLoopRun();
}

Expand Down