Files
renderdoc/qrenderdoc/Windows/Dialogs/RemoteManager.cpp
T
baldurk 082ab4d75d GUIInvoke takes a QObject* to avoid callbacks after object lifetime
* The GUIInvoke object takes a QObject, and uses QPointer to check that
  it hasn't been deleted when the callback fires. This prevents delayed
  callbacks from executing after the object has been deleted and
  crashing.
* In most cases the pointer is just 'this'.
2018-05-08 11:54:34 +01:00

696 lines
16 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2017-2018 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "RemoteManager.h"
#include <QKeyEvent>
#include "3rdparty/flowlayout/FlowLayout.h"
#include "Code/Interface/QRDInterface.h"
#include "Code/Resources.h"
#include "Windows/Dialogs/LiveCapture.h"
#include "Windows/MainWindow.h"
#include "ui_RemoteManager.h"
struct RemoteConnect
{
RemoteConnect() {}
RemoteConnect(const QString &h, const QString &f, uint32_t i) : host(h), friendly(f), ident(i) {}
QString host;
QString friendly;
uint32_t ident = 0;
};
Q_DECLARE_METATYPE(RemoteConnect);
static void setRemoteConnect(RDTreeWidgetItem *item, const RemoteConnect &connect)
{
if(!item)
return;
item->setTag(QVariant::fromValue(connect));
}
static RemoteConnect getRemoteConnect(RDTreeWidgetItem *item)
{
if(!item)
return RemoteConnect();
return item->tag().value<RemoteConnect>();
}
static void setRemoteHost(RDTreeWidgetItem *item, RemoteHost *host)
{
if(!item)
return;
item->setTag(QVariant::fromValue((uintptr_t)host));
}
static RemoteHost *getRemoteHost(RDTreeWidgetItem *item)
{
if(!item)
return NULL;
return (RemoteHost *)item->tag().value<uintptr_t>();
}
void deleteItemAndHost(RDTreeWidgetItem *item)
{
delete getRemoteHost(item);
delete item;
}
RemoteManager::RemoteManager(ICaptureContext &ctx, MainWindow *main)
: QDialog(NULL), ui(new Ui::RemoteManager), m_Ctx(ctx), m_Main(main)
{
ui->setupUi(this);
m_ExternalRef.release(1);
ui->hosts->setFont(Formatter::PreferredFont());
ui->hostname->setFont(Formatter::PreferredFont());
ui->runCommand->setFont(Formatter::PreferredFont());
ui->hosts->setColumns({tr("Hostname"), tr("Running")});
ui->hosts->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->hosts->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
lookupsProgressFlow = new QWidget(this);
FlowLayout *flow = new FlowLayout(lookupsProgressFlow, 0, 3, 3);
lookupsProgressFlow->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
flow->addWidget(ui->progressIcon);
flow->addWidget(ui->progressText);
flow->addWidget(ui->progressCount);
QVBoxLayout *vertical = new QVBoxLayout(this);
vertical->addWidget(ui->hosts);
vertical->addWidget(lookupsProgressFlow);
vertical->addWidget(ui->bottomLayout->parentWidget());
m_Ctx.Config().AddAndroidHosts();
for(RemoteHost *h : m_Ctx.Config().RemoteHosts)
addHost(h);
on_hosts_itemSelectionChanged();
}
RemoteManager::~RemoteManager()
{
for(RDTreeWidgetItem *item : m_QueuedDeletes)
deleteItemAndHost(item);
delete ui;
}
void RemoteManager::closeWhenFinished()
{
m_ExternalRef.acquire(1);
updateStatus();
}
void RemoteManager::setRemoteServerLive(RDTreeWidgetItem *node, bool live, bool busy)
{
RemoteHost *host = getRemoteHost(node);
if(!host)
return;
host->serverRunning = live;
host->busy = busy;
if(host->IsLocalhost())
{
node->setIcon(0, QIcon());
node->setText(1, QString());
}
else
{
QString text = live ? tr("Remote server running") : tr("No remote server");
if(host->connected)
text += tr(" (Active Context)");
else if(host->versionMismatch)
text += tr(" (Version Mismatch)");
else if(host->busy)
text += tr(" (Busy)");
node->setText(1, text);
node->setIcon(0, live ? Icons::connect() : Icons::disconnect());
}
}
bool RemoteManager::isRemoteServerLive(RDTreeWidgetItem *node)
{
RemoteHost *host = getRemoteHost(node);
return host && host->serverRunning;
}
void RemoteManager::addHost(RemoteHost *host)
{
RDTreeWidgetItem *node = new RDTreeWidgetItem({host->Name(), lit("...")});
node->setItalic(true);
node->setIcon(0, Icons::hourglass());
setRemoteHost(node, host);
ui->hosts->addTopLevelItem(node);
ui->hosts->setSelectedItem(node);
ui->refreshOne->setEnabled(false);
ui->refreshAll->setEnabled(false);
refreshHost(node);
updateLookupsStatus();
}
void RemoteManager::updateLookupsStatus()
{
lookupsProgressFlow->setVisible(!ui->refreshAll->isEnabled());
ui->progressCount->setText(tr("%1 lookups remaining").arg(m_Lookups.available()));
}
void RemoteManager::runRemoteServer(RDTreeWidgetItem *node)
{
RemoteHost *host = getRemoteHost(node);
if(!host)
{
m_Lookups.acquire();
return;
}
host->Launch();
// now refresh this host
refreshHost(node);
m_Lookups.acquire();
}
void RemoteManager::refreshHost(RDTreeWidgetItem *node)
{
RemoteHost *host = getRemoteHost(node);
if(!host)
return;
m_Lookups.release();
// this function looks up the remote connections and for each one open
// queries it for the API, target (usually executable name) and if any user is already connected
LambdaThread *th = new LambdaThread([this, node, host]() {
QByteArray username = GetSystemUsername().toUtf8();
host->CheckStatus();
GUIInvoke::call(
this, [this, node, host]() { setRemoteServerLive(node, host->serverRunning, host->busy); });
uint32_t nextIdent = 0;
for(;;)
{
// just a sanity check to make sure we don't hit some unexpected case and infinite loop
uint32_t prevIdent = nextIdent;
nextIdent = RENDERDOC_EnumerateRemoteTargets(host->hostname.c_str(), nextIdent);
if(nextIdent == 0 || prevIdent >= nextIdent)
break;
ITargetControl *conn =
RENDERDOC_CreateTargetControl(host->hostname.c_str(), nextIdent, username.data(), false);
if(conn)
{
QString target = QString::fromUtf8(conn->GetTarget());
QString api = QString::fromUtf8(conn->GetAPI());
QString busy = QString::fromUtf8(conn->GetBusyClient());
QString running;
if(!busy.isEmpty())
running = tr("Running %1, %2 is connected").arg(api).arg(busy);
else
running = tr("Running %1").arg(api);
RemoteConnect tag(host->hostname, host->Name(), nextIdent);
GUIInvoke::call(this, [this, node, target, running, tag]() {
RDTreeWidgetItem *child = new RDTreeWidgetItem({target, running});
setRemoteConnect(child, tag);
node->addChild(child);
ui->hosts->expandItem(node);
});
conn->Shutdown();
}
}
GUIInvoke::call(this, [node]() { node->setItalic(false); });
m_Lookups.acquire();
GUIInvoke::call(this, [this]() { updateStatus(); });
});
th->selfDelete(true);
th->start();
}
// don't allow the user to refresh until all pending connections have been checked
// (to stop flooding)
void RemoteManager::updateStatus()
{
if(m_Lookups.available() == 0)
{
ui->refreshOne->setEnabled(true);
ui->refreshAll->setEnabled(true);
for(RDTreeWidgetItem *item : m_QueuedDeletes)
deleteItemAndHost(item);
m_QueuedDeletes.clear();
// if the external ref is gone now, we can delete ourselves
if(m_ExternalRef.available() == 0)
{
deleteLater();
return;
}
}
updateConnectButton();
updateLookupsStatus();
}
void RemoteManager::connectToApp(RDTreeWidgetItem *node)
{
if(node)
{
RemoteConnect connect = getRemoteConnect(node);
if(connect.ident > 0)
{
LiveCapture *live =
new LiveCapture(m_Ctx, connect.host, connect.friendly, connect.ident, m_Main, m_Main);
m_Main->ShowLiveCapture(live);
accept();
}
}
}
void RemoteManager::updateConnectButton()
{
RDTreeWidgetItem *item = ui->hosts->selectedItem();
if(item)
{
ui->connect->setEnabled(true);
ui->connect->setText(tr("Connect to App"));
RemoteHost *host = getRemoteHost(item);
if(host)
{
if(host->IsLocalhost())
{
ui->connect->setText(tr("Run Server"));
ui->connect->setEnabled(false);
}
else if(host->serverRunning)
{
ui->connect->setText(tr("Shutdown"));
if(host->busy && !host->connected)
ui->connect->setEnabled(false);
}
else
{
ui->connect->setText(tr("Run Server"));
if(host->runCommand.isEmpty())
ui->connect->setEnabled(false);
}
}
}
else
{
ui->connect->setEnabled(false);
}
}
void RemoteManager::addNewHost()
{
QString host = ui->hostname->text().trimmed();
if(!host.isEmpty())
{
bool found = false;
for(int i = 0; i < m_Ctx.Config().RemoteHosts.count(); i++)
{
QString hostname = m_Ctx.Config().RemoteHosts[i]->hostname;
if(hostname.compare(host, Qt::CaseInsensitive) == 0)
{
found = true;
break;
}
}
if(!found)
{
RemoteHost *h = new RemoteHost();
h->hostname = host;
h->runCommand = ui->runCommand->text().trimmed();
m_Ctx.Config().RemoteHosts.push_back(h);
m_Ctx.Config().Save();
addHost(h);
}
}
ui->hostname->setText(host);
on_hostname_textEdited(host);
}
void RemoteManager::setRunCommand()
{
RDTreeWidgetItem *item = ui->hosts->selectedItem();
if(!item)
return;
RemoteHost *h = getRemoteHost(item);
if(h)
{
h->runCommand = ui->runCommand->text().trimmed();
m_Ctx.Config().Save();
}
}
void RemoteManager::queueDelete(RDTreeWidgetItem *item)
{
// if there are refreshes pending, queue it for deletion when they complete.
if(m_Lookups.available() > 0)
{
m_QueuedDeletes.push_back(item);
}
else
{
deleteItemAndHost(item);
}
}
void RemoteManager::on_hosts_itemActivated(RDTreeWidgetItem *item, int column)
{
RemoteConnect connect = getRemoteConnect(item);
if(connect.ident > 0)
connectToApp(item);
}
void RemoteManager::on_hosts_itemSelectionChanged()
{
ui->addUpdateHost->setText(tr("Add"));
ui->addUpdateHost->setEnabled(true);
ui->deleteHost->setEnabled(false);
ui->refreshOne->setEnabled(false);
ui->hostname->setEnabled(true);
ui->runCommand->setEnabled(true);
RDTreeWidgetItem *item = ui->hosts->selectedItem();
RemoteHost *host = item ? getRemoteHost(item) : NULL;
ui->runCommand->setText(QString());
if(host)
{
if(ui->refreshAll->isEnabled())
ui->refreshOne->setEnabled(true);
ui->runCommand->setText(host->runCommand);
ui->hostname->setText(host->Name());
ui->addUpdateHost->setText(tr("Update"));
if(host->IsLocalhost() || host->IsADB())
{
// localhost and android hosts cannot be updated or have their run command changed
ui->addUpdateHost->setEnabled(false);
ui->runCommand->setEnabled(false);
}
else
{
// any other host can be deleted
ui->deleteHost->setEnabled(true);
}
}
updateConnectButton();
}
void RemoteManager::on_hostname_textEdited(const QString &text)
{
RDTreeWidgetItem *node = NULL;
for(int i = 0; i < ui->hosts->topLevelItemCount(); i++)
{
RDTreeWidgetItem *n = ui->hosts->topLevelItem(i);
if(n->text(0) == text)
{
node = n;
break;
}
}
if(node)
ui->hosts->setSelectedItem(node);
else
ui->hosts->clearSelection();
}
void RemoteManager::on_hosts_keyPress(QKeyEvent *event)
{
if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
{
if(ui->connect->isEnabled())
on_connect_clicked();
}
if(event->key() == Qt::Key_Delete && ui->deleteHost->isEnabled())
on_deleteHost_clicked();
}
void RemoteManager::on_hostname_keyPress(QKeyEvent *event)
{
if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
{
if(ui->addUpdateHost->isEnabled())
on_addUpdateHost_clicked();
}
}
void RemoteManager::on_runCommand_keyPress(QKeyEvent *event)
{
if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
{
if(ui->addUpdateHost->isEnabled())
on_addUpdateHost_clicked();
}
}
void RemoteManager::on_addUpdateHost_clicked()
{
RDTreeWidgetItem *item = ui->hosts->selectedItem();
if(item && getRemoteHost(item))
setRunCommand();
else
addNewHost();
}
void RemoteManager::on_refreshAll_clicked()
{
if(m_Lookups.available())
return;
ui->refreshOne->setEnabled(false);
ui->refreshAll->setEnabled(false);
for(int i = 0; i < ui->hosts->topLevelItemCount(); i++)
{
RDTreeWidgetItem *n = ui->hosts->topLevelItem(i);
n->clear();
n->setItalic(true);
n->setIcon(0, Icons::hourglass());
refreshHost(n);
}
updateLookupsStatus();
}
void RemoteManager::on_refreshOne_clicked()
{
RDTreeWidgetItem *n = ui->hosts->selectedItem();
if(m_Lookups.available() || !n)
return;
ui->refreshOne->setEnabled(false);
ui->refreshAll->setEnabled(false);
{
n->clear();
n->setItalic(true);
n->setIcon(0, Icons::hourglass());
refreshHost(n);
}
updateLookupsStatus();
}
void RemoteManager::on_connect_clicked()
{
RDTreeWidgetItem *node = ui->hosts->selectedItem();
if(!node)
return;
RemoteConnect connect = getRemoteConnect(node);
RemoteHost *host = getRemoteHost(node);
if(connect.ident > 0)
{
connectToApp(node);
}
else if(host)
{
if(host->serverRunning)
{
QMessageBox::StandardButton res = RDDialog::question(
this, tr("Remote server shutdown"),
tr("Are you sure you wish to shut down running remote server on %1?").arg(host->Name()),
RDDialog::YesNoCancel);
if(res == QMessageBox::Cancel || res == QMessageBox::No)
return;
// shut down
if(host->connected)
{
m_Ctx.Replay().ShutdownServer();
setRemoteServerLive(node, false, false);
}
else
{
IRemoteServer *server = NULL;
ReplayStatus status =
RENDERDOC_CreateRemoteServerConnection(host->hostname.c_str(), 0, &server);
if(server)
server->ShutdownServerAndConnection();
setRemoteServerLive(node, false, false);
if(status != ReplayStatus::Succeeded)
RDDialog::critical(this, tr("Shutdown error"),
tr("Error shutting down remote server: %1").arg(ToQStr(status)));
}
updateConnectButton();
}
else
{
// try to run
ui->refreshOne->setEnabled(false);
ui->refreshAll->setEnabled(false);
// hold a ref for running the remote server
m_Lookups.release();
LambdaThread *th = new LambdaThread([this, node]() { runRemoteServer(node); });
th->selfDelete(true);
th->start();
updateLookupsStatus();
}
}
}
void RemoteManager::on_deleteHost_clicked()
{
RDTreeWidgetItem *item = ui->hosts->selectedItem();
if(!item)
return;
RemoteHost *host = getRemoteHost(item);
int itemIdx = ui->hosts->indexOfTopLevelItem(item);
// don't delete running instances on a host
if(item->parent() != ui->hosts->invisibleRootItem() || itemIdx < 0 || !host)
return;
QString hostname = item->text(0);
if(hostname == lit("localhost"))
return;
QMessageBox::StandardButton res = RDDialog::question(
this, tr("Deleting host"), tr("Are you sure you wish to delete %1?").arg(hostname),
RDDialog::YesNoCancel);
if(res == QMessageBox::Cancel || res == QMessageBox::No)
return;
if(res == QMessageBox::Yes)
{
int32_t idx = m_Ctx.Config().RemoteHosts.indexOf(host);
if(idx < 0)
return;
// the host will be removed in queueDelete.
m_Ctx.Config().RemoteHosts.erase(idx);
m_Ctx.Config().Save();
item->clear();
queueDelete(ui->hosts->takeTopLevelItem(itemIdx));
ui->hosts->clearSelection();
ui->hostname->setText(hostname);
on_hostname_textEdited(hostname);
}
}