| | """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) |
| |
|
| |
|
| | _triggered = Event() |
| |
|
| | def _input_callback(fdref, flags, info): |
| | """Callback to fire when there's input to be read""" |
| | _triggered.set() |
| | 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""" |
| | _triggered.clear() |
| | 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')) |
| | if not _triggered.is_set(): |
| | |
| | |
| | |
| | |
| | CoreFoundation.CFRunLoopRun() |
| |
|