Expose capture connection windows to python scripting

This commit is contained in:
baldurk
2026-08-13 17:46:46 +01:00
parent 7806d1557d
commit db75eec620
10 changed files with 581 additions and 117 deletions
+6
View File
@@ -76,6 +76,12 @@ Capture Dialog
.. autoclass:: qrenderdoc.CaptureDialog
:members:
.. autoclass:: qrenderdoc.CaptureConnection
:members:
.. autoclass:: qrenderdoc.ConnectedTempCapture
:members:
Debug Messages
--------------
+2 -2
View File
@@ -2348,11 +2348,11 @@ ICaptureDialog *CaptureContext::GetCaptureDialog()
*this,
[this](const QString &exe, const QString &workingDir, const QString &cmdLine,
const rdcarray<EnvironmentModification> &env, CaptureOptions opts,
std::function<void(LiveCapture *)> callback) {
std::function<void(ICaptureConnection *)> callback) {
return m_MainWindow->OnCaptureTrigger(exe, workingDir, cmdLine, env, opts, callback);
},
[this](uint32_t PID, const rdcarray<EnvironmentModification> &env, const QString &name,
CaptureOptions opts, std::function<void(LiveCapture *)> callback) {
CaptureOptions opts, std::function<void(ICaptureConnection *)> callback) {
return m_MainWindow->OnInjectTrigger(PID, env, name, opts, callback);
},
m_MainWindow, m_MainWindow);
+273 -2
View File
@@ -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<void(ICaptureConnection *)> 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<rdcstr> 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 <ConnectedTempCapture>` 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<ConnectedTempCapture> 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<uint32_t> 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`.
+1
View File
@@ -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
+17 -12
View File
@@ -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 rdcarray<EnvironmentModifi
ui->envVar->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;
}
+3 -4
View File
@@ -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<void(const QString &exe, const QString &workingDir, const QString &cmdLine,
const rdcarray<EnvironmentModification> &env, CaptureOptions opts,
std::function<void(LiveCapture *)> callback)>
std::function<void(ICaptureConnection *)> callback)>
OnCaptureMethod;
typedef std::function<void(uint32_t PID, const rdcarray<EnvironmentModification> &env, const QString &name,
CaptureOptions opts, std::function<void(LiveCapture *)> callback)>
CaptureOptions opts, std::function<void(ICaptureConnection *)> 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;
+187 -34
View File
@@ -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<rdcstr> LiveCapture::GetAPIs()
{
rdcarray<rdcstr> 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<ConnectedTempCapture> LiveCapture::GetCaptures()
{
rdcarray<ConnectedTempCapture> 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<uint32_t> LiveCapture::GetChildProcesses()
{
rdcarray<uint32_t> 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"));
+35 -5
View File
@@ -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<rdcstr> 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<ConnectedTempCapture> 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<uint32_t> 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<Capture *> m_Captures;
QMutex m_ChildrenLock;
QList<ChildProcess> m_Children;
QMap<QString, APIStatus> m_APIs;
QList<ClosedCallback> m_CloseCallbacks;
};
+55 -56
View File
@@ -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<EnvironmentModification> &env, CaptureOptions opts,
std::function<void(LiveCapture *)> callback)
std::function<void(ICaptureConnection *)> 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("<html>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 <b>all</b> instances of Android Studio or other Android programs "
"and try again.</html>")
.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("<html>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 <b>all</b> instances of Android Studio or other Android programs "
"and try again.</html>")
.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<EnvironmentModification> &env,
const QString &name, CaptureOptions opts,
std::function<void(LiveCapture *)> callback)
std::function<void(ICaptureConnection *)> 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 rdcarray<EnvironmentModific
[th]() { return !th->isRunning(); });
}
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
+2 -2
View File
@@ -110,10 +110,10 @@ public:
void OnCaptureTrigger(const QString &exe, const QString &workingDir, const QString &cmdLine,
const rdcarray<EnvironmentModification> &env, CaptureOptions opts,
std::function<void(LiveCapture *)> callback);
std::function<void(ICaptureConnection *)> callback);
void OnInjectTrigger(uint32_t PID, const rdcarray<EnvironmentModification> &env,
const QString &name, CaptureOptions opts,
std::function<void(LiveCapture *)> callback);
std::function<void(ICaptureConnection *)> callback);
void ShowLiveCapture(LiveCapture *live);
void LiveCaptureClosed(LiveCapture *live);