#include <notify.h>
#include <utmpx.h>
#include <sys/stat.h>
#include <err.h>
#include <asl.h>
#include <sys/queue.h>
#include <sys/param.h>
#include "PrivateLib.h"
#include "TTYKeepAwake.h"
#include "PMAssertions.h"
#include "PMSettings.h"
#include "PMConnection.h"
#if !TARGET_OS_EMBEDDED
#ifndef DEVMAXPATHSIZE
#define DEVMAXPATHSIZE 128
#endif
typedef char dev_path_t[DEVMAXPATHSIZE];
SLIST_HEAD(ttyhead, ttyentry);
struct ttyentry {
dev_path_t ttydev;
SLIST_ENTRY(ttyentry) next;
};
static char s_activetty_names[DEVMAXPATHSIZE * 4];
#define kMinIdleCheckTime 10
static CFStringRef kTTYAssertion = CFSTR("com.apple.powermanagement.ttyassertion");
static struct ttyhead s_activettys = SLIST_HEAD_INITIALIZER(s_activettys);
static time_t settingIdleSleepSeconds = 0;
static bool settingTTYSPreventSleep = true;
static IOPMAssertionID s_assertion = 0;
static int s_utmpx_notify_token = -1;
static dispatch_source_t s_timer_source;
static dispatch_queue_t s_tty_queue;
static void freettys(void);
static void addtty(char *ttyname);
static void read_logins(void);
static boolean_t ttys_are_active(time_t *time_to_idle_out);
static void create_assertion(void);
static void release_assertion(void);
static void rearm_timer(time_t time_to_idle);
static void pause_timer(void);
static void cleanup_tty_tracking(void);
void TTYKeepAwake_prime(void)
{
dispatch_queue_t q_default;
uint32_t status;
int result = -1;
s_tty_queue = dispatch_queue_create("com.apple.powermanagement.tty", 0);
if (!s_tty_queue) {
goto finish;
}
q_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
if (!q_default) {
goto finish;
}
status = notify_register_dispatch(UTMPX_CHANGE_NOTIFICATION,
&s_utmpx_notify_token, q_default, ^(int t){ read_logins(); });
if (status != NOTIFY_STATUS_OK) {
result = -1;
goto finish;
}
s_timer_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
q_default);
if (!s_timer_source) {
result = -1;
goto finish;
}
dispatch_source_set_event_handler(s_timer_source, ^{
TTYKeepAwakeConsiderAssertion();
});
dispatch_source_set_timer(s_timer_source, DISPATCH_TIME_FOREVER, 0, 0);
dispatch_resume(s_timer_source);
TTYKeepAwakePrefsHaveChanged();
read_logins();
result = 0; finish:
if (result) cleanup_tty_tracking();
return;
}
#define kUseDefaultMinutesWhenSystemSleepIsNever 180
#define SEC_PER_MIN 60UL
void TTYKeepAwakePrefsHaveChanged( void )
{
CFDictionaryRef activePMSettings;
CFNumberRef systemIdleNum;
CFNumberRef ttysPreventNum;
int systemIdleMinutes;
int ttysPreventSleep;
activePMSettings = PMSettings_CopyActivePMSettings();
if(!activePMSettings)
goto finish;
systemIdleNum = isA_CFNumber(CFDictionaryGetValue(activePMSettings,
CFSTR(kIOPMSystemSleepKey)));
if (!systemIdleNum)
goto finish;
CFNumberGetValue(systemIdleNum, kCFNumberIntType, &systemIdleMinutes);
if (0 == systemIdleMinutes) {
systemIdleMinutes = kUseDefaultMinutesWhenSystemSleepIsNever;
}
ttysPreventNum = isA_CFNumber(CFDictionaryGetValue(activePMSettings,
CFSTR(kIOPMTTYSPreventSleepKey)));
if(!ttysPreventNum)
goto finish;
CFNumberGetValue(ttysPreventNum, kCFNumberIntType, &ttysPreventSleep);
dispatch_async(s_tty_queue, ^{
settingIdleSleepSeconds = systemIdleMinutes * SEC_PER_MIN;
settingTTYSPreventSleep = ttysPreventSleep;
});
TTYKeepAwakeConsiderAssertion();
finish:
if (activePMSettings) CFRelease(activePMSettings);
return;
}
bool TTYKeepAwakeConsiderAssertion( void )
{
boolean_t active;
bool allow_sleep = true;
time_t time_to_idle = 0;
active = ttys_are_active(&time_to_idle);
if (active && settingTTYSPreventSleep)
{
time_to_idle = MAX(time_to_idle, kMinIdleCheckTime);
create_assertion();
rearm_timer(time_to_idle);
allow_sleep = false;
} else {
release_assertion();
pause_timer();
}
return allow_sleep;
}
static void read_logins(void)
{
struct utmpx *ent;
int cnt = 0;
freettys();
setutxent();
while((ent = getutxent()))
{
if (0 < strlen(ent->ut_host))
{
addtty(ent->ut_line);
cnt++;
}
}
endutxent();
if (cnt) _unclamp_silent_running();
TTYKeepAwakeConsiderAssertion();
}
static void freettys(void)
{
dispatch_sync(s_tty_queue, ^{
struct ttyentry *tty;
struct ttyentry *tmptty;
SLIST_FOREACH_SAFE(tty, &s_activettys, next, tmptty) {
SLIST_REMOVE(&s_activettys, tty, ttyentry, next);
free(tty);
}
});
}
static void addtty(char *ttyname)
{
struct ttyentry *tty;
size_t len;
tty = malloc(sizeof(*tty));
if (!tty)
goto finish;
len = strlcpy(tty->ttydev, "/dev/", sizeof(tty->ttydev));
if (len > sizeof(tty->ttydev))
goto finish;
len = strlcat(tty->ttydev, ttyname, sizeof(tty->ttydev));
if (len > sizeof(tty->ttydev))
goto finish;
dispatch_async(s_tty_queue, ^{
SLIST_INSERT_HEAD(&s_activettys, tty, next);
});
tty = NULL;
finish:
if (tty)
free(tty);
return;
}
static boolean_t ttys_are_active(time_t *time_to_idle_out)
{
time_t curtime;
__block time_t time_to_idle;
__block boolean_t active = FALSE;
*time_to_idle_out = time_to_idle = 0;
bzero(s_activetty_names, sizeof(s_activetty_names));
curtime = time(NULL);
if (curtime == (time_t)-1) {
goto finish;
}
dispatch_sync(s_tty_queue, ^{
struct ttyentry *tty;
struct stat sb;
time_t minimum_idle_secs;
bool isPrintedFirst = TRUE;
minimum_idle_secs = settingIdleSleepSeconds;
SLIST_FOREACH(tty, &s_activettys, next) {
time_t current_idle_secs;
int result;
result = stat(tty->ttydev, &sb);
if (result != 0)
continue;
current_idle_secs = curtime - sb.st_atime + 1;
if (current_idle_secs > settingIdleSleepSeconds) {
continue;
}
minimum_idle_secs = MIN(current_idle_secs, minimum_idle_secs);
active = TRUE;
if (isPrintedFirst) {
isPrintedFirst = FALSE;
} else { strlcat(s_activetty_names, ", ", sizeof(s_activetty_names));
}
strlcat(s_activetty_names, tty->ttydev, sizeof(s_activetty_names));
}
time_to_idle = (active) ? (settingIdleSleepSeconds - minimum_idle_secs) : 0;
});
*time_to_idle_out = time_to_idle;
finish:
return active;
}
static void create_assertion()
{
dispatch_async(s_tty_queue, ^{
CFMutableDictionaryRef assertionProperties = NULL;
CFStringRef activeTTYList = NULL;
int i = kIOPMAssertionLevelOn;
CFNumberRef n1 = NULL;
assertionProperties = CFDictionaryCreateMutable(0, 6, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (!assertionProperties)
return;
CFDictionarySetValue(assertionProperties, kIOPMAssertionTypeKey, kIOPMAssertionTypePreventUserIdleSystemSleep);
CFDictionarySetValue(assertionProperties, kIOPMAssertionNameKey, kTTYAssertion);
CFDictionarySetValue(assertionProperties, kIOPMAssertionHumanReadableReasonKey, kAssertionHumanReadableReasonTTY);
CFDictionarySetValue(assertionProperties, kIOPMAssertionLocalizationBundlePathKey, kPowerManagementBundlePathString);
n1 = CFNumberCreate(0, kCFNumberIntType, &i);
if (n1) {
CFDictionarySetValue(assertionProperties, kIOPMAssertionLevelKey, n1);
CFRelease(n1);
}
activeTTYList = CFStringCreateWithFormat(0, NULL, CFSTR("%s"), s_activetty_names);
if (activeTTYList) {
CFDictionarySetValue(assertionProperties, kIOPMAssertionDetailsKey, activeTTYList);
CFRelease(activeTTYList);
}
InternalCreateAssertion(assertionProperties, &s_assertion);
CFRelease(assertionProperties);
});
}
static void release_assertion(void)
{
dispatch_async(s_tty_queue, ^{
InternalReleaseAssertion(&s_assertion);
});
}
static void rearm_timer(time_t time_to_idle)
{
dispatch_async(s_tty_queue, ^{
dispatch_source_set_timer(s_timer_source,
dispatch_time(DISPATCH_TIME_NOW, time_to_idle * NSEC_PER_SEC),
DISPATCH_TIME_FOREVER, 1 * NSEC_PER_SEC);
});
}
static void pause_timer(void)
{
dispatch_async(s_tty_queue, ^{
dispatch_source_set_timer(s_timer_source, DISPATCH_TIME_FOREVER, 0, 0);
});
}
static void cleanup_tty_tracking(void)
{
freettys();
dispatch_sync(s_tty_queue, ^{
if (s_utmpx_notify_token != -1) {
notify_cancel(s_utmpx_notify_token);
s_utmpx_notify_token = -1;
}
if (s_timer_source) {
dispatch_release(s_timer_source);
s_timer_source = NULL;
}
});
dispatch_release(s_tty_queue);
s_tty_queue = NULL;
}
#endif