|
|
"""Inputhook for OS X |
|
|
|
|
|
Calls NSApp / CoreFoundation APIs via ctypes. |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
import ctypes |
|
|
import ctypes.util |
|
|
from threading import Event |
|
|
|
|
|
objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) |
|
|
|
|
|
void_p = ctypes.c_void_p |
|
|
|
|
|
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 ObjC methods)""" |
|
|
return objc.sel_registerName(_utf8(name)) |
|
|
|
|
|
def C(classname): |
|
|
"""get an ObjC Class by name""" |
|
|
return objc.objc_getClass(_utf8(classname)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library("CoreFoundation")) |
|
|
|
|
|
CFFileDescriptorCreate = CoreFoundation.CFFileDescriptorCreate |
|
|
CFFileDescriptorCreate.restype = void_p |
|
|
CFFileDescriptorCreate.argtypes = [void_p, ctypes.c_int, ctypes.c_bool, void_p, void_p] |
|
|
|
|
|
CFFileDescriptorGetNativeDescriptor = CoreFoundation.CFFileDescriptorGetNativeDescriptor |
|
|
CFFileDescriptorGetNativeDescriptor.restype = ctypes.c_int |
|
|
CFFileDescriptorGetNativeDescriptor.argtypes = [void_p] |
|
|
|
|
|
CFFileDescriptorEnableCallBacks = CoreFoundation.CFFileDescriptorEnableCallBacks |
|
|
CFFileDescriptorEnableCallBacks.restype = None |
|
|
CFFileDescriptorEnableCallBacks.argtypes = [void_p, ctypes.c_ulong] |
|
|
|
|
|
CFFileDescriptorCreateRunLoopSource = CoreFoundation.CFFileDescriptorCreateRunLoopSource |
|
|
CFFileDescriptorCreateRunLoopSource.restype = void_p |
|
|
CFFileDescriptorCreateRunLoopSource.argtypes = [void_p, void_p, void_p] |
|
|
|
|
|
CFRunLoopGetCurrent = CoreFoundation.CFRunLoopGetCurrent |
|
|
CFRunLoopGetCurrent.restype = void_p |
|
|
|
|
|
CFRunLoopAddSource = CoreFoundation.CFRunLoopAddSource |
|
|
CFRunLoopAddSource.restype = None |
|
|
CFRunLoopAddSource.argtypes = [void_p, void_p, void_p] |
|
|
|
|
|
CFRelease = CoreFoundation.CFRelease |
|
|
CFRelease.restype = None |
|
|
CFRelease.argtypes = [void_p] |
|
|
|
|
|
CFFileDescriptorInvalidate = CoreFoundation.CFFileDescriptorInvalidate |
|
|
CFFileDescriptorInvalidate.restype = None |
|
|
CFFileDescriptorInvalidate.argtypes = [void_p] |
|
|
|
|
|
|
|
|
kCFFileDescriptorReadCallBack = 1 |
|
|
kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, 'kCFRunLoopCommonModes') |
|
|
|
|
|
|
|
|
def _NSApp(): |
|
|
"""Return the global NSApplication instance (NSApp)""" |
|
|
objc.objc_msgSend.argtypes = [void_p, void_p] |
|
|
return msg(C('NSApplication'), n('sharedApplication')) |
|
|
|
|
|
|
|
|
def _wake(NSApp): |
|
|
"""Wake the Application""" |
|
|
objc.objc_msgSend.argtypes = [ |
|
|
void_p, |
|
|
void_p, |
|
|
void_p, |
|
|
void_p, |
|
|
void_p, |
|
|
void_p, |
|
|
void_p, |
|
|
void_p, |
|
|
void_p, |
|
|
void_p, |
|
|
void_p, |
|
|
] |
|
|
event = msg( |
|
|
C("NSEvent"), |
|
|
n( |
|
|
"otherEventWithType:location:modifierFlags:" |
|
|
"timestamp:windowNumber:context:subtype:data1:data2:" |
|
|
), |
|
|
15, |
|
|
0, |
|
|
0, |
|
|
0, |
|
|
0, |
|
|
None, |
|
|
0, |
|
|
0, |
|
|
0, |
|
|
) |
|
|
objc.objc_msgSend.argtypes = [void_p, void_p, void_p, void_p] |
|
|
msg(NSApp, n('postEvent:atStart:'), void_p(event), True) |
|
|
|
|
|
|
|
|
def _input_callback(fdref, flags, info): |
|
|
"""Callback to fire when there's input to be read""" |
|
|
CFFileDescriptorInvalidate(fdref) |
|
|
CFRelease(fdref) |
|
|
NSApp = _NSApp() |
|
|
objc.objc_msgSend.argtypes = [void_p, void_p, void_p] |
|
|
msg(NSApp, n('stop:'), NSApp) |
|
|
_wake(NSApp) |
|
|
|
|
|
_c_callback_func_type = ctypes.CFUNCTYPE(None, void_p, void_p, void_p) |
|
|
_c_input_callback = _c_callback_func_type(_input_callback) |
|
|
|
|
|
|
|
|
def _stop_on_read(fd): |
|
|
"""Register callback to stop eventloop when there's data on fd""" |
|
|
fdref = CFFileDescriptorCreate(None, fd, False, _c_input_callback, None) |
|
|
CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack) |
|
|
source = CFFileDescriptorCreateRunLoopSource(None, fdref, 0) |
|
|
loop = CFRunLoopGetCurrent() |
|
|
CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes) |
|
|
CFRelease(source) |
|
|
|
|
|
|
|
|
def inputhook(context): |
|
|
"""Inputhook for Cocoa (NSApp)""" |
|
|
NSApp = _NSApp() |
|
|
_stop_on_read(context.fileno()) |
|
|
objc.objc_msgSend.argtypes = [void_p, void_p] |
|
|
msg(NSApp, n('run')) |
|
|
|