vmm preview
@@ -189,6 +189,44 @@ add_library(ui STATIC
|
||||
qt_mediahistorymanager.cpp
|
||||
qt_mediahistorymanager.hpp
|
||||
|
||||
qt_updatecheck.cpp
|
||||
qt_updatecheck.hpp
|
||||
qt_updatecheckdialog.cpp
|
||||
qt_updatecheckdialog.hpp
|
||||
qt_updatedetails.cpp
|
||||
qt_updatedetails.hpp
|
||||
qt_downloader.cpp
|
||||
qt_downloader.hpp
|
||||
|
||||
qt_vmmanager_clientsocket.cpp
|
||||
qt_vmmanager_clientsocket.hpp
|
||||
qt_vmmanager_serversocket.cpp
|
||||
qt_vmmanager_serversocket.hpp
|
||||
qt_vmmanager_protocol.cpp
|
||||
qt_vmmanager_protocol.hpp
|
||||
qt_vmmanager_details.hpp
|
||||
qt_vmmanager_details.cpp
|
||||
qt_vmmanager_details.ui
|
||||
qt_vmmanager_addmachine.cpp
|
||||
qt_vmmanager_addmachine.hpp
|
||||
qt_vmmanager_detailsection.cpp
|
||||
qt_vmmanager_detailsection.hpp
|
||||
qt_vmmanager_listviewdelegate.hpp
|
||||
qt_vmmanager_listviewdelegate.cpp
|
||||
qt_vmmanager_preferences.cpp
|
||||
qt_vmmanager_preferences.hpp
|
||||
qt_vmmanager_main.hpp
|
||||
qt_vmmanager_main.cpp
|
||||
qt_vmmanager_main.ui
|
||||
qt_vmmanager_model.cpp
|
||||
qt_vmmanager_model.hpp
|
||||
qt_vmmanager_system.cpp
|
||||
qt_vmmanager_system.hpp
|
||||
qt_vmmanager_config.cpp
|
||||
qt_vmmanager_config.hpp
|
||||
qt_vmmanager_mainwindow.cpp
|
||||
qt_vmmanager_mainwindow.hpp
|
||||
|
||||
../qt_resources.qrc
|
||||
./qdarkstyle/dark/darkstyle.qrc
|
||||
|
||||
|
||||
BIN
src/qt/assets/86box-wizard.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
src/qt/assets/systemicons/cpq_deskpro.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
src/qt/assets/systemicons/cpq_port_386.png
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
src/qt/assets/systemicons/cpq_port_II.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
src/qt/assets/systemicons/cpq_port_III.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
src/qt/assets/systemicons/cpq_portable.png
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
src/qt/assets/systemicons/cpq_pres_2240.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
src/qt/assets/systemicons/cpq_pres_4500.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
src/qt/assets/systemicons/ibm330.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
src/qt/assets/systemicons/ibm_at.png
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
src/qt/assets/systemicons/ibm_pc_81.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
src/qt/assets/systemicons/ibm_pc_82.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
src/qt/assets/systemicons/ibm_pcjr.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
src/qt/assets/systemicons/ibm_ps2_m70.png
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
src/qt/assets/systemicons/ibm_ps2_m80.png
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
src/qt/assets/systemicons/ibm_psvp_486.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
src/qt/assets/systemicons/ibm_psvp_p60.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
src/qt/assets/systemicons/ibm_xt_82.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
src/qt/assets/systemicons/ibm_xt_86.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
src/qt/assets/systemicons/olivetti_m19.png
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
src/qt/assets/systemicons/olivetti_m21.png
Normal file
|
After Width: | Height: | Size: 317 KiB |
BIN
src/qt/assets/systemicons/olivetti_m24.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
src/qt/assets/systemicons/olivetti_m24sp.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
src/qt/assets/systemicons/os_archlinux_x2.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/qt/assets/systemicons/os_cloud_x2.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/qt/assets/systemicons/os_debian_x2.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/qt/assets/systemicons/os_dos_x2.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src/qt/assets/systemicons/os_fedora_x2.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/qt/assets/systemicons/os_freebsd_x2.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/qt/assets/systemicons/os_gentoo_x2.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src/qt/assets/systemicons/os_jrockitve_x2.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src/qt/assets/systemicons/os_l4_x2.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
src/qt/assets/systemicons/os_linux22_x2.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/qt/assets/systemicons/os_linux24_x2.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
src/qt/assets/systemicons/os_linux26_x2.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
src/qt/assets/systemicons/os_linux_x2.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src/qt/assets/systemicons/os_macosx_x2.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/qt/assets/systemicons/os_mandriva_x2.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src/qt/assets/systemicons/os_netbsd_x2.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/qt/assets/systemicons/os_netware_x2.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
src/qt/assets/systemicons/os_openbsd_x2.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
src/qt/assets/systemicons/os_opensuse_x2.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
src/qt/assets/systemicons/os_oracle_x2.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
src/qt/assets/systemicons/os_oraclesolaris_x2.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src/qt/assets/systemicons/os_os2_other_x2.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
src/qt/assets/systemicons/os_os2ecs_x2.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src/qt/assets/systemicons/os_os2warp3_x2.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/qt/assets/systemicons/os_os2warp45_x2.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/qt/assets/systemicons/os_os2warp4_x2.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/qt/assets/systemicons/os_other_x2.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
src/qt/assets/systemicons/os_qnx_x2.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
src/qt/assets/systemicons/os_redhat_x2.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/qt/assets/systemicons/os_solaris_x2.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src/qt/assets/systemicons/os_turbolinux_x2.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/qt/assets/systemicons/os_ubuntu_x2.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/qt/assets/systemicons/os_win10_x2.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/qt/assets/systemicons/os_win2k3_x2.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
src/qt/assets/systemicons/os_win2k8_x2.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
src/qt/assets/systemicons/os_win2k_x2.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/qt/assets/systemicons/os_win31_x2.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/qt/assets/systemicons/os_win7_x2.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
src/qt/assets/systemicons/os_win81_x2.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/qt/assets/systemicons/os_win8_x2.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/qt/assets/systemicons/os_win95_x2.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
src/qt/assets/systemicons/os_win98_x2.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
src/qt/assets/systemicons/os_win_other_x2.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/qt/assets/systemicons/os_winme_x2.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/qt/assets/systemicons/os_winnt4_x2.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
src/qt/assets/systemicons/os_winvista_x2.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
src/qt/assets/systemicons/os_winxp_x2.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
src/qt/assets/systemicons/os_xandros_x2.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/qt/assets/systemicons/pb_bora_pro.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
src/qt/assets/systemicons/pb_pb410.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
src/qt/assets/systemicons/pb_pb640.png
Normal file
|
After Width: | Height: | Size: 173 KiB |
BIN
src/qt/assets/systemicons/pb_pb680.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
src/qt/assets/systemicons/tandy_1000.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
src/qt/assets/systemicons/tandy_1000_hx.png
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
src/qt/assets/systemicons/tandy_1000_sl2.png
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
src/qt/assets/systemicons/toshiba_t1000.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
src/qt/assets/systemicons/toshiba_t1200.png
Normal file
|
After Width: | Height: | Size: 285 KiB |
BIN
src/qt/assets/systemicons/toshiba_t1200_hdd.png
Normal file
|
After Width: | Height: | Size: 285 KiB |
BIN
src/qt/icons/green-square-16.png
Normal file
|
After Width: | Height: | Size: 115 B |
BIN
src/qt/icons/pause-16.png
Normal file
|
After Width: | Height: | Size: 123 B |
BIN
src/qt/icons/play-16.png
Normal file
|
After Width: | Height: | Size: 156 B |
BIN
src/qt/icons/red-power-16.png
Normal file
|
After Width: | Height: | Size: 174 B |
BIN
src/qt/icons/red-square-16.png
Normal file
|
After Width: | Height: | Size: 118 B |
BIN
src/qt/icons/stop-16.png
Normal file
|
After Width: | Height: | Size: 120 B |
BIN
src/qt/icons/yellow-square-16.png
Normal file
|
After Width: | Height: | Size: 117 B |
95
src/qt/qt_downloader.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* Downloader module
|
||||
*
|
||||
*
|
||||
*
|
||||
* Authors: cold-brewed
|
||||
*
|
||||
* Copyright 2024 cold-brewed
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QNetworkReply>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "qt_downloader.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <86box/plat.h>
|
||||
}
|
||||
|
||||
Downloader::
|
||||
Downloader(const DownloadLocation downloadLocation, QObject *parent)
|
||||
: QObject(parent)
|
||||
, file(nullptr)
|
||||
, reply(nullptr)
|
||||
, variantData(QVariant::Invalid)
|
||||
{
|
||||
char PATHBUF[256];
|
||||
switch (downloadLocation) {
|
||||
case DownloadLocation::Data:
|
||||
plat_get_global_data_dir(PATHBUF, 255);
|
||||
break;
|
||||
case DownloadLocation::Config:
|
||||
plat_get_global_config_dir(PATHBUF, 255);
|
||||
break;
|
||||
case DownloadLocation::Temp:
|
||||
plat_get_temp_dir(PATHBUF, 255);
|
||||
break;
|
||||
}
|
||||
downloadDirectory = QDir(PATHBUF);
|
||||
}
|
||||
|
||||
Downloader::~Downloader() { delete file; }
|
||||
|
||||
void Downloader::download(const QUrl &url, const QString &filepath, const QVariant &varData) {
|
||||
|
||||
variantData = varData;
|
||||
// temporary until I get the plat stuff fixed
|
||||
// const auto global_dir = temporaryGetGlobalDataDir();
|
||||
// qDebug() << "I was passed filepath " << filepath;
|
||||
// Join with filename to create final file
|
||||
// const auto final_path = QDir(global_dir).filePath(filepath);
|
||||
const auto final_path = downloadDirectory.filePath(filepath);
|
||||
|
||||
file = new QFile(final_path);
|
||||
if(!file->open(QIODevice::WriteOnly)) {
|
||||
qWarning() << "Unable to open file " << final_path;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto nam = new QNetworkAccessManager(this);
|
||||
// Create the network request and execute
|
||||
const auto request = QNetworkRequest(url);
|
||||
reply = nam->get(request);
|
||||
// Connect to the finished signal
|
||||
connect(reply, &QNetworkReply::finished, this, &Downloader::onResult);
|
||||
}
|
||||
|
||||
void
|
||||
Downloader::onResult()
|
||||
{
|
||||
if (reply->error()) {
|
||||
qWarning() << "Error returned from QNetworkRequest: " << reply->errorString();
|
||||
emit errorOccurred(reply->errorString());
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
file->write(reply->readAll());
|
||||
file->flush();
|
||||
file->close();
|
||||
|
||||
reply->deleteLater();
|
||||
qDebug() << Q_FUNC_INFO << "Downloaded complete: file written to " << file->fileName();
|
||||
emit downloadCompleted(file->fileName(), variantData);
|
||||
}
|
||||
|
||||
57
src/qt/qt_downloader.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* Header for the downloader module
|
||||
*
|
||||
*
|
||||
*
|
||||
* Authors: cold-brewed
|
||||
*
|
||||
* Copyright 2024 cold-brewed
|
||||
*/
|
||||
|
||||
#ifndef QT_DOWNLOADER_HPP
|
||||
#define QT_DOWNLOADER_HPP
|
||||
|
||||
#include <QDir>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QString>
|
||||
#include <QFile>
|
||||
|
||||
|
||||
class Downloader : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class DownloadLocation {
|
||||
Data, // AppDataLocation via plat_get_global_data_dir()
|
||||
Config, // AppConfigLocation via plat_get_global_config_dir()
|
||||
Temp // TempLocation via plat_get_temp_dir()
|
||||
};
|
||||
explicit Downloader(DownloadLocation downloadLocation = DownloadLocation::Data, QObject *parent = nullptr);
|
||||
~Downloader() final;
|
||||
|
||||
void download(const QUrl &url, const QString &filepath, const QVariant &varData = QVariant::Invalid);
|
||||
|
||||
signals:
|
||||
// Signal emitted when the download is successful
|
||||
void downloadCompleted(QString filename, QVariant varData);
|
||||
// Signal emitted when an error occurs
|
||||
void errorOccurred(const QString&);
|
||||
|
||||
private slots:
|
||||
void onResult();
|
||||
|
||||
private:
|
||||
QFile *file;
|
||||
QNetworkAccessManager nam;
|
||||
QNetworkReply *reply;
|
||||
QVariant variantData;
|
||||
QDir downloadDirectory;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -77,6 +77,8 @@ extern "C" {
|
||||
#include "qt_styleoverride.hpp"
|
||||
#include "qt_unixmanagerfilter.hpp"
|
||||
#include "qt_util.hpp"
|
||||
#include "qt_vmmanager_clientsocket.hpp"
|
||||
#include "qt_vmmanager_mainwindow.hpp"
|
||||
|
||||
// Void Cast
|
||||
#define VC(x) const_cast<wchar_t *>(x)
|
||||
@@ -662,6 +664,19 @@ main(int argc, char *argv[])
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (vmm_enabled) {
|
||||
// VMManagerMain vmm;
|
||||
// // Hackish until there is a proper solution
|
||||
// QApplication::setApplicationName("86Box VM Manager");
|
||||
// QApplication::setApplicationDisplayName("86Box VM Manager");
|
||||
// vmm.show();
|
||||
// vmm.exec();
|
||||
const auto vmm_main_window = new VMManagerMainWindow();
|
||||
vmm_main_window->show();
|
||||
QApplication::exec();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef DISCORD
|
||||
discord_load();
|
||||
#endif
|
||||
@@ -758,6 +773,36 @@ main(int argc, char *argv[])
|
||||
socket.connectToServer(qgetenv("86BOX_MANAGER_SOCKET"));
|
||||
}
|
||||
|
||||
VMManagerClientSocket manager_socket;
|
||||
if (qgetenv("VMM_86BOX_SOCKET").size()) {
|
||||
manager_socket.IPCConnect(qgetenv("VMM_86BOX_SOCKET"));
|
||||
QObject::connect(&manager_socket, &VMManagerClientSocket::pause, main_window, &MainWindow::togglePause);
|
||||
QObject::connect(&manager_socket, &VMManagerClientSocket::resetVM, main_window, &MainWindow::hardReset);
|
||||
QObject::connect(&manager_socket, &VMManagerClientSocket::showsettings, main_window, &MainWindow::showSettings);
|
||||
QObject::connect(&manager_socket, &VMManagerClientSocket::ctrlaltdel, []() { pc_send_cad(); });
|
||||
QObject::connect(&manager_socket, &VMManagerClientSocket::request_shutdown, main_window, &MainWindow::close);
|
||||
QObject::connect(&manager_socket, &VMManagerClientSocket::force_shutdown, []() {
|
||||
do_stop();
|
||||
emit main_window->close();
|
||||
});
|
||||
QObject::connect(main_window, &MainWindow::vmmRunningStateChanged, &manager_socket, &VMManagerClientSocket::clientRunningStateChanged);
|
||||
main_window->installEventFilter(&manager_socket);
|
||||
}
|
||||
|
||||
/* Warn the user about unsupported configs */
|
||||
if (cpu_override) {
|
||||
QMessageBox warningbox(QMessageBox::Icon::Warning, QObject::tr("You are loading an unsupported configuration"),
|
||||
QObject::tr("CPU type filtering based on selected machine is disabled for this emulated machine.\n\nThis makes it possible to choose a CPU that is otherwise incompatible with the selected machine. However, you may run into incompatibilities with the machine BIOS or other software.\n\nEnabling this setting is not officially supported and any bug reports filed may be closed as invalid."),
|
||||
QMessageBox::NoButton, main_window);
|
||||
warningbox.addButton(QObject::tr("Continue"), QMessageBox::AcceptRole);
|
||||
warningbox.addButton(QObject::tr("Exit"), QMessageBox::RejectRole);
|
||||
warningbox.exec();
|
||||
if (warningbox.result() == QDialog::Accepted) {
|
||||
confirm_exit_cmdl = 0; /* skip the confirmation prompt without touching the config */
|
||||
emit main_window->close();
|
||||
}
|
||||
}
|
||||
|
||||
// pc_reset_hard_init();
|
||||
|
||||
QTimer onesec;
|
||||
|
||||
@@ -2131,6 +2131,7 @@ MainWindow::updateUiPauseState()
|
||||
QString(tr("Pause execution"));
|
||||
ui->actionPause->setIcon(pause_icon);
|
||||
ui->actionPause->setToolTip(tooltip_text);
|
||||
emit vmmRunningStateChanged(static_cast<VMManagerProtocol::RunningState>(dopause));
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
#include "qt_vmmanager_protocol.hpp"
|
||||
|
||||
class MediaMenu;
|
||||
class RendererStack;
|
||||
|
||||
@@ -63,6 +65,8 @@ signals:
|
||||
|
||||
void showMessageForNonQtThread(int flags, const QString &header, const QString &message, bool richText, std::atomic_bool* done);
|
||||
void getTitleForNonQtThread(wchar_t *title);
|
||||
|
||||
void vmmRunningStateChanged(VMManagerProtocol::RunningState state);
|
||||
public slots:
|
||||
void showSettings();
|
||||
void hardReset();
|
||||
|
||||
360
src/qt/qt_updatecheck.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* Update check module
|
||||
*
|
||||
*
|
||||
*
|
||||
* Authors: cold-brewed
|
||||
*
|
||||
* Copyright 2024 cold-brewed
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
|
||||
#include "qt_updatecheck.hpp"
|
||||
#include "qt_downloader.hpp"
|
||||
#include "qt_updatedetails.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <86box/version.h>
|
||||
}
|
||||
|
||||
UpdateCheck::
|
||||
UpdateCheck(const UpdateChannel channel, QObject *parent) : QObject(parent)
|
||||
{
|
||||
updateChannel = channel;
|
||||
currentVersion = getCurrentVersion(channel);
|
||||
}
|
||||
|
||||
UpdateCheck::~
|
||||
UpdateCheck()
|
||||
= default;
|
||||
|
||||
void
|
||||
UpdateCheck::checkForUpdates()
|
||||
{
|
||||
if (updateChannel == UpdateChannel::Stable) {
|
||||
const auto githubDownloader = new Downloader(Downloader::DownloadLocation::Temp);
|
||||
connect(githubDownloader, &Downloader::downloadCompleted, this, &UpdateCheck::githubDownloadComplete);
|
||||
connect(githubDownloader, &Downloader::errorOccurred, this, &UpdateCheck::generalDownloadError);
|
||||
githubDownloader->download(QUrl(githubReleaseApi), "github_releases.json");
|
||||
} else {
|
||||
const auto jenkinsDownloader = new Downloader(Downloader::DownloadLocation::Temp);
|
||||
connect(jenkinsDownloader, &Downloader::downloadCompleted, this, &UpdateCheck::jenkinsDownloadComplete);
|
||||
connect(jenkinsDownloader, &Downloader::errorOccurred, this, &UpdateCheck::generalDownloadError);
|
||||
jenkinsDownloader->download(jenkinsLatestNReleasesUrl(10), "jenkins_list.json");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
UpdateCheck::jenkinsDownloadComplete(const QString &filename, const QVariant &varData)
|
||||
{
|
||||
auto generalError = tr("Unable to determine release information");
|
||||
auto jenkinsReleaseListResult = parseJenkinsJson(filename);
|
||||
auto latestVersion = 0; // NOLINT (Default value as a fallback)
|
||||
|
||||
if(!jenkinsReleaseListResult.has_value() || jenkinsReleaseListResult.value().isEmpty()) {
|
||||
generalDownloadError(generalError);
|
||||
return;
|
||||
}
|
||||
const auto jenkinsReleaseList = jenkinsReleaseListResult.value();
|
||||
latestVersion = jenkinsReleaseListResult->first().buildNumber;
|
||||
|
||||
// If we can't determine the local build (blank current version), always show an update as available.
|
||||
// Callers can adjust accordingly.
|
||||
// Otherwise, do a comparison with EMU_BUILD_NUM
|
||||
bool updateAvailable = false;
|
||||
bool upToDate = true;
|
||||
if(currentVersion.isEmpty() || EMU_BUILD_NUM < latestVersion) {
|
||||
updateAvailable = true;
|
||||
upToDate = false;
|
||||
}
|
||||
|
||||
const auto updateResult = UpdateResult {
|
||||
.channel = updateChannel,
|
||||
.updateAvailable = updateAvailable,
|
||||
.upToDate = upToDate,
|
||||
.currentVersion = currentVersion,
|
||||
.latestVersion = QString::number(latestVersion),
|
||||
.githubInfo = {},
|
||||
.jenkinsInfo = jenkinsReleaseList,
|
||||
};
|
||||
|
||||
emit updateCheckComplete(updateResult);
|
||||
}
|
||||
|
||||
void
|
||||
UpdateCheck::generalDownloadError(const QString &error)
|
||||
{
|
||||
emit updateCheckError(error);
|
||||
}
|
||||
|
||||
void
|
||||
UpdateCheck::githubDownloadComplete(const QString &filename, const QVariant &varData)
|
||||
{
|
||||
const auto generalError = tr("Unable to determine release information");
|
||||
const auto githubReleaseListResult = parseGithubJson(filename);
|
||||
QString latestVersion = "0.0";
|
||||
if(!githubReleaseListResult.has_value() || githubReleaseListResult.value().isEmpty()) {
|
||||
generalDownloadError(generalError);
|
||||
}
|
||||
auto githubReleaseList = githubReleaseListResult.value();
|
||||
// Warning: this check (using the tag name) relies on a consistent naming scheme: "v<number>"
|
||||
// where <number> is the release number. For example, 4.2 from v4.2 as the tag name.
|
||||
// Another option would be parsing the name field which is generally "86Box <number>" but
|
||||
// either option requires a consistent naming scheme.
|
||||
latestVersion = githubReleaseList.first().tag_name.replace("v", "");
|
||||
for (const auto &release: githubReleaseList) {
|
||||
qDebug().noquote().nospace() << release.name << ": " << release.html_url << " (" << release.created_at << ")";
|
||||
}
|
||||
|
||||
// const auto updateDetails = new UpdateDetails(githubReleaseList, currentVersion);
|
||||
bool updateAvailable = false;
|
||||
bool upToDate = true;
|
||||
if(currentVersion.isEmpty() || (versionCompare(currentVersion, latestVersion) < 0)) {
|
||||
updateAvailable = true;
|
||||
upToDate = false;
|
||||
}
|
||||
|
||||
const auto updateResult = UpdateResult {
|
||||
.channel = updateChannel,
|
||||
.updateAvailable = updateAvailable,
|
||||
.upToDate = upToDate,
|
||||
.currentVersion = currentVersion,
|
||||
.latestVersion = latestVersion,
|
||||
.githubInfo = githubReleaseList,
|
||||
.jenkinsInfo = {},
|
||||
};
|
||||
|
||||
emit updateCheckComplete(updateResult);
|
||||
|
||||
}
|
||||
|
||||
QUrl
|
||||
UpdateCheck::jenkinsLatestNReleasesUrl(const int &count)
|
||||
{
|
||||
const auto urlPath = QString("https://ci.86box.net/job/86box/api/json?tree=builds[number,result,timestamp,changeSets[items[commitId,affectedPaths,author[fullName],msg,id]]]{0,%1}").arg(count);
|
||||
return { urlPath };
|
||||
}
|
||||
|
||||
QString
|
||||
UpdateCheck::getCurrentVersion(const UpdateChannel &updateChannel)
|
||||
{
|
||||
if (updateChannel == UpdateChannel::Stable) {
|
||||
return {EMU_VERSION};
|
||||
}
|
||||
// If EMU_BUILD_NUM is anything other than the default of zero it was set by the build process
|
||||
if constexpr (EMU_BUILD_NUM != 0) {
|
||||
return QString::number(EMU_BUILD_NUM); // NOLINT because EMU_BUILD_NUM is defined as 0 by default and is set at build time
|
||||
}
|
||||
// EMU_BUILD_NUM is not set, most likely a local build
|
||||
return {}; // NOLINT (Having EMU_BUILD_NUM assigned to a default number throws off the linter)
|
||||
}
|
||||
|
||||
std::optional<QList<UpdateCheck::JenkinsReleaseInfo>>
|
||||
UpdateCheck::parseJenkinsJson(const QString &filename)
|
||||
{
|
||||
QList<JenkinsReleaseInfo> releaseInfoList;
|
||||
QFile json_file(filename);
|
||||
if (!json_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qWarning() << "Couldn't open the json file: error" << json_file.error();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QString read_file = json_file.readAll();
|
||||
json_file.close();
|
||||
|
||||
const auto json_doc = QJsonDocument::fromJson(read_file.toUtf8());
|
||||
|
||||
if (json_doc.isNull()) {
|
||||
qWarning("Failed to create QJsonDocument, possibly invalid JSON");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
qWarning("JSON does not have the expected format (object in root), cannot continue");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto json_object = json_doc.object();
|
||||
|
||||
// The json contains multiple release
|
||||
if(json_object.contains("builds") && json_object["builds"].isArray()) {
|
||||
|
||||
QJsonArray builds = json_object["builds"].toArray();
|
||||
for (const auto &each_build: builds) {
|
||||
if (auto build = parseJenkinsRelease(each_build.toObject()); build.has_value() && build.value().result == "SUCCESS") {
|
||||
releaseInfoList.append(build.value());
|
||||
}
|
||||
}
|
||||
} else if(json_object.contains("changeSets") && json_object["changeSets"].isArray()) {
|
||||
// The json contains only one release, as obtained by the lastSuccessfulBuild api
|
||||
if (const auto build = parseJenkinsRelease(json_object); build.has_value()) {
|
||||
releaseInfoList.append(build.value());
|
||||
}
|
||||
} else {
|
||||
qWarning("JSON is missing data or has invalid data, cannot continue");
|
||||
qDebug() << json_object;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return releaseInfoList;
|
||||
}
|
||||
|
||||
std::optional<UpdateCheck::JenkinsReleaseInfo>
|
||||
UpdateCheck::parseJenkinsRelease(const QJsonObject &json)
|
||||
{
|
||||
// The root should contain number, result, and timestamp.
|
||||
if (!json.contains("number") || !json.contains("result") || !json.contains("timestamp")) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto releaseInfo = JenkinsReleaseInfo {
|
||||
.buildNumber = json["number"].toInt(),
|
||||
.result = json["result"].toString(),
|
||||
.timestamp = static_cast<qint64>(json["timestamp"].toDouble())
|
||||
};
|
||||
|
||||
// Overview
|
||||
// Each build should contain a changeSets object with an array. Only the first element is needed.
|
||||
// The first element should be an object containing an items object with an array.
|
||||
// Each array element in the items object has information releated to the build. More or less: commit data
|
||||
// In jq parlance it would be similar to `builds[].changeSets[0].items[]`
|
||||
|
||||
// To break down the somewhat complicated if-init statement below:
|
||||
// * Get the object for `changeSets`
|
||||
// * Convert the value to array
|
||||
// * Grab the first element in the array
|
||||
// Proceed if
|
||||
// * the element (first in changeSets) is an object that contains the key `items`
|
||||
if (const auto changeSet = json["changeSets"].toArray().first(); changeSet.isObject() && changeSet.toObject().contains("items")) {
|
||||
// Then proceed to process each `items` array element
|
||||
for (const auto &item : changeSet.toObject()["items"].toArray()) {
|
||||
auto itemObject = item.toObject();
|
||||
// Basic validation
|
||||
if (!itemObject.contains("commitId") || !itemObject.contains("msg") || !itemObject.contains("affectedPaths")) {
|
||||
return std::nullopt;
|
||||
}
|
||||
// Convert the paths for each commit to a string list
|
||||
QStringList paths;
|
||||
for (const auto &each_path : itemObject["affectedPaths"].toArray().toVariantList()) {
|
||||
if (each_path.type() == QVariant::String) {
|
||||
paths.append(each_path.toString());
|
||||
}
|
||||
}
|
||||
// Build the structure
|
||||
const auto releaseItem = JenkinsChangeSetItem {
|
||||
.buildId = itemObject["commitId"].toString(),
|
||||
.author = itemObject["author"].toObject()["fullName"].toString(),
|
||||
.message = itemObject["msg"].toString(),
|
||||
.affectedPaths = paths,
|
||||
};
|
||||
releaseInfo.changeSetItems.append(releaseItem);
|
||||
}
|
||||
} else {
|
||||
qWarning("Could not parse release information, possibly invalid JSON");
|
||||
}
|
||||
return releaseInfo;
|
||||
}
|
||||
|
||||
std::optional<QList<UpdateCheck::GithubReleaseInfo>>
|
||||
UpdateCheck::parseGithubJson(const QString &filename)
|
||||
{
|
||||
QList<GithubReleaseInfo> releaseInfoList;
|
||||
QFile json_file(filename);
|
||||
if (!json_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qWarning("Couldn't open the json file: error %d", json_file.error());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QString read_file = json_file.readAll();
|
||||
json_file.close();
|
||||
|
||||
const auto json_doc = QJsonDocument::fromJson(read_file.toUtf8());
|
||||
|
||||
if (json_doc.isNull()) {
|
||||
qWarning("Failed to create QJsonDocument, possibly invalid JSON");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!json_doc.isArray()) {
|
||||
qWarning("JSON does not have the expected format (array in root), cannot continue");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto release_array = json_doc.array();
|
||||
|
||||
for (const auto &each_release: release_array) {
|
||||
if (auto release = parseGithubRelease(each_release.toObject()); release.has_value()) {
|
||||
releaseInfoList.append(release.value());
|
||||
}
|
||||
}
|
||||
return releaseInfoList;
|
||||
}
|
||||
std::optional<UpdateCheck::GithubReleaseInfo>
|
||||
UpdateCheck::parseGithubRelease(const QJsonObject &json)
|
||||
{
|
||||
// Perform some basic validation
|
||||
if (!json.contains("name") || !json.contains("tag_name") || !json.contains("html_url")) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto githubRelease = GithubReleaseInfo {
|
||||
.name = json["name"].toString(),
|
||||
.tag_name = json["tag_name"].toString(),
|
||||
.html_url = json["html_url"].toString(),
|
||||
.target_commitish = json["target_commitish"].toString(),
|
||||
.created_at = json["created_at"].toString(),
|
||||
.published_at = json["published_at"].toString(),
|
||||
.body = json["body"].toString(),
|
||||
};
|
||||
|
||||
return githubRelease;
|
||||
}
|
||||
|
||||
// A simple method to compare version numbers
|
||||
// Should work for comparing x.y.z and x.y. Missing
|
||||
// values (parts) will be treated as zeroes
|
||||
int
|
||||
UpdateCheck::versionCompare(const QString &version1, const QString &version2)
|
||||
{
|
||||
// Split both
|
||||
QStringList v1List = version1.split('.');
|
||||
QStringList v2List = version2.split('.');
|
||||
|
||||
// Out of the two versions get the maximum amount of "parts"
|
||||
const int maxParts = std::max(v1List.size(), v2List.size());
|
||||
|
||||
// Initialize both with zeros
|
||||
QVector<int> v1Parts(maxParts, 0);
|
||||
QVector<int> v2Parts(maxParts, 0);
|
||||
|
||||
for (int i = 0; i < v1List.size(); ++i) {
|
||||
v1Parts[i] = v1List[i].toInt();
|
||||
}
|
||||
|
||||
for (int i = 0; i < v2List.size(); ++i) {
|
||||
v2Parts[i] = v2List[i].toInt();
|
||||
}
|
||||
|
||||
for (int i = 0; i < maxParts; ++i) {
|
||||
// First version is greater
|
||||
if (v1Parts[i] > v2Parts[i])
|
||||
return 1;
|
||||
// First version is less
|
||||
if (v1Parts[i] < v2Parts[i])
|
||||
return -1;
|
||||
}
|
||||
// They are equal
|
||||
return 0;
|
||||
}
|
||||
104
src/qt/qt_updatecheck.hpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* Header for the update check module
|
||||
*
|
||||
*
|
||||
*
|
||||
* Authors: cold-brewed
|
||||
*
|
||||
* Copyright 2024 cold-brewed
|
||||
*/
|
||||
|
||||
#ifndef QT_UPDATECHECK_HPP
|
||||
#define QT_UPDATECHECK_HPP
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
|
||||
#include <optional>
|
||||
|
||||
class UpdateCheck final : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class UpdateChannel {
|
||||
Stable,
|
||||
CI,
|
||||
};
|
||||
|
||||
struct JenkinsChangeSetItem {
|
||||
QString buildId; // sha hash
|
||||
QString author; // github username
|
||||
QString message; // commit message
|
||||
QStringList affectedPaths; // list of files in the change
|
||||
};
|
||||
|
||||
struct JenkinsReleaseInfo {
|
||||
int buildNumber = 0;
|
||||
QString result;
|
||||
qint64 timestamp = 0;
|
||||
QList<JenkinsChangeSetItem> changeSetItems;
|
||||
};
|
||||
|
||||
struct GithubReleaseInfo {
|
||||
QString name;
|
||||
QString tag_name;
|
||||
QString html_url;
|
||||
QString target_commitish;
|
||||
QString created_at;
|
||||
QString published_at;
|
||||
QString body;
|
||||
};
|
||||
|
||||
struct UpdateResult {
|
||||
UpdateChannel channel;
|
||||
bool updateAvailable = false;
|
||||
bool upToDate = false;
|
||||
QString currentVersion;
|
||||
QString latestVersion;
|
||||
QList<GithubReleaseInfo> githubInfo;
|
||||
QList<JenkinsReleaseInfo> jenkinsInfo;
|
||||
};
|
||||
|
||||
explicit UpdateCheck(UpdateChannel channel, QObject *parent = nullptr);
|
||||
~UpdateCheck() override;
|
||||
void checkForUpdates();
|
||||
static int versionCompare(const QString &version1, const QString &version2);
|
||||
[[nodiscard]] static QString getCurrentVersion(const UpdateChannel &updateChannel = UpdateChannel::Stable);
|
||||
|
||||
signals:
|
||||
// void updateCheckComplete(const UpdateCheck::UpdateChannel &channel, const QVariant &updateData);
|
||||
void updateCheckComplete(const UpdateCheck::UpdateResult &result);
|
||||
void updateCheckError(const QString &errorMsg);
|
||||
|
||||
private:
|
||||
UpdateChannel updateChannel = UpdateChannel::Stable;
|
||||
|
||||
const QUrl githubReleaseApi = QUrl("https://api.github.com/repos/86box/86Box/releases");
|
||||
const QUrl jenkinsLatestApi = QUrl("https://ci.86box.net/job/86box/lastSuccessfulBuild/api/json");
|
||||
QString jenkinsLatestVersion;
|
||||
QString currentVersion;
|
||||
|
||||
static QUrl jenkinsLatestNReleasesUrl(const int &count);
|
||||
|
||||
static std::optional<QList<JenkinsReleaseInfo>> parseJenkinsJson(const QString &filename);
|
||||
static std::optional<JenkinsReleaseInfo> parseJenkinsRelease(const QJsonObject &json);
|
||||
|
||||
static std::optional<QList<GithubReleaseInfo>> parseGithubJson(const QString &filename);
|
||||
static std::optional<GithubReleaseInfo> parseGithubRelease(const QJsonObject &json);
|
||||
|
||||
|
||||
private slots:
|
||||
void jenkinsDownloadComplete(const QString &filename, const QVariant& varData);
|
||||
void githubDownloadComplete(const QString &filename, const QVariant& varData);
|
||||
void generalDownloadError(const QString &error);
|
||||
};
|
||||
|
||||
#endif // QT_UPDATECHECK_HPP
|
||||
90
src/qt/qt_updatecheckdialog.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* Update check dialog module
|
||||
*
|
||||
*
|
||||
*
|
||||
* Authors: cold-brewed
|
||||
*
|
||||
* Copyright 2024 cold-brewed
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QTimer>
|
||||
|
||||
#include "qt_updatecheckdialog.hpp"
|
||||
#include "ui_qt_updatecheckdialog.h"
|
||||
#include "qt_updatedetails.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <86box/version.h>
|
||||
}
|
||||
|
||||
UpdateCheckDialog::
|
||||
UpdateCheckDialog(const UpdateCheck::UpdateChannel channel, QWidget *parent) : QDialog(parent), ui(new Ui::UpdateCheckDialog), updateCheck(new UpdateCheck(channel))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowTitle(tr("Update check"));
|
||||
ui->statusLabel->setHidden(true);
|
||||
updateChannel = channel;
|
||||
currentVersion = UpdateCheck::getCurrentVersion(updateChannel);
|
||||
connect(updateCheck, &UpdateCheck::updateCheckError, [=](const QString &errorMsg) {
|
||||
generalDownloadError(errorMsg);
|
||||
});
|
||||
connect(updateCheck, &UpdateCheck::updateCheckComplete, this, &UpdateCheckDialog::downloadComplete);
|
||||
|
||||
QTimer::singleShot(0, [this] {
|
||||
updateCheck->checkForUpdates();
|
||||
});
|
||||
}
|
||||
|
||||
UpdateCheckDialog::~
|
||||
UpdateCheckDialog()
|
||||
= default;
|
||||
|
||||
void
|
||||
UpdateCheckDialog::generalDownloadError(const QString &error) const
|
||||
{
|
||||
ui->progressBar->setMaximum(100);
|
||||
ui->progressBar->setValue(100);
|
||||
ui->statusLabel->setVisible(true);
|
||||
const auto statusText = tr("There was an error checking for updates:\n\n%1\n\nPlease try again later.").arg(error);
|
||||
ui->statusLabel->setText(statusText);
|
||||
ui->buttonBox->setStandardButtons(QDialogButtonBox::Ok);
|
||||
}
|
||||
|
||||
void
|
||||
UpdateCheckDialog::downloadComplete(const UpdateCheck::UpdateResult &result)
|
||||
{
|
||||
if (result.upToDate) {
|
||||
upToDate();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto updateDetails = new UpdateDetails(result);
|
||||
connect(updateDetails, &QDialog::accepted, [this] {
|
||||
accept();
|
||||
});
|
||||
connect(updateDetails, &QDialog::rejected, [this] {
|
||||
reject();
|
||||
});
|
||||
updateDetails->exec();
|
||||
}
|
||||
|
||||
void
|
||||
UpdateCheckDialog::upToDate()
|
||||
{
|
||||
ui->titleLabel->setText(tr("Update check complete"));
|
||||
ui->progressBar->setMaximum(100);
|
||||
ui->progressBar->setValue(100);
|
||||
ui->statusLabel->setVisible(true);
|
||||
const auto statusText = tr("You are running the latest %1 version of 86Box: %2").arg(updateChannel == UpdateCheck::UpdateChannel::Stable ? "stable" : "beta", currentVersion);
|
||||
ui->statusLabel->setText(statusText);
|
||||
ui->buttonBox->setStandardButtons(QDialogButtonBox::Ok);
|
||||
}
|
||||
47
src/qt/qt_updatecheckdialog.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* Header for the update check dialog module
|
||||
*
|
||||
*
|
||||
*
|
||||
* Authors: cold-brewed
|
||||
*
|
||||
* Copyright 2024 cold-brewed
|
||||
*/
|
||||
|
||||
#ifndef QT_UPDATECHECKDIALOG_HPP
|
||||
#define QT_UPDATECHECKDIALOG_HPP
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include <qt_updatecheck.hpp>
|
||||
|
||||
namespace Ui {
|
||||
class UpdateCheckDialog;
|
||||
}
|
||||
|
||||
class UpdateCheckDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UpdateCheckDialog(UpdateCheck::UpdateChannel channel, QWidget *parent = nullptr);
|
||||
~UpdateCheckDialog() override;
|
||||
|
||||
private:
|
||||
Ui::UpdateCheckDialog *ui;
|
||||
UpdateCheck::UpdateChannel updateChannel = UpdateCheck::UpdateChannel::Stable;
|
||||
UpdateCheck *updateCheck;
|
||||
QString currentVersion;
|
||||
void upToDate();
|
||||
|
||||
private slots:
|
||||
void downloadComplete(const UpdateCheck::UpdateResult &result);
|
||||
void generalDownloadError(const QString &error) const;
|
||||
};
|
||||
|
||||
#endif // QT_UPDATECHECKDIALOG_HPP
|
||||
106
src/qt/qt_updatecheckdialog.ui
Normal file
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>UpdateCheckDialog</class>
|
||||
<widget class="QDialog" name="UpdateCheckDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>350</width>
|
||||
<height>134</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="titleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Checking for updates..</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="maximum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="text">
|
||||
<string>Status text here</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>UpdateCheckDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>UpdateCheckDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
111
src/qt/qt_updatedetails.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* Update details module
|
||||
*
|
||||
*
|
||||
*
|
||||
* Authors: cold-brewed
|
||||
*
|
||||
* Copyright 2024 cold-brewed
|
||||
*/
|
||||
|
||||
#include "qt_updatedetails.hpp"
|
||||
#include "ui_qt_updatedetails.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QPushButton>
|
||||
|
||||
|
||||
UpdateDetails::
|
||||
UpdateDetails(const UpdateCheck::UpdateResult &updateResult, QWidget *parent) : ui(new Ui::UpdateDetails)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowTitle("86Box Update");
|
||||
ui->updateTitle->setText("<b>An update to 86Box is available!</b>");
|
||||
QString currentVersionText;
|
||||
QString releaseType = updateResult.channel == UpdateCheck::UpdateChannel::Stable ? tr("version") : tr("build");
|
||||
if(!updateResult.currentVersion.isEmpty()) {
|
||||
currentVersionText = tr("You are currently running %1 <b>%2</b>. ").arg(releaseType, updateResult.currentVersion);
|
||||
}
|
||||
|
||||
const auto updateDetailsText = tr("<b>%1 %2</b> is now available. %3Would you like to visit the download page?").arg(releaseType[0].toUpper() + releaseType.mid(1), updateResult.latestVersion, currentVersionText);
|
||||
ui->updateDetails->setText(updateDetailsText);
|
||||
|
||||
if(updateResult.channel == UpdateCheck::UpdateChannel::Stable) {
|
||||
ui->updateText->setMarkdown(githubUpdateToMarkdown(updateResult.githubInfo));
|
||||
} else {
|
||||
ui->updateText->setMarkdown(jenkinsUpdateToMarkdown(updateResult.jenkinsInfo));
|
||||
}
|
||||
|
||||
const auto downloadButton = new QPushButton(tr("Visit download page"));
|
||||
ui->buttonBox->addButton(downloadButton, QDialogButtonBox::AcceptRole);
|
||||
// Override accepted to mean "I want to visit the download page"
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, [this, updateResult] {
|
||||
visitDownloadPage(updateResult.channel);
|
||||
});
|
||||
const auto logo = QPixmap(":/assets/86box.png").scaled(QSize(64, 64), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
ui->icon->setPixmap(logo);
|
||||
}
|
||||
|
||||
UpdateDetails::~
|
||||
UpdateDetails()
|
||||
= default;
|
||||
|
||||
QString
|
||||
UpdateDetails::jenkinsUpdateToMarkdown(const QList<UpdateCheck::JenkinsReleaseInfo> &releaseInfoList)
|
||||
{
|
||||
QStringList fullText;
|
||||
for (const auto &update : releaseInfoList) {
|
||||
fullText.append(QString("### Build %1").arg(update.buildNumber));
|
||||
fullText.append("Changes:");
|
||||
for (const auto &item : update.changeSetItems) {
|
||||
fullText.append(QString("* %1").arg(item.message));
|
||||
}
|
||||
fullText.append("\n\n\n---\n\n\n");
|
||||
}
|
||||
// pop off the last hr
|
||||
fullText.removeLast();
|
||||
// return fullText.join("\n\n---\n\n");
|
||||
return fullText.join("\n");
|
||||
}
|
||||
|
||||
QString
|
||||
UpdateDetails::githubUpdateToMarkdown(const QList<UpdateCheck::GithubReleaseInfo> &releaseInfoList)
|
||||
{
|
||||
// The github release info can be rather large so we'll only
|
||||
// display the most recent one
|
||||
QList<UpdateCheck::GithubReleaseInfo> singleRelease;
|
||||
if (!releaseInfoList.isEmpty()) {
|
||||
singleRelease.append(releaseInfoList.first());
|
||||
}
|
||||
QStringList fullText;
|
||||
for (const auto &release : singleRelease) {
|
||||
fullText.append(QString("#### %1").arg(release.name));
|
||||
// Github body text should already be in markdown and can just
|
||||
// be placed here as-is
|
||||
fullText.append(release.body);
|
||||
fullText.append("\n\n\n---\n\n\n");
|
||||
}
|
||||
// pop off the last hr
|
||||
fullText.removeLast();
|
||||
return fullText.join("\n");
|
||||
}
|
||||
void
|
||||
UpdateDetails::visitDownloadPage(const UpdateCheck::UpdateChannel &channel)
|
||||
{
|
||||
switch (channel) {
|
||||
case UpdateCheck::UpdateChannel::Stable:
|
||||
QDesktopServices::openUrl(QUrl("https://ci.86box.net/job/86Box/lastSuccessfulBuild/artifact/"));
|
||||
break;
|
||||
case UpdateCheck::UpdateChannel::CI:
|
||||
QDesktopServices::openUrl(QUrl("https://github.com/86Box/86Box/releases/latest"));
|
||||
break;
|
||||
}
|
||||
}
|
||||