377 lines
14 KiB
Objective-C
377 lines
14 KiB
Objective-C
#import <Cocoa/Cocoa.h>
|
|
#import <Carbon/Carbon.h>
|
|
#import <IOKit/pwr_mgt/IOPMLib.h>
|
|
#import <LuaSkin/LuaSkin.h>
|
|
|
|
// Apple Private API items
|
|
#define kIOPMAssertionAppliesToLimitedPowerKey CFSTR("AppliesToLimitedPower")
|
|
CFBundleRef loginFramework = NULL;
|
|
typedef void (*SACLockScreenImmediatePtr)(void);
|
|
|
|
static IOPMAssertionID noIdleDisplaySleep = 0;
|
|
static IOPMAssertionID noIdleSystemSleep = 0;
|
|
static IOPMAssertionID noSystemSleep = 0;
|
|
|
|
NSString* stringFromError(unsigned int errorVal) {
|
|
NSDictionary *ioReturnMap =
|
|
@{@kIOReturnSuccess: @"success",
|
|
@kIOReturnError: @"general error",
|
|
@kIOReturnNoMemory: @"memory allocation error",
|
|
@kIOReturnNoResources: @"resource shortage",
|
|
@kIOReturnIPCError: @"Mach IPC failure",
|
|
@kIOReturnNoDevice: @"no such device",
|
|
@kIOReturnNotPrivileged: @"privilege violation",
|
|
@kIOReturnBadArgument: @"invalid argument",
|
|
@kIOReturnLockedRead: @"device is read locked",
|
|
@kIOReturnLockedWrite: @"device is write locked",
|
|
@kIOReturnExclusiveAccess: @"device is exclusive access",
|
|
@kIOReturnBadMessageID: @"bad IPC message ID",
|
|
@kIOReturnUnsupported: @"unsupported function",
|
|
@kIOReturnVMError: @"virtual memory error",
|
|
@kIOReturnInternalError: @"internal driver error",
|
|
@kIOReturnIOError: @"I/O error",
|
|
@kIOReturnCannotLock: @"cannot acquire lock",
|
|
@kIOReturnNotOpen: @"device is not open",
|
|
@kIOReturnNotReadable: @"device is not readable",
|
|
@kIOReturnNotWritable: @"device is not writeable",
|
|
@kIOReturnNotAligned: @"alignment error",
|
|
@kIOReturnBadMedia: @"media error",
|
|
@kIOReturnStillOpen: @"device is still open",
|
|
@kIOReturnRLDError: @"rld failure",
|
|
@kIOReturnDMAError: @"DMA failure",
|
|
@kIOReturnBusy: @"device is busy",
|
|
@kIOReturnTimeout: @"I/O timeout",
|
|
@kIOReturnOffline: @"device is offline",
|
|
@kIOReturnNotReady: @"device is not ready",
|
|
@kIOReturnNotAttached: @"device/channel is not attached",
|
|
@kIOReturnNoChannels: @"no DMA channels available",
|
|
@kIOReturnNoSpace: @"no space for data",
|
|
@kIOReturnPortExists: @"device port already exists",
|
|
@kIOReturnCannotWire: @"cannot wire physical memory",
|
|
@kIOReturnNoInterrupt: @"no interrupt attached",
|
|
@kIOReturnNoFrames: @"no DMA frames enqueued",
|
|
@kIOReturnMessageTooLarge: @"message is too large",
|
|
@kIOReturnNotPermitted: @"operation is not permitted",
|
|
@kIOReturnNoPower: @"device is without power",
|
|
@kIOReturnNoMedia: @"media is not present",
|
|
@kIOReturnUnformattedMedia: @"media is not formatted",
|
|
@kIOReturnUnsupportedMode: @"unsupported mode",
|
|
@kIOReturnUnderrun: @"data underrun",
|
|
@kIOReturnOverrun: @"data overrun",
|
|
@kIOReturnDeviceError: @"device error",
|
|
@kIOReturnNoCompletion: @"no completion routine",
|
|
@kIOReturnAborted: @"operation was aborted",
|
|
@kIOReturnNoBandwidth: @"bus bandwidth would be exceeded",
|
|
@kIOReturnNotResponding: @"device is not responding",
|
|
@kIOReturnInvalid: @"unanticipated driver error",
|
|
};
|
|
|
|
return [ioReturnMap objectForKey:[NSNumber numberWithInt:errorVal]];
|
|
}
|
|
|
|
// Create an IOPM Assertion of specified type and store its ID in the specified variable
|
|
static void caffeinate_create_assertion(lua_State *L, CFStringRef assertionType, IOPMAssertionID *assertionID) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
IOReturn result = 1;
|
|
|
|
if (*assertionID) return;
|
|
|
|
result = IOPMAssertionCreateWithDescription(assertionType,
|
|
CFSTR("hs.caffeinate"),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
assertionID);
|
|
|
|
if (result != kIOReturnSuccess) {
|
|
[skin logError:[NSString stringWithFormat:@"caffeinate_create_assertion: failed (%@)", stringFromError(result)]];
|
|
}
|
|
}
|
|
|
|
// Release a previously stored assertion
|
|
static void caffeinate_release_assertion(lua_State *L, IOPMAssertionID *assertionID) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
IOReturn result = 1;
|
|
|
|
if (!*assertionID) return;
|
|
|
|
result = IOPMAssertionRelease(*assertionID);
|
|
|
|
if (result != kIOReturnSuccess) {
|
|
[skin logError:[NSString stringWithFormat:@"caffeinate_release_assertion: failed (%@)", stringFromError(result)]];
|
|
}
|
|
|
|
*assertionID = 0;
|
|
}
|
|
|
|
// ----------------------- Functions for display sleep when user is idle ----------------------------
|
|
|
|
// Prevent display sleep if the user goes idle (and by implication, system sleep)
|
|
static int caffeinate_preventIdleDisplaySleep(lua_State *L) {
|
|
caffeinate_create_assertion(L, kIOPMAssertionTypePreventUserIdleDisplaySleep, &noIdleDisplaySleep);
|
|
return 0;
|
|
}
|
|
|
|
// Allow display sleep if the user goes idle
|
|
static int caffeinate_allowIdleDisplaySleep(lua_State *L) {
|
|
caffeinate_release_assertion(L, &noIdleDisplaySleep);
|
|
return 0;
|
|
}
|
|
|
|
// Determine if idle display sleep is currently prevented
|
|
static int caffeinate_isIdleDisplaySleepPrevented(lua_State *L) {
|
|
lua_pushboolean(L, noIdleDisplaySleep);
|
|
return 1;
|
|
}
|
|
|
|
// ----------------------- Functions for system sleep when user is idle ----------------------------
|
|
|
|
// Prevent system sleep if the user goes idle (display may still sleep)
|
|
static int caffeinate_preventIdleSystemSleep(lua_State *L) {
|
|
caffeinate_create_assertion(L, kIOPMAssertionTypePreventUserIdleSystemSleep, &noIdleSystemSleep);
|
|
return 0;
|
|
}
|
|
|
|
// Allow system sleep if the user goes idle
|
|
static int caffeinate_allowIdleSystemSleep(lua_State *L) {
|
|
caffeinate_release_assertion(L, &noIdleSystemSleep);
|
|
return 0;
|
|
}
|
|
|
|
// Determine if idle system sleep is currently prevented
|
|
static int caffeinate_isIdleSystemSleepPrevented(lua_State *L) {
|
|
lua_pushboolean(L, noIdleSystemSleep);
|
|
return 1;
|
|
}
|
|
|
|
// ----------------------- Functions for system sleep ----------------------------
|
|
|
|
// Prevent system sleep
|
|
static int caffeinate_preventSystemSleep(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
IOReturn result = 1;
|
|
BOOL ac_and_battery = false;
|
|
|
|
if lua_isboolean(L, 1)
|
|
ac_and_battery = lua_toboolean(L, 1);
|
|
lua_settop(L, 1);
|
|
|
|
caffeinate_create_assertion(L, kIOPMAssertionTypePreventSystemSleep, &noSystemSleep);
|
|
|
|
if (noSystemSleep) {
|
|
CFBooleanRef value;
|
|
if (ac_and_battery) {
|
|
value = kCFBooleanTrue;
|
|
} else {
|
|
value = kCFBooleanFalse;
|
|
}
|
|
result = IOPMAssertionSetProperty(noSystemSleep,
|
|
kIOPMAssertionAppliesToLimitedPowerKey,
|
|
(CFBooleanRef)value);
|
|
if (result != kIOReturnSuccess) {
|
|
[skin logError:[NSString stringWithFormat:@"ERROR: Unable to set systemSleep assertion property (%@)", stringFromError(result)]];
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Allow system sleep
|
|
static int caffeinate_allowSystemSleep(lua_State *L) {
|
|
caffeinate_release_assertion(L, &noSystemSleep);
|
|
return 0;
|
|
}
|
|
|
|
// Determine if system sleep is currently prevented
|
|
static int caffeinate_isSystemSleepPrevented(lua_State *L) {
|
|
lua_pushboolean(L, noSystemSleep);
|
|
return 1;
|
|
}
|
|
|
|
/// hs.caffeinate.systemSleep()
|
|
/// Function
|
|
/// Requests the system to sleep immediately
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * None
|
|
static int caffeinate_systemSleep(lua_State *L __unused) {
|
|
io_connect_t port = IOPMFindPowerManagement(MACH_PORT_NULL);
|
|
IOPMSleepSystem(port);
|
|
IOServiceClose(port);
|
|
return 0;
|
|
}
|
|
|
|
/// hs.caffeinate.declareUserActivity([id])
|
|
/// Function
|
|
/// Informs the OS that the user performed some activity
|
|
///
|
|
/// Parameters:
|
|
/// * id - An option number containing the assertion ID returned by a previous call of this function
|
|
///
|
|
/// Returns:
|
|
/// * A number containing the ID of the assertion generated by this function
|
|
///
|
|
/// Notes:
|
|
/// * This is intended to simulate user activity, for example to prevent displays from sleeping, or to wake them up
|
|
/// * It is not mandatory to re-use assertion IDs if you are calling this function mulitple times, but it is recommended that you do so if the calls are related
|
|
static int caffeinate_declareUserActivity(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TNUMBER | LS_TINTEGER | LS_TNIL | LS_TOPTIONAL, LS_TBREAK]; // The LS_TNIL is so people can call foo = hs.caffeinate.declareUserActivity(foo) and not have it error out the first time
|
|
|
|
IOPMAssertionID assertionID = kIOPMNullAssertionID;
|
|
|
|
if (lua_type(L, 1) == LUA_TNUMBER) {
|
|
assertionID = (uint32)lua_tointeger(L, 1);
|
|
}
|
|
IOPMAssertionDeclareUserActivity(CFSTR("hs.caffeinate.declareUserActivity()"), kIOPMUserActiveLocal, &assertionID);
|
|
|
|
lua_pushinteger(L, (int)assertionID);
|
|
return 1;
|
|
}
|
|
|
|
/// hs.caffeinate.lockScreen()
|
|
/// Function
|
|
/// Locks the displays
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * None
|
|
///
|
|
/// Notes:
|
|
/// * This function uses private Apple APIs and could therefore stop working in any given release of macOS without warning.
|
|
static int caffeinate_lockScreen(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TBREAK];
|
|
|
|
// Load the private API we need to call SACLockScreenImmediate()
|
|
if (!loginFramework) {
|
|
NSString *bundlePath = @"/System/Library/PrivateFrameworks/login.framework";
|
|
NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath];
|
|
loginFramework = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)bundleURL);
|
|
}
|
|
|
|
SACLockScreenImmediatePtr SACLockScreenImmediate;
|
|
SACLockScreenImmediate = (SACLockScreenImmediatePtr)CFBundleGetFunctionPointerForName(loginFramework, (__bridge CFStringRef)@"SACLockScreenImmediate");
|
|
if (!SACLockScreenImmediate) {
|
|
[skin logError:@"Unable to load SACLockScreenImmediate from private login.framework"];
|
|
} else {
|
|
SACLockScreenImmediate();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// hs.caffeinate.sessionProperties()
|
|
/// Function
|
|
/// Fetches information from the display server about the current session
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * A table containing information about the current session, or nil if an error occurred
|
|
///
|
|
/// Notes:
|
|
/// * The keys in this dictionary will vary based on the current state of the system (e.g. local vs VNC login, screen locked vs unlocked).
|
|
static int caffeinate_sessionProperties(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TBREAK];
|
|
|
|
CFDictionaryRef ref = CGSessionCopyCurrentDictionary();
|
|
|
|
[skin pushNSObject:(__bridge NSDictionary *)ref];
|
|
|
|
CFRelease(ref);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/// hs.caffeinate.currentAssertions()
|
|
/// Function
|
|
/// Fetches information about processes which are currently asserting display/power sleep restrictions
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * A table containing information about current power assertions, with process IDs (PID) as the keys, each of which may contain multiple assertions
|
|
static int caffeinate_currentAssertions(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TBREAK];
|
|
|
|
CFDictionaryRef assertions = NULL;
|
|
IOReturn result = IOPMCopyAssertionsByProcess(&assertions);
|
|
if (result != kIOReturnSuccess) {
|
|
[skin pushNSObject:@{}];
|
|
return 1;
|
|
}
|
|
|
|
[skin pushNSObject:(__bridge id)assertions];
|
|
CFRelease(assertions);
|
|
|
|
return 1;
|
|
}
|
|
|
|
// ----------------------- Lua/hs glue GAR ---------------------
|
|
|
|
static int caffeinate_gc(lua_State *L) {
|
|
// TODO: We should register which of the assertions we have active, somewhere that persists a reload()
|
|
caffeinate_allowIdleDisplaySleep(L);
|
|
caffeinate_allowIdleSystemSleep(L);
|
|
caffeinate_allowSystemSleep(L);
|
|
|
|
if (loginFramework) {
|
|
CFRelease(loginFramework);
|
|
loginFramework = NULL ;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const luaL_Reg caffeinatelib[] = {
|
|
{"preventIdleDisplaySleep", caffeinate_preventIdleDisplaySleep},
|
|
{"allowIdleDisplaySleep", caffeinate_allowIdleDisplaySleep},
|
|
{"isIdleDisplaySleepPrevented", caffeinate_isIdleDisplaySleepPrevented},
|
|
|
|
{"preventIdleSystemSleep", caffeinate_preventIdleSystemSleep},
|
|
{"allowIdleSystemSleep", caffeinate_allowIdleSystemSleep},
|
|
{"isIdleSystemSleepPrevented", caffeinate_isIdleSystemSleepPrevented},
|
|
|
|
{"_preventSystemSleep", caffeinate_preventSystemSleep},
|
|
{"allowSystemSleep", caffeinate_allowSystemSleep},
|
|
{"isSystemSleepPrevented", caffeinate_isSystemSleepPrevented},
|
|
{"systemSleep", caffeinate_systemSleep},
|
|
|
|
{"declareUserActivity", caffeinate_declareUserActivity},
|
|
{"lockScreen", caffeinate_lockScreen},
|
|
|
|
{"sessionProperties", caffeinate_sessionProperties},
|
|
|
|
{"currentAssertions", caffeinate_currentAssertions},
|
|
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static const luaL_Reg metalib[] = {
|
|
{"__gc", caffeinate_gc},
|
|
|
|
{NULL, NULL}
|
|
};
|
|
|
|
/* NOTE: The substring "hs_caffeinate_internal" in the following function's name
|
|
must match the require-path of this file, i.e. "hs.caffeinate.internal". */
|
|
|
|
int luaopen_hs_libcaffeinate(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin registerLibrary:"hs.caffeinate" functions:caffeinatelib metaFunctions:metalib];
|
|
|
|
return 1;
|
|
}
|