|
|
|
|
|
|
|
|
|
|
|
|
|
from contextlib import contextmanager |
|
|
|
import ctypes |
|
import ctypes.util |
|
|
|
objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) |
|
_ = ctypes.cdll.LoadLibrary(ctypes.util.find_library('Foundation')) |
|
|
|
void_p = ctypes.c_void_p |
|
ull = ctypes.c_uint64 |
|
|
|
objc.objc_getClass.restype = void_p |
|
objc.sel_registerName.restype = void_p |
|
objc.objc_msgSend.restype = void_p |
|
objc.objc_msgSend.argtypes = [void_p, void_p] |
|
|
|
msg = objc.objc_msgSend |
|
|
|
def _utf8(s): |
|
"""ensure utf8 bytes""" |
|
if not isinstance(s, bytes): |
|
s = s.encode('utf8') |
|
return s |
|
|
|
def n(name): |
|
"""create a selector name (for methods)""" |
|
return objc.sel_registerName(_utf8(name)) |
|
|
|
def C(classname): |
|
"""get an ObjC Class by name""" |
|
ret = objc.objc_getClass(_utf8(classname)) |
|
assert ret is not None, "Couldn't find Class %s" % classname |
|
return ret |
|
|
|
|
|
|
|
NSActivityIdleDisplaySleepDisabled = (1 << 40) |
|
NSActivityIdleSystemSleepDisabled = (1 << 20) |
|
NSActivitySuddenTerminationDisabled = (1 << 14) |
|
NSActivityAutomaticTerminationDisabled = (1 << 15) |
|
NSActivityUserInitiated = (0x00FFFFFF | NSActivityIdleSystemSleepDisabled) |
|
NSActivityUserInitiatedAllowingIdleSystemSleep = (NSActivityUserInitiated & ~NSActivityIdleSystemSleepDisabled) |
|
NSActivityBackground = 0x000000FF |
|
NSActivityLatencyCritical = 0xFF00000000 |
|
|
|
def beginActivityWithOptions(options, reason=""): |
|
"""Wrapper for: |
|
|
|
[ [ NSProcessInfo processInfo] |
|
beginActivityWithOptions: (uint64)options |
|
reason: (str)reason |
|
] |
|
""" |
|
NSProcessInfo = C('NSProcessInfo') |
|
NSString = C('NSString') |
|
|
|
objc.objc_msgSend.argtypes = [void_p, void_p, void_p] |
|
reason = msg(NSString, n("stringWithUTF8String:"), _utf8(reason)) |
|
objc.objc_msgSend.argtypes = [void_p, void_p] |
|
info = msg(NSProcessInfo, n('processInfo')) |
|
objc.objc_msgSend.argtypes = [void_p, void_p, ull, void_p] |
|
activity = msg(info, |
|
n('beginActivityWithOptions:reason:'), |
|
ull(options), |
|
void_p(reason) |
|
) |
|
return activity |
|
|
|
def endActivity(activity): |
|
"""end a process activity assertion""" |
|
NSProcessInfo = C('NSProcessInfo') |
|
objc.objc_msgSend.argtypes = [void_p, void_p] |
|
info = msg(NSProcessInfo, n('processInfo')) |
|
objc.objc_msgSend.argtypes = [void_p, void_p, void_p] |
|
msg(info, n("endActivity:"), void_p(activity)) |
|
|
|
_theactivity = None |
|
|
|
def nope(): |
|
"""disable App Nap by setting NSActivityUserInitiatedAllowingIdleSystemSleep""" |
|
global _theactivity |
|
_theactivity = beginActivityWithOptions( |
|
NSActivityUserInitiatedAllowingIdleSystemSleep, |
|
"Because Reasons" |
|
) |
|
|
|
def nap(): |
|
"""end the caffeinated state started by `nope`""" |
|
global _theactivity |
|
if _theactivity is not None: |
|
endActivity(_theactivity) |
|
_theactivity = None |
|
|
|
def napping_allowed(): |
|
"""is napping allowed?""" |
|
return _theactivity is None |
|
|
|
@contextmanager |
|
def nope_scope( |
|
options=NSActivityUserInitiatedAllowingIdleSystemSleep, |
|
reason="Because Reasons" |
|
): |
|
"""context manager for beginActivityWithOptions. |
|
|
|
Within this context, App Nap will be disabled. |
|
""" |
|
activity = beginActivityWithOptions(options, reason) |
|
try: |
|
yield |
|
finally: |
|
endActivity(activity) |
|
|
|
__all__ = [ |
|
"NSActivityIdleDisplaySleepDisabled", |
|
"NSActivityIdleSystemSleepDisabled", |
|
"NSActivitySuddenTerminationDisabled", |
|
"NSActivityAutomaticTerminationDisabled", |
|
"NSActivityUserInitiated", |
|
"NSActivityUserInitiatedAllowingIdleSystemSleep", |
|
"NSActivityBackground", |
|
"NSActivityLatencyCritical", |
|
"beginActivityWithOptions", |
|
"endActivity", |
|
"nope", |
|
"nap", |
|
"napping_allowed", |
|
"nope_scope", |
|
] |
|
|