diff --git a/CMakeLists.txt b/CMakeLists.txt index 98f3be14a..b1ed98399 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.3.0 +project(${PROJECT} VERSION 4.8.2.0 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index aeed439b6..0faacec92 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -463,4 +463,15 @@ void AmneziaApplication::initControllers() m_systemController.reset(new SystemController(m_settings)); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); + + m_updateController.reset(new UpdateController(m_settings)); + m_engine->rootContext()->setContextProperty("UpdateController", m_updateController.get()); + +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + connect(m_updateController.get(), &UpdateController::updateFound, this, [this]() { + QTimer::singleShot(1000, this, [this]() { m_pageController->showChangelogDrawer(); }); + }); + + m_updateController->checkForUpdates(); +#endif } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index cfeac0d1f..8a4f80b18 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -25,6 +25,7 @@ #include "ui/controllers/sitesController.h" #include "ui/controllers/systemController.h" #include "ui/controllers/appSplitTunnelingController.h" +#include "ui/controllers/updateController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" @@ -134,6 +135,7 @@ class AmneziaApplication : public AMNEZIA_BASE_CLASS QScopedPointer m_sitesController; QScopedPointer m_systemController; QScopedPointer m_appSplitTunnelingController; + QScopedPointer m_updateController; QNetworkAccessManager *m_nam; diff --git a/client/client_scripts/linux_installer.sh b/client/client_scripts/linux_installer.sh new file mode 100644 index 000000000..829875357 --- /dev/null +++ b/client/client_scripts/linux_installer.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +EXTRACT_DIR="$1" +INSTALLER_PATH="$2" + +# Create and clean extract directory +rm -rf "$EXTRACT_DIR" +mkdir -p "$EXTRACT_DIR" + +# Extract ZIP archive +unzip "$INSTALLER_PATH" -d "$EXTRACT_DIR" +if [ $? -ne 0 ]; then + echo 'Failed to extract ZIP archive' + exit 1 +fi + +# Find and extract TAR archive +TAR_FILE=$(find "$EXTRACT_DIR" -name '*.tar' -type f) +if [ -z "$TAR_FILE" ]; then + echo 'TAR file not found' + exit 1 +fi + +tar -xf "$TAR_FILE" -C "$EXTRACT_DIR" +if [ $? -ne 0 ]; then + echo 'Failed to extract TAR archive' + exit 1 +fi + +rm -f "$TAR_FILE" + +# Find and run installer +INSTALLER=$(find "$EXTRACT_DIR" -type f -executable) +if [ -z "$INSTALLER" ]; then + echo 'Installer not found' + exit 1 +fi + +"$INSTALLER" +EXIT_CODE=$? + +# Cleanup +rm -rf "$EXTRACT_DIR" +exit $EXIT_CODE \ No newline at end of file diff --git a/client/client_scripts/mac_installer.sh b/client/client_scripts/mac_installer.sh new file mode 100644 index 000000000..186f15024 --- /dev/null +++ b/client/client_scripts/mac_installer.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +EXTRACT_DIR="$1" +INSTALLER_PATH="$2" + +# Create and clean extract directory +rm -rf "$EXTRACT_DIR" +mkdir -p "$EXTRACT_DIR" + +# Mount the DMG +MOUNT_POINT="$EXTRACT_DIR/mounted_dmg" +hdiutil attach "$INSTALLER_PATH" -mountpoint "$MOUNT_POINT" +if [ $? -ne 0 ]; then + echo "Failed to mount DMG" + exit 1 +fi + +# Check if the application exists in the mounted DMG +if [ ! -d "$MOUNT_POINT/AmneziaVPN.app" ]; then + echo "Error: AmneziaVPN.app not found in the mounted DMG." + hdiutil detach "$MOUNT_POINT" #-quiet + exit 1 +fi + +# Run the application +echo "Running AmneziaVPN.app from the mounted DMG..." +open "$MOUNT_POINT/AmneziaVPN.app" + +# Get the PID of the app launched from the DMG +APP_PATH="$MOUNT_POINT/AmneziaVPN.app" +PID=$(pgrep -f "$APP_PATH") + +if [ -z "$PID" ]; then + echo "Failed to retrieve PID for AmneziaVPN.app" + hdiutil detach "$MOUNT_POINT" + exit 1 +fi + +# Wait for the specific PID to exit +echo "Waiting for AmneziaVPN.app to exit..." +while kill -0 "$PID" 2>/dev/null; do + sleep 1 +done + +# Unmount the DMG +hdiutil detach "$EXTRACT_DIR/mounted_dmg" +if [ $? -ne 0 ]; then + echo "Failed to unmount DMG" + exit 1 +fi + +# Optional: Remove the DMG file +rm "$INSTALLER_PATH" + +echo "Installation completed successfully" +exit 0 diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 95b5df4ad..d2b17cb93 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -54,6 +54,15 @@ QString amnezia::scriptName(ProtocolScriptType type) } } +QString amnezia::scriptName(ClientScriptType type) +{ + switch (type) { + case ClientScriptType::linux_installer: return QLatin1String("linux_installer.sh"); + case ClientScriptType::mac_installer: return QLatin1String("mac_installer.sh"); + default: return QString(); + } +} + QString amnezia::scriptData(amnezia::SharedScriptType type) { QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type)); @@ -81,3 +90,19 @@ QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer co data.replace("\r", ""); return data; } + +QString amnezia::scriptData(ClientScriptType type) +{ + QString fileName = QString(":/client_scripts/%1").arg(amnezia::scriptName(type)); + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "Warning: script missing" << fileName; + return ""; + } + QByteArray data = file.readAll(); + if (data.isEmpty()) { + qDebug() << "Warning: script is empty" << fileName; + } + data.replace("\r", ""); + return data; +} diff --git a/client/core/scripts_registry.h b/client/core/scripts_registry.h index d952dafb0..87fddbb56 100644 --- a/client/core/scripts_registry.h +++ b/client/core/scripts_registry.h @@ -1,44 +1,53 @@ #ifndef SCRIPTS_REGISTRY_H #define SCRIPTS_REGISTRY_H -#include -#include "core/defs.h" #include "containers/containers_defs.h" +#include "core/defs.h" +#include -namespace amnezia { - -enum SharedScriptType { - // General scripts - prepare_host, - install_docker, - build_container, - remove_container, - remove_all_containers, - setup_host_firewall, - check_connection, - check_server_is_busy, - check_user_in_sudo -}; -enum ProtocolScriptType { - // Protocol scripts - dockerfile, - run_container, - configure_container, - container_startup, - openvpn_template, - wireguard_template, - awg_template, - xray_template -}; - - -QString scriptFolder(DockerContainer container); - -QString scriptName(SharedScriptType type); -QString scriptName(ProtocolScriptType type); - -QString scriptData(SharedScriptType type); -QString scriptData(ProtocolScriptType type, DockerContainer container); +namespace amnezia +{ + + enum SharedScriptType { + // General scripts + prepare_host, + install_docker, + build_container, + remove_container, + remove_all_containers, + setup_host_firewall, + check_connection, + check_server_is_busy, + check_user_in_sudo + }; + + enum ProtocolScriptType { + // Protocol scripts + dockerfile, + run_container, + configure_container, + container_startup, + openvpn_template, + wireguard_template, + awg_template, + xray_template + }; + + enum ClientScriptType { + // Client-side scripts + linux_installer, + mac_installer + }; + + QString scriptFolder(DockerContainer container); + + QString scriptName(SharedScriptType type); + QString scriptName(ProtocolScriptType type); + QString scriptName(ClientScriptType type); + + QString scriptData(SharedScriptType type); + QString scriptData(ProtocolScriptType type, DockerContainer container); + QString scriptData(ClientScriptType type); } #endif // SCRIPTS_REGISTRY_H diff --git a/client/resources.qrc b/client/resources.qrc index ff03a6e7d..692b5d292 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -59,6 +59,9 @@ images/tray/active.png images/tray/default.png images/tray/error.png + client_scripts/linux_installer.sh + client_scripts/mac_installer.sh + server_scripts/openvpn_cloak/Dockerfile server_scripts/awg/configure_container.sh server_scripts/awg/Dockerfile server_scripts/awg/run_container.sh @@ -172,10 +175,11 @@ ui/qml/Controls2/TopCloseButtonType.qml ui/qml/Controls2/VerticalRadioButton.qml ui/qml/Controls2/WarningType.qml + ui/qml/Components/ChangelogDrawer.qml + ui/qml/Modules/Style/qmldir ui/qml/Filters/ContainersModelFilters.qml ui/qml/main2.qml ui/qml/Modules/Style/AmneziaStyle.qml - ui/qml/Modules/Style/qmldir ui/qml/Pages2/PageDeinstalling.qml ui/qml/Pages2/PageDevMenu.qml ui/qml/Pages2/PageHome.qml diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index ffbdd3a1c..312dc392e 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -137,6 +137,7 @@ public slots: void escapePressed(); void closeTopDrawer(); + void showChangelogDrawer(); private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h index 8cb3a0d16..1f4fbed39 100644 --- a/client/ui/controllers/systemController.h +++ b/client/ui/controllers/systemController.h @@ -9,7 +9,7 @@ class SystemController : public QObject { Q_OBJECT public: - explicit SystemController(const std::shared_ptr &setting, QObject *parent = nullptr); + explicit SystemController(const std::shared_ptr &settings, QObject *parent = nullptr); static void saveFile(const QString &fileName, const QString &data); static bool readFile(const QString &fileName, QByteArray &data); diff --git a/client/ui/controllers/updateController.cpp b/client/ui/controllers/updateController.cpp new file mode 100644 index 000000000..770ca75cc --- /dev/null +++ b/client/ui/controllers/updateController.cpp @@ -0,0 +1,288 @@ +#include "updateController.h" + +#include +#include +#include +#include + +#include "amnezia_application.h" +#include "core/errorstrings.h" +#include "core/scripts_registry.h" +#include "logger.h" +#include "version.h" + +namespace +{ + Logger logger("UpdateController"); + +#ifdef Q_OS_MACOS + const QString installerPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.dmg"; +#elif defined Q_OS_WINDOWS + const QString installerPath = + QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN_installer.exe"; +#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + const QString installerPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.tar.zip"; +#endif +} + +UpdateController::UpdateController(const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_settings(settings) +{ +} + +QString UpdateController::getHeaderText() +{ + return tr("New version released: %1 (%2)").arg(m_version, m_releaseDate); +} + +QString UpdateController::getChangelogText() +{ + return m_changelogText; +} + +void UpdateController::checkForUpdates() +{ + QNetworkRequest request; + request.setTransferTimeout(7000); + QString endpoint = "https://api.github.com/repos/amnezia-vpn/amnezia-client/releases/latest"; + request.setUrl(endpoint); + + QNetworkReply *reply = amnApp->manager()->get(request); + + QObject::connect(reply, &QNetworkReply::finished, [this, reply]() { + if (reply->error() == QNetworkReply::NoError) { + QString contents = QString::fromUtf8(reply->readAll()); + QJsonObject data = QJsonDocument::fromJson(contents.toUtf8()).object(); + m_version = data.value("tag_name").toString(); + + auto currentVersion = QVersionNumber::fromString(QString(APP_VERSION)); + auto newVersion = QVersionNumber::fromString(m_version); + if (newVersion > currentVersion) { + m_changelogText = data.value("body").toString(); + + QString dateString = data.value("published_at").toString(); + QDateTime dateTime = QDateTime::fromString(dateString, "yyyy-MM-ddTHH:mm:ssZ"); + m_releaseDate = dateTime.toString("MMM dd yyyy"); + + QJsonArray assets = data.value("assets").toArray(); + + for (auto asset : assets) { + QJsonObject assetObject = asset.toObject(); +#ifdef Q_OS_WINDOWS + if (assetObject.value("name").toString().endsWith(".exe")) { + m_downloadUrl = assetObject.value("browser_download_url").toString(); + } +#elif defined(Q_OS_MACOS) + if (assetObject.value("name").toString().endsWith(".dmg")) { + m_downloadUrl = assetObject.value("browser_download_url").toString(); + } +#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + if (assetObject.value("name").toString().contains(".tar.zip")) { + m_downloadUrl = assetObject.value("browser_download_url").toString(); + } +#endif + } + + emit updateFound(); + } + } else { + if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError + || reply->error() == QNetworkReply::NetworkError::TimeoutError) { + logger.error() << errorString(ErrorCode::ApiConfigTimeoutError); + } else { + QString err = reply->errorString(); + logger.error() << QString::fromUtf8(reply->readAll()); + logger.error() << "Network error code:" << QString::number(static_cast(reply->error())); + logger.error() << "Error message:" << err; + logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + logger.error() << errorString(ErrorCode::ApiConfigDownloadError); + } + } + + reply->deleteLater(); + }); + + QObject::connect(reply, &QNetworkReply::errorOccurred, [this, reply](QNetworkReply::NetworkError error) { + logger.error() << "Network error occurred:" << reply->errorString() << error; + }); + connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList &errors) { + QStringList errorStrings; + for (const QSslError &error : errors) { + errorStrings << error.errorString(); + } + logger.error() << "SSL errors:" << errorStrings; + logger.error() << errorString(ErrorCode::ApiConfigSslError); + }); +} + +void UpdateController::runInstaller() +{ +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + if (m_downloadUrl.isEmpty()) { + logger.error() << "Download URL is empty"; + return; + } + + QNetworkRequest request; + request.setTransferTimeout(7000); + request.setUrl(m_downloadUrl); + + QNetworkReply *reply = amnApp->manager()->get(request); + + QObject::connect(reply, &QNetworkReply::finished, [this, reply]() { + if (reply->error() == QNetworkReply::NoError) { + QFile file(installerPath); + if (!file.open(QIODevice::WriteOnly)) { + logger.error() << "Failed to open installer file for writing:" << installerPath + << "Error:" << file.errorString(); + reply->deleteLater(); + return; + } + + if (file.write(reply->readAll()) == -1) { + logger.error() << "Failed to write installer data to file:" << installerPath + << "Error:" << file.errorString(); + file.close(); + reply->deleteLater(); + return; + } + + file.close(); + +#if defined(Q_OS_WINDOWS) + runWindowsInstaller(installerPath); +#elif defined(Q_OS_MACOS) + runMacInstaller(installerPath); +#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + runLinuxInstaller(installerPath); +#endif + } else { + if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError + || reply->error() == QNetworkReply::NetworkError::TimeoutError) { + logger.error() << errorString(ErrorCode::ApiConfigTimeoutError); + } else { + QString err = reply->errorString(); + logger.error() << QString::fromUtf8(reply->readAll()); + logger.error() << "Network error code:" << QString::number(static_cast(reply->error())); + logger.error() << "Error message:" << err; + logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + logger.error() << errorString(ErrorCode::ApiConfigDownloadError); + } + } + reply->deleteLater(); + }); +#endif +} + +#if defined(Q_OS_WINDOWS) +int UpdateController::runWindowsInstaller(const QString &installerPath) +{ + qint64 pid; + bool success = QProcess::startDetached(installerPath, QStringList(), QString(), &pid); + + if (success) { + logger.info() << "Installation process started with PID:" << pid; + } else { + logger.error() << "Failed to start installation process"; + return -1; + } + + return 0; +} +#endif + +#if defined(Q_OS_MACOS) +int UpdateController::runMacInstaller(const QString &installerPath) +{ + // Create temporary directory for extraction + QTemporaryDir extractDir; + extractDir.setAutoRemove(false); + if (!extractDir.isValid()) { + logger.error() << "Failed to create temporary directory"; + return -1; + } + logger.info() << "Temporary directory created:" << extractDir.path(); + + // Create script file in the temporary directory + QString scriptPath = extractDir.path() + "/mac_installer.sh"; + QFile scriptFile(scriptPath); + if (!scriptFile.open(QIODevice::WriteOnly)) { + logger.error() << "Failed to create script file"; + return -1; + } + + // Get script content from registry + QString scriptContent = amnezia::scriptData(amnezia::ClientScriptType::mac_installer); + if (scriptContent.isEmpty()) { + logger.error() << "macOS installer script content is empty"; + scriptFile.close(); + return -1; + } + + scriptFile.write(scriptContent.toUtf8()); + scriptFile.close(); + logger.info() << "Script file created:" << scriptPath; + + // Make script executable + QFile::setPermissions(scriptPath, QFile::permissions(scriptPath) | QFile::ExeUser); + + // Start detached process + qint64 pid; + bool success = QProcess::startDetached( + "/bin/bash", QStringList() << scriptPath << extractDir.path() << installerPath, extractDir.path(), &pid); + + if (success) { + logger.info() << "Installation process started with PID:" << pid; + } else { + logger.error() << "Failed to start installation process"; + return -1; + } + + return 0; +} +#endif + +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) +int UpdateController::runLinuxInstaller(const QString &installerPath) +{ + // Create temporary directory for extraction + QTemporaryDir extractDir; + extractDir.setAutoRemove(false); + if (!extractDir.isValid()) { + logger.error() << "Failed to create temporary directory"; + return -1; + } + logger.info() << "Temporary directory created:" << extractDir.path(); + + // Create script file in the temporary directory + QString scriptPath = extractDir.path() + "/installer.sh"; + QFile scriptFile(scriptPath); + if (!scriptFile.open(QIODevice::WriteOnly)) { + logger.error() << "Failed to create script file"; + return -1; + } + + // Get script content from registry + QString scriptContent = amnezia::scriptData(amnezia::ClientScriptType::linux_installer); + scriptFile.write(scriptContent.toUtf8()); + scriptFile.close(); + logger.info() << "Script file created:" << scriptPath; + + // Make script executable + QFile::setPermissions(scriptPath, QFile::permissions(scriptPath) | QFile::ExeUser); + + // Start detached process + qint64 pid; + bool success = QProcess::startDetached( + "/bin/bash", QStringList() << scriptPath << extractDir.path() << installerPath, extractDir.path(), &pid); + + if (success) { + logger.info() << "Installation process started with PID:" << pid; + } else { + logger.error() << "Failed to start installation process"; + return -1; + } + + return 0; +} +#endif diff --git a/client/ui/controllers/updateController.h b/client/ui/controllers/updateController.h new file mode 100644 index 000000000..1f667c045 --- /dev/null +++ b/client/ui/controllers/updateController.h @@ -0,0 +1,42 @@ +#ifndef UPDATECONTROLLER_H +#define UPDATECONTROLLER_H + +#include + +#include "settings.h" + +class UpdateController : public QObject +{ + Q_OBJECT +public: + explicit UpdateController(const std::shared_ptr &settings, QObject *parent = nullptr); + + Q_PROPERTY(QString changelogText READ getChangelogText NOTIFY updateFound) + Q_PROPERTY(QString headerText READ getHeaderText NOTIFY updateFound) +public slots: + QString getHeaderText(); + QString getChangelogText(); + + void checkForUpdates(); + void runInstaller(); +signals: + void updateFound(); + +private: + std::shared_ptr m_settings; + + QString m_changelogText; + QString m_version; + QString m_releaseDate; + QString m_downloadUrl; + +#if defined(Q_OS_WINDOWS) + int runWindowsInstaller(const QString &installerPath); +#elif defined(Q_OS_MACOS) + int runMacInstaller(const QString &installerPath); +#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + int runLinuxInstaller(const QString &installerPath); +#endif +}; + +#endif // UPDATECONTROLLER_H diff --git a/client/ui/qml/Components/ChangelogDrawer.qml b/client/ui/qml/Components/ChangelogDrawer.qml new file mode 100644 index 000000000..1bb767be4 --- /dev/null +++ b/client/ui/qml/Components/ChangelogDrawer.qml @@ -0,0 +1,103 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +import "../Config" + +DrawerType2 { + id: root + + anchors.fill: parent + expandedHeight: parent.height * 0.9 + + expandedStateContent: Item { + implicitHeight: root.expandedHeight + + Header2TextType { + id: header + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.bottomMargin: 16 + + text: UpdateController.headerText + } + + FlickableType { + anchors.top: header.bottom + anchors.bottom: updateButton.top + contentHeight: changelog.height + 32 + + ParagraphTextType { + id: changelog + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.bottomMargin: 16 + + HoverHandler { + enabled: parent.hoveredLink + cursorShape: Qt.PointingHandCursor + } + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + + text: UpdateController.changelogText + textFormat: Text.MarkdownText + } + } + + BasicButtonType { + id: updateButton + anchors.bottom: skipButton.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottomMargin: 8 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + text: qsTr("Update") + + clickedFunc: function() { + PageController.showBusyIndicator(true) + UpdateController.runInstaller() + PageController.showBusyIndicator(false) + root.closeTriggered() + } + } + + BasicButtonType { + id: skipButton + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottomMargin: 16 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Skip") + + clickedFunc: function() { + root.closeTriggered() + } + } + } +} diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 8b73e62d2..c57bbd0ae 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -96,6 +96,10 @@ Window { busyIndicator.visible = visible PageController.disableControls(visible) } + + function onShowChangelogDrawer() { + changelogDrawer.openTriggered() + } } Connections { @@ -285,4 +289,14 @@ Window { onAccepted: SystemController.fileDialogClosed(true) onRejected: SystemController.fileDialogClosed(false) } + + Item { + anchors.fill: parent + + ChangelogDrawer { + id: changelogDrawer + + anchors.fill: parent + } + } } diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 6dd0071e1..ba3d9de0b 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -8,8 +8,8 @@ #include "logger.h" #include "router.h" -#include "../core/networkUtilities.h" #include "../client/protocols/protocols_defs.h" +#include "../core/networkUtilities.h" #ifdef Q_OS_WIN #include "../client/platforms/windows/daemon/windowsdaemon.h" #include "../client/platforms/windows/daemon/windowsfirewall.h" @@ -59,10 +59,23 @@ int IpcServer::createPrivilegedProcess() } }); - QObject::connect(pd.serverNode.data(), &QRemoteObjectHost::error, this, - [pd](QRemoteObjectNode::ErrorCode errorCode) { qDebug() << "QRemoteObjectHost::error" << errorCode; }); + QObject::connect(pd.serverNode.data(), &QRemoteObjectHost::error, this, [pd](QRemoteObjectNode::ErrorCode errorCode) { + qDebug() << "QRemoteObjectHost::error" << errorCode; + }); - QObject::connect(pd.serverNode.data(), &QRemoteObjectHost::destroyed, this, [pd]() { qDebug() << "QRemoteObjectHost::destroyed"; }); + QObject::connect(pd.serverNode.data(), &QRemoteObjectHost::destroyed, this, + [pd]() { qDebug() << "QRemoteObjectHost::destroyed"; }); + + // connect(pd.ipcProcess.data(), &IpcServerProcess::finished, this, [this, pid=m_localpid](int exitCode, + // QProcess::ExitStatus exitStatus){ + // qDebug() << "IpcServerProcess finished" << exitCode << exitStatus; + //// if (m_processes.contains(pid)) { + //// m_processes[pid].ipcProcess.reset(); + //// m_processes[pid].serverNode.reset(); + //// m_processes[pid].localServer.reset(); + //// m_processes.remove(pid); + //// } + // }); m_processes.insert(m_localpid, pd); diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index 9810046b9..f66dae90a 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -1,11 +1,11 @@ #ifndef IPCSERVER_H #define IPCSERVER_H +#include "../client/daemon/interfaceconfig.h" +#include #include #include #include -#include -#include "../client/daemon/interfaceconfig.h" #include "ipc.h" #include "ipcserverprocess.h" @@ -37,13 +37,15 @@ class IpcServer : public IpcInterfaceSource virtual bool enablePeerTraffic(const QJsonObject &configStr) override; virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override; virtual bool disableKillSwitch() override; - virtual bool updateResolvers(const QString& ifname, const QList& resolvers) override; + virtual bool updateResolvers(const QString &ifname, const QList &resolvers) override; private: int m_localpid = 0; - struct ProcessDescriptor { - ProcessDescriptor (QObject *parent = nullptr) { + struct ProcessDescriptor + { + ProcessDescriptor(QObject *parent = nullptr) + { serverNode = QSharedPointer(new QRemoteObjectHost(parent)); ipcProcess = QSharedPointer(new IpcServerProcess(parent)); tun2socksProcess = QSharedPointer(new IpcProcessTun2Socks(parent));