mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
commit
dc58df149f
6 changed files with 289 additions and 17 deletions
|
|
@ -26,6 +26,7 @@ GUI_WX = 'wx'
|
|||
GUI_QT = 'qt'
|
||||
GUI_QT4 = 'qt4'
|
||||
GUI_QT5 = 'qt5'
|
||||
GUI_QT6 = 'qt6'
|
||||
GUI_GTK = 'gtk'
|
||||
GUI_TK = 'tk'
|
||||
GUI_OSX = 'osx'
|
||||
|
|
@ -173,8 +174,10 @@ class InputHookManager(object):
|
|||
self.clear_inputhook()
|
||||
|
||||
def enable_qt(self, app=None):
|
||||
from pydev_ipython.qt_for_kernel import QT_API, QT_API_PYQT5
|
||||
if QT_API == QT_API_PYQT5:
|
||||
from pydev_ipython.qt_for_kernel import QT_API, QT_API_PYQT5, QT_API_PYQT6
|
||||
if QT_API == QT_API_PYQT6:
|
||||
self.enable_qt6(app)
|
||||
elif QT_API == QT_API_PYQT5:
|
||||
self.enable_qt5(app)
|
||||
else:
|
||||
self.enable_qt4(app)
|
||||
|
|
@ -234,6 +237,21 @@ class InputHookManager(object):
|
|||
self._apps[GUI_QT5]._in_event_loop = False
|
||||
self.clear_inputhook()
|
||||
|
||||
def enable_qt6(self, app=None):
|
||||
from pydev_ipython.inputhookqt6 import create_inputhook_qt6
|
||||
app, inputhook_qt6 = create_inputhook_qt6(self, app)
|
||||
self.set_inputhook(inputhook_qt6)
|
||||
|
||||
self._current_gui = GUI_QT6
|
||||
app._in_event_loop = True
|
||||
self._apps[GUI_QT6] = app
|
||||
return app
|
||||
|
||||
def disable_qt6(self):
|
||||
if GUI_QT6 in self._apps:
|
||||
self._apps[GUI_QT6]._in_event_loop = False
|
||||
self.clear_inputhook()
|
||||
|
||||
def enable_gtk(self, app=None):
|
||||
"""Enable event loop integration with PyGTK.
|
||||
|
||||
|
|
|
|||
199
src/debugpy/_vendored/pydevd/pydev_ipython/inputhookqt6.py
Normal file
199
src/debugpy/_vendored/pydevd/pydev_ipython/inputhookqt6.py
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Qt6's inputhook support function
|
||||
|
||||
Author: Christian Boos, Marijn van Vliet
|
||||
"""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2011 The IPython Development Team
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import signal
|
||||
|
||||
import threading
|
||||
|
||||
from pydev_ipython.qt_for_kernel import QtCore, QtGui
|
||||
from pydev_ipython.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready
|
||||
|
||||
|
||||
# To minimise future merging complexity, rather than edit the entire code base below
|
||||
# we fake InteractiveShell here
|
||||
class InteractiveShell:
|
||||
_instance = None
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
|
||||
def set_hook(self, *args, **kwargs):
|
||||
# We don't consider the pre_prompt_hook because we don't have
|
||||
# KeyboardInterrupts to consider since we are running under PyDev
|
||||
pass
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Module Globals
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
got_kbdint = False
|
||||
sigint_timer = None
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Code
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def create_inputhook_qt6(mgr, app=None):
|
||||
"""Create an input hook for running the Qt6 application event loop.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mgr : an InputHookManager
|
||||
|
||||
app : Qt Application, optional.
|
||||
Running application to use. If not given, we probe Qt for an
|
||||
existing application object, and create a new one if none is found.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A pair consisting of a Qt Application (either the one given or the
|
||||
one found or created) and a inputhook.
|
||||
|
||||
Notes
|
||||
-----
|
||||
We use a custom input hook instead of PyQt6's default one, as it
|
||||
interacts better with the readline packages (issue #481).
|
||||
|
||||
The inputhook function works in tandem with a 'pre_prompt_hook'
|
||||
which automatically restores the hook as an inputhook in case the
|
||||
latter has been temporarily disabled after having intercepted a
|
||||
KeyboardInterrupt.
|
||||
"""
|
||||
|
||||
if app is None:
|
||||
app = QtCore.QCoreApplication.instance()
|
||||
if app is None:
|
||||
from PyQt6 import QtWidgets
|
||||
app = QtWidgets.QApplication([" "])
|
||||
|
||||
# Re-use previously created inputhook if any
|
||||
ip = InteractiveShell.instance()
|
||||
if hasattr(ip, '_inputhook_qt6'):
|
||||
return app, ip._inputhook_qt6
|
||||
|
||||
# Otherwise create the inputhook_qt6/preprompthook_qt6 pair of
|
||||
# hooks (they both share the got_kbdint flag)
|
||||
|
||||
def inputhook_qt6():
|
||||
"""PyOS_InputHook python hook for Qt6.
|
||||
|
||||
Process pending Qt events and if there's no pending keyboard
|
||||
input, spend a short slice of time (50ms) running the Qt event
|
||||
loop.
|
||||
|
||||
As a Python ctypes callback can't raise an exception, we catch
|
||||
the KeyboardInterrupt and temporarily deactivate the hook,
|
||||
which will let a *second* CTRL+C be processed normally and go
|
||||
back to a clean prompt line.
|
||||
"""
|
||||
try:
|
||||
allow_CTRL_C()
|
||||
app = QtCore.QCoreApplication.instance()
|
||||
if not app: # shouldn't happen, but safer if it happens anyway...
|
||||
return 0
|
||||
app.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
|
||||
if not stdin_ready():
|
||||
# Generally a program would run QCoreApplication::exec()
|
||||
# from main() to enter and process the Qt event loop until
|
||||
# quit() or exit() is called and the program terminates.
|
||||
#
|
||||
# For our input hook integration, we need to repeatedly
|
||||
# enter and process the Qt event loop for only a short
|
||||
# amount of time (say 50ms) to ensure that Python stays
|
||||
# responsive to other user inputs.
|
||||
#
|
||||
# A naive approach would be to repeatedly call
|
||||
# QCoreApplication::exec(), using a timer to quit after a
|
||||
# short amount of time. Unfortunately, QCoreApplication
|
||||
# emits an aboutToQuit signal before stopping, which has
|
||||
# the undesirable effect of closing all modal windows.
|
||||
#
|
||||
# To work around this problem, we instead create a
|
||||
# QEventLoop and call QEventLoop::exec(). Other than
|
||||
# setting some state variables which do not seem to be
|
||||
# used anywhere, the only thing QCoreApplication adds is
|
||||
# the aboutToQuit signal which is precisely what we are
|
||||
# trying to avoid.
|
||||
timer = QtCore.QTimer()
|
||||
event_loop = QtCore.QEventLoop()
|
||||
timer.timeout.connect(event_loop.quit)
|
||||
while not stdin_ready():
|
||||
timer.start(50)
|
||||
event_loop.exec()
|
||||
timer.stop()
|
||||
except KeyboardInterrupt:
|
||||
global got_kbdint, sigint_timer
|
||||
|
||||
ignore_CTRL_C()
|
||||
got_kbdint = True
|
||||
mgr.clear_inputhook()
|
||||
|
||||
# This generates a second SIGINT so the user doesn't have to
|
||||
# press CTRL+C twice to get a clean prompt.
|
||||
#
|
||||
# Since we can't catch the resulting KeyboardInterrupt here
|
||||
# (because this is a ctypes callback), we use a timer to
|
||||
# generate the SIGINT after we leave this callback.
|
||||
#
|
||||
# Unfortunately this doesn't work on Windows (SIGINT kills
|
||||
# Python and CTRL_C_EVENT doesn't work).
|
||||
if(os.name == 'posix'):
|
||||
pid = os.getpid()
|
||||
if(not sigint_timer):
|
||||
sigint_timer = threading.Timer(.01, os.kill,
|
||||
args=[pid, signal.SIGINT])
|
||||
sigint_timer.start()
|
||||
else:
|
||||
print("\nKeyboardInterrupt - Ctrl-C again for new prompt")
|
||||
|
||||
except: # NO exceptions are allowed to escape from a ctypes callback
|
||||
ignore_CTRL_C()
|
||||
from traceback import print_exc
|
||||
print_exc()
|
||||
print("Got exception from inputhook_qt6, unregistering.")
|
||||
mgr.clear_inputhook()
|
||||
finally:
|
||||
allow_CTRL_C()
|
||||
return 0
|
||||
|
||||
def preprompthook_qt6(ishell):
|
||||
"""'pre_prompt_hook' used to restore the Qt6 input hook
|
||||
|
||||
(in case the latter was temporarily deactivated after a
|
||||
CTRL+C)
|
||||
"""
|
||||
global got_kbdint, sigint_timer
|
||||
|
||||
if(sigint_timer):
|
||||
sigint_timer.cancel()
|
||||
sigint_timer = None
|
||||
|
||||
if got_kbdint:
|
||||
mgr.set_inputhook(inputhook_qt6)
|
||||
got_kbdint = False
|
||||
|
||||
ip._inputhook_qt6 = inputhook_qt6
|
||||
ip.set_hook('pre_prompt_hook', preprompthook_qt6)
|
||||
|
||||
return app, inputhook_qt6
|
||||
|
|
@ -8,6 +8,7 @@ backends = {'tk': 'TkAgg',
|
|||
'qt': 'QtAgg', # Auto-choose qt4/5
|
||||
'qt4': 'Qt4Agg',
|
||||
'qt5': 'Qt5Agg',
|
||||
'qt6': 'Qt6Agg',
|
||||
'osx': 'MacOSX'}
|
||||
|
||||
# We also need a reverse backends2guis mapping that will properly choose which
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ Do not use this if you need PyQt with the old QString/QVariant API.
|
|||
|
||||
import os
|
||||
|
||||
from pydev_ipython.qt_loaders import (load_qt, QT_API_PYSIDE,
|
||||
QT_API_PYQT, QT_API_PYQT5)
|
||||
from pydev_ipython.qt_loaders import (load_qt, QT_API_PYSIDE, QT_API_PYSIDE2,
|
||||
QT_API_PYQT, QT_API_PYQT5, QT_API_PYQT6)
|
||||
|
||||
QT_API = os.environ.get('QT_API', None)
|
||||
if QT_API not in [QT_API_PYSIDE, QT_API_PYQT, QT_API_PYQT5, None]:
|
||||
raise RuntimeError("Invalid Qt API %r, valid values are: %r, %r" %
|
||||
(QT_API, QT_API_PYSIDE, QT_API_PYQT, QT_API_PYQT5))
|
||||
if QT_API not in [QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT, QT_API_PYQT5, QT_API_PYQT6, None]:
|
||||
raise RuntimeError("Invalid Qt API %r, valid values are: %r, %r, %r, %r, %r" %
|
||||
(QT_API, QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT, QT_API_PYQT5, QT_API_PYQT6))
|
||||
if QT_API is None:
|
||||
api_opts = [QT_API_PYSIDE, QT_API_PYQT, QT_API_PYQT5]
|
||||
api_opts = [QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT, QT_API_PYQT5, QT_API_PYQT6]
|
||||
else:
|
||||
api_opts = [QT_API]
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import sys
|
|||
from pydev_ipython.version import check_version
|
||||
from pydev_ipython.qt_loaders import (load_qt, QT_API_PYSIDE, QT_API_PYSIDE2,
|
||||
QT_API_PYQT, QT_API_PYQT_DEFAULT,
|
||||
loaded_api, QT_API_PYQT5)
|
||||
loaded_api, QT_API_PYQT5, QT_API_PYQT6)
|
||||
|
||||
|
||||
# Constraints placed on an imported matplotlib
|
||||
|
|
@ -71,10 +71,21 @@ def matplotlib_options(mpl):
|
|||
raise ImportError("unhandled value for backend.qt5 from matplotlib: %r" %
|
||||
mpqt)
|
||||
|
||||
elif backend == 'Qt6Agg':
|
||||
mpqt = mpl.rcParams.get('backend.qt6', None)
|
||||
if mpqt is None:
|
||||
return None
|
||||
if mpqt.lower() == 'pyqt6':
|
||||
return [QT_API_PYQT6]
|
||||
raise ImportError("unhandled value for backend.qt6 from matplotlib: %r" %
|
||||
mpqt)
|
||||
|
||||
# Fallback without checking backend (previous code)
|
||||
mpqt = mpl.rcParams.get('backend.qt4', None)
|
||||
if mpqt is None:
|
||||
mpqt = mpl.rcParams.get('backend.qt5', None)
|
||||
if mpqt is None:
|
||||
mpqt = mpl.rcParams.get('backend.qt6', None)
|
||||
|
||||
if mpqt is None:
|
||||
return None
|
||||
|
|
@ -84,6 +95,8 @@ def matplotlib_options(mpl):
|
|||
return [QT_API_PYQT_DEFAULT]
|
||||
elif mpqt.lower() == 'pyqt5':
|
||||
return [QT_API_PYQT5]
|
||||
elif mpqt.lower() == 'pyqt6':
|
||||
return [QT_API_PYQT6]
|
||||
raise ImportError("unhandled value for qt backend from matplotlib: %r" %
|
||||
mpqt)
|
||||
|
||||
|
|
@ -105,7 +118,7 @@ def get_options():
|
|||
|
||||
if os.environ.get('QT_API', None) is None:
|
||||
# no ETS variable. Ask mpl, then use either
|
||||
return matplotlib_options(mpl) or [QT_API_PYQT_DEFAULT, QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT5]
|
||||
return matplotlib_options(mpl) or [QT_API_PYQT_DEFAULT, QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT5, QT_API_PYQT6]
|
||||
|
||||
# ETS variable present. Will fallback to external.qt
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ QT_API_PYQTv1 = 'pyqtv1'
|
|||
QT_API_PYQT_DEFAULT = 'pyqtdefault' # don't set SIP explicitly
|
||||
QT_API_PYSIDE = 'pyside'
|
||||
QT_API_PYSIDE2 = 'pyside2'
|
||||
QT_API_PYSIDE6 = 'pyside6'
|
||||
QT_API_PYQT5 = 'pyqt5'
|
||||
QT_API_PYQT6 = 'pyqt6'
|
||||
|
||||
|
||||
class ImportDenier(object):
|
||||
|
|
@ -58,9 +60,11 @@ def commit_api(api):
|
|||
if api == QT_API_PYSIDE:
|
||||
ID.forbid('PyQt4')
|
||||
ID.forbid('PyQt5')
|
||||
ID.forbid('PyQt6')
|
||||
else:
|
||||
ID.forbid('PySide')
|
||||
ID.forbid('PySide2')
|
||||
ID.forbid('PySide6')
|
||||
|
||||
|
||||
def loaded_api():
|
||||
|
|
@ -71,7 +75,7 @@ def loaded_api():
|
|||
|
||||
Returns
|
||||
-------
|
||||
None, 'pyside', 'pyside2', 'pyqt', or 'pyqtv1'
|
||||
None, 'pyside', 'pyside2', 'pyside6', 'pyqt5', 'pyqt6', or 'pyqtv1'
|
||||
"""
|
||||
if 'PyQt4.QtCore' in sys.modules:
|
||||
if qtapi_version() == 2:
|
||||
|
|
@ -82,13 +86,17 @@ def loaded_api():
|
|||
return QT_API_PYSIDE
|
||||
elif 'PySide2.QtCore' in sys.modules:
|
||||
return QT_API_PYSIDE2
|
||||
elif 'PySide6.QtCore' in sys.modules:
|
||||
return QT_API_PYSIDE6
|
||||
elif 'PyQt5.QtCore' in sys.modules:
|
||||
return QT_API_PYQT5
|
||||
elif 'PyQt6.QtCore' in sys.modules:
|
||||
return QT_API_PYQT6
|
||||
return None
|
||||
|
||||
|
||||
def has_binding(api):
|
||||
"""Safely check for PyQt4 or PySide, without importing
|
||||
"""Safely check for PyQt or PySide, without importing
|
||||
submodules
|
||||
|
||||
Parameters
|
||||
|
|
@ -105,10 +113,12 @@ def has_binding(api):
|
|||
# check for complete presence before importing
|
||||
module_name = {QT_API_PYSIDE: 'PySide',
|
||||
QT_API_PYSIDE2: 'PySide2',
|
||||
QT_API_PYSIDE6: 'PySide6',
|
||||
QT_API_PYQT: 'PyQt4',
|
||||
QT_API_PYQTv1: 'PyQt4',
|
||||
QT_API_PYQT_DEFAULT: 'PyQt4',
|
||||
QT_API_PYQT5: 'PyQt5',
|
||||
QT_API_PYQT6: 'PyQt6',
|
||||
}
|
||||
module_name = module_name[api]
|
||||
|
||||
|
|
@ -154,7 +164,7 @@ def can_import(api):
|
|||
|
||||
current = loaded_api()
|
||||
if api == QT_API_PYQT_DEFAULT:
|
||||
return current in [QT_API_PYQT, QT_API_PYQTv1, QT_API_PYQT5, None]
|
||||
return current in [QT_API_PYQT, QT_API_PYQTv1, QT_API_PYQT5, QT_API_PYQT6, None]
|
||||
else:
|
||||
return current in [api, None]
|
||||
|
||||
|
|
@ -211,6 +221,21 @@ def import_pyqt5():
|
|||
return QtCore, QtGui, QtSvg, QT_API_PYQT5
|
||||
|
||||
|
||||
def import_pyqt6():
|
||||
"""
|
||||
Import PyQt6
|
||||
|
||||
ImportErrors raised within this function are non-recoverable
|
||||
"""
|
||||
from PyQt6 import QtGui, QtCore, QtSvg
|
||||
|
||||
# Alias PyQt-specific functions for PySide compatibility.
|
||||
QtCore.Signal = QtCore.pyqtSignal
|
||||
QtCore.Slot = QtCore.pyqtSlot
|
||||
|
||||
return QtCore, QtGui, QtSvg, QT_API_PYQT6
|
||||
|
||||
|
||||
def import_pyside():
|
||||
"""
|
||||
Import PySide
|
||||
|
|
@ -228,7 +253,17 @@ def import_pyside2():
|
|||
ImportErrors raised within this function are non-recoverable
|
||||
"""
|
||||
from PySide2 import QtGui, QtCore, QtSvg # @UnresolvedImport
|
||||
return QtCore, QtGui, QtSvg, QT_API_PYSIDE
|
||||
return QtCore, QtGui, QtSvg, QT_API_PYSIDE2
|
||||
|
||||
|
||||
def import_pyside6():
|
||||
"""
|
||||
Import PySide6
|
||||
|
||||
ImportErrors raised within this function are non-recoverable
|
||||
"""
|
||||
from PySide6 import QtGui, QtCore, QtSvg # @UnresolvedImport
|
||||
return QtCore, QtGui, QtSvg, QT_API_PYSIDE6
|
||||
|
||||
|
||||
def load_qt(api_options):
|
||||
|
|
@ -259,19 +294,21 @@ def load_qt(api_options):
|
|||
"""
|
||||
loaders = {QT_API_PYSIDE: import_pyside,
|
||||
QT_API_PYSIDE2: import_pyside2,
|
||||
QT_API_PYSIDE6: import_pyside6,
|
||||
QT_API_PYQT: import_pyqt4,
|
||||
QT_API_PYQTv1: partial(import_pyqt4, version=1),
|
||||
QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None),
|
||||
QT_API_PYQT5: import_pyqt5,
|
||||
QT_API_PYQT6: import_pyqt6,
|
||||
}
|
||||
|
||||
for api in api_options:
|
||||
|
||||
if api not in loaders:
|
||||
raise RuntimeError(
|
||||
"Invalid Qt API %r, valid values are: %r, %r, %r, %r, %r, %r" %
|
||||
(api, QT_API_PYSIDE, QT_API_PYSIDE, QT_API_PYQT,
|
||||
QT_API_PYQTv1, QT_API_PYQT_DEFAULT, QT_API_PYQT5))
|
||||
"Invalid Qt API %r, valid values are: %r, %r, %r, %r, %r, %r, %r" %
|
||||
(api, QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT,
|
||||
QT_API_PYQTv1, QT_API_PYQT_DEFAULT, QT_API_PYQT5, QT_API_PYQT6))
|
||||
|
||||
if not can_import(api):
|
||||
continue
|
||||
|
|
@ -290,12 +327,16 @@ def load_qt(api_options):
|
|||
Currently-imported Qt library: %r
|
||||
PyQt4 installed: %s
|
||||
PyQt5 installed: %s
|
||||
PyQt6 installed: %s
|
||||
PySide >= 1.0.3 installed: %s
|
||||
PySide2 installed: %s
|
||||
PySide6 installed: %s
|
||||
Tried to load: %r
|
||||
""" % (loaded_api(),
|
||||
has_binding(QT_API_PYQT),
|
||||
has_binding(QT_API_PYQT5),
|
||||
has_binding(QT_API_PYQT6),
|
||||
has_binding(QT_API_PYSIDE),
|
||||
has_binding(QT_API_PYSIDE2),
|
||||
has_binding(QT_API_PYSIDE6),
|
||||
api_options))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue