|
|
|
|
|
|
|
|
|
|
|
|
|
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", |
|
] |
|
|