"""Inputhook for OS X Calls NSApp / CoreFoundation APIs via ctypes. """ # obj-c boilerplate from appnope, used under BSD 2-clause 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)) # end obj-c boilerplate from appnope # CoreFoundation C-API calls we will use: 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] 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] # From CFFileDescriptor.h kCFFileDescriptorReadCallBack = 1 kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, 'kCFRunLoopCommonModes') def _NSApp(): """Return the global NSApplication instance (NSApp)""" return msg(C('NSApplication'), n('sharedApplication')) def _wake(NSApp): """Wake the Application""" event = msg(C('NSEvent'), n('otherEventWithType:location:modifierFlags:' 'timestamp:windowNumber:context:subtype:data1:data2:'), 15, # Type 0, # location 0, # flags 0, # timestamp 0, # window None, # context 0, # subtype 0, # data1 0, # data2 ) 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() 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()) msg(NSApp, n('run')) if not _triggered.is_set(): # app closed without firing callback, # probably due to last window being closed. # Run the loop manually in this case, # since there may be events still to process (#9734) CoreFoundation.CFRunLoopRun()