Воспользуемся вторым способом (приложение не нацелено на продакшн и его нужно реализовать быстро) и напишем приложение, которое будет выводить оставшееся время работы ноутбука от батареи в статус баре.
Принцип работы будет следующим:
- используя IOKit подпишемся на нотификации системы об изменении состояния батареи для обновления данных в статус баре (можно конечно сделать при помощи таймера, но это лишняя трата ресурсов системы)
- при изменении состояния батареи используя NSTask выполняем консольную команду и захватываем ее вывод
- парсим вывод и пересоздаем меню в статус баре
Чтобы все успешно заработало необходимо добавить IOKit.framework и зареференсить хедер IOKit/ps/IOPowerSources.h
Так же, нужно не забыть снять галочку Visible At Launch с NSWindow в Xib интерфейса приложения (это запретит окну автоматически появляться на экране, нам нужен только статус бар)
Чтобы скрыть иконку приложения из дока MacOS нужно в info.plist установить флаг LSUIElement в значение YES
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Capture Power Source updates and make sure our callback is called
CFRunLoopSourceRef loop = IOPSNotificationCreateRunLoopSource(PowerSourceChanged, (__bridge void *)self);
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, kCFRunLoopDefaultMode);
CFRelease(loop);
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
self.statusItem.title = @"Checking...";
self.statusItem.highlightMode = YES;
//request battery status for the first time
[self requestBatteryStatus];
}
// IOPS notification callback on power source change
static void PowerSourceChanged(void * context)
{
//requesting battery status
AppDelegate *self = (__bridge AppDelegate *)context;
[self requestBatteryStatus];
}
-(void)requestBatteryStatus
{
//create task & launch 'pmset' to get battery status string
NSPipe *pipe = [NSPipe pipe];
NSFileHandle *file = pipe.fileHandleForReading;
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/pmset";
task.arguments = @[@"-g", @"batt"];
task.standardOutput = pipe;
[task launch];
NSData *data = [file readDataToEndOfFile];
[file closeFile];
//parse string to get particular items
NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSString *statusData = [result componentsSeparatedByString:@" "][1];
NSArray *status = [statusData componentsSeparatedByString:@";"];
//battery charge percent
NSString *batteryPercent = [status[0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
//charging status
NSString *batteryChanging = [status[1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
//ETA
NSString *batteryEstimate = [status[2] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
//remaining hours
NSString *batteryRemaining = batteryEstimate;
NSRange range = [batteryEstimate rangeOfString:@"present"];
if(range.length > 0){
batteryEstimate = [[[batteryEstimate substringToIndex:range.location] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"()"]];
batteryRemaining = [batteryEstimate componentsSeparatedByString:@" "][0];
}
//recrete status bar menu
NSMenu *menu = [[NSMenu alloc] init];
[menu addItemWithTitle:batteryPercent action:nil keyEquivalent:@""];
[menu addItemWithTitle:batteryEstimate action:nil keyEquivalent:@""];
[menu addItemWithTitle:batteryChanging action:nil keyEquivalent:@""];
[menu addItem:[NSMenuItem separatorItem]];
[menu addItemWithTitle:@"Exit" action:@selector(exitAction:) keyEquivalent:@""];
self.statusItem.menu = menu;
self.statusItem.title = [batteryRemaining stringByAppendingString:@" ETA"];
NSLog(@"Request success!");
}
-(void)exitAction:(id)sender
{
exit(0);
}