Merge branch 'dev' into ios-wireguard
|
@ -1,5 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/vpnicon_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/vpnicon_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
|
@ -1,5 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/vpnicon_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/vpnicon_round"/>
|
|
||||||
</adaptive-icon>
|
|
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="vpnicon_background">#000000</color>
|
|
||||||
</resources>
|
|
|
@ -377,11 +377,11 @@ class VPNService : android.net.VpnService() {
|
||||||
val wgConfig: String = wireguard_conf!!.toWgUserspaceString()
|
val wgConfig: String = wireguard_conf!!.toWgUserspaceString()
|
||||||
val builder = Builder()
|
val builder = Builder()
|
||||||
setupBuilder(wireguard_conf, builder)
|
setupBuilder(wireguard_conf, builder)
|
||||||
builder.setSession("mvpn0")
|
builder.setSession("avpn0")
|
||||||
builder.establish().use { tun ->
|
builder.establish().use { tun ->
|
||||||
if (tun == null)return
|
if (tun == null)return
|
||||||
Log.i(tag, "Go backend " + wgVersion())
|
Log.i(tag, "Go backend " + wgVersion())
|
||||||
currentTunnelHandle = wgTurnOn("mvpn0", tun.detachFd(), wgConfig)
|
currentTunnelHandle = wgTurnOn("avpn0", tun.detachFd(), wgConfig)
|
||||||
}
|
}
|
||||||
if (currentTunnelHandle < 0) {
|
if (currentTunnelHandle < 0) {
|
||||||
Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
|
Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
|
||||||
|
|
|
@ -39,6 +39,7 @@ HEADERS += \
|
||||||
managementserver.h \
|
managementserver.h \
|
||||||
protocols/protocols_defs.h \
|
protocols/protocols_defs.h \
|
||||||
settings.h \
|
settings.h \
|
||||||
|
ui/notificationhandler.h \
|
||||||
ui/models/containers_model.h \
|
ui/models/containers_model.h \
|
||||||
ui/models/protocols_model.h \
|
ui/models/protocols_model.h \
|
||||||
ui/pages.h \
|
ui/pages.h \
|
||||||
|
@ -94,6 +95,7 @@ SOURCES += \
|
||||||
managementserver.cpp \
|
managementserver.cpp \
|
||||||
protocols/protocols_defs.cpp \
|
protocols/protocols_defs.cpp \
|
||||||
settings.cpp \
|
settings.cpp \
|
||||||
|
ui/notificationhandler.cpp \
|
||||||
ui/models/containers_model.cpp \
|
ui/models/containers_model.cpp \
|
||||||
ui/models/protocols_model.cpp \
|
ui/models/protocols_model.cpp \
|
||||||
ui/pages_logic/AppSettingsLogic.cpp \
|
ui/pages_logic/AppSettingsLogic.cpp \
|
||||||
|
@ -133,6 +135,8 @@ TRANSLATIONS = \
|
||||||
translations/amneziavpn_ru.ts
|
translations/amneziavpn_ru.ts
|
||||||
|
|
||||||
win32 {
|
win32 {
|
||||||
|
DEFINES += MVPN_WINDOWS
|
||||||
|
|
||||||
OTHER_FILES += platform_win/vpnclient.rc
|
OTHER_FILES += platform_win/vpnclient.rc
|
||||||
RC_FILE = platform_win/vpnclient.rc
|
RC_FILE = platform_win/vpnclient.rc
|
||||||
|
|
||||||
|
@ -168,6 +172,8 @@ win32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
macx {
|
macx {
|
||||||
|
DEFINES += MVPN_MACOS
|
||||||
|
|
||||||
ICON = $$PWD/images/app.icns
|
ICON = $$PWD/images/app.icns
|
||||||
|
|
||||||
HEADERS += ui/macos_util.h
|
HEADERS += ui/macos_util.h
|
||||||
|
@ -180,6 +186,8 @@ macx {
|
||||||
}
|
}
|
||||||
|
|
||||||
linux:!android {
|
linux:!android {
|
||||||
|
DEFINES += MVPN_LINUX
|
||||||
|
|
||||||
LIBS += /usr/lib/x86_64-linux-gnu/libcrypto.a
|
LIBS += /usr/lib/x86_64-linux-gnu/libcrypto.a
|
||||||
LIBS += /usr/lib/x86_64-linux-gnu/libssl.a
|
LIBS += /usr/lib/x86_64-linux-gnu/libssl.a
|
||||||
}
|
}
|
||||||
|
@ -187,6 +195,7 @@ linux:!android {
|
||||||
win32|macx|linux:!android {
|
win32|macx|linux:!android {
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
ui/systemtray_notificationhandler.h \
|
||||||
protocols/openvpnprotocol.h \
|
protocols/openvpnprotocol.h \
|
||||||
protocols/ikev2_vpn_protocol.h \
|
protocols/ikev2_vpn_protocol.h \
|
||||||
protocols/openvpnovercloakprotocol.h \
|
protocols/openvpnovercloakprotocol.h \
|
||||||
|
@ -194,6 +203,7 @@ win32|macx|linux:!android {
|
||||||
protocols/wireguardprotocol.h \
|
protocols/wireguardprotocol.h \
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
ui/systemtray_notificationhandler.cpp \
|
||||||
protocols/openvpnprotocol.cpp \
|
protocols/openvpnprotocol.cpp \
|
||||||
protocols/ikev2_vpn_protocol.cpp \
|
protocols/ikev2_vpn_protocol.cpp \
|
||||||
protocols/openvpnovercloakprotocol.cpp \
|
protocols/openvpnovercloakprotocol.cpp \
|
||||||
|
@ -203,12 +213,20 @@ win32|macx|linux:!android {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
QT += androidextras
|
QT += androidextras
|
||||||
|
DEFINES += MVPN_ANDROID
|
||||||
|
|
||||||
INCLUDEPATH += platforms/android
|
INCLUDEPATH += platforms/android
|
||||||
|
|
||||||
HEADERS += protocols/android_vpnprotocol.h \
|
HEADERS += \
|
||||||
|
platforms/android/android_controller.h \
|
||||||
|
platforms/android/android_notificationhandler.h \
|
||||||
|
protocols/android_vpnprotocol.h
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
platforms/android/android_controller.cpp \
|
||||||
|
platforms/android/android_notificationhandler.cpp \
|
||||||
|
protocols/android_vpnprotocol.cpp
|
||||||
|
|
||||||
SOURCES += protocols/android_vpnprotocol.cpp \
|
|
||||||
|
|
||||||
DISTFILES += \
|
DISTFILES += \
|
||||||
android/AndroidManifest.xml \
|
android/AndroidManifest.xml \
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "servercontroller.h"
|
#include "servercontroller.h"
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
|
@ -10,6 +11,7 @@
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
#include "sftpchannel.h"
|
#include "sftpchannel.h"
|
||||||
#include "sshconnectionmanager.h"
|
#include "sshconnectionmanager.h"
|
||||||
|
@ -134,12 +136,7 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
|
||||||
{
|
{
|
||||||
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
||||||
|
|
||||||
QString mkdir = "sudo docker exec -i $CONTAINER_NAME mkdir -p /opt/amnezia/";
|
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
|
||||||
ErrorCode e = runScript(credentials,
|
|
||||||
replaceVars(mkdir, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
|
||||||
if (e) return e;
|
|
||||||
|
|
||||||
e = uploadTextFileToContainer(container, credentials, script, fileName);
|
|
||||||
if (e) return e;
|
if (e) return e;
|
||||||
|
|
||||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName);
|
QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName);
|
||||||
|
@ -167,6 +164,14 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// mkdir
|
||||||
|
QFileInfo fi(path);
|
||||||
|
QString mkdir = "sudo docker exec -i $CONTAINER_NAME mkdir -p " + fi.absoluteDir().absolutePath();
|
||||||
|
e = runScript(credentials,
|
||||||
|
replaceVars(mkdir, genVarsForScript(credentials, container)));
|
||||||
|
if (e) return e;
|
||||||
|
|
||||||
|
|
||||||
if (overwriteMode == QSsh::SftpOverwriteMode::SftpOverwriteExisting) {
|
if (overwriteMode == QSsh::SftpOverwriteMode::SftpOverwriteExisting) {
|
||||||
e = runScript(credentials,
|
e = runScript(credentials,
|
||||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
||||||
|
|
67
client/images/icon_src.svg
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M1216 2523 c-3 -21 -8 -91 -12 -156 -3 -64 -10 -120 -15 -123 -5 -3
|
||||||
|
-9 -13 -9 -23 0 -26 -48 -71 -76 -71 -13 0 -24 -4 -24 -9 0 -5 -10 -11 -22
|
||||||
|
-13 -16 -2 -24 -11 -26 -30 -3 -22 -9 -27 -43 -33 -21 -4 -39 -10 -39 -15 0
|
||||||
|
-4 -29 -13 -65 -20 -36 -7 -65 -16 -65 -21 0 -4 -20 -11 -45 -15 -40 -6 -47
|
||||||
|
-11 -65 -48 -36 -72 -51 -96 -60 -96 -5 0 -17 -14 -25 -31 -14 -25 -21 -30
|
||||||
|
-47 -27 -27 3 -32 -1 -46 -36 -9 -22 -31 -53 -49 -69 -18 -16 -33 -34 -33 -40
|
||||||
|
0 -5 -13 -37 -29 -71 -15 -34 -31 -71 -35 -83 -4 -13 -11 -23 -15 -23 -4 0
|
||||||
|
-15 -24 -26 -52 -10 -29 -21 -57 -25 -63 -4 -5 -13 -34 -21 -62 -7 -29 -16
|
||||||
|
-53 -20 -53 -11 0 -31 -154 -35 -274 l-4 -110 -43 -45 c-24 -26 -65 -63 -92
|
||||||
|
-84 -119 -89 -132 -113 -57 -102 26 4 52 11 57 15 16 14 144 32 169 24 22 -7
|
||||||
|
66 -49 66 -64 0 -5 52 -63 116 -130 l117 -121 -6 -96 c-4 -52 -11 -101 -17
|
||||||
|
-108 -12 -14 -27 -145 -16 -145 3 0 19 11 35 25 18 17 34 47 46 88 9 34 21 71
|
||||||
|
26 82 5 11 13 32 19 48 5 15 16 27 25 27 8 0 15 5 15 10 0 16 43 12 86 -9 126
|
||||||
|
-60 153 -71 177 -71 15 0 27 -4 27 -8 0 -4 62 -16 138 -27 75 -10 141 -22 147
|
||||||
|
-27 12 -11 121 -10 143 1 9 5 44 15 77 21 33 7 78 20 100 30 23 10 54 22 70
|
||||||
|
26 17 4 35 10 41 15 15 12 133 39 170 39 23 0 33 -5 37 -19 2 -10 17 -25 32
|
||||||
|
-32 18 -10 34 -31 49 -67 23 -57 87 -132 112 -132 15 0 16 10 9 82 -4 46 -10
|
||||||
|
85 -14 88 -8 5 -26 95 -37 188 -7 60 11 151 32 159 8 3 14 11 14 18 0 6 21 37
|
||||||
|
48 68 103 123 96 106 102 258 3 75 11 143 17 150 6 8 14 32 18 54 6 34 42 103
|
||||||
|
74 141 5 6 12 25 16 42 l7 32 -68 0 -67 0 6 47 c3 26 10 49 16 51 6 2 11 9 11
|
||||||
|
15 0 14 104 117 118 117 5 0 16 8 26 19 9 10 34 26 56 36 22 10 40 22 40 27 0
|
||||||
|
4 7 8 15 8 9 0 15 9 15 21 0 20 -3 21 -57 14 -32 -4 -62 -10 -68 -15 -18 -13
|
||||||
|
-145 -33 -177 -26 -17 3 -41 16 -52 29 -39 40 -104 94 -122 101 -13 5 -15 14
|
||||||
|
-10 45 6 41 -3 51 -44 51 -11 0 -39 19 -64 43 -45 43 -80 72 -151 126 -22 17
|
||||||
|
-47 36 -55 43 -8 7 -22 18 -30 23 -9 6 -32 20 -51 32 -28 19 -48 23 -111 23
|
||||||
|
-42 0 -80 5 -83 10 -3 6 -16 10 -28 10 -12 0 -29 6 -38 13 -27 20 -68 109 -80
|
||||||
|
172 -7 33 -16 65 -20 70 -4 6 -13 33 -19 60 -15 70 -29 95 -56 95 -18 0 -23
|
||||||
|
-7 -28 -37z m409 -738 c32 -16 75 -39 95 -52 19 -13 43 -29 54 -36 49 -32 113
|
||||||
|
-88 134 -116 35 -47 34 -81 -3 -100 -16 -9 -35 -21 -41 -27 -7 -6 -33 -17 -58
|
||||||
|
-24 -26 -7 -49 -17 -52 -21 -3 -5 -29 -9 -59 -9 -49 0 -56 3 -79 33 -14 18
|
||||||
|
-33 53 -41 78 -9 24 -22 58 -29 74 -8 17 -19 57 -26 90 -7 33 -16 62 -20 65
|
||||||
|
-4 3 -10 22 -14 44 l-7 39 43 -5 c24 -3 70 -18 103 -33z m-595 -1 c0 -9 -6
|
||||||
|
-30 -14 -47 -12 -27 -65 -193 -83 -262 -3 -11 -9 -24 -13 -30 -4 -5 -15 -41
|
||||||
|
-25 -80 -10 -38 -21 -74 -25 -80 -4 -5 -13 -35 -20 -65 -7 -30 -16 -57 -19
|
||||||
|
-60 -3 -3 -11 -14 -17 -24 -6 -11 -19 -24 -30 -30 -10 -6 -21 -14 -24 -17 -3
|
||||||
|
-3 -22 -12 -42 -20 -21 -7 -38 -16 -38 -20 0 -4 -31 -15 -70 -25 -38 -10 -70
|
||||||
|
-22 -70 -26 0 -4 -19 -8 -42 -8 l-41 0 6 98 c9 130 24 183 84 297 42 79 94
|
||||||
|
144 184 229 112 105 114 106 128 106 6 0 14 6 17 13 2 7 25 22 49 32 25 11 45
|
||||||
|
23 45 28 0 4 14 7 30 7 21 0 30 -5 30 -16z m239 -6 c7 -13 23 -41 36 -63 14
|
||||||
|
-22 35 -60 46 -85 12 -25 25 -47 29 -50 4 -3 13 -23 20 -45 7 -22 17 -45 22
|
||||||
|
-52 6 -7 12 -39 15 -73 5 -54 3 -62 -18 -84 -36 -35 -98 -63 -170 -76 -35 -6
|
||||||
|
-83 -15 -107 -21 -23 -5 -54 -9 -67 -9 -28 0 -43 23 -28 43 6 6 16 35 23 62 7
|
||||||
|
28 16 55 20 60 4 6 15 45 25 88 10 42 21 77 25 77 4 0 10 19 14 42 4 23 13 56
|
||||||
|
21 72 7 17 19 49 25 71 21 68 47 84 69 43z m807 -449 c8 -13 7 -261 -2 -329
|
||||||
|
-3 -25 -9 -54 -14 -65 -5 -11 -13 -48 -19 -82 -7 -34 -18 -76 -27 -95 -55
|
||||||
|
-123 -71 -133 -102 -66 -51 108 -71 158 -77 192 -4 20 -10 36 -14 36 -4 0 -13
|
||||||
|
21 -21 47 -7 26 -19 58 -26 72 -16 31 -39 155 -31 174 8 21 142 83 212 98 33
|
||||||
|
7 64 16 70 21 13 12 43 10 51 -3z m-514 -219 c37 -29 55 -74 67 -170 8 -64 19
|
||||||
|
-101 45 -150 78 -148 98 -196 103 -247 7 -66 -7 -95 -53 -113 -19 -8 -34 -17
|
||||||
|
-34 -21 0 -4 -21 -15 -47 -25 -27 -10 -50 -21 -53 -25 -16 -22 -77 -29 -259
|
||||||
|
-29 -197 0 -289 9 -346 34 -16 8 -45 17 -63 21 -18 4 -52 25 -75 47 l-42 40 1
|
||||||
|
89 c1 49 6 95 12 102 5 6 13 30 16 51 11 61 64 145 136 216 80 78 88 85 137
|
||||||
|
107 21 10 40 20 43 23 3 3 25 12 50 19 25 7 54 17 65 22 26 12 133 26 211 28
|
||||||
|
47 1 67 -4 86 -19z m-882 -286 c0 -30 -32 -154 -41 -159 -5 -4 -9 -16 -9 -29
|
||||||
|
0 -12 -6 -31 -14 -41 -14 -19 -15 -19 -49 -1 -34 19 -87 103 -87 138 0 33 68
|
||||||
|
88 109 88 12 0 21 5 21 10 0 6 16 10 35 10 25 0 35 -4 35 -16z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
|
@ -116,12 +116,12 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
app.setQuitOnLastWindowClosed(false);
|
app.setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
qRegisterMetaType<VpnProtocol::VpnConnectionState>("VpnProtocol::ConnectionState");
|
qRegisterMetaType<VpnProtocol::VpnConnectionState>("VpnProtocol::VpnConnectionState");
|
||||||
qRegisterMetaType<ServerCredentials>("ServerCredentials");
|
qRegisterMetaType<ServerCredentials>("ServerCredentials");
|
||||||
|
|
||||||
qRegisterMetaType<DockerContainer>("DockerContainer");
|
qRegisterMetaType<DockerContainer>("DockerContainer");
|
||||||
qRegisterMetaType<TransportProto>("TransportProto");
|
qRegisterMetaType<TransportProto>("TransportProto");
|
||||||
qRegisterMetaType<Proto>("Protocol");
|
qRegisterMetaType<Proto>("Proto");
|
||||||
qRegisterMetaType<ServiceType>("ServiceType");
|
qRegisterMetaType<ServiceType>("ServiceType");
|
||||||
qRegisterMetaType<Page>("Page");
|
qRegisterMetaType<Page>("Page");
|
||||||
qRegisterMetaType<VpnProtocol::VpnConnectionState>("ConnectionState");
|
qRegisterMetaType<VpnProtocol::VpnConnectionState>("ConnectionState");
|
||||||
|
|
334
client/platforms/android/android_controller.cpp
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
#include <QAndroidBinder>
|
||||||
|
#include <QAndroidIntent>
|
||||||
|
#include <QAndroidJniEnvironment>
|
||||||
|
#include <QAndroidJniObject>
|
||||||
|
#include <QAndroidParcel>
|
||||||
|
#include <QAndroidServiceConnection>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
#include <QTextCodec>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QtAndroid>
|
||||||
|
|
||||||
|
#include "android_controller.h"
|
||||||
|
#include "core/errorstrings.h"
|
||||||
|
|
||||||
|
// Binder Codes for VPNServiceBinder
|
||||||
|
// See also - VPNServiceBinder.kt
|
||||||
|
// Actions that are Requestable
|
||||||
|
const int ACTION_ACTIVATE = 1;
|
||||||
|
const int ACTION_DEACTIVATE = 2;
|
||||||
|
const int ACTION_REGISTERLISTENER = 3;
|
||||||
|
const int ACTION_REQUEST_STATISTIC = 4;
|
||||||
|
const int ACTION_REQUEST_GET_LOG = 5;
|
||||||
|
const int ACTION_REQUEST_CLEANUP_LOG = 6;
|
||||||
|
const int ACTION_RESUME_ACTIVATE = 7;
|
||||||
|
const int ACTION_SET_NOTIFICATION_TEXT = 8;
|
||||||
|
const int ACTION_SET_NOTIFICATION_FALLBACK = 9;
|
||||||
|
|
||||||
|
// Event Types that will be Dispatched after registration
|
||||||
|
const int EVENT_INIT = 0;
|
||||||
|
const int EVENT_CONNECTED = 1;
|
||||||
|
const int EVENT_DISCONNECTED = 2;
|
||||||
|
const int EVENT_STATISTIC_UPDATE = 3;
|
||||||
|
const int EVENT_BACKEND_LOGS = 4;
|
||||||
|
const int EVENT_ACTIVATION_ERROR = 5;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
AndroidController* s_instance = nullptr;
|
||||||
|
|
||||||
|
constexpr auto PERMISSIONHELPER_CLASS =
|
||||||
|
"org/amnezia/vpn/qt/VPNPermissionHelper";
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
AndroidController::AndroidController():
|
||||||
|
m_binder(this)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidController* AndroidController::instance() {
|
||||||
|
if (!s_instance) s_instance = new AndroidController();
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidController::initialize()
|
||||||
|
{
|
||||||
|
qDebug() << "Initializing";
|
||||||
|
|
||||||
|
// Hook in the native implementation for startActivityForResult into the JNI
|
||||||
|
JNINativeMethod methods[]{{"startActivityForResult",
|
||||||
|
"(Landroid/content/Intent;)V",
|
||||||
|
reinterpret_cast<void*>(startActivityForResult)}};
|
||||||
|
QAndroidJniObject javaClass(PERMISSIONHELPER_CLASS);
|
||||||
|
QAndroidJniEnvironment env;
|
||||||
|
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
|
||||||
|
env->RegisterNatives(objectClass, methods,
|
||||||
|
sizeof(methods) / sizeof(methods[0]));
|
||||||
|
env->DeleteLocalRef(objectClass);
|
||||||
|
|
||||||
|
auto appContext = QtAndroid::androidActivity().callObjectMethod(
|
||||||
|
"getApplicationContext", "()Landroid/content/Context;");
|
||||||
|
|
||||||
|
QAndroidJniObject::callStaticMethod<void>(
|
||||||
|
"org/amnezia/vpn/VPNService", "startService",
|
||||||
|
"(Landroid/content/Context;)V", appContext.object());
|
||||||
|
|
||||||
|
// Start the VPN Service (if not yet) and Bind to it
|
||||||
|
const bool bindResult = QtAndroid::bindService(
|
||||||
|
QAndroidIntent(appContext.object(), "org.amnezia.vpn.VPNService"),
|
||||||
|
*this, QtAndroid::BindFlag::AutoCreate);
|
||||||
|
qDebug() << "Binding to the service..." << bindResult;
|
||||||
|
|
||||||
|
return bindResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode AndroidController::start()
|
||||||
|
{
|
||||||
|
|
||||||
|
//qDebug().noquote() << "AndroidController::start" << QJsonDocument(m_rawConfig).toJson();
|
||||||
|
qDebug() << "Prompting for VPN permission";
|
||||||
|
auto appContext = QtAndroid::androidActivity().callObjectMethod(
|
||||||
|
"getApplicationContext", "()Landroid/content/Context;");
|
||||||
|
QAndroidJniObject::callStaticMethod<void>(
|
||||||
|
PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V",
|
||||||
|
appContext.object());
|
||||||
|
|
||||||
|
QAndroidParcel sendData;
|
||||||
|
sendData.writeData(QJsonDocument(m_vpnConfig).toJson());
|
||||||
|
bool activateResult = false;
|
||||||
|
while (!activateResult){
|
||||||
|
activateResult = m_serviceBinder.transact(ACTION_ACTIVATE, sendData, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return activateResult ? NoError : UnknownError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidController::stop() {
|
||||||
|
qDebug() << "AndroidController::stop";
|
||||||
|
|
||||||
|
// if (reason != ReasonNone) {
|
||||||
|
// // Just show that we're disconnected
|
||||||
|
// // we're doing the actual disconnect once
|
||||||
|
// // the vpn-service has the new server ready in Action->Activate
|
||||||
|
// emit disconnected();
|
||||||
|
// qCritical() << "deactivation skipped for Switching";
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
QAndroidParcel nullData;
|
||||||
|
m_serviceBinder.transact(ACTION_DEACTIVATE, nullData, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activates the tunnel that is currently set
|
||||||
|
// in the VPN Service
|
||||||
|
void AndroidController::resumeStart() {
|
||||||
|
QAndroidParcel nullData;
|
||||||
|
m_serviceBinder.transact(ACTION_RESUME_ACTIVATE, nullData, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets the current notification text that is shown
|
||||||
|
*/
|
||||||
|
void AndroidController::setNotificationText(const QString& title,
|
||||||
|
const QString& message,
|
||||||
|
int timerSec) {
|
||||||
|
QJsonObject args;
|
||||||
|
args["title"] = title;
|
||||||
|
args["message"] = message;
|
||||||
|
args["sec"] = timerSec;
|
||||||
|
QJsonDocument doc(args);
|
||||||
|
QAndroidParcel data;
|
||||||
|
data.writeData(doc.toJson());
|
||||||
|
m_serviceBinder.transact(ACTION_SET_NOTIFICATION_TEXT, data, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets fallback Notification text that should be shown in case the VPN
|
||||||
|
* switches into the Connected state without the app open
|
||||||
|
* e.g via always-on vpn
|
||||||
|
*/
|
||||||
|
void AndroidController::setFallbackConnectedNotification() {
|
||||||
|
QJsonObject args;
|
||||||
|
args["title"] = tr("AmneziaVPN");
|
||||||
|
//% "Ready for you to connect"
|
||||||
|
//: Refers to the app - which is currently running the background and waiting
|
||||||
|
args["message"] = tr("VPN Connected");
|
||||||
|
QJsonDocument doc(args);
|
||||||
|
QAndroidParcel data;
|
||||||
|
data.writeData(doc.toJson());
|
||||||
|
m_serviceBinder.transact(ACTION_SET_NOTIFICATION_FALLBACK, data, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidController::checkStatus() {
|
||||||
|
qDebug() << "check status";
|
||||||
|
|
||||||
|
QAndroidParcel nullParcel;
|
||||||
|
m_serviceBinder.transact(ACTION_REQUEST_STATISTIC, nullParcel, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidController::getBackendLogs(std::function<void(const QString&)>&& a_callback) {
|
||||||
|
qDebug() << "get logs";
|
||||||
|
|
||||||
|
m_logCallback = std::move(a_callback);
|
||||||
|
QAndroidParcel nullData, replyData;
|
||||||
|
m_serviceBinder.transact(ACTION_REQUEST_GET_LOG, nullData, &replyData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidController::cleanupBackendLogs() {
|
||||||
|
qDebug() << "cleanup logs";
|
||||||
|
|
||||||
|
QAndroidParcel nullParcel;
|
||||||
|
m_serviceBinder.transact(ACTION_REQUEST_CLEANUP_LOG, nullParcel, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidController::onServiceConnected(
|
||||||
|
const QString& name, const QAndroidBinder& serviceBinder) {
|
||||||
|
qDebug() << "Server " + name + " connected";
|
||||||
|
|
||||||
|
Q_UNUSED(name);
|
||||||
|
|
||||||
|
m_serviceBinder = serviceBinder;
|
||||||
|
|
||||||
|
// Send the Service our Binder to recive incoming Events
|
||||||
|
QAndroidParcel binderParcel;
|
||||||
|
binderParcel.writeBinder(m_binder);
|
||||||
|
m_serviceBinder.transact(ACTION_REGISTERLISTENER, binderParcel, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidController::onServiceDisconnected(const QString& name) {
|
||||||
|
qDebug() << "Server disconnected";
|
||||||
|
m_serviceConnected = false;
|
||||||
|
Q_UNUSED(name);
|
||||||
|
// TODO: Maybe restart? Or crash?
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject &AndroidController::vpnConfig() const
|
||||||
|
{
|
||||||
|
return m_vpnConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidController::setVpnConfig(const QJsonObject &newVpnConfig)
|
||||||
|
{
|
||||||
|
m_vpnConfig = newVpnConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AndroidController::VPNBinder::onTransact
|
||||||
|
* @param code the Event-Type we get From the VPNService See
|
||||||
|
* @param data - Might contain UTF-8 JSON in case the Event has a payload
|
||||||
|
* @param reply - always null
|
||||||
|
* @param flags - unused
|
||||||
|
* @return Returns true is the code was a valid Event Code
|
||||||
|
*/
|
||||||
|
bool AndroidController::VPNBinder::onTransact(int code,
|
||||||
|
const QAndroidParcel& data,
|
||||||
|
const QAndroidParcel& reply,
|
||||||
|
QAndroidBinder::CallType flags) {
|
||||||
|
Q_UNUSED(data);
|
||||||
|
Q_UNUSED(reply);
|
||||||
|
Q_UNUSED(flags);
|
||||||
|
|
||||||
|
QJsonDocument doc;
|
||||||
|
QString buffer;
|
||||||
|
switch (code) {
|
||||||
|
case EVENT_INIT:
|
||||||
|
qDebug() << "Transact: init";
|
||||||
|
doc = QJsonDocument::fromJson(data.readData());
|
||||||
|
emit m_controller->initialized(
|
||||||
|
true, doc.object()["connected"].toBool(),
|
||||||
|
QDateTime::fromMSecsSinceEpoch(
|
||||||
|
doc.object()["time"].toVariant().toLongLong()));
|
||||||
|
// Pass a localised version of the Fallback string for the Notification
|
||||||
|
m_controller->setFallbackConnectedNotification();
|
||||||
|
|
||||||
|
break;
|
||||||
|
case EVENT_CONNECTED:
|
||||||
|
qDebug() << "Transact: connected";
|
||||||
|
emit m_controller->connectionStateChanged(VpnProtocol::Connected);
|
||||||
|
break;
|
||||||
|
case EVENT_DISCONNECTED:
|
||||||
|
qDebug() << "Transact: disconnected";
|
||||||
|
emit m_controller->connectionStateChanged(VpnProtocol::Disconnected);
|
||||||
|
break;
|
||||||
|
case EVENT_STATISTIC_UPDATE:
|
||||||
|
qDebug() << "Transact:: update";
|
||||||
|
|
||||||
|
// Data is here a JSON String
|
||||||
|
doc = QJsonDocument::fromJson(data.readData());
|
||||||
|
// TODO update counters
|
||||||
|
// emit m_controller->statusUpdated(doc.object()["endpoint"].toString(),
|
||||||
|
// doc.object()["deviceIpv4"].toString(),
|
||||||
|
// doc.object()["totalTX"].toInt(),
|
||||||
|
// doc.object()["totalRX"].toInt());
|
||||||
|
break;
|
||||||
|
case EVENT_BACKEND_LOGS:
|
||||||
|
qDebug() << "Transact: backend logs";
|
||||||
|
|
||||||
|
buffer = readUTF8Parcel(data);
|
||||||
|
if (m_controller->m_logCallback) {
|
||||||
|
m_controller->m_logCallback(buffer);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EVENT_ACTIVATION_ERROR:
|
||||||
|
qDebug() << "Transact: error";
|
||||||
|
emit m_controller->connectionStateChanged(VpnProtocol::Error);
|
||||||
|
|
||||||
|
default:
|
||||||
|
qWarning() << "Transact: Invalid!";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AndroidController::VPNBinder::readUTF8Parcel(QAndroidParcel data) {
|
||||||
|
// 106 is the Code for UTF-8
|
||||||
|
return QTextCodec::codecForMib(106)->toUnicode(data.readData());
|
||||||
|
}
|
||||||
|
|
||||||
|
const int ACTIVITY_RESULT_OK = 0xffffffff;
|
||||||
|
/**
|
||||||
|
* @brief Starts the Given intent in Context of the QTActivity
|
||||||
|
* @param env
|
||||||
|
* @param intent
|
||||||
|
*/
|
||||||
|
void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject intent)
|
||||||
|
{
|
||||||
|
qDebug() << "start activity";
|
||||||
|
Q_UNUSED(env);
|
||||||
|
QtAndroid::startActivity(intent, 1337,
|
||||||
|
[](int receiverRequestCode, int resultCode,
|
||||||
|
const QAndroidJniObject& data) {
|
||||||
|
// Currently this function just used in
|
||||||
|
// VPNService.kt::checkPersmissions. So the result
|
||||||
|
// we're getting is if the User gave us the
|
||||||
|
// Vpn.bind permission. In case of NO we should
|
||||||
|
// abort.
|
||||||
|
Q_UNUSED(receiverRequestCode);
|
||||||
|
Q_UNUSED(data);
|
||||||
|
|
||||||
|
AndroidController* controller =
|
||||||
|
AndroidController::instance();
|
||||||
|
if (!controller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultCode == ACTIVITY_RESULT_OK) {
|
||||||
|
qDebug() << "VPN PROMPT RESULT - Accepted";
|
||||||
|
controller->resumeStart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If the request got rejected abort the current
|
||||||
|
// connection.
|
||||||
|
qWarning() << "VPN PROMPT RESULT - Rejected";
|
||||||
|
emit controller->connectionStateChanged(VpnProtocol::Disconnected);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
84
client/platforms/android/android_controller.h
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#ifndef ANDROID_CONTROLLER_H
|
||||||
|
#define ANDROID_CONTROLLER_H
|
||||||
|
|
||||||
|
#include <QAndroidBinder>
|
||||||
|
#include <QAndroidServiceConnection>
|
||||||
|
|
||||||
|
#include "protocols/vpnprotocol.h"
|
||||||
|
using namespace amnezia;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AndroidController : public QObject, public QAndroidServiceConnection
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AndroidController();
|
||||||
|
static AndroidController* instance();
|
||||||
|
|
||||||
|
virtual ~AndroidController() override = default;
|
||||||
|
|
||||||
|
bool initialize();
|
||||||
|
|
||||||
|
ErrorCode start();
|
||||||
|
void stop();
|
||||||
|
void resumeStart();
|
||||||
|
|
||||||
|
void checkStatus();
|
||||||
|
void setNotificationText(const QString& title, const QString& message, int timerSec);
|
||||||
|
void setFallbackConnectedNotification();
|
||||||
|
void getBackendLogs(std::function<void(const QString&)>&& callback);
|
||||||
|
void cleanupBackendLogs();
|
||||||
|
|
||||||
|
// from QAndroidServiceConnection
|
||||||
|
void onServiceConnected(const QString& name, const QAndroidBinder& serviceBinder) override;
|
||||||
|
void onServiceDisconnected(const QString& name) override;
|
||||||
|
|
||||||
|
const QJsonObject &vpnConfig() const;
|
||||||
|
void setVpnConfig(const QJsonObject &newVpnConfig);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void connectionStateChanged(VpnProtocol::VpnConnectionState state);
|
||||||
|
|
||||||
|
// This signal is emitted when the controller is initialized. Note that the
|
||||||
|
// VPN tunnel can be already active. In this case, "connected" should be set
|
||||||
|
// to true and the "connectionDate" should be set to the activation date if
|
||||||
|
// known.
|
||||||
|
// If "status" is set to false, the backend service is considered unavailable.
|
||||||
|
void initialized(bool status, bool connected,
|
||||||
|
const QDateTime& connectionDate);
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
//Protocol m_protocol;
|
||||||
|
QJsonObject m_vpnConfig;
|
||||||
|
|
||||||
|
bool m_serviceConnected = false;
|
||||||
|
std::function<void(const QString&)> m_logCallback;
|
||||||
|
|
||||||
|
QAndroidBinder m_serviceBinder;
|
||||||
|
class VPNBinder : public QAndroidBinder {
|
||||||
|
public:
|
||||||
|
VPNBinder(AndroidController* controller) : m_controller(controller) {}
|
||||||
|
|
||||||
|
bool onTransact(int code, const QAndroidParcel& data,
|
||||||
|
const QAndroidParcel& reply,
|
||||||
|
QAndroidBinder::CallType flags) override;
|
||||||
|
|
||||||
|
QString readUTF8Parcel(QAndroidParcel data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AndroidController* m_controller = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
VPNBinder m_binder;
|
||||||
|
|
||||||
|
static void startActivityForResult(JNIEnv* env, jobject /*thiz*/, jobject intent);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ANDROID_CONTROLLER_H
|
21
client/platforms/android/android_notificationhandler.cpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "android_notificationhandler.h"
|
||||||
|
#include "platforms/android/android_controller.h"
|
||||||
|
|
||||||
|
AndroidNotificationHandler::AndroidNotificationHandler(QObject* parent)
|
||||||
|
: NotificationHandler(parent) {
|
||||||
|
}
|
||||||
|
AndroidNotificationHandler::~AndroidNotificationHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidNotificationHandler::notify(NotificationHandler::Message type,
|
||||||
|
const QString& title,
|
||||||
|
const QString& message, int timerMsec) {
|
||||||
|
Q_UNUSED(type);
|
||||||
|
qDebug() << "Send notification - " << message;
|
||||||
|
AndroidController::instance()->setNotificationText(title, message,
|
||||||
|
timerMsec / 1000);
|
||||||
|
}
|
24
client/platforms/android/android_notificationhandler.h
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef ANDROIDNOTIFICATIONHANDLER_H
|
||||||
|
#define ANDROIDNOTIFICATIONHANDLER_H
|
||||||
|
|
||||||
|
#include "ui/notificationhandler.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class AndroidNotificationHandler final : public NotificationHandler {
|
||||||
|
Q_DISABLE_COPY_MOVE(AndroidNotificationHandler)
|
||||||
|
|
||||||
|
public:
|
||||||
|
AndroidNotificationHandler(QObject* parent);
|
||||||
|
~AndroidNotificationHandler();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void notify(Message type, const QString& title, const QString& message,
|
||||||
|
int timerMsec) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ANDROIDNOTIFICATIONHANDLER_H
|
|
@ -17,338 +17,25 @@
|
||||||
#include "android_vpnprotocol.h"
|
#include "android_vpnprotocol.h"
|
||||||
#include "core/errorstrings.h"
|
#include "core/errorstrings.h"
|
||||||
|
|
||||||
// Binder Codes for VPNServiceBinder
|
#include "platforms/android/android_controller.h"
|
||||||
// See also - VPNServiceBinder.kt
|
|
||||||
// Actions that are Requestable
|
|
||||||
const int ACTION_ACTIVATE = 1;
|
|
||||||
const int ACTION_DEACTIVATE = 2;
|
|
||||||
const int ACTION_REGISTERLISTENER = 3;
|
|
||||||
const int ACTION_REQUEST_STATISTIC = 4;
|
|
||||||
const int ACTION_REQUEST_GET_LOG = 5;
|
|
||||||
const int ACTION_REQUEST_CLEANUP_LOG = 6;
|
|
||||||
const int ACTION_RESUME_ACTIVATE = 7;
|
|
||||||
const int ACTION_SET_NOTIFICATION_TEXT = 8;
|
|
||||||
const int ACTION_SET_NOTIFICATION_FALLBACK = 9;
|
|
||||||
|
|
||||||
// Event Types that will be Dispatched after registration
|
|
||||||
const int EVENT_INIT = 0;
|
|
||||||
const int EVENT_CONNECTED = 1;
|
|
||||||
const int EVENT_DISCONNECTED = 2;
|
|
||||||
const int EVENT_STATISTIC_UPDATE = 3;
|
|
||||||
const int EVENT_BACKEND_LOGS = 4;
|
|
||||||
const int EVENT_ACTIVATION_ERROR = 5;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
AndroidVpnProtocol* s_instance = nullptr;
|
|
||||||
|
|
||||||
constexpr auto PERMISSIONHELPER_CLASS =
|
|
||||||
"org/amnezia/vpn/qt/VPNPermissionHelper";
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
AndroidVpnProtocol::AndroidVpnProtocol(Proto protocol, const QJsonObject &configuration, QObject* parent)
|
AndroidVpnProtocol::AndroidVpnProtocol(Proto protocol, const QJsonObject &configuration, QObject* parent)
|
||||||
: VpnProtocol(configuration, parent),
|
: VpnProtocol(configuration, parent),
|
||||||
m_protocol(protocol),
|
m_protocol(protocol)
|
||||||
m_binder(this)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidVpnProtocol* AndroidVpnProtocol::instance() {
|
|
||||||
return s_instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidVpnProtocol::initialize()
|
|
||||||
{
|
|
||||||
qDebug() << "Initializing";
|
|
||||||
|
|
||||||
// Hook in the native implementation for startActivityForResult into the JNI
|
|
||||||
JNINativeMethod methods[]{{"startActivityForResult",
|
|
||||||
"(Landroid/content/Intent;)V",
|
|
||||||
reinterpret_cast<void*>(startActivityForResult)}};
|
|
||||||
QAndroidJniObject javaClass(PERMISSIONHELPER_CLASS);
|
|
||||||
QAndroidJniEnvironment env;
|
|
||||||
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
|
|
||||||
env->RegisterNatives(objectClass, methods,
|
|
||||||
sizeof(methods) / sizeof(methods[0]));
|
|
||||||
env->DeleteLocalRef(objectClass);
|
|
||||||
|
|
||||||
auto appContext = QtAndroid::androidActivity().callObjectMethod(
|
|
||||||
"getApplicationContext", "()Landroid/content/Context;");
|
|
||||||
|
|
||||||
QAndroidJniObject::callStaticMethod<void>(
|
|
||||||
"org/amnezia/vpn/VPNService", "startService",
|
|
||||||
"(Landroid/content/Context;)V", appContext.object());
|
|
||||||
|
|
||||||
// Start the VPN Service (if not yet) and Bind to it
|
|
||||||
const bool bindResult = QtAndroid::bindService(
|
|
||||||
QAndroidIntent(appContext.object(), "org.amnezia.vpn.VPNService"),
|
|
||||||
*this, QtAndroid::BindFlag::AutoCreate);
|
|
||||||
qDebug() << "Binding to the service..." << bindResult;
|
|
||||||
|
|
||||||
return bindResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode AndroidVpnProtocol::start()
|
ErrorCode AndroidVpnProtocol::start()
|
||||||
{
|
{
|
||||||
|
AndroidController::instance()->setVpnConfig(m_rawConfig);
|
||||||
//qDebug().noquote() << "AndroidVpnProtocol::start" << QJsonDocument(m_rawConfig).toJson();
|
return AndroidController::instance()->start();
|
||||||
qDebug() << "Prompting for VPN permission";
|
|
||||||
auto appContext = QtAndroid::androidActivity().callObjectMethod(
|
|
||||||
"getApplicationContext", "()Landroid/content/Context;");
|
|
||||||
QAndroidJniObject::callStaticMethod<void>(
|
|
||||||
PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V",
|
|
||||||
appContext.object());
|
|
||||||
|
|
||||||
|
|
||||||
// QJsonObject jServer;
|
|
||||||
// jServer["ipv4AddrIn"] = server.ipv4AddrIn();
|
|
||||||
// jServer["ipv4Gateway"] = server.ipv4Gateway();
|
|
||||||
// jServer["ipv6AddrIn"] = server.ipv6AddrIn();
|
|
||||||
// jServer["ipv6Gateway"] = server.ipv6Gateway();
|
|
||||||
// jServer["publicKey"] = server.publicKey();
|
|
||||||
// jServer["port"] = (int)server.choosePort();
|
|
||||||
|
|
||||||
// QJsonArray allowedIPs;
|
|
||||||
// foreach (auto item, allowedIPAddressRanges) {
|
|
||||||
// QJsonValue val;
|
|
||||||
// val = item.toString();
|
|
||||||
// allowedIPs.append(val);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// QJsonArray excludedApps;
|
|
||||||
// foreach (auto appID, vpnDisabledApps) {
|
|
||||||
// excludedApps.append(QJsonValue(appID));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// QJsonObject args;
|
|
||||||
// args["device"] = jDevice;
|
|
||||||
// args["keys"] = jKeys;
|
|
||||||
// args["server"] = jServer;
|
|
||||||
// args["reason"] = (int)reason;
|
|
||||||
// args["allowedIPs"] = allowedIPs;
|
|
||||||
// args["excludedApps"] = excludedApps;
|
|
||||||
// args["dns"] = dns.toString();
|
|
||||||
|
|
||||||
QAndroidParcel sendData;
|
|
||||||
sendData.writeData(QJsonDocument(m_rawConfig).toJson());
|
|
||||||
bool activateResult = false;
|
|
||||||
while (!activateResult){
|
|
||||||
activateResult = m_serviceBinder.transact(ACTION_ACTIVATE, sendData, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return activateResult ? NoError : UnknownError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activates the tunnel that is currently set
|
void AndroidVpnProtocol::stop()
|
||||||
// in the VPN Service
|
|
||||||
void AndroidVpnProtocol::resume_start() {
|
|
||||||
QAndroidParcel nullData;
|
|
||||||
m_serviceBinder.transact(ACTION_RESUME_ACTIVATE, nullData, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidVpnProtocol::stop() {
|
|
||||||
qDebug() << "deactivation";
|
|
||||||
|
|
||||||
// if (reason != ReasonNone) {
|
|
||||||
// // Just show that we're disconnected
|
|
||||||
// // we're doing the actual disconnect once
|
|
||||||
// // the vpn-service has the new server ready in Action->Activate
|
|
||||||
// emit disconnected();
|
|
||||||
// logger.warning() << "deactivation skipped for Switching";
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
QAndroidParcel nullData;
|
|
||||||
m_serviceBinder.transact(ACTION_DEACTIVATE, nullData, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sets the current notification text that is shown
|
|
||||||
*/
|
|
||||||
void AndroidVpnProtocol::setNotificationText(const QString& title,
|
|
||||||
const QString& message,
|
|
||||||
int timerSec) {
|
|
||||||
QJsonObject args;
|
|
||||||
args["title"] = title;
|
|
||||||
args["message"] = message;
|
|
||||||
args["sec"] = timerSec;
|
|
||||||
QJsonDocument doc(args);
|
|
||||||
QAndroidParcel data;
|
|
||||||
data.writeData(doc.toJson());
|
|
||||||
m_serviceBinder.transact(ACTION_SET_NOTIFICATION_TEXT, data, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sets fallback Notification text that should be shown in case the VPN
|
|
||||||
* switches into the Connected state without the app open
|
|
||||||
* e.g via always-on vpn
|
|
||||||
*/
|
|
||||||
void AndroidVpnProtocol::setFallbackConnectedNotification() {
|
|
||||||
QJsonObject args;
|
|
||||||
args["title"] = qtTrId("vpn.main.productName");
|
|
||||||
//% "Ready for you to connect"
|
|
||||||
//: Refers to the app - which is currently running the background and waiting
|
|
||||||
args["message"] = qtTrId("vpn.android.notification.isIDLE");
|
|
||||||
QJsonDocument doc(args);
|
|
||||||
QAndroidParcel data;
|
|
||||||
data.writeData(doc.toJson());
|
|
||||||
m_serviceBinder.transact(ACTION_SET_NOTIFICATION_FALLBACK, data, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidVpnProtocol::checkStatus() {
|
|
||||||
qDebug() << "check status";
|
|
||||||
|
|
||||||
QAndroidParcel nullParcel;
|
|
||||||
m_serviceBinder.transact(ACTION_REQUEST_STATISTIC, nullParcel, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidVpnProtocol::getBackendLogs(std::function<void(const QString&)>&& a_callback) {
|
|
||||||
qDebug() << "get logs";
|
|
||||||
|
|
||||||
m_logCallback = std::move(a_callback);
|
|
||||||
QAndroidParcel nullData, replyData;
|
|
||||||
m_serviceBinder.transact(ACTION_REQUEST_GET_LOG, nullData, &replyData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidVpnProtocol::cleanupBackendLogs() {
|
|
||||||
qDebug() << "cleanup logs";
|
|
||||||
|
|
||||||
QAndroidParcel nullParcel;
|
|
||||||
m_serviceBinder.transact(ACTION_REQUEST_CLEANUP_LOG, nullParcel, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidVpnProtocol::onServiceConnected(
|
|
||||||
const QString& name, const QAndroidBinder& serviceBinder) {
|
|
||||||
qDebug() << "Server " + name + " connected";
|
|
||||||
|
|
||||||
Q_UNUSED(name);
|
|
||||||
|
|
||||||
m_serviceBinder = serviceBinder;
|
|
||||||
|
|
||||||
// Send the Service our Binder to recive incoming Events
|
|
||||||
QAndroidParcel binderParcel;
|
|
||||||
binderParcel.writeBinder(m_binder);
|
|
||||||
m_serviceBinder.transact(ACTION_REGISTERLISTENER, binderParcel, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidVpnProtocol::onServiceDisconnected(const QString& name) {
|
|
||||||
qDebug() << "Server disconnected";
|
|
||||||
m_serviceConnected = false;
|
|
||||||
Q_UNUSED(name);
|
|
||||||
// TODO: Maybe restart? Or crash?
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief AndroidController::VPNBinder::onTransact
|
|
||||||
* @param code the Event-Type we get From the VPNService See
|
|
||||||
* @param data - Might contain UTF-8 JSON in case the Event has a payload
|
|
||||||
* @param reply - always null
|
|
||||||
* @param flags - unused
|
|
||||||
* @return Returns true is the code was a valid Event Code
|
|
||||||
*/
|
|
||||||
bool AndroidVpnProtocol::VPNBinder::onTransact(int code,
|
|
||||||
const QAndroidParcel& data,
|
|
||||||
const QAndroidParcel& reply,
|
|
||||||
QAndroidBinder::CallType flags) {
|
|
||||||
Q_UNUSED(data);
|
|
||||||
Q_UNUSED(reply);
|
|
||||||
Q_UNUSED(flags);
|
|
||||||
|
|
||||||
QJsonDocument doc;
|
|
||||||
QString buffer;
|
|
||||||
switch (code) {
|
|
||||||
case EVENT_INIT:
|
|
||||||
qDebug() << "Transact: init";
|
|
||||||
doc = QJsonDocument::fromJson(data.readData());
|
|
||||||
emit m_controller->initialized(
|
|
||||||
true, doc.object()["connected"].toBool(),
|
|
||||||
QDateTime::fromMSecsSinceEpoch(
|
|
||||||
doc.object()["time"].toVariant().toLongLong()));
|
|
||||||
// Pass a localised version of the Fallback string for the Notification
|
|
||||||
m_controller->setFallbackConnectedNotification();
|
|
||||||
|
|
||||||
break;
|
|
||||||
case EVENT_CONNECTED:
|
|
||||||
qDebug() << "Transact: connected";
|
|
||||||
m_controller->setConnectionState(Connected);
|
|
||||||
break;
|
|
||||||
case EVENT_DISCONNECTED:
|
|
||||||
qDebug() << "Transact: disconnected";
|
|
||||||
m_controller->setConnectionState(Disconnected);
|
|
||||||
break;
|
|
||||||
case EVENT_STATISTIC_UPDATE:
|
|
||||||
qDebug() << "Transact:: update";
|
|
||||||
|
|
||||||
// Data is here a JSON String
|
|
||||||
doc = QJsonDocument::fromJson(data.readData());
|
|
||||||
// TODO update counters
|
|
||||||
// emit m_controller->statusUpdated(doc.object()["endpoint"].toString(),
|
|
||||||
// doc.object()["deviceIpv4"].toString(),
|
|
||||||
// doc.object()["totalTX"].toInt(),
|
|
||||||
// doc.object()["totalRX"].toInt());
|
|
||||||
break;
|
|
||||||
case EVENT_BACKEND_LOGS:
|
|
||||||
qDebug() << "Transact: backend logs";
|
|
||||||
|
|
||||||
buffer = readUTF8Parcel(data);
|
|
||||||
if (m_controller->m_logCallback) {
|
|
||||||
m_controller->m_logCallback(buffer);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case EVENT_ACTIVATION_ERROR:
|
|
||||||
m_controller->setConnectionState(Error);
|
|
||||||
default:
|
|
||||||
qWarning() << "Transact: Invalid!";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AndroidVpnProtocol::VPNBinder::readUTF8Parcel(QAndroidParcel data) {
|
|
||||||
// 106 is the Code for UTF-8
|
|
||||||
return QTextCodec::codecForMib(106)->toUnicode(data.readData());
|
|
||||||
}
|
|
||||||
|
|
||||||
const int ACTIVITY_RESULT_OK = 0xffffffff;
|
|
||||||
/**
|
|
||||||
* @brief Starts the Given intent in Context of the QTActivity
|
|
||||||
* @param env
|
|
||||||
* @param intent
|
|
||||||
*/
|
|
||||||
void AndroidVpnProtocol::startActivityForResult(JNIEnv *env, jobject, jobject intent)
|
|
||||||
{
|
{
|
||||||
qDebug() << "start activity";
|
qDebug() << "AndroidVpnProtocol::stop()";
|
||||||
Q_UNUSED(env);
|
AndroidController::instance()->stop();
|
||||||
QtAndroid::startActivity(intent, 1337,
|
|
||||||
[](int receiverRequestCode, int resultCode,
|
|
||||||
const QAndroidJniObject& data) {
|
|
||||||
// Currently this function just used in
|
|
||||||
// VPNService.kt::checkPersmissions. So the result
|
|
||||||
// we're getting is if the User gave us the
|
|
||||||
// Vpn.bind permission. In case of NO we should
|
|
||||||
// abort.
|
|
||||||
Q_UNUSED(receiverRequestCode);
|
|
||||||
Q_UNUSED(data);
|
|
||||||
|
|
||||||
AndroidVpnProtocol* controller =
|
|
||||||
AndroidVpnProtocol::instance();
|
|
||||||
if (!controller) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resultCode == ACTIVITY_RESULT_OK) {
|
|
||||||
qDebug() << "VPN PROMPT RESULT - Accepted";
|
|
||||||
controller->resume_start();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If the request got rejected abort the current
|
|
||||||
// connection.
|
|
||||||
qWarning() << "VPN PROMPT RESULT - Rejected";
|
|
||||||
controller->setConnectionState(Disconnected);
|
|
||||||
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,38 +11,16 @@ using namespace amnezia;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AndroidVpnProtocol : public VpnProtocol,
|
class AndroidVpnProtocol : public VpnProtocol
|
||||||
public QAndroidServiceConnection
|
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AndroidVpnProtocol(Proto protocol, const QJsonObject& configuration, QObject* parent = nullptr);
|
explicit AndroidVpnProtocol(Proto protocol, const QJsonObject& configuration, QObject* parent = nullptr);
|
||||||
static AndroidVpnProtocol* instance();
|
|
||||||
|
|
||||||
virtual ~AndroidVpnProtocol() override = default;
|
virtual ~AndroidVpnProtocol() override = default;
|
||||||
|
|
||||||
bool initialize();
|
ErrorCode start() override;
|
||||||
|
void stop() override;
|
||||||
virtual ErrorCode start() override;
|
|
||||||
virtual void stop() override;
|
|
||||||
|
|
||||||
void resume_start();
|
|
||||||
|
|
||||||
void checkStatus();
|
|
||||||
|
|
||||||
void setNotificationText(const QString& title, const QString& message,
|
|
||||||
int timerSec);
|
|
||||||
void setFallbackConnectedNotification();
|
|
||||||
|
|
||||||
void getBackendLogs(std::function<void(const QString&)>&& callback);
|
|
||||||
|
|
||||||
void cleanupBackendLogs();
|
|
||||||
|
|
||||||
// from QAndroidServiceConnection
|
|
||||||
void onServiceConnected(const QString& name,
|
|
||||||
const QAndroidBinder& serviceBinder) override;
|
|
||||||
void onServiceDisconnected(const QString& name) override;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
|
@ -55,28 +33,6 @@ protected:
|
||||||
private:
|
private:
|
||||||
Proto m_protocol;
|
Proto m_protocol;
|
||||||
|
|
||||||
bool m_serviceConnected = false;
|
|
||||||
std::function<void(const QString&)> m_logCallback;
|
|
||||||
|
|
||||||
QAndroidBinder m_serviceBinder;
|
|
||||||
class VPNBinder : public QAndroidBinder {
|
|
||||||
public:
|
|
||||||
VPNBinder(AndroidVpnProtocol* controller) : m_controller(controller) {}
|
|
||||||
|
|
||||||
bool onTransact(int code, const QAndroidParcel& data,
|
|
||||||
const QAndroidParcel& reply,
|
|
||||||
QAndroidBinder::CallType flags) override;
|
|
||||||
|
|
||||||
QString readUTF8Parcel(QAndroidParcel data);
|
|
||||||
|
|
||||||
private:
|
|
||||||
AndroidVpnProtocol* m_controller = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
VPNBinder m_binder;
|
|
||||||
|
|
||||||
static void startActivityForResult(JNIEnv* env, jobject /*thiz*/,
|
|
||||||
jobject intent);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ANDROID_VPNPROTOCOL_H
|
#endif // ANDROID_VPNPROTOCOL_H
|
||||||
|
|
|
@ -241,7 +241,7 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE
|
||||||
|
|
||||||
void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration)
|
void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration)
|
||||||
{
|
{
|
||||||
m_config = configuration.value(ProtocolProps::key_proto_config_data(Protocol::Ikev2)).toObject();
|
m_config = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode Ikev2Protocol::start()
|
ErrorCode Ikev2Protocol::start()
|
||||||
|
|
|
@ -78,7 +78,7 @@ ErrorCode OpenVpnOverCloakProtocol::start()
|
||||||
m_ckProcess.waitForStarted();
|
m_ckProcess.waitForStarted();
|
||||||
|
|
||||||
if (m_ckProcess.state() == QProcess::ProcessState::Running) {
|
if (m_ckProcess.state() == QProcess::ProcessState::Running) {
|
||||||
setConnectionState(ConnectionState::Connecting);
|
setConnectionState(VpnConnectionState::Connecting);
|
||||||
|
|
||||||
return OpenVpnProtocol::start();
|
return OpenVpnProtocol::start();
|
||||||
}
|
}
|
||||||
|
@ -113,5 +113,5 @@ QString OpenVpnOverCloakProtocol::cloakExecPath()
|
||||||
|
|
||||||
void OpenVpnOverCloakProtocol::readCloakConfiguration(const QJsonObject &configuration)
|
void OpenVpnOverCloakProtocol::readCloakConfiguration(const QJsonObject &configuration)
|
||||||
{
|
{
|
||||||
m_cloakConfig = configuration.value(ProtocolProps::key_proto_config_data(Protocol::Cloak)).toObject();
|
m_cloakConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::Cloak)).toObject();
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,8 +86,8 @@ void OpenVpnProtocol::killOpenVpnProcess()
|
||||||
|
|
||||||
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
|
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
|
||||||
{
|
{
|
||||||
if (configuration.contains(ProtocolProps::key_proto_config_data(Protocol::OpenVpn))) {
|
if (configuration.contains(ProtocolProps::key_proto_config_data(Proto::OpenVpn))) {
|
||||||
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Protocol::OpenVpn)).toObject();
|
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::OpenVpn)).toObject();
|
||||||
|
|
||||||
m_configFile.open();
|
m_configFile.open();
|
||||||
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
|
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
|
||||||
|
@ -167,7 +167,7 @@ ErrorCode OpenVpnProtocol::start()
|
||||||
return lastError();
|
return lastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
setConnectionState(ConnectionState::Connecting);
|
setConnectionState(VpnConnectionState::Connecting);
|
||||||
|
|
||||||
m_openVpnProcess = IpcClient::CreatePrivilegedProcess();
|
m_openVpnProcess = IpcClient::CreatePrivilegedProcess();
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ ErrorCode OpenVpnProtocol::start()
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, [&]() {
|
connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, [&]() {
|
||||||
setConnectionState(ConnectionState::Disconnected);
|
setConnectionState(VpnConnectionState::Disconnected);
|
||||||
});
|
});
|
||||||
|
|
||||||
m_openVpnProcess->start();
|
m_openVpnProcess->start();
|
||||||
|
|
|
@ -75,7 +75,7 @@ ErrorCode ShadowSocksVpnProtocol::start()
|
||||||
m_ssProcess.waitForStarted();
|
m_ssProcess.waitForStarted();
|
||||||
|
|
||||||
if (m_ssProcess.state() == QProcess::ProcessState::Running) {
|
if (m_ssProcess.state() == QProcess::ProcessState::Running) {
|
||||||
setConnectionState(ConnectionState::Connecting);
|
setConnectionState(VpnConnectionState::Connecting);
|
||||||
|
|
||||||
return OpenVpnProtocol::start();
|
return OpenVpnProtocol::start();
|
||||||
}
|
}
|
||||||
|
@ -112,5 +112,5 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath()
|
||||||
|
|
||||||
void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration)
|
void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration)
|
||||||
{
|
{
|
||||||
m_shadowSocksConfig = configuration.value(ProtocolProps::key_proto_config_data(Protocol::ShadowSocks)).toObject();
|
m_shadowSocksConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::ShadowSocks)).toObject();
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,24 +48,18 @@ signals:
|
||||||
void timeoutTimerEvent();
|
void timeoutTimerEvent();
|
||||||
void protocolError(amnezia::ErrorCode e);
|
void protocolError(amnezia::ErrorCode e);
|
||||||
|
|
||||||
// This signal is emitted when the controller is initialized. Note that the
|
public slots:
|
||||||
// VPN tunnel can be already active. In this case, "connected" should be set
|
virtual void onTimeout(); // todo: remove?
|
||||||
// to true and the "connectionDate" should be set to the activation date if
|
|
||||||
// known.
|
void setBytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||||
// If "status" is set to false, the backend service is considered unavailable.
|
void setConnectionState(VpnProtocol::VpnConnectionState state);
|
||||||
void initialized(bool status, bool connected,
|
|
||||||
const QDateTime& connectionDate);
|
|
||||||
protected slots:
|
|
||||||
virtual void onTimeout();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void startTimeoutTimer();
|
void startTimeoutTimer();
|
||||||
void stopTimeoutTimer();
|
void stopTimeoutTimer();
|
||||||
|
|
||||||
virtual void setBytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
|
||||||
virtual void setConnectionState(VpnProtocol::VpnConnectionState state);
|
|
||||||
|
|
||||||
VpnConnectionState m_connectionState;
|
VpnConnectionState m_connectionState;
|
||||||
|
|
||||||
QString m_routeGateway;
|
QString m_routeGateway;
|
||||||
QString m_vpnLocalAddress;
|
QString m_vpnLocalAddress;
|
||||||
QString m_vpnGateway;
|
QString m_vpnGateway;
|
||||||
|
|
|
@ -61,7 +61,7 @@ void WireguardProtocol::stop()
|
||||||
|
|
||||||
connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
|
connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
|
||||||
qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error;
|
qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error;
|
||||||
setConnectionState(ConnectionState::Disconnected);
|
setConnectionState(VpnConnectionState::Disconnected);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) {
|
connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) {
|
||||||
|
@ -79,7 +79,7 @@ void WireguardProtocol::stop()
|
||||||
|
|
||||||
void WireguardProtocol::readWireguardConfiguration(const QJsonObject &configuration)
|
void WireguardProtocol::readWireguardConfiguration(const QJsonObject &configuration)
|
||||||
{
|
{
|
||||||
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Protocol::WireGuard)).toObject();
|
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toObject();
|
||||||
|
|
||||||
if (!m_configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
if (!m_configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||||
qCritical() << "Failed to save wireguard config to" << m_configFile.fileName();
|
qCritical() << "Failed to save wireguard config to" << m_configFile.fileName();
|
||||||
|
@ -93,7 +93,7 @@ void WireguardProtocol::readWireguardConfiguration(const QJsonObject &configurat
|
||||||
m_configFileName = m_configFile.fileName();
|
m_configFileName = m_configFile.fileName();
|
||||||
|
|
||||||
qDebug().noquote() << QString("Set config data") << m_configFileName;
|
qDebug().noquote() << QString("Set config data") << m_configFileName;
|
||||||
qDebug().noquote() << QString("Set config data") << configuration.value(ProtocolProps::key_proto_config_data(Protocol::WireGuard)).toString().toUtf8();
|
qDebug().noquote() << QString("Set config data") << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ ErrorCode WireguardProtocol::start()
|
||||||
return lastError();
|
return lastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
setConnectionState(ConnectionState::Connecting);
|
setConnectionState(VpnConnectionState::Connecting);
|
||||||
|
|
||||||
m_wireguardStartProcess = IpcClient::CreatePrivilegedProcess();
|
m_wireguardStartProcess = IpcClient::CreatePrivilegedProcess();
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ ErrorCode WireguardProtocol::start()
|
||||||
|
|
||||||
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
|
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
|
||||||
qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error;
|
qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error;
|
||||||
setConnectionState(ConnectionState::Disconnected);
|
setConnectionState(VpnConnectionState::Disconnected);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) {
|
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) {
|
||||||
|
@ -186,7 +186,7 @@ ErrorCode WireguardProtocol::start()
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, [this]() {
|
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, [this]() {
|
||||||
setConnectionState(ConnectionState::Connected);
|
setConnectionState(VpnConnectionState::Connected);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() {
|
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() {
|
||||||
|
|
124
client/ui/notificationhandler.cpp
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include "notificationhandler.h"
|
||||||
|
|
||||||
|
#if defined(Q_OS_IOS)
|
||||||
|
# include "platforms/ios/iosnotificationhandler.h"
|
||||||
|
#elif defined(Q_OS_ANDROID)
|
||||||
|
# include "platforms/android/android_notificationhandler.h"
|
||||||
|
#else
|
||||||
|
|
||||||
|
# if defined(Q_OS_LINUX)
|
||||||
|
# include "platforms/linux/linuxsystemtraynotificationhandler.h"
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# include "systemtray_notificationhandler.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// static
|
||||||
|
NotificationHandler* NotificationHandler::create(QObject* parent) {
|
||||||
|
#if defined(Q_OS_IOS)
|
||||||
|
return new IOSNotificationHandler(parent);
|
||||||
|
#elif defined(Q_OS_ANDROID)
|
||||||
|
return new AndroidNotificationHandler(parent);
|
||||||
|
#else
|
||||||
|
|
||||||
|
# if defined(Q_OS_LINUX)
|
||||||
|
if (LinuxSystemTrayNotificationHandler::requiredCustomImpl()) {
|
||||||
|
return new LinuxSystemTrayNotificationHandler(parent);
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
return new SystemTrayNotificationHandler(parent);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
NotificationHandler* s_instance = nullptr;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// static
|
||||||
|
NotificationHandler* NotificationHandler::instance() {
|
||||||
|
Q_ASSERT(s_instance);
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationHandler::NotificationHandler(QObject* parent) : QObject(parent) {
|
||||||
|
Q_ASSERT(!s_instance);
|
||||||
|
s_instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationHandler::~NotificationHandler() {
|
||||||
|
Q_ASSERT(s_instance == this);
|
||||||
|
s_instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState state)
|
||||||
|
{
|
||||||
|
if (state != VpnProtocol::Connected && state != VpnProtocol::Disconnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString title;
|
||||||
|
QString message;
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case VpnProtocol::VpnConnectionState::Connected:
|
||||||
|
m_connected = true;
|
||||||
|
|
||||||
|
title = tr("AmneziaVPN");
|
||||||
|
message = tr("VPN Connected");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VpnProtocol::VpnConnectionState::Disconnected:
|
||||||
|
if (m_connected) {
|
||||||
|
m_connected = false;
|
||||||
|
title = tr("AmneziaVPN");
|
||||||
|
message = tr("VPN Disconnected");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_ASSERT(title.isEmpty() == message.isEmpty());
|
||||||
|
|
||||||
|
if (!title.isEmpty()) {
|
||||||
|
notifyInternal(VpnState, title, message, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationHandler::unsecuredNetworkNotification(const QString& networkName) {
|
||||||
|
qDebug() << "Unsecured network notification shown";
|
||||||
|
|
||||||
|
|
||||||
|
QString title = tr("AmneziaVPN notification");
|
||||||
|
QString message = tr("Unsucured network detected: ") + networkName;
|
||||||
|
|
||||||
|
notifyInternal(UnsecuredNetwork, title, message, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationHandler::notifyInternal(Message type, const QString& title,
|
||||||
|
const QString& message,
|
||||||
|
int timerMsec) {
|
||||||
|
m_lastMessage = type;
|
||||||
|
|
||||||
|
emit notificationShown(title, message);
|
||||||
|
notify(type, title, message, timerMsec);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationHandler::messageClickHandle() {
|
||||||
|
qDebug() << "Message clicked";
|
||||||
|
|
||||||
|
if (m_lastMessage == VpnState) {
|
||||||
|
qCritical() << "Random message clicked received";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit notificationClicked(m_lastMessage);
|
||||||
|
m_lastMessage = VpnState;
|
||||||
|
}
|
64
client/ui/notificationhandler.h
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef NOTIFICATIONHANDLER_H
|
||||||
|
#define NOTIFICATIONHANDLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include "protocols/vpnprotocol.h"
|
||||||
|
|
||||||
|
class QMenu;
|
||||||
|
|
||||||
|
class NotificationHandler : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(NotificationHandler)
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Message {
|
||||||
|
VpnState,
|
||||||
|
UnsecuredNetwork
|
||||||
|
};
|
||||||
|
|
||||||
|
static NotificationHandler* create(QObject* parent);
|
||||||
|
|
||||||
|
static NotificationHandler* instance();
|
||||||
|
|
||||||
|
virtual ~NotificationHandler();
|
||||||
|
|
||||||
|
void unsecuredNetworkNotification(const QString& networkName);
|
||||||
|
|
||||||
|
void messageClickHandle();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
virtual void setConnectionState(VpnProtocol::VpnConnectionState state);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void notificationShown(const QString& title, const QString& message);
|
||||||
|
void notificationClicked(Message message);
|
||||||
|
|
||||||
|
void raiseRequested();
|
||||||
|
void connectRequested();
|
||||||
|
void disconnectRequested();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
explicit NotificationHandler(QObject* parent);
|
||||||
|
|
||||||
|
virtual void notify(Message type, const QString& title,
|
||||||
|
const QString& message, int timerMsec) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void notifyInternal(Message type, const QString& title,
|
||||||
|
const QString& message, int timerMsec);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Message m_lastMessage = VpnState;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// We want to show a 'disconnected' notification only if we were actually
|
||||||
|
// connected.
|
||||||
|
bool m_connected = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NOTIFICATIONHANDLER_H
|
|
@ -88,8 +88,6 @@ void VpnLogic::onConnectionStateChanged(VpnProtocol::VpnConnectionState state)
|
||||||
bool pbConnectVisible = false;
|
bool pbConnectVisible = false;
|
||||||
set_labelStateText(VpnProtocol::textConnectionState(state));
|
set_labelStateText(VpnProtocol::textConnectionState(state));
|
||||||
|
|
||||||
uiLogic()->setTrayState(state);
|
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case VpnProtocol::Disconnected:
|
case VpnProtocol::Disconnected:
|
||||||
onBytesChanged(0,0);
|
onBytesChanged(0,0);
|
||||||
|
|
181
client/ui/systemtray_notificationhandler.cpp
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include "systemtray_notificationhandler.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
# include "platforms/macos/macosutils.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QWindow>
|
||||||
|
|
||||||
|
#include "defines.h"
|
||||||
|
|
||||||
|
|
||||||
|
SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) :
|
||||||
|
NotificationHandler(parent),
|
||||||
|
m_systemTrayIcon(parent)
|
||||||
|
|
||||||
|
{
|
||||||
|
m_systemTrayIcon.show();
|
||||||
|
connect(&m_systemTrayIcon, &QSystemTrayIcon::activated, this, &SystemTrayNotificationHandler::onTrayActivated);
|
||||||
|
|
||||||
|
|
||||||
|
m_menu.addAction(QIcon(":/images/tray/application.png"), tr("Show") + " " + APPLICATION_NAME, this, [this](){
|
||||||
|
emit raiseRequested();
|
||||||
|
});
|
||||||
|
m_menu.addSeparator();
|
||||||
|
m_trayActionConnect = m_menu.addAction(tr("Connect"), this, [this](){ emit connectRequested(); });
|
||||||
|
m_trayActionDisconnect = m_menu.addAction(tr("Disconnect"), this, [this](){ emit disconnectRequested(); });
|
||||||
|
|
||||||
|
m_menu.addSeparator();
|
||||||
|
|
||||||
|
m_menu.addAction(QIcon(":/images/tray/link.png"), tr("Visit Website"), [&](){
|
||||||
|
QDesktopServices::openUrl(QUrl("https://amnezia.org"));
|
||||||
|
});
|
||||||
|
|
||||||
|
m_menu.addAction(QIcon(":/images/tray/cancel.png"), tr("Quit") + " " + APPLICATION_NAME, this, [&](){
|
||||||
|
qApp->quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_systemTrayIcon.setContextMenu(&m_menu);
|
||||||
|
setTrayState(VpnProtocol::Disconnected);
|
||||||
|
|
||||||
|
|
||||||
|
// m_preferencesAction = m_menu.addAction("", vpn, &MozillaVPN::requestSettings);
|
||||||
|
|
||||||
|
// m_menu.addSeparator();
|
||||||
|
|
||||||
|
// m_quitAction = m_menu.addAction("", vpn->controller(), &Controller::quit);
|
||||||
|
// m_systemTrayIcon.setContextMenu(&m_menu);
|
||||||
|
|
||||||
|
// updateIcon(vpn->statusIcon()->iconString());
|
||||||
|
|
||||||
|
// connect(QmlEngineHolder::instance()->window(), &QWindow::visibleChanged, this,
|
||||||
|
// &SystemTrayNotificationHandler::updateContextMenu);
|
||||||
|
|
||||||
|
// connect(&m_systemTrayIcon, &QSystemTrayIcon::activated, this,
|
||||||
|
// &SystemTrayNotificationHandler::maybeActivated);
|
||||||
|
|
||||||
|
// connect(&m_systemTrayIcon, &QSystemTrayIcon::messageClicked, this,
|
||||||
|
// &SystemTrayNotificationHandler::messageClickHandle);
|
||||||
|
|
||||||
|
// retranslate();
|
||||||
|
|
||||||
|
// m_systemTrayIcon.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemTrayNotificationHandler::~SystemTrayNotificationHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemTrayNotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState state)
|
||||||
|
{
|
||||||
|
setTrayState(state);
|
||||||
|
NotificationHandler::setConnectionState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemTrayNotificationHandler::setTrayIcon(const QString &iconPath)
|
||||||
|
{
|
||||||
|
QIcon trayIconMask(QPixmap(iconPath).scaled(128,128));
|
||||||
|
trayIconMask.setIsMask(true);
|
||||||
|
m_systemTrayIcon.setIcon(trayIconMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemTrayNotificationHandler::onTrayActivated(QSystemTrayIcon::ActivationReason reason)
|
||||||
|
{
|
||||||
|
#ifndef Q_OS_MAC
|
||||||
|
if(reason == QSystemTrayIcon::DoubleClick || reason == QSystemTrayIcon::Trigger) {
|
||||||
|
emit raiseRequested();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemTrayNotificationHandler::setTrayState(VpnProtocol::VpnConnectionState state)
|
||||||
|
{
|
||||||
|
qDebug() << "SystemTrayNotificationHandler::setTrayState" << state;
|
||||||
|
QString resourcesPath = ":/images/tray/%1";
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case VpnProtocol::Disconnected:
|
||||||
|
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||||
|
m_trayActionConnect->setEnabled(true);
|
||||||
|
m_trayActionDisconnect->setEnabled(false);
|
||||||
|
break;
|
||||||
|
case VpnProtocol::Preparing:
|
||||||
|
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||||
|
m_trayActionConnect->setEnabled(false);
|
||||||
|
m_trayActionDisconnect->setEnabled(true);
|
||||||
|
break;
|
||||||
|
case VpnProtocol::Connecting:
|
||||||
|
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||||
|
m_trayActionConnect->setEnabled(false);
|
||||||
|
m_trayActionDisconnect->setEnabled(true);
|
||||||
|
break;
|
||||||
|
case VpnProtocol::Connected:
|
||||||
|
setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName));
|
||||||
|
m_trayActionConnect->setEnabled(false);
|
||||||
|
m_trayActionDisconnect->setEnabled(true);
|
||||||
|
break;
|
||||||
|
case VpnProtocol::Disconnecting:
|
||||||
|
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||||
|
m_trayActionConnect->setEnabled(false);
|
||||||
|
m_trayActionDisconnect->setEnabled(true);
|
||||||
|
break;
|
||||||
|
case VpnProtocol::Reconnecting:
|
||||||
|
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||||
|
m_trayActionConnect->setEnabled(false);
|
||||||
|
m_trayActionDisconnect->setEnabled(true);
|
||||||
|
break;
|
||||||
|
case VpnProtocol::Error:
|
||||||
|
setTrayIcon(QString(resourcesPath).arg(ErrorTrayIconName));
|
||||||
|
m_trayActionConnect->setEnabled(true);
|
||||||
|
m_trayActionDisconnect->setEnabled(false);
|
||||||
|
break;
|
||||||
|
case VpnProtocol::Unknown:
|
||||||
|
default:
|
||||||
|
m_trayActionConnect->setEnabled(false);
|
||||||
|
m_trayActionDisconnect->setEnabled(true);
|
||||||
|
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||||
|
}
|
||||||
|
|
||||||
|
//#ifdef Q_OS_MAC
|
||||||
|
// // Get theme from current user (note, this app can be launched as root application and in this case this theme can be different from theme of real current user )
|
||||||
|
// bool darkTaskBar = MacOSFunctions::instance().isMenuBarUseDarkTheme();
|
||||||
|
// darkTaskBar = forceUseBrightIcons ? true : darkTaskBar;
|
||||||
|
// resourcesPath = ":/images_mac/tray_icon/%1";
|
||||||
|
// useIconName = useIconName.replace(".png", darkTaskBar ? "@2x.png" : " dark@2x.png");
|
||||||
|
//#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SystemTrayNotificationHandler::notify(NotificationHandler::Message type,
|
||||||
|
const QString& title,
|
||||||
|
const QString& message,
|
||||||
|
int timerMsec) {
|
||||||
|
Q_UNUSED(type);
|
||||||
|
|
||||||
|
QIcon icon(ConnectedTrayIconName);
|
||||||
|
m_systemTrayIcon.showMessage(title, message, icon, timerMsec);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemTrayNotificationHandler::showHideWindow() {
|
||||||
|
// QmlEngineHolder* engine = QmlEngineHolder::instance();
|
||||||
|
// if (engine->window()->isVisible()) {
|
||||||
|
// engine->hideWindow();
|
||||||
|
//#ifdef MVPN_MACOS
|
||||||
|
// MacOSUtils::hideDockIcon();
|
||||||
|
//#endif
|
||||||
|
// } else {
|
||||||
|
// engine->showWindow();
|
||||||
|
//#ifdef MVPN_MACOS
|
||||||
|
// MacOSUtils::showDockIcon();
|
||||||
|
//#endif
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
49
client/ui/systemtray_notificationhandler.h
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef SYSTEMTRAY_NOTIFICATIONHANDLER_H
|
||||||
|
#define SYSTEMTRAY_NOTIFICATIONHANDLER_H
|
||||||
|
|
||||||
|
#include "notificationhandler.h"
|
||||||
|
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QSystemTrayIcon>
|
||||||
|
|
||||||
|
class SystemTrayNotificationHandler : public NotificationHandler {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SystemTrayNotificationHandler(QObject* parent);
|
||||||
|
~SystemTrayNotificationHandler();
|
||||||
|
|
||||||
|
void setConnectionState(VpnProtocol::VpnConnectionState state) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void notify(Message type, const QString& title,
|
||||||
|
const QString& message, int timerMsec) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void showHideWindow();
|
||||||
|
|
||||||
|
void setTrayState(VpnProtocol::VpnConnectionState state);
|
||||||
|
void onTrayActivated(QSystemTrayIcon::ActivationReason reason);
|
||||||
|
|
||||||
|
void setTrayIcon(const QString &iconPath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QMenu m_menu;
|
||||||
|
QSystemTrayIcon m_systemTrayIcon;
|
||||||
|
|
||||||
|
QAction* m_trayActionConnect = nullptr;
|
||||||
|
QAction* m_trayActionDisconnect = nullptr;
|
||||||
|
QAction* m_preferencesAction = nullptr;
|
||||||
|
QAction* m_statusLabel = nullptr;
|
||||||
|
QAction* m_separator = nullptr;
|
||||||
|
|
||||||
|
const QString ConnectedTrayIconName = "active.png";
|
||||||
|
const QString DisconnectedTrayIconName = "default.png";
|
||||||
|
const QString ErrorTrayIconName = "error.png";
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SYSTEMTRAY_NOTIFICATIONHANDLER_H
|
|
@ -44,6 +44,10 @@
|
||||||
#include "ui/macos_util.h"
|
#include "ui/macos_util.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
#include "platforms/android/android_controller.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "pages_logic/AppSettingsLogic.h"
|
#include "pages_logic/AppSettingsLogic.h"
|
||||||
#include "pages_logic/GeneralSettingsLogic.h"
|
#include "pages_logic/GeneralSettingsLogic.h"
|
||||||
#include "pages_logic/NetworkSettingsLogic.h"
|
#include "pages_logic/NetworkSettingsLogic.h"
|
||||||
|
@ -94,7 +98,7 @@ UiLogic::UiLogic(QObject *parent) :
|
||||||
m_protocolLogicMap.insert(Proto::OpenVpn, new OpenVpnLogic(this));
|
m_protocolLogicMap.insert(Proto::OpenVpn, new OpenVpnLogic(this));
|
||||||
m_protocolLogicMap.insert(Proto::ShadowSocks, new ShadowSocksLogic(this));
|
m_protocolLogicMap.insert(Proto::ShadowSocks, new ShadowSocksLogic(this));
|
||||||
m_protocolLogicMap.insert(Proto::Cloak, new CloakLogic(this));
|
m_protocolLogicMap.insert(Proto::Cloak, new CloakLogic(this));
|
||||||
//m_protocolLogicMap->insert(Protocol::WireGuard, new WireguardLogic(this));
|
//m_protocolLogicMap->insert(Proto::WireGuard, new WireguardLogic(this));
|
||||||
|
|
||||||
m_protocolLogicMap.insert(Proto::Dns, new OtherProtocolsLogic(this));
|
m_protocolLogicMap.insert(Proto::Dns, new OtherProtocolsLogic(this));
|
||||||
m_protocolLogicMap.insert(Proto::Sftp, new OtherProtocolsLogic(this));
|
m_protocolLogicMap.insert(Proto::Sftp, new OtherProtocolsLogic(this));
|
||||||
|
@ -104,8 +108,6 @@ UiLogic::UiLogic(QObject *parent) :
|
||||||
|
|
||||||
UiLogic::~UiLogic()
|
UiLogic::~UiLogic()
|
||||||
{
|
{
|
||||||
m_tray = nullptr;
|
|
||||||
|
|
||||||
emit hide();
|
emit hide();
|
||||||
|
|
||||||
if (m_vpnConnection->connectionState() != VpnProtocol::VpnConnectionState::Disconnected) {
|
if (m_vpnConnection->connectionState() != VpnProtocol::VpnConnectionState::Disconnected) {
|
||||||
|
@ -128,9 +130,22 @@ UiLogic::~UiLogic()
|
||||||
|
|
||||||
void UiLogic::initalizeUiLogic()
|
void UiLogic::initalizeUiLogic()
|
||||||
{
|
{
|
||||||
qDebug() << "UiLogic::initalizeUiLogic()";
|
#ifdef Q_OS_ANDROID
|
||||||
setupTray();
|
if (!AndroidController::instance()->initialize()) {
|
||||||
|
qDebug() << QString("Init failed") ;
|
||||||
|
emit VpnProtocol::Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
qDebug() << "UiLogic::initalizeUiLogic()";
|
||||||
|
|
||||||
|
m_notificationHandler = NotificationHandler::create(qmlRoot());
|
||||||
|
|
||||||
|
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, &NotificationHandler::setConnectionState);
|
||||||
|
connect(m_notificationHandler, &NotificationHandler::raiseRequested, this, &UiLogic::raise);
|
||||||
|
connect(m_notificationHandler, &NotificationHandler::connectRequested, vpnLogic(), &VpnLogic::onConnect);
|
||||||
|
connect(m_notificationHandler, &NotificationHandler::disconnectRequested, vpnLogic(), &VpnLogic::onDisconnect);
|
||||||
|
|
||||||
// if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::Windows7) {
|
// if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::Windows7) {
|
||||||
// needToHideCustomTitlebar = true;
|
// needToHideCustomTitlebar = true;
|
||||||
|
@ -161,7 +176,7 @@ void UiLogic::initalizeUiLogic()
|
||||||
selectedServerIndex = m_settings.defaultServerIndex();
|
selectedServerIndex = m_settings.defaultServerIndex();
|
||||||
//goToPage(Page::ServerContainers, true, false);
|
//goToPage(Page::ServerContainers, true, false);
|
||||||
//goToPage(Page::NewServerProtocols, true, false);
|
//goToPage(Page::NewServerProtocols, true, false);
|
||||||
//onGotoProtocolPage(Protocol::OpenVpn);
|
//onGotoProtocolPage(Proto::OpenVpn);
|
||||||
|
|
||||||
|
|
||||||
//ui->pushButton_general_settings_exit->hide();
|
//ui->pushButton_general_settings_exit->hide();
|
||||||
|
@ -600,27 +615,8 @@ ErrorCode UiLogic::doInstallAction(const std::function<ErrorCode()> &action,
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UiLogic::setupTray()
|
PageProtocolLogicBase *UiLogic::protocolLogic(Proto p)
|
||||||
{
|
{
|
||||||
m_tray = new QSystemTrayIcon(qmlRoot());
|
|
||||||
setTrayState(VpnProtocol::Disconnected);
|
|
||||||
|
|
||||||
m_tray->show();
|
|
||||||
|
|
||||||
connect(m_tray, &QSystemTrayIcon::activated, this, &UiLogic::onTrayActivated);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UiLogic::setTrayIcon(const QString &iconPath)
|
|
||||||
{
|
|
||||||
if (m_tray) m_tray->setIcon(QIcon(QPixmap(iconPath).scaled(128,128)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void UiLogic::onTrayActivated(QSystemTrayIcon::ActivationReason reason)
|
|
||||||
{
|
|
||||||
emit raise();
|
|
||||||
}
|
|
||||||
|
|
||||||
PageProtocolLogicBase *UiLogic::protocolLogic(Proto p) {
|
|
||||||
PageProtocolLogicBase *logic = m_protocolLogicMap.value(p);
|
PageProtocolLogicBase *logic = m_protocolLogicMap.value(p);
|
||||||
if (logic) return logic;
|
if (logic) return logic;
|
||||||
else {
|
else {
|
||||||
|
@ -639,57 +635,23 @@ void UiLogic::setQmlRoot(QObject *newQmlRoot)
|
||||||
m_qmlRoot = newQmlRoot;
|
m_qmlRoot = newQmlRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotificationHandler *UiLogic::notificationHandler() const
|
||||||
|
{
|
||||||
|
return m_notificationHandler;
|
||||||
|
}
|
||||||
|
|
||||||
PageEnumNS::Page UiLogic::currentPage()
|
PageEnumNS::Page UiLogic::currentPage()
|
||||||
{
|
{
|
||||||
return static_cast<PageEnumNS::Page>(currentPageValue());
|
return static_cast<PageEnumNS::Page>(currentPageValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void UiLogic::setTrayState(VpnProtocol::VpnConnectionState state)
|
|
||||||
{
|
|
||||||
QString resourcesPath = ":/images/tray/%1";
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case VpnProtocol::Disconnected:
|
|
||||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
|
||||||
break;
|
|
||||||
case VpnProtocol::Preparing:
|
|
||||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
|
||||||
break;
|
|
||||||
case VpnProtocol::Connecting:
|
|
||||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
|
||||||
break;
|
|
||||||
case VpnProtocol::Connected:
|
|
||||||
setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName));
|
|
||||||
break;
|
|
||||||
case VpnProtocol::Disconnecting:
|
|
||||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
|
||||||
break;
|
|
||||||
case VpnProtocol::Reconnecting:
|
|
||||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
|
||||||
break;
|
|
||||||
case VpnProtocol::Error:
|
|
||||||
setTrayIcon(QString(resourcesPath).arg(ErrorTrayIconName));
|
|
||||||
break;
|
|
||||||
case VpnProtocol::Unknown:
|
|
||||||
default:
|
|
||||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
|
||||||
}
|
|
||||||
|
|
||||||
//#ifdef Q_OS_MAC
|
|
||||||
// // Get theme from current user (note, this app can be launched as root application and in this case this theme can be different from theme of real current user )
|
|
||||||
// bool darkTaskBar = MacOSFunctions::instance().isMenuBarUseDarkTheme();
|
|
||||||
// darkTaskBar = forceUseBrightIcons ? true : darkTaskBar;
|
|
||||||
// resourcesPath = ":/images_mac/tray_icon/%1";
|
|
||||||
// useIconName = useIconName.replace(".png", darkTaskBar ? "@2x.png" : " dark@2x.png");
|
|
||||||
//#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool UiLogic::saveTextFile(const QString& desc, const QString& ext, const QString& data)
|
bool UiLogic::saveTextFile(const QString& desc, const QString& ext, const QString& data)
|
||||||
{
|
{
|
||||||
QString fileName = QFileDialog::getSaveFileName(nullptr, desc,
|
QString fileName = QFileDialog::getSaveFileName(nullptr, desc,
|
||||||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), ext);
|
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), ext);
|
||||||
|
|
||||||
|
if (fileName.isEmpty()) return false;
|
||||||
|
|
||||||
QSaveFile save(fileName);
|
QSaveFile save(fileName);
|
||||||
save.open(QIODevice::WriteOnly);
|
save.open(QIODevice::WriteOnly);
|
||||||
save.write(data.toUtf8());
|
save.write(data.toUtf8());
|
||||||
|
@ -705,6 +667,8 @@ bool UiLogic::saveBinaryFile(const QString &desc, const QString &ext, const QStr
|
||||||
QString fileName = QFileDialog::getSaveFileName(nullptr, desc,
|
QString fileName = QFileDialog::getSaveFileName(nullptr, desc,
|
||||||
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), ext);
|
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), ext);
|
||||||
|
|
||||||
|
if (fileName.isEmpty()) return false;
|
||||||
|
|
||||||
QSaveFile save(fileName);
|
QSaveFile save(fileName);
|
||||||
save.open(QIODevice::WriteOnly);
|
save.open(QIODevice::WriteOnly);
|
||||||
save.write(QByteArray::fromBase64(data.toUtf8()));
|
save.write(QByteArray::fromBase64(data.toUtf8()));
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QSystemTrayIcon>
|
|
||||||
|
|
||||||
#include "property_helper.h"
|
#include "property_helper.h"
|
||||||
#include "pages.h"
|
#include "pages.h"
|
||||||
|
@ -16,6 +15,7 @@
|
||||||
#include "models/containers_model.h"
|
#include "models/containers_model.h"
|
||||||
#include "models/protocols_model.h"
|
#include "models/protocols_model.h"
|
||||||
|
|
||||||
|
#include "notificationhandler.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
class AppSettingsLogic;
|
class AppSettingsLogic;
|
||||||
|
@ -107,12 +107,8 @@ public:
|
||||||
void setDialogConnectErrorText(const QString &dialogConnectErrorText);
|
void setDialogConnectErrorText(const QString &dialogConnectErrorText);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void trayIconUrlChanged();
|
|
||||||
void trayActionDisconnectEnabledChanged();
|
|
||||||
void trayActionConnectEnabledChanged();
|
|
||||||
void dialogConnectErrorTextChanged();
|
void dialogConnectErrorTextChanged();
|
||||||
|
|
||||||
|
|
||||||
void goToPage(PageEnumNS::Page page, bool reset = true, bool slide = true);
|
void goToPage(PageEnumNS::Page page, bool reset = true, bool slide = true);
|
||||||
void goToProtocolPage(Proto protocol, bool reset = true, bool slide = true);
|
void goToProtocolPage(Proto protocol, bool reset = true, bool slide = true);
|
||||||
void goToShareProtocolPage(Proto protocol, bool reset = true, bool slide = true);
|
void goToShareProtocolPage(Proto protocol, bool reset = true, bool slide = true);
|
||||||
|
@ -126,17 +122,12 @@ signals:
|
||||||
void raise();
|
void raise();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSystemTrayIcon *m_tray;
|
|
||||||
|
|
||||||
QString m_dialogConnectErrorText;
|
QString m_dialogConnectErrorText;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
// containers - INOUT arg
|
// containers - INOUT arg
|
||||||
void installServer(QMap<DockerContainer, QJsonObject> &containers);
|
void installServer(QMap<DockerContainer, QJsonObject> &containers);
|
||||||
|
|
||||||
void setTrayState(VpnProtocol::VpnConnectionState state);
|
|
||||||
void onTrayActivated(QSystemTrayIcon::ActivationReason reason);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PageEnumNS::Page currentPage();
|
PageEnumNS::Page currentPage();
|
||||||
struct ProgressFunc {
|
struct ProgressFunc {
|
||||||
|
@ -171,9 +162,6 @@ private:
|
||||||
const ButtonFunc& button,
|
const ButtonFunc& button,
|
||||||
const LabelFunc& info);
|
const LabelFunc& info);
|
||||||
|
|
||||||
void setupTray();
|
|
||||||
void setTrayIcon(const QString &iconPath);
|
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AppSettingsLogic *appSettingsLogic() { return m_appSettingsLogic; }
|
AppSettingsLogic *appSettingsLogic() { return m_appSettingsLogic; }
|
||||||
|
@ -195,6 +183,8 @@ public:
|
||||||
QObject *qmlRoot() const;
|
QObject *qmlRoot() const;
|
||||||
void setQmlRoot(QObject *newQmlRoot);
|
void setQmlRoot(QObject *newQmlRoot);
|
||||||
|
|
||||||
|
NotificationHandler *notificationHandler() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QObject *m_qmlRoot{nullptr};
|
QObject *m_qmlRoot{nullptr};
|
||||||
|
|
||||||
|
@ -218,6 +208,8 @@ private:
|
||||||
QThread m_vpnConnectionThread;
|
QThread m_vpnConnectionThread;
|
||||||
Settings m_settings;
|
Settings m_settings;
|
||||||
|
|
||||||
|
NotificationHandler* m_notificationHandler;
|
||||||
|
|
||||||
|
|
||||||
// QRegExpValidator m_ipAddressValidator;
|
// QRegExpValidator m_ipAddressValidator;
|
||||||
// QRegExpValidator m_ipAddressPortValidator;
|
// QRegExpValidator m_ipAddressPortValidator;
|
||||||
|
@ -230,10 +222,6 @@ private:
|
||||||
// void showEvent(QShowEvent *event) override;
|
// void showEvent(QShowEvent *event) override;
|
||||||
// void hideEvent(QHideEvent *event) override;
|
// void hideEvent(QHideEvent *event) override;
|
||||||
|
|
||||||
const QString ConnectedTrayIconName = "active.png";
|
|
||||||
const QString DisconnectedTrayIconName = "default.png";
|
|
||||||
const QString ErrorTrayIconName = "error.png";
|
|
||||||
|
|
||||||
|
|
||||||
// QStack<Page> pagesStack;
|
// QStack<Page> pagesStack;
|
||||||
int selectedServerIndex = -1; // server index to use when proto settings page opened
|
int selectedServerIndex = -1; // server index to use when proto settings page opened
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
#include <protocols/wireguardprotocol.h>
|
#include <protocols/wireguardprotocol.h>
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include <protocols/android_vpnprotocol.h>
|
#include "android_controller.h"
|
||||||
|
#include "protocols/android_vpnprotocol.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
|
@ -261,11 +262,8 @@ void VpnConnection::connectToVpn(int serverIndex,
|
||||||
#elif defined Q_OS_ANDROID
|
#elif defined Q_OS_ANDROID
|
||||||
Proto proto = ContainerProps::defaultProtocol(container);
|
Proto proto = ContainerProps::defaultProtocol(container);
|
||||||
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration);
|
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration);
|
||||||
if (!androidVpnProtocol->initialize()) {
|
connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState);
|
||||||
qDebug() << QString("Init failed") ;
|
|
||||||
emit VpnProtocol::Error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_vpnProtocol.reset(androidVpnProtocol);
|
m_vpnProtocol.reset(androidVpnProtocol);
|
||||||
#elif defined Q_OS_IOS
|
#elif defined Q_OS_IOS
|
||||||
Proto proto = ContainerProps::defaultProtocol(container);
|
Proto proto = ContainerProps::defaultProtocol(container);
|
||||||
|
@ -279,7 +277,7 @@ void VpnConnection::connectToVpn(int serverIndex,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||||
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::ConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::ConnectionState)));
|
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::VpnConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::VpnConnectionState)));
|
||||||
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
||||||
|
|
||||||
ServerController::disconnectFromHost(credentials);
|
ServerController::disconnectFromHost(credentials);
|
||||||
|
|