| /* |
| * Copyright (c) 2020-2026 Valve Corporation |
| * Copyright (c) 2020-2026 LunarG, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * Authors: |
| * - Richard S. Wright Jr. <richard@lunarg.com> |
| * - Christophe Riccio <christophe@lunarg.com> |
| */ |
| |
| #include "mainwindow.h" |
| #include "style.h" |
| |
| #include "../vkconfig_core/configurator.h" |
| #include "../vkconfig_core/util.h" |
| #include "../vkconfig_core/ui.h" |
| #include "../vkconfig_core/version.h" |
| #include "../vkconfig_core/date.h" |
| #include "../vkconfig_core/type_platform.h" |
| #include "../vkconfig_core/type_log.h" |
| #include "../vkconfig_core/type_hide_message.h" |
| |
| #include <QMenu> |
| #include <QMessageBox> |
| #include <QFrame> |
| #include <QComboBox> |
| #include <QVariant> |
| #include <QContextMenuEvent> |
| #include <QLineEdit> |
| #include <QFileDialog> |
| #include <QDesktopServices> |
| #include <QSettings> |
| #include <QWidgetAction> |
| #include <QShortcut> |
| #include <QSignalMapper> |
| |
| #include <cassert> |
| |
| MainWindow::MainWindow(QApplication& app, QWidget* parent) |
| : QMainWindow(parent), _tray_icon(new QSystemTrayIcon(this)), app(app), ui(new Ui::MainWindow) { |
| ui->setupUi(this); |
| |
| this->tabs[TAB_DIAGNOSTIC].reset(new TabDiagnostics(*this, ui)); |
| this->tabs[TAB_APPLICATIONS].reset(new TabApplications(*this, ui)); |
| this->tabs[TAB_LAYERS_PATHS].reset(new TabLayers(*this, ui)); |
| this->tabs[TAB_CONFIGURATIONS].reset(new TabConfigurations(*this, ui)); |
| this->tabs[TAB_DOCUMENTATION].reset(new TabDocumentation(*this, ui)); |
| this->tabs[TAB_DRIVERS].reset(new TabDrivers(*this, ui)); |
| this->tabs[TAB_PREFERENCES].reset(new TabPreferences(*this, ui)); |
| this->tabs[TAB_ABOUT].reset(new TabAbout(*this, ui)); |
| |
| this->connect(qApp, &QGuiApplication::commitDataRequest, this, &MainWindow::commitDataRequest); |
| |
| for (int i = TAB_FIRST, l = TAB_LAST; i <= l; ++i) { |
| this->ui->tab_widget->setTabText(i, GetLabel(static_cast<TabType>(i))); |
| } |
| |
| Configurator& configurator = Configurator::Get(); |
| |
| if (!configurator.window_geometry.isEmpty()) { |
| this->restoreGeometry(configurator.window_geometry); |
| this->restoreState(configurator.window_state); |
| } |
| |
| this->ui->tab_widget->setCurrentIndex(configurator.active_tab); |
| this->ui->tab_widget->installEventFilter(this); |
| |
| this->connect(this->ui->tab_widget, SIGNAL(currentChanged(int)), this, SLOT(on_tab_widget_currentChanged(int))); |
| this->connect(this->_tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::OnIconActivated); |
| |
| this->UpdateUI(UPDATE_REBUILD_UI); |
| |
| configurator.Override(OVERRIDE_AREA_ALL); |
| } |
| |
| MainWindow::~MainWindow() {} |
| |
| void MainWindow::commitDataRequest(QSessionManager& manager) { |
| (void)manager; |
| |
| Configurator& configurator = Configurator::Get(); |
| configurator.Surrender(OVERRIDE_AREA_ALL); |
| |
| Configurator::Release(); |
| } |
| |
| void MainWindow::UpdateUI(UpdateUIMode mode) { |
| if (this->tabs[this->ui->tab_widget->currentIndex()] != nullptr) { |
| this->tabs[this->ui->tab_widget->currentIndex()]->UpdateUI(mode); |
| } |
| |
| // this->UpdateUI_Status(); |
| } |
| |
| void MainWindow::UpdateUI_Status() { |
| const Configurator& configurator = Configurator::Get(); |
| |
| this->setWindowTitle(GetMainWindowTitle().c_str()); |
| |
| if (QSystemTrayIcon::isSystemTrayAvailable()) { |
| // Device |
| { |
| QMenu* menu = new QMenu(this); |
| |
| QSignalMapper* mapper_device = new QSignalMapper(menu); |
| QSignalMapper* mapper_layers = new QSignalMapper(menu); |
| |
| QAction* tray_restore_action = new QAction("&Show Vulkan Configurator UI", this); |
| tray_restore_action->setIcon(QIcon(":/resourcefiles/vkconfig-on.png")); |
| QFont font = tray_restore_action->font(); |
| font.setBold(true); |
| tray_restore_action->setFont(font); |
| this->connect(tray_restore_action, &QAction::triggered, this, &MainWindow::OnTrayActionShow); |
| menu->addAction(tray_restore_action); |
| |
| menu->addSeparator(); |
| const bool enabled_device = configurator.driver_override_enabled; |
| |
| QAction* tray_override = new QAction("Override System Vulkan &Device with:", this); |
| tray_override->setFont(font); |
| tray_override->setCheckable(true); |
| tray_override->setChecked(enabled_device); |
| |
| this->connect(tray_override, &QAction::toggled, this, &MainWindow::OnTrayActionOverrideDevice); |
| menu->addAction(tray_override); |
| |
| for (std::size_t i = 0, n = configurator.vulkan_system_info.physicalDevices.size(); i < n; ++i) { |
| const VulkanPhysicalDeviceInfo& info = configurator.vulkan_system_info.physicalDevices[i]; |
| const DeviceInfo& device_info = ::GetDeviceInfo(info); |
| |
| QAction* action = new QAction(format("%s (%s)", info.GetLabel().c_str(), info.GetVersion().c_str()).c_str()); |
| action->setCheckable(true); |
| action->setChecked(device_info == configurator.driver_override_info); |
| action->setEnabled(enabled_device); |
| |
| menu->addAction(action); |
| |
| this->connect(action, SIGNAL(triggered()), mapper_device, SLOT(map())); |
| mapper_device->setMapping(action, i); |
| } |
| |
| this->connect(mapper_device, &QSignalMapper::mappedInt, this, &MainWindow::OnDeviceChanged); |
| |
| menu->addSeparator(); |
| const bool enabled_layers = configurator.layers_override_enabled && configurator.GetExecutableScope() != EXECUTABLE_PER; |
| |
| QAction* tray_override_layers = new QAction("Override System Vulkan &Layers Configuration with:", this); |
| tray_override_layers->setFont(font); |
| tray_override_layers->setCheckable(true); |
| tray_override_layers->setChecked(enabled_layers); |
| |
| this->connect(tray_override_layers, &QAction::toggled, this, &MainWindow::OnTrayActionOverrideLayers); |
| menu->addAction(tray_override_layers); |
| |
| for (std::size_t i = 0, n = configurator.configurations.available_configurations.size(); i < n; ++i) { |
| const Configuration& configuration = configurator.configurations.available_configurations[i]; |
| |
| QAction* action = new QAction(configuration.key.c_str(), this); |
| action->setCheckable(true); |
| action->setChecked(configuration.key == configurator.GetSelectedGlobalConfiguration()); |
| action->setEnabled(enabled_layers); |
| |
| menu->addAction(action); |
| |
| this->connect(action, SIGNAL(triggered()), mapper_layers, SLOT(map())); |
| mapper_layers->setMapping(action, i); |
| } |
| |
| this->connect(mapper_layers, &QSignalMapper::mappedInt, this, &MainWindow::OnLayersChanged); |
| |
| menu->addSeparator(); |
| |
| QAction* tray_override_loader = new QAction("Override System Vulkan Loader Log", this); |
| tray_override_loader->setFont(font); |
| tray_override_loader->setCheckable(true); |
| tray_override_loader->setChecked(configurator.loader_log_enabled); |
| this->connect(tray_override_loader, &QAction::toggled, this, &MainWindow::OnTrayActionOverrideLog); |
| menu->addAction(tray_override_loader); |
| |
| menu->addSeparator(); |
| |
| QAction* tray_quit_action = new QAction("&Quit Vulkan Configurator", this); |
| tray_quit_action->setIcon(::Get(configurator.current_theme_mode, ::ICON_EXIT)); |
| tray_quit_action->setFont(font); |
| this->connect(tray_quit_action, &QAction::triggered, qApp, &QCoreApplication::quit); |
| menu->addAction(tray_quit_action); |
| |
| if (VKC_PLATFORM == PLATFORM_LINUX) { |
| if (this->_tray_icon != nullptr) { |
| delete this->_tray_icon; |
| } |
| this->_tray_icon = new QSystemTrayIcon(this); |
| this->connect(this->_tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::OnIconActivated); |
| } |
| |
| this->_tray_icon->setContextMenu(menu); |
| |
| if (configurator.layers_override_enabled || configurator.driver_override_enabled || configurator.loader_log_enabled || |
| configurator.loader_log_enabled) { |
| this->_tray_icon->setIcon(QIcon(":/resourcefiles/vkconfig-on.png")); |
| } else { |
| this->_tray_icon->setIcon(QIcon(":/resourcefiles/vkconfig-off.png")); |
| } |
| |
| this->_tray_icon->show(); |
| } |
| } |
| |
| if (configurator.layers_override_enabled || configurator.driver_override_enabled || configurator.loader_log_enabled) { |
| this->setWindowIcon(QIcon(":/resourcefiles/vkconfig-on.png")); |
| } else { |
| this->setWindowIcon(QIcon(":/resourcefiles/vkconfig-off.png")); |
| } |
| } |
| |
| void MainWindow::OnIconActivated(QSystemTrayIcon::ActivationReason reason) { |
| switch (reason) { |
| default: |
| break; |
| case QSystemTrayIcon::Context: |
| break; |
| case QSystemTrayIcon::DoubleClick: |
| if (this->isMinimized() || this->isHidden()) { |
| this->hide(); |
| this->showNormal(); |
| this->setWindowState(Qt::WindowState::WindowActive); |
| this->UpdateUI(UPDATE_REBUILD_UI); |
| } else { |
| this->hide(); |
| } |
| break; |
| } |
| } |
| |
| void MainWindow::OnTrayActionShow() { |
| this->hide(); |
| this->showNormal(); |
| this->UpdateUI(UPDATE_REBUILD_UI); |
| } |
| |
| void MainWindow::OnTrayActionOverrideLayers(bool toggled) { |
| Configurator& configurator = Configurator::Get(); |
| configurator.layers_override_enabled = toggled; |
| configurator.Override(OVERRIDE_AREA_ALL); |
| |
| this->tabs[TAB_CONFIGURATIONS]->UpdateUI(UPDATE_REBUILD_UI); |
| } |
| |
| void MainWindow::OnLayersChanged(int index) { |
| Configurator& configurator = Configurator::Get(); |
| configurator.SetActiveConfigurationName(configurator.configurations.available_configurations[index].key); |
| configurator.Override(OVERRIDE_AREA_ALL); |
| |
| this->tabs[TAB_CONFIGURATIONS]->UpdateUI(UPDATE_REBUILD_UI); |
| } |
| |
| void MainWindow::OnTrayActionOverrideDevice(bool toggled) { |
| Configurator& configurator = Configurator::Get(); |
| configurator.driver_override_mode = DRIVER_MODE_SINGLE; |
| configurator.driver_override_enabled = toggled; |
| configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT); |
| |
| this->tabs[TAB_DRIVERS]->UpdateUI(UPDATE_REBUILD_UI); |
| } |
| |
| void MainWindow::OnDeviceChanged(int index) { |
| Configurator& configurator = Configurator::Get(); |
| configurator.driver_override_info = ::GetDeviceInfo(configurator.vulkan_system_info.physicalDevices[index]); |
| configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT); |
| |
| this->tabs[TAB_DRIVERS]->UpdateUI(UPDATE_REBUILD_UI); |
| } |
| |
| void MainWindow::OnTrayActionOverrideLog(bool toggled) { |
| Configurator& configurator = Configurator::Get(); |
| configurator.loader_log_enabled = toggled; |
| configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT); |
| |
| this->tabs[TAB_DIAGNOSTIC]->UpdateUI(UPDATE_REBUILD_UI); |
| } |
| |
| /// The only thing we need to do here is clear the configuration if |
| /// the user does not want it active. |
| void MainWindow::closeEvent(QCloseEvent* event) { |
| Configurator& configurator = Configurator::Get(); |
| |
| QGuiApplication::setQuitOnLastWindowClosed(!configurator.GetUseSystemTray()); |
| |
| if (configurator.GetUseSystemTray()) { |
| // Alert the user to the current state of the vulkan configurator and |
| // give them the option to not shutdown. |
| if (!(configurator.Get(HIDE_MESSAGE_USE_SYSTEM_TRAY))) { |
| std::string shut_down_state; |
| |
| if (configurator.layers_override_enabled) { |
| shut_down_state = |
| "Vulkan Layers override will remain in effect while Vulkan Configurator remain active in the system tray."; |
| } else { |
| shut_down_state = |
| "No Vulkan layers override will be active when Vulkan Configurator remain active in the system tray."; |
| } |
| |
| QMessageBox alert(this); |
| alert.setWindowTitle("Vulkan Configurator will remain in the system tray"); |
| alert.setText(shut_down_state.c_str()); |
| alert.setIcon(QMessageBox::Question); |
| alert.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); |
| alert.setDefaultButton(QMessageBox::Ok); |
| alert.setCheckBox(new QCheckBox("Do not show again.")); |
| |
| QPalette palette, palette_saved = this->ui->preferences_keep_running->palette(); |
| palette.setColor(QPalette::WindowText, QColor(Qt::red)); |
| this->ui->preferences_keep_running->setPalette(palette); |
| |
| this->ui->tab_widget->setCurrentIndex(TAB_PREFERENCES); |
| |
| int ret_val = alert.exec(); |
| if (alert.checkBox()->isChecked()) { |
| configurator.Set(HIDE_MESSAGE_USE_SYSTEM_TRAY); |
| } |
| |
| this->ui->preferences_keep_running->setPalette(palette_saved); |
| |
| if (ret_val == QMessageBox::Cancel) { |
| event->ignore(); // Not closing the window |
| return; |
| } |
| } |
| } |
| |
| this->tabs[this->ui->tab_widget->currentIndex()]->CleanUI(); |
| |
| configurator.window_geometry = this->saveGeometry(); |
| configurator.window_state = this->saveState(); |
| |
| QMainWindow::closeEvent(event); |
| } |
| /* |
| void MainWindow::showEvent(QShowEvent *event) { |
| (void)event; |
| |
| this->UpdateUI(UPDATE_REBUILD_UI); |
| |
| event->accept(); |
| } |
| */ |
| bool MainWindow::eventFilter(QObject* target, QEvent* event) { |
| if (this->tabs[this->ui->tab_widget->currentIndex()] == nullptr) { |
| return true; |
| } |
| |
| return this->tabs[this->ui->tab_widget->currentIndex()]->EventFilter(target, event); |
| } |
| |
| void MainWindow::changeEvent(QEvent* event) { |
| if (event->type() == QEvent::ThemeChange) { |
| Configurator& configurator = Configurator::Get(); |
| |
| static_cast<TabPreferences*>(this->tabs[TAB_PREFERENCES].get())->on_theme_mode_changed(configurator.current_theme_mode); |
| } |
| } |
| |
| void MainWindow::on_tab_widget_currentChanged(int index) { |
| assert(index >= 0); |
| this->tabs[index]->UpdateUI(UPDATE_REBUILD_UI); |
| } |