From 02ead513038d788c7487710dcf9df7d0a6ef270f Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 23 Mar 2026 12:13:01 +0000 Subject: [PATCH] Add a status entry showing loaded python extensions & debugger status --- qrenderdoc/Code/CaptureContext.cpp | 10 +++++ qrenderdoc/Code/CaptureContext.h | 2 + qrenderdoc/Code/Interface/Extensions.h | 17 +++++++++ qrenderdoc/Code/pyrenderdoc/PythonContext.cpp | 16 ++++++++ qrenderdoc/Code/pyrenderdoc/PythonContext.h | 3 ++ qrenderdoc/Windows/MainWindow.cpp | 38 +++++++++++++++++++ qrenderdoc/Windows/MainWindow.h | 4 ++ qrenderdoc/Windows/PythonShell.cpp | 2 + 8 files changed, 92 insertions(+) diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 46213b496..0826947c3 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -453,11 +453,21 @@ rdcarray CaptureContext::GetInstalledExtensions() return ret; } +rdcarray CaptureContext::GetLoadedExtensions() +{ + return m_ExtensionObjects.keys(); +} + bool CaptureContext::IsExtensionLoaded(rdcstr name) { return m_ExtensionObjects.contains(name); } +bool CaptureContext::IsPythonDebuggerConnected() +{ + return PythonContext::IsDebuggingEnabled() && PythonContext::IsDebuggerConnected(); +} + rdcstr CaptureContext::LoadExtension(rdcstr name) { QString ret; diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index a1472dfc5..66b1752b3 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -76,8 +76,10 @@ public: // IExtensionManager rdcarray GetInstalledExtensions() override; + rdcarray GetLoadedExtensions() override; bool IsExtensionLoaded(rdcstr name) override; rdcstr LoadExtension(rdcstr name) override; + bool IsPythonDebuggerConnected() override; void RegisterWindowMenu(WindowMenu base, const rdcarray &submenus, ExtensionCallback callback) override; diff --git a/qrenderdoc/Code/Interface/Extensions.h b/qrenderdoc/Code/Interface/Extensions.h index fa813ebcd..6b4387ad1 100644 --- a/qrenderdoc/Code/Interface/Extensions.h +++ b/qrenderdoc/Code/Interface/Extensions.h @@ -1098,6 +1098,13 @@ struct IExtensionManager )"); virtual bool IsExtensionLoaded(rdcstr name) = 0; + DOCUMENT(R"(Retrieve a list of loaded extensions. + +:return: The list of installed extension names. +:rtype: List[str] +)"); + virtual rdcarray GetLoadedExtensions() = 0; + DOCUMENT(R"(Enable an extension by name. If the extension is already enabled, this will reload it. :param str name: The qualified name of the extension, e.g. ``foo.bar`` @@ -1107,6 +1114,16 @@ struct IExtensionManager )"); virtual rdcstr LoadExtension(rdcstr name) = 0; + DOCUMENT(R"(Check if a python debugger is connected. + +.. note:: + If python debugging is not supported or failed to load, this will return ``False``. + +:return: If a python debugger is connected. +:rtype: bool +)"); + virtual bool IsPythonDebuggerConnected() = 0; + ////////////////////////////////////////////////////////////////////////// // UI hook/callback registration diff --git a/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp b/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp index 932d7caed..b71fd9b70 100644 --- a/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp +++ b/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp @@ -2113,6 +2113,22 @@ PyObject *PythonContext::outstream_trace(PyObject *self, PyObject *args, PyObjec return self; } +bool PythonContext::IsDebuggerConnected() +{ + if(!m_DebugPy) + return false; + + PyGILState_STATE gil = PyGILState_Ensure(); + + PyObject *is_connected = PyObject_CallMethod(m_DebugPy, "is_client_connected", NULL); + bool ret = (PyBool_Check(is_connected) && is_connected == Py_True); + Py_XDECREF(is_connected); + + PyGILState_Release(gil); + + return ret; +} + void PythonContext::PrepareDebuggerWait() { if(!m_DebugPy) diff --git a/qrenderdoc/Code/pyrenderdoc/PythonContext.h b/qrenderdoc/Code/pyrenderdoc/PythonContext.h index 27af1f8b5..d1878fd6e 100644 --- a/qrenderdoc/Code/pyrenderdoc/PythonContext.h +++ b/qrenderdoc/Code/pyrenderdoc/PythonContext.h @@ -70,6 +70,9 @@ public: static void PrepareDebugTracing(); + static bool IsDebuggingEnabled() { return m_DebugPy != NULL; } + static bool IsDebuggerConnected(); + static void PrepareDebuggerWait(); static bool WaitForDebugger(); static void LaunchDebugger(QWidget *window, PersistantConfig &config, QString context_location); diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index cb5a37e06..66f612889 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -39,8 +39,10 @@ #include #include "Code/QRDUtils.h" #include "Code/Resources.h" +#include "Code/pyrenderdoc/PythonContext.h" #include "Widgets/Extended/RDLabel.h" #include "Widgets/Extended/RDMenu.h" +#include "Widgets/Extended/RDToolButton.h" #include "Widgets/ReplayOptionsSelector.h" #include "Windows/Dialogs/AboutDialog.h" #include "Windows/Dialogs/CaptureDialog.h" @@ -224,6 +226,26 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai QObject::connect(statusIcon, &RDLabel::doubleClicked, this, &MainWindow::statusDoubleClicked); QObject::connect(statusText, &RDLabel::doubleClicked, this, &MainWindow::statusDoubleClicked); + extensionStatus = new RDToolButton(this); + extensionStatus->setAutoRaise(true); + extensionStatus->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + ui->statusBar->addWidget(extensionStatus); + + extensionStatus->setIcon(Icons::plugin()); + extensionStatus->setVisible(false); + + QObject::connect(extensionStatus, &RDToolButton::clicked, this, + &MainWindow::on_action_Manage_Extensions_triggered); + QObject::connect(PythonContext::GetExtensionContext(), &PythonContext::extensionLoaded, this, + &MainWindow::PythonStatusUpdate); + + QTimer *debuggerTimer = new QTimer(this); + QObject::connect(debuggerTimer, &QTimer::timeout, this, &MainWindow::PythonStatusUpdate); + + debuggerTimer->setSingleShot(false); + debuggerTimer->setInterval(500); + debuggerTimer->start(); + QObject::connect(&m_MessageTick, &QTimer::timeout, this, &MainWindow::messageCheck); m_MessageTick.setSingleShot(false); m_MessageTick.setInterval(175); @@ -1316,6 +1338,22 @@ void MainWindow::ClearRecentCaptureSettings() PopulateRecentCaptureSettings(); } +void MainWindow::PythonStatusUpdate() +{ + int num = m_Ctx.Extensions().GetLoadedExtensions().count(); + + if(num > 0 || PythonContext::IsDebuggerConnected()) + { + // extensions can't be unloaded so we can just unconditionally show this now + extensionStatus->setVisible(true); + + QString text = tr("%n extension(s) active", NULL, num); + if(PythonContext::IsDebuggerConnected()) + text += tr(": Python debugger connected"); + extensionStatus->setText(text); + } +} + void MainWindow::networkRequestFailed(QUrl url, QString error) { if(m_NetworkFailCallbacks.contains(url)) diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index 9c5933d51..e504df214 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -40,6 +40,7 @@ class MainWindow; } class RDLabel; +class RDToolButton; class RDMenu; class LambdaThread; class QMimeData; @@ -203,6 +204,8 @@ private slots: void ClearRecentCaptureFiles(); void ClearRecentCaptureSettings(); + void PythonStatusUpdate(); + void networkRequestFailed(QUrl url, QString error); void networkRequestCompleted(QUrl url, QByteArray data); @@ -248,6 +251,7 @@ private: RDLabel *statusIcon; RDLabel *statusText; + RDToolButton *extensionStatus; QProgressBar *statusProgress; RDMenu *contextChooserMenu; QToolButton *contextChooser; diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index 23b990669..e670528b7 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -375,8 +375,10 @@ struct ExtensionInvoker : ObjectForwarder /////////////////////////////////////////////////////////////////////// // rdcarray GetInstalledExtensions() { return m_Obj.GetInstalledExtensions(); } + rdcarray GetLoadedExtensions() { return m_Obj.GetLoadedExtensions(); } bool IsExtensionLoaded(rdcstr name) { return m_Obj.IsExtensionLoaded(name); } rdcstr LoadExtension(rdcstr name) { return m_Obj.LoadExtension(name); } + bool IsPythonDebuggerConnected() { return m_Obj.IsPythonDebuggerConnected(); } IMiniQtHelper &GetMiniQtHelper() { return *m_MiniQt; } // ///////////////////////////////////////////////////////////////////////