diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 8b6c09c2b..e4929a0b4 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -23,9 +23,10 @@ set(SRCS
debugger/profiler.cpp
debugger/registers.cpp
debugger/wait_tree.cpp
- aboutdialog.cpp
+ updater/updater.cpp
util/spinbox.cpp
util/util.cpp
+ aboutdialog.cpp
bootmanager.cpp
game_list.cpp
hotkeys.cpp
@@ -56,6 +57,8 @@ set(HEADERS
debugger/profiler.h
debugger/registers.h
debugger/wait_tree.h
+ updater/updater.h
+ updater/updater_p.h
util/spinbox.h
util/util.h
aboutdialog.h
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 601c8b0e3..97993e426 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -157,6 +157,12 @@ void Config::ReadValues() {
qt_config->beginGroup("UI");
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
+ qt_config->beginGroup("Updater");
+ UISettings::values.check_for_update_on_start =
+ qt_config->value("check_for_update_on_start", true).toBool();
+ UISettings::values.update_on_close = qt_config->value("update_on_close", false).toBool();
+ qt_config->endGroup();
+
qt_config->beginGroup("UILayout");
UISettings::values.geometry = qt_config->value("geometry").toByteArray();
UISettings::values.state = qt_config->value("state").toByteArray();
@@ -307,6 +313,11 @@ void Config::SaveValues() {
qt_config->beginGroup("UI");
qt_config->setValue("theme", UISettings::values.theme);
+ qt_config->beginGroup("Updater");
+ qt_config->setValue("check_for_update_on_start", UISettings::values.check_for_update_on_start);
+ qt_config->setValue("update_on_close", UISettings::values.update_on_close);
+ qt_config->endGroup();
+
qt_config->beginGroup("UILayout");
qt_config->setValue("geometry", UISettings::values.geometry);
qt_config->setValue("state", UISettings::values.state);
diff --git a/src/citra_qt/configuration/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp
index 939379717..f8f6c305d 100644
--- a/src/citra_qt/configuration/configure_general.cpp
+++ b/src/citra_qt/configuration/configure_general.cpp
@@ -20,6 +20,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
this->setConfiguration();
ui->toggle_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ ui->updateBox->setVisible(UISettings::values.updater_found);
}
ConfigureGeneral::~ConfigureGeneral() {}
@@ -29,6 +30,9 @@ void ConfigureGeneral::setConfiguration() {
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
+ ui->toggle_update_check->setChecked(UISettings::values.check_for_update_on_start);
+ ui->toggle_auto_update->setChecked(UISettings::values.update_on_close);
+
// The first item is "auto-select" with actual value -1, so plus one here will do the trick
ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1);
@@ -40,6 +44,10 @@ void ConfigureGeneral::applyConfiguration() {
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
UISettings::values.theme =
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
+
+ UISettings::values.check_for_update_on_start = ui->toggle_update_check->isChecked();
+ UISettings::values.update_on_close = ui->toggle_auto_update->isChecked();
+
Settings::values.region_value = ui->region_combobox->currentIndex() - 1;
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
Settings::Apply();
diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui
index eedf2cbb0..00e5b49f5 100644
--- a/src/citra_qt/configuration/configure_general.ui
+++ b/src/citra_qt/configuration/configure_general.ui
@@ -25,16 +25,16 @@
-
-
-
+
- Search sub-directories for games
+ Confirm exit while emulation is running
-
-
+
- Confirm exit while emulation is running
+ Search sub-directories for games
@@ -44,24 +44,51 @@
-
-
-
- Performance
-
-
-
-
-
-
-
-
-
- Enable CPU JIT
-
-
-
-
-
+
+
+ Updates
+
+
+ -
+
+
-
+
+
+ Check for updates on start
+
+
+
+ -
+
+
+ Silently auto update after closing
+
+
+
-
+
+
+
+
+ -
+
+
+ Performance
+
+
+
-
+
+
-
+
+
+ Enable CPU JIT
+
+
+
+
+
+
+
-
@@ -149,8 +176,7 @@
-
-
-
+
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 00230d4bd..5976e092b 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -30,6 +30,7 @@
#include "citra_qt/hotkeys.h"
#include "citra_qt/main.h"
#include "citra_qt/ui_settings.h"
+#include "citra_qt/updater/updater.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
@@ -100,6 +101,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
InitializeDebugWidgets();
InitializeRecentFileMenuActions();
InitializeHotkeys();
+ ShowUpdaterWidgets();
SetDefaultUIGeometry();
RestoreUIState();
@@ -118,6 +120,10 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
// Show one-time "callout" messages to the user
ShowCallouts();
+ if (UISettings::values.check_for_update_on_start) {
+ CheckForUpdates();
+ }
+
QStringList args = QApplication::arguments();
if (args.length() >= 2) {
BootGame(args[1]);
@@ -139,6 +145,10 @@ void GMainWindow::InitializeWidgets() {
game_list = new GameList(this);
ui.horizontalLayout->addWidget(game_list);
+ // Setup updater
+ updater = new Updater(this);
+ UISettings::values.updater_found = updater->HasUpdater();
+
// Create status bar
message_label = new QLabel();
// Configured separately for left alignment
@@ -268,6 +278,13 @@ void GMainWindow::InitializeHotkeys() {
});
}
+void GMainWindow::ShowUpdaterWidgets() {
+ ui.action_Check_For_Updates->setVisible(UISettings::values.updater_found);
+ ui.action_Open_Maintenance_Tool->setVisible(UISettings::values.updater_found);
+
+ connect(updater, &Updater::CheckUpdatesDone, this, &GMainWindow::OnUpdateFound);
+}
+
void GMainWindow::SetDefaultUIGeometry() {
// geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
const QRect screenRect = QApplication::desktop()->screenGeometry(this);
@@ -346,6 +363,10 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_FAQ, &QAction::triggered,
[]() { QDesktopServices::openUrl(QUrl("https://citra-emu.org/wiki/faq/")); });
connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnMenuAboutCitra);
+ connect(ui.action_Check_For_Updates, &QAction::triggered, this,
+ &GMainWindow::OnCheckForUpdates);
+ connect(ui.action_Open_Maintenance_Tool, &QAction::triggered, this,
+ &GMainWindow::OnOpenUpdater);
}
void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -368,6 +389,46 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
}
}
+void GMainWindow::OnCheckForUpdates() {
+ CheckForUpdates();
+}
+
+void GMainWindow::CheckForUpdates() {
+ if (updater->CheckForUpdates()) {
+ LOG_INFO(Frontend, "Update check started");
+ } else {
+ LOG_WARNING(Frontend, "Unable to start check for updates");
+ }
+}
+
+void GMainWindow::OnUpdateFound(bool found, bool error) {
+ if (error) {
+ LOG_WARNING(Frontend, "Update check failed");
+ return;
+ }
+
+ if (!found) {
+ LOG_INFO(Frontend, "No updates found");
+ return;
+ }
+
+ LOG_INFO(Frontend, "Update found!");
+ auto result = QMessageBox::question(
+ this, tr("Update available!"),
+ tr("An update for Citra is available. Do you wish to install it now?
"
+ "This will terminate emulation, if it is running."),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
+
+ if (result == QMessageBox::Yes) {
+ updater->LaunchUIOnExit();
+ close();
+ }
+}
+
+void GMainWindow::OnOpenUpdater() {
+ updater->LaunchUI();
+}
+
bool GMainWindow::LoadROM(const QString& filename) {
// Shutdown previous session if the emu thread is still active...
if (emu_thread != nullptr)
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 18354bdf7..fe7ff6c4a 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -2,8 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#ifndef _CITRA_QT_MAIN_HXX_
-#define _CITRA_QT_MAIN_HXX_
+#pragma once
#include
#include
@@ -24,6 +23,7 @@ class GRenderWindow;
class MicroProfileDialog;
class ProfilerWidget;
class RegistersWidget;
+class Updater;
class WaitTreeWidget;
class AboutDialog;
@@ -82,6 +82,8 @@ private:
void ShutdownGame();
void ShowCallouts();
+ void ShowUpdaterWidgets();
+ void CheckForUpdates();
/**
* Stores the filename in the recently loaded files list.
@@ -134,6 +136,9 @@ private slots:
void OnCoreError(Core::System::ResultStatus, std::string);
/// Called whenever a user selects Help->About Citra
void OnMenuAboutCitra();
+ void OnUpdateFound(bool found, bool error);
+ void OnCheckForUpdates();
+ void OnOpenUpdater();
private:
void UpdateStatusBar();
@@ -166,6 +171,7 @@ private:
GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
GraphicsTracingWidget* graphicsTracingWidget;
WaitTreeWidget* waitTreeWidget;
+ Updater* updater;
QAction* actions_recent_files[max_recent_files_item];
@@ -174,5 +180,3 @@ protected:
void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
};
-
-#endif // _CITRA_QT_MAIN_HXX_
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui
index 442904f01..417a8a6a6 100644
--- a/src/citra_qt/main.ui
+++ b/src/citra_qt/main.ui
@@ -96,6 +96,9 @@
&Help
+
+
+
@@ -211,6 +214,19 @@
Fullscreen
+
+
+ Modify Citra Install
+
+
+ Opens the maintenance tool to modify your Citra installation
+
+
+
+
+ Check for Updates
+
+
diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h
index ffbe14bc3..dba4c5d3c 100644
--- a/src/citra_qt/ui_settings.h
+++ b/src/citra_qt/ui_settings.h
@@ -39,6 +39,10 @@ struct Values {
bool confirm_before_closing;
bool first_start;
+ bool updater_found;
+ bool update_on_close;
+ bool check_for_update_on_start;
+
QString roms_path;
QString symbols_path;
QString gamedir;
diff --git a/src/citra_qt/updater/updater.cpp b/src/citra_qt/updater/updater.cpp
new file mode 100644
index 000000000..399300266
--- /dev/null
+++ b/src/citra_qt/updater/updater.cpp
@@ -0,0 +1,304 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+//
+// Based on the original work by Felix Barx
+// Copyright (c) 2015, Felix Barz
+// All rights reserved.
+
+#include
+#include
+#include
+#include
+#include
+#include "citra_qt/ui_settings.h"
+#include "citra_qt/updater/updater.h"
+#include "citra_qt/updater/updater_p.h"
+#include "common/logging/log.h"
+
+#ifdef Q_OS_OSX
+#define DEFAULT_TOOL_PATH QStringLiteral("../../../../maintenancetool")
+#else
+#define DEFAULT_TOOL_PATH QStringLiteral("../maintenancetool")
+#endif
+
+Updater::Updater(QObject* parent) : Updater(DEFAULT_TOOL_PATH, parent) {}
+
+Updater::Updater(const QString& maintenance_tool_path, QObject* parent)
+ : QObject(parent), backend(std::make_unique(this)) {
+ backend->tool_path = UpdaterPrivate::ToSystemExe(maintenance_tool_path);
+}
+
+Updater::~Updater() = default;
+
+bool Updater::ExitedNormally() const {
+ return backend->normal_exit;
+}
+
+int Updater::ErrorCode() const {
+ return backend->last_error_code;
+}
+
+QByteArray Updater::ErrorLog() const {
+ return backend->last_error_log;
+}
+
+bool Updater::IsRunning() const {
+ return backend->running;
+}
+
+QList Updater::LatestUpdateInfo() const {
+ return backend->update_info;
+}
+
+bool Updater::HasUpdater() const {
+ return backend->HasUpdater();
+}
+
+bool Updater::CheckForUpdates() {
+ return backend->StartUpdateCheck();
+}
+
+void Updater::AbortUpdateCheck(int max_delay, bool async) {
+ backend->StopUpdateCheck(max_delay, async);
+}
+
+void Updater::LaunchUI() {
+ backend->LaunchUI();
+}
+
+void Updater::SilentlyUpdate() {
+ backend->SilentlyUpdate();
+}
+
+void Updater::LaunchUIOnExit() {
+ backend->LaunchUIOnExit();
+}
+
+Updater::UpdateInfo::UpdateInfo() = default;
+
+Updater::UpdateInfo::UpdateInfo(const Updater::UpdateInfo&) = default;
+
+Updater::UpdateInfo::UpdateInfo(QString name, QString version, quint64 size)
+ : name(std::move(name)), version(std::move(version)), size(size) {}
+
+UpdaterPrivate::UpdaterPrivate(Updater* parent_ptr) : QObject(nullptr), parent(parent_ptr) {
+ connect(qApp, &QCoreApplication::aboutToQuit, this, &UpdaterPrivate::AboutToExit,
+ Qt::DirectConnection);
+ qRegisterMetaType("QProcess::ExitStatus");
+}
+
+UpdaterPrivate::~UpdaterPrivate() {
+ if (main_process && main_process->state() != QProcess::NotRunning) {
+ main_process->kill();
+ main_process->waitForFinished(1000);
+ }
+}
+
+QString UpdaterPrivate::ToSystemExe(QString base_path) {
+#if defined(Q_OS_WIN32)
+ if (!base_path.endsWith(QStringLiteral(".exe")))
+ return base_path + QStringLiteral(".exe");
+ else
+ return base_path;
+#elif defined(Q_OS_OSX)
+ if (base_path.endsWith(QStringLiteral(".app")))
+ base_path.truncate(base_path.lastIndexOf(QStringLiteral(".")));
+ return base_path + QStringLiteral(".app/Contents/MacOS/") + QFileInfo(base_path).fileName();
+#elif defined(Q_OS_UNIX)
+ return base_path;
+#endif
+}
+
+bool UpdaterPrivate::HasUpdater() const {
+ QFileInfo tool_info(QCoreApplication::applicationDirPath(), tool_path);
+ return tool_info.exists();
+}
+
+bool UpdaterPrivate::StartUpdateCheck() {
+ if (running || !HasUpdater()) {
+ return false;
+ }
+
+ update_info.clear();
+ normal_exit = true;
+ last_error_code = EXIT_SUCCESS;
+ last_error_log.clear();
+
+ QFileInfo tool_info(QCoreApplication::applicationDirPath(), tool_path);
+ main_process = new QProcess(this);
+ main_process->setProgram(tool_info.absoluteFilePath());
+ main_process->setArguments({QStringLiteral("--checkupdates"), QStringLiteral("-v")});
+
+ connect(main_process,
+ static_cast(&QProcess::finished), this,
+ &UpdaterPrivate::UpdaterReady, Qt::QueuedConnection);
+ connect(main_process, static_cast(&QProcess::error),
+ this, &UpdaterPrivate::UpdaterError, Qt::QueuedConnection);
+
+ main_process->start(QIODevice::ReadOnly);
+ running = true;
+
+ emit parent->UpdateInfoChanged(update_info);
+ emit parent->RunningChanged(true);
+ return true;
+}
+
+void UpdaterPrivate::StopUpdateCheck(int delay, bool async) {
+ if (main_process == nullptr || main_process->state() == QProcess::NotRunning) {
+ return;
+ }
+
+ if (delay > 0) {
+ main_process->terminate();
+ if (async) {
+ QTimer *timer = new QTimer(this);
+ timer->setSingleShot(true);
+
+ connect(timer, &QTimer::timeout, [=]() {
+ StopUpdateCheck(0, false);
+ timer->deleteLater();
+ });
+
+ timer->start(delay);
+ } else {
+ if (!main_process->waitForFinished(delay)) {
+ main_process->kill();
+ main_process->waitForFinished(100);
+ }
+ }
+ } else {
+ main_process->kill();
+ main_process->waitForFinished(100);
+ }
+}
+
+XMLParseResult UpdaterPrivate::ParseResult(const QByteArray& output,
+ QList& out) {
+ const auto out_string = QString::fromUtf8(output);
+ const auto xml_begin = out_string.indexOf(QStringLiteral(""));
+ if (xml_begin < 0)
+ return XMLParseResult::NoUpdate;
+ const auto xml_end = out_string.indexOf(QStringLiteral(""), xml_begin);
+ if (xml_end < 0)
+ return XMLParseResult::NoUpdate;
+
+ QList updates;
+ QXmlStreamReader reader(out_string.mid(xml_begin, (xml_end + 10) - xml_begin));
+
+ reader.readNextStartElement();
+ // should always work because it was search for
+ if (reader.name() != QStringLiteral("updates")) {
+ return XMLParseResult::InvalidXML;
+ }
+
+ while (reader.readNextStartElement()) {
+ if (reader.name() != QStringLiteral("update"))
+ return XMLParseResult::InvalidXML;
+
+ auto ok = false;
+ Updater::UpdateInfo info(
+ reader.attributes().value(QStringLiteral("name")).toString(),
+ reader.attributes().value(QStringLiteral("version")).toString(),
+ reader.attributes().value(QStringLiteral("size")).toULongLong(&ok));
+
+ if (info.name.isEmpty() || info.version.isNull() || !ok)
+ return XMLParseResult::InvalidXML;
+ if (reader.readNextStartElement())
+ return XMLParseResult::InvalidXML;
+
+ updates.append(info);
+ }
+
+ if (reader.hasError()) {
+ LOG_ERROR(Frontend, "Cannot read xml for update: %s",
+ reader.errorString().toStdString().c_str());
+ return XMLParseResult::InvalidXML;
+ }
+
+ out = updates;
+ return XMLParseResult::Success;
+}
+
+void UpdaterPrivate::UpdaterReady(int exit_code, QProcess::ExitStatus exit_status) {
+ if (main_process == nullptr) {
+ return;
+ }
+
+ if (exit_status != QProcess::NormalExit) {
+ UpdaterError(QProcess::Crashed);
+ return;
+ }
+
+ normal_exit = true;
+ last_error_code = exit_code;
+ last_error_log = main_process->readAllStandardError();
+ const auto update_out = main_process->readAllStandardOutput();
+ main_process->deleteLater();
+ main_process = nullptr;
+
+ running = false;
+ emit parent->RunningChanged(false);
+
+ QList update_info;
+ auto err = ParseResult(update_out, update_info);
+ bool has_error = false;
+
+ if (err == XMLParseResult::Success) {
+ if (!update_info.isEmpty())
+ emit parent->UpdateInfoChanged(update_info);
+ } else if (err == XMLParseResult::InvalidXML) {
+ has_error = true;
+ }
+
+ emit parent->CheckUpdatesDone(!update_info.isEmpty(), has_error);
+}
+
+void UpdaterPrivate::UpdaterError(QProcess::ProcessError error) {
+ if (main_process) {
+ normal_exit = false;
+ last_error_code = error;
+ last_error_log = main_process->errorString().toUtf8();
+ main_process->deleteLater();
+ main_process = nullptr;
+
+ running = false;
+ emit parent->RunningChanged(false);
+ emit parent->CheckUpdatesDone(false, true);
+ }
+}
+
+void UpdaterPrivate::LaunchWithArguments(const QStringList& args) {
+ if (!HasUpdater()) {
+ return;
+ }
+
+ QFileInfo tool_info(QCoreApplication::applicationDirPath(), tool_path);
+
+ if (!QProcess::startDetached(tool_info.absoluteFilePath(), args, tool_info.absolutePath())) {
+ LOG_WARNING(Frontend, "Unable to start program %s",
+ tool_info.absoluteFilePath().toStdString().c_str());
+ }
+}
+
+void UpdaterPrivate::LaunchUI() {
+ LOG_INFO(Frontend, "Launching update UI...");
+ LaunchWithArguments(run_arguments);
+}
+
+void UpdaterPrivate::SilentlyUpdate() {
+ LOG_INFO(Frontend, "Launching silent update...");
+ LaunchWithArguments(silent_arguments);
+}
+
+void UpdaterPrivate::AboutToExit() {
+ if (launch_ui_on_exit) {
+ LaunchUI();
+ } else if (UISettings::values.update_on_close) {
+ SilentlyUpdate();
+ }
+}
+
+void UpdaterPrivate::LaunchUIOnExit() {
+ launch_ui_on_exit = true;
+}
diff --git a/src/citra_qt/updater/updater.h b/src/citra_qt/updater/updater.h
new file mode 100644
index 000000000..a7220dd91
--- /dev/null
+++ b/src/citra_qt/updater/updater.h
@@ -0,0 +1,120 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+//
+// Based on the original work by Felix Barx
+// Copyright (c) 2015, Felix Barz
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// * Neither the name of QtAutoUpdater nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class UpdaterPrivate;
+
+/// The main updater. Can check for updates and run the maintenancetool as updater
+class Updater : public QObject {
+ Q_OBJECT;
+
+ /// Specifies whether the updater is currently checking for updates or not
+ Q_PROPERTY(bool running READ IsRunning NOTIFY RunningChanged);
+ /// Holds extended information about the last update check
+ Q_PROPERTY(QList update_info READ LatestUpdateInfo NOTIFY UpdateInfoChanged);
+
+public:
+ /// Provides information about updates for components
+ struct UpdateInfo {
+ /// The name of the component that has an update
+ QString name;
+ /// The new version for that compontent
+ QString version;
+ /// The update download size (in Bytes)
+ quint64 size = 0;
+
+ /// Default Constructor
+ UpdateInfo();
+ /// Copy Constructor
+ UpdateInfo(const UpdateInfo& other);
+ /// Constructor that takes name, version and size
+ UpdateInfo(QString name, QString version, quint64 size);
+ };
+
+ /// Default constructor
+ explicit Updater(QObject* parent = nullptr);
+ /// Constructor with an explicitly set path
+ explicit Updater(const QString& maintenance_tool_path, QObject* parent = nullptr);
+ /// Destroys the updater and kills the update check (if running)
+ ~Updater();
+
+ /// Returns `true`, if the updater exited normally
+ bool ExitedNormally() const;
+ /// Returns the mainetancetools error code of the last update
+ int ErrorCode() const;
+ /// returns the error output (stderr) of the last update
+ QByteArray ErrorLog() const;
+
+ /// readAcFn{Updater::running}
+ bool IsRunning() const;
+ /// readAcFn{Updater::updateInfo}
+ QList LatestUpdateInfo() const;
+
+ /// Launches the updater UI formally
+ void LaunchUI();
+
+ /// Silently updates the application in the background
+ void SilentlyUpdate();
+
+ /// Checks to see if a updater application is available
+ bool HasUpdater() const;
+
+ /// Instead of silently updating, explictly open the UI on shutdown
+ void LaunchUIOnExit();
+
+public slots:
+ /// Starts checking for updates
+ bool CheckForUpdates();
+ /// Aborts checking for updates
+ void AbortUpdateCheck(int max_delay = 5000, bool async = false);
+
+signals:
+ /// Will be emitted as soon as the updater finished checking for updates
+ void CheckUpdatesDone(bool has_updates, bool has_error);
+
+ /// notifyAcFn{Updater::running}
+ void RunningChanged(bool running);
+ /// notifyAcFn{Updater::updateInfo}
+ void UpdateInfoChanged(QList update_info);
+
+private:
+ std::unique_ptr backend;
+};
diff --git a/src/citra_qt/updater/updater_p.h b/src/citra_qt/updater/updater_p.h
new file mode 100644
index 000000000..210400535
--- /dev/null
+++ b/src/citra_qt/updater/updater_p.h
@@ -0,0 +1,66 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+//
+// Based on the original work by Felix Barx
+// Copyright (c) 2015, Felix Barz
+// All rights reserved.
+
+#pragma once
+
+#include
+#include "citra_qt/updater/updater.h"
+
+enum class XMLParseResult {
+ Success,
+ NoUpdate,
+ InvalidXML,
+};
+
+class UpdaterPrivate : public QObject {
+ Q_OBJECT;
+
+public:
+ explicit UpdaterPrivate(Updater* parent_ptr);
+ ~UpdaterPrivate();
+
+ static QString ToSystemExe(QString base_path);
+
+ bool HasUpdater() const;
+
+ bool StartUpdateCheck();
+
+ void LaunchWithArguments(const QStringList& args);
+ void LaunchUI();
+ void SilentlyUpdate();
+
+ void LaunchUIOnExit();
+
+public slots:
+ void StopUpdateCheck(int delay, bool async);
+ void UpdaterReady(int exit_code, QProcess::ExitStatus exit_status);
+ void UpdaterError(QProcess::ProcessError error);
+
+ void AboutToExit();
+
+private:
+ XMLParseResult ParseResult(const QByteArray& output, QList& out);
+
+ Updater* parent;
+
+ QString tool_path{};
+ QList update_info{};
+ bool normal_exit = true;
+ int last_error_code = 0;
+ QByteArray last_error_log = EXIT_SUCCESS;
+
+ bool running = false;
+ QProcess* main_process = nullptr;
+
+ bool launch_ui_on_exit = false;
+
+ QStringList run_arguments{"--updater"};
+ QStringList silent_arguments{"--silentUpdate"};
+
+ friend class Updater;
+};