From 871037f887dae54493be6bf73b0c14c70a6f7ac7 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 25 May 2024 10:04:41 +0200 Subject: [PATCH] added changelog drawer --- client/amnezia_application.cpp | 8 + client/amnezia_application.h | 2 + client/core/controllers/apiController.cpp | 2 +- client/resources.qrc | 3 +- client/ui/controllers/pageController.h | 2 + client/ui/controllers/systemController.h | 2 +- client/ui/controllers/updateController.cpp | 149 +++++++++++++++++++ client/ui/controllers/updateController.h | 34 +++++ client/ui/qml/Components/ChangelogDrawer.qml | 119 +++++++++++++++ client/ui/qml/main2.qml | 14 ++ ipc/ipc_interface.rep | 2 + ipc/ipcserver.cpp | 97 ++++++------ ipc/ipcserver.h | 1 + 13 files changed, 384 insertions(+), 51 deletions(-) create mode 100644 client/ui/controllers/updateController.cpp create mode 100644 client/ui/controllers/updateController.h create mode 100644 client/ui/qml/Components/ChangelogDrawer.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 06d2f9acb..1bd196fd0 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -406,4 +406,12 @@ 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()); + m_updateController->checkForUpdates(); + + connect(m_updateController.get(), &UpdateController::updateFound, this, [this]() { + QTimer::singleShot(1000, this, [this]() { m_pageController->showChangelogDrawer(); }); + }); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 5561d7c7e..395ed2378 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -24,6 +24,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" @@ -130,6 +131,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/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp index fa0fcaec3..ab8fd5d36 100644 --- a/client/core/controllers/apiController.cpp +++ b/client/core/controllers/apiController.cpp @@ -99,7 +99,7 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c QByteArray requestBody = QJsonDocument(apiPayload).toJson(); - QNetworkReply *reply = amnApp->manager()->post(request, requestBody); // ?? + QNetworkReply *reply = amnApp->manager()->post(request, requestBody); QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable { if (reply->error() == QNetworkReply::NoError) { diff --git a/client/resources.qrc b/client/resources.qrc index 49fd66d35..8a42e5645 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -198,7 +198,7 @@ ui/qml/Pages2/PageProtocolOpenVpnSettings.qml ui/qml/Pages2/PageProtocolShadowSocksSettings.qml ui/qml/Pages2/PageProtocolCloakSettings.qml - ui/qml/Pages2/PageProtocolXraySettings.qml + ui/qml/Pages2/PageProtocolXraySettings.qml ui/qml/Pages2/PageProtocolRaw.qml ui/qml/Pages2/PageSettingsLogging.qml ui/qml/Pages2/PageServiceSftpSettings.qml @@ -239,5 +239,6 @@ images/controls/alert-circle.svg images/controls/file-check-2.svg ui/qml/Controls2/WarningType.qml + ui/qml/Components/ChangelogDrawer.qml diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index b286b1b1a..58454ef69 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -126,6 +126,8 @@ public slots: void forceTabBarActiveFocus(); void forceStackActiveFocus(); + void showChangelogDrawer(); + private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h index 274df2349..7dbf89471 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(QString fileName, const QString &data); diff --git a/client/ui/controllers/updateController.cpp b/client/ui/controllers/updateController.cpp new file mode 100644 index 000000000..6bf6f9fd2 --- /dev/null +++ b/client/ui/controllers/updateController.cpp @@ -0,0 +1,149 @@ +#include "updateController.h" + +#include +#include +#include +#include + +#include "amnezia_application.h" +#include "core/errorstrings.h" +#include "version.h" + +namespace { +#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.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)); + qDebug() << currentVersion; + 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(); + if (assetObject.value("name").toString().contains(".dmg")) { + m_downloadUrl = assetObject.value("browser_download_url").toString(); + } + } + + emit updateFound(); + } + } else { + if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError + || reply->error() == QNetworkReply::NetworkError::TimeoutError) { + qDebug() << errorString(ErrorCode::ApiConfigTimeoutError); + } else { + QString err = reply->errorString(); + qDebug() << QString::fromUtf8(reply->readAll()); + qDebug() << reply->error(); + qDebug() << err; + qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + qDebug() << errorString(ErrorCode::ApiConfigDownloadError); + } + } + + reply->deleteLater(); + }); + + QObject::connect(reply, &QNetworkReply::errorOccurred, + [this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; }); + connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList &errors) { + qDebug().noquote() << errors; + qDebug() << errorString(ErrorCode::ApiConfigSslError); + }); +} + +void UpdateController::runInstaller() +{ + 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)) { + file.write(reply->readAll()); + file.close(); + + QFutureWatcher watcher; + QFuture future = QtConcurrent::run([this]() { + QString t = installerPath; + QRemoteObjectPendingReply ipcReply = IpcClient::Interface()->mountDmg(t, true); + ipcReply.waitForFinished(); + QProcess::execute("/Volumes/AmneziaVPN/AmneziaVPN.app/Contents/MacOS/AmneziaVPN"); + ipcReply = IpcClient::Interface()->mountDmg(t, false); + ipcReply.waitForFinished(); + return ipcReply.returnValue(); + }); + + QEventLoop wait; + connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + watcher.setFuture(future); + wait.exec(); + + qDebug() << future.result(); + +// emit errorOccured(""); + } + } else { + if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError + || reply->error() == QNetworkReply::NetworkError::TimeoutError) { + qDebug() << errorString(ErrorCode::ApiConfigTimeoutError); + } else { + QString err = reply->errorString(); + qDebug() << QString::fromUtf8(reply->readAll()); + qDebug() << reply->error(); + qDebug() << err; + qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + qDebug() << errorString(ErrorCode::ApiConfigDownloadError); + } + } + + reply->deleteLater(); + }); + +} diff --git a/client/ui/controllers/updateController.h b/client/ui/controllers/updateController.h new file mode 100644 index 000000000..986174acf --- /dev/null +++ b/client/ui/controllers/updateController.h @@ -0,0 +1,34 @@ +#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(); + void errorOccured(const QString &errorMessage); +private: + std::shared_ptr m_settings; + + QString m_changelogText; + QString m_version; + QString m_releaseDate; + QString m_downloadUrl; +}; + +#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..c2eae80e5 --- /dev/null +++ b/client/ui/qml/Components/ChangelogDrawer.qml @@ -0,0 +1,119 @@ +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 + + expandedContent: Item { + implicitHeight: root.expandedHeight + + Connections { + target: root + enabled: !GC.isMobile() + function onOpened() { + focusItem.forceActiveFocus() + } + } + + Header2TextType { + id: header + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.rightMargin: 16 + anchors.leftMargin: 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: 48 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HoverHandler { + enabled: parent.hoveredLink + cursorShape: Qt.PointingHandCursor + } + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + + text: UpdateController.changelogText + textFormat: Text.MarkdownText + } + } + + Item { + id: focusItem + KeyNavigation.tab: updateButton + } + + BasicButtonType { + id: updateButton + anchors.bottom: skipButton.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.bottomMargin: 8 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + text: qsTr("Update") + + clickedFunc: function() { + PageController.showBusyIndicator(true) + UpdateController.runInstaller() + PageController.showBusyIndicator(false) + root.close() + } + + KeyNavigation.tab: skipButton + } + + 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 this version") + + clickedFunc: function() { + root.close() + } + + KeyNavigation.tab: focusItem + } + } +} diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 7e31bb09d..a366fd2db 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -92,6 +92,10 @@ Window { busyIndicator.visible = visible PageController.disableControls(visible) } + + function onShowChangelogDrawer() { + changelogDrawer.open() + } } Connections { @@ -264,4 +268,14 @@ Window { onAccepted: SystemController.fileDialogClosed(true) onRejected: SystemController.fileDialogClosed(false) } + + Item { + anchors.fill: parent + + ChangelogDrawer { + id: changelogDrawer + + anchors.fill: parent + } + } } diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index 79f2d0422..7b49b8b79 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -32,5 +32,7 @@ class IpcInterface SLOT( bool enablePeerTraffic( const QJsonObject &configStr) ); SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) ); SLOT( bool updateResolvers(const QString& ifname, const QList& resolvers) ); + + SLOT( int mountDmg(const QString &path, bool mount) ); }; diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index c734912b1..9b72a5534 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -1,32 +1,33 @@ #include "ipcserver.h" -#include #include -#include #include +#include +#include +#include -#include "router.h" #include "logger.h" +#include "router.h" #include "../client/protocols/protocols_defs.h" #ifdef Q_OS_WIN -#include "tapcontroller_win.h" -#include "../client/platforms/windows/daemon/windowsfirewall.h" -#include "../client/platforms/windows/daemon/windowsdaemon.h" + #include "../client/platforms/windows/daemon/windowsdaemon.h" + #include "../client/platforms/windows/daemon/windowsfirewall.h" + #include "tapcontroller_win.h" #endif #ifdef Q_OS_LINUX -#include "../client/platforms/linux/daemon/linuxfirewall.h" + #include "../client/platforms/linux/daemon/linuxfirewall.h" #endif #ifdef Q_OS_MACOS -#include "../client/platforms/macos/daemon/macosfirewall.h" + #include "../client/platforms/macos/daemon/macosfirewall.h" #endif -IpcServer::IpcServer(QObject *parent): - IpcInterfaceSource(parent) +IpcServer::IpcServer(QObject *parent) : IpcInterfaceSource(parent) -{} +{ +} int IpcServer::createPrivilegedProcess() { @@ -58,23 +59,20 @@ 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); -//// } -// }); + // 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); @@ -105,7 +103,7 @@ bool IpcServer::routeDeleteList(const QString &gw, const QStringList &ips) qDebug() << "IpcServer::routeDeleteList"; #endif - return Router::routeDeleteList(gw ,ips); + return Router::routeDeleteList(gw, ips); } void IpcServer::flushDns() @@ -172,7 +170,7 @@ bool IpcServer::deleteTun(const QString &dev) return Router::deleteTun(dev); } -bool IpcServer::updateResolvers(const QString& ifname, const QList& resolvers) +bool IpcServer::updateResolvers(const QString &ifname, const QList &resolvers) { return Router::updateResolvers(ifname, resolvers); } @@ -194,13 +192,11 @@ void IpcServer::setLogsEnabled(bool enabled) if (enabled) { Logger::init(); - } - else { + } else { Logger::deinit(); } } - bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { #ifdef Q_OS_WIN @@ -216,13 +212,11 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd QStringList allownets; QStringList blocknets; - if (splitTunnelType == 0) - { + if (splitTunnelType == 0) { blockAll = true; allowNets = true; allownets.append(configStr.value(amnezia::config_key::hostName).toString()); - } else if (splitTunnelType == 1) - { + } else if (splitTunnelType == 1) { blockNets = true; for (auto v : splitTunnelSites) { blocknets.append(v.toString()); @@ -264,18 +258,17 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd // double-check + ensure our firewall is installed and enabled. This is necessary as // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) - if (!MacOSFirewall::isInstalled()) MacOSFirewall::install(); + if (!MacOSFirewall::isInstalled()) + MacOSFirewall::install(); MacOSFirewall::ensureRootAnchorPriority(); MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll); MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), allowNets); - MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, - QStringLiteral("allownets"), allownets); + MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, QStringLiteral("allownets"), allownets); MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), blockNets); - MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, - QStringLiteral("blocknets"), blocknets); + MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, QStringLiteral("blocknets"), blocknets); MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); @@ -326,10 +319,8 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) // Use APP split tunnel if (splitTunnelType == 0 || splitTunnelType == 2) { - config.m_allowedIPAddressRanges.append( - IPAddress(QHostAddress("0.0.0.0"), 0)); - config.m_allowedIPAddressRanges.append( - IPAddress(QHostAddress("::"), 0)); + config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("0.0.0.0"), 0)); + config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("::"), 0)); } if (splitTunnelType == 1) { @@ -337,10 +328,9 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) QString ipRange = v.toString(); if (ipRange.split('/').size() > 1) { config.m_allowedIPAddressRanges.append( - IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit()))); + IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit()))); } else { - config.m_allowedIPAddressRanges.append( - IPAddress(QHostAddress(ipRange), 32)); + config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress(ipRange), 32)); } } } @@ -353,7 +343,7 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) } } - for (const QJsonValue& i : configStr.value(amnezia::config_key::splitTunnelApps).toArray()) { + for (const QJsonValue &i : configStr.value(amnezia::config_key::splitTunnelApps).toArray()) { if (!i.isString()) { break; } @@ -371,3 +361,14 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) #endif return true; } + +int IpcServer::mountDmg(const QString &path, bool mount) +{ +#ifdef Q_OS_MACOS + qDebug() << path; + auto res = QProcess::execute(QString("sudo hdiutil %1 %2").arg(mount ? "attach" : "unmount", path)); + qDebug() << res; + return res; +#endif + return 0; +} diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index bd474481c..21e2a591c 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -35,6 +35,7 @@ class IpcServer : public IpcInterfaceSource virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override; virtual bool disableKillSwitch() override; virtual bool updateResolvers(const QString& ifname, const QList& resolvers) override; + virtual int mountDmg(const QString &path, bool mount) override; private: int m_localpid = 0;