-
-
Notifications
You must be signed in to change notification settings - Fork 132
Implemented native inputhooks for MacOSX backend #156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| """Inputhook for OS X | ||
|
|
||
| Calls NSApp / CoreFoundation APIs via ctypes. | ||
| """ | ||
|
|
||
| import os | ||
| from pydev_ipython.inputhook import stdin_ready | ||
| import time | ||
| from threading import Thread, Event | ||
|
|
||
| # obj-c boilerplate from appnope, used under BSD 2-clause | ||
|
|
||
| import ctypes | ||
| import ctypes.util | ||
|
|
||
| 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 | ||
|
|
||
| ccounter = True | ||
|
|
||
| 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) | ||
|
|
||
| class Timer(Thread): | ||
| def __init__(self, callback=None, interval=0.1): | ||
| super().__init__() | ||
| self.callback = callback | ||
| self.interval = interval | ||
| self.stop = Event() | ||
|
|
||
| def run(self, *args, **kwargs): | ||
| if callable(self.callback): | ||
| while not self.stop.is_set(): | ||
| time.sleep(self.interval) | ||
| self.callback(self.stop) | ||
|
|
||
| def inputhook_mac(): | ||
|
|
||
| import datetime | ||
|
||
| dt = datetime.datetime.utcnow() | ||
|
|
||
| _r, _w = os.pipe() | ||
| def cb(stop): | ||
| if stdin_ready(): | ||
| os.write(_w, b'x') | ||
| stop.set() | ||
| t = Timer(callback=cb) | ||
| t.start() | ||
| NSApp = _NSApp() | ||
| _stop_on_read(_r) | ||
| 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() | ||
| t.join() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you change this import for:
from _pydev_imps._pydev_saved_modules.threading import Thread, Event?The main issue is that gevent monkey-patches threading and that import will make sure that the original (non monkey-patched version) of the module is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please see the commit 7f755ca [1].
[1] 7f755ca