Qt: Add automatic updater
This commit is contained in:
parent
070b16e611
commit
c09bfc4d2b
|
@ -12,6 +12,9 @@ set(SRCS
|
||||||
audiosettingswidget.cpp
|
audiosettingswidget.cpp
|
||||||
audiosettingswidget.h
|
audiosettingswidget.h
|
||||||
audiosettingswidget.ui
|
audiosettingswidget.ui
|
||||||
|
autoupdaterdialog.cpp
|
||||||
|
autoupdaterdialog.h
|
||||||
|
autoupdaterdialog.ui
|
||||||
consolesettingswidget.cpp
|
consolesettingswidget.cpp
|
||||||
consolesettingswidget.h
|
consolesettingswidget.h
|
||||||
consolesettingswidget.ui
|
consolesettingswidget.ui
|
||||||
|
|
|
@ -0,0 +1,444 @@
|
||||||
|
#include "autoupdaterdialog.h"
|
||||||
|
#include "common/file_system.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/minizip_helpers.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "qthostinterface.h"
|
||||||
|
#include "qtutils.h"
|
||||||
|
#include "scmversion/scmversion.h"
|
||||||
|
#include "unzip.h"
|
||||||
|
#include <QtCore/QCoreApplication>
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QJsonArray>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QJsonValue>
|
||||||
|
#include <QtCore/QProcess>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtNetwork/QNetworkAccessManager>
|
||||||
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
#include <QtWidgets/QDialog>
|
||||||
|
#include <QtWidgets/QMessageBox>
|
||||||
|
#include <QtWidgets/QProgressDialog>
|
||||||
|
Log_SetChannel(AutoUpdaterDialog);
|
||||||
|
|
||||||
|
// Logic to detect whether we can use the auto updater.
|
||||||
|
// Currently Windows-only, and requires that the channel be defined by the buildbot.
|
||||||
|
#ifdef WIN32
|
||||||
|
#if defined(__has_include) && __has_include("scmversion/tag.h")
|
||||||
|
#include "scmversion/tag.h"
|
||||||
|
#ifdef SCM_RELEASE_TAG
|
||||||
|
#define AUTO_UPDATER_SUPPORTED
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef AUTO_UPDATER_SUPPORTED
|
||||||
|
|
||||||
|
static constexpr char LATEST_TAG_URL[] = "https://api.github.com/repos/stenzek/duckstation/tags";
|
||||||
|
static constexpr char LATEST_RELEASE_URL[] =
|
||||||
|
"https://api.github.com/repos/stenzek/duckstation/releases/tags/" SCM_RELEASE_TAG;
|
||||||
|
static constexpr char UPDATE_ASSET_FILENAME[] = SCM_RELEASE_ASSET;
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
static constexpr char LATEST_TAG_URL[] = "";
|
||||||
|
static constexpr char LATEST_RELEASE_URL[] = "";
|
||||||
|
static constexpr char UPDATE_ASSET_FILENAME[] = "";
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
AutoUpdaterDialog::AutoUpdaterDialog(QtHostInterface* host_interface, QWidget* parent /* = nullptr */)
|
||||||
|
: QDialog(parent), m_host_interface(host_interface)
|
||||||
|
{
|
||||||
|
m_network_access_mgr = new QNetworkAccessManager(this);
|
||||||
|
|
||||||
|
m_ui.setupUi(this);
|
||||||
|
|
||||||
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
|
|
||||||
|
// m_ui.description->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||||
|
// m_ui.description->setOpenExternalLinks(true);
|
||||||
|
|
||||||
|
connect(m_ui.downloadAndInstall, &QPushButton::clicked, this, &AutoUpdaterDialog::downloadUpdateClicked);
|
||||||
|
connect(m_ui.skipThisUpdate, &QPushButton::clicked, this, &AutoUpdaterDialog::skipThisUpdateClicked);
|
||||||
|
connect(m_ui.remindMeLater, &QPushButton::clicked, this, &AutoUpdaterDialog::remindMeLaterClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoUpdaterDialog::~AutoUpdaterDialog() = default;
|
||||||
|
|
||||||
|
bool AutoUpdaterDialog::isSupported()
|
||||||
|
{
|
||||||
|
#ifdef AUTO_UPDATER_SUPPORTED
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::reportError(const char* msg, ...)
|
||||||
|
{
|
||||||
|
std::va_list ap;
|
||||||
|
va_start(ap, msg);
|
||||||
|
std::string full_msg = StringUtil::StdStringFromFormatV(msg, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
QMessageBox::critical(this, tr("Updater Error"), QString::fromStdString(full_msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::queueUpdateCheck(bool display_message)
|
||||||
|
{
|
||||||
|
connect(m_network_access_mgr, &QNetworkAccessManager::finished, this, &AutoUpdaterDialog::getLatestTagComplete);
|
||||||
|
|
||||||
|
QUrl url(QUrl::fromEncoded(QByteArray(LATEST_TAG_URL, sizeof(LATEST_TAG_URL) - 1)));
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
|
m_network_access_mgr->get(request);
|
||||||
|
|
||||||
|
m_display_messages = display_message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::queueGetLatestRelease()
|
||||||
|
{
|
||||||
|
connect(m_network_access_mgr, &QNetworkAccessManager::finished, this, &AutoUpdaterDialog::getLatestReleaseComplete);
|
||||||
|
|
||||||
|
QUrl url(QUrl::fromEncoded(QByteArray(LATEST_RELEASE_URL, sizeof(LATEST_RELEASE_URL) - 1)));
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
|
m_network_access_mgr->get(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::getLatestTagComplete(QNetworkReply* reply)
|
||||||
|
{
|
||||||
|
// this might fail due to a lack of internet connection - in which case, don't spam the user with messages every time.
|
||||||
|
m_network_access_mgr->disconnect(this);
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NoError)
|
||||||
|
{
|
||||||
|
const QByteArray reply_json(reply->readAll());
|
||||||
|
QJsonParseError parse_error;
|
||||||
|
QJsonDocument doc(QJsonDocument::fromJson(reply_json, &parse_error));
|
||||||
|
if (doc.isArray())
|
||||||
|
{
|
||||||
|
const QJsonArray doc_array(doc.array());
|
||||||
|
for (const QJsonValue& val : doc_array)
|
||||||
|
{
|
||||||
|
if (!val.isObject())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (val["name"].toString() != QStringLiteral(SCM_RELEASE_TAG))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
m_latest_sha = val["commit"].toObject()["sha"].toString();
|
||||||
|
if (m_latest_sha.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (updateNeeded())
|
||||||
|
{
|
||||||
|
queueGetLatestRelease();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_display_messages)
|
||||||
|
QMessageBox::information(this, tr("Automatic Updater"),
|
||||||
|
tr("No updates are currently available. Please try again later."));
|
||||||
|
emit updateCheckCompleted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_display_messages)
|
||||||
|
reportError("latest release not found in JSON");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_display_messages)
|
||||||
|
reportError("JSON is not an array");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_display_messages)
|
||||||
|
reportError("Failed to download latest tag info: %d", static_cast<int>(reply->error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
emit updateCheckCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
|
||||||
|
{
|
||||||
|
m_network_access_mgr->disconnect(this);
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NoError)
|
||||||
|
{
|
||||||
|
const QByteArray reply_json(reply->readAll());
|
||||||
|
QJsonParseError parse_error;
|
||||||
|
QJsonDocument doc(QJsonDocument::fromJson(reply_json, &parse_error));
|
||||||
|
if (doc.isObject())
|
||||||
|
{
|
||||||
|
const QJsonObject doc_object(doc.object());
|
||||||
|
|
||||||
|
// search for the correct file
|
||||||
|
const QJsonArray assets(doc_object["assets"].toArray());
|
||||||
|
const QString asset_filename(UPDATE_ASSET_FILENAME);
|
||||||
|
for (const QJsonValue& asset : assets)
|
||||||
|
{
|
||||||
|
const QJsonObject asset_obj(asset.toObject());
|
||||||
|
if (asset_obj["name"] == asset_filename)
|
||||||
|
{
|
||||||
|
m_download_url = asset_obj["browser_download_url"].toString();
|
||||||
|
if (!m_download_url.isEmpty())
|
||||||
|
{
|
||||||
|
m_ui.currentVersion->setText(tr("Current Version: %1 (%2)").arg(g_scm_hash_str).arg(__TIMESTAMP__));
|
||||||
|
m_ui.newVersion->setText(
|
||||||
|
tr("New Version: %1 (%2)").arg(m_latest_sha).arg(doc_object["published_at"].toString()));
|
||||||
|
m_ui.updateNotes->setText(doc_object["body"].toString());
|
||||||
|
exec();
|
||||||
|
emit updateCheckCompleted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reportError("Asset/asset download not found");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reportError("JSON is not an object");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reportError("Failed to download latest release info: %d", static_cast<int>(reply->error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
emit updateCheckCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::downloadUpdateClicked()
|
||||||
|
{
|
||||||
|
QUrl url(m_download_url);
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
|
QNetworkReply* reply = m_network_access_mgr->get(request);
|
||||||
|
|
||||||
|
QProgressDialog progress(tr("Downloading %1...").arg(m_download_url), tr("Cancel"), 0, 1);
|
||||||
|
progress.setWindowTitle(tr("Automatic Updater"));
|
||||||
|
progress.setWindowIcon(windowIcon());
|
||||||
|
progress.setAutoClose(false);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::downloadProgress, [&progress](quint64 received, quint64 total) {
|
||||||
|
progress.setRange(0, static_cast<int>(total));
|
||||||
|
progress.setValue(static_cast<int>(received));
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_network_access_mgr, &QNetworkAccessManager::finished, [this, &progress](QNetworkReply* reply) {
|
||||||
|
m_network_access_mgr->disconnect();
|
||||||
|
|
||||||
|
if (reply->error() != QNetworkReply::NoError)
|
||||||
|
{
|
||||||
|
reportError("Download failed: %s", reply->errorString().toUtf8().constData());
|
||||||
|
progress.done(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray data = reply->readAll();
|
||||||
|
if (data.isEmpty())
|
||||||
|
{
|
||||||
|
reportError("Download failed: Update is empty");
|
||||||
|
progress.done(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processUpdate(data))
|
||||||
|
progress.done(1);
|
||||||
|
else
|
||||||
|
progress.done(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const int result = progress.exec();
|
||||||
|
if (result == 0)
|
||||||
|
{
|
||||||
|
// cancelled
|
||||||
|
reply->abort();
|
||||||
|
}
|
||||||
|
else if (result == 1)
|
||||||
|
{
|
||||||
|
// updater started
|
||||||
|
m_host_interface->requestExit();
|
||||||
|
done(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoUpdaterDialog::updateNeeded() const
|
||||||
|
{
|
||||||
|
QString last_checked_sha =
|
||||||
|
QString::fromStdString(m_host_interface->GetStringSettingValue("AutoUpdater", "LastVersion"));
|
||||||
|
|
||||||
|
Log_InfoPrintf("Current SHA: %s", g_scm_hash_str);
|
||||||
|
Log_InfoPrintf("Latest SHA: %s", m_latest_sha.toUtf8().constData());
|
||||||
|
Log_InfoPrintf("Last Checked SHA: %s", last_checked_sha.toUtf8().constData());
|
||||||
|
if (m_latest_sha == g_scm_hash_str || m_latest_sha == last_checked_sha)
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("No update needed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_InfoPrintf("Update needed.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::skipThisUpdateClicked()
|
||||||
|
{
|
||||||
|
m_host_interface->SetStringSettingValue("AutoUpdater", "LastVersion", m_latest_sha.toUtf8().constData());
|
||||||
|
done(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::remindMeLaterClicked()
|
||||||
|
{
|
||||||
|
done(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
|
||||||
|
bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data)
|
||||||
|
{
|
||||||
|
const QString update_directory = QCoreApplication::applicationDirPath();
|
||||||
|
const QString update_zip_path = update_directory + QStringLiteral("\\update.zip");
|
||||||
|
const QString updater_path = update_directory + QStringLiteral("\\updater.exe");
|
||||||
|
|
||||||
|
Q_ASSERT(!update_zip_path.isEmpty() && !updater_path.isEmpty() && !update_directory.isEmpty());
|
||||||
|
if ((QFile::exists(update_zip_path) && !QFile::remove(update_zip_path)) ||
|
||||||
|
(QFile::exists(updater_path) && !QFile::remove(updater_path)))
|
||||||
|
{
|
||||||
|
reportError("Removing existing update zip/updater failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QFile update_zip_file(update_zip_path);
|
||||||
|
if (!update_zip_file.open(QIODevice::WriteOnly) || update_zip_file.write(update_data) != update_data.size())
|
||||||
|
{
|
||||||
|
reportError("Writing update zip to '%s' failed", update_zip_path.toUtf8().constData());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
update_zip_file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extractUpdater(update_zip_path, updater_path))
|
||||||
|
{
|
||||||
|
reportError("Extracting updater failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doUpdate(update_zip_path, updater_path, update_directory))
|
||||||
|
{
|
||||||
|
reportError("Launching updater failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoUpdaterDialog::extractUpdater(const QString& zip_path, const QString& destination_path)
|
||||||
|
{
|
||||||
|
unzFile zf = MinizipHelpers::OpenUnzFile(zip_path.toUtf8().constData());
|
||||||
|
if (!zf)
|
||||||
|
{
|
||||||
|
reportError("Failed to open update zip");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unzLocateFile(zf, "updater.exe", 0) != UNZ_OK || unzOpenCurrentFile(zf) != UNZ_OK)
|
||||||
|
{
|
||||||
|
reportError("Failed to locate updater.exe");
|
||||||
|
unzClose(zf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile updater_exe(destination_path);
|
||||||
|
if (!updater_exe.open(QIODevice::WriteOnly))
|
||||||
|
{
|
||||||
|
reportError("Failed to open updater.exe for writing");
|
||||||
|
unzClose(zf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr size_t CHUNK_SIZE = 4096;
|
||||||
|
char chunk[CHUNK_SIZE];
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int size = unzReadCurrentFile(zf, chunk, CHUNK_SIZE);
|
||||||
|
if (size < 0)
|
||||||
|
{
|
||||||
|
reportError("Failed to decompress updater exe");
|
||||||
|
unzClose(zf);
|
||||||
|
updater_exe.close();
|
||||||
|
updater_exe.remove();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (size == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updater_exe.write(chunk, size) != size)
|
||||||
|
{
|
||||||
|
reportError("Failed to write updater exe");
|
||||||
|
unzClose(zf);
|
||||||
|
updater_exe.close();
|
||||||
|
updater_exe.remove();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unzClose(zf);
|
||||||
|
updater_exe.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AutoUpdaterDialog::doUpdate(const QString& zip_path, const QString& updater_path, const QString& destination_path)
|
||||||
|
{
|
||||||
|
const QString program_path = QCoreApplication::applicationFilePath();
|
||||||
|
if (program_path.isEmpty())
|
||||||
|
{
|
||||||
|
reportError("Failed to get current application path");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList arguments;
|
||||||
|
arguments << QStringLiteral("%1").arg(QCoreApplication::applicationPid());
|
||||||
|
arguments << destination_path;
|
||||||
|
arguments << zip_path;
|
||||||
|
arguments << program_path;
|
||||||
|
|
||||||
|
// this will leak, but not sure how else to handle it...
|
||||||
|
QProcess* updater_process = new QProcess();
|
||||||
|
updater_process->setProgram(updater_path);
|
||||||
|
updater_process->setArguments(arguments);
|
||||||
|
updater_process->start(QIODevice::NotOpen);
|
||||||
|
if (!updater_process->waitForStarted())
|
||||||
|
{
|
||||||
|
reportError("Failed to start updater");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,55 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ui_autoupdaterdialog.h"
|
||||||
|
#include <QtWidgets/QDialog>
|
||||||
|
|
||||||
|
class QNetworkAccessManager;
|
||||||
|
class QNetworkReply;
|
||||||
|
|
||||||
|
class QtHostInterface;
|
||||||
|
|
||||||
|
class AutoUpdaterDialog final : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AutoUpdaterDialog(QtHostInterface* host_interface, QWidget* parent = nullptr);
|
||||||
|
~AutoUpdaterDialog();
|
||||||
|
|
||||||
|
static bool isSupported();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void updateCheckCompleted();
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void queueUpdateCheck(bool display_message);
|
||||||
|
void queueGetLatestRelease();
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void getLatestTagComplete(QNetworkReply* reply);
|
||||||
|
void getLatestReleaseComplete(QNetworkReply* reply);
|
||||||
|
|
||||||
|
void downloadUpdateClicked();
|
||||||
|
void skipThisUpdateClicked();
|
||||||
|
void remindMeLaterClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void reportError(const char* msg, ...);
|
||||||
|
bool updateNeeded() const;
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
bool processUpdate(const QByteArray& update_data);
|
||||||
|
bool extractUpdater(const QString& zip_path, const QString& destination_path);
|
||||||
|
bool doUpdate(const QString& zip_path, const QString& updater_path, const QString& destination_path);
|
||||||
|
#else
|
||||||
|
bool processUpdate(const QByteArray& update_data);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Ui::AutoUpdaterDialog m_ui;
|
||||||
|
|
||||||
|
QtHostInterface* m_host_interface;
|
||||||
|
QNetworkAccessManager* m_network_access_mgr = nullptr;
|
||||||
|
QString m_latest_sha;
|
||||||
|
QString m_download_url;
|
||||||
|
|
||||||
|
bool m_display_messages = false;
|
||||||
|
};
|
|
@ -0,0 +1,110 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AutoUpdaterDialog</class>
|
||||||
|
<widget class="QDialog" name="AutoUpdaterDialog">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::ApplicationModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>651</width>
|
||||||
|
<height>474</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Automatic Updater</string>
|
||||||
|
</property>
|
||||||
|
<property name="modal">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>16</pointsize>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Update Available</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="currentVersion">
|
||||||
|
<property name="text">
|
||||||
|
<string>Current Version: </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="newVersion">
|
||||||
|
<property name="text">
|
||||||
|
<string>New Version: </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Update Notes:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTextEdit" name="updateNotes">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="downloadAndInstall">
|
||||||
|
<property name="text">
|
||||||
|
<string>Download and Install...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="skipThisUpdate">
|
||||||
|
<property name="text">
|
||||||
|
<string>Skip This Update</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="remindMeLater">
|
||||||
|
<property name="text">
|
||||||
|
<string>Remind Me Later</string>
|
||||||
|
</property>
|
||||||
|
<property name="default">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -38,6 +38,7 @@
|
||||||
<ClCompile Include="aboutdialog.cpp" />
|
<ClCompile Include="aboutdialog.cpp" />
|
||||||
<ClCompile Include="advancedsettingswidget.cpp" />
|
<ClCompile Include="advancedsettingswidget.cpp" />
|
||||||
<ClCompile Include="audiosettingswidget.cpp" />
|
<ClCompile Include="audiosettingswidget.cpp" />
|
||||||
|
<ClCompile Include="autoupdaterdialog.cpp" />
|
||||||
<ClCompile Include="consolesettingswidget.cpp" />
|
<ClCompile Include="consolesettingswidget.cpp" />
|
||||||
<ClCompile Include="gamelistmodel.cpp" />
|
<ClCompile Include="gamelistmodel.cpp" />
|
||||||
<ClCompile Include="gamelistsearchdirectoriesmodel.cpp" />
|
<ClCompile Include="gamelistsearchdirectoriesmodel.cpp" />
|
||||||
|
@ -74,6 +75,7 @@
|
||||||
<QtMoc Include="inputbindingdialog.h" />
|
<QtMoc Include="inputbindingdialog.h" />
|
||||||
<QtMoc Include="gamelistmodel.h" />
|
<QtMoc Include="gamelistmodel.h" />
|
||||||
<QtMoc Include="gamelistsearchdirectoriesmodel.h" />
|
<QtMoc Include="gamelistsearchdirectoriesmodel.h" />
|
||||||
|
<QtMoc Include="autoupdaterdialog.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<ClInclude Include="settingwidgetbinder.h" />
|
<ClInclude Include="settingwidgetbinder.h" />
|
||||||
<QtMoc Include="consolesettingswidget.h" />
|
<QtMoc Include="consolesettingswidget.h" />
|
||||||
|
@ -145,6 +147,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="$(IntDir)moc_aboutdialog.cpp" />
|
<ClCompile Include="$(IntDir)moc_aboutdialog.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_audiosettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_audiosettingswidget.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_autoupdaterdialog.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_advancedsettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_advancedsettingswidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_consolesettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_consolesettingswidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_controllersettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_controllersettingswidget.cpp" />
|
||||||
|
@ -195,6 +198,11 @@
|
||||||
<None Include="translations\duckstation-qt_pt-br.ts" />
|
<None Include="translations\duckstation-qt_pt-br.ts" />
|
||||||
<None Include="translations\duckstation-qt_pt-pt.ts" />
|
<None Include="translations\duckstation-qt_pt-pt.ts" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<QtUi Include="autoupdaterdialog.ui">
|
||||||
|
<FileType>Document</FileType>
|
||||||
|
</QtUi>
|
||||||
|
</ItemGroup>
|
||||||
<Target Name="CopyCommonDataFiles" AfterTargets="Build" Inputs="@(CommonDataFiles)" Outputs="@(CommonDataFiles -> '$(BinaryOutputDir)%(RecursiveDir)%(Filename)%(Extension)')">
|
<Target Name="CopyCommonDataFiles" AfterTargets="Build" Inputs="@(CommonDataFiles)" Outputs="@(CommonDataFiles -> '$(BinaryOutputDir)%(RecursiveDir)%(Filename)%(Extension)')">
|
||||||
<Message Text="Copying common data files" Importance="High" />
|
<Message Text="Copying common data files" Importance="High" />
|
||||||
<Copy SourceFiles="@(CommonDataFiles)" DestinationFolder="$(BinaryOutputDir)\%(RecursiveDir)" SkipUnchangedFiles="true" />
|
<Copy SourceFiles="@(CommonDataFiles)" DestinationFolder="$(BinaryOutputDir)\%(RecursiveDir)" SkipUnchangedFiles="true" />
|
||||||
|
|
|
@ -46,6 +46,8 @@
|
||||||
<ClCompile Include="$(IntDir)moc_gamelistmodel.cpp" />
|
<ClCompile Include="$(IntDir)moc_gamelistmodel.cpp" />
|
||||||
<ClCompile Include="gamelistsearchdirectoriesmodel.cpp" />
|
<ClCompile Include="gamelistsearchdirectoriesmodel.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_gamelistsearchdirectoriesmodel.cpp" />
|
<ClCompile Include="$(IntDir)moc_gamelistsearchdirectoriesmodel.cpp" />
|
||||||
|
<ClCompile Include="autoupdaterdialog.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_autoupdaterdialog.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="qtutils.h" />
|
<ClInclude Include="qtutils.h" />
|
||||||
|
@ -85,6 +87,7 @@
|
||||||
<QtMoc Include="inputbindingdialog.h" />
|
<QtMoc Include="inputbindingdialog.h" />
|
||||||
<QtMoc Include="gamelistmodel.h" />
|
<QtMoc Include="gamelistmodel.h" />
|
||||||
<QtMoc Include="gamelistsearchdirectoriesmodel.h" />
|
<QtMoc Include="gamelistsearchdirectoriesmodel.h" />
|
||||||
|
<QtMoc Include="autoupdaterdialog.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtUi Include="consolesettingswidget.ui" />
|
<QtUi Include="consolesettingswidget.ui" />
|
||||||
|
@ -98,6 +101,7 @@
|
||||||
<QtUi Include="gamepropertiesdialog.ui" />
|
<QtUi Include="gamepropertiesdialog.ui" />
|
||||||
<QtUi Include="aboutdialog.ui" />
|
<QtUi Include="aboutdialog.ui" />
|
||||||
<QtUi Include="inputbindingdialog.ui" />
|
<QtUi Include="inputbindingdialog.ui" />
|
||||||
|
<QtUi Include="autoupdaterdialog.ui" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="qt5.natvis" />
|
<Natvis Include="qt5.natvis" />
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "generalsettingswidget.h"
|
#include "generalsettingswidget.h"
|
||||||
|
#include "autoupdaterdialog.h"
|
||||||
#include "settingsdialog.h"
|
#include "settingsdialog.h"
|
||||||
#include "settingwidgetbinder.h"
|
#include "settingwidgetbinder.h"
|
||||||
|
|
||||||
|
@ -80,16 +81,27 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW
|
||||||
tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage."));
|
tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage."));
|
||||||
|
|
||||||
// Since this one is compile-time selected, we don't put it in the .ui file.
|
// Since this one is compile-time selected, we don't put it in the .ui file.
|
||||||
|
const int last_row_count = m_ui.formLayout_4->rowCount();
|
||||||
#ifdef WITH_DISCORD_PRESENCE
|
#ifdef WITH_DISCORD_PRESENCE
|
||||||
{
|
{
|
||||||
QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Discord Presence"), m_ui.groupBox_4);
|
QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Discord Presence"), m_ui.groupBox_4);
|
||||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "Main",
|
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "Main",
|
||||||
"EnableDiscordPresence");
|
"EnableDiscordPresence");
|
||||||
m_ui.formLayout_4->addWidget(enableDiscordPresence, m_ui.formLayout_4->rowCount(), 0);
|
m_ui.formLayout_4->addWidget(enableDiscordPresence, last_row_count, 0);
|
||||||
dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Discord Presence"), tr("Unchecked"),
|
dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Discord Presence"), tr("Unchecked"),
|
||||||
tr("Shows the game you are currently playing as part of your profile in Discord."));
|
tr("Shows the game you are currently playing as part of your profile in Discord."));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (AutoUpdaterDialog::isSupported())
|
||||||
|
{
|
||||||
|
QCheckBox* enableDiscordPresence = new QCheckBox(tr("Enable Automatic Update Check"), m_ui.groupBox_4);
|
||||||
|
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, enableDiscordPresence, "AutoUpdater",
|
||||||
|
"CheckAtStartup");
|
||||||
|
m_ui.formLayout_4->addWidget(enableDiscordPresence, last_row_count, 1);
|
||||||
|
dialog->registerWidgetHelp(enableDiscordPresence, tr("Enable Automatic Update Check"), tr("Checked"),
|
||||||
|
tr("Automatically checks for updates to the program on startup. Updates can be deferred "
|
||||||
|
"until later or skipped entirely."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GeneralSettingsWidget::~GeneralSettingsWidget() = default;
|
GeneralSettingsWidget::~GeneralSettingsWidget() = default;
|
||||||
|
|
|
@ -46,6 +46,10 @@ int main(int argc, char* argv[])
|
||||||
host_interface->bootSystem(*boot_params);
|
host_interface->bootSystem(*boot_params);
|
||||||
boot_params.reset();
|
boot_params.reset();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window->startupUpdateCheck();
|
||||||
|
}
|
||||||
|
|
||||||
int result = app.exec();
|
int result = app.exec();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "aboutdialog.h"
|
#include "aboutdialog.h"
|
||||||
|
#include "autoupdaterdialog.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "core/game_list.h"
|
#include "core/game_list.h"
|
||||||
#include "core/host_display.h"
|
#include "core/host_display.h"
|
||||||
|
@ -609,6 +610,7 @@ void MainWindow::connectSignals()
|
||||||
connect(m_ui.actionIssueTracker, &QAction::triggered, this, &MainWindow::onIssueTrackerActionTriggered);
|
connect(m_ui.actionIssueTracker, &QAction::triggered, this, &MainWindow::onIssueTrackerActionTriggered);
|
||||||
connect(m_ui.actionDiscordServer, &QAction::triggered, this, &MainWindow::onDiscordServerActionTriggered);
|
connect(m_ui.actionDiscordServer, &QAction::triggered, this, &MainWindow::onDiscordServerActionTriggered);
|
||||||
connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered);
|
connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered);
|
||||||
|
connect(m_ui.actionCheckForUpdates, &QAction::triggered, [this]() { checkForUpdates(true); });
|
||||||
|
|
||||||
connect(m_host_interface, &QtHostInterface::errorReported, this, &MainWindow::reportError,
|
connect(m_host_interface, &QtHostInterface::errorReported, this, &MainWindow::reportError,
|
||||||
Qt::BlockingQueuedConnection);
|
Qt::BlockingQueuedConnection);
|
||||||
|
@ -820,3 +822,38 @@ void MainWindow::changeEvent(QEvent* event)
|
||||||
|
|
||||||
QMainWindow::changeEvent(event);
|
QMainWindow::changeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::startupUpdateCheck()
|
||||||
|
{
|
||||||
|
if (!m_host_interface->GetBoolSettingValue("AutoUpdater", "CheckAtStartup", true))
|
||||||
|
return;
|
||||||
|
|
||||||
|
checkForUpdates(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::checkForUpdates(bool display_message)
|
||||||
|
{
|
||||||
|
if (!AutoUpdaterDialog::isSupported())
|
||||||
|
{
|
||||||
|
if (display_message)
|
||||||
|
QMessageBox::critical(this, tr("Updater Error"), tr("Updates are not supported on this build."));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_auto_updater_dialog)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_auto_updater_dialog = new AutoUpdaterDialog(m_host_interface, this);
|
||||||
|
connect(m_auto_updater_dialog, &AutoUpdaterDialog::updateCheckCompleted, this, &MainWindow::onUpdateCheckComplete);
|
||||||
|
m_auto_updater_dialog->queueUpdateCheck(display_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onUpdateCheckComplete()
|
||||||
|
{
|
||||||
|
if (!m_auto_updater_dialog)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_auto_updater_dialog->deleteLater();
|
||||||
|
m_auto_updater_dialog = nullptr;
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ class QThread;
|
||||||
class GameListWidget;
|
class GameListWidget;
|
||||||
class QtHostInterface;
|
class QtHostInterface;
|
||||||
class QtDisplayWidget;
|
class QtDisplayWidget;
|
||||||
|
class AutoUpdaterDialog;
|
||||||
|
|
||||||
class HostDisplay;
|
class HostDisplay;
|
||||||
struct GameListEntry;
|
struct GameListEntry;
|
||||||
|
@ -25,6 +26,9 @@ public:
|
||||||
explicit MainWindow(QtHostInterface* host_interface);
|
explicit MainWindow(QtHostInterface* host_interface);
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
|
|
||||||
|
/// Performs update check if enabled in settings.
|
||||||
|
void startupUpdateCheck();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void reportError(const QString& message);
|
void reportError(const QString& message);
|
||||||
void reportMessage(const QString& message);
|
void reportMessage(const QString& message);
|
||||||
|
@ -61,6 +65,9 @@ private Q_SLOTS:
|
||||||
void onGameListEntryDoubleClicked(const GameListEntry* entry);
|
void onGameListEntryDoubleClicked(const GameListEntry* entry);
|
||||||
void onGameListContextMenuRequested(const QPoint& point, const GameListEntry* entry);
|
void onGameListContextMenuRequested(const QPoint& point, const GameListEntry* entry);
|
||||||
|
|
||||||
|
void checkForUpdates(bool display_message);
|
||||||
|
void onUpdateCheckComplete();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent* event) override;
|
void closeEvent(QCloseEvent* event) override;
|
||||||
void changeEvent(QEvent* event) override;
|
void changeEvent(QEvent* event) override;
|
||||||
|
@ -94,6 +101,7 @@ private:
|
||||||
QLabel* m_status_frame_time_widget = nullptr;
|
QLabel* m_status_frame_time_widget = nullptr;
|
||||||
|
|
||||||
SettingsDialog* m_settings_dialog = nullptr;
|
SettingsDialog* m_settings_dialog = nullptr;
|
||||||
|
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
|
||||||
|
|
||||||
bool m_emulation_running = false;
|
bool m_emulation_running = false;
|
||||||
};
|
};
|
||||||
|
|
|
@ -122,6 +122,8 @@
|
||||||
<addaction name="actionIssueTracker"/>
|
<addaction name="actionIssueTracker"/>
|
||||||
<addaction name="actionDiscordServer"/>
|
<addaction name="actionDiscordServer"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionCheckForUpdates"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
<addaction name="actionAbout"/>
|
<addaction name="actionAbout"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuDebug">
|
<widget class="QMenu" name="menuDebug">
|
||||||
|
@ -346,6 +348,11 @@
|
||||||
<string>&Discord Server...</string>
|
<string>&Discord Server...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionCheckForUpdates">
|
||||||
|
<property name="text">
|
||||||
|
<string>Check for &Updates...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="actionAbout">
|
<action name="actionAbout">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&About...</string>
|
<string>&About...</string>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/audio_stream.h"
|
#include "common/audio_stream.h"
|
||||||
#include "common/byte_stream.h"
|
#include "common/byte_stream.h"
|
||||||
|
#include "common/file_system.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/controller.h"
|
#include "core/controller.h"
|
||||||
|
@ -720,7 +721,7 @@ void QtHostInterface::saveInputProfile(const QString& profile_name)
|
||||||
QString QtHostInterface::getUserDirectoryRelativePath(const QString& arg) const
|
QString QtHostInterface::getUserDirectoryRelativePath(const QString& arg) const
|
||||||
{
|
{
|
||||||
QString result = QString::fromStdString(m_user_directory);
|
QString result = QString::fromStdString(m_user_directory);
|
||||||
result += '/';
|
result += FS_OSPATH_SEPERATOR_CHARACTER;
|
||||||
result += arg;
|
result += arg;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -728,11 +729,16 @@ QString QtHostInterface::getUserDirectoryRelativePath(const QString& arg) const
|
||||||
QString QtHostInterface::getProgramDirectoryRelativePath(const QString& arg) const
|
QString QtHostInterface::getProgramDirectoryRelativePath(const QString& arg) const
|
||||||
{
|
{
|
||||||
QString result = QString::fromStdString(m_program_directory);
|
QString result = QString::fromStdString(m_program_directory);
|
||||||
result += '/';
|
result += FS_OSPATH_SEPERATOR_CHARACTER;
|
||||||
result += arg;
|
result += arg;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString QtHostInterface::getProgramDirectory() const
|
||||||
|
{
|
||||||
|
return QString::fromStdString(m_program_directory);
|
||||||
|
}
|
||||||
|
|
||||||
void QtHostInterface::powerOffSystem()
|
void QtHostInterface::powerOffSystem()
|
||||||
{
|
{
|
||||||
if (!isOnWorkerThread())
|
if (!isOnWorkerThread())
|
||||||
|
|
|
@ -71,6 +71,7 @@ public:
|
||||||
ALWAYS_INLINE const HotkeyInfoList& getHotkeyInfoList() const { return GetHotkeyInfoList(); }
|
ALWAYS_INLINE const HotkeyInfoList& getHotkeyInfoList() const { return GetHotkeyInfoList(); }
|
||||||
ALWAYS_INLINE ControllerInterface* getControllerInterface() const { return GetControllerInterface(); }
|
ALWAYS_INLINE ControllerInterface* getControllerInterface() const { return GetControllerInterface(); }
|
||||||
ALWAYS_INLINE bool inBatchMode() const { return InBatchMode(); }
|
ALWAYS_INLINE bool inBatchMode() const { return InBatchMode(); }
|
||||||
|
ALWAYS_INLINE void requestExit() { RequestExit(); }
|
||||||
|
|
||||||
ALWAYS_INLINE bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; }
|
ALWAYS_INLINE bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; }
|
||||||
|
|
||||||
|
@ -99,6 +100,9 @@ public:
|
||||||
/// Returns a list of supported languages and codes (suffixes for translation files).
|
/// Returns a list of supported languages and codes (suffixes for translation files).
|
||||||
static std::vector<std::pair<QString, QString>> getAvailableLanguageList();
|
static std::vector<std::pair<QString, QString>> getAvailableLanguageList();
|
||||||
|
|
||||||
|
/// Returns program directory as a QString.
|
||||||
|
QString getProgramDirectory() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void errorReported(const QString& message);
|
void errorReported(const QString& message);
|
||||||
void messageReported(const QString& message);
|
void messageReported(const QString& message);
|
||||||
|
|
Loading…
Reference in New Issue