Thursday, September 29, 2011

Getting power source info from OS X (Objective-C)

I trust any introduction is unnecessary here, so will go straight to the code.
First approach is to use power source support methods from the IOKit/ps package. Here's how to do it:
#import <Cocoa/Cocoa.h>
#import <IOKit/ps/IOPowerSources.h>
#import <IOKit/ps/IOPSKeys.h>
#import "PowerSourceInfo.h"

@interface IOKitPowerSourceInfo : NSObject {

}

- (PowerSourceInfo*) getPowerSourceInfoFor: (int) index;

@end
... and the implementation:
#import "IOKitPowerSourceInfo.h"


@implementation IOKitPowerSourceInfo

- (PowerSourceInfo*) getPowerSourceInfoFor: (int) index {
 CFTypeRef info = IOPSCopyPowerSourcesInfo();
 CFArrayRef sources = IOPSCopyPowerSourcesList(info);
 PowerSourceInfo* psi = nil;

 int numOfSources = CFArrayGetCount(sources);
 if(numOfSources == 0) {
  return nil;
 }
 CFDictionaryRef source = IOPSGetPowerSourceDescription(info, CFArrayGetValueAtIndex(sources, index));
 psi = [[PowerSourceInfo alloc] initWithDictionary:(NSDictionary*)source];

 CFRelease(sources);
 CFRelease(info);

 return psi;
}

@end

Two functions, IOPSCopyPowerSourcesInfo() and IOPSCopyPowerSourcesList() are used to get information from the system and create list of available power sources. Then, by invoking IOPSGetPowerSourceDescription() function with references to our info object and particular source passed as arguments, we get a reference to a dictionary with all information about selected power source provided by vendor.
The key values for the dictionary are stored in IOKit/ps/IOPSKeys.h file. Unfortunately the dictionary does not have to contain values for all the keys as some of them, according to documentation, are optional.

Another apprach is to read system IO registry related to particular power source. The code is as follows:

#import <Cocoa/Cocoa.h>
#import <IOKit/IOKitLib.h>
#import "PowerSourceInfo.h"

@interface IORegPowerSourceInfo : NSObject {

}

- (PowerSourceInfo*) getPowerSourceInfo;

@end
and...
#import "IORegPowerSourceInfo.h"


@implementation IORegPowerSourceInfo

- (PowerSourceInfo*) getPowerSourceInfo {
 io_object_t deviceHandle;
 kern_return_t kernReturn;
 CFMutableDictionaryRef serviceMatch, properties;
 PowerSourceInfo* psi = nil;

 serviceMatch = IOServiceMatching("IOPMPowerSource");
 deviceHandle = IOServiceGetMatchingService(kIOMasterPortDefault, serviceMatch);
 kernReturn = IORegistryEntryCreateCFProperties(deviceHandle, &properties, NULL, 0);
 if(kernReturn == kIOReturnSuccess) {
  psi = [[PowerSourceInfo alloc] initWithDictionary:(NSDictionary*)properties];
 }
 CFRelease(properties);
 IOObjectRelease(deviceHandle);
 return psi;
}

@end

First, we get a dictionary matching IOService class called "IOPMPowerSource". Then we ask the system to return first IOService related to this class. Next step is to invoke IORegistryEntryCreateCFProperties, passing the device handle we just got, address of a pointer which will refer to a dictionary with all registry values of a particular power source. The function returns status code of type kern_return_t to inform whether it succeeded or failed. Finally, we have to release the memory.

In both examples, the PowerSourceInfo class is just a custom wrapper for the returned dictionary that exposes all keys as class methods.

OS X allows us to be notified about any changes that occur in different parts of the system (including power source chanage). To listen for those changes we have to create a RunLoopSource and attach it to current RunLoop. See the code below:

#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#import <IOKit/ps/IOPowerSources.h>

@interface PowerSourceInfoWorker : NSObject {
 WebView* webView;
 CFRunLoopSourceRef runLoopSource;
}

@property (readonly, nonatomic) WebView* webView;

-(void) startThread;
-(void) stopThread;

void powerSourceChange(void* context);

@end

PowerSourceInfoWorker is just a simple Cocoa class that exposes two messages: "startThread" and "stopThread" which will be used to add and remove our "listener" from system loop. The most important is the void powerSourceChange(void* context). It's a regular C function, which will be the callback from the loop.

NOTE: The WebView* webView attribute in the above code will be used later on to call JavaScript functions from the callback function.

Implementation of the PowerSourceInfoWorker:

#import "PowerSourceInfoWorker.h"


@implementation PowerSourceInfoWorker
@synthesize webView;

-(id) initWithWebView:(WebView*) aWebView {
 self = [super init];
 
 if(self) {
  webView = [aWebView retain];
 }
 return self;
}

-(void) dealloc {
 [self stopThread];
 [webView release];
 [super dealloc];
}

-(void) startThread {
 runLoopSource = (CFRunLoopSourceRef)IOPSNotificationCreateRunLoopSource(powerSourceChange, self);
 if(runLoopSource) {
  CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);
 }
}

-(void) stopThread {
 if(runLoopSource) {
  CFRunLoopSourceInvalidate(runLoopSource);
  CFRelease(runLoopSource);
 } 
}

void powerSourceChange(void* context) {
 NSArray* args = [NSArray arrayWithObjects: @"Power Source has changed!", nil];
 id win = [[(PowerSourceInfoWorker*)context webView] windowScriptObject];
 [win callWebScriptMethod:@"jsCallback" withArguments:args];
}

@end

In startThread new IOPS RunLoopSource is being created. The two params are our callback function and context (which in this case is "self"). Then, this newly created source is attached to currenct RunLoop in default mode (see documentation for details about available RunLoop modes).
stopThread is responsible for removing our RunLoopSource from the system loop, by invoking CFRunLoopSourceInvalidate function, and releasing resources.
The callback function simply gets WebView* from the context, which in my case is the PowerSourceInfoWorker class itself (see: startThread), and calls some WebScript method with arguments passed as an array.

And here comes the JavaScript callback function (with one argument):

function jsCallback(msg) {
    document.getElementById('status').innerHTML = msg + ' (' + new Date().toUTCString() + ')';
    //refresh view
}

The code is quite obvious, and I believe does not need any explanation.

There's one big advantage of the latter method. It returns much more information about power sources available in system than the IOPSCopyPowerSourcesInfo. I wrote a simple Cocoa application to present the differences between those two methods. For source code see the following Mercurial repository:
hg clone https://code.google.com/p/osx-battery-info-app/