mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-23 23:16:31 +00:00
Expose capture connection windows to python scripting
This commit is contained in:
@@ -76,6 +76,12 @@ Capture Dialog
|
||||
.. autoclass:: qrenderdoc.CaptureDialog
|
||||
:members:
|
||||
|
||||
.. autoclass:: qrenderdoc.CaptureConnection
|
||||
:members:
|
||||
|
||||
.. autoclass:: qrenderdoc.ConnectedTempCapture
|
||||
:members:
|
||||
|
||||
Debug Messages
|
||||
--------------
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user