diff --git a/qrenderdoc/Code/QRDUtils.cpp b/qrenderdoc/Code/QRDUtils.cpp index e23319bbc..d061c75c2 100644 --- a/qrenderdoc/Code/QRDUtils.cpp +++ b/qrenderdoc/Code/QRDUtils.cpp @@ -378,12 +378,14 @@ QMessageBox::StandardButton RDDialog::messageBox(QMessageBox::Icon icon, QWidget } QString RDDialog::getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, - QFileDialog::Options options) + QFileDialog::Options options, QAbstractProxyModel *proxy) { QFileDialog fd(parent, caption, dir, QString()); fd.setAcceptMode(QFileDialog::AcceptOpen); fd.setFileMode(QFileDialog::DirectoryOnly); fd.setOptions(options); + if(proxy) + fd.setProxyModel(proxy); show(&fd); if(fd.result() == QFileDialog::Accepted) @@ -420,7 +422,7 @@ QString RDDialog::getOpenFileName(QWidget *parent, const QString &caption, const } QString RDDialog::getExecutableFileName(QWidget *parent, const QString &caption, const QString &dir, - QFileDialog::Options options) + QFileDialog::Options options, QAbstractProxyModel *proxy) { QString filter; @@ -433,8 +435,12 @@ QString RDDialog::getExecutableFileName(QWidget *parent, const QString &caption, fd.setOptions(options); fd.setAcceptMode(QFileDialog::AcceptOpen); fd.setFileMode(QFileDialog::ExistingFile); - QFileFilterModel *proxy = new QFileFilterModel(parent); - proxy->setRequirePermissions(QDir::Executable); + if(!proxy) + { + QFileFilterModel *fileProxy = new QFileFilterModel(parent); + fileProxy->setRequirePermissions(QDir::Executable); + proxy = fileProxy; + } fd.setProxyModel(proxy); show(&fd); diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index b5603f434..58d668283 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -628,7 +628,8 @@ struct RDDialog static QString getExistingDirectory(QWidget *parent = NULL, const QString &caption = QString(), const QString &dir = QString(), - QFileDialog::Options options = QFileDialog::ShowDirsOnly); + QFileDialog::Options options = QFileDialog::ShowDirsOnly, + QAbstractProxyModel *proxy = NULL); static QString getOpenFileName(QWidget *parent = NULL, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), @@ -637,7 +638,8 @@ struct RDDialog static QString getExecutableFileName(QWidget *parent = NULL, const QString &caption = QString(), const QString &dir = QString(), - QFileDialog::Options options = QFileDialog::Options()); + QFileDialog::Options options = QFileDialog::Options(), + QAbstractProxyModel *proxy = NULL); static QString getSaveFileName(QWidget *parent = NULL, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), diff --git a/qrenderdoc/Code/RenderManager.cpp b/qrenderdoc/Code/RenderManager.cpp index 1dd1a4adc..cef5a8bbd 100644 --- a/qrenderdoc/Code/RenderManager.cpp +++ b/qrenderdoc/Code/RenderManager.cpp @@ -23,7 +23,9 @@ ******************************************************************************/ #include "RenderManager.h" +#include #include +#include #include "CaptureContext.h" #include "QRDUtils.h" @@ -71,11 +73,150 @@ void RenderManager::DeleteCapture(const QString &logfile, bool local) } else { - // TODO Remote - // m_Remote.TakeOwnershipCapture(logfile); + // this will be cleaned up automatically when the remote connection + // is closed. + if(m_Remote) + { + QMutexLocker autolock(&m_RemoteLock); + m_Remote->TakeOwnershipCapture(logfile.toUtf8().data()); + } } } +QStringList RenderManager::GetRemoteSupport() +{ + QStringList ret; + + if(m_Remote && !IsRunning()) + { + QMutexLocker autolock(&m_RemoteLock); + + rdctype::array supported; + m_Remote->RemoteSupportedReplays(&supported); + for(rdctype::str &s : supported) + ret << ToQStr(s); + } + + return ret; +} + +void RenderManager::GetHomeFolder(DirectoryBrowseMethod cb) +{ + if(!m_Remote) + return; + + if(IsRunning() && m_Thread->isCurrentThread()) + { + AsyncInvoke([cb, this](IReplayRenderer *r) { + cb(m_Remote->GetHomeFolder().c_str(), rdctype::array()); + }); + return; + } + + rdctype::str home; + + { + QMutexLocker autolock(&m_RemoteLock); + home = m_Remote->GetHomeFolder(); + } + + cb(home.c_str(), rdctype::array()); +} + +bool RenderManager::ListFolder(QString path, DirectoryBrowseMethod cb) +{ + if(!m_Remote) + return false; + + if(IsRunning() && m_Thread->isCurrentThread()) + { + AsyncInvoke([cb, path, this](IReplayRenderer *r) { + const char *pathstr = path.toUtf8().data(); + cb(pathstr, m_Remote->ListFolder(pathstr)); + }); + return true; + } + + rdctype::array contents; + + const char *pathstr = path.toUtf8().data(); + + // prevent pings while fetching remote FS data + { + QMutexLocker autolock(&m_RemoteLock); + contents = m_Remote->ListFolder(pathstr); + } + + cb(pathstr, contents); + + return true; +} + +QString RenderManager::CopyCaptureToRemote(const QString &localpath, QWidget *window) +{ + if(!m_Remote) + return ""; + + QString remotepath = ""; + + bool copied = false; + float progress = 0.0f; + + auto lambda = [this, localpath, &remotepath, &progress, &copied](IReplayRenderer *r) { + QMutexLocker autolock(&m_RemoteLock); + remotepath = m_Remote->CopyCaptureToRemote(localpath.toUtf8().data(), &progress); + copied = true; + }; + + // we should never have the thread running at this point, but let's be safe. + if(IsRunning()) + { + AsyncInvoke(lambda); + } + else + { + LambdaThread *thread = new LambdaThread([&lambda]() { lambda(NULL); }); + thread->selfDelete(true); + thread->start(); + } + + ShowProgressDialog(window, QApplication::translate("RenderManager", "Transferring..."), + [&copied]() { return !copied; }, [&progress]() { return progress; }); + + return remotepath; +} + +void RenderManager::CopyCaptureFromRemote(const QString &remotepath, const QString &localpath, + QWidget *window) +{ + if(!m_Remote) + return; + + bool copied = false; + float progress = 0.0f; + + auto lambda = [this, localpath, remotepath, &progress, &copied](IReplayRenderer *r) { + QMutexLocker autolock(&m_RemoteLock); + m_Remote->CopyCaptureFromRemote(remotepath.toUtf8().data(), localpath.toUtf8().data(), &progress); + copied = true; + }; + + // we should never have the thread running at this point, but let's be safe. + if(IsRunning()) + { + AsyncInvoke(lambda); + } + else + { + LambdaThread *thread = new LambdaThread([&lambda]() { lambda(NULL); }); + thread->selfDelete(true); + thread->start(); + } + + ShowProgressDialog(window, QApplication::translate("RenderManager", "Transferring..."), + [&copied]() { return !copied; }, [&progress]() { return progress; }); +} + bool RenderManager::IsRunning() { return m_Thread && m_Thread->isRunning() && m_Running; @@ -122,33 +263,90 @@ void RenderManager::CloseThread() m_Thread = NULL; } +ReplayCreateStatus RenderManager::ConnectToRemoteServer(RemoteHost *host) +{ + ReplayCreateStatus status = + RENDERDOC_CreateRemoteServerConnection(host->Hostname.toUtf8().data(), 0, &m_Remote); + + m_RemoteHost = host; + + if(status == eReplayCreate_Success) + { + m_RemoteHost->Connected = true; + return status; + } + + return status; +} + +void RenderManager::DisconnectFromRemoteServer() +{ + if(m_Remote) + { + QMutexLocker autolock(&m_RemoteLock); + m_Remote->ShutdownConnection(); + } + + m_RemoteHost = NULL; + m_Remote = NULL; +} + +void RenderManager::ShutdownServer() +{ + if(m_Remote) + { + QMutexLocker autolock(&m_RemoteLock); + m_Remote->ShutdownServerAndConnection(); + } + + m_Remote = NULL; +} + +void RenderManager::PingRemote() +{ + if(!m_Remote) + return; + + if(m_RemoteLock.tryLock(0)) + { + if(!IsRunning() || m_Thread->isCurrentThread()) + { + if(!m_Remote->Ping()) + m_RemoteHost->ServerRunning = false; + } + m_RemoteLock.unlock(); + } +} + uint32_t RenderManager::ExecuteAndInject(const QString &exe, const QString &workingDir, const QString &cmdLine, const QList &env, const QString &logfile, CaptureOptions opts) { - // if (m_Remote == null) + void *envList = RENDERDOC_MakeEnvironmentModificationList(env.size()); + + for(int i = 0; i < env.size(); i++) + RENDERDOC_SetEnvironmentModification(envList, i, env[i].variable.toUtf8().data(), + env[i].value.toUtf8().data(), env[i].type, env[i].separator); + + uint32_t ret = 0; + + if(m_Remote) { - void *envList = RENDERDOC_MakeEnvironmentModificationList(env.size()); - - for(int i = 0; i < env.size(); i++) - RENDERDOC_SetEnvironmentModification(envList, i, env[i].variable.toUtf8().data(), - env[i].value.toUtf8().data(), env[i].type, - env[i].separator); - - uint32_t ret = RENDERDOC_ExecuteAndInject(exe.toUtf8().data(), workingDir.toUtf8().data(), - cmdLine.toUtf8().data(), envList, - logfile.toUtf8().data(), &opts, false); - - RENDERDOC_FreeEnvironmentModificationList(envList); - - return ret; + QMutexLocker autolock(&m_RemoteLock); + ret = m_Remote->ExecuteAndInject(exe.toUtf8().data(), workingDir.toUtf8().data(), + cmdLine.toUtf8().data(), envList, &opts); } - /* else { + ret = RENDERDOC_ExecuteAndInject(exe.toUtf8().data(), workingDir.toUtf8().data(), + cmdLine.toUtf8().data(), envList, logfile.toUtf8().data(), + &opts, false); } - */ + + RENDERDOC_FreeEnvironmentModificationList(envList); + + return ret; } void RenderManager::PushInvoke(RenderManager::InvokeHandle *cmd) @@ -162,17 +360,19 @@ void RenderManager::PushInvoke(RenderManager::InvokeHandle *cmd) return; } - m_RenderLock.lock(); + QMutexLocker autolock(&m_RenderLock); m_RenderQueue.push_back(cmd); m_RenderCondition.wakeAll(); - m_RenderLock.unlock(); } void RenderManager::run() { IReplayRenderer *renderer = NULL; - m_CreateStatus = RENDERDOC_CreateReplayRenderer(m_Logfile.toUtf8(), m_Progress, &renderer); + if(m_Remote) + m_CreateStatus = m_Remote->OpenCapture(~0U, m_Logfile.toUtf8().data(), m_Progress, &renderer); + else + m_CreateStatus = RENDERDOC_CreateReplayRenderer(m_Logfile.toUtf8().data(), m_Progress, &renderer); if(renderer == NULL) return; @@ -189,10 +389,9 @@ void RenderManager::run() // wait for the condition to be woken, grab current queue, // unlock again. { - m_RenderLock.lock(); + QMutexLocker autolock(&m_RenderLock); m_RenderCondition.wait(&m_RenderLock, 10); m_RenderQueue.swap(queue); - m_RenderLock.unlock(); } // process all the commands @@ -216,9 +415,10 @@ void RenderManager::run() { QQueue queue; - m_RenderLock.lock(); - m_RenderQueue.swap(queue); - m_RenderLock.unlock(); + { + QMutexLocker autolock(&m_RenderLock); + m_RenderQueue.swap(queue); + } for(InvokeHandle *cmd : queue) { @@ -233,5 +433,8 @@ void RenderManager::run() } // close the core renderer - renderer->Shutdown(); + if(m_Remote) + m_Remote->CloseCapture(renderer); + else + renderer->Shutdown(); } diff --git a/qrenderdoc/Code/RenderManager.h b/qrenderdoc/Code/RenderManager.h index 00c8d8176..f1b1ec2f9 100644 --- a/qrenderdoc/Code/RenderManager.h +++ b/qrenderdoc/Code/RenderManager.h @@ -33,10 +33,12 @@ #include #include #include "QRDUtils.h" +#include "RemoteHost.h" #include "renderdoc_replay.h" struct IReplayRenderer; class LambdaThread; +class RemoteHost; // simple helper for the common case of 'we just need to run this on the render thread #define INVOKE_MEMFN(function) \ @@ -119,6 +121,7 @@ class RenderManager { public: typedef std::function InvokeMethod; + typedef std::function &)> DirectoryBrowseMethod; RenderManager(); ~RenderManager(); @@ -133,10 +136,22 @@ public: void CloseThread(); + ReplayCreateStatus ConnectToRemoteServer(RemoteHost *host); + void DisconnectFromRemoteServer(); + void ShutdownServer(); + void PingRemote(); + + const RemoteHost *remote() { return m_RemoteHost; } uint32_t ExecuteAndInject(const QString &exe, const QString &workingDir, const QString &cmdLine, const QList &env, const QString &logfile, CaptureOptions opts); + QStringList GetRemoteSupport(); + void GetHomeFolder(DirectoryBrowseMethod cb); + bool ListFolder(QString path, DirectoryBrowseMethod cb); + QString CopyCaptureToRemote(const QString &localpath, QWidget *window); + void CopyCaptureFromRemote(const QString &remotepath, const QString &localpath, QWidget *window); + private: struct InvokeHandle { @@ -164,6 +179,10 @@ private: QString m_Logfile; float *m_Progress; + QMutex m_RemoteLock; + RemoteHost *m_RemoteHost = NULL; + IRemoteServer *m_Remote = NULL; + volatile bool m_Running; LambdaThread *m_Thread; ReplayCreateStatus m_CreateStatus; diff --git a/qrenderdoc/Windows/DebugMessageView.cpp b/qrenderdoc/Windows/DebugMessageView.cpp index bf7e58aa4..4eca2e886 100644 --- a/qrenderdoc/Windows/DebugMessageView.cpp +++ b/qrenderdoc/Windows/DebugMessageView.cpp @@ -371,7 +371,7 @@ void DebugMessageView::messages_contextMenu(const QPoint &pos) m_ContextMessage = msg; - m_ContextMenu->exec(ui->messages->viewport()->mapToGlobal(pos)); + RDDialog::show(m_ContextMenu, ui->messages->viewport()->mapToGlobal(pos)); } } diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp index 710671d18..d57e0d221 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp @@ -244,9 +244,21 @@ void CaptureDialog::on_exePath_textChanged(const QString &exe) bool valid = dir.makeAbsolute(); if(valid && f.isAbsolute()) - ui->workDirPath->setPlaceholderText(QDir::toNativeSeparators(dir.absolutePath())); + { + QString path = QDir::toNativeSeparators(dir.absolutePath()); + + // match the path separators from the path + if(exe.count(QChar('/')) > exe.count(QChar('\\'))) + path = path.replace('\\', '/'); + else + path = path.replace('/', '\\'); + + ui->workDirPath->setPlaceholderText(path); + } else if(exe == "") + { ui->workDirPath->setPlaceholderText(""); + } updateGlobalHook(); } @@ -279,24 +291,20 @@ void CaptureDialog::on_exePathBrowse_clicked() file = m_Ctx->Config.LastCaptureExe; } - // TODO Remote + QAbstractProxyModel *proxy = NULL; + QFileDialog::Options options; - // if(m_Core.Renderer.Remote == null) + if(m_Ctx->Renderer()->remote()) { - QString filename = RDDialog::getExecutableFileName(this, tr("Choose executable"), initDir); - - if(filename != "") - setExecutableFilename(filename); + // proxy = new RemoteFileProxy(m_Ctx->Renderer()); + options = QFileDialog::DontUseNativeDialog; } - /* - else - { - VirtualOpenFileDialog remoteBrowser = new VirtualOpenFileDialog(m_Core.Renderer); - remoteBrowser.Opened += new EventHandler(this.virtualExeBrowser_Opened); - remoteBrowser.ShowDialog(this); - } - */ + QString filename = + RDDialog::getExecutableFileName(this, tr("Choose executable"), initDir, options, proxy); + + if(filename != "") + setExecutableFilename(filename); } void CaptureDialog::on_workDirBrowse_clicked() @@ -316,27 +324,20 @@ void CaptureDialog::on_workDirBrowse_clicked() initDir = m_Ctx->Config.LastCapturePath; } - // TODO Remote + QAbstractProxyModel *proxy = NULL; + QFileDialog::Options options = QFileDialog::ShowDirsOnly; - // if(m_Core.Renderer.Remote == null) + if(m_Ctx->Renderer()->remote()) { - QString dir = RDDialog::getExistingDirectory(this, "Choose working directory", initDir); - - if(dir != "") - ui->workDirPath->setText(dir); + // proxy = new RemoteFileProxy(m_Ctx->Renderer()); + options |= QFileDialog::DontUseNativeDialog; } - /* - else - { - VirtualOpenFileDialog remoteBrowser = new VirtualOpenFileDialog(m_Core.Renderer); - remoteBrowser.Opened += new - EventHandler(this.virtualWorkDirBrowser_Opened); - remoteBrowser.DirectoryBrowse = true; + QString dir = + RDDialog::getExistingDirectory(this, "Choose working directory", initDir, options, proxy); - remoteBrowser.ShowDialog(this); - } - */ + if(dir != "") + ui->workDirPath->setText(dir); } void CaptureDialog::on_envVarEdit_clicked() @@ -584,10 +585,8 @@ void CaptureDialog::triggerCapture() { QString exe = ui->exePath->text(); - // TODO Remote - // for non-remote captures, check the executable locally - // if(m_Core.Renderer.Remote == null) + if(!m_Ctx->Renderer()->remote()) { if(!QFileInfo::exists(exe)) { @@ -599,20 +598,16 @@ void CaptureDialog::triggerCapture() QString workingDir = ""; - // TODO Remote - // for non-remote captures, check the directory locally - // if(m_Core.Renderer.Remote == null) + if(m_Ctx->Renderer()->remote()) + { + workingDir = ui->workDirPath->text(); + } + else { if(QDir(ui->workDirPath->text()).exists()) workingDir = ui->workDirPath->text(); } - /* - else - { - workingDir = RealWorkDir; - } - */ QString cmdLine = ui->cmdline->text(); diff --git a/qrenderdoc/Windows/Dialogs/LiveCapture.cpp b/qrenderdoc/Windows/Dialogs/LiveCapture.cpp index a49132d3a..0255a3993 100644 --- a/qrenderdoc/Windows/Dialogs/LiveCapture.cpp +++ b/qrenderdoc/Windows/Dialogs/LiveCapture.cpp @@ -560,38 +560,35 @@ bool LiveCapture::checkAllowClose() return false; } - // TODO Remote - // we either have to save or delete the log. Make sure that if it's remote that we are able // to by having an active connection or replay context on that host. - /* if(suppressRemoteWarning == false && (m_Connection == NULL || !m_Connection->Connected()) && - !log->local && - (m_Core.Renderer.Remote == null || - m_Core.Renderer.Remote.Hostname != m_Host || - !m_Core.Renderer.Remote.Connected) - ) + !log->local && + (m_Ctx->Renderer()->remote() == NULL || m_Ctx->Renderer()->remote()->Hostname != m_Hostname || + !m_Ctx->Renderer()->remote()->Connected)) { - DialogResult res2 = MessageBox.Show( - String.Format("This capture is on remote host {0} and there is no active replay context on - that host.\n", m_Host) + - "Without an active replay context the capture cannot be " + (res == DialogResult.Yes ? - "saved.\n\n" : "deleted.\n\n") + - "Would you like to continue and discard this capture and any others, to be left in the - temporary folder on the remote machine?", - "No active replay context", MessageBoxButtons.YesNoCancel); + QMessageBox::StandardButton res2 = RDDialog::question( + this, tr("No active replay context"), + tr("This capture is on remote host %1 and there is no active replay context on that " + "host.\n") + .arg(m_Hostname) + + tr("Without an active replay context the capture cannot be %1.\n\n") + .arg(tr(res == QMessageBox::Yes ? "saved" : "deleted")) + + tr("Would you like to continue and discard this capture and any others, to be left " + "in the temporary folder on the remote machine?"), + RDDialog::YesNoCancel); - if(res2 == DialogResult.Yes) + if(res2 == QMessageBox::Yes) { suppressRemoteWarning = true; - res = DialogResult.No; + res = QMessageBox::No; } else { m_IgnoreThreadClosed = false; return false; } - }*/ + } if(res == QMessageBox::Yes) { @@ -613,22 +610,18 @@ void LiveCapture::openCapture(CaptureLog *log) { log->opened = true; - // TODO Remote - /* if(!log->local && - (m_Core.Renderer.Remote == null || - m_Core.Renderer.Remote.Hostname != m_Host || - !m_Core.Renderer.Remote.Connected) - ) + (m_Ctx->Renderer()->remote() == NULL || m_Ctx->Renderer()->remote()->Hostname != m_Hostname || + !m_Ctx->Renderer()->remote()->Connected)) { - RDDialog::critical(this, - tr("No active replay context"), - tr("This capture is on remote host %1 and there is no active replay context on that host.\n" \ - "You can either save the log locally, or switch to a replay context on - %1.").arg(m_Hostname)); + RDDialog::critical( + this, tr("No active replay context"), + tr("This capture is on remote host %1 and there is no active replay context on that " + "host.\n") + + tr("You can either save the log locally, or switch to a replay context on %1.") + .arg(m_Hostname)); return; } - */ m_Main->LoadLogfile(log->path, !log->saved, log->local); } @@ -672,24 +665,28 @@ bool LiveCapture::saveCapture(CaptureLog *log) } else { - // TODO Remote - /* - if(m_Core.Renderer.Remote == null || - m_Core.Renderer.Remote.Hostname != m_Host || - !m_Core.Renderer.Remote.Connected) + if(m_Ctx->Renderer()->remote() == NULL || m_Ctx->Renderer()->remote()->Hostname != m_Hostname || + !m_Ctx->Renderer()->remote()->Connected) { - MessageBox.Show( - String.Format("This capture is on remote host {0} and there is no active replay context on - that host.\n" + - "Without an active replay context the capture cannot be saved, try switching to a replay - context on {0}.\n\n", m_Host), - "No active replay context", MessageBoxButtons.OK); + RDDialog::critical(this, tr("No active replay context"), + tr("This capture is on remote host %1 and there is no active replay " + "context on that host.\n") + + tr("Without an active replay context the capture cannot be saved, " + "try switching to a replay context on %1.") + .arg(m_Hostname)); return false; } - m_Core.Renderer.CopyCaptureFromRemote(log.path, path, this); - m_Core.Renderer.DeleteCapture(log.path, false); - */ + m_Ctx->Renderer()->CopyCaptureFromRemote(log->path, path, this); + + if(!QFile::exists(path)) + { + RDDialog::critical(this, tr("Cannot save"), + tr("File couldn't be transferred from remote host")); + return false; + } + + m_Ctx->Renderer()->DeleteCapture(log->path, false); } log->saved = true; @@ -874,18 +871,15 @@ void LiveCapture::connectionClosed() { CaptureLog *log = GetLog(ui->captures->item(0)); - // TODO Remote - // only auto-open a non-local log if we are successfully connected // to this machine as a remote context - /* if(!log->local) { - if(m_Core.Renderer.Remote == null || - m_Host != m_Core.Renderer.Remote.Hostname || - !m_Core.Renderer.Remote.Connected) + if(m_Ctx->Renderer()->remote() == NULL || + m_Ctx->Renderer()->remote()->Hostname != m_Hostname || + !m_Ctx->Renderer()->remote()->Connected) return; - }*/ + } if(log->opened) return; diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 15c6dbb82..78f645d6d 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "Code/CaptureContext.h" #include "Code/QRDUtils.h" #include "PipelineState/PipelineStateViewer.h" @@ -37,6 +38,7 @@ #include "Windows/Dialogs/CaptureDialog.h" #include "Windows/Dialogs/LiveCapture.h" #include "Windows/Dialogs/SettingsDialog.h" +#include "Windows/Dialogs/SuggestRemoteDialog.h" #include "APIInspector.h" #include "BufferViewer.h" #include "ConstantBufferPreviewer.h" @@ -100,6 +102,24 @@ MainWindow::MainWindow(CaptureContext *ctx) : QMainWindow(NULL), ui(new Ui::Main QObject::connect(ui->action_Save_Layout_6, &QAction::triggered, this, &MainWindow::saveLayout_triggered); + contextChooserMenu = new QMenu(this); + + FillRemotesMenu(contextChooserMenu, true); + + contextChooser = new QToolButton(this); + contextChooser->setText(tr("Replay Context: %1").arg("Local")); + contextChooser->setIcon(QIcon(QPixmap(QString::fromUtf8(":/Resources/house.png")))); + contextChooser->setAutoRaise(true); + contextChooser->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + contextChooser->setPopupMode(QToolButton::InstantPopup); + contextChooser->setMenu(contextChooserMenu); + contextChooser->setStyleSheet("QToolButton::menu-indicator { image: none; }"); + contextChooser->setContextMenuPolicy(Qt::DefaultContextMenu); + QObject::connect(contextChooserMenu, &QMenu::aboutToShow, this, + &MainWindow::contextChooser_menuShowing); + + ui->statusBar->addWidget(contextChooser); + statusIcon = new RDLabel(this); ui->statusBar->addWidget(statusIcon); @@ -126,6 +146,11 @@ MainWindow::MainWindow(CaptureContext *ctx) : QMainWindow(NULL), ui(new Ui::Main m_MessageTick.setInterval(500); m_MessageTick.start(); + QObject::connect(&m_RemoteProbe, &QTimer::timeout, this, &MainWindow::remoteProbe); + m_RemoteProbe.setSingleShot(false); + m_RemoteProbe.setInterval(7500); + m_RemoteProbe.start(); + ui->statusBar->setStyleSheet("QStatusBar::item { border: 0px }"); SetTitle(); @@ -143,6 +168,14 @@ MainWindow::MainWindow(CaptureContext *ctx) : QMainWindow(NULL), ui(new Ui::Main bool loaded = LoadLayout(0); + LambdaThread *th = new LambdaThread([this]() { + m_Ctx->Config.AddAndroidHosts(); + for(RemoteHost *host : m_Ctx->Config.RemoteHosts) + host->CheckStatus(); + }); + th->selfDelete(true); + th->start(); + // create default layout if layout failed to load if(!loaded) { @@ -265,9 +298,9 @@ LiveCapture *MainWindow::OnCaptureTrigger(const QString &exe, const QString &wor return NULL; } - // TODO Remote - // m_Core.Renderer.Remote == NULL ? "" : m_Core.Renderer.Remote.Hostname - LiveCapture *live = new LiveCapture(m_Ctx, "", ret, this, this); + LiveCapture *live = new LiveCapture( + m_Ctx, m_Ctx->Renderer()->remote() ? m_Ctx->Renderer()->remote()->Hostname : "", ret, this, + this); ShowLiveCapture(live); return live; } @@ -315,36 +348,32 @@ void MainWindow::LoadLogfile(const QString &filename, bool temporary, bool local rdctype::str machineIdent; ReplaySupport support = eReplaySupport_Unsupported; - // TODO Remote - bool remoteReplay = - !local /*|| (m_Core.Renderer.Remote != null && m_Core.Renderer.Remote.Connected)*/; + !local || (m_Ctx->Renderer()->remote() && m_Ctx->Renderer()->remote()->Connected); if(local) { support = RENDERDOC_SupportLocalReplay(filename.toUtf8().data(), &driver, &machineIdent); // if the return value suggests remote replay, and it's not already selected, AND the user - // hasn't - // previously chosen to always replay locally without being prompted, ask if they'd prefer to - // switch to a remote context for replaying. + // hasn't previously chosen to always replay locally without being prompted, ask if they'd + // prefer to switch to a remote context for replaying. if(support == eReplaySupport_SuggestRemote && !remoteReplay && !m_Ctx->Config.AlwaysReplayLocally) { - RDDialog::information(NULL, tr("Not Implemented"), - tr("Can't suggest a remote host, replaying locally")); - /* - var dialog = new Dialogs.SuggestRemoteDialog(driver, machineIdent); + SuggestRemoteDialog dialog(ToQStr(driver), ToQStr(machineIdent), this); - FillRemotesToolStrip(dialog.RemoteItems, false); + FillRemotesMenu(dialog.remotesMenu(), false); - dialog.ShowDialog(); + dialog.remotesAdded(); - if(dialog.Result == Dialogs.SuggestRemoteDialog.SuggestRemoteResult.Cancel) + RDDialog::show(&dialog); + + if(dialog.choice() == SuggestRemoteDialog::Cancel) { return; } - else if(dialog.Result == Dialogs.SuggestRemoteDialog.SuggestRemoteResult.Remote) + else if(dialog.choice() == SuggestRemoteDialog::Remote) { // we only get back here from the dialog once the context switch has begun, // so contextChooser will have been disabled. @@ -352,27 +381,21 @@ void MainWindow::LoadLogfile(const QString &filename, bool temporary, bool local // it has finished already. Otherwise pop up a waiting dialog until it completes // one way or another, then process the result. - if(!contextChooser.Enabled) + if(!contextChooser->isEnabled()) { - ProgressPopup modal = new ProgressPopup((ModalCloseCallback)delegate - { - return contextChooser.Enabled; - }, false); - modal.SetModalText("Please Wait - Checking remote connection..."); - - modal.ShowDialog(); + ShowProgressDialog(this, tr("Please Wait - Checking remote connection..."), + [this]() { return contextChooser->isEnabled(); }); } - remoteReplay = (m_Core.Renderer.Remote != null && m_Core.Renderer.Remote.Connected); + remoteReplay = (m_Ctx->Renderer()->remote() && m_Ctx->Renderer()->remote()->Connected); if(!remoteReplay) { - string remoteMessage = "Failed to make a connection to the remote server.\n\n"; + QString remoteMessage = tr("Failed to make a connection to the remote server.\n\n"); - remoteMessage += "More information may be available in the status bar."; + remoteMessage += tr("More information may be available in the status bar."); - MessageBox.Show(remoteMessage, "Couldn't connect to remote server", - MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + RDDialog::information(this, tr("Couldn't connect to remote server"), remoteMessage); return; } } @@ -381,14 +404,13 @@ void MainWindow::LoadLogfile(const QString &filename, bool temporary, bool local // nothing to do - we just continue replaying locally // however we need to check if the user selected 'always replay locally' and // set that bit as sticky in the config - if(dialog.AlwaysReplayLocally) + if(dialog.alwaysReplayLocally()) { - m_Core.Config.AlwaysReplayLocally = true; + m_Ctx->Config.AlwaysReplayLocally = true; m_Ctx->Config.Save(); } } - */ } if(remoteReplay) @@ -436,34 +458,24 @@ void MainWindow::LoadLogfile(const QString &filename, bool temporary, bool local } else { + QString fileToLoad = filename; + if(remoteReplay && local) { - RDDialog::critical(NULL, tr("Not Implemented"), tr("Not Implemented")); - return; + fileToLoad = m_Ctx->Renderer()->CopyCaptureToRemote(filename, this); - // TODO Remote - /* - try + // deliberately leave local as true so that we keep referring to the locally saved log + + // some error + if(fileToLoad == "") { - filename = m_Ctx->Renderer.CopyCaptureToRemote(filename, this); - - // deliberately leave local as true so that we keep referring to the locally saved log - - // some error - if(filename == "") - throw new ApplicationException(); - } - catch(Exception) - { - MessageBox.Show("Couldn't copy " + filename + " to remote host for replaying", "Error - copying to remote", - MessageBoxButtons.OK, MessageBoxIcon.Error); + RDDialog::critical(NULL, tr("Error copying to remote"), + tr("Couldn't copy %1 to remote host for replaying").arg(filename)); return; } - */ } - m_Ctx->LoadLogfile(filename, origFilename, temporary, local); + m_Ctx->LoadLogfile(fileToLoad, origFilename, temporary, local); } if(!remoteReplay) @@ -529,6 +541,7 @@ bool MainWindow::PromptSaveLog() } bool success = false; + QString error; if(m_Ctx->IsLogLocal()) { @@ -536,16 +549,20 @@ bool MainWindow::PromptSaveLog() // the original path. // This ensures that if the user deletes the saved path we can still open or re-save it. success = QFile::copy(m_Ctx->LogFilename(), saveFilename); + + error = tr("Couldn't save to %1").arg(saveFilename); } else { - // TODO Remote - // m_Core.Renderer.CopyCaptureFromRemote(m_Core.LogFileName, saveFilename, this); + m_Ctx->Renderer()->CopyCaptureFromRemote(m_Ctx->LogFilename(), saveFilename, this); + success = QFile::exists(saveFilename); + + error = tr("File couldn't be transferred from remote host"); } if(!success) { - RDDialog::critical(NULL, tr("File not found"), tr("Couldn't save to %1").arg(saveFilename)); + RDDialog::critical(NULL, tr("Error Saving"), error); return false; } @@ -623,7 +640,7 @@ void MainWindow::SetTitle(const QString &filename) { QString prefix = ""; - if(m_Ctx != NULL && m_Ctx->LogLoaded()) + if(m_Ctx && m_Ctx->LogLoaded()) { prefix = QFileInfo(filename).fileName(); if(m_Ctx->APIProps().degraded) @@ -631,9 +648,8 @@ void MainWindow::SetTitle(const QString &filename) prefix += " - "; } - // TODO Remote - // if(m_Ctx != NULL && m_Ctx->Renderer.Remote != null) - // prefix += String.Format("Remote: {0} - ", m_Core.Renderer.Remote.Hostname); + if(m_Ctx && m_Ctx->Renderer()->remote()) + prefix += tr("Remote: %1 - ").arg(m_Ctx->Renderer()->remote()->Hostname); QString text = prefix + "RenderDoc "; @@ -835,6 +851,21 @@ void MainWindow::setLogHasErrors(bool errors) } } +void MainWindow::remoteProbe() +{ + if(!m_Ctx->LogLoaded() && !m_Ctx->LogLoading()) + { + for(RemoteHost *host : m_Ctx->Config.RemoteHosts) + { + // don't mess with a host we're connected to - this is handled anyway + if(host->Connected) + continue; + + host->CheckStatus(); + } + } +} + void MainWindow::messageCheck() { if(m_Ctx->LogLoaded()) @@ -845,17 +876,15 @@ void MainWindow::messageCheck() bool disconnected = false; - // TODO Remote - /* - if(me.m_Core.Renderer.Remote != null) + if(m_Ctx->Renderer()->remote()) { - bool prev = me.m_Core.Renderer.Remote.ServerRunning; + bool prev = m_Ctx->Renderer()->remote()->ServerRunning; - me.m_Core.Renderer.PingRemote(); + m_Ctx->Renderer()->PingRemote(); - if(prev != me.m_Core.Renderer.Remote.ServerRunning) + if(prev != m_Ctx->Renderer()->remote()->ServerRunning) disconnected = true; - }*/ + } GUIInvoke::call([this, disconnected, msgs] { // if we just got disconnected while replaying a log, alert the user. @@ -868,9 +897,8 @@ void MainWindow::messageCheck() "RenderDoc to reconnect and load the capture again")); } - // TODO Remote - // if(me.m_Core.Renderer.Remote != null && !me.m_Core.Renderer.Remote.ServerRunning) - // me.contextChooser.Image = global::renderdocui.Properties.Resources.cross; + if(m_Ctx->Renderer()->remote() && !m_Ctx->Renderer()->remote()->ServerRunning) + contextChooser->setIcon(QIcon(QPixmap(QString::fromUtf8(":/Resources/cross.png")))); if(!msgs.empty()) { @@ -887,6 +915,217 @@ void MainWindow::messageCheck() }); }); } + else if(!m_Ctx->LogLoaded() && !m_Ctx->LogLoading()) + { + if(m_Ctx->Renderer()->remote()) + m_Ctx->Renderer()->PingRemote(); + + GUIInvoke::call([this]() { + if(m_Ctx->Renderer()->remote() && !m_Ctx->Renderer()->remote()->ServerRunning) + { + contextChooser->setIcon(QIcon(QPixmap(QString::fromUtf8(":/Resources/cross.png")))); + contextChooser->setText(tr("Replay Context: %1").arg("Local")); + statusText->setText( + tr("Remote server disconnected. To attempt to reconnect please select it again.")); + + m_Ctx->Renderer()->DisconnectFromRemoteServer(); + } + }); + } +} + +void MainWindow::FillRemotesMenu(QMenu *menu, bool includeLocalhost) +{ + menu->clear(); + + QIcon tick(QPixmap(QString::fromUtf8(":/Resources/tick.png"))); + QIcon cross(QPixmap(QString::fromUtf8(":/Resources/cross.png"))); + + for(int i = 0; i < m_Ctx->Config.RemoteHosts.count(); i++) + { + RemoteHost *host = m_Ctx->Config.RemoteHosts[i]; + + // add localhost at the end + if(host->Hostname == "localhost") + continue; + + QAction *action = new QAction(menu); + + action->setIcon(host->ServerRunning && !host->VersionMismatch ? tick : cross); + if(host->Connected) + action->setText(tr("%1 (Connected)").arg(host->Hostname)); + else if(host->ServerRunning && host->VersionMismatch) + action->setText(tr("%1 (Bad Version)").arg(host->Hostname)); + else if(host->ServerRunning && host->Busy) + action->setText(tr("%1 (Busy)").arg(host->Hostname)); + else if(host->ServerRunning) + action->setText(tr("%1 (Online)").arg(host->Hostname)); + else + action->setText(tr("%1 (Offline)").arg(host->Hostname)); + QObject::connect(action, &QAction::triggered, this, &MainWindow::switchContext); + action->setData(i); + + // don't allow switching to the connected host + if(host->Connected) + action->setEnabled(false); + + menu->addAction(action); + } + + if(includeLocalhost) + { + QAction *localContext = new QAction(menu); + + localContext->setText("Local"); + localContext->setIcon(QIcon(QPixmap(QString::fromUtf8(":/Resources/house.png")))); + + QObject::connect(localContext, &QAction::triggered, this, &MainWindow::switchContext); + localContext->setData(-1); + + menu->addAction(localContext); + } +} + +void MainWindow::switchContext() +{ + QAction *item = qobject_cast(QObject::sender()); + + if(!item) + return; + + bool ok = false; + int hostIdx = item->data().toInt(&ok); + + if(!ok) + return; + + RemoteHost *host = NULL; + + if(hostIdx >= 0 && hostIdx < m_Ctx->Config.RemoteHosts.count()) + { + host = m_Ctx->Config.RemoteHosts[hostIdx]; + } + + for(LiveCapture *live : m_LiveCaptures) + { + // 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 && live->hostname() == host->Hostname) + continue; + + if(!live->checkAllowClose()) + return; + } + + if(!PromptCloseLog()) + return; + + for(LiveCapture *live : m_LiveCaptures) + { + // 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 && live->hostname() == host->Hostname) + continue; + + live->cleanItems(); + live->close(); + } + + m_Ctx->Renderer()->DisconnectFromRemoteServer(); + + if(!host) + { + contextChooser->setIcon(QIcon(QPixmap(QString::fromUtf8(":/Resources/house.png")))); + contextChooser->setText(tr("Replay Context: %1").arg("Local")); + + ui->action_Inject_into_Process->setEnabled(true); + + statusText->setText(""); + + SetTitle(); + } + else + { + contextChooser->setText(tr("Replay Context: %1").arg(host->Hostname)); + contextChooser->setIcon(host->ServerRunning + ? QIcon(QPixmap(QString::fromUtf8(":/Resources/connect.png"))) + : QIcon(QPixmap(QString::fromUtf8(":/Resources/disconnect.png")))); + + // disable until checking is done + contextChooser->setEnabled(false); + + ui->action_Inject_into_Process->setEnabled(false); + + SetTitle(); + + statusText->setText(tr("Checking remote server status...")); + + LambdaThread *th = new LambdaThread([this, host]() { + // see if the server is up + host->CheckStatus(); + + if(!host->ServerRunning && host->RunCommand != "") + { + GUIInvoke::call([this]() { statusText->setText(tr("Running remote server command...")); }); + + host->Launch(); + + // check if it's running now + host->CheckStatus(); + } + + ReplayCreateStatus status = eReplayCreate_Success; + + if(host->ServerRunning && !host->Busy) + { + status = m_Ctx->Renderer()->ConnectToRemoteServer(host); + } + + GUIInvoke::call([this, host, status]() { + contextChooser->setIcon( + host->ServerRunning && !host->Busy + ? QIcon(QPixmap(QString::fromUtf8(":/Resources/connect.png"))) + : QIcon(QPixmap(QString::fromUtf8(":/Resources/disconnect.png")))); + + if(status != eReplayCreate_Success) + { + contextChooser->setIcon(QIcon(QPixmap(QString::fromUtf8(":/Resources/cross.png")))); + contextChooser->setText(tr("Replay Context: %1").arg("Local")); + statusText->setText(tr("Connection failed: %1").arg(ToQStr(status))); + } + else if(host->VersionMismatch) + { + statusText->setText(tr("Remote server is not running RenderDoc %1").arg(Version::string())); + } + else if(host->Busy) + { + statusText->setText(tr("Remote server in use elsewhere")); + } + else if(host->ServerRunning) + { + statusText->setText(tr("Remote server ready")); + } + else + { + if(host->RunCommand != "") + statusText->setText(tr("Remote server not running or failed to start")); + else + statusText->setText(tr("Remote server not running - no start command configured")); + } + + contextChooser->setEnabled(true); + }); + }); + th->selfDelete(true); + th->start(); + } +} + +void MainWindow::contextChooser_menuShowing() +{ + FillRemotesMenu(contextChooserMenu, true); } void MainWindow::statusDoubleClicked() @@ -896,9 +1135,8 @@ void MainWindow::statusDoubleClicked() void MainWindow::OnLogfileLoaded() { - // TODO Remote // don't allow changing context while log is open - // contextChooser.Enabled = false; + contextChooser->setEnabled(false); statusProgress->setVisible(false); @@ -925,8 +1163,7 @@ void MainWindow::OnLogfileLoaded() void MainWindow::OnLogfileClosed() { - // TODO Remote - // contextChooser.Enabled = true; + contextChooser->setEnabled(true); statusText->setText(""); statusIcon->setPixmap(QPixmap()); @@ -938,15 +1175,13 @@ void MainWindow::OnLogfileClosed() SetTitle(); // if the remote sever disconnected during log replay, resort back to a 'disconnected' state - // TODO Remote - /* - if(m_Core.Renderer.Remote != null && !m_Core.Renderer.Remote.ServerRunning) + if(m_Ctx->Renderer()->remote() && !m_Ctx->Renderer()->remote()->ServerRunning) { - statusText.Text = "Remote server disconnected. To attempt to reconnect please select it again."; - contextChooser.Text = "Replay Context: Local"; - m_Core.Renderer.DisconnectFromRemoteServer(); + statusText->setText( + tr("Remote server disconnected. To attempt to reconnect please select it again.")); + contextChooser->setText(tr("Replay Context: %1").arg("Local")); + m_Ctx->Renderer()->DisconnectFromRemoteServer(); } - */ } void MainWindow::OnEventChanged(uint32_t eventID) @@ -1091,6 +1326,11 @@ void MainWindow::on_action_Resolve_Symbols_triggered() m_Ctx->apiInspector()->on_apiEvents_itemSelectionChanged(); } +void MainWindow::on_action_Start_Android_Remote_Server_triggered() +{ + RENDERDOC_StartAndroidRemoteServer(); +} + void MainWindow::on_action_Settings_triggered() { SettingsDialog about(m_Ctx, this); diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index 98d3756ce..529047d47 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -38,6 +38,7 @@ class MainWindow; class RDLabel; class QMimeData; class QProgressBar; +class QToolButton; class CaptureDialog; class LiveCapture; @@ -97,13 +98,17 @@ private slots: void on_action_Statistics_Viewer_triggered(); void on_action_Inject_into_Process_triggered(); void on_action_Resolve_Symbols_triggered(); + void on_action_Start_Android_Remote_Server_triggered(); void on_action_Settings_triggered(); // manual slots void saveLayout_triggered(); void loadLayout_triggered(); void messageCheck(); + void remoteProbe(); void statusDoubleClicked(); + void switchContext(); + void contextChooser_menuShowing(); private: void closeEvent(QCloseEvent *event) override; @@ -124,8 +129,11 @@ private: RDLabel *statusIcon; RDLabel *statusText; QProgressBar *statusProgress; + QMenu *contextChooserMenu; + QToolButton *contextChooser; QTimer m_MessageTick; + QTimer m_RemoteProbe; bool m_messageAlternate = false; @@ -155,4 +163,6 @@ private: void LoadSaveLayout(QAction *action, bool save); bool LoadLayout(int layout); bool SaveLayout(int layout); + + void FillRemotesMenu(QMenu *menu, bool includeLocalhost); };