AutoWakeScheduler.c [plain text]
#include <IOKit/hidsystem/IOHIDLib.h>
#include <IOKit/graphics/IOGraphicsTypes.h>
#include <syslog.h>
#include "PrivateLib.h"
#include "AutoWakeScheduler.h"
#include "RepeatingAutoWake.h"
enum {
kIOWakeTimer = 0,
kIOPowerOnTimer = 1,
kIOSleepTimer = 2,
kIOShutdownTimer = 3
};
#if TARGET_OS_EMBEDDED
#define MIN_SCHEDULE_TIME (5.0)
#else
#define MIN_SCHEDULE_TIME (10.0)
#endif
typedef void (*powerEventCallout)(CFDictionaryRef);
struct PowerEventBehavior {
CFMutableArrayRef array;
CFDictionaryRef currentEvent;
CFRunLoopTimerRef timer;
CFStringRef title;
struct PowerEventBehavior *sharedEvents;
powerEventCallout timerExpirationCallout;
powerEventCallout scheduleNextCallout;
powerEventCallout noScheduledEventCallout;
};
typedef struct PowerEventBehavior PowerEventBehavior;
PowerEventBehavior sleepBehavior;
PowerEventBehavior shutdownBehavior;
PowerEventBehavior restartBehavior;
PowerEventBehavior wakeBehavior;
PowerEventBehavior poweronBehavior;
PowerEventBehavior wakeorpoweronBehavior;
enum {
kBehaviorsCount = 6
};
PowerEventBehavior *behaviors[] =
{
&sleepBehavior,
&shutdownBehavior,
&restartBehavior,
&wakeBehavior,
&poweronBehavior,
&wakeorpoweronBehavior
};
static bool isEntryValidAndFuturistic(CFDictionaryRef, CFDateRef);
static void schedulePowerEvent(PowerEventBehavior *);
static bool purgePastEvents(PowerEventBehavior *);
static void copyScheduledPowerChangeArrays(void);
static CFDictionaryRef getEarliestUpcoming(PowerEventBehavior *);
static kern_return_t openHIDService(io_connect_t *);
static void wakeDozingMachine(void);
static bool isRepeating(CFDictionaryRef);
static CFDateRef _getScheduledEventDate(CFDictionaryRef);
static CFArrayRef copyMergedEventArray(PowerEventBehavior *,
PowerEventBehavior *);
static CFComparisonResult compareEvDates(CFDictionaryRef,
CFDictionaryRef, void *);
void poweronScheduleCallout(CFDictionaryRef);
void wakeScheduleCallout(CFDictionaryRef);
void wakeNoScheduledEventCallout(CFDictionaryRef);
void wakeTimerExpiredCallout(CFDictionaryRef);
void poweronTimerExpiredCallout(CFDictionaryRef);
void sleepTimerExpiredCallout(CFDictionaryRef);
void shutdownTimerExpiredCallout(CFDictionaryRef);
void restartTimerExpiredCallout(CFDictionaryRef);
__private_extern__ void
AutoWake_prime(void)
{
PowerEventBehavior *this_behavior;
int i;
for(i=0; i<kBehaviorsCount; i++)
{
this_behavior = behaviors[i];
bzero(this_behavior, sizeof(PowerEventBehavior));
}
wakeBehavior.title = CFSTR(kIOPMAutoWake);
poweronBehavior.title = CFSTR(kIOPMAutoPowerOn);
wakeorpoweronBehavior.title = CFSTR(kIOPMAutoWakeOrPowerOn);
sleepBehavior.title = CFSTR(kIOPMAutoSleep);
shutdownBehavior.title = CFSTR(kIOPMAutoShutdown);
restartBehavior.title = CFSTR(kIOPMAutoRestart);
wakeBehavior.timerExpirationCallout = wakeTimerExpiredCallout;
poweronBehavior.timerExpirationCallout = poweronTimerExpiredCallout;
sleepBehavior.timerExpirationCallout = sleepTimerExpiredCallout;
shutdownBehavior.timerExpirationCallout = shutdownTimerExpiredCallout;
restartBehavior.timerExpirationCallout = restartTimerExpiredCallout;
wakeBehavior.scheduleNextCallout = wakeScheduleCallout;
wakeBehavior.noScheduledEventCallout = wakeNoScheduledEventCallout;
poweronBehavior.scheduleNextCallout = poweronScheduleCallout;
wakeBehavior.sharedEvents =
poweronBehavior.sharedEvents = &wakeorpoweronBehavior;
copyScheduledPowerChangeArrays();
for(i=0; i<kBehaviorsCount; i++)
{
this_behavior = behaviors[i];
if(!this_behavior) continue;
purgePastEvents(this_behavior);
schedulePowerEvent(this_behavior);
}
return;
}
__private_extern__ void
AutoWakeSleepWakeNotification(
natural_t messageType,
int runState)
{
PowerEventBehavior *this_behavior = NULL;
int i;
if (kIOMessageSystemWillSleep == messageType)
{
schedulePowerEvent(&wakeBehavior);
} else
if ( (kIOMessageSystemHasPoweredOn == messageType)
&& (kRStateNormal == runState) )
{
for(i=0; i<kBehaviorsCount; i++)
{
this_behavior = behaviors[i];
if(!this_behavior) continue;
purgePastEvents(this_behavior);
schedulePowerEvent(this_behavior);
}
}
}
__private_extern__ void
AutoWakePrefsHaveChanged(void)
{
PowerEventBehavior *this_behavior = NULL;
int i;
copyScheduledPowerChangeArrays();
IOPMSchedulePowerEvent(
NULL, NULL,
CFSTR(kIOPMAutoWakeScheduleImmediate) );
IOPMSchedulePowerEvent(
NULL, NULL,
CFSTR(kIOPMAutoPowerScheduleImmediate) );
for(i=0; i<kBehaviorsCount; i++)
{
this_behavior = behaviors[i];
if(!this_behavior) continue;
schedulePowerEvent(this_behavior);
}
}
static void
handleTimerExpiration(CFRunLoopTimerRef blah, void *info)
{
PowerEventBehavior *behave = (PowerEventBehavior *)info;
if(!behave) return;
behave->timer = 0;
if( behave->timerExpirationCallout ) {
(*behave->timerExpirationCallout)(behave->currentEvent);
}
if(isRepeating(behave->currentEvent)) {
RepeatingAutoWakeRepeatingEventOcurred(behave->currentEvent);
}
return;
}
static void
schedulePowerEvent(PowerEventBehavior *behave)
{
static CFRunLoopTimerContext tmr_context = {0,0,0,0,0};
CFAbsoluteTime fire_time = 0.0;
CFDictionaryRef upcoming = NULL;
CFDateRef temp_date = NULL;
if(behave->timer)
{
CFRunLoopTimerInvalidate(behave->timer);
behave->timer = 0;
}
upcoming = getEarliestUpcoming(behave);
if(!upcoming)
{
if (behave->noScheduledEventCallout) {
(*behave->noScheduledEventCallout)(NULL);
}
return;
}
if ( behave->scheduleNextCallout ) {
(*behave->scheduleNextCallout)(upcoming);
}
behave->currentEvent = (CFDictionaryRef)upcoming;
tmr_context.info = (void *)behave;
temp_date = _getScheduledEventDate(upcoming);
if(!temp_date) goto exit;
fire_time = CFDateGetAbsoluteTime(temp_date);
behave->timer = CFRunLoopTimerCreate(0, fire_time, 0.0, 0,
0, handleTimerExpiration, &tmr_context);
if(behave->timer)
{
CFRunLoopAddTimer( CFRunLoopGetCurrent(),
behave->timer,
kCFRunLoopDefaultMode);
CFRelease(behave->timer);
}
exit:
return;
}
void poweronScheduleCallout(CFDictionaryRef event)
{
IOPMSchedulePowerEvent( _getScheduledEventDate(event),
NULL, CFSTR(kIOPMAutoPowerScheduleImmediate) );
}
void poweronTimerExpiredCallout(CFDictionaryRef event __unused)
{
schedulePowerEvent(&poweronBehavior);
}
void wakeScheduleCallout(CFDictionaryRef event)
{
CFDateRef chosenWakeDate = _getScheduledEventDate(event);
_pm_scheduledevent_choose_best_wake_event(kChooseFullWake,
CFDateGetAbsoluteTime(chosenWakeDate));
}
void wakeTimerExpiredCallout(CFDictionaryRef event __unused)
{
wakeDozingMachine();
}
void wakeNoScheduledEventCallout(CFDictionaryRef event __unused)
{
_pm_scheduledevent_choose_best_wake_event(kChooseFullWake, 0);
}
void sleepTimerExpiredCallout(CFDictionaryRef event __unused)
{
_askNicelyThenSleepSystem();
}
void shutdownTimerExpiredCallout(CFDictionaryRef event __unused)
{
_askNicelyThenShutdownSystem();
}
void restartTimerExpiredCallout(CFDictionaryRef event __unused)
{
_askNicelyThenRestartSystem();
}
static bool
isEntryValidAndFuturistic(CFDictionaryRef wakeup_dict, CFDateRef date_now)
{
CFDateRef wakeup_date;
bool ret = true;
wakeup_dict = isA_CFDictionary(wakeup_dict);
if(!wakeup_dict)
{
ret = false;
} else
{
wakeup_date = isA_CFDate(CFDictionaryGetValue(wakeup_dict,
CFSTR(kIOPMPowerEventTimeKey)));
if( !wakeup_date
|| (kCFCompareLessThan == CFDateCompare(wakeup_date, date_now, 0)))
{
ret = false;
}
}
return ret;
}
static bool
purgePastEvents(PowerEventBehavior *behave)
{
bool array_has_changed = false;
CFDateRef date_now;
bool ret;
SCPreferencesRef prefs = 0;
if( !behave
|| !behave->title
|| !behave->array
|| (0 == CFArrayGetCount(behave->array)))
{
return true;
}
date_now = CFDateCreate(0, CFAbsoluteTimeGetCurrent());
while( (0 < CFArrayGetCount(behave->array))
&& !isEntryValidAndFuturistic(CFArrayGetValueAtIndex(behave->array, 0), date_now))
{
CFArrayRemoveValueAtIndex(behave->array, 0);
array_has_changed = true;
}
CFRelease(date_now);
if(array_has_changed)
{
prefs = SCPreferencesCreate(0, CFSTR("IOKit-AutoWake"),
CFSTR(kIOPMAutoWakePrefsPath));
if(!prefs) return 0;
if(!SCPreferencesLock(prefs, true)) {
ret = false;
goto exit;
}
SCPreferencesSetValue(prefs, behave->title, behave->array);
if(!SCPreferencesCommitChanges(prefs)) {
ret = false;
goto exit;
}
SCPreferencesUnlock(prefs);
}
ret = true;
exit:
if(prefs) CFRelease(prefs);
return ret;
}
static void
copyScheduledPowerChangeArrays(void)
{
CFArrayRef tmp;
SCPreferencesRef prefs;
PowerEventBehavior *this_behavior;
int i;
prefs = SCPreferencesCreate(0,
CFSTR("PM-configd-AutoWake"),
CFSTR(kIOPMAutoWakePrefsPath));
if(!prefs) return;
for(i=0; i<kBehaviorsCount; i++)
{
this_behavior = behaviors[i];
if(this_behavior->array) {
CFRelease(this_behavior->array);
this_behavior->array = NULL;
}
tmp = isA_CFArray(SCPreferencesGetValue(prefs, this_behavior->title));
if(tmp && (0 < CFArrayGetCount(tmp))) {
this_behavior->array = CFArrayCreateMutableCopy(0, 0, tmp);
} else {
this_behavior->array = NULL;
}
}
CFRelease(prefs);
}
static CFDictionaryRef
getEarliestUpcoming(PowerEventBehavior *b)
{
CFArrayRef arr = NULL;
CFDateRef now = NULL;
CFDictionaryRef the_result = NULL;
int i, count;
if(!b) return NULL;
if(b->sharedEvents) {
arr = copyMergedEventArray(b, b->sharedEvents);
} else {
arr = b->array;
}
if(!arr || (0 == CFArrayGetCount(arr))) goto exit;
count = CFArrayGetCount(arr);
if(0 == count) goto exit;
now = CFDateCreate(0, CFAbsoluteTimeGetCurrent() + MIN_SCHEDULE_TIME);
i = 0;
while( (i < count)
&& !isEntryValidAndFuturistic(CFArrayGetValueAtIndex(arr, i), now) )
{
i++;
}
CFRelease(now);
if(i < count)
{
the_result = CFArrayGetValueAtIndex(arr, i);
}
exit:
if(arr && b->sharedEvents) CFRelease(arr);
return the_result;
}
static CFComparisonResult
compareEvDates(
CFDictionaryRef a1,
CFDictionaryRef a2,
void *c __unused)
{
CFDateRef d1, d2;
a1 = isA_CFDictionary(a1);
a2 = isA_CFDictionary(a2);
if(!a1) return kCFCompareGreaterThan;
else if(!a2) return kCFCompareLessThan;
d1 = isA_CFDate(CFDictionaryGetValue(a1, CFSTR(kIOPMPowerEventTimeKey)));
d2 = isA_CFDate(CFDictionaryGetValue(a2, CFSTR(kIOPMPowerEventTimeKey)));
if(!d1) return kCFCompareGreaterThan;
else if(!d2) return kCFCompareLessThan;
return CFDateCompare(d1, d2, 0);
}
static CFArrayRef
copyMergedEventArray(
PowerEventBehavior *a,
PowerEventBehavior *b)
{
CFMutableArrayRef merged;
int bcount;
CFRange rng;
if(!a || !b) return NULL;
if(!a->array && !b->array) return NULL;
if(!a->array) return CFRetain(b->array);
if(!b->array) return CFRetain(a->array);
merged = CFArrayCreateMutableCopy(0, 0, a->array);
bcount = CFArrayGetCount(b->array);
rng = CFRangeMake(0, bcount);
CFArrayAppendArray(merged, b->array, rng);
bcount = CFArrayGetCount(merged);
rng = CFRangeMake(0, bcount);
CFArraySortValues(merged, rng, (CFComparatorFunction)compareEvDates, 0);
return merged;
}
#if HAVE_HID_SYSTEM
static kern_return_t openHIDService(io_connect_t *connection)
{
kern_return_t kr;
io_service_t service;
io_connect_t hid_connect = MACH_PORT_NULL;
service = IOServiceGetMatchingService(MACH_PORT_NULL,
IOServiceMatching(kIOHIDSystemClass));
if (MACH_PORT_NULL == service) {
return kIOReturnNotFound;
}
kr = IOServiceOpen( service, mach_task_self(),
kIOHIDParamConnectType, &hid_connect);
IOObjectRelease(service);
if (kr != KERN_SUCCESS) {
return kr;
}
*connection = hid_connect;
return kr;
}
#endif
static void wakeDozingMachine(void)
{
#if HAVE_HID_SYSTEM
IOGPoint loc;
kern_return_t kr;
NXEvent nullEvent = {NX_NULLEVENT, {0, 0}, 0, -1, 0};
static io_connect_t io_connection = MACH_PORT_NULL;
if (io_connection == MACH_PORT_NULL)
{
kr = openHIDService(&io_connection);
if (kr != KERN_SUCCESS)
{
io_connection = MACH_PORT_NULL;
return;
}
}
IOHIDPostEvent( io_connection, NX_NULLEVENT, loc,
&nullEvent.data, FALSE, 0, FALSE );
#endif
return;
}
static bool
isRepeating(CFDictionaryRef event)
{
CFStringRef whose = CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventAppNameKey));
if( whose && CFEqual(whose, CFSTR("Repeating")) )
return true;
else
return false;
}
static CFDateRef
_getScheduledEventDate(CFDictionaryRef event)
{
return isA_CFDate(CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventTimeKey)));
}