Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions pydev_ipython/inputhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,24 +423,12 @@ def disable_gtk3(self):
def enable_mac(self, app=None):
""" Enable event loop integration with MacOSX.

We call function pyplot.pause, which updates and displays active
figure during pause. It's not MacOSX-specific, but it enables to
avoid inputhooks in native MacOSX backend.
Also we shouldn't import pyplot, until user does it. Cause it's
possible to choose backend before importing pyplot for the first
time only.
Uses native inputhooks for MacOSX that significantly improve
performance and responsiveness.

"""
def inputhook_mac(app=None):
if self.pyplot_imported:
pyplot = sys.modules['matplotlib.pyplot']
try:
pyplot.pause(0.01)
except:
pass
else:
if 'matplotlib.pyplot' in sys.modules:
self.pyplot_imported = True

from pydev_ipython.inputhookmac import inputhook_mac
self.set_inputhook(inputhook_mac)
self._current_gui = GUI_OSX

Expand Down
167 changes: 167 additions & 0 deletions pydev_ipython/inputhookmac.py
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
Copy link
Owner

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.

Copy link
Author

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


# 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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this import top-level? The reason for this is that Python 2 has an import lock, so, if a thread is stopped in a breakpoint inside of some import no other import will run (so, in the debugger imports must be top-level).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed anymore.

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()