diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py index 8b738c3a..b011f29e 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py @@ -125,6 +125,9 @@ class PyDevdAPI(object): ''' pydevd_file_utils.set_ide_os(ide_os) + def set_gui_event_loop(self, py_db, gui_event_loop): + py_db._gui_event_loop = gui_event_loop + def send_error_message(self, py_db, msg): sys.stderr.write('pydevd: %s\n' % (msg,)) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_json_debug_options.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_json_debug_options.py index c6def933..96ed582b 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_json_debug_options.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_json_debug_options.py @@ -19,6 +19,7 @@ class DebugOptions(object): 'flask_debug', 'stop_on_entry', 'max_exception_stack_frames', + 'gui_event_loop', ] def __init__(self): @@ -30,6 +31,7 @@ class DebugOptions(object): self.flask_debug = False self.stop_on_entry = False self.max_exception_stack_frames = 0 + self.gui_event_loop = 'matplotlib' def to_json(self): dct = {} @@ -92,6 +94,8 @@ class DebugOptions(object): self.max_exception_stack_frames = int_parser(args.get('maxExceptionStackFrames', 0)) + if 'guiEventLoop' in args: + self.gui_event_loop = str(args['guiEventLoop']) def int_parser(s, default_value=0): try: diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py index 18c54ec5..bda519b8 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py @@ -460,6 +460,8 @@ class PyDevJsonCommandProcessor(object): if self._options.stop_on_entry and start_reason == 'launch': self.api.stop_on_entry() + self.api.set_gui_event_loop(py_db, self._options.gui_event_loop) + def _send_process_event(self, py_db, start_method): argv = getattr(sys, 'argv', []) if len(argv) > 0: diff --git a/src/debugpy/_vendored/pydevd/pydevd.py b/src/debugpy/_vendored/pydevd/pydevd.py index daecf096..a44de4d5 100644 --- a/src/debugpy/_vendored/pydevd/pydevd.py +++ b/src/debugpy/_vendored/pydevd/pydevd.py @@ -614,9 +614,19 @@ class PyDB(object): self.thread_analyser = None self.asyncio_analyser = None + # The GUI event loop that's going to run. + # Possible values: + # matplotlib - Whatever GUI backend matplotlib is using. + # 'wx'/'qt'/'none'/... - GUI toolkits that have bulitin support. See pydevd_ipython/inputhook.py:24. + # Other - A custom function that'll be imported and run. + self._gui_event_loop = 'matplotlib' + self._installed_gui_support = False + self.gui_in_use = False + + # GUI event loop support in debugger + self.activate_gui_function = None + # matplotlib support in debugger and debug console - self._installed_mpl_support = False - self.mpl_in_use = False self.mpl_hooks_in_debug_console = False self.mpl_modules_for_patching = {} @@ -1522,14 +1532,15 @@ class PyDB(object): for module in dict_keys(self.mpl_modules_for_patching): import_hook_manager.add_module_name(module, self.mpl_modules_for_patching.pop(module)) - def init_matplotlib_support(self): - if self._installed_mpl_support: + def init_gui_support(self): + if self._installed_gui_support: return - self._installed_mpl_support = True - # prepare debugger for integration with matplotlib GUI event loop + self._installed_gui_support = True + # prepare debugger for integration with GUI event loop from pydev_ipython.matplotlibtools import activate_matplotlib, activate_pylab, activate_pyplot, do_enable_gui + from pydev_ipython.inputhook import enable_gui - # enable_gui_function in activate_matplotlib should be called in main thread. Unlike integrated console, + # enalbe_gui and enable_gui_function in activate_matplotlib should be called in main thread. Unlike integrated console, # in the debug console we have no interpreter instance with exec_queue, but we run this code in the main # thread and can call it directly. class _MatplotlibHelper: @@ -1545,11 +1556,14 @@ class PyDB(object): from pydev_ipython.inputhook import set_return_control_callback set_return_control_callback(return_control) - self.mpl_modules_for_patching = {"matplotlib": lambda: activate_matplotlib(do_enable_gui), - "matplotlib.pyplot": activate_pyplot, - "pylab": activate_pylab } + if self._gui_event_loop == 'matplotlib': + self.mpl_modules_for_patching = {"matplotlib": lambda: activate_matplotlib(do_enable_gui), + "matplotlib.pyplot": activate_pyplot, + "pylab": activate_pylab } + else: + self.activate_gui_function = enable_gui - def _activate_mpl_if_needed(self): + def _activate_gui_if_needed(self): if len(self.mpl_modules_for_patching) > 0: if is_current_thread_main_thread(): # Note that we call only in the main thread. for module in dict_keys(self.mpl_modules_for_patching): @@ -1557,9 +1571,31 @@ class PyDB(object): activate_function = self.mpl_modules_for_patching.pop(module, None) if activate_function is not None: activate_function() - self.mpl_in_use = True + self.gui_in_use = True - def _call_mpl_hook(self): + if self.activate_gui_function: + if is_current_thread_main_thread(): # Only call enable_gui in the main thread. + try: + # First try to activate builtin GUI event loops. + self.activate_gui_function(self._gui_event_loop) + self.activate_gui_function = None + self.gui_in_use = True + except ValueError: + # The user requested a custom GUI event loop, try to import it. + from importlib import import_module + from pydev_ipython.inputhook import set_inputhook + try: + module_name, inputhook_name = self._gui_event_loop.rsplit('.', 1) + module = import_module(module_name) + inputhook_function = getattr(module, inputhook_name) + set_inputhook(inputhook_function) + self.gui_in_use = True + except Exception as e: + pydev_log.debug("Cannot activate custom GUI event loop {}: {}".format(self._gui_event_loop, e)) + finally: + self.activate_gui_function = None + + def _call_input_hook(self): try: from pydev_ipython.inputhook import get_inputhook inputhook = get_inputhook() @@ -1704,7 +1740,7 @@ class PyDB(object): # add import hooks for matplotlib patches if only debug console was started try: self.init_matplotlib_in_debug_console() - self.mpl_in_use = True + self.gui_in_use = True except: pydev_log.debug("Matplotlib support in debug console failed", traceback.format_exc()) self.mpl_hooks_in_debug_console = True @@ -2006,12 +2042,13 @@ class PyDB(object): keep_suspended = False with self._main_lock: # Use lock to check if suspended state changed - activate_matplotlib = info.pydev_state == STATE_SUSPEND and not self.pydb_disposed + activate_gui = info.pydev_state == STATE_SUSPEND and not self.pydb_disposed in_main_thread = is_current_thread_main_thread() - if activate_matplotlib and in_main_thread: + if activate_gui and in_main_thread: # before every stop check if matplotlib modules were imported inside script code - self._activate_mpl_if_needed() + # or some GUI event loop needs to be activated + self._activate_gui_if_needed() while True: with self._main_lock: # Use lock to check if suspended state changed @@ -2019,9 +2056,9 @@ class PyDB(object): # Note: we can't exit here if terminate was requested while a breakpoint was hit. break - if in_main_thread and self.mpl_in_use: - # call input hooks if only matplotlib is in use - self._call_mpl_hook() + if in_main_thread and self.gui_in_use: + # call input hooks if only GUI is in use + self._call_input_hook() self.process_internal_commands() time.sleep(0.01) @@ -2396,7 +2433,7 @@ class PyDB(object): try: if INTERACTIVE_MODE_AVAILABLE: - self.init_matplotlib_support() + self.init_gui_support() except: pydev_log.exception("Matplotlib support in debugger failed") @@ -2441,7 +2478,7 @@ class PyDB(object): return globals def wait_for_commands(self, globals): - self._activate_mpl_if_needed() + self._activate_gui_if_needed() thread = threading.current_thread() from _pydevd_bundle import pydevd_frame_utils @@ -2455,9 +2492,9 @@ class PyDB(object): self.writer.add_command(cmd) while True: - if self.mpl_in_use: - # call input hooks if only matplotlib is in use - self._call_mpl_hook() + if self.gui_in_use: + # call input hooks if only GUI is in use + self._call_input_hook() self.process_internal_commands() time.sleep(0.01) @@ -2849,7 +2886,7 @@ def _locked_settrace( try: if INTERACTIVE_MODE_AVAILABLE: - py_db.init_matplotlib_support() + py_db.init_gui_support() except: pydev_log.exception("Matplotlib support in debugger failed") diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py index cffada86..bae420cc 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py @@ -5210,6 +5210,7 @@ def test_debug_options(case_setup, val): flask=val, stopOnEntry=val, maxExceptionStackFrames=4 if val else 5, + guiEventLoop='qt5' if val else 'matplotlib', ) json_facade.write_launch(**args) @@ -5232,6 +5233,7 @@ def test_debug_options(case_setup, val): 'breakOnSystemExitZero': 'break_system_exit_zero', 'stopOnEntry': 'stop_on_entry', 'maxExceptionStackFrames': 'max_exception_stack_frames', + 'guiEventLoop': 'gui_event_loop', } assert json.loads(output.body.output) == dict((translation[key], val) for key, val in args.items())