diff --git a/client/android/openvpn.tar.gz b/client/android/openvpn.tar.gz deleted file mode 100644 index 92a6a613..00000000 Binary files a/client/android/openvpn.tar.gz and /dev/null differ diff --git a/client/android/res/drawable/ic_amnezia_round.xml b/client/android/res/drawable/ic_amnezia_round.xml index c3158ddd..32df5ca4 100644 --- a/client/android/res/drawable/ic_amnezia_round.xml +++ b/client/android/res/drawable/ic_amnezia_round.xml @@ -1,11 +1,10 @@ - + android:width="256dp" + android:height="256dp" + android:viewportWidth="256" + android:viewportHeight="256"> + android:pathData="M121.6,3.7c-0.3,2.1 -0.8,9.1 -1.2,15.6 -0.3,6.4 -1,12 -1.5,12.3 -0.5,0.3 -0.9,1.3 -0.9,2.3 0,2.6 -4.8,7.1 -7.6,7.1 -1.3,-0 -2.4,0.4 -2.4,0.9 0,0.5 -1,1.1 -2.2,1.3 -1.6,0.2 -2.4,1.1 -2.6,3 -0.3,2.2 -0.9,2.7 -4.3,3.3 -2.1,0.4 -3.9,1 -3.9,1.5 0,0.4 -2.9,1.3 -6.5,2 -3.6,0.7 -6.5,1.6 -6.5,2.1 0,0.4 -2,1.1 -4.5,1.5 -4,0.6 -4.7,1.1 -6.5,4.8 -3.6,7.2 -5.1,9.6 -6,9.6 -0.5,-0 -1.7,1.4 -2.5,3.1 -1.4,2.5 -2.1,3 -4.7,2.7 -2.7,-0.3 -3.2,0.1 -4.6,3.6 -0.9,2.2 -3.1,5.3 -4.9,6.9 -1.8,1.6 -3.3,3.4 -3.3,4 0,0.5 -1.3,3.7 -2.9,7.1 -1.5,3.4 -3.1,7.1 -3.5,8.3 -0.4,1.3 -1.1,2.3 -1.5,2.3 -0.4,-0 -1.5,2.4 -2.6,5.2 -1,2.9 -2.1,5.7 -2.5,6.3 -0.4,0.5 -1.3,3.4 -2.1,6.2 -0.7,2.9 -1.6,5.3 -2,5.3 -1.1,-0 -3.1,15.4 -3.5,27.4l-0.4,11 -4.3,4.5c-2.4,2.6 -6.5,6.3 -9.2,8.4 -11.9,8.9 -13.2,11.3 -5.7,10.2 2.6,-0.4 5.2,-1.1 5.7,-1.5 1.6,-1.4 14.4,-3.2 16.9,-2.4 2.2,0.7 6.6,4.9 6.6,6.4 0,0.5 5.2,6.3 11.6,13l11.7,12.1 -0.6,9.6c-0.4,5.2 -1.1,10.1 -1.7,10.8 -1.2,1.4 -2.7,14.5 -1.6,14.5 0.3,-0 1.9,-1.1 3.5,-2.5 1.8,-1.7 3.4,-4.7 4.6,-8.8 0.9,-3.4 2.1,-7.1 2.6,-8.2 0.5,-1.1 1.3,-3.2 1.9,-4.8 0.5,-1.5 1.6,-2.7 2.5,-2.7 0.8,-0 1.5,-0.5 1.5,-1 0,-1.6 4.3,-1.2 8.6,0.9 12.6,6 15.3,7.1 17.7,7.1 1.5,-0 2.7,0.4 2.7,0.8 0,0.4 6.2,1.6 13.8,2.7 7.5,1 14.1,2.2 14.7,2.7 1.2,1.1 12.1,1 14.3,-0.1 0.9,-0.5 4.4,-1.5 7.7,-2.1 3.3,-0.7 7.8,-2 10,-3 2.3,-1 5.4,-2.2 7,-2.6 1.7,-0.4 3.5,-1 4.1,-1.5 1.5,-1.2 13.3,-3.9 17,-3.9 2.3,-0 3.3,0.5 3.7,1.9 0.2,1 1.7,2.5 3.2,3.2 1.8,1 3.4,3.1 4.9,6.7 2.3,5.7 8.7,13.2 11.2,13.2 1.5,-0 1.6,-1 0.9,-8.2 -0.4,-4.6 -1,-8.5 -1.4,-8.8 -0.8,-0.5 -2.6,-9.5 -3.7,-18.8 -0.7,-6 1.1,-15.1 3.2,-15.9 0.8,-0.3 1.4,-1.1 1.4,-1.8 0,-0.6 2.1,-3.7 4.8,-6.8 10.3,-12.3 9.6,-10.6 10.2,-25.8 0.3,-7.5 1.1,-14.3 1.7,-15 0.6,-0.8 1.4,-3.2 1.8,-5.4 0.6,-3.4 4.2,-10.3 7.4,-14.1 0.5,-0.6 1.2,-2.5 1.6,-4.2l0.7,-3.2 -6.8,-0 -6.7,-0 0.6,-4.7c0.3,-2.6 1,-4.9 1.6,-5.1 0.6,-0.2 1.1,-0.9 1.1,-1.5 0,-1.4 10.4,-11.7 11.8,-11.7 0.5,-0 1.6,-0.8 2.6,-1.9 0.9,-1 3.4,-2.6 5.6,-3.6 2.2,-1 4,-2.2 4,-2.7 0,-0.4 0.7,-0.8 1.5,-0.8 0.9,-0 1.5,-0.9 1.5,-2.1 0,-2 -0.3,-2.1 -5.7,-1.4 -3.2,0.4 -6.2,1 -6.8,1.5 -1.8,1.3 -14.5,3.3 -17.7,2.6 -1.7,-0.3 -4.1,-1.6 -5.2,-2.9 -3.9,-4 -10.4,-9.4 -12.2,-10.1 -1.3,-0.5 -1.5,-1.4 -1,-4.5 0.6,-4.1 -0.3,-5.1 -4.4,-5.1 -1.1,-0 -3.9,-1.9 -6.4,-4.3 -4.5,-4.3 -8,-7.2 -15.1,-12.6 -2.2,-1.7 -4.7,-3.6 -5.5,-4.3 -0.8,-0.7 -2.2,-1.8 -3,-2.3 -0.9,-0.6 -3.2,-2 -5.1,-3.2 -2.8,-1.9 -4.8,-2.3 -11.1,-2.3 -4.2,-0 -8,-0.5 -8.3,-1 -0.3,-0.6 -1.6,-1 -2.8,-1 -1.2,-0 -2.9,-0.6 -3.8,-1.3 -2.7,-2 -6.8,-10.9 -8,-17.2 -0.7,-3.3 -1.6,-6.5 -2,-7 -0.4,-0.6 -1.3,-3.3 -1.9,-6 -1.5,-7 -2.9,-9.5 -5.6,-9.5 -1.8,-0 -2.3,0.7 -2.8,3.7zM162.5,77.5c3.2,1.6 7.5,3.9 9.5,5.2 1.9,1.3 4.3,2.9 5.4,3.6 4.9,3.2 11.3,8.8 13.4,11.6 3.5,4.7 3.4,8.1 -0.3,10 -1.6,0.9 -3.5,2.1 -4.1,2.7 -0.7,0.6 -3.3,1.7 -5.8,2.4 -2.6,0.7 -4.9,1.7 -5.2,2.1 -0.3,0.5 -2.9,0.9 -5.9,0.9 -4.9,-0 -5.6,-0.3 -7.9,-3.3 -1.4,-1.8 -3.3,-5.3 -4.1,-7.8 -0.9,-2.4 -2.2,-5.8 -2.9,-7.4 -0.8,-1.7 -1.9,-5.7 -2.6,-9 -0.7,-3.3 -1.6,-6.2 -2,-6.5 -0.4,-0.3 -1,-2.2 -1.4,-4.4l-0.7,-3.9 4.3,0.5c2.4,0.3 7,1.8 10.3,3.3zM103,77.6c0,0.9 -0.6,3 -1.4,4.7 -1.2,2.7 -6.5,19.3 -8.3,26.2 -0.3,1.1 -0.9,2.4 -1.3,3 -0.4,0.5 -1.5,4.1 -2.5,8 -1,3.8 -2.1,7.4 -2.5,8 -0.4,0.5 -1.3,3.5 -2,6.5 -0.7,3 -1.6,5.7 -1.9,6 -0.3,0.3 -1.1,1.4 -1.7,2.4 -0.6,1.1 -1.9,2.4 -3,3 -1,0.6 -2.1,1.4 -2.4,1.7 -0.3,0.3 -2.2,1.2 -4.2,2 -2.1,0.7 -3.8,1.6 -3.8,2 0,0.4 -3.1,1.5 -7,2.5 -3.8,1 -7,2.2 -7,2.6 0,0.4 -1.9,0.8 -4.2,0.8l-4.1,-0 0.6,-9.8c0.9,-13 2.4,-18.3 8.4,-29.7 4.2,-7.9 9.4,-14.4 18.4,-22.9 11.2,-10.5 11.4,-10.6 12.8,-10.6 0.6,-0 1.4,-0.6 1.7,-1.3 0.2,-0.7 2.5,-2.2 4.9,-3.2 2.5,-1.1 4.5,-2.3 4.5,-2.8 0,-0.4 1.4,-0.7 3,-0.7 2.1,-0 3,0.5 3,1.6zM126.9,78.2c0.7,1.3 2.3,4.1 3.6,6.3 1.4,2.2 3.5,6 4.6,8.5 1.2,2.5 2.5,4.7 2.9,5 0.4,0.3 1.3,2.3 2,4.5 0.7,2.2 1.7,4.5 2.2,5.2 0.6,0.7 1.2,3.9 1.5,7.3 0.5,5.4 0.3,6.2 -1.8,8.4 -3.6,3.5 -9.8,6.3 -17,7.6 -3.5,0.6 -8.3,1.5 -10.7,2.1 -2.3,0.5 -5.4,0.9 -6.7,0.9 -2.8,-0 -4.3,-2.3 -2.8,-4.3 0.6,-0.6 1.6,-3.5 2.3,-6.2 0.7,-2.8 1.6,-5.5 2,-6 0.4,-0.6 1.5,-4.5 2.5,-8.8 1,-4.2 2.1,-7.7 2.5,-7.7 0.4,-0 1,-1.9 1.4,-4.2 0.4,-2.3 1.3,-5.6 2.1,-7.2 0.7,-1.7 1.9,-4.9 2.5,-7.1 2.1,-6.8 4.7,-8.4 6.9,-4.3zM207.6,123.1c0.8,1.3 0.7,26.1 -0.2,32.9 -0.3,2.5 -0.9,5.4 -1.4,6.5 -0.5,1.1 -1.3,4.8 -1.9,8.2 -0.7,3.4 -1.8,7.6 -2.7,9.5 -5.5,12.3 -7.1,13.3 -10.2,6.6 -5.1,-10.8 -7.1,-15.8 -7.7,-19.2 -0.4,-2 -1,-3.6 -1.4,-3.6 -0.4,-0 -1.3,-2.1 -2.1,-4.7 -0.7,-2.6 -1.9,-5.8 -2.6,-7.2 -1.6,-3.1 -3.9,-15.5 -3.1,-17.4 0.8,-2.1 14.2,-8.3 21.2,-9.8 3.3,-0.7 6.4,-1.6 7,-2.1 1.3,-1.2 4.3,-1 5.1,0.3zM156.2,145c3.7,2.9 5.5,7.4 6.7,17 0.8,6.4 1.9,10.1 4.5,15 7.8,14.8 9.8,19.6 10.3,24.7 0.7,6.6 -0.7,9.5 -5.3,11.3 -1.9,0.8 -3.4,1.7 -3.4,2.1 0,0.4 -2.1,1.5 -4.7,2.5 -2.7,1 -5,2.1 -5.3,2.5 -1.6,2.2 -7.7,2.9 -25.9,2.9 -19.7,-0 -28.9,-0.9 -34.6,-3.4 -1.6,-0.8 -4.5,-1.7 -6.3,-2.1 -1.8,-0.4 -5.2,-2.5 -7.5,-4.7l-4.2,-4 0.1,-8.9c0.1,-4.9 0.6,-9.5 1.2,-10.2 0.5,-0.6 1.3,-3 1.6,-5.1 1.1,-6.1 6.4,-14.5 13.6,-21.6 8,-7.8 8.8,-8.5 13.7,-10.7 2.1,-1 4,-2 4.3,-2.3 0.3,-0.3 2.5,-1.2 5,-1.9 2.5,-0.7 5.4,-1.7 6.5,-2.2 2.6,-1.2 13.3,-2.6 21.1,-2.8 4.7,-0.1 6.7,0.4 8.6,1.9zM68,173.6c0,3 -3.2,15.4 -4.1,15.9 -0.5,0.4 -0.9,1.6 -0.9,2.9 0,1.2 -0.6,3.1 -1.4,4.1 -1.4,1.9 -1.5,1.9 -4.9,0.1 -3.4,-1.9 -8.7,-10.3 -8.7,-13.8 0,-3.3 6.8,-8.8 10.9,-8.8 1.2,-0 2.1,-0.5 2.1,-1 0,-0.6 1.6,-1 3.5,-1 2.5,-0 3.5,0.4 3.5,1.6z" + android:fillColor="#000000" + android:strokeColor="#00000000"/> diff --git a/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon.xml b/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon.xml deleted file mode 100644 index 172bc624..00000000 --- a/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon_round.xml b/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon_round.xml deleted file mode 100644 index 7e0a9c7b..00000000 --- a/client/android/src/debug/res/mipmap-anydpi-v26/vpnicon_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/client/android/src/debug/res/mipmap-hdpi/vpnicon.png b/client/android/src/debug/res/mipmap-hdpi/vpnicon.png deleted file mode 100644 index fa762712..00000000 Binary files a/client/android/src/debug/res/mipmap-hdpi/vpnicon.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-hdpi/vpnicon_foreground.png b/client/android/src/debug/res/mipmap-hdpi/vpnicon_foreground.png deleted file mode 100644 index 849f6ffd..00000000 Binary files a/client/android/src/debug/res/mipmap-hdpi/vpnicon_foreground.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-hdpi/vpnicon_round.png b/client/android/src/debug/res/mipmap-hdpi/vpnicon_round.png deleted file mode 100644 index 0fc3bb7f..00000000 Binary files a/client/android/src/debug/res/mipmap-hdpi/vpnicon_round.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-mdpi/vpnicon.png b/client/android/src/debug/res/mipmap-mdpi/vpnicon.png deleted file mode 100644 index b5d3fe52..00000000 Binary files a/client/android/src/debug/res/mipmap-mdpi/vpnicon.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-mdpi/vpnicon_foreground.png b/client/android/src/debug/res/mipmap-mdpi/vpnicon_foreground.png deleted file mode 100644 index 198212fd..00000000 Binary files a/client/android/src/debug/res/mipmap-mdpi/vpnicon_foreground.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-mdpi/vpnicon_round.png b/client/android/src/debug/res/mipmap-mdpi/vpnicon_round.png deleted file mode 100644 index d6ce6de9..00000000 Binary files a/client/android/src/debug/res/mipmap-mdpi/vpnicon_round.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xhdpi/vpnicon.png b/client/android/src/debug/res/mipmap-xhdpi/vpnicon.png deleted file mode 100644 index b6f63054..00000000 Binary files a/client/android/src/debug/res/mipmap-xhdpi/vpnicon.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xhdpi/vpnicon_foreground.png b/client/android/src/debug/res/mipmap-xhdpi/vpnicon_foreground.png deleted file mode 100644 index bc00fe12..00000000 Binary files a/client/android/src/debug/res/mipmap-xhdpi/vpnicon_foreground.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xhdpi/vpnicon_round.png b/client/android/src/debug/res/mipmap-xhdpi/vpnicon_round.png deleted file mode 100644 index ba4aa7b7..00000000 Binary files a/client/android/src/debug/res/mipmap-xhdpi/vpnicon_round.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon.png b/client/android/src/debug/res/mipmap-xxhdpi/vpnicon.png deleted file mode 100644 index e6d3c7ed..00000000 Binary files a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_foreground.png b/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_foreground.png deleted file mode 100644 index 4f281d05..00000000 Binary files a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_foreground.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_round.png b/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_round.png deleted file mode 100644 index deb1e253..00000000 Binary files a/client/android/src/debug/res/mipmap-xxhdpi/vpnicon_round.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon.png b/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon.png deleted file mode 100644 index be451734..00000000 Binary files a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_foreground.png b/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_foreground.png deleted file mode 100644 index 8b8bad14..00000000 Binary files a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_foreground.png and /dev/null differ diff --git a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_round.png b/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_round.png deleted file mode 100644 index ec01eaee..00000000 Binary files a/client/android/src/debug/res/mipmap-xxxhdpi/vpnicon_round.png and /dev/null differ diff --git a/client/android/src/debug/res/values/vpnicon_background.xml b/client/android/src/debug/res/values/vpnicon_background.xml deleted file mode 100644 index 5f343ea1..00000000 --- a/client/android/src/debug/res/values/vpnicon_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #000000 - \ No newline at end of file diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index b824610b..d6efc732 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -377,11 +377,11 @@ class VPNService : android.net.VpnService() { val wgConfig: String = wireguard_conf!!.toWgUserspaceString() val builder = Builder() setupBuilder(wireguard_conf, builder) - builder.setSession("mvpn0") + builder.setSession("avpn0") builder.establish().use { tun -> if (tun == null)return Log.i(tag, "Go backend " + wgVersion()) - currentTunnelHandle = wgTurnOn("mvpn0", tun.detachFd(), wgConfig) + currentTunnelHandle = wgTurnOn("avpn0", tun.detachFd(), wgConfig) } if (currentTunnelHandle < 0) { Log.e(tag, "Activation Error Code -> $currentTunnelHandle") diff --git a/client/client.pro b/client/client.pro index d4f5a70c..91109890 100644 --- a/client/client.pro +++ b/client/client.pro @@ -39,6 +39,7 @@ HEADERS += \ managementserver.h \ protocols/protocols_defs.h \ settings.h \ + ui/notificationhandler.h \ ui/models/containers_model.h \ ui/models/protocols_model.h \ ui/pages.h \ @@ -94,6 +95,7 @@ SOURCES += \ managementserver.cpp \ protocols/protocols_defs.cpp \ settings.cpp \ + ui/notificationhandler.cpp \ ui/models/containers_model.cpp \ ui/models/protocols_model.cpp \ ui/pages_logic/AppSettingsLogic.cpp \ @@ -133,6 +135,8 @@ TRANSLATIONS = \ translations/amneziavpn_ru.ts win32 { + DEFINES += MVPN_WINDOWS + OTHER_FILES += platform_win/vpnclient.rc RC_FILE = platform_win/vpnclient.rc @@ -168,6 +172,8 @@ win32 { } macx { + DEFINES += MVPN_MACOS + ICON = $$PWD/images/app.icns HEADERS += ui/macos_util.h @@ -180,6 +186,8 @@ macx { } linux:!android { + DEFINES += MVPN_LINUX + LIBS += /usr/lib/x86_64-linux-gnu/libcrypto.a LIBS += /usr/lib/x86_64-linux-gnu/libssl.a } @@ -187,6 +195,7 @@ linux:!android { win32|macx|linux:!android { HEADERS += \ + ui/systemtray_notificationhandler.h \ protocols/openvpnprotocol.h \ protocols/ikev2_vpn_protocol.h \ protocols/openvpnovercloakprotocol.h \ @@ -194,6 +203,7 @@ win32|macx|linux:!android { protocols/wireguardprotocol.h \ SOURCES += \ + ui/systemtray_notificationhandler.cpp \ protocols/openvpnprotocol.cpp \ protocols/ikev2_vpn_protocol.cpp \ protocols/openvpnovercloakprotocol.cpp \ @@ -203,12 +213,20 @@ win32|macx|linux:!android { android { QT += androidextras + DEFINES += MVPN_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 += \ android/AndroidManifest.xml \ diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index a5d32be0..1e4c9c8d 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -1,6 +1,7 @@ #include "servercontroller.h" #include +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #include "sftpchannel.h" #include "sshconnectionmanager.h" @@ -134,12 +136,7 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti { QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh"; - QString mkdir = "sudo docker exec -i $CONTAINER_NAME mkdir -p /opt/amnezia/"; - ErrorCode e = runScript(credentials, - replaceVars(mkdir, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); - if (e) return e; - - e = uploadTextFileToContainer(container, credentials, script, fileName); + ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName); if (e) return e; 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"; }; + // 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) { e = runScript(credentials, replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), diff --git a/client/images/icon_src.svg b/client/images/icon_src.svg new file mode 100644 index 00000000..b27d1360 --- /dev/null +++ b/client/images/icon_src.svg @@ -0,0 +1,67 @@ + + + + + + + + diff --git a/client/main.cpp b/client/main.cpp index d3251f1e..817e885f 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -116,12 +116,12 @@ int main(int argc, char *argv[]) app.setQuitOnLastWindowClosed(false); - qRegisterMetaType("VpnProtocol::ConnectionState"); + qRegisterMetaType("VpnProtocol::VpnConnectionState"); qRegisterMetaType("ServerCredentials"); qRegisterMetaType("DockerContainer"); qRegisterMetaType("TransportProto"); - qRegisterMetaType("Protocol"); + qRegisterMetaType("Proto"); qRegisterMetaType("ServiceType"); qRegisterMetaType("Page"); qRegisterMetaType("ConnectionState"); diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp new file mode 100644 index 00000000..6b2c1aa0 --- /dev/null +++ b/client/platforms/android/android_controller.cpp @@ -0,0 +1,334 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(startActivityForResult)}}; + QAndroidJniObject javaClass(PERMISSIONHELPER_CLASS); + QAndroidJniEnvironment env; + jclass objectClass = env->GetObjectClass(javaClass.object()); + env->RegisterNatives(objectClass, methods, + sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); + + auto appContext = QtAndroid::androidActivity().callObjectMethod( + "getApplicationContext", "()Landroid/content/Context;"); + + QAndroidJniObject::callStaticMethod( + "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( + 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&& 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; +} diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h new file mode 100644 index 00000000..a4734414 --- /dev/null +++ b/client/platforms/android/android_controller.h @@ -0,0 +1,84 @@ +#ifndef ANDROID_CONTROLLER_H +#define ANDROID_CONTROLLER_H + +#include +#include + +#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&& 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 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 diff --git a/client/platforms/android/android_notificationhandler.cpp b/client/platforms/android/android_notificationhandler.cpp new file mode 100644 index 00000000..2bd26b7f --- /dev/null +++ b/client/platforms/android/android_notificationhandler.cpp @@ -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); +} diff --git a/client/platforms/android/android_notificationhandler.h b/client/platforms/android/android_notificationhandler.h new file mode 100644 index 00000000..e3e7325e --- /dev/null +++ b/client/platforms/android/android_notificationhandler.h @@ -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 + +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 diff --git a/client/protocols/android_vpnprotocol.cpp b/client/protocols/android_vpnprotocol.cpp index 3f8141b0..66e2eaf5 100644 --- a/client/protocols/android_vpnprotocol.cpp +++ b/client/protocols/android_vpnprotocol.cpp @@ -17,338 +17,25 @@ #include "android_vpnprotocol.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; +#include "platforms/android/android_controller.h" -// 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) : VpnProtocol(configuration, parent), - m_protocol(protocol), - m_binder(this) + m_protocol(protocol) { } -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(startActivityForResult)}}; - QAndroidJniObject javaClass(PERMISSIONHELPER_CLASS); - QAndroidJniEnvironment env; - jclass objectClass = env->GetObjectClass(javaClass.object()); - env->RegisterNatives(objectClass, methods, - sizeof(methods) / sizeof(methods[0])); - env->DeleteLocalRef(objectClass); - - auto appContext = QtAndroid::androidActivity().callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); - - QAndroidJniObject::callStaticMethod( - "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() { - - //qDebug().noquote() << "AndroidVpnProtocol::start" << QJsonDocument(m_rawConfig).toJson(); - qDebug() << "Prompting for VPN permission"; - auto appContext = QtAndroid::androidActivity().callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); - QAndroidJniObject::callStaticMethod( - 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; + AndroidController::instance()->setVpnConfig(m_rawConfig); + return AndroidController::instance()->start(); } -// Activates the tunnel that is currently set -// 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&& 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) +void AndroidVpnProtocol::stop() { - 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); - - 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; + qDebug() << "AndroidVpnProtocol::stop()"; + AndroidController::instance()->stop(); } + diff --git a/client/protocols/android_vpnprotocol.h b/client/protocols/android_vpnprotocol.h index 55ac0fdd..cdcff2c6 100644 --- a/client/protocols/android_vpnprotocol.h +++ b/client/protocols/android_vpnprotocol.h @@ -11,38 +11,16 @@ using namespace amnezia; -class AndroidVpnProtocol : public VpnProtocol, - public QAndroidServiceConnection +class AndroidVpnProtocol : public VpnProtocol { Q_OBJECT public: explicit AndroidVpnProtocol(Proto protocol, const QJsonObject& configuration, QObject* parent = nullptr); - static AndroidVpnProtocol* instance(); - virtual ~AndroidVpnProtocol() override = default; - bool initialize(); - - 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&& callback); - - void cleanupBackendLogs(); - - // from QAndroidServiceConnection - void onServiceConnected(const QString& name, - const QAndroidBinder& serviceBinder) override; - void onServiceDisconnected(const QString& name) override; + ErrorCode start() override; + void stop() override; signals: @@ -55,28 +33,6 @@ protected: private: Proto m_protocol; - bool m_serviceConnected = false; - std::function 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 diff --git a/client/protocols/ikev2_vpn_protocol.cpp b/client/protocols/ikev2_vpn_protocol.cpp index 42e34ee8..8f5d4cd3 100644 --- a/client/protocols/ikev2_vpn_protocol.cpp +++ b/client/protocols/ikev2_vpn_protocol.cpp @@ -241,7 +241,7 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE 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() diff --git a/client/protocols/openvpnovercloakprotocol.cpp b/client/protocols/openvpnovercloakprotocol.cpp index f2ac4512..01afb4ca 100644 --- a/client/protocols/openvpnovercloakprotocol.cpp +++ b/client/protocols/openvpnovercloakprotocol.cpp @@ -78,7 +78,7 @@ ErrorCode OpenVpnOverCloakProtocol::start() m_ckProcess.waitForStarted(); if (m_ckProcess.state() == QProcess::ProcessState::Running) { - setConnectionState(ConnectionState::Connecting); + setConnectionState(VpnConnectionState::Connecting); return OpenVpnProtocol::start(); } @@ -113,5 +113,5 @@ QString OpenVpnOverCloakProtocol::cloakExecPath() 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(); } diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index df57e365..75e56674 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -86,8 +86,8 @@ void OpenVpnProtocol::killOpenVpnProcess() void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration) { - if (configuration.contains(ProtocolProps::key_proto_config_data(Protocol::OpenVpn))) { - QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Protocol::OpenVpn)).toObject(); + if (configuration.contains(ProtocolProps::key_proto_config_data(Proto::OpenVpn))) { + QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::OpenVpn)).toObject(); m_configFile.open(); m_configFile.write(jConfig.value(config_key::config).toString().toUtf8()); @@ -167,7 +167,7 @@ ErrorCode OpenVpnProtocol::start() return lastError(); } - setConnectionState(ConnectionState::Connecting); + setConnectionState(VpnConnectionState::Connecting); m_openVpnProcess = IpcClient::CreatePrivilegedProcess(); @@ -201,7 +201,7 @@ ErrorCode OpenVpnProtocol::start() }); connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, [&]() { - setConnectionState(ConnectionState::Disconnected); + setConnectionState(VpnConnectionState::Disconnected); }); m_openVpnProcess->start(); diff --git a/client/protocols/shadowsocksvpnprotocol.cpp b/client/protocols/shadowsocksvpnprotocol.cpp index 702a35d8..e2465c1f 100644 --- a/client/protocols/shadowsocksvpnprotocol.cpp +++ b/client/protocols/shadowsocksvpnprotocol.cpp @@ -75,7 +75,7 @@ ErrorCode ShadowSocksVpnProtocol::start() m_ssProcess.waitForStarted(); if (m_ssProcess.state() == QProcess::ProcessState::Running) { - setConnectionState(ConnectionState::Connecting); + setConnectionState(VpnConnectionState::Connecting); return OpenVpnProtocol::start(); } @@ -112,5 +112,5 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath() 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(); } diff --git a/client/protocols/vpnprotocol.h b/client/protocols/vpnprotocol.h index 8310d825..4b6876d5 100644 --- a/client/protocols/vpnprotocol.h +++ b/client/protocols/vpnprotocol.h @@ -48,24 +48,18 @@ signals: void timeoutTimerEvent(); void protocolError(amnezia::ErrorCode e); - // 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: - virtual void onTimeout(); +public slots: + virtual void onTimeout(); // todo: remove? + + void setBytesChanged(quint64 receivedBytes, quint64 sentBytes); + void setConnectionState(VpnProtocol::VpnConnectionState state); protected: void startTimeoutTimer(); void stopTimeoutTimer(); - virtual void setBytesChanged(quint64 receivedBytes, quint64 sentBytes); - virtual void setConnectionState(VpnProtocol::VpnConnectionState state); - VpnConnectionState m_connectionState; + QString m_routeGateway; QString m_vpnLocalAddress; QString m_vpnGateway; diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index d08843e7..d650cb3f 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -61,7 +61,7 @@ void WireguardProtocol::stop() connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error; - setConnectionState(ConnectionState::Disconnected); + setConnectionState(VpnConnectionState::Disconnected); }); connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { @@ -79,7 +79,7 @@ void WireguardProtocol::stop() 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)) { 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(); 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(); } - setConnectionState(ConnectionState::Connecting); + setConnectionState(VpnConnectionState::Connecting); m_wireguardStartProcess = IpcClient::CreatePrivilegedProcess(); @@ -178,7 +178,7 @@ ErrorCode WireguardProtocol::start() connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error; - setConnectionState(ConnectionState::Disconnected); + setConnectionState(VpnConnectionState::Disconnected); }); 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]() { - setConnectionState(ConnectionState::Connected); + setConnectionState(VpnConnectionState::Connected); }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() { diff --git a/client/ui/notificationhandler.cpp b/client/ui/notificationhandler.cpp new file mode 100644 index 00000000..81f6430a --- /dev/null +++ b/client/ui/notificationhandler.cpp @@ -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 +#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; +} diff --git a/client/ui/notificationhandler.h b/client/ui/notificationhandler.h new file mode 100644 index 00000000..b87e9575 --- /dev/null +++ b/client/ui/notificationhandler.h @@ -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 +#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 diff --git a/client/ui/pages_logic/VpnLogic.cpp b/client/ui/pages_logic/VpnLogic.cpp index 7c2b9305..1d66547d 100644 --- a/client/ui/pages_logic/VpnLogic.cpp +++ b/client/ui/pages_logic/VpnLogic.cpp @@ -88,8 +88,6 @@ void VpnLogic::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) bool pbConnectVisible = false; set_labelStateText(VpnProtocol::textConnectionState(state)); - uiLogic()->setTrayState(state); - switch (state) { case VpnProtocol::Disconnected: onBytesChanged(0,0); diff --git a/client/ui/systemtray_notificationhandler.cpp b/client/ui/systemtray_notificationhandler.cpp new file mode 100644 index 00000000..9b10f0de --- /dev/null +++ b/client/ui/systemtray_notificationhandler.cpp @@ -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 +#include "systemtray_notificationhandler.h" + + +#ifdef Q_OS_MACOS +# include "platforms/macos/macosutils.h" +#endif + +#include +#include +#include +#include + +#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 +// } +} + diff --git a/client/ui/systemtray_notificationhandler.h b/client/ui/systemtray_notificationhandler.h new file mode 100644 index 00000000..46563bde --- /dev/null +++ b/client/ui/systemtray_notificationhandler.h @@ -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 +#include + +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 diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index fd14b975..f3cbed04 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -44,6 +44,10 @@ #include "ui/macos_util.h" #endif +#ifdef Q_OS_ANDROID +#include "platforms/android/android_controller.h" +#endif + #include "pages_logic/AppSettingsLogic.h" #include "pages_logic/GeneralSettingsLogic.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::ShadowSocks, new ShadowSocksLogic(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::Sftp, new OtherProtocolsLogic(this)); @@ -104,8 +108,6 @@ UiLogic::UiLogic(QObject *parent) : UiLogic::~UiLogic() { - m_tray = nullptr; - emit hide(); if (m_vpnConnection->connectionState() != VpnProtocol::VpnConnectionState::Disconnected) { @@ -128,9 +130,22 @@ UiLogic::~UiLogic() void UiLogic::initalizeUiLogic() { - qDebug() << "UiLogic::initalizeUiLogic()"; - setupTray(); +#ifdef Q_OS_ANDROID + 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) { // needToHideCustomTitlebar = true; @@ -161,7 +176,7 @@ void UiLogic::initalizeUiLogic() selectedServerIndex = m_settings.defaultServerIndex(); //goToPage(Page::ServerContainers, true, false); //goToPage(Page::NewServerProtocols, true, false); - //onGotoProtocolPage(Protocol::OpenVpn); + //onGotoProtocolPage(Proto::OpenVpn); //ui->pushButton_general_settings_exit->hide(); @@ -600,27 +615,8 @@ ErrorCode UiLogic::doInstallAction(const std::function &action, 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); if (logic) return logic; else { @@ -639,57 +635,23 @@ void UiLogic::setQmlRoot(QObject *newQmlRoot) m_qmlRoot = newQmlRoot; } +NotificationHandler *UiLogic::notificationHandler() const +{ + return m_notificationHandler; +} + PageEnumNS::Page UiLogic::currentPage() { return static_cast(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) { QString fileName = QFileDialog::getSaveFileName(nullptr, desc, QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), ext); + if (fileName.isEmpty()) return false; + QSaveFile save(fileName); save.open(QIODevice::WriteOnly); 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, QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), ext); + if (fileName.isEmpty()) return false; + QSaveFile save(fileName); save.open(QIODevice::WriteOnly); save.write(QByteArray::fromBase64(data.toUtf8())); diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h index 2b9b91bf..9eefb381 100644 --- a/client/ui/uilogic.h +++ b/client/ui/uilogic.h @@ -6,7 +6,6 @@ #include #include #include -#include #include "property_helper.h" #include "pages.h" @@ -16,6 +15,7 @@ #include "models/containers_model.h" #include "models/protocols_model.h" +#include "notificationhandler.h" #include "settings.h" class AppSettingsLogic; @@ -107,12 +107,8 @@ public: void setDialogConnectErrorText(const QString &dialogConnectErrorText); signals: - void trayIconUrlChanged(); - void trayActionDisconnectEnabledChanged(); - void trayActionConnectEnabledChanged(); void dialogConnectErrorTextChanged(); - void goToPage(PageEnumNS::Page page, 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); @@ -126,17 +122,12 @@ signals: void raise(); private: - QSystemTrayIcon *m_tray; - QString m_dialogConnectErrorText; private slots: // containers - INOUT arg void installServer(QMap &containers); - void setTrayState(VpnProtocol::VpnConnectionState state); - void onTrayActivated(QSystemTrayIcon::ActivationReason reason); - private: PageEnumNS::Page currentPage(); struct ProgressFunc { @@ -171,9 +162,6 @@ private: const ButtonFunc& button, const LabelFunc& info); - void setupTray(); - void setTrayIcon(const QString &iconPath); - public: AppSettingsLogic *appSettingsLogic() { return m_appSettingsLogic; } @@ -195,6 +183,8 @@ public: QObject *qmlRoot() const; void setQmlRoot(QObject *newQmlRoot); + NotificationHandler *notificationHandler() const; + private: QObject *m_qmlRoot{nullptr}; @@ -218,6 +208,8 @@ private: QThread m_vpnConnectionThread; Settings m_settings; + NotificationHandler* m_notificationHandler; + // QRegExpValidator m_ipAddressValidator; // QRegExpValidator m_ipAddressPortValidator; @@ -230,10 +222,6 @@ private: // void showEvent(QShowEvent *event) override; // void hideEvent(QHideEvent *event) override; - const QString ConnectedTrayIconName = "active.png"; - const QString DisconnectedTrayIconName = "default.png"; - const QString ErrorTrayIconName = "error.png"; - // QStack pagesStack; int selectedServerIndex = -1; // server index to use when proto settings page opened diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index e08e6c02..5837ce58 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -12,7 +12,8 @@ #include #ifdef Q_OS_ANDROID -#include +#include "android_controller.h" +#include "protocols/android_vpnprotocol.h" #endif #ifdef Q_OS_IOS @@ -261,11 +262,8 @@ void VpnConnection::connectToVpn(int serverIndex, #elif defined Q_OS_ANDROID Proto proto = ContainerProps::defaultProtocol(container); AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration); - if (!androidVpnProtocol->initialize()) { - qDebug() << QString("Init failed") ; - emit VpnProtocol::Error; - return; - } + connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState); + m_vpnProtocol.reset(androidVpnProtocol); #elif defined Q_OS_IOS Proto proto = ContainerProps::defaultProtocol(container); @@ -279,7 +277,7 @@ void VpnConnection::connectToVpn(int serverIndex, #endif 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))); ServerController::disconnectFromHost(credentials);