1from __future__ import nested_scopes 2import os 3 4def set_trace_in_qt(): 5 import pydevd_tracing 6 from _pydevd_bundle.pydevd_comm import get_global_debugger 7 debugger = get_global_debugger() 8 if debugger is not None: 9 pydevd_tracing.SetTrace(debugger.trace_dispatch, debugger.frame_eval_func) 10 11 12_patched_qt = False 13def patch_qt(qt_support_mode): 14 ''' 15 This method patches qt (PySide, PyQt4, PyQt5) so that we have hooks to set the tracing for QThread. 16 ''' 17 if not qt_support_mode: 18 return 19 20 if qt_support_mode is True or qt_support_mode == 'True': 21 # do not break backward compatibility 22 qt_support_mode = 'auto' 23 24 if qt_support_mode == 'auto': 25 qt_support_mode = os.getenv('PYDEVD_PYQT_MODE', 'auto') 26 27 # Avoid patching more than once 28 global _patched_qt 29 if _patched_qt: 30 return 31 32 _patched_qt = True 33 34 if qt_support_mode == 'auto': 35 36 patch_qt_on_import = None 37 try: 38 import PySide # @UnresolvedImport @UnusedImport 39 qt_support_mode = 'pyside' 40 except: 41 try: 42 import PyQt5 # @UnresolvedImport @UnusedImport 43 qt_support_mode = 'pyqt5' 44 except: 45 try: 46 import PyQt4 # @UnresolvedImport @UnusedImport 47 qt_support_mode = 'pyqt4' 48 except: 49 return 50 51 52 if qt_support_mode == 'pyside': 53 import PySide.QtCore # @UnresolvedImport 54 _internal_patch_qt(PySide.QtCore, qt_support_mode) 55 56 elif qt_support_mode == 'pyqt5': 57 import PyQt5.QtCore # @UnresolvedImport 58 _internal_patch_qt(PyQt5.QtCore) 59 60 elif qt_support_mode == 'pyqt4': 61 # Ok, we have an issue here: 62 # PyDev-452: Selecting PyQT API version using sip.setapi fails in debug mode 63 # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html 64 # Mostly, if the user uses a different API version (i.e.: v2 instead of v1), 65 # that has to be done before importing PyQt4 modules (PySide/PyQt5 don't have this issue 66 # as they only implements v2). 67 patch_qt_on_import = 'PyQt4' 68 def get_qt_core_module(): 69 import PyQt4.QtCore # @UnresolvedImport 70 return PyQt4.QtCore 71 _patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module) 72 73 else: 74 raise ValueError('Unexpected qt support mode: %s' % (qt_support_mode,)) 75 76 77def _patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module): 78 # I don't like this approach very much as we have to patch __import__, but I like even less 79 # asking the user to configure something in the client side... 80 # So, our approach is to patch PyQt4 right before the user tries to import it (at which 81 # point he should've set the sip api version properly already anyways). 82 83 dotted = patch_qt_on_import + '.' 84 original_import = __import__ 85 86 from _pydev_imps._pydev_sys_patch import patch_sys_module, patch_reload, cancel_patches_in_sys_module 87 88 patch_sys_module() 89 patch_reload() 90 91 def patched_import(name, *args, **kwargs): 92 if patch_qt_on_import == name or name.startswith(dotted): 93 builtins.__import__ = original_import 94 cancel_patches_in_sys_module() 95 _internal_patch_qt(get_qt_core_module()) # Patch it only when the user would import the qt module 96 return original_import(name, *args, **kwargs) 97 98 try: 99 import builtins 100 except ImportError: 101 import __builtin__ as builtins 102 builtins.__import__ = patched_import 103 104 105def _internal_patch_qt(QtCore, qt_support_mode='auto'): 106 _original_thread_init = QtCore.QThread.__init__ 107 _original_runnable_init = QtCore.QRunnable.__init__ 108 _original_QThread = QtCore.QThread 109 110 class FuncWrapper: 111 def __init__(self, original): 112 self._original = original 113 114 def __call__(self, *args, **kwargs): 115 set_trace_in_qt() 116 return self._original(*args, **kwargs) 117 118 class StartedSignalWrapper(QtCore.QObject): # Wrapper for the QThread.started signal 119 120 try: 121 _signal = QtCore.Signal() # @UndefinedVariable 122 except: 123 _signal = QtCore.pyqtSignal() # @UndefinedVariable 124 125 def __init__(self, thread, original_started): 126 QtCore.QObject.__init__(self) 127 self.thread = thread 128 self.original_started = original_started 129 if qt_support_mode == 'pyside': 130 self._signal = original_started 131 else: 132 self._signal.connect(self._on_call) 133 self.original_started.connect(self._signal) 134 135 def connect(self, func, *args, **kwargs): 136 if qt_support_mode == 'pyside': 137 return self._signal.connect(FuncWrapper(func), *args, **kwargs) 138 else: 139 return self._signal.connect(func, *args, **kwargs) 140 141 def disconnect(self, *args, **kwargs): 142 return self._signal.disconnect(*args, **kwargs) 143 144 def emit(self, *args, **kwargs): 145 return self._signal.emit(*args, **kwargs) 146 147 def _on_call(self, *args, **kwargs): 148 set_trace_in_qt() 149 150 class ThreadWrapper(QtCore.QThread): # Wrapper for QThread 151 152 def __init__(self, *args, **kwargs): 153 _original_thread_init(self, *args, **kwargs) 154 155 # In PyQt5 the program hangs when we try to call original run method of QThread class. 156 # So we need to distinguish instances of QThread class and instances of QThread inheritors. 157 if self.__class__.run == _original_QThread.run: 158 self.run = self._exec_run 159 else: 160 self._original_run = self.run 161 self.run = self._new_run 162 self._original_started = self.started 163 self.started = StartedSignalWrapper(self, self.started) 164 165 def _exec_run(self): 166 set_trace_in_qt() 167 self.exec_() 168 return None 169 170 def _new_run(self): 171 set_trace_in_qt() 172 return self._original_run() 173 174 class RunnableWrapper(QtCore.QRunnable): # Wrapper for QRunnable 175 176 def __init__(self, *args, **kwargs): 177 _original_runnable_init(self, *args, **kwargs) 178 179 self._original_run = self.run 180 self.run = self._new_run 181 182 183 def _new_run(self): 184 set_trace_in_qt() 185 return self._original_run() 186 187 QtCore.QThread = ThreadWrapper 188 QtCore.QRunnable = RunnableWrapper 189