From b49c6907ffd785b75876b80534ef980f46260c2c Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 18 Sep 2024 17:03:28 +0200 Subject: [PATCH] vkconfig3: Implement application launcher Change-Id: I4a41f6e8270b0c27f8c890455c4ee795343c38a5 --- vkconfig_core/configuration.cpp | 26 -- .../configurations/3.0.0/Frame Capture.json | 2 +- vkconfig_core/executable_manager.cpp | 50 ++- vkconfig_core/executable_manager.h | 6 +- .../test/test_executable_manager.cpp | 8 +- vkconfig_core/util.cpp | 28 ++ vkconfig_core/util.h | 4 + vkconfig_gui/mainwindow.cpp | 180 +--------- vkconfig_gui/mainwindow.h | 34 -- vkconfig_gui/mainwindow.ui | 48 ++- vkconfig_gui/tab_applications.cpp | 320 ++++++++++++++---- vkconfig_gui/tab_applications.h | 21 +- vkconfig_gui/tab_configurations.cpp | 2 +- 13 files changed, 398 insertions(+), 331 deletions(-) diff --git a/vkconfig_core/configuration.cpp b/vkconfig_core/configuration.cpp index 8947aa7d39..7fb0acfb20 100644 --- a/vkconfig_core/configuration.cpp +++ b/vkconfig_core/configuration.cpp @@ -494,32 +494,6 @@ bool Configuration::IsBuiltIn() const { static const size_t NOT_FOUND = static_cast(-1); -static std::size_t ExtractDuplicateNumber(const std::string& configuration_name) { - const std::size_t name_open = configuration_name.find_last_of("("); - if (name_open == NOT_FOUND) { - return NOT_FOUND; - } - - const std::size_t name_close = configuration_name.find_last_of(")"); - if (name_close == NOT_FOUND) { - return NOT_FOUND; - } - - const std::string number = configuration_name.substr(name_open + 1, name_close - (name_open + 1)); - if (!IsNumber(number)) { - return NOT_FOUND; - } - - return std::stoi(number); -} - -static std::string ExtractDuplicateBaseName(const std::string& configuration_name) { - assert(ExtractDuplicateNumber(configuration_name) != NOT_FOUND); - const std::size_t found = configuration_name.find_last_of("("); - assert(found != NOT_FOUND); - return configuration_name.substr(0, found - 1); -} - std::string MakeConfigurationName(const std::vector& configurations, const std::string& configuration_name) { const std::string key = configuration_name; const std::string base_name = ExtractDuplicateNumber(key) != NOT_FOUND ? ExtractDuplicateBaseName(key) : key; diff --git a/vkconfig_core/configurations/3.0.0/Frame Capture.json b/vkconfig_core/configurations/3.0.0/Frame Capture.json index f0503f0b5b..848c779df4 100644 --- a/vkconfig_core/configurations/3.0.0/Frame Capture.json +++ b/vkconfig_core/configurations/3.0.0/Frame Capture.json @@ -16,7 +16,7 @@ { "key": "capture_file", "type": "SAVE_FILE", - "value": "${VK_LOCAL}/gfxrecon_capture.gfxr" + "value": "${VK_HOME}/gfxrecon_capture.gfxr" } ], "control": "on", diff --git a/vkconfig_core/executable_manager.cpp b/vkconfig_core/executable_manager.cpp index 03858838a9..36c59c0304 100644 --- a/vkconfig_core/executable_manager.cpp +++ b/vkconfig_core/executable_manager.cpp @@ -21,6 +21,7 @@ #include "executable_manager.h" #include "version.h" #include "type_platform.h" +#include "util.h" #include #include @@ -57,6 +58,29 @@ bool ExecutableManager::Empty() const { return this->data.empty(); } std::size_t ExecutableManager::Size() const { return this->data.size(); } +static const size_t NOT_FOUND = static_cast(-1); + +std::string ExecutableManager::MakeOptionsName(const std::string& name) const { + const std::string key = name; + const std::string base_name = ExtractDuplicateNumber(key) != NOT_FOUND ? ExtractDuplicateBaseName(key) : key; + + const Executable* executable = GetActiveExecutable(); + + std::size_t max_duplicate = 0; + for (std::size_t i = 0, n = executable->options.size(); i < n; ++i) { + const std::string& search_name = executable->options[i].label; + + if (search_name.compare(0, base_name.length(), base_name) != 0) { + continue; + } + + const std::size_t found_number = ExtractDuplicateNumber(search_name); + max_duplicate = std::max(max_duplicate, found_number != NOT_FOUND ? found_number : 1); + } + + return base_name + (max_duplicate > 0 ? format(" (%d)", max_duplicate + 1).c_str() : ""); +} + bool ExecutableManager::Load(const QJsonObject& json_root_object) { // applications json object const QJsonObject& json_executables_object = json_root_object.value("executables").toObject(); @@ -88,6 +112,9 @@ bool ExecutableManager::Load(const QJsonObject& json_root_object) { ExecutableOptions executable_options; executable_options.label = json_options_object.value("label").toString().toStdString(); + executable_options.layers_mode = + GetLayersMode(json_options_object.value("layers_mode").toString().toStdString().c_str()); + executable_options.configuration = json_options_object.value("configuration").toString().toStdString(); executable_options.working_folder = json_options_object.value("working_folder").toString().toStdString(); const QJsonArray& json_command_lines_array = json_options_object.value("arguments").toArray(); @@ -165,9 +192,14 @@ bool ExecutableManager::Save(QJsonObject& json_root_object) const { return true; } -void ExecutableManager::SelectActiveExecutable(std::size_t executable_index) { - assert(executable_index < this->data.size()); - this->active_executable = this->data[executable_index].path; +void ExecutableManager::SetActiveExecutable(int executable_index) { + assert(executable_index < static_cast(this->data.size())); + + if (executable_index < 0) { + this->active_executable.Clear(); + } else { + this->active_executable = this->data[executable_index].path; + } } int ExecutableManager::GetActiveExecutableIndex() const { @@ -177,7 +209,6 @@ int ExecutableManager::GetActiveExecutableIndex() const { } } - assert(0); return -1; // Not found, but the list is present, so return the first item. } @@ -222,6 +253,13 @@ bool ExecutableManager::RemoveExecutable(std::size_t executable_index) { } std::swap(this->data, new_executables); + + if (this->data.empty()) { + this->active_executable.Clear(); + } else { + this->active_executable = this->data[0].path; + } + return true; } @@ -376,12 +414,14 @@ Executable ExecutableManager::CreateDefaultExecutable(const DefaultExecutable& d ExecutableOptions options; options.label = "Default"; options.working_folder = default_paths.working_folder; + options.args.push_back(default_executable.arguments); + // On all operating systems, but Windows we keep running into problems with this ending up // somewhere the user isn't allowed to create and write files. For consistncy sake, the log // initially will be set to the users home folder across all OS's. This is highly visible // in the application launcher and should not present a usability issue. The developer can // easily change this later to anywhere they like. - options.log_file = std::string("${VK_LOCAL}") + default_executable.key + ".txt"; + options.log_file = std::string("${VK_HOME}") + default_executable.key + ".txt"; Executable executable; executable.path = Path(default_paths.executable_path.AbsolutePath(), true); diff --git a/vkconfig_core/executable_manager.h b/vkconfig_core/executable_manager.h index 89ef872a28..2c1d36247e 100644 --- a/vkconfig_core/executable_manager.h +++ b/vkconfig_core/executable_manager.h @@ -39,7 +39,7 @@ struct DefaultPath { }; struct ExecutableOptions { - std::string label = "Default"; + std::string label = "Default Options"; LayersMode layers_mode = LAYERS_CONTROLLED_BY_APPLICATIONS; std::string configuration = "Validation"; Path working_folder; @@ -68,7 +68,9 @@ class ExecutableManager : public Serialize { bool Empty() const; std::size_t Size() const; - void SelectActiveExecutable(std::size_t executable_index); + std::string MakeOptionsName(const std::string& name) const; + + void SetActiveExecutable(int executable_index); int GetActiveExecutableIndex() const; bool AppendExecutable(const Executable& executable); bool AppendExecutable(const Path& executable_path); diff --git a/vkconfig_core/test/test_executable_manager.cpp b/vkconfig_core/test/test_executable_manager.cpp index 8e0a5e9e74..e23be1a6b9 100644 --- a/vkconfig_core/test/test_executable_manager.cpp +++ b/vkconfig_core/test/test_executable_manager.cpp @@ -38,10 +38,10 @@ TEST(test_executable_manager, reset_default_applications_sdk_found) { // Make sure the variable are not replaced EXPECT_TRUE(executables[0].path.RelativePath().find("${VULKAN_SDK}") != std::string::npos); EXPECT_TRUE(executables[0].options[0].working_folder.RelativePath().find("${VULKAN_SDK}") != std::string::npos); - EXPECT_TRUE(executables[0].options[0].log_file.RelativePath().find("${VK_LOCAL}") != std::string::npos); + EXPECT_TRUE(executables[0].options[0].log_file.RelativePath().find("${VK_HOME}") != std::string::npos); EXPECT_TRUE(executables[1].path.RelativePath().find("${VULKAN_SDK}") != std::string::npos); EXPECT_TRUE(executables[1].options[0].working_folder.RelativePath().find("${VULKAN_SDK}") != std::string::npos); - EXPECT_TRUE(executables[1].options[0].log_file.RelativePath().find("${VK_LOCAL}") != std::string::npos); + EXPECT_TRUE(executables[1].options[0].log_file.RelativePath().find("${VK_HOME}") != std::string::npos); } } @@ -58,10 +58,10 @@ TEST(test_executable_manager, reset_default_applications_no_sdk) { // Make sure the variable are not replaced EXPECT_TRUE(executables[0].path.RelativePath().find("vkcube") != std::string::npos); EXPECT_TRUE(executables[0].options[0].working_folder.RelativePath().find(".") != std::string::npos); - EXPECT_TRUE(executables[0].options[0].log_file.RelativePath().find("${VK_LOCAL}") != std::string::npos); + EXPECT_TRUE(executables[0].options[0].log_file.RelativePath().find("${VK_HOME}") != std::string::npos); EXPECT_TRUE(executables[1].path.RelativePath().find("vkcubepp") != std::string::npos); EXPECT_TRUE(executables[1].options[0].working_folder.RelativePath().find(".") != std::string::npos); - EXPECT_TRUE(executables[1].options[0].log_file.RelativePath().find("${VK_LOCAL}") != std::string::npos); + EXPECT_TRUE(executables[1].options[0].log_file.RelativePath().find("${VK_HOME}") != std::string::npos); } TEST(test_executable_manager, remove_missing_applications) { diff --git a/vkconfig_core/util.cpp b/vkconfig_core/util.cpp index 6679392b77..7e5a1c5350 100644 --- a/vkconfig_core/util.cpp +++ b/vkconfig_core/util.cpp @@ -336,3 +336,31 @@ std::string GetLayerSettingPrefix(const std::string& key) { result.remove("VK_LAYER_"); return ToLowerCase(result.toStdString()) + "."; } + +static const size_t NOT_FOUND = static_cast(-1); + +std::size_t ExtractDuplicateNumber(const std::string& name) { + const std::size_t name_open = name.find_last_of("("); + if (name_open == NOT_FOUND) { + return NOT_FOUND; + } + + const std::size_t name_close = name.find_last_of(")"); + if (name_close == NOT_FOUND) { + return NOT_FOUND; + } + + const std::string number = name.substr(name_open + 1, name_close - (name_open + 1)); + if (!IsNumber(number)) { + return NOT_FOUND; + } + + return std::stoi(number); +} + +std::string ExtractDuplicateBaseName(const std::string& name) { + assert(ExtractDuplicateNumber(name) != NOT_FOUND); + const std::size_t found = name.find_last_of("("); + assert(found != NOT_FOUND); + return name.substr(0, found - 1); +} diff --git a/vkconfig_core/util.h b/vkconfig_core/util.h index 074ae94f03..602fa9e822 100644 --- a/vkconfig_core/util.h +++ b/vkconfig_core/util.h @@ -156,3 +156,7 @@ std::vector GetVector(const T& value) { result.push_back(value); return result; } + +std::size_t ExtractDuplicateNumber(const std::string& name); + +std::string ExtractDuplicateBaseName(const std::string& name); diff --git a/vkconfig_gui/mainwindow.cpp b/vkconfig_gui/mainwindow.cpp index 0f96e6535e..1a74b9334a 100644 --- a/vkconfig_gui/mainwindow.cpp +++ b/vkconfig_gui/mainwindow.cpp @@ -51,7 +51,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), - _launch_application(nullptr), _tray_icon(nullptr), _tray_icon_menu(nullptr), _tray_restore_action(nullptr), @@ -103,7 +102,7 @@ MainWindow::MainWindow(QWidget *parent) this->UpdateUI(); } -MainWindow::~MainWindow() { this->ResetLaunchApplication(); } +MainWindow::~MainWindow() {} void MainWindow::InitTray() { if (QSystemTrayIcon::isSystemTrayAvailable()) { @@ -277,8 +276,6 @@ void MainWindow::UpdateUI() { ui->configurations_list->blockSignals(true); const bool has_application_list = !configurator.executables.GetExecutables().empty(); - ui->push_button_launcher->setText(_launch_application ? "Terminate" : "Launch"); - ui->check_box_clear_on_launch->setChecked(configurator.executables.launcher_clear_on_launch); // Mode states // this->UpdateUI_Status(); @@ -289,10 +286,6 @@ void MainWindow::UpdateUI() { --check_recurse; } -void MainWindow::on_check_box_clear_on_launch_clicked() { - Configurator::Get().executables.launcher_clear_on_launch = ui->check_box_clear_on_launch->isChecked(); -} - void MainWindow::toolsResetToDefault(bool checked) { (void)checked; @@ -410,11 +403,6 @@ void MainWindow::closeEvent(QCloseEvent *event) { } } - // If a child process is still running, destroy it - if (_launch_application) { - ResetLaunchApplication(); - } - this->tabs[this->ui->tab_widget->currentIndex()]->CleanUI(); QSettings settings("LunarG", VKCONFIG_SHORT_NAME); @@ -446,13 +434,6 @@ void MainWindow::showEvent(QShowEvent *event) { event->accept(); } -// Clear the browser window -void MainWindow::on_push_button_clear_log_clicked() { - ui->log_browser->clear(); - ui->log_browser->update(); - ui->push_button_clear_log->setEnabled(false); -} - bool MainWindow::eventFilter(QObject *target, QEvent *event) { if (this->tabs[this->ui->tab_widget->currentIndex()] == nullptr) { return false; @@ -461,166 +442,7 @@ bool MainWindow::eventFilter(QObject *target, QEvent *event) { return this->tabs[this->ui->tab_widget->currentIndex()]->EventFilter(target, event); } -void MainWindow::ResetLaunchApplication() { - if (_launch_application) { - _launch_application->kill(); - _launch_application->waitForFinished(); - _launch_application.reset(); - - UpdateUI(); - } -} - -QStringList MainWindow::BuildEnvVariables() const { - Configurator &configurator = Configurator::Get(); - - QStringList env = QProcess::systemEnvironment(); - env << (QString("VK_LOADER_DEBUG=") + ::GetLogString(configurator.environment.GetLoaderMessageFlags()).c_str()); - return env; -} - void MainWindow::on_tab_widget_currentChanged(int index) { assert(index >= 0); this->tabs[index]->UpdateUI(UPDATE_REBUILD_UI); } - -void MainWindow::on_push_button_launcher_clicked() { - // Are we already monitoring a running app? If so, terminate it - if (_launch_application != nullptr) { - ResetLaunchApplication(); - return; - } - - // We are logging, let's add that we've launched a new application - std::string launch_log = "Launching Vulkan Application:\n"; - - Configurator &configurator = Configurator::Get(); - const Executable *active_executable = configurator.executables.GetActiveExecutable(); - - assert(!active_executable->path.Empty()); - launch_log += format("- Executable: %s\n", active_executable->path.AbsolutePath().c_str()); - if (!active_executable->path.Exists()) { - Alert::PathInvalid( - active_executable->path, - format("The '%s' application will fail to launch.", active_executable->path.AbsolutePath().c_str()).c_str()); - } - - const ExecutableOptions *options = active_executable->GetActiveOptions(); - - launch_log += format("- Working Directory: %s\n", options->working_folder.AbsolutePath().c_str()); - if (!options->working_folder.Exists()) { - Alert::PathInvalid( - options->working_folder, - format("The '%s' application will fail to launch.", active_executable->path.AbsolutePath().c_str()).c_str()); - } - /* - if (!_launcher_arguments->text().isEmpty()) { - launch_log += format("- Command-line Arguments: %s\n", _launcher_arguments->text().toStdString().c_str()); - } - */ - if (!options->log_file.Empty()) { - launch_log += format("- Log file: %s\n", options->log_file.AbsolutePath().c_str()); - } - - if (!options->log_file.Empty()) { - // Start logging - // Make sure the log file is not already opened. This can occur if the - // launched application is closed from the applicaiton. - if (!_log_file.isOpen()) { - _log_file.setFileName(options->log_file.AbsolutePath().c_str()); - - // Open and append, or open and truncate? - QIODevice::OpenMode mode = QIODevice::WriteOnly | QIODevice::Text; - if (!ui->check_box_clear_on_launch->isChecked()) mode |= QIODevice::Append; - - if (!_log_file.open(mode)) { - Alert::LogFileFailed(); - } - } - } - - if (ui->check_box_clear_on_launch->isChecked()) ui->log_browser->clear(); - Log(launch_log.c_str()); - - // Launch the test application - _launch_application.reset(new QProcess(this)); - connect(_launch_application.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(standardOutputAvailable())); - connect(_launch_application.get(), SIGNAL(readyReadStandardError()), this, SLOT(errorOutputAvailable())); - connect(_launch_application.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, - SLOT(processClosed(int, QProcess::ExitStatus))); - - //_launch_application->setProgram(ui->edit_executable->text()); - //_launch_application->setWorkingDirectory(ui->edit_dir->text()); - //_launch_application->setEnvironment(BuildEnvVariables() + ui->edit_env->text().split(",")); - - if (!options->args.empty()) { - const QStringList args = ConvertString(options->args); - _launch_application->setArguments(args); - } - - _launch_application->start(QIODevice::ReadOnly | QIODevice::Unbuffered); - _launch_application->setProcessChannelMode(QProcess::MergedChannels); - _launch_application->closeWriteChannel(); - - // Wait... did we start? Give it 4 seconds, more than enough time - if (!_launch_application->waitForStarted(4000)) { - _launch_application->deleteLater(); - _launch_application = nullptr; - - const std::string failed_log = std::string("Failed to launch ") + active_executable->path.AbsolutePath().c_str() + "!\n"; - Log(failed_log); - } - - UpdateUI(); -} - -/// The process we are following is closed. We don't actually care about the -/// exit status/code, we just need to know to destroy the QProcess object -/// and set it back to nullptr so that we know we can launch a new app. -/// Also, if we are logging, it's time to close the log file. -void MainWindow::processClosed(int exit_code, QProcess::ExitStatus status) { - (void)exit_code; - (void)status; - - assert(_launch_application); - - disconnect(_launch_application.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, - SLOT(processClosed(int, QProcess::ExitStatus))); - disconnect(_launch_application.get(), SIGNAL(readyReadStandardError()), this, SLOT(errorOutputAvailable())); - disconnect(_launch_application.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(standardOutputAvailable())); - - Log("Process terminated"); - - if (_log_file.isOpen()) { - _log_file.close(); - } - - ResetLaunchApplication(); -} - -/// This signal get's raised whenever the spawned Vulkan appliction writes -/// to stdout and there is data to be read. The layers flush after all stdout -/// writes, so we should see layer output here in realtime, as we just read -/// the string and append it to the text browser. -/// If a log file is open, we also write the output to the log. -void MainWindow::standardOutputAvailable() { - if (_launch_application) { - Log(_launch_application->readAllStandardOutput().toStdString()); - } -} - -void MainWindow::errorOutputAvailable() { - if (_launch_application) { - Log(_launch_application->readAllStandardError().toStdString()); - } -} - -void MainWindow::Log(const std::string &log) { - ui->log_browser->setPlainText(ui->log_browser->toPlainText() + "\n" + log.c_str()); - ui->push_button_clear_log->setEnabled(true); - - if (_log_file.isOpen()) { - _log_file.write(log.c_str(), log.size()); - _log_file.flush(); - } -} diff --git a/vkconfig_gui/mainwindow.h b/vkconfig_gui/mainwindow.h index 3b389a644a..0ae1e4054b 100644 --- a/vkconfig_gui/mainwindow.h +++ b/vkconfig_gui/mainwindow.h @@ -31,16 +31,11 @@ #include "ui_mainwindow.h" -#include #include -#include -#include #include #include -#include #include -#include #include enum Tool { TOOL_VULKAN_INFO, TOOL_VULKAN_INSTALL }; @@ -55,9 +50,6 @@ class MainWindow : public QMainWindow { void UpdateUI(); private: - std::unique_ptr _launch_application; // Keeps track of the monitored app - QFile _log_file; // Log file for layer output - void closeEvent(QCloseEvent *event) override; void showEvent(QShowEvent *event) override; bool eventFilter(QObject *target, QEvent *event) override; @@ -65,8 +57,6 @@ class MainWindow : public QMainWindow { std::unique_ptr vk_info_dialog; std::unique_ptr vk_installation_dialog; - void Log(const std::string &log); - QSystemTrayIcon *_tray_icon; QMenu *_tray_icon_menu; QAction *_tray_restore_action; @@ -96,28 +86,6 @@ class MainWindow : public QMainWindow { void on_tab_widget_currentChanged(int index); - // Applications tabs - void on_push_button_launcher_clicked(); - void on_push_button_clear_log_clicked(); - void on_check_box_clear_on_launch_clicked(); - - void standardOutputAvailable(); // stdout output is available - void errorOutputAvailable(); // Layeroutput is available - void processClosed(int exitCode, QProcess::ExitStatus status); // app died - - /* - void launchItemExpanded(QTreeWidgetItem *item); - void launchItemCollapsed(QTreeWidgetItem *item); - void launchItemChanged(int index); - void launchSetLogFile(); - void launchSetExecutable(); - void launchSetWorkingFolder(); - void launchChangeLogFile(const QString &new_text); - void launchChangeExecutable(const QString &new_text); - void launchChangeWorkingFolder(const QString &new_text); - void launchArgsEdited(const QString &new_text); - */ - void UpdateUI_Status(); private: @@ -125,9 +93,7 @@ class MainWindow : public QMainWindow { MainWindow &operator=(const MainWindow &) = delete; void InitTray(); - void ResetLaunchApplication(); void StartTool(Tool tool); - QStringList BuildEnvVariables() const; std::shared_ptr ui; std::array, TAB_COUNT> tabs; diff --git a/vkconfig_gui/mainwindow.ui b/vkconfig_gui/mainwindow.ui index 8cf299ae5c..f59315e320 100644 --- a/vkconfig_gui/mainwindow.ui +++ b/vkconfig_gui/mainwindow.ui @@ -876,7 +876,7 @@ - + 0 @@ -944,7 +944,7 @@ - + 0 @@ -971,7 +971,7 @@ - + 0 @@ -991,7 +991,14 @@ 0 - + + + + 0 + 0 + + + @@ -1046,7 +1053,14 @@ - + + + + 0 + 0 + + + @@ -1085,7 +1099,7 @@ 0 - + 16777215 @@ -1095,7 +1109,7 @@ - + 32 @@ -1125,7 +1139,7 @@ - + 32 @@ -1189,7 +1203,7 @@ - + Arial @@ -1202,7 +1216,7 @@ - + Arial @@ -1218,7 +1232,7 @@ - + Arial @@ -1285,7 +1299,7 @@ - VK_LOCAL + VK_HOME @@ -1363,7 +1377,7 @@ - :/resourcefiles/lunarg_logo.png + :/resourcefiles/lunarg_logo.png Qt::TextBrowserInteraction @@ -1382,7 +1396,7 @@ - :/resourcefiles/qt_logo.png + :/resourcefiles/qt_logo.png @@ -1649,7 +1663,7 @@ - :/resourcefiles/vulkan_configurator.png + :/resourcefiles/vulkan_configurator.png true @@ -1759,8 +1773,6 @@ - - - + diff --git a/vkconfig_gui/tab_applications.cpp b/vkconfig_gui/tab_applications.cpp index d62b4a1a6e..0bec3b0802 100644 --- a/vkconfig_gui/tab_applications.cpp +++ b/vkconfig_gui/tab_applications.cpp @@ -22,43 +22,12 @@ #include "mainwindow.h" #include "../vkconfig_core/configurator.h" +#include "../vkconfig_core/alert.h" #include -/* - case PATH_EXECUTABLE: { - static const char* TABLE[] = { - "Applications (*.exe)", // PLATFORM_WINDOWS - "Applications (*)", // PLATFORM_LINUX - "Applications (*.app, *)", // PLATFORM_MACOS - "N/A" // PLATFORM_ANDROID - }; - static_assert(std::size(TABLE) == PLATFORM_COUNT, - "The tranlation table size doesn't match the enum number of elements"); - - const std::string filter = TABLE[VKC_PLATFORM]; - const std::string selected_path = QFileDialog::getOpenFileName(parent, "Select a Vulkan Executable...", - suggested_path.AbsolutePath().c_str(), filter.c_str()) - .toStdString(); - if (selected_path.empty()) // The user cancelled - return Path(""); - - SetPath(path, QFileInfo(selected_path.c_str()).absolutePath().toStdString()); - return GetFullPath(path, QFileInfo(selected_path.c_str()).fileName().toStdString()); - } - case PATH_WORKING_DIR: { - const std::string selected_path = - QFileDialog::getExistingDirectory(parent, "Set Working Folder To...", suggested_path.AbsolutePath().c_str()) - .toStdString(); - if (selected_path.empty()) // The user cancelled - return Path(""); - - SetPath(path, selected_path); - return Path(GetPath(path)); - } -*/ - -TabApplications::TabApplications(MainWindow &window, std::shared_ptr ui) : Tab(TAB_APPLICATIONS, window, ui) { +TabApplications::TabApplications(MainWindow &window, std::shared_ptr ui) + : Tab(TAB_APPLICATIONS, window, ui), _launch_application(nullptr) { this->connect(this->ui->applications_remove_application_pushButton, SIGNAL(pressed()), this, SLOT(on_applications_remove_application_pushButton_pressed())); this->connect(this->ui->applications_append_application_pushButton, SIGNAL(pressed()), this, @@ -70,8 +39,8 @@ TabApplications::TabApplications(MainWindow &window, std::shared_ptrconnect(this->ui->applications_options_remove_pushButton, SIGNAL(pressed()), this, SLOT(on_applications_options_remove_pushButton_pressed())); - this->connect(this->ui->applications_options_duplicate_pushButton, SIGNAL(pressed()), this, - SLOT(on_applications_options_duplicate_pushButton_pressed())); + this->connect(this->ui->applications_options_append_pushButton, SIGNAL(pressed()), this, + SLOT(on_applications_options_append_pushButton_pressed())); this->connect(this->ui->applications_options_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(on_applications_options_comboBox_activated(int))); this->connect(this->ui->applications_options_comboBox, SIGNAL(editTextChanged(QString)), this, @@ -81,9 +50,16 @@ TabApplications::TabApplications(MainWindow &window, std::shared_ptrconnect(this->ui->applications_configuration_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(on_applications_configuration_comboBox_activated(int))); + + this->connect(this->ui->applications_push_button_launcher, SIGNAL(pressed()), this, + SLOT(on_applications_launcher_pushButton_pressed())); + this->connect(this->ui->applications_push_button_clear_log, SIGNAL(pressed()), this, + SLOT(on_applications_clear_log_pushButton_pressed())); + this->connect(this->ui->applications_check_box_clear_on_launch, SIGNAL(pressed()), this, + SLOT(on_applications_check_box_clear_on_launch_pressed())); } -TabApplications::~TabApplications() {} +TabApplications::~TabApplications() { this->ResetLaunchApplication(); } void TabApplications::UpdateUI(UpdateUIMode mode) { const Configurator &configurator = Configurator::Get(); @@ -110,10 +86,12 @@ void TabApplications::UpdateUI(UpdateUIMode mode) { ui->applications_list_comboBox->setCurrentIndex(configurator.executables.GetActiveExecutableIndex()); } + ui->applications_check_box_clear_on_launch->setChecked(configurator.executables.launcher_clear_on_launch); + this->on_applications_list_comboBox_activated(configurator.executables.GetActiveExecutableIndex()); } -void TabApplications::CleanUI() {} +void TabApplications::CleanUI() { this->ResetLaunchApplication(); } bool TabApplications::EventFilter(QObject *target, QEvent *event) { return false; } @@ -121,7 +99,7 @@ void TabApplications::on_applications_remove_application_pushButton_pressed() { Configurator &configurator = Configurator::Get(); configurator.executables.RemoveExecutable(ui->applications_list_comboBox->currentIndex()); - ui->applications_list_comboBox->setEnabled(!configurator.executables.Empty()); + this->EnableOptions(); this->UpdateUI(UPDATE_REBUILD_UI); } @@ -156,39 +134,57 @@ void TabApplications::on_applications_append_application_pushButton_pressed() { void TabApplications::on_applications_list_comboBox_activated(int index) { Configurator &configurator = Configurator::Get(); - configurator.executables.SelectActiveExecutable(index); + configurator.executables.SetActiveExecutable(index); const Executable *executable = configurator.executables.GetActiveExecutable(); - ui->applications_list_comboBox->setToolTip(executable->path.AbsolutePath().c_str()); - ui->applications_options_remove_pushButton->setEnabled(executable->options.size() > 1); + if (executable != nullptr) { + ui->applications_list_comboBox->setToolTip(executable->path.AbsolutePath().c_str()); + ui->applications_options_remove_pushButton->setEnabled(executable->options.size() > 1); + this->on_applications_options_comboBox_activated(executable->active_option_index); + } + ui->applications_options_list_layout->setEnabled(executable != nullptr); + ui->applications_options_layout->setEnabled(executable != nullptr); ui->applications_options_comboBox->blockSignals(true); ui->applications_options_comboBox->clear(); - for (std::size_t i = 0, n = executable->options.size(); i < n; ++i) { - ui->applications_options_comboBox->addItem(executable->options[i].label.c_str()); + if (executable != nullptr) { + for (std::size_t i = 0, n = executable->options.size(); i < n; ++i) { + ui->applications_options_comboBox->addItem(executable->options[i].label.c_str()); + } } ui->applications_options_comboBox->blockSignals(false); - ui->applications_options_comboBox->setCurrentIndex(executable->active_option_index); - - this->on_applications_options_comboBox_activated(executable->active_option_index); + ui->applications_options_comboBox->setCurrentIndex(index); } void TabApplications::on_applications_list_comboBox_textEdited(const QString &text) { Configurator &configurator = Configurator::Get(); Executable *executable = configurator.executables.GetActiveExecutable(); - executable->path = text.toStdString(); + if (executable != nullptr) { + executable->path = text.toStdString(); + } } void TabApplications::on_applications_options_remove_pushButton_pressed() { Configurator &configurator = Configurator::Get(); Executable *executable = configurator.executables.GetActiveExecutable(); - this->ui->applications_options_remove_pushButton->setEnabled(executable->options.size() > 1); + this->EnableOptions(); } -void TabApplications::on_applications_options_duplicate_pushButton_pressed() {} +void TabApplications::on_applications_options_append_pushButton_pressed() { + Configurator &configurator = Configurator::Get(); + Executable *executable = configurator.executables.GetActiveExecutable(); + + ExecutableOptions options = *executable->GetActiveOptions(); + options.label = configurator.executables.MakeOptionsName(options.label); + + executable->active_option_index = static_cast(executable->options.size()); + executable->options.push_back(options); + + this->UpdateUI(UPDATE_REBUILD_UI); +} void TabApplications::on_applications_options_comboBox_activated(int index) { Configurator &configurator = Configurator::Get(); @@ -205,22 +201,22 @@ void TabApplications::on_applications_options_comboBox_activated(int index) { ui->applications_directory_edit->setText(options->working_folder.RelativePath().c_str()); ui->applications_directory_edit->setToolTip(options->working_folder.AbsolutePath().c_str()); - ui->application_args_list->blockSignals(true); - ui->application_args_list->clear(); + ui->applications_args_list->blockSignals(true); + ui->applications_args_list->clear(); for (std::size_t i = 0, n = options->args.size(); i < n; ++i) { - ui->application_args_list->addItem(options->args[i].c_str()); + ui->applications_args_list->addItem(options->args[i].c_str()); } - ui->application_args_list->blockSignals(false); + ui->applications_args_list->blockSignals(false); - ui->application_envs_list->blockSignals(true); - ui->application_envs_list->clear(); + ui->applications_envs_list->blockSignals(true); + ui->applications_envs_list->clear(); for (std::size_t i = 0, n = options->envs.size(); i < n; ++i) { - ui->application_envs_list->addItem(options->envs[i].c_str()); + ui->applications_envs_list->addItem(options->envs[i].c_str()); } - ui->application_envs_list->blockSignals(false); + ui->applications_envs_list->blockSignals(false); - ui->application_output_log_edit->setText(options->log_file.RelativePath().c_str()); - ui->application_output_log_edit->setToolTip(options->log_file.AbsolutePath().c_str()); + ui->applications_output_log_edit->setText(options->log_file.RelativePath().c_str()); + ui->applications_output_log_edit->setToolTip(options->log_file.AbsolutePath().c_str()); } void TabApplications::on_applications_options_comboBox_textEdited(const QString &text) { @@ -249,3 +245,207 @@ void TabApplications::on_applications_configuration_comboBox_activated(int index options->configuration = ui->applications_configuration_comboBox->itemText(index).toStdString(); } + +void TabApplications::on_check_box_clear_on_launch_clicked() { + Configurator::Get().executables.launcher_clear_on_launch = ui->applications_check_box_clear_on_launch->isChecked(); +} + +void TabApplications::on_applications_clear_log_pushButton_pressed() { + ui->log_browser->clear(); + ui->log_browser->update(); + ui->applications_push_button_clear_log->setEnabled(false); +} + +void TabApplications::on_applications_launcher_pushButton_pressed() { + // Are we already monitoring a running app? If so, terminate it + if (this->_launch_application != nullptr) { + ResetLaunchApplication(); + return; + } + + // We are logging, let's add that we've launched a new application + std::string launch_log = "Launching Vulkan Application:\n"; + + Configurator &configurator = Configurator::Get(); + const Executable *active_executable = configurator.executables.GetActiveExecutable(); + + assert(!active_executable->path.Empty()); + launch_log += format("- Executable: %s\n", active_executable->path.AbsolutePath().c_str()); + if (!active_executable->path.Exists()) { + Alert::PathInvalid( + active_executable->path, + format("The '%s' application will fail to launch.", active_executable->path.AbsolutePath().c_str()).c_str()); + } + + const ExecutableOptions *options = active_executable->GetActiveOptions(); + + launch_log += format("- Working Directory: %s\n", options->working_folder.AbsolutePath().c_str()); + if (!options->working_folder.Exists()) { + Alert::PathInvalid( + options->working_folder, + format("The '%s' application will fail to launch.", active_executable->path.AbsolutePath().c_str()).c_str()); + } + /* + if (!_launcher_arguments->text().isEmpty()) { + launch_log += format("- Command-line Arguments: %s\n", _launcher_arguments->text().toStdString().c_str()); + } + */ + if (!options->log_file.Empty()) { + launch_log += format("- Log file: %s\n", options->log_file.AbsolutePath().c_str()); + } + + if (!options->log_file.Empty()) { + // Start logging + // Make sure the log file is not already opened. This can occur if the + // launched application is closed from the applicaiton. + if (!this->_log_file.isOpen()) { + this->_log_file.setFileName(options->log_file.AbsolutePath().c_str()); + + // Open and append, or open and truncate? + QIODevice::OpenMode mode = QIODevice::WriteOnly | QIODevice::Text; + if (!ui->applications_check_box_clear_on_launch->isChecked()) { + mode |= QIODevice::Append; + } + + if (!this->_log_file.open(mode)) { + Alert::LogFileFailed(); + } + } + } + + if (ui->applications_check_box_clear_on_launch->isChecked()) { + ui->log_browser->clear(); + } + this->Log(launch_log.c_str()); + + // Launch the test application + this->_launch_application.reset(new QProcess(this)); + this->connect(_launch_application.get(), SIGNAL(readyReadStandardOutput()), &this->window, SLOT(standardOutputAvailable())); + this->connect(_launch_application.get(), SIGNAL(readyReadStandardError()), &this->window, SLOT(errorOutputAvailable())); + this->connect(_launch_application.get(), SIGNAL(finished(int, QProcess::ExitStatus)), &this->window, + SLOT(processClosed(int, QProcess::ExitStatus))); + + this->_launch_application->setProgram(active_executable->path.AbsolutePath().c_str()); + this->_launch_application->setWorkingDirectory(options->working_folder.AbsolutePath().c_str()); + + Configuration *configuration = configurator.configurations.FindConfiguration(options->configuration); + + QStringList env = QProcess::systemEnvironment(); + env << (QString("VK_LOADER_DEBUG=") + ::GetLogString(configuration->loader_log_messages_flags).c_str()); + if (!options->envs.empty()) { + const QStringList envs = ConvertString(options->envs); + env << envs; + } + this->_launch_application->setEnvironment(env); + + if (!options->args.empty()) { + const QStringList args = ConvertString(options->args); + this->_launch_application->setArguments(args); + } + + this->_launch_application->start(QIODevice::ReadOnly | QIODevice::Unbuffered); + this->_launch_application->setProcessChannelMode(QProcess::MergedChannels); + this->_launch_application->closeWriteChannel(); + + // Wait... did we start? Give it 4 seconds, more than enough time + if (!this->_launch_application->waitForStarted(4000)) { + this->_launch_application->deleteLater(); + this->_launch_application = nullptr; + + const std::string failed_log = std::string("Failed to launch ") + active_executable->path.AbsolutePath().c_str() + "!\n"; + this->Log(failed_log); + } + + this->ui->applications_push_button_launcher->setText(this->_launch_application ? "Terminate" : "Launch"); +} + +void TabApplications::EnableOptions() { + Configurator &configurator = Configurator::Get(); + const bool executable_enabled = !configurator.executables.Empty(); + + const Executable *executable = configurator.executables.GetActiveExecutable(); + const bool options_enabled = executable_enabled && (executable == nullptr ? false : !executable->options.empty()); + + this->ui->applications_list_comboBox->setEnabled(executable_enabled); + this->ui->applications_remove_application_pushButton->setEnabled(executable_enabled); + + this->ui->applications_options_comboBox->setEnabled(executable_enabled); + this->ui->applications_options_remove_pushButton->setEnabled(executable_enabled ? (executable->options.size() > 1) : false); + this->ui->applications_options_append_pushButton->setEnabled(executable_enabled); + + this->ui->applications_layers_mode_comboBox->setEnabled(options_enabled); + this->ui->applications_directory_edit->setEnabled(options_enabled); + this->ui->applications_directory_edit_pushButton->setEnabled(options_enabled); + this->ui->applications_args_list->setEnabled(options_enabled); + this->ui->applications_envs_list->setEnabled(options_enabled); + this->ui->applications_output_log_edit->setEnabled(options_enabled); + this->ui->applications_output_log_pushButton->setEnabled(options_enabled); + + if (!options_enabled) { + this->ui->applications_directory_edit->clear(); + this->ui->applications_args_list->clear(); + this->ui->applications_envs_list->clear(); + this->ui->applications_output_log_edit->clear(); + } +} + +void TabApplications::ResetLaunchApplication() { + if (this->_launch_application) { + this->_launch_application->kill(); + this->_launch_application->waitForFinished(); + this->_launch_application.reset(); + } + + ui->applications_push_button_launcher->setText(this->_launch_application ? "Terminate" : "Launch"); +} + +/// The process we are following is closed. We don't actually care about the +/// exit status/code, we just need to know to destroy the QProcess object +/// and set it back to nullptr so that we know we can launch a new app. +/// Also, if we are logging, it's time to close the log file. +void TabApplications::processClosed(int exit_code, QProcess::ExitStatus status) { + (void)exit_code; + (void)status; + + assert(_launch_application); + + this->disconnect(_launch_application.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, + SLOT(processClosed(int, QProcess::ExitStatus))); + this->disconnect(_launch_application.get(), SIGNAL(readyReadStandardError()), this, SLOT(errorOutputAvailable())); + this->disconnect(_launch_application.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(standardOutputAvailable())); + + Log("Process terminated"); + + if (_log_file.isOpen()) { + _log_file.close(); + } + + ResetLaunchApplication(); +} + +/// This signal get's raised whenever the spawned Vulkan appliction writes +/// to stdout and there is data to be read. The layers flush after all stdout +/// writes, so we should see layer output here in realtime, as we just read +/// the string and append it to the text browser. +/// If a log file is open, we also write the output to the log. +void TabApplications::standardOutputAvailable() { + if (_launch_application) { + this->Log(_launch_application->readAllStandardOutput().toStdString()); + } +} + +void TabApplications::errorOutputAvailable() { + if (_launch_application) { + this->Log(_launch_application->readAllStandardError().toStdString()); + } +} + +void TabApplications::Log(const std::string &log) { + ui->log_browser->setPlainText(ui->log_browser->toPlainText() + "\n" + log.c_str()); + ui->applications_push_button_clear_log->setEnabled(true); + + if (this->_log_file.isOpen()) { + this->_log_file.write(log.c_str(), log.size()); + this->_log_file.flush(); + } +} diff --git a/vkconfig_gui/tab_applications.h b/vkconfig_gui/tab_applications.h index a3f422d853..ed831fce6e 100644 --- a/vkconfig_gui/tab_applications.h +++ b/vkconfig_gui/tab_applications.h @@ -22,6 +22,10 @@ #include "tab.h" +#include + +#include + struct TabApplications : public Tab { Q_OBJECT @@ -33,6 +37,8 @@ struct TabApplications : public Tab { virtual void CleanUI() override; virtual bool EventFilter(QObject* target, QEvent* event) override; + void ResetLaunchApplication(); + public Q_SLOTS: void on_applications_remove_application_pushButton_pressed(); void on_applications_append_application_pushButton_pressed(); @@ -40,12 +46,25 @@ struct TabApplications : public Tab { void on_applications_list_comboBox_textEdited(const QString& text); void on_applications_options_remove_pushButton_pressed(); - void on_applications_options_duplicate_pushButton_pressed(); + void on_applications_options_append_pushButton_pressed(); void on_applications_options_comboBox_activated(int index); void on_applications_options_comboBox_textEdited(const QString& text); void on_applications_layers_mode_comboBox_activated(int index); void on_applications_configuration_comboBox_activated(int index); + void on_applications_launcher_pushButton_pressed(); + void on_applications_clear_log_pushButton_pressed(); + void on_check_box_clear_on_launch_clicked(); + private: + void EnableOptions(); + + void Log(const std::string& log); + void standardOutputAvailable(); // stdout output is available + void errorOutputAvailable(); // Layeroutput is available + void processClosed(int exitCode, QProcess::ExitStatus status); // app died + + std::unique_ptr _launch_application; // Keeps track of the monitored app + QFile _log_file; // Log file for layer output }; diff --git a/vkconfig_gui/tab_configurations.cpp b/vkconfig_gui/tab_configurations.cpp index 5c73794c4e..d3d2e1be74 100644 --- a/vkconfig_gui/tab_configurations.cpp +++ b/vkconfig_gui/tab_configurations.cpp @@ -742,7 +742,7 @@ void TabConfigurations::on_combo_box_mode_currentIndexChanged(int index) { void TabConfigurations::on_combo_box_applications_currentIndexChanged(int index) { Configurator &configurator = Configurator::Get(); - configurator.executables.SelectActiveExecutable(index); + configurator.executables.SetActiveExecutable(index); this->UpdateUI(UPDATE_REFRESH_UI); this->window.UpdateUI_Status();