From db75eec62027361ff1a0734c2adf1312d891ae67 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 28 May 2026 17:08:54 +0100 Subject: [PATCH] Expose capture connection windows to python scripting --- docs/python_api/qrenderdoc/windows.rst | 6 + qrenderdoc/Code/CaptureContext.cpp | 4 +- qrenderdoc/Code/Interface/QRDInterface.h | 275 ++++++++++++++++++- qrenderdoc/Code/pyrenderdoc/qrenderdoc.i | 1 + qrenderdoc/Windows/Dialogs/CaptureDialog.cpp | 29 +- qrenderdoc/Windows/Dialogs/CaptureDialog.h | 7 +- qrenderdoc/Windows/Dialogs/LiveCapture.cpp | 221 ++++++++++++--- qrenderdoc/Windows/Dialogs/LiveCapture.h | 40 ++- qrenderdoc/Windows/MainWindow.cpp | 111 ++++---- qrenderdoc/Windows/MainWindow.h | 4 +- 10 files changed, 581 insertions(+), 117 deletions(-) diff --git a/docs/python_api/qrenderdoc/windows.rst b/docs/python_api/qrenderdoc/windows.rst index b0ff66cc5..41857ba32 100644 --- a/docs/python_api/qrenderdoc/windows.rst +++ b/docs/python_api/qrenderdoc/windows.rst @@ -76,6 +76,12 @@ Capture Dialog .. autoclass:: qrenderdoc.CaptureDialog :members: +.. autoclass:: qrenderdoc.CaptureConnection + :members: + +.. autoclass:: qrenderdoc.ConnectedTempCapture + :members: + Debug Messages -------------- diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 2ac84f66b..5c34e0571 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -2348,11 +2348,11 @@ ICaptureDialog *CaptureContext::GetCaptureDialog() *this, [this](const QString &exe, const QString &workingDir, const QString &cmdLine, const rdcarray &env, CaptureOptions opts, - std::function callback) { + std::function callback) { return m_MainWindow->OnCaptureTrigger(exe, workingDir, cmdLine, env, opts, callback); }, [this](uint32_t PID, const rdcarray &env, const QString &name, - CaptureOptions opts, std::function callback) { + CaptureOptions opts, std::function callback) { return m_MainWindow->OnInjectTrigger(PID, env, name, opts, callback); }, m_MainWindow, m_MainWindow); diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 955a63138..6c50a55af 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -151,6 +151,64 @@ struct CaptureSettings DECLARE_REFLECTION_STRUCT(CaptureSettings); +DOCUMENT(R"(The details of a capture that has been made on a connection but may not +have been saved to disk or local. +)"); +struct ConnectedTempCapture +{ + DOCUMENT(""); + ConnectedTempCapture() = default; + ConnectedTempCapture(const ConnectedTempCapture &) = default; + ConnectedTempCapture &operator=(const ConnectedTempCapture &) = default; + + bool operator==(const ConnectedTempCapture &o) const + { + return captureID == o.captureID && frameNumber == o.frameNumber && timestamp == o.timestamp && + api == o.api; + } + bool operator!=(const ConnectedTempCapture &o) const { return !(*this == o); } + bool operator<(const ConnectedTempCapture &o) const + { + if(!(captureID == o.captureID)) + return captureID < o.captureID; + if(!(frameNumber == o.frameNumber)) + return frameNumber < o.frameNumber; + if(!(timestamp == o.timestamp)) + return timestamp < o.timestamp; + if(!(api == o.api)) + return api < o.api; + return false; + } + + DOCUMENT(R"(The ID of the capture, which is arbitrary. IDs are unique for the capture within +a given connection, but two connections may have the same ID. + +:type: int +)"); + uint32_t captureID; + + DOCUMENT(R"(The name of the API used for this capture. + +:type: str +)"); + rdcstr api; + + DOCUMENT(R"(The timestamp when the capture completed. + +:type: datetime +)"); + rdcdatetime timestamp; + + DOCUMENT(R"(The frame number which was captured. May be ``-1`` if the capture was made via the RenderDoc +API. + +:type: int +)"); + int32_t frameNumber; +}; + +DECLARE_REFLECTION_STRUCT(ConnectedTempCapture); + DOCUMENT(R"(The main parent window of the application. This window is retrieved by calling :meth:`CaptureContext.GetMainWindow`. @@ -959,6 +1017,215 @@ protected: DECLARE_REFLECTION_STRUCT(IResourceInspector); +DOCUMENT(R"(A capture connection window. + +When a program is successfully launched by :class:`CaptureDialog` and a connection is made, +this window can be used to access the details of that connection and any captures that have +been made. + +.. function:: ClosedCallback(connection) + + Not a member function - the signature for any ``ClosedCallback`` callbacks. + + Called whenever a capture connection is closed. + + :param CaptureConnection connection: The connection window. Invalid to use after the callback + has returned. +)"); +struct ICaptureConnection +{ + typedef std::function ClosedCallback; + + DOCUMENT(R"(Retrieves the PySide2 QWidget for this :class:`CaptureConnection` if PySide2 is available, or otherwise +returns a unique opaque pointer that can be passed back to any RenderDoc functions expecting a +QWidget. + +:return: Return the widget handle, either a PySide2 handle or an opaque handle. +:rtype: QWidget +)"); + virtual QWidget *Widget() = 0; + + DOCUMENT(R"(Register a callback to be called when the connection window is closed for any reason. + +Because capture connections can self-close when a program exits with no captures having been made, it can +be useful to get a callback to notify you that the connection is no longer legal to use. + +This callback happens on the UI thread. + +:param Callable[[CaptureConnection], None] method: The function to callback when closed. + Callback function signature must match :func:`ClosedCallback`. +)"); + virtual void RegisterClosedCallback(ClosedCallback method) = 0; + + DOCUMENT(R"(Checks whether or not the connection is still active. If a program exits the connection +will no longer be active. + +Note that there is an inherent race here - the connection could drop immediately after returning. + +:return: ``True`` if the window is set up for injecting. +:rtype: bool +)"); + virtual bool IsConnected() = 0; + + DOCUMENT(R"(Normally connection windows will self-close if the program exits and there are no captures +made. This behaviour can be disabled by calling this function at which point the window will not close +itself automatically. +)"); + virtual void PreventAutoClose() = 0; + + DOCUMENT(R"(Lists the names of APIs that have been used in the program. These APIs may not all +be actively in use, as APIs are not removed from this list once they have been observed. + +Note that some APIs may be listed here that RenderDoc does not support, but does recognise. You +should not assume that these names will match those in the :class:`~renderdoc.GraphicsAPI` enum. + +:return: The set of API names that have been used in the program. +:rtype: List[str] +)"); + virtual rdcarray GetAPIs() = 0; + + DOCUMENT(R"(Asks the connected program to take captures beginning at a certain frame +number. + +If the frame number has already passed when the request is recevied, no capture is made. + +:param int frameNumber: The first frame number to capture. +:param int numFrames: How many frames to capture including the first. If set to 0, nothing + is captured. +)"); + virtual void QueueCapture(int32_t frameNumber, int32_t numFrames) = 0; + + DOCUMENT(R"(Asks the connected program to take a capture after a certain number +of seconds have passed, possibly 0 seconds to immediately capture. This has no effect +if the connection is inactive and does not guarantee that the program will still be +connected in the future - if the connection is lost no capture is made. + +While a delayed capture is waiting to be triggered, requests to trigger another delayed +capture will be ignored. Only one can be active at once. If you need more complex or +overlapping delayed captures you should do this yourself manually and call this function +when each one is due. + +:param float secondsDelay: How many seconds to wait before triggering a capture. +:param int numFrames: How many frames to capture including the first. If set to 0, nothing + is captured. +)"); + virtual void TimedCapture(float secondsDelay, int32_t numFrames) = 0; + + DOCUMENT(R"(Cycles which window is active on the connected device. Has no effect if there are +not multiple windows or if the connection is inactive. +)"); + virtual void CycleActiveWindow() = 0; + + DOCUMENT(R"(Gets the name of the program connected to. Usually the name of the +executable for the process. + +:return: The connection target. +:rtype: str +)"); + virtual rdcstr Target() = 0; + + DOCUMENT(R"(Gets the raw hostname for the connected target. This can be used elsewhere +as a true hostname. For displaying to the user, prefer :meth:`FriendlyHostname`. + +:return: The raw hostname of the target this connection is to. +:rtype: str +)"); + virtual rdcstr Hostname() = 0; + + DOCUMENT(R"(Gets the user friendly hostname for the connected target. This may not be +a true hostname but is suitable for displaying to users particularly for platforms where +the raw hostname is not necessarily user-friendly and there may be a more descriptive name +available. + +:return: The friendly hostname of the target this connection is to. +:rtype: str +)"); + virtual rdcstr FriendlyHostname() = 0; + + DOCUMENT(R"(Closes the connection to the target program. Normally there will be +a prompt to the user to ask them whether they would like to save any unsaved +captures to prevent data loss. If they choose to cancel, the connection may not +be closed. + +This prompt can be overridden with the parameter, at which point all unsaved captures +will be deleted. You should ensure the user understands that this will happen. + +:param bool discardUnsaved: Whether to discard unsaved captures without prompting. +)"); + virtual void Close(bool discardUnsaved) = 0; + + DOCUMENT(R"(Lists the captures that are known to have been made on this connection. Note +that this reflects the UI and so the user is free to delete captures at any point, which +will be reflected by them being removed from this list. + +Each :class:`capture ` has an ID that is used to refer to it, note +that this ID is not globally unique and is only unique within this connection. + +:return: The currently known captures on this connection. +:rtype: List[ConnectedTempCapture] +)"); + virtual rdcarray GetCaptures() = 0; + + DOCUMENT(R"(Open the given capture, referred to by ID, in the UI for analysis. If the +capture is remote, an appropriate remote host connection is required. + +This will prompt the user for closing any currently open capture and if the user +chooses not to close the current capture the loading will be stopped. + +:param int ID: The ID of the capture to open. +)"); + virtual void OpenCapture(uint32_t ID) = 0; + + DOCUMENT(R"(Delete the given capture, referred to by ID. If the +capture is remote, an appropriate remote host connection is required. + +Normally deleting a capture will prompt the user to prevent data loss, but this can be +overridden with the argument. + +:param int ID: The ID of the capture to delete. +:param bool promptForSave: ``True`` if the user should be prompted as normal to save the + capture. +)"); + virtual void DeleteCapture(uint32_t ID, bool promptForSave) = 0; + + DOCUMENT(R"(Save the given capture to disk, referred to by ID. If the +capture is remote, an appropriate remote host connection is required. + +If the filename is omitted the user will be prompted to choose a file and the capture +will only be saved if they select a filename. + +:param int ID: The ID of the capture to delete. +:param str filename="": **Optional parameter**. The filename to save to on the local disk, + if omitted the user will be prompted to choose a filename. +)"); + virtual void SaveCapture(uint32_t ID, rdcstr filename = "") = 0; + + DOCUMENT(R"(Lists the PIDs of child processes that are known to still be running. + +:return: The PIDs of each child process running under the connected program. +:rtype: List[int] +)"); + virtual rdcarray GetChildProcesses() = 0; + + DOCUMENT(R"(Connected to a given child process and return a connection window. + +The connection window will automatically be shown if the connection is successfully made. + +If the PID is unrecognised, no connection will be made. + +:param int pid: The PID of the child to connect to. +:return: The connection window if successful, or ``None`` if no connection was made. +:rtype: CaptureConnection +)"); + virtual ICaptureConnection *ConnectToChild(uint32_t pid) = 0; + +protected: + ICaptureConnection() = default; + ~ICaptureConnection() = default; +}; + +DECLARE_REFLECTION_STRUCT(ICaptureConnection); + DOCUMENT(R"(The executable capture window. This window is retrieved by calling :meth:`CaptureContext.GetCaptureDialog`. @@ -1023,8 +1290,12 @@ QWidget. )"); virtual CaptureSettings Settings() = 0; - DOCUMENT("Launches a capture of the current executable."); - virtual void Launch() = 0; + DOCUMENT(R"(Launches a capture of the current executable. + +:return: The connection window if successful, or ``None`` if no connection was made. +:rtype: CaptureConnection +)"); + virtual ICaptureConnection *Launch() = 0; DOCUMENT(R"(Loads settings from a file and applies them. See :meth:`SetSettings`. diff --git a/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i b/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i index e27494414..dc8765ed4 100644 --- a/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i @@ -181,6 +181,7 @@ TEMPLATE_ARRAY_INSTANTIATE(rdcarray, BugReport) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ExtensionMetadata) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, DialogButton) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, RemoteHost) +TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ConnectedTempCapture) TEMPLATE_ARRAY_INSTANTIATE_PTR(rdcarray, ICaptureViewer) // unignore the function from above diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp index 550d6cc5a..337a52bc6 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp @@ -35,7 +35,6 @@ #include "Windows/MainWindow.h" #include "flowlayout/FlowLayout.h" #include "toolwindowmanager/ToolWindowManager.h" -#include "LiveCapture.h" #include "ui_CaptureDialog.h" #define JSON_ID "rdocCaptureSettings" @@ -1168,8 +1167,9 @@ void CaptureDialog::SetEnvironmentModifications(const rdcarrayenvVar->setText(envModText); } -void CaptureDialog::Launch() +ICaptureConnection *CaptureDialog::Launch() { + ICaptureConnection *ret = NULL; if(IsInjectMode()) { QModelIndexList sel = ui->processList->selectionModel()->selectedRows(); @@ -1184,11 +1184,13 @@ void CaptureDialog::Launch() QString name = m_ProcessModel->data(m_ProcessModel->index(item.row(), 0)).toString(); uint32_t PID = m_ProcessModel->data(m_ProcessModel->index(item.row(), 1)).toUInt(); - m_InjectCallback( - PID, Settings().environment, name, Settings().options, [this](LiveCapture *live) { - if(ui->queueFrameCap->isChecked()) - live->QueueCapture((int)ui->queuedFrame->value(), (int)ui->numFrames->value()); - }); + m_InjectCallback(PID, Settings().environment, name, Settings().options, + [this, &ret](ICaptureConnection *live) { + if(ui->queueFrameCap->isChecked()) + live->QueueCapture((int)ui->queuedFrame->value(), + (int)ui->numFrames->value()); + ret = live; + }); } else { @@ -1205,7 +1207,7 @@ void CaptureDialog::Launch() RDDialog::critical(this, tr("No executable selected"), tr("No program selected to launch, click browse next to 'Executable Path' " "above to select the program to launch.")); - return; + return NULL; } // for non-remote captures, check the executable locally @@ -1217,7 +1219,7 @@ void CaptureDialog::Launch() this, tr("Invalid executable"), tr("Invalid executable: %1\nCan't locate this path or a matching executable in PATH") .arg(exe)); - return; + return NULL; } } @@ -1231,7 +1233,7 @@ void CaptureDialog::Launch() RDDialog::critical( this, tr("Invalid working directory"), tr("Invalid working directory: %1\nThis path does not exist").arg(workingDir)); - return; + return NULL; } } @@ -1253,15 +1255,18 @@ void CaptureDialog::Launch() "The intent arguments must include the full parameters e.g. " "--es args \"my arguments\"") .arg(cmdLine)); - return; + return NULL; } } m_CaptureCallback(exe, workingDir, cmdLine, Settings().environment, Settings().options, - [this](LiveCapture *live) { + [this, &ret](ICaptureConnection *live) { if(ui->queueFrameCap->isChecked()) live->QueueCapture((int)ui->queuedFrame->value(), (int)ui->numFrames->value()); + ret = live; }); } + + return ret; } diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.h b/qrenderdoc/Windows/Dialogs/CaptureDialog.h index afffce971..1038f66fb 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.h +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.h @@ -34,7 +34,6 @@ class CaptureDialog; } class QStandardItemModel; -class LiveCapture; class MainWindow; class RDLabel; @@ -45,10 +44,10 @@ class CaptureDialog : public QFrame, public ICaptureDialog public: typedef std::function &env, CaptureOptions opts, - std::function callback)> + std::function callback)> OnCaptureMethod; typedef std::function &env, const QString &name, - CaptureOptions opts, std::function callback)> + CaptureOptions opts, std::function callback)> OnInjectMethod; explicit CaptureDialog(ICaptureContext &ctx, OnCaptureMethod captureCallback, @@ -73,7 +72,7 @@ public: void SetSettings(CaptureSettings settings) override; CaptureSettings Settings() override; - void Launch() override; + ICaptureConnection *Launch() override; void LoadSettings(const rdcstr &filename) override; void SaveSettings(const rdcstr &filename) override; diff --git a/qrenderdoc/Windows/Dialogs/LiveCapture.cpp b/qrenderdoc/Windows/Dialogs/LiveCapture.cpp index d33a36044..ad750cec9 100644 --- a/qrenderdoc/Windows/Dialogs/LiveCapture.cpp +++ b/qrenderdoc/Windows/Dialogs/LiveCapture.cpp @@ -182,6 +182,9 @@ LiveCapture::LiveCapture(ICaptureContext &ctx, const QString &hostname, const QS LiveCapture::~LiveCapture() { + for(ClosedCallback cb : m_CloseCallbacks) + cb(this); + m_Main->LiveCaptureClosed(this); cleanItems(); @@ -190,6 +193,19 @@ LiveCapture::~LiveCapture() delete ui; } +bool LiveCapture::IsConnected() +{ + return m_Connected.available(); +} + +rdcarray LiveCapture::GetAPIs() +{ + rdcarray ret; + for(QString api : m_APIs.keys()) + ret.push_back(api); + return ret; +} + void LiveCapture::QueueCapture(int frameNumber, int numFrames) { m_QueueCaptureFrameNum = frameNumber; @@ -197,6 +213,112 @@ void LiveCapture::QueueCapture(int frameNumber, int numFrames) m_QueueCapture.release(); } +void LiveCapture::TimedCapture(float secondsDelay, int numFrames) +{ + ui->captureDelay->setValue(qMax(0.0f, secondsDelay)); + ui->numFrames->setValue(qMax(0.0f, float(numFrames))); + + on_triggerDelayedCapture_clicked(); +} + +void LiveCapture::CycleActiveWindow() +{ + on_cycleActiveWindow_clicked(); +} + +void LiveCapture::Close(bool discardUnsaved) +{ + if(!discardUnsaved) + { + if(!checkAllowClose()) + return; + } + + cleanItems(); +} + +rdcarray LiveCapture::GetCaptures() +{ + rdcarray ret; + ret.reserve(m_Captures.size()); + for(Capture *src : m_Captures) + { + ConnectedTempCapture dst; + dst.captureID = src->remoteID; + dst.api = src->api; + dst.timestamp = src->timestamp; + dst.frameNumber = src->frameNumber; + ret.push_back(dst); + } + return ret; +} + +void LiveCapture::OpenCapture(uint32_t ID) +{ + for(Capture *c : m_Captures) + { + if(c->remoteID == ID) + { + openCapture(c); + return; + } + } +} + +void LiveCapture::DeleteCapture(uint32_t ID, bool promptForSave) +{ + for(Capture *c : m_Captures) + { + if(c->remoteID == ID) + { + deleteCapture(c); + return; + } + } +} + +void LiveCapture::SaveCapture(uint32_t ID, rdcstr filename) +{ + for(Capture *c : m_Captures) + { + if(c->remoteID == ID) + { + saveCapture(c, filename); + return; + } + } +} + +rdcarray LiveCapture::GetChildProcesses() +{ + rdcarray ret; + + QMutexLocker l(&m_ChildrenLock); + ret.reserve(m_Children.size()); + for(const ChildProcess &c : m_Children) + ret.push_back(c.PID); + + return ret; +} + +ICaptureConnection *LiveCapture::ConnectToChild(uint32_t pid) +{ + QMutexLocker l(&m_ChildrenLock); + + for(const ChildProcess &c : m_Children) + { + if(c.PID == pid) + { + LiveCapture *live = + new LiveCapture(m_Ctx, m_Hostname, m_HostFriendlyname, c.ident, m_Main, m_Main); + m_Main->ShowLiveCapture(live); + return live; + } + } + + return NULL; +} + void LiveCapture::showEvent(QShowEvent *event) { if(!m_ConnectThread) @@ -403,36 +525,7 @@ void LiveCapture::deleteCapture_triggered() { Capture *cap = GetCapture(item); - if(!cap->saved) - { - if(cap->path == m_Ctx.GetCaptureFilename()) - { - m_Main->takeCaptureOwnership(); - m_Ctx.CloseCapture(); - } - else - { - // if connected, prefer using the live connection - if(m_Connected.available() && !cap->local) - { - QMutexLocker l(&m_DeleteCapturesLock); - m_DeleteCaptures.push_back(cap->remoteID); - } - else - { - m_Ctx.Replay().DeleteCapture(cap->path, cap->local); - } - - if(cap->local) - { - m_Main->RemoveRecentCapture(cap->path); - } - } - } - - delete cap; - - delete ui->captures->takeItem(ui->captures->row(item)); + deleteCapture(cap); } } @@ -955,6 +1048,50 @@ bool LiveCapture::saveCapture(Capture *cap, QString path) return true; } +void LiveCapture::deleteCapture(Capture *cap) +{ + if(!cap->saved) + { + if(cap->path == m_Ctx.GetCaptureFilename()) + { + m_Main->takeCaptureOwnership(); + m_Ctx.CloseCapture(); + } + else + { + // if connected, prefer using the live connection + if(m_Connected.available() && !cap->local) + { + QMutexLocker l(&m_DeleteCapturesLock); + m_DeleteCaptures.push_back(cap->remoteID); + } + else + { + m_Ctx.Replay().DeleteCapture(cap->path, cap->local); + } + + if(cap->local) + { + m_Main->RemoveRecentCapture(cap->path); + } + } + } + + m_Captures.removeOne(cap); + delete cap; + + for(int row = 0; row < ui->captures->count(); row++) + { + QListWidgetItem *item = ui->captures->item(row); + + if(GetCapture(item) == cap) + { + delete ui->captures->takeItem(row); + break; + } + } +} + void LiveCapture::cleanItems() { for(int i = 0; i < ui->captures->count(); i++) @@ -987,6 +1124,7 @@ void LiveCapture::cleanItems() } } + m_Captures.removeOne(cap); delete cap; } ui->captures->clear(); @@ -1172,6 +1310,8 @@ void LiveCapture::captureAdded(const QString &name, const NewCaptureData &newCap AddCapture(item, cap); ui->captures->addItem(item); + + m_Captures.push_back(cap); } void LiveCapture::connectionClosed() @@ -1216,10 +1356,19 @@ void LiveCapture::connectionClosed() } } + int childCount = 0; + uint32_t ident0 = 0; + { + QMutexLocker l(&m_ChildrenLock); + childCount = m_Children.count(); + if(childCount > 0) + ident0 = m_Children[0].ident; + } + // auto-close and load capture if we got a capture. If we // don't have any captures but DO have child processes, // then don't close just yet. - if(ui->captures->count() == 1 || m_Children.count() == 0) + if(ui->captures->count() == 1 || childCount == 0) { // raise the texture viewer if it exists, instead of falling back to most likely the capture // executable dialog which is not useful. @@ -1232,10 +1381,9 @@ void LiveCapture::connectionClosed() // if we have no captures and only one child, close and // open up a connection to it (similar to behaviour with // only one capture - if(ui->captures->count() == 0 && m_Children.count() == 1) + if(ui->captures->count() == 0 && childCount == 1) { - LiveCapture *live = - new LiveCapture(m_Ctx, m_Hostname, m_HostFriendlyname, m_Children[0].ident, m_Main); + LiveCapture *live = new LiveCapture(m_Ctx, m_Hostname, m_HostFriendlyname, ident0, m_Main); m_Main->ShowLiveCapture(live); selfClose(); return; @@ -1245,6 +1393,9 @@ void LiveCapture::connectionClosed() void LiveCapture::selfClose() { + if(!m_SelfClosing) + return; + if(m_ContextMenu) { qInfo() << "preventing race"; @@ -1296,6 +1447,8 @@ void LiveCapture::connectionThreadEntry() else setTitle(target); + m_Target = target; + ui->target->setText(windowTitle()); ui->connectionIcon->setPixmap(Pixmaps::connect(ui->connectionIcon)); ui->connectionStatus->setText(tr("Established")); diff --git a/qrenderdoc/Windows/Dialogs/LiveCapture.h b/qrenderdoc/Windows/Dialogs/LiveCapture.h index 5063b23a0..897c4174a 100644 --- a/qrenderdoc/Windows/Dialogs/LiveCapture.h +++ b/qrenderdoc/Windows/Dialogs/LiveCapture.h @@ -47,7 +47,7 @@ class MainWindow; class QKeyEvent; class NameEditOnlyDelegate; -class LiveCapture : public QFrame +class LiveCapture : public QFrame, public ICaptureConnection { Q_OBJECT @@ -55,10 +55,31 @@ public: explicit LiveCapture(ICaptureContext &ctx, const QString &hostname, const QString &friendlyname, uint32_t ident, MainWindow *main, QWidget *parent = 0); - ~LiveCapture(); + virtual ~LiveCapture(); + + // implement ICaptureConnection + QWidget *Widget() override { return this; } + void RegisterClosedCallback(ClosedCallback method) override + { + m_CloseCallbacks.push_back(method); + } + bool IsConnected() override; + void PreventAutoClose() override { m_SelfClosing = false; } + rdcarray GetAPIs() override; + void QueueCapture(int frameNumber, int numFrames) override; + void TimedCapture(float secondsDelay, int numFrames) override; + void CycleActiveWindow() override; + rdcstr Target() override { return m_Target; } + rdcstr Hostname() override { return m_Hostname; } + rdcstr FriendlyHostname() override { return m_HostFriendlyname; } + void Close(bool discardUnsaved) override; + rdcarray GetCaptures() override; + void OpenCapture(uint32_t ID) override; + void DeleteCapture(uint32_t ID, bool promptForSave) override; + void SaveCapture(uint32_t ID, rdcstr filename) override; + rdcarray GetChildProcesses() override; + ICaptureConnection *ConnectToChild(uint32_t pid) override; - void QueueCapture(int frameNumber, int numFrames); - const QString &hostname() { return m_Hostname; } void cleanItems(); void fileSaved(QString from, QString to); int unsavedCaptureCount(); @@ -89,6 +110,7 @@ private slots: void openNewWindow_triggered(); void saveCapture_triggered(); void deleteCapture_triggered(); + void previewToggle_toggled(bool); void preview_mouseClick(QMouseEvent *e); @@ -154,8 +176,8 @@ private: void setTitle(const QString &title); void openCapture(Capture *cap); bool saveCapture(Capture *cap, QString path); + void deleteCapture(Capture *cap); bool checkAllowDelete(); - void deleteCaptureUnprompted(QListWidgetItem *item); bool isLocal() const; @@ -185,6 +207,8 @@ private: bool m_IgnoreThreadClosed = false; bool m_IgnorePreviewToggle = false; + bool m_SelfClosing = true; + QMenu *m_ContextMenu = NULL; QAction *previewToggle; @@ -197,7 +221,13 @@ private: QPoint previewDragStart; + QString m_Target; + + QList m_Captures; + QMutex m_ChildrenLock; QList m_Children; QMap m_APIs; + + QList m_CloseCallbacks; }; diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index a02eb198f..25fddf352 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -752,12 +752,14 @@ void MainWindow::LoadFromFilename(const QString &filename, bool temporary) void MainWindow::OnCaptureTrigger(const QString &exe, const QString &workingDir, const QString &cmdLine, const rdcarray &env, CaptureOptions opts, - std::function callback) + std::function callback) { if(!PromptCloseCapture()) return; - LambdaThread *th = new LambdaThread([this, exe, workingDir, cmdLine, env, opts, callback]() { + ExecuteResult ret; + + LambdaThread *th = new LambdaThread([this, exe, workingDir, cmdLine, env, opts, callback, &ret]() { if(isUnshareableDeviceInUse()) { RDDialog::warning(this, tr("RenderDoc is already capturing an app on this device"), @@ -769,42 +771,7 @@ void MainWindow::OnCaptureTrigger(const QString &exe, const QString &workingDir, QString capturefile = m_Ctx.TempCaptureFilename(QFileInfo(exe).baseName()); - ExecuteResult ret = - m_Ctx.Replay().ExecuteAndInject(exe, workingDir, cmdLine, env, capturefile, opts); - - GUIInvoke::call(this, [this, exe, ret, callback]() { - if(ret.result.code == ResultCode::JDWPFailure) - { - RDDialog::critical( - this, tr("Error connecting to debugger"), - tr("Error launching %1 for capture.\n\n" - "Something went wrong connecting to the debugger on the Android device.\n\n" - "This can happen if the package is not marked as debuggable, the device is not " - "configured to allow app debugging, if the intent arguments are badly specified, or " - "if another android tool such as Android Studio is interfering with the debug " - "connection.\n\n" - "Close all instances of Android Studio or other Android programs " - "and try again.") - .arg(exe)); - return; - } - - if(ret.result.code != ResultCode::Succeeded) - { - RDDialog::critical( - this, tr("Error launching capture"), - tr("Error launching %1 for capture.\n\n%2").arg(exe).arg(ret.result.Message())); - return; - } - - LiveCapture *live = new LiveCapture( - m_Ctx, - m_Ctx.Replay().CurrentRemote().IsValid() ? m_Ctx.Replay().CurrentRemote().Hostname() : "", - m_Ctx.Replay().CurrentRemote().IsValid() ? m_Ctx.Replay().CurrentRemote().Name() : "", - ret.ident, this, this); - ShowLiveCapture(live); - callback(live); - }); + ret = m_Ctx.Replay().ExecuteAndInject(exe, workingDir, cmdLine, env, capturefile, opts); }); th->setName(lit("ExecuteAndInject")); th->start(); @@ -817,33 +784,53 @@ void MainWindow::OnCaptureTrigger(const QString &exe, const QString &workingDir, [th]() { return !th->isRunning(); }); } th->deleteLater(); + + if(ret.result.code == ResultCode::JDWPFailure) + { + RDDialog::critical( + this, tr("Error connecting to debugger"), + tr("Error launching %1 for capture.\n\n" + "Something went wrong connecting to the debugger on the Android device.\n\n" + "This can happen if the package is not marked as debuggable, the device is not " + "configured to allow app debugging, if the intent arguments are badly specified, or " + "if another android tool such as Android Studio is interfering with the debug " + "connection.\n\n" + "Close all instances of Android Studio or other Android programs " + "and try again.") + .arg(exe)); + return; + } + + if(ret.result.code != ResultCode::Succeeded) + { + RDDialog::critical( + this, tr("Error launching capture"), + tr("Error launching %1 for capture.\n\n%2").arg(exe).arg(ret.result.Message())); + return; + } + + LiveCapture *live = new LiveCapture( + m_Ctx, + m_Ctx.Replay().CurrentRemote().IsValid() ? m_Ctx.Replay().CurrentRemote().Hostname() : "", + m_Ctx.Replay().CurrentRemote().IsValid() ? m_Ctx.Replay().CurrentRemote().Name() : "", + ret.ident, this, this); + ShowLiveCapture(live); + callback(live); } void MainWindow::OnInjectTrigger(uint32_t PID, const rdcarray &env, const QString &name, CaptureOptions opts, - std::function callback) + std::function callback) { if(!PromptCloseCapture()) return; - LambdaThread *th = new LambdaThread([this, PID, env, name, opts, callback]() { + ExecuteResult ret; + + LambdaThread *th = new LambdaThread([this, PID, env, name, opts, callback, &ret]() { QString capturefile = m_Ctx.TempCaptureFilename(name); - ExecuteResult ret = RENDERDOC_InjectIntoProcess(PID, env, capturefile, opts, false); - - GUIInvoke::call(this, [this, PID, ret, callback]() { - if(ret.result.code != ResultCode::Succeeded) - { - RDDialog::critical( - this, tr("Error injecting into process"), - tr("Error injecting into process %1 for capture.\n\n%2").arg(PID).arg(ret.result.Message())); - return; - } - - LiveCapture *live = new LiveCapture(m_Ctx, QString(), QString(), ret.ident, this, this); - ShowLiveCapture(live); - callback(live); - }); + ret = RENDERDOC_InjectIntoProcess(PID, env, capturefile, opts, false); }); th->start(); // wait a few ms before popping up a progress bar @@ -854,6 +841,18 @@ void MainWindow::OnInjectTrigger(uint32_t PID, const rdcarrayisRunning(); }); } th->deleteLater(); + + if(ret.result.code != ResultCode::Succeeded) + { + RDDialog::critical( + this, tr("Error injecting into process"), + tr("Error injecting into process %1 for capture.\n\n%2").arg(PID).arg(ret.result.Message())); + return; + } + + LiveCapture *live = new LiveCapture(m_Ctx, QString(), QString(), ret.ident, this, this); + ShowLiveCapture(live); + callback(live); } void MainWindow::LoadCapture(const QString &filename, const ReplayOptions &opts, bool temporary, @@ -2071,7 +2070,7 @@ void MainWindow::setRemoteHost(int hostIdx) // allow live captures to this host to stay open, that way // we can connect to a live capture, then switch into that // context - if(host.IsValid() && live->hostname() == host.Hostname()) + if(host.IsValid() && live->Hostname() == host.Hostname()) continue; // if the user previously selected 'no to all' in the save prompts below, apply that to all diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index 205b534f6..fe6ca81e5 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -110,10 +110,10 @@ public: void OnCaptureTrigger(const QString &exe, const QString &workingDir, const QString &cmdLine, const rdcarray &env, CaptureOptions opts, - std::function callback); + std::function callback); void OnInjectTrigger(uint32_t PID, const rdcarray &env, const QString &name, CaptureOptions opts, - std::function callback); + std::function callback); void ShowLiveCapture(LiveCapture *live); void LiveCaptureClosed(LiveCapture *live);