From 178e551964e2f2b52e7efc601e2a397dc5db0236 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Sun, 28 Jan 2024 16:37:50 +0600 Subject: [PATCH] Built-in manager Missing features: 1. Multi-selection not done yet. 2. Icons for VMs. --- src/86box.c | 34 +- src/config.c | 253 +++++++- src/include/86box/86box.h | 10 +- src/include/86box/config.h | 25 + src/qt/CMakeLists.txt | 5 +- src/qt/manager/CMakeLists.txt | 16 + src/qt/manager/qt_manager_mainwindow.cpp | 734 ++++++++++++++++++++++ src/qt/manager/qt_manager_mainwindow.h | 85 +++ src/qt/manager/qt_manager_mainwindow.ui | 309 +++++++++ src/qt/manager/qt_manager_settings.cpp | 192 ++++++ src/qt/manager/qt_manager_settings.h | 55 ++ src/qt/manager/qt_manager_settings.ui | 210 +++++++ src/qt/manager/qt_manager_vmadddialog.cpp | 217 +++++++ src/qt/manager/qt_manager_vmadddialog.h | 42 ++ src/qt/manager/qt_manager_vmadddialog.ui | 139 ++++ src/qt/qt_main.cpp | 29 +- src/qt/qt_platform.cpp | 6 + src/qt/qt_settings.cpp | 2 +- src/qt/qt_unixmanagerfilter.cpp | 18 +- 19 files changed, 2335 insertions(+), 46 deletions(-) create mode 100644 src/qt/manager/CMakeLists.txt create mode 100644 src/qt/manager/qt_manager_mainwindow.cpp create mode 100644 src/qt/manager/qt_manager_mainwindow.h create mode 100644 src/qt/manager/qt_manager_mainwindow.ui create mode 100644 src/qt/manager/qt_manager_settings.cpp create mode 100644 src/qt/manager/qt_manager_settings.h create mode 100644 src/qt/manager/qt_manager_settings.ui create mode 100644 src/qt/manager/qt_manager_vmadddialog.cpp create mode 100644 src/qt/manager/qt_manager_vmadddialog.h create mode 100644 src/qt/manager/qt_manager_vmadddialog.ui diff --git a/src/86box.c b/src/86box.c index 8218c208cf..3711041743 100644 --- a/src/86box.c +++ b/src/86box.c @@ -204,6 +204,11 @@ int video_fullscreen_scale_maximized = 0; /* (C) Whether int do_auto_pause = 0; /* (C) Auto-pause the emulator on focus loss */ + +int manager_mode = 0; + +config_manager_t manager_config; + /* Statistics. */ extern int mmuflush; extern int readlnum; @@ -217,9 +222,10 @@ extern int CPUID; extern int output; int atfullspeed; -char exe_path[2048]; /* path (dir) of executable */ -char usr_path[1024]; /* path (dir) of user data */ -char cfg_path[1024]; /* full path of config file */ +char exe_path[2048]; /* path (dir) of executable */ +char usr_path[1024]; /* path (dir) of user data */ +char cfg_path[1024]; /* full path of config file */ +char cfg_global_path[1024]; /* full path of config file */ FILE *stdlog = NULL; /* file to log output to */ #if 0 int scrnsz_x = SCREEN_RES_X; /* current screen size, X */ @@ -452,6 +458,7 @@ pc_init(int argc, char *argv[]) char *ppath = NULL; char *rpath = NULL; char *cfg = NULL; + char *global_cfg = NULL; char *p; char temp[2048]; char *fn[FDD_NUM] = { NULL }; @@ -547,6 +554,7 @@ pc_init(int argc, char *argv[]) #ifndef USE_SDL_UI printf("-S or --settings - show only the settings dialog\n"); #endif + printf("-U or --globalconfig - set 'path' to be global config file\n"); printf("-V or --vmname name - overrides the name of the running VM\n"); printf("-X or --clear what - clears the 'what' (cmos/flash/both)\n"); printf("-Y or --donothing - do not show any UI or run the emulation\n"); @@ -589,6 +597,11 @@ pc_init(int argc, char *argv[]) goto usage; cfg = argv[++c]; + } else if (!strcasecmp(argv[c], "--globalconfig") || !strcasecmp(argv[c], "-U")) { + if ((c + 1) == argc || plat_dir_check(argv[c + 1])) + goto usage; + + global_cfg = argv[++c]; } else if (!strcasecmp(argv[c], "--image") || !strcasecmp(argv[c], "-I")) { if ((c + 1) == argc) goto usage; @@ -725,7 +738,8 @@ pc_init(int argc, char *argv[]) create it. */ if (!plat_dir_check(usr_path)) plat_dir_create(usr_path); - } + } else + manager_mode = 1; // Add the VM-local ROM path. path_append_filename(temp, usr_path, "roms"); @@ -808,6 +822,15 @@ pc_init(int argc, char *argv[]) /* At this point, we can safely create the full path name. */ path_append_filename(cfg_path, usr_path, p); + cfg_global_path[0] = 0; + if (global_cfg == NULL) { + plat_get_global_config_dir(cfg_global_path); + path_slash(cfg_global_path); + strncat(cfg_global_path, CONFIG_FILE, sizeof(CONFIG_FILE)); + } else { + strncpy(cfg_global_path, global_cfg, sizeof(cfg_global_path) - 1); + } + /* * Get the current directory's name * @@ -837,7 +860,8 @@ pc_init(int argc, char *argv[]) pclog("# ROM path: %s\n", rom_path->path); } - pclog("# Configuration file: %s\n#\n\n", cfg_path); + pclog("# Configuration file: %s\n", cfg_path); + pclog("# Global configuration file: %s\n#\n\n", cfg_global_path); /* * We are about to read the configuration file, which MAY * put data into global variables (the hard- and floppy diff --git a/src/config.c b/src/config.c index c973abf233..0a8bea2d64 100644 --- a/src/config.c +++ b/src/config.c @@ -82,7 +82,7 @@ static int cx; static int cy; static int cw; static int ch; -static ini_t config; +static ini_t config, config_global; #ifdef ENABLE_CONFIG_LOG int config_do_log = ENABLE_CONFIG_LOG; @@ -1534,6 +1534,7 @@ void config_load(void) { int i; + int default_create = 0; config_log("Loading config file '%s'..\n", cfg_path); @@ -1548,6 +1549,32 @@ config_load(void) if (!config) { config = ini_new(); + default_create = 1; + + config_changed = 1; + } + + load_general(); /* General */ + for (i = 0; i < MONITORS_NUM; i++) + load_monitor(i); /* Monitors */ + load_machine(); /* Machine */ + load_video(); /* Video */ + load_input_devices(); /* Input devices */ + load_sound(); /* Sound */ + load_network(); /* Network */ + load_ports(); /* Ports (COM & LPT) */ + load_storage_controllers(); /* Storage controllers */ + load_hard_disks(); /* Hard disks */ + load_floppy_and_cdrom_drives(); /* Floppy and CD-ROM drives */ + load_other_removable_devices(); /* Other removable devices */ + load_other_peripherals(); /* Other peripherals */ + + /* Mark the configuration as changed. */ + config_changed = 1; + + if (!default_create) { + config_log("Config loaded.\n\n"); + } else { config_changed = 1; cpu_f = (cpu_family_t *) &cpu_families[0]; @@ -1605,31 +1632,13 @@ config_load(void) cassette_append = 0; cassette_pcm = 0; cassette_ui_writeprot = 0; - - config_log("Config file not present or invalid!\n"); - } else { - load_general(); /* General */ - for (i = 0; i < MONITORS_NUM; i++) - load_monitor(i); /* Monitors */ - load_machine(); /* Machine */ - load_video(); /* Video */ - load_input_devices(); /* Input devices */ - load_sound(); /* Sound */ - load_network(); /* Network */ - load_ports(); /* Ports (COM & LPT) */ - load_storage_controllers(); /* Storage controllers */ - load_hard_disks(); /* Hard disks */ - load_floppy_and_cdrom_drives(); /* Floppy and CD-ROM drives */ - load_other_removable_devices(); /* Other removable devices */ - load_other_peripherals(); /* Other peripherals */ - - /* Mark the configuration as changed. */ - config_changed = 1; - - config_log("Config loaded.\n\n"); + config_log("Config file not present or invalid!\n\n"); } video_copy = (video_grayscale || invert_display) ? video_transform_copy : memcpy; + + if (!manager_mode) + config_load_global(); } /* Save "General" section. */ @@ -2730,6 +2739,204 @@ config_save(void) save_other_peripherals(); /* Other peripherals */ ini_write(config, cfg_path); + + if (!manager_mode) + config_save_global(); +} + +void +load_manager_settings_global() +{ + char temp[1024] = { 0 }; + int i = 0, c = 0; + char *p = NULL; + ini_section_t section = NULL; + + /* This only matters when running in manager mode. */ + if (!manager_mode) { + memset(&manager_config, 0, sizeof(manager_config)); + return; + } + + section = ini_find_or_create_section(config_global, "Manager"); + + manager_config.close_to_tray_icon = !!ini_section_get_int(section, "close_to_tray_icon", 0); + manager_config.minimize_to_tray_icon = !!ini_section_get_int(section, "minimize_to_tray_icon", 0); + manager_config.minimize_when_vm_started = !!ini_section_get_int(section, "minimize_when_vm_started", 0); + manager_config.enable_grid_lines = !!ini_section_get_int(section, "enable_grid_lines", 0); + manager_config.enable_logging = !!ini_section_get_int(section, "enable_logging", 0); + + manager_config.logging_path[0] = 0; + strncpy(manager_config.logging_path, ini_section_get_string(section, "logging_path", ""), sizeof(manager_config.logging_path)); +} + +/* Global config section. */ +void +load_manager_vms_global() +{ + char temp[1024] = { 0 }; + char vms_path_default[1024] = { 0 }; + int i = 0, c = 0; + char *p = NULL; + ini_section_t section = NULL; + + /* This only matters when running in manager mode. */ + if (!manager_mode) { + memset(&manager_config, 0, sizeof(manager_config)); + return; + } + plat_get_global_config_dir(vms_path_default); + path_slash(vms_path_default); + strncat(vms_path_default, "86Box VMs", sizeof(vms_path_default) - 1); + strncat(vms_path_default, path_get_slash(vms_path_default), sizeof(vms_path_default) - 1); + + memset(manager_config.vm, 0, sizeof(manager_config.vm)); + memset(manager_config.vms_path, 0, sizeof(manager_config.vms_path)); + + section = ini_find_or_create_section(config_global, "Manager"); + strncpy(manager_config.vms_path, ini_section_get_string(section, "vms_path", vms_path_default), sizeof(manager_config.vms_path)); + + for (i = 0; i < 256; i++) { + temp[0] = 0; + snprintf(temp, sizeof(temp) - 1, "VM #%d", i + 1); + section = ini_find_section(config_global, temp); + if (section) { + if (ini_section_get_string(section, "name", "")[0] != 0) { + p = ini_section_get_string(section, "name", ""); + strncpy(manager_config.vm[c].name, p, sizeof(manager_config.vm[0].name)); + p = ini_section_get_string(section, "description", ""); + strncpy(manager_config.vm[c].description, p, sizeof(manager_config.vm[0].description)); + p = ini_section_get_string(section, "path", ""); + manager_config.vm[c].path[0] = 0; + if (p[0] == 0) { + strncat(manager_config.vm[c].path, vms_path_default, sizeof(manager_config.vm[c].path)); + path_slash(manager_config.vm[c].path); + strncat(manager_config.vm[c].path, manager_config.vm[c].name, sizeof(manager_config.vm[c].path)); + path_slash(manager_config.vm[c].path); + } else { + strncpy(manager_config.vm[c].path, p, sizeof(manager_config.vm[c].path)); + path_slash(manager_config.vm[c].path); + } + c++; + } + } + } +} + +void +save_manager_vms_global() +{ + char temp[1024] = { 0 }; + char vms_path_default[1024] = { 0 }; + ini_section_t section = NULL; + int i = 0, c = 0; + + /* This only matters when running in manager mode. */ + if (!manager_mode) { + return; + } + plat_get_global_config_dir(vms_path_default); + path_slash(vms_path_default); + strncat(vms_path_default, "86Box VMs", sizeof(vms_path_default) - 1); + strncat(vms_path_default, path_get_slash(vms_path_default), sizeof(vms_path_default) - 1); + + section = ini_find_or_create_section(config_global, "Manager"); + + if (strncmp(manager_config.vms_path, vms_path_default, 1024)) { + ini_section_set_string(section, "vms_path", manager_config.vms_path); + } else { + ini_section_delete_var(section, "vms_path"); + } + + ini_delete_section_if_empty(config_global, section); + + for (i = 0; i < 256; i++) { + temp[0] = 0; + snprintf(temp, sizeof(temp) - 1, "VM #%d", c + 1); + section = ini_find_or_create_section(config_global, temp); + if (manager_config.vm[i].name[0]) { + ini_section_set_string(section, "name", manager_config.vm[i].name); + if (manager_config.vm[i].description[0]) { + ini_section_set_string(section, "description", manager_config.vm[i].description); + } else { + ini_section_delete_var(section, "description"); + } + /* Always save paths. */ + ini_section_set_string(section, "path", manager_config.vm[i].path); + c++; + } else { + ini_section_delete_var(section, "name"); + ini_section_delete_var(section, "description"); + ini_section_delete_var(section, "path"); + } + ini_delete_section_if_empty(config_global, section); + } +} + +void +save_manager_settings_global() +{ + char temp[1024] = { 0 }; + int i = 0, c = 0; + char *p = NULL; + ini_section_t section = NULL; + + /* This only matters when running in manager mode. */ + if (!manager_mode) { + return; + } + + section = ini_find_or_create_section(config_global, "Manager"); + + if (manager_config.close_to_tray_icon == 0) + ini_section_delete_var(section, "close_to_tray_icon"); + else + ini_section_set_int(section, "close_to_tray_icon", manager_config.close_to_tray_icon); + + if (manager_config.minimize_to_tray_icon == 0) + ini_section_delete_var(section, "minimize_to_tray_icon"); + else + ini_section_set_int(section, "minimize_to_tray_icon", manager_config.minimize_to_tray_icon); + + if (manager_config.minimize_when_vm_started == 0) + ini_section_delete_var(section, "minimize_when_vm_started"); + else + ini_section_set_int(section, "minimize_when_vm_started", manager_config.minimize_when_vm_started); + + if (manager_config.enable_grid_lines == 0) + ini_section_delete_var(section, "enable_grid_lines"); + else + ini_section_set_int(section, "enable_grid_lines", manager_config.enable_grid_lines); + + if (manager_config.enable_logging == 0) + ini_section_delete_var(section, "enable_logging"); + else + ini_section_set_int(section, "enable_logging", manager_config.enable_logging); + + if (manager_config.logging_path[0] == 0) + ini_section_delete_var(section, "logging_path"); + else + ini_section_set_string(section, "logging_path", manager_config.logging_path); + + ini_delete_section_if_empty(config_global, section); +} + +/* Load global config. */ +void +config_load_global() +{ + config_global = ini_read(cfg_global_path); + load_manager_settings_global(); + load_manager_vms_global(); +} + +/* Save global config. */ +void +config_save_global() +{ + save_manager_settings_global(); + save_manager_vms_global(); + ini_write(config_global, cfg_global_path); } ini_t diff --git a/src/include/86box/86box.h b/src/include/86box/86box.h index 20f3fffccb..6693849f47 100644 --- a/src/include/86box/86box.h +++ b/src/include/86box/86box.h @@ -144,6 +144,7 @@ extern int confirm_reset; /* (C) enable reset confirmation */ extern int confirm_exit; /* (C) enable exit confirmation */ extern int confirm_save; /* (C) enable save confirmation */ extern int enable_discord; /* (C) enable Discord integration */ +extern int manager_mode; /* (C) manager mode */ extern int fixed_size_x; extern int fixed_size_y; @@ -165,10 +166,11 @@ extern uint16_t key_prefix_2_2; extern uint16_t key_uncapture_1; extern uint16_t key_uncapture_2; -extern char exe_path[2048]; /* path (dir) of executable */ -extern char usr_path[1024]; /* path (dir) of user data */ -extern char cfg_path[1024]; /* full path of config file */ -extern int open_dir_usr_path; /* default file open dialog directory of usr_path */ +extern char exe_path[2048]; /* path (dir) of executable */ +extern char usr_path[1024]; /* path (dir) of user data */ +extern char cfg_path[1024]; /* full path of config file */ +extern char cfg_global_path[1024]; /* full path of global config file */ +extern int open_dir_usr_path; /* default file open dialog directory of usr_path */ #ifndef USE_NEW_DYNAREC extern FILE *stdlog; /* file to log output to */ #endif diff --git a/src/include/86box/config.h b/src/include/86box/config.h index 80c987162e..09a3a6012d 100644 --- a/src/include/86box/config.h +++ b/src/include/86box/config.h @@ -134,8 +134,33 @@ typedef struct config_t { } config_t; #endif +typedef struct config_manager_vm_t +{ + char name[256]; + char description[4096]; + char path[4096]; +} config_manager_vm_t; + +typedef struct config_manager_t +{ + char vms_path[1024]; + char logging_path[1024]; + struct config_manager_vm_t vm[256]; + + unsigned int minimize_to_tray_icon; + unsigned int close_to_tray_icon; + unsigned int minimize_when_vm_started; + + unsigned int enable_grid_lines; + unsigned int enable_logging; +} config_manager_t; + +extern config_manager_t manager_config; + extern void config_load(void); extern void config_save(void); +extern void config_load_global(void); +extern void config_save_global(void); #ifdef EMU_INI_H extern ini_t config_get_ini(void); diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index fb96de1ea8..b9ff287094 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -262,7 +262,7 @@ target_link_libraries( target_link_libraries( ui - PRIVATE + PUBLIC Qt${QT_MAJOR}::Widgets Qt${QT_MAJOR}::Gui Qt${QT_MAJOR}::OpenGL @@ -443,3 +443,6 @@ foreach(po_file ${po_files}) endforeach() configure_file(qt_translations.qrc ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) target_sources(ui PRIVATE ${QM_FILES} ${CMAKE_CURRENT_BINARY_DIR}/qt_translations.qrc) + +# Manager +add_subdirectory(manager) diff --git a/src/qt/manager/CMakeLists.txt b/src/qt/manager/CMakeLists.txt new file mode 100644 index 0000000000..0259fc750f --- /dev/null +++ b/src/qt/manager/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(manager STATIC + qt_manager_mainwindow.h qt_manager_mainwindow.cpp qt_manager_mainwindow.ui + qt_manager_vmadddialog.h qt_manager_vmadddialog.cpp qt_manager_vmadddialog.ui + qt_manager_settings.h qt_manager_settings.cpp qt_manager_settings.ui + + +) +target_include_directories(manager PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../" "${CMAKE_CURRENT_SOURCE_DIR}/") +target_link_libraries(manager PRIVATE + Qt${QT_MAJOR}::Widgets + Qt${QT_MAJOR}::Gui + Qt${QT_MAJOR}::Network + Threads::Threads +) +target_link_libraries(plat PUBLIC manager) +target_link_libraries(manager PRIVATE plat ui) diff --git a/src/qt/manager/qt_manager_mainwindow.cpp b/src/qt/manager/qt_manager_mainwindow.cpp new file mode 100644 index 0000000000..720c53ec3e --- /dev/null +++ b/src/qt/manager/qt_manager_mainwindow.cpp @@ -0,0 +1,734 @@ +#include "qt_manager_mainwindow.h" +#include "ui_qt_manager_mainwindow.h" + +#include "qt_manager_vmadddialog.h" +#include "ui_qt_manager_vmadddialog.h" +#include "qt_manager_settings.h" + +#include "../qt_settings.hpp" + +#ifdef Q_OS_WINDOWS +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include <86box/86box.h> +#include <86box/config.h> +#include <86box/plat.h> +#include <86box/path.h> +#include <86box/ui.h> +#include <86box/video.h> +#include <86box/version.h> +} + +ManagerMainWindow::ManagerMainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::ManagerMainWindow) + , server(new QLocalServer(this)) +{ + ui->setupUi(this); + + statusBar()->show(); + statusBarLabel = new QLabel; + statusBar()->addWidget(statusBarLabel); + config_load_global(); + server->listen(QApplication::applicationFilePath() + QString::asprintf(":%lld", (qint64)QApplication::applicationPid())); + ui->tableWidget->setShowGrid(manager_config.enable_grid_lines); + + trayIcon = new QSystemTrayIcon(qApp->windowIcon(), this); + + connect(trayIcon, &QSystemTrayIcon::activated, this, [this] (QSystemTrayIcon::ActivationReason reason) { + if (reason != QSystemTrayIcon::Context && reason != QSystemTrayIcon::Unknown) + showNormal(); + }); + + trayIcon->show(); + + auto menu = new QMenu(this); + menu->addAction(tr("Show 86Box"), this, [this] () { + showNormal(); + }); + + menu->addAction(tr("Settings"), this, [this] () { + showNormal(); + ui->pushButtonSettings->click(); + }); + + menu->addSeparator(); + menu->addAction(tr("Exit"), this, [this] () { + bool running = false; + for (int i = 0; i < 256; i++) { + running |= (processes[i].state() == QProcess::Running); + } + + if (running) { + showNormal(); + auto res = QMessageBox::warning(this, tr("Virtual machines are still running"), tr("Some virtual machines are still running. It's recommended you stop them first before closing 86Box Manager. Do you want to stop them now?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + switch (res) { + default: + case QMessageBox::Cancel: + return; + case QMessageBox::No: + { + for (int i = 0; i < 256; i++) { + if (sockets[i]) { + sockets[i]->close(); + delete sockets[i]; + sockets[i] = nullptr; + } + processes[i].detach(); + } + QApplication::quit(); + return; + } + case QMessageBox::Yes: + { + QApplication::quit(); + return; + } + } + } else { + QApplication::quit(); + } + }); + + trayIcon->setContextMenu(menu); + + connect(ui->actionHard_reset, &QAction::triggered, ui->pushButtonReset, &QPushButton::clicked); + connect(ui->actionStart, &QAction::triggered, ui->pushButtonStartStop, &QPushButton::clicked); + connect(ui->actionSend_CTRL_ALT_DEL, &QAction::triggered, ui->pushButtonCAD, &QPushButton::clicked); + connect(ui->actionConfigure, &QAction::triggered, ui->pushButtonConfigure, &QPushButton::clicked); + connect(ui->actionPause, &QAction::triggered, ui->pushButtonPause, &QPushButton::clicked); + + connect(ui->actionEdit, &QAction::triggered, ui->pushButtonEdit, &QPushButton::clicked); + connect(ui->actionRemove, &QAction::triggered, ui->pushButtonRemove, &QPushButton::clicked); + + connect(server, &QLocalServer::newConnection, this, [this] { + auto rowCount = ui->tableWidget->rowCount(); + + for (int i = 0; i < rowCount; i++) { + if (selectedVMIndex == ui->tableWidget->item(i, 0)->data(Qt::UserRole).toInt()) { + sockets[selectedVMIndex] = server->nextPendingConnection(); + connect(sockets[selectedVMIndex], &QLocalSocket::readyRead, this, [this, i] () { + auto VMIndex = ui->tableWidget->item(i, 0)->data(Qt::UserRole).toInt(); + while (sockets[VMIndex]->canReadLine()) { + auto byteArray = sockets[VMIndex]->readLine(); + switch(byteArray[0]) { + case '0': + blocked[VMIndex] = false; + break; + case '1': + blocked[VMIndex] = true; + break; + case '2': + paused[VMIndex] = false; + break; + case '3': + paused[VMIndex] = true; + break; + } + if (blocked[VMIndex]) + ui->tableWidget->item(i, 1)->setText(tr("Waiting")); + else + ui->tableWidget->item(i, 1)->setText(paused[VMIndex] ? tr("Paused") : tr("Running")); + ui->tableWidget->setCurrentItem(ui->tableWidget->item(i, 0)); + emit ui->tableWidget->itemPressed(ui->tableWidget->item(i, 0)); + } + }); + setDisabled(false); + ui->tableWidget->item(i, 1)->setText(tr("Running")); + blocked[selectedVMIndex] = false; + ui->tableWidget->setCurrentItem(ui->tableWidget->item(i, 0)); + emit ui->tableWidget->itemPressed(ui->tableWidget->item(i, 0)); + if (manager_config.minimize_when_vm_started) + showMinimized(); + return; + } + } + }); + + ui->pushButtonStartStop->setDisabled(true); + ui->pushButtonConfigure->setDisabled(true); + ui->pushButtonCAD->setDisabled(true); + ui->pushButtonEdit->setDisabled(true); + ui->pushButtonPause->setDisabled(true); + ui->pushButtonReset->setDisabled(true); + ui->pushButtonRemove->setDisabled(true); + ui->menuOptions->setDisabled(true); + for (int i = 0; i < 256; i++) { + if (manager_config.vm[i].name[0]) { + ui->tableWidget->insertRow(ui->tableWidget->rowCount()); + int row = ui->tableWidget->rowCount() - 1; + + // Name. + auto tableItem = new QTableWidgetItem(manager_config.vm[i].name); + tableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + tableItem->setData(Qt::UserRole, i); + ui->tableWidget->setItem(row, 0, tableItem); + // Status + tableItem = new QTableWidgetItem(tr("Stopped")); + tableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + tableItem->setData(Qt::UserRole, i); + ui->tableWidget->setItem(row, 1, tableItem); + // Description. + tableItem = new QTableWidgetItem(manager_config.vm[i].description); + tableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + tableItem->setData(Qt::UserRole, i); + ui->tableWidget->setItem(row, 2, tableItem); + // Path. + tableItem = new QTableWidgetItem(manager_config.vm[i].path); + tableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + tableItem->setData(Qt::UserRole, i); + ui->tableWidget->setItem(row, 3, tableItem); + } + connect(&processes[i], &QProcess::errorOccurred, this, [this, i] (QProcess::ProcessError error) { + if (error == QProcess::FailedToStart || error == QProcess::Crashed) { + if (sockets[i]) { + delete sockets[i]; + sockets[i] = nullptr; + } + auto rowCount = ui->tableWidget->rowCount(); + + for (int c = 0; c < rowCount; c++) { + if (i == ui->tableWidget->item(c, 0)->data(Qt::UserRole).toInt()) { + ui->tableWidget->item(c, 1)->setText(tr("Stopped")); + ui->tableWidget->setCurrentItem(ui->tableWidget->item(c, 0)); + emit ui->tableWidget->itemPressed(ui->tableWidget->item(i, 0)); + } + } + + paused[i] = false; + blocked[i] = false; + this->setDisabled(false); + } + }); + connect(&processes[i], &QProcess::finished, this, [this, i] (int exitCode, QProcess::ExitStatus status) { + if (sockets[i]) { + delete sockets[i]; + sockets[i] = nullptr; + } + auto rowCount = ui->tableWidget->rowCount(); + + for (int c = 0; c < rowCount; c++) { + if (i == ui->tableWidget->item(c, 0)->data(Qt::UserRole).toInt()) { + ui->tableWidget->item(c, 1)->setText(tr("Stopped")); + ui->tableWidget->setCurrentItem(ui->tableWidget->item(c, 0)); + emit ui->tableWidget->itemPressed(ui->tableWidget->item(i, 0)); + } + } + + paused[i] = false; + blocked[i] = false; + this->setDisabled(false); + }); + blocked[i] = false; + paused[i] = false; + } + connect(qApp, &QApplication::aboutToQuit, this, [this] () { + for (int i = 0; i < 256; i++) { + if (sockets[i] && processes[i].state() == QProcess::Running) { + sockets[i]->write("shutdownnoprompt\n"); + processes[i].waitForFinished(500); + sockets[i]->close(); + delete sockets[i]; + sockets[i] = nullptr; + } + } + }); +#ifndef Q_OS_WINDOWS + ui->actionCreate_a_desktop_shortcut->setDisabled(true); +#endif +} + +ManagerMainWindow::~ManagerMainWindow() +{ + config_save_global(); + delete ui; +} + +void ManagerMainWindow::closeEvent(QCloseEvent* event) +{ + if (manager_config.close_to_tray_icon) { + hide(); + event->ignore(); + return; + } + bool running = false; + for (int i = 0; i < 256; i++) { + running |= (processes[i].state() == QProcess::Running); + } + + if (running) { + showNormal(); + auto res = QMessageBox::warning(this, tr("Virtual machines are still running"), tr("Some virtual machines are still running. It's recommended you stop them first before closing 86Box Manager. Do you want to stop them now?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + switch (res) { + default: + case QMessageBox::Cancel: + event->ignore(); + return; + case QMessageBox::No: + { + for (int i = 0; i < 256; i++) { + if (sockets[i]) { + sockets[i]->close(); + delete sockets[i]; + sockets[i] = nullptr; + } + processes[i].detach(); + } + event->accept(); + return; + } + case QMessageBox::Yes: + { + event->accept(); + return; + } + } + } + event->accept(); +} + +bool ManagerMainWindow::event(QEvent* event) +{ + if (event->type() == QEvent::WindowStateChange) { + bool res = QMainWindow::event(event); + if ((windowState() & Qt::WindowMinimized) && manager_config.minimize_to_tray_icon) { + hide(); + trayIcon->show(); + } + return res; + } + return QMainWindow::event(event); +} + +void ManagerMainWindow::refreshVM(int i) +{ + if (manager_config.vm[i].name[0]) { + ui->tableWidget->insertRow(ui->tableWidget->rowCount()); + int row = ui->tableWidget->rowCount() - 1; + + // Name. + auto tableItem = new QTableWidgetItem(manager_config.vm[i].name); + tableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + tableItem->setData(Qt::UserRole, i); + ui->tableWidget->setItem(row, 0, tableItem); + // Status + tableItem = new QTableWidgetItem(tr("Stopped")); + tableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + tableItem->setData(Qt::UserRole, i); + ui->tableWidget->setItem(row, 1, tableItem); + // Description. + tableItem = new QTableWidgetItem(manager_config.vm[i].description); + tableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + tableItem->setData(Qt::UserRole, i); + ui->tableWidget->setItem(row, 2, tableItem); + // Path. + tableItem = new QTableWidgetItem(manager_config.vm[i].path); + tableItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + tableItem->setData(Qt::UserRole, i); + ui->tableWidget->setItem(row, 3, tableItem); + ui->menuOptions->setDisabled(false); + } else { + auto rowCount = ui->tableWidget->rowCount(); + + for (int c = 0; c < rowCount; c++) { + if (i == ui->tableWidget->item(c, 0)->data(Qt::UserRole).toInt()) { + if (c > 0) { + ui->tableWidget->setCurrentItem(ui->tableWidget->item(c - 1, 0)); + emit ui->tableWidget->itemPressed(ui->tableWidget->item(c - 1, 0)); + } + ui->tableWidget->removeRow(c); + if (ui->tableWidget->rowCount() == 0) { + ui->pushButtonStartStop->setDisabled(true); + ui->pushButtonConfigure->setDisabled(true); + ui->pushButtonCAD->setDisabled(true); + ui->pushButtonEdit->setDisabled(true); + ui->pushButtonPause->setDisabled(true); + ui->pushButtonReset->setDisabled(true); + ui->pushButtonRemove->setDisabled(true); + ui->menuOptions->setDisabled(true); + } + break; + } + } + } + config_save_global(); +} + +void ManagerMainWindow::on_tableWidget_currentItemChanged(QTableWidgetItem *item, QTableWidgetItem *oldItem) +{ + if (!item) { + ui->pushButtonStartStop->setDisabled(true); + ui->pushButtonConfigure->setDisabled(true); + ui->pushButtonCAD->setDisabled(true); + ui->pushButtonEdit->setDisabled(true); + ui->pushButtonPause->setDisabled(true); + ui->pushButtonReset->setDisabled(true); + ui->pushButtonRemove->setDisabled(true); + ui->actionKill->setDisabled(true); + ui->actionWipe->setDisabled(true); + ui->menuOptions->setDisabled(true); + selectedVMIndex = 0; + updateMenu(); + return; + } + ui->menuOptions->setDisabled(false); + this->selectedVMIndex = item->data(Qt::UserRole).toInt(); + std::set indexes; + for (auto &curItem : item->tableWidget()->selectedItems()) { + indexes.emplace(curItem->data(Qt::UserRole).toInt()); + } + multipleSelected = indexes.size() > 1; + if (multipleSelected) { + ui->pushButtonStartStop->setDisabled(true); + ui->pushButtonConfigure->setDisabled(true); + ui->pushButtonCAD->setDisabled(true); + ui->pushButtonEdit->setDisabled(true); + ui->pushButtonPause->setDisabled(true); + ui->pushButtonReset->setDisabled(true); + } else { + ui->pushButtonStartStop->setEnabled(true); + ui->pushButtonConfigure->setEnabled(true); + if (blocked[selectedVMIndex]) { + ui->pushButtonStartStop->setDisabled(true); + ui->pushButtonConfigure->setDisabled(true); + ui->pushButtonCAD->setDisabled(true); + ui->pushButtonEdit->setDisabled(true); + ui->pushButtonPause->setDisabled(true); + ui->pushButtonReset->setDisabled(true); + ui->pushButtonRemove->setDisabled(true); + } else if (processes[selectedVMIndex].state() == QProcess::ProcessState::Running || processes[selectedVMIndex].state() == QProcess::ProcessState::Starting) { + ui->pushButtonEdit->setDisabled(true); + ui->pushButtonRemove->setDisabled(true); + ui->pushButtonCAD->setEnabled(true); + ui->pushButtonPause->setEnabled(true); + ui->pushButtonReset->setEnabled(true); + ui->pushButtonStartStop->setText(tr("Stop")); + ui->pushButtonPause->setText(paused[selectedVMIndex] ? tr("Resume") : tr("Pause")); + } else { + ui->pushButtonCAD->setDisabled(true); + ui->pushButtonPause->setDisabled(true); + ui->pushButtonReset->setDisabled(true); + ui->pushButtonEdit->setDisabled(false); + ui->pushButtonRemove->setDisabled(false); + ui->pushButtonStartStop->setText(tr("Start")); + } + } + updateMenu(); +} + + +void ManagerMainWindow::on_pushButtonConfigure_clicked() +{ + if (sockets[selectedVMIndex]) { + sockets[selectedVMIndex]->write("showsettings\n"); + return; + } + cfg_path[0] = 0; + snprintf(cfg_path, sizeof(cfg_path) - 1, "%s", manager_config.vm[this->selectedVMIndex].path); + memcpy(usr_path, cfg_path, sizeof(usr_path)); + QDir(usr_path).mkpath("."); + strncat(cfg_path, CONFIG_FILE, sizeof(CONFIG_FILE) - 1); + // Load configuration. + config_load(); + Settings settings(this); + if (settings.exec() == QDialog::Accepted) { + settings.save(); + config_save(); + } +} + + +void ManagerMainWindow::on_pushButtonStartStop_clicked() +{ + if (processes[selectedVMIndex].state() == QProcess::ProcessState::NotRunning) { + auto envList = QProcessEnvironment::systemEnvironment(); + QStringList argList = { + "-P", + QString(manager_config.vm[selectedVMIndex].path), + "-V", + QString(manager_config.vm[selectedVMIndex].name) + }; + if (manager_config.enable_logging) { + argList.push_back("-L"); + if (manager_config.logging_path[0] == 0) { + argList.push_back(QString(manager_config.vm[selectedVMIndex].path) + QString("/86Box.log")); + } else { + argList.push_back(manager_config.logging_path); + } + } + envList.insert(QStringLiteral("86BOX_MANAGER_SOCKET"), server->serverName()); + this->setDisabled(true); + paused[selectedVMIndex] = false; + processes[selectedVMIndex].setProcessEnvironment(envList); + processes[selectedVMIndex].start(QApplication::applicationFilePath(), + { + "-P", + QString(manager_config.vm[selectedVMIndex].path), + "-V", + QString(manager_config.vm[selectedVMIndex].name) + }); + } else if (processes[selectedVMIndex].state() == QProcess::ProcessState::Running && sockets[selectedVMIndex]) { + sockets[selectedVMIndex]->write("shutdown\n"); + } +} + + +void ManagerMainWindow::on_pushButtonCAD_clicked() +{ + if (sockets[selectedVMIndex]) { + sockets[selectedVMIndex]->write("cad\n"); + } +} + + +void ManagerMainWindow::on_pushButtonPause_clicked() +{ + if (sockets[selectedVMIndex]) { + sockets[selectedVMIndex]->write("pause\n"); + } +} + + +void ManagerMainWindow::on_tableWidget_cellChanged(int row, int column) +{ + if (column == 1) { + unsigned int stopped = 0, running = 0, paused = 0, waiting = 0; + + for (int i = 0; i < 256; i++) { + if (ui->tableWidget->item(i, 0) && ui->tableWidget->item(i, 0)->data(Qt::UserRole) == i) { + if (processes[i].state() == QProcess::NotRunning) + stopped++; + else if (this->blocked[i]) + waiting++; + else if (this->paused[i]) { + paused++; + } else { + running++; + } + } + } + + statusBarLabel->setText(QString(tr("All VMs: %1 | Running: %2 | Paused: %3 | Waiting: %4 | Stopped: %5").arg(ui->tableWidget->rowCount()).arg(running).arg(paused).arg(waiting).arg(stopped))); + } +} + + +void ManagerMainWindow::on_pushButtonAdd_clicked() +{ + auto adddialog = ManagerVMAddDialog(this, -1, -1); + auto res = adddialog.exec(); + if (res == QDialog::Accepted) { + if (adddialog.ui->checkBoxConfigureVM->isChecked()) { + on_pushButtonConfigure_clicked(); + } + if (adddialog.ui->checkBoxStartVM->isChecked()) { + on_pushButtonStartStop_clicked(); + } + } +} + + +void ManagerMainWindow::on_pushButtonRemove_clicked() +{ + QMessageBox questionBox(QMessageBox::Icon::Warning, tr("Remove virtual machine"), tr("Are you sure you want to remove the virtual machine \"%1\"?").arg(QString(manager_config.vm[selectedVMIndex].name)), QMessageBox::Yes | QMessageBox::No, this); + if (questionBox.exec()) { + if (questionBox.result() == QMessageBox::Yes) { + QString origName = manager_config.vm[selectedVMIndex].name; + QString origPath = manager_config.vm[selectedVMIndex].path; + manager_config.vm[selectedVMIndex].name[0] = 0; + refreshVM(selectedVMIndex); + if (QMessageBox::information(this, tr("Virtual machine removed"), tr("Virtual machine \"%1\" was successfully removed. Would you like to delete its files as well?").arg(origName), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (!QDir(origPath).removeRecursively()) { + QMessageBox::critical(this, tr("Error"), tr("86Box was unable to delete the files of this virtual machine.")); + } + } + } + } +} + + +void ManagerMainWindow::on_pushButtonEdit_clicked() +{ + ManagerVMAddDialog(this, selectedVMIndex, ui->tableWidget->currentRow()).exec(); +} + + +void ManagerMainWindow::on_tableWidget_itemPressed(QTableWidgetItem *item) +{ + on_tableWidget_currentItemChanged(item, item); +} + + +void ManagerMainWindow::on_pushButtonSettings_clicked() +{ + ManagerSettings settings(this); + settings.exec(); + if (settings.result() == QDialog::Accepted) { + ui->tableWidget->setShowGrid(manager_config.enable_grid_lines); + } + this->setEnabled(true); +} + +void ManagerMainWindow::updateMenu() +{ + ui->actionStart->setEnabled(ui->pushButtonStartStop->isEnabled()); + ui->actionStart->setText(ui->pushButtonStartStop->text()); + ui->actionPause->setEnabled(ui->pushButtonPause->isEnabled()); + ui->actionPause->setText(ui->pushButtonPause->text()); + + ui->actionSend_CTRL_ALT_DEL->setEnabled(ui->pushButtonCAD->isEnabled()); + ui->actionConfigure->setEnabled(ui->pushButtonConfigure->isEnabled()); + ui->actionHard_reset->setEnabled(ui->pushButtonReset->isEnabled()); + ui->actionEdit->setEnabled(ui->pushButtonEdit->isEnabled()); + ui->actionRemove->setEnabled(ui->pushButtonRemove->isEnabled()); + ui->actionClone->setEnabled(ui->pushButtonAdd->isEnabled()); + + ui->actionKill->setEnabled(processes[selectedVMIndex].state() != QProcess::NotRunning); + ui->actionWipe->setEnabled(processes[selectedVMIndex].state() == QProcess::NotRunning); +} + +void ManagerMainWindow::on_pushButtonAbout_clicked() +{ + QMessageBox msgBox; + msgBox.setTextFormat(Qt::RichText); + QString versioninfo; +#ifdef EMU_GIT_HASH + versioninfo = QString(" [%1]").arg(EMU_GIT_HASH); +#endif +#ifdef USE_DYNAREC +# ifdef USE_NEW_DYNAREC +# define DYNAREC_STR "new dynarec" +# else +# define DYNAREC_STR "old dynarec" +# endif +#else +# define DYNAREC_STR "no dynarec" +#endif + versioninfo.append(QString(" [%1, %2]").arg(QSysInfo::buildCpuArchitecture(), tr(DYNAREC_STR))); + msgBox.setText(QString("%3%1%2").arg(EMU_VERSION_FULL, versioninfo, tr("86Box v"))); + msgBox.setInformativeText(tr("An emulator of old computers\n\nAuthors: Miran Grča (OBattler), RichardG867, Jasmine Iwanek, TC1995, coldbrewed, Teemu Korhonen (Manaatti), Joakim L. Gilje, Adrien Moulin (elyosh), Daniel Balsom (gloriouscow), Cacodemon345, Fred N. van Kempen (waltje), Tiseno100, reenigne, and others.\n\nWith previous core contributions from Sarah Walker, leilei, JohnElliott, greatpsycho, and others.\n\nReleased under the GNU General Public License version 2 or later. See LICENSE for more information.")); + msgBox.setWindowTitle("About 86Box"); + msgBox.addButton("OK", QMessageBox::ButtonRole::AcceptRole); + auto webSiteButton = msgBox.addButton(EMU_SITE, QMessageBox::ButtonRole::HelpRole); + webSiteButton->connect(webSiteButton, &QPushButton::released, []() { + QDesktopServices::openUrl(QUrl("https://" EMU_SITE)); + }); +#ifdef RELEASE_BUILD + msgBox.setIconPixmap(QIcon(":/settings/win/icons/86Box-green.ico").pixmap(32, 32)); +#elif defined ALPHA_BUILD + msgBox.setIconPixmap(QIcon(":/settings/win/icons/86Box-red.ico").pixmap(32, 32)); +#elif defined BETA_BUILD + msgBox.setIconPixmap(QIcon(":/settings/win/icons/86Box-yellow.ico").pixmap(32, 32)); +#else + msgBox.setIconPixmap(QIcon(":/settings/win/icons/86Box-gray.ico").pixmap(32, 32)); +#endif + msgBox.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + msgBox.exec(); +} + + +void ManagerMainWindow::on_actionClone_triggered() +{ + auto adddialog = ManagerVMAddDialog(this, -1, -1, selectedVMIndex); + auto res = adddialog.exec(); + if (res == QDialog::Accepted) { + if (adddialog.ui->checkBoxConfigureVM->isChecked()) { + on_pushButtonConfigure_clicked(); + } + if (adddialog.ui->checkBoxStartVM->isChecked()) { + on_pushButtonStartStop_clicked(); + } + } +} + + +void ManagerMainWindow::on_actionOpen_folder_of_VM_triggered() +{ + QDir(manager_config.vm[selectedVMIndex].path).mkpath("."); + if (!QDesktopServices::openUrl(QString("file://") + manager_config.vm[selectedVMIndex].path)) { + QMessageBox::critical(this, tr("Error"), tr("The folder for the virtual machine \"%1\" could not be opened. Make sure it still exists and that you have sufficient privileges to access it.").arg(manager_config.vm[selectedVMIndex].name)); + } +} + + +void ManagerMainWindow::on_actionOpen_config_file_triggered() +{ + QDir dir(manager_config.vm[selectedVMIndex].path); + dir.mkpath("."); + if (!QDesktopServices::openUrl(QString("file://") + manager_config.vm[selectedVMIndex].path + QString("/") + CONFIG_FILE)) { + QMessageBox::critical(this, tr("Error"), tr("The config file for the virtual machine \"%1\" could not be opened. Make sure it still exists and that you have sufficient privileges to access it.").arg(manager_config.vm[selectedVMIndex].name)); + } +} + + +void ManagerMainWindow::on_actionKill_triggered() +{ + if (QMessageBox::warning(this, tr("Warning"), tr("Killing a virtual machine can cause data loss. Only do this if it gets stuck.\n\nDo you really wish to kill the virtual machine \"%1\"?").arg(manager_config.vm[selectedVMIndex].name), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (sockets[selectedVMIndex]) { + sockets[selectedVMIndex]->close(); + } + processes[selectedVMIndex].kill(); + emit processes[selectedVMIndex].finished(-1, QProcess::NormalExit); + } +} + + +void ManagerMainWindow::on_actionWipe_triggered() +{ + if (QMessageBox::warning(this, tr("Warning"), tr("Wiping a virtual machine deletes its configuration and nvr files. You'll have to reconfigure the virtual machine (and the BIOS if applicable).\n\n Are you sure you wish to wipe the virtual machine \"%1\"?").arg(manager_config.vm[selectedVMIndex].name), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + bool success = false; + QDir dir(manager_config.vm[selectedVMIndex].path); + success = dir.remove(CONFIG_FILE) && QDir(dir.canonicalPath() + "/nvr/").removeRecursively(); + if (success) + QMessageBox::critical(this, tr("Error"), tr("An error occurred trying to wipe the virtual machine \"%1\".").arg(manager_config.vm[selectedVMIndex].name)); + else + QMessageBox::information(this, tr("Success"), tr("The virtual machine \"%1\" was successfully wiped").arg(manager_config.vm[selectedVMIndex].name)); + } +} + + +void ManagerMainWindow::on_actionCreate_a_desktop_shortcut_triggered() +{ +#ifdef Q_OS_WINDOWS + IShellLinkW* lnk = NULL; + (void)CoInitialize(NULL); + + auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&lnk); + if (SUCCEEDED(hres)) { + IPersistFile* ppf = NULL; + + lnk->SetPath(QApplication::applicationFilePath().toStdWString().c_str()); + lnk->SetArguments(QString("-S -P \"%1\"").arg(manager_config.vm[selectedVMIndex].path).replace("/", "\\").toStdWString().c_str()); + lnk->SetWorkingDirectory(QApplication::applicationDirPath().replace("/", "\\").toStdWString().c_str()); + lnk->SetDescription(QString(manager_config.vm[selectedVMIndex].description).toStdWString().c_str()); + lnk->SetIconLocation(QApplication::applicationFilePath().toStdWString().c_str(), 0); + hres = lnk->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); + if (SUCCEEDED(hres)) { + hres = ppf->Save((QStandardPaths::standardLocations(QStandardPaths::DesktopLocation)[0].replace('/', '\\') + QString("\\") + QString(manager_config.vm[selectedVMIndex].name) + ".lnk").toStdWString().c_str(), TRUE); + QMessageBox::information(this, tr("Success"), tr("A desktop shortcut for the virtual machine \"%1\" was successfully created.").arg(manager_config.vm[selectedVMIndex].name)); + ppf->Release(); + } + else + QMessageBox::critical(this, tr("Error"), tr("A desktop shortcut for the virtual machine \"%1\" could not be created.").arg(manager_config.vm[selectedVMIndex].name)); + lnk->Release(); + } else + QMessageBox::critical(this, tr("Error"), tr("A desktop shortcut for the virtual machine \"%1\" could not be created.").arg(manager_config.vm[selectedVMIndex].name)); +#endif +} + diff --git a/src/qt/manager/qt_manager_mainwindow.h b/src/qt/manager/qt_manager_mainwindow.h new file mode 100644 index 0000000000..53c3987e49 --- /dev/null +++ b/src/qt/manager/qt_manager_mainwindow.h @@ -0,0 +1,85 @@ +#ifndef QT_MANAGER_MAINWINDOW_H +#define QT_MANAGER_MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ui { +class ManagerMainWindow; +} + +class QDetachableProcess : public QProcess +{ +public: + QDetachableProcess(QObject *parent = 0) : QProcess(parent){} + void detach() + { + this->waitForStarted(); + setProcessState(QProcess::NotRunning); + } +}; + +class ManagerMainWindow : public QMainWindow { + Q_OBJECT + +public: + explicit ManagerMainWindow(QWidget *parent = nullptr); + ~ManagerMainWindow(); + + void refreshVM(int i); + +private: + void updateMenu(); + +protected: + void closeEvent(QCloseEvent* event) override; + bool event(QEvent* event) override; + +private slots: + void on_tableWidget_currentItemChanged(QTableWidgetItem *item, QTableWidgetItem *oldItem); + void on_pushButtonConfigure_clicked(); + void on_pushButtonStartStop_clicked(); + void on_pushButtonCAD_clicked(); + void on_pushButtonPause_clicked(); + void on_tableWidget_cellChanged(int row, int column); + void on_pushButtonAdd_clicked(); + void on_pushButtonRemove_clicked(); + void on_pushButtonEdit_clicked(); + void on_tableWidget_itemPressed(QTableWidgetItem *item); + void on_pushButtonSettings_clicked(); + void on_pushButtonAbout_clicked(); + void on_actionClone_triggered(); + + void on_actionOpen_folder_of_VM_triggered(); + + void on_actionOpen_config_file_triggered(); + + void on_actionKill_triggered(); + + void on_actionWipe_triggered(); + + void on_actionCreate_a_desktop_shortcut_triggered(); + +private: + Ui::ManagerMainWindow *ui; + QLocalServer* server; + // Mapped by index; + std::array processes; + std::array paused, blocked; + QMap sockets; + int selectedVMIndex; + int multipleSelected; + QLabel* statusBarLabel; + QSystemTrayIcon* trayIcon; + QMenu* trayIconMenu; + + friend class ManagerVMAddDialog; +}; + +#endif // QT_MANAGER_MAINWINDOW_H diff --git a/src/qt/manager/qt_manager_mainwindow.ui b/src/qt/manager/qt_manager_mainwindow.ui new file mode 100644 index 0000000000..c174b06976 --- /dev/null +++ b/src/qt/manager/qt_manager_mainwindow.ui @@ -0,0 +1,309 @@ + + + ManagerMainWindow + + + + 0 + 0 + 1156 + 588 + + + + 86Box + + + true + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + 0 + + + 4 + + + false + + + true + + + true + + + false + + + + Name + + + + + Status + + + + + Description + + + + + Path + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Start + + + + + + + Reset this virtual machine + + + Reset + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 30 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Pause this virtual machine + + + Pause + + + + + + + Change the configuration of this virtual machine + + + Configure + + + + + + + Edit the properties of this virtual machine + + + Edit + + + + + + + Add a new or an existing virtual machine + + + Add + + + + + + + Remove this virtual machine + + + Remove + + + + + + + Send the CTRL+ALT+DEL keystroke to this virtual machine + + + C+A+D + + + + + + + About + + + + + + + Open manager-related settings + + + Settings + + + + + + + + + + + + 0 + 0 + 1156 + 21 + + + + + Options + + + + + + + + + + + + + + + + + + + + + + + Start + + + + + Configure + + + + + Pause + + + + + Send CTRL+ALT+DEL + + + + + Hard reset + + + + + Kill + + + + + Wipe + + + + + Edit + + + + + Clone + + + + + Remove + + + + + Open folder of VM + + + + + Open config file + + + + + Create a desktop shortcut + + + + + + diff --git a/src/qt/manager/qt_manager_settings.cpp b/src/qt/manager/qt_manager_settings.cpp new file mode 100644 index 0000000000..cb7fa1b68a --- /dev/null +++ b/src/qt/manager/qt_manager_settings.cpp @@ -0,0 +1,192 @@ +#include "qt_manager_settings.h" +#include "ui_qt_manager_settings.h" + +#include +#include +#include +#include + +extern "C" { +#include <86box/86box.h> +#include <86box/config.h> +#include <86box/plat.h> +#include <86box/path.h> +#include <86box/ui.h> +#include <86box/video.h> +} + +ManagerSettings::ManagerSettings(QWidget *parent) + : QDialog(parent) + , ui(new Ui::ManagerSettings) +{ + ui->setupUi(this); + + ui->checkBoxCloseTrayIcon->setChecked(!!manager_config.close_to_tray_icon); + ui->checkBoxMinimizeTrayIcon->setChecked(!!manager_config.minimize_to_tray_icon); + ui->checkBoxMinimizeVMStarted->setChecked(!!manager_config.minimize_when_vm_started); + ui->checkBoxEnableGridLines->setChecked(!!manager_config.enable_grid_lines); + ui->checkBoxLoggingEnabled->setChecked(!!manager_config.enable_logging); + ui->lineEditLoggingPath->setEnabled(!!manager_config.enable_logging); + + ui->lineEditLoggingPath->setText(manager_config.logging_path); + ui->lineEditVMPath->setText(manager_config.vms_path); + + ui->lineEditLoggingPath->setValidator(new QRegularExpressionValidator(QRegularExpression("[^\\\\/:*?\"<>|]*"))); + ui->lineEditVMPath->setValidator(new QRegularExpressionValidator(QRegularExpression("[^\\\\/:*?\"<>|]*"))); + + connect(this, &ManagerSettings::accepted, this, &ManagerSettings::save); + + applyButton = ui->buttonBox->button(QDialogButtonBox::Apply); + applyButton->setDisabled(true); + + this->setEnabled(true); +} + +ManagerSettings::~ManagerSettings() +{ + delete ui; +} + +void ManagerSettings::reject() +{ + bool changed = settingsChanged(); + + if (changed) { + auto res = QMessageBox::question(this, tr("Unsaved changes"), tr("Would you like to save the changes you've made to the settings?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + switch (res) { + default: + case QMessageBox::Cancel: + return; + case QMessageBox::No: + QDialog::reject(); + return; + case QMessageBox::Yes: + save(); + QDialog::accept(); + return; + } + } else + QDialog::reject(); +} + +void ManagerSettings::save() +{ + manager_config.close_to_tray_icon = ui->checkBoxCloseTrayIcon->isChecked(); + manager_config.minimize_to_tray_icon = ui->checkBoxMinimizeTrayIcon->isChecked(); + manager_config.minimize_when_vm_started = ui->checkBoxMinimizeVMStarted->isChecked(); + manager_config.enable_grid_lines = ui->checkBoxEnableGridLines->isChecked(); + manager_config.enable_logging = ui->checkBoxLoggingEnabled->isChecked(); + + strncpy(manager_config.vms_path, ui->lineEditVMPath->text().toUtf8(), sizeof(manager_config.vms_path)); + strncpy(manager_config.logging_path, ui->lineEditLoggingPath->text().toUtf8(), sizeof(manager_config.logging_path)); + config_save_global(); + + applyButton->setDisabled(true); +} + +bool ManagerSettings::settingsChanged() +{ + bool changed = false; + auto vmPath = ui->lineEditVMPath->text().toUtf8(); + auto loggingPath = ui->lineEditLoggingPath->text().toUtf8(); + + changed |= (manager_config.close_to_tray_icon != ui->checkBoxCloseTrayIcon->isChecked()); + changed |= (manager_config.minimize_to_tray_icon != ui->checkBoxMinimizeTrayIcon->isChecked()); + changed |= (manager_config.minimize_when_vm_started != ui->checkBoxMinimizeVMStarted->isChecked()); + changed |= (manager_config.enable_grid_lines != ui->checkBoxEnableGridLines->isChecked()); + changed |= (manager_config.enable_logging != ui->checkBoxLoggingEnabled->isChecked()); + + changed |= !!strncmp(manager_config.vms_path, ui->lineEditVMPath->text().toUtf8(), sizeof(manager_config.vms_path)); + changed |= !!strncmp(manager_config.logging_path, ui->lineEditLoggingPath->text().toUtf8(), sizeof(manager_config.logging_path)); + + applyButton->setDisabled(!changed); + + return changed; +} + +void ManagerSettings::on_checkBoxLoggingEnabled_clicked(bool checked) +{ + ui->lineEditLoggingPath->setEnabled(checked); +} + +void ManagerSettings::on_buttonBox_clicked(QAbstractButton *button) +{ + if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole) + save(); +} + +void ManagerSettings::on_checkBoxMinimizeTrayIcon_stateChanged(int arg1) +{ + settingsChanged(); +} + +void ManagerSettings::on_checkBoxCloseTrayIcon_stateChanged(int arg1) +{ + settingsChanged(); +} + +void ManagerSettings::on_checkBoxMinimizeVMStarted_stateChanged(int arg1) +{ + settingsChanged(); +} + +void ManagerSettings::on_checkBoxLoggingEnabled_stateChanged(int arg1) +{ + settingsChanged(); +} + +void ManagerSettings::on_checkBoxEnableGridLines_stateChanged(int arg1) +{ + settingsChanged(); +} + +void ManagerSettings::on_pushButtonDefaults_clicked() +{ + if (QMessageBox::warning(this, tr("Settings will be reset"), tr("All settings will be reset to their default values. Do you wish to continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + char vms_path_default[1024] = { 0 }; + + ui->checkBoxCloseTrayIcon->setChecked(0); + ui->checkBoxMinimizeTrayIcon->setChecked(0); + ui->checkBoxMinimizeVMStarted->setChecked(0); + ui->checkBoxEnableGridLines->setChecked(0); + ui->checkBoxLoggingEnabled->setChecked(0); + ui->lineEditLoggingPath->setEnabled(0); + + plat_get_global_config_dir(vms_path_default); + path_slash(vms_path_default); + strncat(vms_path_default, "86Box VMs", sizeof(vms_path_default) - 1); + strncat(vms_path_default, path_get_slash(vms_path_default), sizeof(vms_path_default) - 1); + + ui->lineEditVMPath->setText(vms_path_default); + ui->lineEditLoggingPath->setText(""); + + settingsChanged(); + } +} + +void ManagerSettings::on_lineEditVMPath_textChanged(const QString &arg1) +{ + settingsChanged(); +} + +void ManagerSettings::on_lineEditLoggingPath_textChanged(const QString &arg1) +{ + settingsChanged(); +} + + +void ManagerSettings::on_pushButtonBrowseVMPath_clicked() +{ + auto str = QFileDialog::getExistingDirectory(this, tr("Select a folder where your virtual machines (configs, nvr folders, etc.) will be located"), qApp->applicationDirPath()); + if (str.size()) + ui->lineEditVMPath->setText(str); +} + + +void ManagerSettings::on_pushButton_clicked() +{ + auto str = QFileDialog::getSaveFileName(this, tr("Select a file where 86Box logs will be saved"), qApp->applicationDirPath(), "Log files (*.log)|*.log"); + if (str.size()) + ui->lineEditLoggingPath->setText(str); +} + diff --git a/src/qt/manager/qt_manager_settings.h b/src/qt/manager/qt_manager_settings.h new file mode 100644 index 0000000000..177066c59e --- /dev/null +++ b/src/qt/manager/qt_manager_settings.h @@ -0,0 +1,55 @@ +#ifndef QT_MANAGER_SETTINGS_H +#define QT_MANAGER_SETTINGS_H + +#include +#include +#include + +namespace Ui { +class ManagerSettings; +} + +class ManagerSettings : public QDialog { + Q_OBJECT + +public: + explicit ManagerSettings(QWidget *parent = nullptr); + ~ManagerSettings(); + +protected: + void reject() override; + +private slots: + void on_checkBoxLoggingEnabled_clicked(bool checked); + void save(); + + void on_buttonBox_clicked(QAbstractButton *button); + + void on_checkBoxMinimizeTrayIcon_stateChanged(int arg1); + + void on_checkBoxCloseTrayIcon_stateChanged(int arg1); + + void on_checkBoxMinimizeVMStarted_stateChanged(int arg1); + + void on_checkBoxLoggingEnabled_stateChanged(int arg1); + + void on_checkBoxEnableGridLines_stateChanged(int arg1); + + void on_pushButtonDefaults_clicked(); + + void on_lineEditVMPath_textChanged(const QString &arg1); + + void on_lineEditLoggingPath_textChanged(const QString &arg1); + + void on_pushButtonBrowseVMPath_clicked(); + + void on_pushButton_clicked(); + +private: + Ui::ManagerSettings *ui; + QPushButton* applyButton; + + bool settingsChanged(); +}; + +#endif // QT_MANAGER_SETTINGS_H diff --git a/src/qt/manager/qt_manager_settings.ui b/src/qt/manager/qt_manager_settings.ui new file mode 100644 index 0000000000..a392c03101 --- /dev/null +++ b/src/qt/manager/qt_manager_settings.ui @@ -0,0 +1,210 @@ + + + ManagerSettings + + + + 0 + 0 + 400 + 300 + + + + Settings + + + + QLayout::SetFixedSize + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Defaults + + + + + + + 0 + + + + General + + + + + + Behaviour + + + + + + Minimize to tray icon + + + + + + + Close to tray icon + + + + + + + Minimize when VM is started + + + + + + + + + + Paths + + + + + + + + + VM path: + + + + + + + Browse... + + + + + + + + + + + Advanced + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Logging + + + + + + false + + + + + + + Enable 86Box logging to file: + + + + + + + Browse... + + + + + + + + + + Miscellaneous + + + + + + Enable grid lines in virtual machine list + + + + + + + + + + + + + + + + buttonBox + accepted() + ManagerSettings + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ManagerSettings + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/qt/manager/qt_manager_vmadddialog.cpp b/src/qt/manager/qt_manager_vmadddialog.cpp new file mode 100644 index 0000000000..2544c461a8 --- /dev/null +++ b/src/qt/manager/qt_manager_vmadddialog.cpp @@ -0,0 +1,217 @@ +#include "qt_manager_vmadddialog.h" +#include "ui_qt_manager_vmadddialog.h" + +#include "ui_qt_manager_mainwindow.h" + +#include +#include +#include +#include +#include + +extern "C" { +#include <86box/86box.h> +#include <86box/config.h> +#include <86box/plat.h> +#include <86box/path.h> +#include <86box/ui.h> +#include <86box/video.h> +} + +// https://stackoverflow.com/a/36460740 +// Slightly modified to be more strict. +static bool copyPath(QString sourceDir, QString destinationDir, bool overWriteDirectory) +{ + QDir originDirectory(sourceDir); + + if (! originDirectory.exists()) + { + return false; + } + + QDir destinationDirectory(destinationDir); + + if(destinationDirectory.exists() && !overWriteDirectory) + { + return false; + } + else if(destinationDirectory.exists() && overWriteDirectory) + { + destinationDirectory.removeRecursively(); + } + + originDirectory.mkpath(destinationDir); + + foreach (QString directoryName, originDirectory.entryList(QDir::Dirs | \ + QDir::NoDotAndDotDot)) + { + QString destinationPath = destinationDir + "/" + directoryName; + originDirectory.mkpath(destinationPath); + if (!copyPath(sourceDir + "/" + directoryName, destinationPath, overWriteDirectory)) { + return false; + } + } + + foreach (QString fileName, originDirectory.entryList(QDir::Files)) + { + if (!QFile::copy(sourceDir + "/" + fileName, destinationDir + "/" + fileName)) { + return false; + } + } + + /*! Possible race-condition mitigation? */ + QDir finalDestination(destinationDir); + finalDestination.refresh(); + + if(finalDestination.exists()) + { + return true; + } + + return false; +} + +ManagerVMAddDialog::ManagerVMAddDialog(ManagerMainWindow *parent, int i, int row, int clone) + : QDialog(parent) + , ui(new Ui::ManagerVMAddDialog) +{ + ui->setupUi(this); + + ui->labelPathDisplay->setText(QString(manager_config.vms_path)); + ui->lineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression("[^\\\\/:*?\"<>|]*"))); + ui->lineEdit_3->setValidator(new QRegularExpressionValidator(QRegularExpression("[^\\\\/:*?\"<>|]*"))); + ui->labelClone->hide(); + + managerMainWindow = parent; + + if (i != -1) { + editingVM = true; + VMIndex = i; + VMRow = row; + setWindowTitle(tr("Edit a virtual machine")); + ui->pushButton->setText(tr("Apply")); + ui->lineEdit->setText(manager_config.vm[VMIndex].name); + ui->lineEdit_2->setText(manager_config.vm[VMIndex].description); + ui->lineEdit_3->hide(); + ui->checkBoxImport->hide(); + ui->pushButtonBrowse->hide(); + ui->checkBoxStartVM->hide(); + ui->checkBoxConfigureVM->hide(); + } + + if (clone != -1) { + cloneVM = clone; + ui->lineEdit_3->hide(); + ui->checkBoxImport->hide(); + ui->pushButtonBrowse->hide(); + + ui->checkBoxImport->setChecked(true); + ui->lineEdit_3->setText(manager_config.vm[VMIndex].path); + ui->labelClone->setText(ui->labelClone->text().arg(manager_config.vm[VMIndex].name)); + ui->labelClone->show(); + + setWindowTitle(tr("Clone a virtual machine")); + ui->pushButton->setText(tr("Clone")); + } +} + +ManagerVMAddDialog::~ManagerVMAddDialog() +{ + delete ui; +} + +void ManagerVMAddDialog::on_pushButton_clicked() +{ + if (ui->lineEdit->text().trimmed().size() == 0) { + QMessageBox::critical(this, tr("Error"), tr("You must specify a name")); + return; + } + for (int i = 0; i < 256; i++) { + if (strncmp(manager_config.vm[i].name, QByteArray(this->ui->lineEdit->text().toUtf8()), QByteArray(this->ui->lineEdit->text().toUtf8()).size()) + && QDir(manager_config.vms_path).exists(this->ui->lineEdit->text())) { + QMessageBox::critical(this, tr("Error"), tr("A virtual machine with this name already exists. Please pick a different name.")); + return; + } + } + + if (!editingVM) { + int j = 0; + if (ui->checkBoxImport->isChecked() && ui->lineEdit_3->text().trimmed().size() == 0) { + QMessageBox::critical(this, tr("Error"), tr("If you wish to import VM files, you must specify a path.")); + return; + } + for (int j = 0; j < 256; j++) { + if (!manager_config.vm[j].name[0]) { + strncpy(manager_config.vm[j].name, QByteArray(this->ui->lineEdit->text().trimmed().toUtf8()), QByteArray(this->ui->lineEdit->text().toUtf8()).size()); + auto newPath = (QDir(manager_config.vms_path).canonicalPath() + QStringLiteral("/") + manager_config.vm[j].name) + QString("/"); + strncpy(manager_config.vm[j].description, QByteArray(this->ui->lineEdit_2->text().toUtf8()), QByteArray(this->ui->lineEdit_2->text().toUtf8()).size()); + strncpy(manager_config.vm[j].path, newPath.toUtf8(), newPath.toUtf8().size()); + this->managerMainWindow->refreshVM(j); + if (ui->checkBoxImport->isChecked()) { + bool success = copyPath(ui->lineEdit_3->text().trimmed(), QDir(manager_config.vms_path).canonicalPath() + QStringLiteral("/") + manager_config.vm[j].name, true); + + if (!success) { + QMessageBox::information(this, tr("Success"), tr("Virtual machine \"%1\" was successfully created, but files could not be imported. Make sure the path you selected was correct and valid.\n\nIf the VM is already located in your VMs folder, you don't need to select the Import option, just add a new VM with the same name.").arg(manager_config.vm[j].name)); + } else { + QMessageBox::information(this, tr("Success"), tr("Virtual machine \"%1\" was successfully created, files were imported. Remember to update any paths pointing to disk images in your config!").arg(manager_config.vm[j].name)); + } + } else { + QMessageBox::information(this, tr("Success"), tr("Virtual machine \"%1\" was successfully created!").arg(manager_config.vm[j].name)); + } + break; + } + } + } else { + if (strncmp(manager_config.vm[VMIndex].name, QByteArray(this->ui->lineEdit->text().toUtf8()), QByteArray(this->ui->lineEdit->text().toUtf8()).size())) { + if (!(copyPath(manager_config.vm[VMIndex].path, QDir(manager_config.vms_path).canonicalPath() + QStringLiteral("/") + manager_config.vm[VMIndex].name, true) + && QDir(manager_config.vm[VMIndex].path).removeRecursively())) { + QMessageBox::critical(this, tr("Error"), tr("An error has occurred while trying to move the files for this virtual machine. Please try to move them manually.")); + } + } + strncpy(manager_config.vm[VMIndex].name, QByteArray(this->ui->lineEdit->text().trimmed().toUtf8()), QByteArray(this->ui->lineEdit->text().toUtf8()).size()); + auto newPath = (QDir(manager_config.vms_path).canonicalPath() + QStringLiteral("/") + manager_config.vm[VMIndex].name) + QString("/"); + strncpy(manager_config.vm[VMIndex].description, QByteArray(this->ui->lineEdit_2->text().toUtf8()), QByteArray(this->ui->lineEdit_2->text().toUtf8()).size()); + strncpy(manager_config.vm[VMIndex].path, newPath.toUtf8(), newPath.toUtf8().size()); + //this->managerMainWindow->refreshVM(VMIndex); + + managerMainWindow->ui->tableWidget->item(VMRow, 0)->setText(ui->lineEdit->text()); + managerMainWindow->ui->tableWidget->item(VMRow, 2)->setText(ui->lineEdit_2->text()); + managerMainWindow->ui->tableWidget->item(VMRow, 3)->setText(newPath); + managerMainWindow->ui->tableWidget->setCurrentItem(managerMainWindow->ui->tableWidget->item(VMRow, 0)); + emit managerMainWindow->ui->tableWidget->itemPressed(managerMainWindow->ui->tableWidget->item(VMRow, 0)); + QMessageBox::information(this, tr("Success"), tr("Virtual machine \"%1\" was successfully modified. Please update its configuration so that any absolute paths (e.g. for hard disk images) point to the new folder.").arg(ui->lineEdit->text())); + } + QDialog::accept(); +} + +void ManagerVMAddDialog::on_pushButton_2_clicked() +{ + QDialog::reject(); +} + + +void ManagerVMAddDialog::on_lineEdit_textChanged(const QString &arg1) +{ + if (arg1.size()) + ui->labelPathDisplay->setText(QString(manager_config.vms_path) + arg1 + '/'); + else + ui->labelPathDisplay->setText(QString(manager_config.vms_path)); + + ui->pushButton->setDisabled(arg1.size() == 0); +} + + +void ManagerVMAddDialog::on_checkBoxImport_clicked(bool checked) +{ + ui->lineEdit_3->setEnabled(checked); + ui->pushButtonBrowse->setEnabled(checked); +} + + +void ManagerVMAddDialog::on_pushButtonBrowse_clicked() +{ + auto str = QFileDialog::getExistingDirectory(this, tr("Select a folder where your virtual machines (configs, nvr folders, etc.) will be located"), qApp->applicationDirPath()); + if (str.size()) + ui->lineEdit_3->setText(str); +} + diff --git a/src/qt/manager/qt_manager_vmadddialog.h b/src/qt/manager/qt_manager_vmadddialog.h new file mode 100644 index 0000000000..c73eb8af70 --- /dev/null +++ b/src/qt/manager/qt_manager_vmadddialog.h @@ -0,0 +1,42 @@ +#ifndef QT_MANAGER_VMADDDIALOG_H +#define QT_MANAGER_VMADDDIALOG_H + +#include "qt_manager_mainwindow.h" + +#include + +namespace Ui { +class ManagerVMAddDialog; +} + +class ManagerVMAddDialog : public QDialog { + Q_OBJECT + +public: + ManagerVMAddDialog(ManagerMainWindow *parent = nullptr, int i = -1, int row = -1, int clone = -1); + ~ManagerVMAddDialog(); + +private slots: + void on_pushButton_clicked(); + + void on_pushButton_2_clicked(); + + void on_lineEdit_textChanged(const QString &arg1); + + void on_checkBoxImport_clicked(bool checked); + + void on_pushButtonBrowse_clicked(); + +private: + Ui::ManagerVMAddDialog *ui; + ManagerMainWindow* managerMainWindow; + + bool editingVM = false; + int cloneVM = -1; + int VMIndex = 0; + int VMRow = -1; + + friend class ManagerMainWindow; +}; + +#endif // QT_MANAGER_VMADDDIALOG_H diff --git a/src/qt/manager/qt_manager_vmadddialog.ui b/src/qt/manager/qt_manager_vmadddialog.ui new file mode 100644 index 0000000000..5a06598a8f --- /dev/null +++ b/src/qt/manager/qt_manager_vmadddialog.ui @@ -0,0 +1,139 @@ + + + ManagerVMAddDialog + + + Qt::WindowModal + + + + 0 + 0 + 541 + 250 + + + + + 0 + 0 + + + + Add a virtual machine + + + + QLayout::SetFixedSize + + + + + + + + Name: + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + false + + + + + + + false + + + Browse... + + + + + + + Path: + + + + + + + Description: + + + + + + + Add + + + + + + + + + + Cancel + + + + + + + + + + + + + + Virtual machine "%1" will be cloned into: + + + + + + + Import VM Files from: + + + + + + + Start this virtual machine now + + + + + + + Configure this virtual machine now + + + + + + + + diff --git a/src/qt/qt_main.cpp b/src/qt/qt_main.cpp index 4d02e26011..4c7371f4f9 100644 --- a/src/qt/qt_main.cpp +++ b/src/qt/qt_main.cpp @@ -71,6 +71,8 @@ extern "C" { #include "qt_styleoverride.hpp" #include "qt_unixmanagerfilter.hpp" +#include "./manager/qt_manager_mainwindow.h" + // Void Cast #define VC(x) const_cast(x) @@ -154,6 +156,7 @@ main_thread_fn() static std::thread *main_thread; +UnixManagerSocket* managerSocket = nullptr; int main(int argc, char *argv[]) { @@ -217,6 +220,12 @@ main(int argc, char *argv[]) return 6; } + if (manager_mode) { + ManagerMainWindow manager_main_window; + manager_main_window.show(); + return app.exec(); + } + if (settings_only) { Settings settings; if (settings.exec() == QDialog::Accepted) { @@ -294,19 +303,19 @@ main(int argc, char *argv[]) } #endif - UnixManagerSocket socket; + managerSocket = new UnixManagerSocket(qApp); if (qgetenv("86BOX_MANAGER_SOCKET").size()) { - QObject::connect(&socket, &UnixManagerSocket::showsettings, main_window, &MainWindow::showSettings); - QObject::connect(&socket, &UnixManagerSocket::pause, main_window, &MainWindow::togglePause); - QObject::connect(&socket, &UnixManagerSocket::resetVM, main_window, &MainWindow::hardReset); - QObject::connect(&socket, &UnixManagerSocket::request_shutdown, main_window, &MainWindow::close); - QObject::connect(&socket, &UnixManagerSocket::force_shutdown, []() { + QObject::connect(managerSocket, &UnixManagerSocket::showsettings, main_window, &MainWindow::showSettings); + QObject::connect(managerSocket, &UnixManagerSocket::pause, main_window, &MainWindow::togglePause); + QObject::connect(managerSocket, &UnixManagerSocket::resetVM, main_window, &MainWindow::hardReset); + QObject::connect(managerSocket, &UnixManagerSocket::request_shutdown, main_window, &MainWindow::close); + QObject::connect(managerSocket, &UnixManagerSocket::force_shutdown, []() { do_stop(); emit main_window->close(); }); - QObject::connect(&socket, &UnixManagerSocket::ctrlaltdel, []() { pc_send_cad(); }); - main_window->installEventFilter(&socket); - socket.connectToServer(qgetenv("86BOX_MANAGER_SOCKET")); + QObject::connect(managerSocket, &UnixManagerSocket::ctrlaltdel, []() { pc_send_cad(); }); + main_window->installEventFilter(managerSocket); + managerSocket->connectToServer(qgetenv("86BOX_MANAGER_SOCKET")); } // pc_reset_hard_init(); @@ -355,6 +364,6 @@ main(int argc, char *argv[]) pc_close(nullptr); endblit(); - socket.close(); + managerSocket->close(); return ret; } diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index 6890bd407d..b32eb0b68d 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -48,6 +48,7 @@ #include "qt_mainwindow.hpp" #include "qt_progsettings.hpp" #include "qt_util.hpp" +#include "qt_unixmanagerfilter.hpp" #ifdef Q_OS_UNIX # include @@ -365,6 +366,7 @@ plat_munmap(void *ptr, size_t size) #endif } +extern UnixManagerSocket* managerSocket; void plat_pause(int p) { @@ -377,6 +379,8 @@ plat_pause(int p) if (source_hwnd) PostMessage((HWND) (uintptr_t) source_hwnd, WM_SENDSTATUS, (WPARAM) !!p, (LPARAM) (HWND) main_window->winId()); #endif + if (managerSocket) + managerSocket->write(QByteArray{ !!p ? "3\n" : "2\n" }); return; } @@ -407,6 +411,8 @@ plat_pause(int p) if (source_hwnd) PostMessage((HWND) (uintptr_t) source_hwnd, WM_SENDSTATUS, (WPARAM) !!p, (LPARAM) (HWND) main_window->winId()); #endif + if (managerSocket) + managerSocket->write(QByteArray{ !!p ? "3\n" : "2\n" }); } void diff --git a/src/qt/qt_settings.cpp b/src/qt/qt_settings.cpp index c7cb990868..3613c3549d 100644 --- a/src/qt/qt_settings.cpp +++ b/src/qt/qt_settings.cpp @@ -193,7 +193,7 @@ Settings::save() void Settings::accept() { - if (confirm_save && !settings_only) { + if (confirm_save && !settings_only && !manager_mode) { QMessageBox questionbox(QMessageBox::Icon::Question, "86Box", QStringLiteral("%1\n\n%2").arg(tr("Do you want to save the settings?"), tr("This will hard reset the emulated machine.")), diff --git a/src/qt/qt_unixmanagerfilter.cpp b/src/qt/qt_unixmanagerfilter.cpp index 5d94584e63..fa610709a6 100644 --- a/src/qt/qt_unixmanagerfilter.cpp +++ b/src/qt/qt_unixmanagerfilter.cpp @@ -18,6 +18,9 @@ */ #include "qt_unixmanagerfilter.hpp" +#include "qt_mainwindow.hpp" + +extern MainWindow* main_window; UnixManagerSocket::UnixManagerSocket(QObject *obj) : QLocalSocket(obj) @@ -32,6 +35,17 @@ UnixManagerSocket::readyToRead() QByteArray line = readLine(); if (line.size()) { line.resize(line.size() - 1); + if (line != "shutdownnoprompt") { + /* TODO: This needs a better solution. */ + auto eFlags = main_window->windowFlags(); + main_window->setWindowFlags(eFlags | Qt::WindowStaysOnTopHint); + main_window->show(); + main_window->setWindowFlags(eFlags); + main_window->show(); + + main_window->raise(); //bring window from minimized state on OSX + main_window->activateWindow(); //bring window to front/unminimize on windows + } if (line == "showsettings") { emit showsettings(); } else if (line == "pause") { @@ -54,9 +68,9 @@ UnixManagerSocket::eventFilter(QObject *obj, QEvent *event) { if (state() == QLocalSocket::ConnectedState) { if (event->type() == QEvent::WindowBlocked) { - write(QByteArray { "1" }); + write(QByteArray { "1\n" }); } else if (event->type() == QEvent::WindowUnblocked) { - write(QByteArray { "0" }); + write(QByteArray { "0\n" }); } }