osx.py
157 lines
| 4.6 KiB
| text/x-python
|
PythonLexer
Min RK
|
r22161 | """Inputhook for OS X | ||
Calls NSApp / CoreFoundation APIs via ctypes. | ||||
""" | ||||
Min RK
|
r22159 | |||
# obj-c boilerplate from appnope, used under BSD 2-clause | ||||
import ctypes | ||||
import ctypes.util | ||||
Min RK
|
r22776 | from threading import Event | ||
Min RK
|
r22159 | |||
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): | ||||
Min RK
|
r22160 | """create a selector name (for ObjC methods)""" | ||
Min RK
|
r22159 | return objc.sel_registerName(_utf8(name)) | ||
def C(classname): | ||||
"""get an ObjC Class by name""" | ||||
return objc.objc_getClass(_utf8(classname)) | ||||
Min RK
|
r22161 | # end obj-c boilerplate from appnope | ||
# CoreFoundation C-API calls we will use: | ||||
CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation')) | ||||
Min RK
|
r22159 | CFFileDescriptorCreate = CoreFoundation.CFFileDescriptorCreate | ||
CFFileDescriptorCreate.restype = void_p | ||||
Matthias Bussonnier
|
r26321 | CFFileDescriptorCreate.argtypes = [void_p, ctypes.c_int, ctypes.c_bool, void_p, void_p] | ||
Min RK
|
r22159 | |||
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') | ||||
Min RK
|
r22161 | |||
Min RK
|
r22159 | def _NSApp(): | ||
"""Return the global NSApplication instance (NSApp)""" | ||||
Matthias Bussonnier
|
r26316 | objc.objc_msgSend.argtypes = [void_p, void_p] | ||
Min RK
|
r22159 | return msg(C('NSApplication'), n('sharedApplication')) | ||
Min RK
|
r22161 | |||
Min RK
|
r22160 | def _wake(NSApp): | ||
"""Wake the Application""" | ||||
Matthias Bussonnier
|
r26316 | 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, # Type | ||||
0, # location | ||||
0, # flags | ||||
0, # timestamp | ||||
0, # window | ||||
None, # context | ||||
0, # subtype | ||||
0, # data1 | ||||
0, # data2 | ||||
Min RK
|
r22160 | ) | ||
Matthias Bussonnier
|
r26316 | objc.objc_msgSend.argtypes = [void_p, void_p, void_p, void_p] | ||
Min RK
|
r22160 | msg(NSApp, n('postEvent:atStart:'), void_p(event), True) | ||
Min RK
|
r22161 | |||
Min RK
|
r22776 | _triggered = Event() | ||
Min RK
|
r22159 | def _input_callback(fdref, flags, info): | ||
"""Callback to fire when there's input to be read""" | ||||
Min RK
|
r22776 | _triggered.set() | ||
Min RK
|
r22159 | CFFileDescriptorInvalidate(fdref) | ||
CFRelease(fdref) | ||||
NSApp = _NSApp() | ||||
Matthias Bussonnier
|
r26316 | objc.objc_msgSend.argtypes = [void_p, void_p, void_p] | ||
Min RK
|
r22159 | msg(NSApp, n('stop:'), NSApp) | ||
Min RK
|
r22160 | _wake(NSApp) | ||
Min RK
|
r22159 | |||
_c_callback_func_type = ctypes.CFUNCTYPE(None, void_p, void_p, void_p) | ||||
Min RK
|
r22160 | _c_input_callback = _c_callback_func_type(_input_callback) | ||
Min RK
|
r22159 | |||
Min RK
|
r22161 | |||
Min RK
|
r22159 | def _stop_on_read(fd): | ||
"""Register callback to stop eventloop when there's data on fd""" | ||||
Min RK
|
r22776 | _triggered.clear() | ||
Min RK
|
r22160 | fdref = CFFileDescriptorCreate(None, fd, False, _c_input_callback, None) | ||
Min RK
|
r22159 | CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack) | ||
source = CFFileDescriptorCreateRunLoopSource(None, fdref, 0) | ||||
loop = CFRunLoopGetCurrent() | ||||
CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes) | ||||
CFRelease(source) | ||||
Min RK
|
r22161 | |||
Min RK
|
r22159 | def inputhook(context): | ||
"""Inputhook for Cocoa (NSApp)""" | ||||
NSApp = _NSApp() | ||||
_stop_on_read(context.fileno()) | ||||
Matthias Bussonnier
|
r26316 | objc.objc_msgSend.argtypes = [void_p, void_p] | ||
Min RK
|
r22159 | msg(NSApp, n('run')) | ||
Min RK
|
r22776 | 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() | ||||