diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a5605263..ca7ac87d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,5 +1,6 @@ name: 'Deploy workflow' + on: [push] jobs: @@ -8,7 +9,7 @@ jobs: runs-on: ubuntu-latest env: - QT_VERSION: 5.15.2 + QT_VERSION: 6.4.1 QIF_VERSION: 4.5 steps: @@ -19,6 +20,7 @@ jobs: host: 'linux' target: 'desktop' arch: 'gcc_64' + modules: 'qtremoteobjects qt5compat qtshadertools' dir: ${{ runner.temp }} setup-python: 'true' tools: 'tools_ifw' @@ -36,38 +38,34 @@ jobs: - name: 'Build project' run: | + sudo apt-get install libxkbcommon-x11-0 export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin bash deploy/build_linux.sh + - name: 'Upload installer artifact' + uses: actions/upload-artifact@v3 + with: + name: AmneziaVPN_Linux_installer + path: deploy/AmneziaVPN_Linux_Installer + retention-days: 7 + - name: 'Upload unpacked artifact' + uses: actions/upload-artifact@v3 + with: + name: AmneziaVPN_Linux_unpacked + path: deploy/AppDir + retention-days: 7 + # ------------------------------------------------------ Build-Windows: name: Build-Windows runs-on: windows-latest - continue-on-error: true - - strategy: - matrix: - arch: [32, 64] - include: - - qt-arch: 'win32_msvc2019' - arch: 32 - - qt-msvc-path: 'msvc2019' - arch: 32 - - msvc-arch: 'x86' - arch: 32 - - qt-arch: 'win64_msvc2019_64' - arch: 64 - - qt-msvc-path: 'msvc2019_64' - arch: 64 - - msvc-arch: 'x64' - arch: 64 env: - QT_VERSION: 5.15.2 + QT_VERSION: 6.4.1 QIF_VERSION: 4.5 - BUILD_ARCH: ${{ matrix.arch }} + BUILD_ARCH: 64 steps: - name: 'Get sources' @@ -85,7 +83,8 @@ jobs: version: ${{ env.QT_VERSION }} host: 'windows' target: 'desktop' - arch: '${{ matrix.qt-arch }}' + arch: 'win64_msvc2019_64' + modules: 'qtremoteobjects qt5compat qtshadertools' dir: ${{ runner.temp }} setup-python: 'true' tools: 'tools_ifw' @@ -95,16 +94,29 @@ jobs: - name: 'Setup mvsc' uses: ilammy/msvc-dev-cmd@v1 with: - arch: ${{ matrix.msvc-arch }} + arch: 'x64' - name: 'Build project' shell: cmd run: | set BUILD_ARCH=${{ env.BUILD_ARCH }} - set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\${{ matrix.qt-msvc-path }}\\bin" + set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin" set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin" call deploy\\build_windows.bat + - name: 'Upload installer artifact' + uses: actions/upload-artifact@v3 + with: + name: AmneziaVPN_Windows_installer + path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe + retention-days: 7 + - name: 'Upload unpacked artifact' + uses: actions/upload-artifact@v3 + with: + name: AmneziaVPN_Windows_unpacked + path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release + retention-days: 7 + # ------------------------------------------------------ Build-IOS: @@ -112,7 +124,7 @@ jobs: runs-on: macos-latest env: - QT_VERSION: 5.15.2 + QT_VERSION: 6.4.1 QIF_VERSION: 4.4 steps: @@ -121,12 +133,24 @@ jobs: with: xcode-version: '13.4' - - name: 'Install Qt' + - name: 'Install desktop Qt' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'mac' + target: 'desktop' + arch: 'clang_64' + modules: 'qtremoteobjects qt5compat qtshadertools' + dir: ${{ runner.temp }} + set-env: 'true' + + - name: 'Install ios Qt' uses: jurplel/install-qt-action@v3 with: version: ${{ env.QT_VERSION }} host: 'mac' target: 'ios' + modules: 'qtremoteobjects qt5compat qtshadertools' dir: ${{ runner.temp }} setup-python: 'true' set-env: 'true' @@ -153,9 +177,21 @@ jobs: - name: 'Build project' run: | export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/ios/bin" + export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos" export QT_IOS_BIN=$QT_BIN_DIR export PATH=$PATH:~/go/bin - bash deploy/build_ios.sh + mkdir build-ios + $QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR + +# - name: iOS Build/Release With Multiple Targets Action +# uses: kumarsunil0007/build-ios-action@latest +# with: +# project-path: build-ios/AmneziaVPN.xcodeproj +# p12-base64: ~/amnezia-vpn/amnezia-ios-certificates/certs/distribution/443886Q5PL.p12 +# mobileprovision-base64: ~/amnezia-vpn/amnezia-ios-certificates/testprofilez.mobileprovision +# code-signing-identity: 'iOS Distribution' +# team-id: 'X7UJ388FXK' +# configuration: Release # ------------------------------------------------------ @@ -164,7 +200,7 @@ jobs: runs-on: macos-latest env: - QT_VERSION: 5.15.2 + QT_VERSION: 6.4.1 QIF_VERSION: 4.5 steps: @@ -180,6 +216,7 @@ jobs: host: 'mac' target: 'desktop' arch: 'clang_64' + modules: 'qtremoteobjects qt5compat qtshadertools' dir: ${{ runner.temp }} setup-python: 'true' tools: 'tools_ifw' @@ -197,28 +234,60 @@ jobs: - name: 'Build project' run: | - export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/clang_64/bin" + export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin" export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin" bash deploy/build_macos.sh + - name: 'Upload installer artifact' + uses: actions/upload-artifact@v3 + with: + name: AmneziaVPN_MacOS_installer + path: AmneziaVPN.dmg + retention-days: 7 + - name: 'Upload unpacked artifact' + uses: actions/upload-artifact@v3 + with: + name: AmneziaVPN_MacOS_unpacked + path: deploy/build/client/AmneziaVPN.app + retention-days: 7 + # ------------------------------------------------------ Build-Android: name: 'Build-Android' runs-on: ubuntu-latest + continue-on-error: true + + strategy: + matrix: + arch: ['android_x86_64', 'android_x86'] #, 'android_armv7', 'android_arm64_v8a'] env: - QT_VERSION: 5.15.2 + QT_VERSION: 6.4.1 QIF_VERSION: 4.5 steps: - - name: 'Install Qt' + - name: 'Install desktop Qt' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'linux' + target: 'desktop' + arch: 'gcc_64' + modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools' + dir: ${{ runner.temp }} + setup-python: 'true' + set-env: 'true' + extra: '--external 7z' + + - name: 'Install android Qt' uses: jurplel/install-qt-action@v3 with: version: ${{ env.QT_VERSION }} host: 'linux' target: 'android' - arch: 'android' + arch: ${{ matrix.arch }} + modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools' dir: ${{ runner.temp }} setup-python: 'true' set-env: 'true' @@ -237,19 +306,21 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: '8' + java-version: '11' - name: 'Build project' run: | - export NDK_VERSION=21d - export ANDROID_NDK_PLATFORM=android-21 + export QT_HOST_PATH="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64" + export NDK_VERSION=23c + export ANDROID_NDK_PLATFORM=android-23 export ANDROID_NDK_HOME=${{ runner.temp }}/android-ndk-r${NDK_VERSION} export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME + export ANDROID_CURRENT_ARCH=${{ matrix.arch }} if [ ! -f $ANDROID_NDK_ROOT/ndk-build ]; then - wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux-x86_64.zip -qO ${{ runner.temp }}/ndk.zip && + wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux.zip -qO ${{ runner.temp }}/ndk.zip && unzip -q -d ${{ runner.temp }} ${{ runner.temp }}/ndk.zip ; fi - export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android/bin - bash deploy/build_android.sh \ No newline at end of file + export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/${{ matrix.arch }}/bin + bash deploy/build_android.sh diff --git a/.github/workflows/tag-deploy.yml b/.github/workflows/tag-deploy.yml new file mode 100644 index 00000000..b88390f4 --- /dev/null +++ b/.github/workflows/tag-deploy.yml @@ -0,0 +1,142 @@ +name: 'Release deploy workflow' + +on: + workflow_dispatch: + # push: + # tags: + # - ** + +jobs: + + Build-Android-Release: + name: 'Build-Android-Release' + runs-on: ubuntu-latest + + env: + QT_VERSION: 6.4.1 + QIF_VERSION: 4.5 + + steps: + - name: 'Install desktop Qt' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'linux' + target: 'desktop' + arch: 'gcc_64' + modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools' + dir: ${{ runner.temp }} + setup-python: 'true' + set-env: 'true' + extra: '--external 7z' + + - name: 'Install android Qt x86_64' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'linux' + target: 'android' + arch: 'android_x86_64' + modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools' + dir: ${{ runner.temp }} + setup-python: 'true' + set-env: 'true' + extra: '--external 7z' + + - name: 'Install android Qt x86' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'linux' + target: 'android' + arch: 'android_x86' + modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools' + dir: ${{ runner.temp }} + setup-python: 'true' + set-env: 'true' + extra: '--external 7z' + + - name: 'Install android Qt arm_v7' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'linux' + target: 'android' + arch: 'android_armv7' + modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools' + dir: ${{ runner.temp }} + setup-python: 'true' + set-env: 'true' + extra: '--external 7z' + + - name: 'Install android Qt arm_v8' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'linux' + target: 'android' + arch: 'android_arm64_v8a' + modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools' + dir: ${{ runner.temp }} + setup-python: 'true' + set-env: 'true' + extra: '--external 7z' + + - name: 'Get sources' + uses: actions/checkout@v3 + with: + path: main + submodules: 'true' + fetch-depth: 10 + + - name: 'Preparations before keystore fetching' + run: | + mkdir keystore + + - name: 'Getting keystore' + uses: actions/checkout@v3 + with: + repository: amnezia-vpn/amnezia-android-certificates + ssh-key: ${{ secrets.ANDROID_CERTS_SSH_PRIVATE_KEY }} + path: keystore + + - name: 'Setup ccache' + uses: hendrikmuhs/ccache-action@v1.2 + + - name: 'Setup Java' + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '11' + + - name: 'Build project' + run: | + export QT_HOST_PATH="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64" + export NDK_VERSION=23c + export ANDROID_NDK_PLATFORM=android-23 + export ANDROID_NDK_HOME=${{ runner.temp }}/android-ndk-r${NDK_VERSION} + export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME + + if [ ! -f $ANDROID_NDK_ROOT/ndk-build ]; then + wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux.zip -qO ${{ runner.temp }}/ndk.zip && + unzip -q -d ${{ runner.temp }} ${{ runner.temp }}/ndk.zip ; + fi + + export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android_arm64_v8a/bin + cd main + bash deploy/build_android.sh + + - name: 'Signing APK' + run: | + pwd + + ANDROID_BUILD_TOOLS_VERSION=30.0.3 + + ${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/zipalign -f -v 4 AmneziaVPN-release-unsigned.apk AmneziaVPN-release-aligned.apk + ${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/apksigner sign --out AmneziaVPN-release-signed.apk --ks keystore/debug.keystore --ks-key-alias ${{ secrets.DEBUG_ANDROID_KEYSTORE_KEY_ALIAS }} --ks-pass pass:${{secrets.DEBUG_ANDROID_KEYSTOTE_KEY_PASS }} AmneziaVPN-release-aligned.apk + + - name: 'Upload' + uses: actions/upload-artifact@v3 + with: + name: Release APK + path: ${{ runner.temp }}/main/AmneziaVPN-release-signed.apk diff --git a/.gitignore b/.gitignore index e2fdf13d..562b2ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ qrc_*.cpp ui_*.h Makefile* *build-* +compile_commands.json # fastlane client/fastlane/report.xml @@ -126,3 +127,6 @@ captures/ # Android Profiling *.hprof client/3rd/ShadowSocks/ss_ios.xcconfig + +# UML generated pics +out/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index a8295ac7..7a51fcf7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,9 @@ [submodule "client/3rd/qtkeychain"] path = client/3rd/qtkeychain url = https://github.com/frankosterfeld/qtkeychain.git +[submodule "deploy/amnezia-ios-certificates"] + path = deploy/amnezia-ios-certificates + url = https://github.com/amnezia-vpn/amnezia-ios-certificates.git +[submodule "client/3rd/SortFilterProxyModel"] + path = client/3rd/SortFilterProxyModel + url = https://github.com/mitchcurtis/SortFilterProxyModel.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..f1e5d37c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) + +set(PROJECT AmneziaVPN) +project(${PROJECT}) + +if(ANDROID) + set(QT_ANDROID_BUILD_ALL_ABIS ON) +endif() + +if(APPLE AND NOT IOS) + set(CMAKE_OSX_ARCHITECTURES "x86_64") +endif() + +add_subdirectory(client) + +if(NOT IOS AND NOT ANDROID) + add_subdirectory(service) +endif() diff --git a/README.md b/README.md index 4d5f993c..c183de36 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ a "`qmake` cannot be found in your `$PATH`" error. In this case run this script using QT\IOS\_BIN env to set the path for the Qt5 macos build bin folder. For example, the path could look like this: ```bash -QT_IOS_BIN="/Users/username/Qt/5.15.2/ios/bin" ./scripts/apple_compile.sh ios +QT_IOS_BIN="/Users/username/Qt/6.4.1/ios/bin" ./scripts/apple_compile.sh ios ``` If you get `gomobile: command not found` make sure to set PATH to the location @@ -96,6 +96,31 @@ Build might fail with "source files not found" error the first time you try it, dependencies in parallel, and some dependencies end up being built after the ones that require them. In this case simply restart the build. +## How to build the Android app +_tested on Mac OS_ + +The Android app has the following requirements: +* JDK 11 +* Android platform SDK 33 +* cmake 3.25.0 + +After you have installed QT, QT Creator and Android Studio installed, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`. + * set path to jdk 11 + * set path to Android SDK ($ANDROID_HOME) + +In case you get errors regarding missing SDK or 'sdkmanager not running', you cannot fix them by correcting the paths and you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and click on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine! + +Double check that the right cmake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab you'll find an entry `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at ` from the drop down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case click in the preferences window on the side menu item `CMake`, then on the tab `Tools`in the center content view and finally on the Button `Add` to set the path to your installed CMake. + +Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on on `Projects`, on the left you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that always all Android targets will be build. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (The `Details` button might be hidden in case QT Creator Window is not running in full screen!). Here we are: choose `android-33` as `Android Build platfrom SDK`. + +That's it you should be ready to compile the project from QT Creator! + +### Development flow +After you've hit the build button, QT-Creator copies the whole project to a folder in the repositories parent directory. The folder should look something like `build-amnezia-client-Android_Qt__Clang_-`. +If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt__Clang_-/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated proejct, you'll need to copy and paste the affected files to the corresponding path in the repositories Android project so that you can add and commit your changes! + +You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the geneated project's root directory (`/client/android-build/.`) and you should be good to continue. ## License GPL v.3 diff --git a/client/3rd/QSimpleCrypto/QSimpleCrypto.cmake b/client/3rd/QSimpleCrypto/QSimpleCrypto.cmake new file mode 100644 index 00000000..7ec5498a --- /dev/null +++ b/client/3rd/QSimpleCrypto/QSimpleCrypto.cmake @@ -0,0 +1,20 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/include/QAead.h + ${CMAKE_CURRENT_LIST_DIR}/include/QBlockCipher.h + ${CMAKE_CURRENT_LIST_DIR}/include/QCryptoError.h + ${CMAKE_CURRENT_LIST_DIR}/include/QRsa.h + ${CMAKE_CURRENT_LIST_DIR}/include/QSimpleCrypto_global.h + ${CMAKE_CURRENT_LIST_DIR}/include/QX509.h + ${CMAKE_CURRENT_LIST_DIR}/include/QX509Store.h +) + +set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/sources/QAead.cpp + ${CMAKE_CURRENT_LIST_DIR}/sources/QBlockCipher.cpp + ${CMAKE_CURRENT_LIST_DIR}/sources/QCryptoError.cpp + ${CMAKE_CURRENT_LIST_DIR}/sources/QRsa.cpp + ${CMAKE_CURRENT_LIST_DIR}/sources/QX509.cpp + ${CMAKE_CURRENT_LIST_DIR}/sources/QX509Store.cpp +) diff --git a/client/3rd/QtSsh/src/botan/botan.cmake b/client/3rd/QtSsh/src/botan/botan.cmake new file mode 100644 index 00000000..983b76db --- /dev/null +++ b/client/3rd/QtSsh/src/botan/botan.cmake @@ -0,0 +1,71 @@ +include_directories( + ${CMAKE_CURRENT_LIST_DIR}/include + ${CMAKE_CURRENT_LIST_DIR}/include/external +) + +if(WIN32) + add_compile_definitions(MVPN_WINDOWS) + add_compile_options(/bigobj) + set(LIBS ${LIBS} + crypt32 + ) + + if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + include_directories(${CMAKE_CURRENT_LIST_DIR}/windows/x86_64) + set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/windows/x86_64/botan_all.h) + set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/windows/x86_64/botan_all.cpp) + else() + include_directories(${CMAKE_CURRENT_LIST_DIR}/windows/x86) + set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/windows/x86/botan_all.h) + set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/windows/x86/botan_all.cpp) + endif() +endif() + +if(APPLE AND NOT IOS) + include_directories(${CMAKE_CURRENT_LIST_DIR}/macos) + set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/macos/botan_all.h) + set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/macos/botan_all.cpp) +endif() + +if(LINUX AND NOT ANDROID) + include_directories(${CMAKE_CURRENT_LIST_DIR}/linux) + set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/linux/botan_all.h) + set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/linux/botan_all.cpp) + set(LIBS ${LIBS} dl) +endif() + +if(ANDROID) + # We need to include qtprivate api's + # As QAndroidBinder is not yet implemented with a public api + set(LIBS ${LIBS} Qt6::CorePrivate) + + set(abi ${CMAKE_ANDROID_ARCH_ABI}) + + include_directories(${CMAKE_CURRENT_LIST_DIR}/android/${abi}) + link_directories(${CMAKE_CURRENT_LIST_DIR}/android/${abi}) + set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/android/${abi}/botan_all.h) + set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/android/${abi}/botan_all.cpp) +endif() + +if(IOS) + # CONFIG(iphoneos, iphoneos|iphonesimulator) { + # contains(QT_ARCH, arm64) { + # INCLUDEPATH += $$PWD/ios/iphone + # HEADERS += $$PWD/ios/iphone/botan_all.h + # SOURCES += $$PWD/ios/iphone/botan_all.cpp + # } else { + # message("Building for iOS/ARM v7 (32-bit) architecture") + # ARCH_TAG = "ios_armv7" + # } + # } + + # CONFIG(iphonesimulator, iphoneos|iphonesimulator) { + # INCLUDEPATH += $$PWD/ios/iphone + # HEADERS += $$PWD/ios/iphone/botan_all.h + # SOURCES += $$PWD/ios/iphone/botan_all.cpp + # } + + include_directories(${CMAKE_CURRENT_LIST_DIR}/ios/iphone) + set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ios/iphone/botan_all.h) + set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ios/iphone/botan_all.cpp) +endif() diff --git a/client/3rd/QtSsh/src/botan/botan.pri b/client/3rd/QtSsh/src/botan/botan.pri index 52847ed4..0c3f299b 100644 --- a/client/3rd/QtSsh/src/botan/botan.pri +++ b/client/3rd/QtSsh/src/botan/botan.pri @@ -37,13 +37,27 @@ linux-g++ { } android { - for (abi, ANDROID_ABIS): { - equals(ANDROID_TARGET_ARCH,$$abi) { - INCLUDEPATH += $$PWD/android/$${abi} - HEADERS += $$PWD/android/$${abi}/botan_all.h - SOURCES += $$PWD/android/$${abi}/botan_all.cpp - } - } + versionAtLeast(QT_VERSION, 6.0.0) { + # We need to include qtprivate api's + # As QAndroidBinder is not yet implemented with a public api + QT+=core-private + ANDROID_ABIS=ANDROID_TARGET_ARCH + + INCLUDEPATH += $$PWD/android/$${ANDROID_TARGET_ARCH} + HEADERS += $$PWD/android/$${ANDROID_TARGET_ARCH}/botan_all.h + SOURCES += $$PWD/android/$${ANDROID_TARGET_ARCH}/botan_all.cpp + } + else { + QT += androidextras + + for (abi, ANDROID_ABIS): { + equals(ANDROID_TARGET_ARCH,$$abi) { + INCLUDEPATH += $$PWD/android/$${abi} + HEADERS += $$PWD/android/$${abi}/botan_all.h + SOURCES += $$PWD/android/$${abi}/botan_all.cpp + } + } + } } ios: { diff --git a/client/3rd/QtSsh/src/ssh/qssh.cmake b/client/3rd/QtSsh/src/ssh/qssh.cmake new file mode 100644 index 00000000..b829fed5 --- /dev/null +++ b/client/3rd/QtSsh/src/ssh/qssh.cmake @@ -0,0 +1,96 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +find_package(Qt6 REQUIRED COMPONENTS + Widgets Gui Network Core5Compat +) +set(LIBS ${LIBS} Qt6::Widgets Qt6::Gui Qt6::Network Qt6::Core5Compat) + +set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/sshsendfacility.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshremoteprocess.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshpacketparser.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshpacket.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshoutgoingpacket.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshkeygenerator.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshkeyexchange.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshincomingpacket.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshcryptofacility.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshconnection.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshchannelmanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshchannel.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshcapabilities.cpp + ${CMAKE_CURRENT_LIST_DIR}/sftppacket.cpp + ${CMAKE_CURRENT_LIST_DIR}/sftpoutgoingpacket.cpp + ${CMAKE_CURRENT_LIST_DIR}/sftpoperation.cpp + ${CMAKE_CURRENT_LIST_DIR}/sftpincomingpacket.cpp + ${CMAKE_CURRENT_LIST_DIR}/sftpdefs.cpp + ${CMAKE_CURRENT_LIST_DIR}/sftpchannel.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshremoteprocessrunner.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshconnectionmanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshkeypasswordretriever.cpp + ${CMAKE_CURRENT_LIST_DIR}/sftpfilesystemmodel.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshdirecttcpiptunnel.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshhostkeydatabase.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshlogging.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshtcpipforwardserver.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshtcpiptunnel.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshforwardedtcpiptunnel.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshagent.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshx11channel.cpp + ${CMAKE_CURRENT_LIST_DIR}/sshx11inforetriever.cpp + ${CMAKE_CURRENT_LIST_DIR}/opensshkeyfilereader.cpp +) + +set(PUBLIC_HEADERS ${PUBLIC_HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/sftpdefs.h + ${CMAKE_CURRENT_LIST_DIR}/ssherrors.h + ${CMAKE_CURRENT_LIST_DIR}/sshremoteprocess.h + ${CMAKE_CURRENT_LIST_DIR}/sftpchannel.h + ${CMAKE_CURRENT_LIST_DIR}/sshkeygenerator.h + ${CMAKE_CURRENT_LIST_DIR}/sshremoteprocessrunner.h + ${CMAKE_CURRENT_LIST_DIR}/sshconnectionmanager.h + ${CMAKE_CURRENT_LIST_DIR}/sshpseudoterminal.h + ${CMAKE_CURRENT_LIST_DIR}/sftpfilesystemmodel.h + ${CMAKE_CURRENT_LIST_DIR}/sshdirecttcpiptunnel.h + ${CMAKE_CURRENT_LIST_DIR}/sshtcpipforwardserver.h + ${CMAKE_CURRENT_LIST_DIR}/sshhostkeydatabase.h + ${CMAKE_CURRENT_LIST_DIR}/sshforwardedtcpiptunnel.h + ${CMAKE_CURRENT_LIST_DIR}/ssh_global.h + ${CMAKE_CURRENT_LIST_DIR}/sshconnection.h +) + +set(HEADERS ${HEADERS} + ${PUBLIC_HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/sshsendfacility_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshremoteprocess_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshpacketparser_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshpacket_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshoutgoingpacket_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshkeyexchange_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshincomingpacket_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshexception_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshcryptofacility_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshconnection_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshchannelmanager_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshchannel_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshcapabilities_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshbotanconversions_p.h + ${CMAKE_CURRENT_LIST_DIR}/sftppacket_p.h + ${CMAKE_CURRENT_LIST_DIR}/sftpoutgoingpacket_p.h + ${CMAKE_CURRENT_LIST_DIR}/sftpoperation_p.h + ${CMAKE_CURRENT_LIST_DIR}/sftpincomingpacket_p.h + ${CMAKE_CURRENT_LIST_DIR}/sftpchannel_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshkeypasswordretriever_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshdirecttcpiptunnel_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshlogging_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshtcpipforwardserver_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshtcpiptunnel_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshforwardedtcpiptunnel_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshagent_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshx11channel_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshx11displayinfo_p.h + ${CMAKE_CURRENT_LIST_DIR}/sshx11inforetriever_p.h + ${CMAKE_CURRENT_LIST_DIR}/opensshkeyfilereader_p.h +) + +# qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/qssh.qrc) \ No newline at end of file diff --git a/client/3rd/QtSsh/src/ssh/qssh.pri b/client/3rd/QtSsh/src/ssh/qssh.pri index 657d0ff7..fbc20358 100644 --- a/client/3rd/QtSsh/src/ssh/qssh.pri +++ b/client/3rd/QtSsh/src/ssh/qssh.pri @@ -1,4 +1,5 @@ QT += gui network widgets +equals(QT_MAJOR_VERSION, 6): QT += core5compat INCLUDEPATH += $$PWD DEPENDPATH += $$PWD diff --git a/client/3rd/QtSsh/src/ssh/sshconnection.cpp b/client/3rd/QtSsh/src/ssh/sshconnection.cpp index e4403499..62e2b622 100644 --- a/client/3rd/QtSsh/src/ssh/sshconnection.cpp +++ b/client/3rd/QtSsh/src/ssh/sshconnection.cpp @@ -47,7 +47,7 @@ #include #include #include -#include +#include #include namespace QSsh { @@ -401,14 +401,14 @@ void SshConnectionPrivate::handleServerId() // "printable US-ASCII characters, with the exception of whitespace characters // and the minus sign" QString legalString = QLatin1String("[]!\"#$!&'()*+,./0-9:;<=>?@A-Z[\\\\^_`a-z{|}~]+"); - const QRegExp versionIdpattern(QString::fromLatin1("SSH-(%1)-%1(?: .+)?.*").arg(legalString)); - if (!versionIdpattern.exactMatch(QString::fromLatin1(m_serverId))) { + const QRegularExpression versionIdpattern(QString::fromLatin1("SSH-(%1)-%1(?: .+)?.*").arg(legalString)); + if (!versionIdpattern.match(QString::fromLatin1(m_serverId)).hasMatch()) { throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, "Identification string is invalid.", tr("Server Identification string \"%1\" is invalid.") .arg(QString::fromLatin1(m_serverId))); } - const QString serverProtoVersion = versionIdpattern.cap(1); + const QString serverProtoVersion = versionIdpattern.match(QString::fromLatin1(m_serverId)).captured(1); if (serverProtoVersion != QLatin1String("2.0") && serverProtoVersion != QLatin1String("1.99")) { throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, "Invalid protocol version.", diff --git a/client/3rd/QtSsh/src/ssh/sshpacket.cpp b/client/3rd/QtSsh/src/ssh/sshpacket.cpp index 94238ffd..06160f11 100644 --- a/client/3rd/QtSsh/src/ssh/sshpacket.cpp +++ b/client/3rd/QtSsh/src/ssh/sshpacket.cpp @@ -138,7 +138,7 @@ QByteArray AbstractSshPacket::generateMac(const SshAbstractCryptoFacility &crypt quint32 seqNr) const { const quint32 seqNrBe = qToBigEndian(seqNr); - QByteArray data(reinterpret_cast(&seqNrBe), sizeof seqNrBe); + QByteArray data(reinterpret_cast(&seqNrBe), static_cast(sizeof seqNrBe)); data += QByteArray(m_data.constData(), length() + 4); return crypt.generateMac(data, data.size()); } @@ -150,8 +150,9 @@ quint32 AbstractSshPacket::minPacketSize() const void AbstractSshPacket::setLengthField(QByteArray &data) { - const quint32 length = qToBigEndian(data.size() - 4); - data.replace(0, 4, reinterpret_cast(&length), 4); + const quint32 length = qToBigEndian(data.size() - 4); + data.replace(static_cast(0), static_cast(4), + reinterpret_cast(&length), static_cast(4)); } } // namespace Internal diff --git a/client/3rd/QtSsh/src/ssh/sshx11inforetriever.cpp b/client/3rd/QtSsh/src/ssh/sshx11inforetriever.cpp index de5505c9..5abfae4e 100644 --- a/client/3rd/QtSsh/src/ssh/sshx11inforetriever.cpp +++ b/client/3rd/QtSsh/src/ssh/sshx11inforetriever.cpp @@ -107,7 +107,7 @@ SshX11InfoRetriever::SshX11InfoRetriever(const QString &displayName, QObject *pa return; } if (dotIndex != -1) { - displayInfo.screen = m_displayName.midRef(dotIndex + 1).toInt(&ok); + displayInfo.screen = m_displayName.mid(dotIndex + 1).toInt(&ok); if (!ok) { emitFailure(tr("Invalid display name \"%1\"").arg(m_displayName)); return; diff --git a/client/3rd/SingleApplication/singleapplication.cmake b/client/3rd/SingleApplication/singleapplication.cmake new file mode 100644 index 00000000..78abfa8a --- /dev/null +++ b/client/3rd/SingleApplication/singleapplication.cmake @@ -0,0 +1,25 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +find_package(Qt6 REQUIRED COMPONENTS + Core Network +) +set(LIBS ${LIBS} Qt6::Core Qt6::Network) + + +set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/singleapplication.h + ${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.h +) + +set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/singleapplication.cpp + ${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.cpp +) + +if(WIN32) + if(MSVC) + set(LIBS ${LIBS} Advapi32.lib) + elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(LIBS ${LIBS} advapi32) + endif() +endif() diff --git a/client/3rd/SortFilterProxyModel b/client/3rd/SortFilterProxyModel new file mode 160000 index 00000000..f2881493 --- /dev/null +++ b/client/3rd/SortFilterProxyModel @@ -0,0 +1 @@ +Subproject commit f2881493e42bd7b7d5b7abe804dad084dd610b71 diff --git a/client/3rd/SortFilterProxyModel/.gitignore b/client/3rd/SortFilterProxyModel/.gitignore deleted file mode 100644 index 73d1dfdc..00000000 --- a/client/3rd/SortFilterProxyModel/.gitignore +++ /dev/null @@ -1,77 +0,0 @@ -# This file is used to ignore files which are generated -# ---------------------------------------------------------------------------- - -*~ -*.autosave -*.a -*.core -*.moc -*.o -*.obj -*.orig -*.rej -*.so -*.so.* -*_pch.h.cpp -*_resource.rc -*.qm -.#* -*.*# -core -!core/ -tags -.DS_Store -.directory -*.debug -Makefile* -*.prl -*.app -moc_*.cpp -ui_*.h -qrc_*.cpp -*.qmlc -Thumbs.db -*.res -*.rc -/.qmake.cache -/.qmake.stash - -# qtcreator generated files -*.pro.user* - -# qtcreator shadow builds -build-SortFilterProxyModel-* - -# xemacs temporary files -*.flc - -# Vim temporary files -.*.swp - -# Visual Studio generated files -*.ib_pdb_index -*.idb -*.ilk -*.pdb -*.sln -*.suo -*.vcproj -*vcproj.*.*.user -*.ncb -*.sdf -*.opensdf -*.vcxproj -*vcxproj.* - -# MinGW generated files -*.Debug -*.Release - -# Python byte code -*.pyc - -# Binaries -# -------- -*.dll -*.exe - diff --git a/client/3rd/SortFilterProxyModel/CMakeLists.txt b/client/3rd/SortFilterProxyModel/CMakeLists.txt deleted file mode 100644 index 3d3bf2bb..00000000 --- a/client/3rd/SortFilterProxyModel/CMakeLists.txt +++ /dev/null @@ -1,49 +0,0 @@ -cmake_minimum_required(VERSION 3.1) - -set(CMAKE_CXX_STANDARD 11) - -find_package(Qt5 REQUIRED - Core - Qml - ) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_INCLUDE_CURRENT_DIR ON) # This is to find generated *.moc and *.h files in build dir - -add_library(SortFilterProxyModel OBJECT - qqmlsortfilterproxymodel.cpp - filters/filter.cpp - filters/filtercontainer.cpp - filters/rolefilter.cpp - filters/valuefilter.cpp - filters/indexfilter.cpp - filters/regexpfilter.cpp - filters/rangefilter.cpp - filters/expressionfilter.cpp - filters/filtercontainerfilter.cpp - filters/anyoffilter.cpp - filters/alloffilter.cpp - filters/filtersqmltypes.cpp - sorters/sorter.cpp - sorters/sortercontainer.cpp - sorters/rolesorter.cpp - sorters/stringsorter.cpp - sorters/expressionsorter.cpp - sorters/sortersqmltypes.cpp - proxyroles/proxyrole.cpp - proxyroles/proxyrolecontainer.cpp - proxyroles/joinrole.cpp - proxyroles/switchrole.cpp - proxyroles/expressionrole.cpp - proxyroles/proxyrolesqmltypes.cpp - proxyroles/singlerole.cpp - proxyroles/regexprole.cpp - sorters/filtersorter.cpp - proxyroles/filterrole.cpp - ) - -target_include_directories(SortFilterProxyModel PUBLIC - ${CMAKE_CURRENT_LIST_DIR} - $ - $ - ) diff --git a/client/3rd/SortFilterProxyModel/LICENSE b/client/3rd/SortFilterProxyModel/LICENSE deleted file mode 100644 index c40ee9d4..00000000 --- a/client/3rd/SortFilterProxyModel/LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -The MIT License (MIT) -Copyright (c) 2016 Pierre-Yves Siret - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/client/3rd/SortFilterProxyModel/README.md b/client/3rd/SortFilterProxyModel/README.md deleted file mode 100644 index 2a0cf7c9..00000000 --- a/client/3rd/SortFilterProxyModel/README.md +++ /dev/null @@ -1,126 +0,0 @@ -SortFilterProxyModel -==================== - -SortFilterProxyModel is an implementation of `QSortFilterProxyModel` conveniently exposed for QML. - -Install -------- -##### With [qpm](https://qpm.io) : -1. `qpm install fr.grecko.sortfilterproxymodel` -2. add `include(vendor/vendor.pri)` in your .pro if it is not already done -3. `import SortFilterProxyModel 0.2` to use this library in your QML files - -##### Without qpm : -1. clone or download this repository -2. * `qmake` add `include (/SortFilterProxyModel.pri)` in your `.pro` - * `CMake` add $ to the sources of your executable target in your cmake project -3. `import SortFilterProxyModel 0.2` to use this library in your QML files - -Sample Usage ------------- - -- You can do simple filtering and sorting with SortFilterProxyModel: -```qml -import QtQuick 2.2 -import QtQuick.Controls 1.2 -import SortFilterProxyModel 0.2 - -ApplicationWindow { - visible: true - width: 640 - height: 480 - - ListModel { - id: personModel - ListElement { - firstName: "Erwan" - lastName: "Castex" - favorite: true - } - // ... - } - - TextField { - id: textField - anchors { top: parent.top; left: parent.left; right: parent.right } - height: implicitHeight - } - - SortFilterProxyModel { - id: personProxyModel - sourceModel: personModel - filters: RegExpFilter { - roleName: "lastName" - pattern: textField.text - caseSensitivity: Qt.CaseInsensitive - } - sorters: StringSorter { roleName: "firstName" } - } - - ListView { - anchors { top: textField.bottom; bottom: parent.bottom; left: parent.left; right: parent.right } - model: personProxyModel - delegate: Text { text: model.firstName + " " + model.lastName} - } -} -``` -Here the `ListView` will only show elements that contains the content of the `TextField` in their `lastName` role. - -- But you can also achieve more complex filtering or sorting with multiple `filters` and `sorters`: -```qml - SortFilterProxyModel { - id: personProxyModel - sourceModel: personModel - filters: [ - ValueFilter { - enabled: onlyShowFavoritesCheckbox.checked - roleName: "favorite" - value: true - }, - AnyOf { - RegExpFilter { - roleName: "lastName" - pattern: textField.text - caseSensitivity: Qt.CaseInsensitive - } - RegExpFilter { - roleName: "firstName" - pattern: textField.text - caseSensitivity: Qt.CaseInsensitive - } - } - ] - sorters: [ - RoleSorter { roleName: "favorite"; sortOrder: Qt.DescendingOrder }, - StringSorter { roleName: "firstName" }, - StringSorter { roleName: "lastName" } - ] - } - - CheckBox { - id:onlyShowFavoritesCheckbox - } -``` -This will show in the corresponding `ListView` only the elements where the `firstName` or the `lastName` match the text entered in the `textField`, and if the `onlyShowFavoritesCheckbox` is checked it will aditionnally filter the elements where `favorite` is `true`. -The favorited elements will be shown first and all the elements are sorted by `firstName` and then `lastName`. - -Showcase Application --------------------- -You can find an application showcasing this library here: https://github.com/oKcerG/SFPMShowcase - -License -------- -This library is licensed under the MIT License. - -Documentation -------------- -This component is a subclass of [`QSortFilterProxyModel`](http://doc.qt.io/qt-5/qsortfilterproxymodel.html), to use it, you need to set the `sourceModel` property to a [`QAbstractItemModel*`](http://doc.qt.io/qt-5/qabstractitemmodel.html) with correct role names. -This means you can use it with custom c++ models or `ListModel`, but not with JavaScript models like arrays, integers or object instances. - -The complete documentation reference is available here: https://okcerg.github.io/SortFilterProxyModel/ - -Contributing ------------- -Don't hesitate to open an issue about a suggestion, a bug, a lack of clarity in the documentation, etc. - -Pull requests are also welcome, if it's a important change you should open an issue first though. diff --git a/client/3rd/SortFilterProxyModel/SortFilterProxyModel.pri b/client/3rd/SortFilterProxyModel/SortFilterProxyModel.pri deleted file mode 100644 index 24a01c4c..00000000 --- a/client/3rd/SortFilterProxyModel/SortFilterProxyModel.pri +++ /dev/null @@ -1,58 +0,0 @@ -INCLUDEPATH += $$PWD - -HEADERS += $$PWD/qqmlsortfilterproxymodel.h \ - $$PWD/filters/filter.h \ - $$PWD/filters/filtercontainer.h \ - $$PWD/filters/rolefilter.h \ - $$PWD/filters/valuefilter.h \ - $$PWD/filters/indexfilter.h \ - $$PWD/filters/regexpfilter.h \ - $$PWD/filters/rangefilter.h \ - $$PWD/filters/expressionfilter.h \ - $$PWD/filters/filtercontainerfilter.h \ - $$PWD/filters/anyoffilter.h \ - $$PWD/filters/alloffilter.h \ - $$PWD/sorters/sorter.h \ - $$PWD/sorters/sortercontainer.h \ - $$PWD/sorters/rolesorter.h \ - $$PWD/sorters/stringsorter.h \ - $$PWD/sorters/expressionsorter.h \ - $$PWD/proxyroles/proxyrole.h \ - $$PWD/proxyroles/proxyrolecontainer.h \ - $$PWD/proxyroles/joinrole.h \ - $$PWD/proxyroles/switchrole.h \ - $$PWD/proxyroles/expressionrole.h \ - $$PWD/proxyroles/singlerole.h \ - $$PWD/proxyroles/regexprole.h \ - $$PWD/sorters/filtersorter.h \ - $$PWD/proxyroles/filterrole.h - -SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \ - $$PWD/filters/filter.cpp \ - $$PWD/filters/filtercontainer.cpp \ - $$PWD/filters/rolefilter.cpp \ - $$PWD/filters/valuefilter.cpp \ - $$PWD/filters/indexfilter.cpp \ - $$PWD/filters/regexpfilter.cpp \ - $$PWD/filters/rangefilter.cpp \ - $$PWD/filters/expressionfilter.cpp \ - $$PWD/filters/filtercontainerfilter.cpp \ - $$PWD/filters/anyoffilter.cpp \ - $$PWD/filters/alloffilter.cpp \ - $$PWD/filters/filtersqmltypes.cpp \ - $$PWD/sorters/sorter.cpp \ - $$PWD/sorters/sortercontainer.cpp \ - $$PWD/sorters/rolesorter.cpp \ - $$PWD/sorters/stringsorter.cpp \ - $$PWD/sorters/expressionsorter.cpp \ - $$PWD/sorters/sortersqmltypes.cpp \ - $$PWD/proxyroles/proxyrole.cpp \ - $$PWD/proxyroles/proxyrolecontainer.cpp \ - $$PWD/proxyroles/joinrole.cpp \ - $$PWD/proxyroles/switchrole.cpp \ - $$PWD/proxyroles/expressionrole.cpp \ - $$PWD/proxyroles/proxyrolesqmltypes.cpp \ - $$PWD/proxyroles/singlerole.cpp \ - $$PWD/proxyroles/regexprole.cpp \ - $$PWD/sorters/filtersorter.cpp \ - $$PWD/proxyroles/filterrole.cpp diff --git a/client/3rd/SortFilterProxyModel/filters/alloffilter.cpp b/client/3rd/SortFilterProxyModel/filters/alloffilter.cpp deleted file mode 100644 index 9b3d2d72..00000000 --- a/client/3rd/SortFilterProxyModel/filters/alloffilter.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "alloffilter.h" - -namespace qqsfpm { - -/*! - \qmltype AllOf - \inherits Filter - \inqmlmodule SortFilterProxyModel - \ingroup Filters - \ingroup FilterContainer - \brief Filter container accepting rows accepted by all its child filters. - - The AllOf type is a \l Filter container that accepts rows if all of its contained (and enabled) filters accept them, or if it has no filter. - - Using it as a top level filter has the same effect as putting all its child filters as top level filters. It can however be usefull to use an AllOf filter when nested in an AnyOf filter. - \sa FilterContainer -*/ -bool AllOfFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const -{ - //return true if all filters return false, or if there is no filter. - return std::all_of(m_filters.begin(), m_filters.end(), - [&sourceIndex, &proxyModel] (Filter* filter) { - return filter->filterAcceptsRow(sourceIndex, proxyModel); - } - ); -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/alloffilter.h b/client/3rd/SortFilterProxyModel/filters/alloffilter.h deleted file mode 100644 index 14b5baec..00000000 --- a/client/3rd/SortFilterProxyModel/filters/alloffilter.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef ALLOFFILTER_H -#define ALLOFFILTER_H - -#include "filtercontainerfilter.h" - -namespace qqsfpm { - -class AllOfFilter : public FilterContainerFilter { - Q_OBJECT - -public: - using FilterContainerFilter::FilterContainerFilter; - -protected: - bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override; -}; - -} - -#endif // ALLOFFILTER_H diff --git a/client/3rd/SortFilterProxyModel/filters/anyoffilter.cpp b/client/3rd/SortFilterProxyModel/filters/anyoffilter.cpp deleted file mode 100644 index 4cfe9b80..00000000 --- a/client/3rd/SortFilterProxyModel/filters/anyoffilter.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "anyoffilter.h" - -namespace qqsfpm { - -/*! - \qmltype AnyOf - \inherits Filter - \inqmlmodule SortFilterProxyModel - \ingroup Filters - \ingroup FilterContainer - \brief Filter container accepting rows accepted by at least one of its child filters. - - The AnyOf type is a \l Filter container that accepts rows if any of its contained (and enabled) filters accept them. - - In the following example, only the rows where the \c firstName role or the \c lastName role match the text entered in the \c nameTextField will be accepted : - \code - TextField { - id: nameTextField - } - - SortFilterProxyModel { - sourceModel: contactModel - filters: AnyOf { - RegExpFilter { - roleName: "lastName" - pattern: nameTextField.text - caseSensitivity: Qt.CaseInsensitive - } - RegExpFilter { - roleName: "firstName" - pattern: nameTextField.text - caseSensitivity: Qt.CaseInsensitive - } - } - } - \endcode - \sa FilterContainer -*/ -bool AnyOfFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const -{ - //return true if any of the enabled filters return true - return std::any_of(m_filters.begin(), m_filters.end(), - [&sourceIndex, &proxyModel] (Filter* filter) { - return filter->enabled() && filter->filterAcceptsRow(sourceIndex, proxyModel); - } - ); -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/anyoffilter.h b/client/3rd/SortFilterProxyModel/filters/anyoffilter.h deleted file mode 100644 index b3bb6f53..00000000 --- a/client/3rd/SortFilterProxyModel/filters/anyoffilter.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef ANYOFFILTER_H -#define ANYOFFILTER_H - -#include "filtercontainerfilter.h" - -namespace qqsfpm { - -class AnyOfFilter : public FilterContainerFilter { - Q_OBJECT - -public: - using FilterContainerFilter::FilterContainerFilter; - -protected: - bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override; -}; - -} - -#endif // ANYOFFILTER_H diff --git a/client/3rd/SortFilterProxyModel/filters/expressionfilter.cpp b/client/3rd/SortFilterProxyModel/filters/expressionfilter.cpp deleted file mode 100644 index f3673ce1..00000000 --- a/client/3rd/SortFilterProxyModel/filters/expressionfilter.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "expressionfilter.h" -#include "qqmlsortfilterproxymodel.h" -#include - -namespace qqsfpm { - -/*! - \qmltype ExpressionFilter - \inherits Filter - \inqmlmodule SortFilterProxyModel - \ingroup Filters - \brief Filters row with a custom filtering. - - An ExpressionFilter is a \l Filter allowing to implement custom filtering based on a javascript expression. -*/ - -/*! - \qmlproperty expression ExpressionFilter::expression - - An expression to implement custom filtering, it must evaluate to a boolean. - It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding} except it will be evaluated for each of the source model's rows. - Rows that have their expression evaluating to \c true will be accepted by the model. - Data for each row is exposed like for a delegate of a QML View. - - This expression is reevaluated for a row every time its model data changes. - When an external property (not \c index or in \c model) the expression depends on changes, the expression is reevaluated for every row of the source model. - To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine. - This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes. - - A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression. -*/ -const QQmlScriptString& ExpressionFilter::expression() const -{ - return m_scriptString; -} - -void ExpressionFilter::setExpression(const QQmlScriptString& scriptString) -{ - if (m_scriptString == scriptString) - return; - - m_scriptString = scriptString; - updateExpression(); - - Q_EMIT expressionChanged(); - invalidate(); -} - -void ExpressionFilter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) -{ - updateContext(proxyModel); -} - -bool ExpressionFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const -{ - if (!m_scriptString.isEmpty()) { - QVariantMap modelMap; - QHash roles = proxyModel.roleNames(); - - QQmlContext context(qmlContext(this)); - auto addToContext = [&] (const QString &name, const QVariant& value) { - context.setContextProperty(name, value); - modelMap.insert(name, value); - }; - - for (auto it = roles.cbegin(); it != roles.cend(); ++it) - addToContext(it.value(), proxyModel.sourceData(sourceIndex, it.key())); - addToContext("index", sourceIndex.row()); - - context.setContextProperty("model", modelMap); - - QQmlExpression expression(m_scriptString, &context); - QVariant variantResult = expression.evaluate(); - - if (expression.hasError()) { - qWarning() << expression.error(); - return true; - } - if (variantResult.canConvert()) { - return variantResult.toBool(); - } else { - qWarning("%s:%i:%i : Can't convert result to bool", - expression.sourceFile().toUtf8().data(), - expression.lineNumber(), - expression.columnNumber()); - return true; - } - } - return true; -} - -void ExpressionFilter::updateContext(const QQmlSortFilterProxyModel& proxyModel) -{ - delete m_context; - m_context = new QQmlContext(qmlContext(this), this); - // what about roles changes ? - QVariantMap modelMap; - - auto addToContext = [&] (const QString &name, const QVariant& value) { - m_context->setContextProperty(name, value); - modelMap.insert(name, value); - }; - - for (const QByteArray& roleName : proxyModel.roleNames().values()) - addToContext(roleName, QVariant()); - - addToContext("index", -1); - - m_context->setContextProperty("model", modelMap); - updateExpression(); -} - -void ExpressionFilter::updateExpression() -{ - if (!m_context) - return; - - delete m_expression; - m_expression = new QQmlExpression(m_scriptString, m_context, 0, this); - connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionFilter::invalidate); - m_expression->setNotifyOnValueChanged(true); - m_expression->evaluate(); -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/expressionfilter.h b/client/3rd/SortFilterProxyModel/filters/expressionfilter.h deleted file mode 100644 index fe7da4aa..00000000 --- a/client/3rd/SortFilterProxyModel/filters/expressionfilter.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef EXPRESSIONFILTER_H -#define EXPRESSIONFILTER_H - -#include "filter.h" -#include - -class QQmlExpression; - -namespace qqsfpm { - -class ExpressionFilter : public Filter -{ - Q_OBJECT - Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged) - -public: - using Filter::Filter; - - const QQmlScriptString& expression() const; - void setExpression(const QQmlScriptString& scriptString); - - void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override; - -protected: - bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override; - -Q_SIGNALS: - void expressionChanged(); - -private: - void updateContext(const QQmlSortFilterProxyModel& proxyModel); - void updateExpression(); - - QQmlScriptString m_scriptString; - QQmlExpression* m_expression = nullptr; - QQmlContext* m_context = nullptr; -}; - -} - -#endif // EXPRESSIONFILTER_H diff --git a/client/3rd/SortFilterProxyModel/filters/filter.cpp b/client/3rd/SortFilterProxyModel/filters/filter.cpp deleted file mode 100644 index 1e46da7e..00000000 --- a/client/3rd/SortFilterProxyModel/filters/filter.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "filter.h" -#include "qqmlsortfilterproxymodel.h" - -namespace qqsfpm { - -/*! - \qmltype Filter - \qmlabstract - \inqmlmodule SortFilterProxyModel - \ingroup Filters - \brief Base type for the \l SortFilterProxyModel filters. - - The Filter type cannot be used directly in a QML file. - It exists to provide a set of common properties and methods, - available across all the other filter types that inherit from it. - Attempting to use the Filter type directly will result in an error. -*/ - -Filter::Filter(QObject *parent) : QObject(parent) -{ -} - -/*! - \qmlproperty bool Filter::enabled - - This property holds whether the filter is enabled. - A disabled filter will accept every rows unconditionally (even if it's inverted). - - By default, filters are enabled. -*/ -bool Filter::enabled() const -{ - return m_enabled; -} - -void Filter::setEnabled(bool enabled) -{ - if (m_enabled == enabled) - return; - - m_enabled = enabled; - Q_EMIT enabledChanged(); - Q_EMIT invalidated(); -} - -/*! - \qmlproperty bool Filter::inverted - - This property holds whether the filter is inverted. - When a filter is inverted, a row normally accepted would be rejected, and vice-versa. - - By default, filters are not inverted. -*/ -bool Filter::inverted() const -{ - return m_inverted; -} - -void Filter::setInverted(bool inverted) -{ - if (m_inverted == inverted) - return; - - m_inverted = inverted; - Q_EMIT invertedChanged(); - invalidate(); -} - -bool Filter::filterAcceptsRow(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const -{ - return !m_enabled || filterRow(sourceIndex, proxyModel) ^ m_inverted; -} - -void Filter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) -{ - Q_UNUSED(proxyModel) -} - -void Filter::invalidate() -{ - if (m_enabled) - Q_EMIT invalidated(); -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/filter.h b/client/3rd/SortFilterProxyModel/filters/filter.h deleted file mode 100644 index c88dec34..00000000 --- a/client/3rd/SortFilterProxyModel/filters/filter.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef FILTER_H -#define FILTER_H - -#include - -namespace qqsfpm { - -class QQmlSortFilterProxyModel; - -class Filter : public QObject -{ - Q_OBJECT - Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) - Q_PROPERTY(bool inverted READ inverted WRITE setInverted NOTIFY invertedChanged) - -public: - explicit Filter(QObject *parent = nullptr); - virtual ~Filter() = default; - - bool enabled() const; - void setEnabled(bool enabled); - - bool inverted() const; - void setInverted(bool inverted); - - bool filterAcceptsRow(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const; - - virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel); - -Q_SIGNALS: - void enabledChanged(); - void invertedChanged(); - void invalidated(); - -protected: - virtual bool filterRow(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const = 0; - void invalidate(); - -private: - bool m_enabled = true; - bool m_inverted = false; -}; - -} - -#endif // FILTER_H diff --git a/client/3rd/SortFilterProxyModel/filters/filtercontainer.cpp b/client/3rd/SortFilterProxyModel/filters/filtercontainer.cpp deleted file mode 100644 index 5bba02d9..00000000 --- a/client/3rd/SortFilterProxyModel/filters/filtercontainer.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "filtercontainer.h" -#include "filter.h" -#include - -namespace qqsfpm { - -/*! - \qmltype FilterContainer - \qmlabstract - \inqmlmodule SortFilterProxyModel - \ingroup FilterAttached - \brief Abstract interface for types containing \l {Filter}{Filters}. - - \section2 Types implementing this interface: - \annotatedlist FilterContainer -*/ - -QList FilterContainer::filters() const -{ - return m_filters; -} - -void FilterContainer::appendFilter(Filter* filter) -{ - m_filters.append(filter); - onFilterAppended(filter); -} - -void FilterContainer::removeFilter(Filter* filter) -{ - m_filters.removeOne(filter); - onFilterRemoved(filter); -} - -void FilterContainer::clearFilters() -{ - m_filters.clear(); - onFiltersCleared(); -} - -QQmlListProperty FilterContainer::filtersListProperty() -{ - return QQmlListProperty(reinterpret_cast(this), &m_filters, - &FilterContainer::append_filter, - &FilterContainer::count_filter, - &FilterContainer::at_filter, - &FilterContainer::clear_filters); -} - -void FilterContainer::append_filter(QQmlListProperty* list, Filter* filter) -{ - if (!filter) - return; - - FilterContainer* that = reinterpret_cast(list->object); - that->appendFilter(filter); -} - -int FilterContainer::count_filter(QQmlListProperty* list) -{ - QList* filters = static_cast*>(list->data); - return filters->count(); -} - -Filter* FilterContainer::at_filter(QQmlListProperty* list, int index) -{ - QList* filters = static_cast*>(list->data); - return filters->at(index); -} - -void FilterContainer::clear_filters(QQmlListProperty *list) -{ - FilterContainer* that = reinterpret_cast(list->object); - that->clearFilters(); -} - -FilterContainerAttached::FilterContainerAttached(QObject* object) : QObject(object), - m_filter(qobject_cast(object)) -{ - if (!m_filter) - qmlWarning(object) << "FilterContainer must be attached to a Filter"; -} - -FilterContainerAttached::~FilterContainerAttached() -{ - if (m_filter && m_container) { - FilterContainer* container = qobject_cast(m_container.data()); - container->removeFilter(m_filter); - } -} - -/*! - \qmlattachedproperty bool FilterContainer::container - This attached property allows you to include in a \l FilterContainer a \l Filter that - has been instantiated outside of the \l FilterContainer, for example in an Instantiator. -*/ -QObject* FilterContainerAttached::container() const -{ - return m_container; -} - -void FilterContainerAttached::setContainer(QObject* object) -{ - if (m_container == object) - return; - - FilterContainer* container = qobject_cast(object); - if (object && !container) - qmlWarning(parent()) << "container must inherits from FilterContainer, " << object->metaObject()->className() << " provided"; - - if (m_container && m_filter) - qobject_cast(m_container.data())->removeFilter(m_filter); - - m_container = container ? object : nullptr; - if (container && m_filter) - container->appendFilter(m_filter); - - Q_EMIT containerChanged(); -} - -FilterContainerAttached* FilterContainerAttached::qmlAttachedProperties(QObject* object) -{ - return new FilterContainerAttached(object); -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/filtercontainer.h b/client/3rd/SortFilterProxyModel/filters/filtercontainer.h deleted file mode 100644 index 4fc06f37..00000000 --- a/client/3rd/SortFilterProxyModel/filters/filtercontainer.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef FILTERCONTAINER_H -#define FILTERCONTAINER_H - -#include -#include -#include -#include - -namespace qqsfpm { - -class Filter; -class QQmlSortFilterProxyModel; - -class FilterContainer { -public: - virtual ~FilterContainer() = default; - - QList filters() const; - void appendFilter(Filter* filter); - void removeFilter(Filter* filter); - void clearFilters(); - - QQmlListProperty filtersListProperty(); - -protected: - QList m_filters; - -private: - virtual void onFilterAppended(Filter* filter) = 0; - virtual void onFilterRemoved(Filter* filter) = 0; - virtual void onFiltersCleared() = 0; - - static void append_filter(QQmlListProperty* list, Filter* filter); - static int count_filter(QQmlListProperty* list); - static Filter* at_filter(QQmlListProperty* list, int index); - static void clear_filters(QQmlListProperty* list); -}; - -class FilterContainerAttached : public QObject -{ - Q_OBJECT - Q_PROPERTY(QObject* container READ container WRITE setContainer NOTIFY containerChanged) - -public: - FilterContainerAttached(QObject* object); - ~FilterContainerAttached(); - - QObject* container() const; - void setContainer(QObject* object); - - static FilterContainerAttached* qmlAttachedProperties(QObject* object); - -Q_SIGNALS: - void containerChanged(); - -private: - QPointer m_container = nullptr; - Filter* m_filter = nullptr; -}; - -} - -#define FilterContainer_iid "fr.grecko.SortFilterProxyModel.FilterContainer" -Q_DECLARE_INTERFACE(qqsfpm::FilterContainer, FilterContainer_iid) - -QML_DECLARE_TYPEINFO(qqsfpm::FilterContainerAttached, QML_HAS_ATTACHED_PROPERTIES) - -#endif // FILTERCONTAINER_H diff --git a/client/3rd/SortFilterProxyModel/filters/filtercontainerfilter.cpp b/client/3rd/SortFilterProxyModel/filters/filtercontainerfilter.cpp deleted file mode 100644 index a847e977..00000000 --- a/client/3rd/SortFilterProxyModel/filters/filtercontainerfilter.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "filtercontainerfilter.h" - -namespace qqsfpm { - -void FilterContainerFilter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) -{ - for (Filter* filter : m_filters) - filter->proxyModelCompleted(proxyModel); -} - -void FilterContainerFilter::onFilterAppended(Filter* filter) -{ - connect(filter, &Filter::invalidated, this, &FilterContainerFilter::invalidate); - invalidate(); -} - -void FilterContainerFilter::onFilterRemoved(Filter* filter) -{ - Q_UNUSED(filter) - invalidate(); -} - -void qqsfpm::FilterContainerFilter::onFiltersCleared() -{ - invalidate(); -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/filtercontainerfilter.h b/client/3rd/SortFilterProxyModel/filters/filtercontainerfilter.h deleted file mode 100644 index 1aa15fb6..00000000 --- a/client/3rd/SortFilterProxyModel/filters/filtercontainerfilter.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef FILTERCONTAINERFILTER_H -#define FILTERCONTAINERFILTER_H - -#include "filter.h" -#include "filtercontainer.h" - -namespace qqsfpm { - -class FilterContainerFilter : public Filter, public FilterContainer { - Q_OBJECT - Q_INTERFACES(qqsfpm::FilterContainer) - Q_PROPERTY(QQmlListProperty filters READ filtersListProperty NOTIFY filtersChanged) - Q_CLASSINFO("DefaultProperty", "filters") - -public: - using Filter::Filter; - - void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override; - -Q_SIGNALS: - void filtersChanged(); - -private: - void onFilterAppended(Filter* filter) override; - void onFilterRemoved(Filter* filter) override; - void onFiltersCleared() override; -}; - -} - -#endif // FILTERCONTAINERFILTER_H diff --git a/client/3rd/SortFilterProxyModel/filters/filtersqmltypes.cpp b/client/3rd/SortFilterProxyModel/filters/filtersqmltypes.cpp deleted file mode 100644 index 6704472b..00000000 --- a/client/3rd/SortFilterProxyModel/filters/filtersqmltypes.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "filter.h" -#include "valuefilter.h" -#include "indexfilter.h" -#include "regexpfilter.h" -#include "rangefilter.h" -#include "expressionfilter.h" -#include "anyoffilter.h" -#include "alloffilter.h" -#include -#include - -namespace qqsfpm { - -void registerFiltersTypes() { - qmlRegisterUncreatableType("SortFilterProxyModel", 0, 2, "Filter", "Filter is an abstract class"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "ValueFilter"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "IndexFilter"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "RegExpFilter"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "RangeFilter"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "ExpressionFilter"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "AnyOf"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "AllOf"); - qmlRegisterUncreatableType("SortFilterProxyModel", 0, 2, "FilterContainer", "FilterContainer can only be used as an attaching type"); -} - -Q_COREAPP_STARTUP_FUNCTION(registerFiltersTypes) - -} diff --git a/client/3rd/SortFilterProxyModel/filters/indexfilter.cpp b/client/3rd/SortFilterProxyModel/filters/indexfilter.cpp deleted file mode 100644 index c1e4b243..00000000 --- a/client/3rd/SortFilterProxyModel/filters/indexfilter.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "indexfilter.h" -#include "qqmlsortfilterproxymodel.h" - -namespace qqsfpm { - -/*! - \qmltype IndexFilter - \inherits Filter - \inqmlmodule SortFilterProxyModel - \ingroup Filters - \brief Filters rows based on their source index. - - An IndexFilter is a filter allowing contents to be filtered based on their source model index. - - In the following example, only the first row of the source model will be accepted: - \code - SortFilterProxyModel { - sourceModel: contactModel - filters: IndexFilter { - maximumIndex: 0 - } - } - \endcode -*/ - -/*! - \qmlproperty int IndexFilter::minimumIndex - - This property holds the minimumIndex of the filter. - Rows with a source index lower than \c minimumIndex will be rejected. - - If \c minimumIndex is negative, it is counted from the end of the source model, meaning that : - \code - minimumIndex: -1 - \endcode - is equivalent to : - \code - minimumIndex: sourceModel.count - 1 - \endcode - By default, no value is set. -*/ -const QVariant& IndexFilter::minimumIndex() const -{ - return m_minimumIndex; -} - -void IndexFilter::setMinimumIndex(const QVariant& minimumIndex) -{ - if (m_minimumIndex == minimumIndex) - return; - - m_minimumIndex = minimumIndex; - Q_EMIT minimumIndexChanged(); - invalidate(); -} - -/*! - \qmlproperty int IndexFilter::maximumIndex - - This property holds the maximumIndex of the filter. - Rows with a source index higher than \c maximumIndex will be rejected. - - If \c maximumIndex is negative, it is counted from the end of the source model, meaning that: - \code - maximumIndex: -1 - \endcode - is equivalent to : - \code - maximumIndex: sourceModel.count - 1 - \endcode - By default, no value is set. -*/ -const QVariant& IndexFilter::maximumIndex() const -{ - return m_maximumIndex; -} - -void IndexFilter::setMaximumIndex(const QVariant& maximumIndex) -{ - if (m_maximumIndex == maximumIndex) - return; - - m_maximumIndex = maximumIndex; - Q_EMIT maximumIndexChanged(); - invalidate(); -} - -bool IndexFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const -{ - int sourceRowCount = proxyModel.sourceModel()->rowCount(); - int sourceRow = sourceIndex.row(); - - bool minimumIsValid; - int minimum = m_minimumIndex.toInt(&minimumIsValid); - if (minimumIsValid) { - int actualMinimum = minimum < 0 ? sourceRowCount + minimum : minimum; - if (sourceRow < actualMinimum) - return false; - } - - bool maximumIsValid; - int maximum = m_maximumIndex.toInt(&maximumIsValid); - if (maximumIsValid) { - int actualMaximum = maximum < 0 ? sourceRowCount + maximum : maximum; - if (sourceRow > actualMaximum) - return false; - } - - return true; -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/indexfilter.h b/client/3rd/SortFilterProxyModel/filters/indexfilter.h deleted file mode 100644 index d693a9bf..00000000 --- a/client/3rd/SortFilterProxyModel/filters/indexfilter.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef INDEXFILTER_H -#define INDEXFILTER_H - -#include "filter.h" -#include - -namespace qqsfpm { - -class IndexFilter: public Filter { - Q_OBJECT - Q_PROPERTY(QVariant minimumIndex READ minimumIndex WRITE setMinimumIndex NOTIFY minimumIndexChanged) - Q_PROPERTY(QVariant maximumIndex READ maximumIndex WRITE setMaximumIndex NOTIFY maximumIndexChanged) - -public: - using Filter::Filter; - - const QVariant& minimumIndex() const; - void setMinimumIndex(const QVariant& minimumIndex); - - const QVariant& maximumIndex() const; - void setMaximumIndex(const QVariant& maximumIndex); - -protected: - bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override; - -Q_SIGNALS: - void minimumIndexChanged(); - void maximumIndexChanged(); - -private: - QVariant m_minimumIndex; - QVariant m_maximumIndex; -}; - -} - -#endif // INDEXFILTER_H diff --git a/client/3rd/SortFilterProxyModel/filters/rangefilter.cpp b/client/3rd/SortFilterProxyModel/filters/rangefilter.cpp deleted file mode 100644 index 2a6fde2c..00000000 --- a/client/3rd/SortFilterProxyModel/filters/rangefilter.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#include "rangefilter.h" - -namespace qqsfpm { - -/*! - \qmltype RangeFilter - \inherits RoleFilter - \inqmlmodule SortFilterProxyModel - \ingroup Filters - \brief Filters rows between boundary values. - - A RangeFilter is a \l RoleFilter that accepts rows if their data is between the filter's minimum and maximum value. - - In the following example, only rows with their \c price role set to a value between the tow boundary of the slider will be accepted : - \code - RangeSlider { - id: priceRangeSlider - } - - SortFilterProxyModel { - sourceModel: priceModel - filters: RangeFilter { - roleName: "price" - minimumValue: priceRangeSlider.first.value - maximumValue: priceRangeSlider.second.value - } - } - \endcode -*/ - -/*! - \qmlproperty int RangeFilter::minimumValue - - This property holds the minimumValue of the filter. - Rows with a value lower than \c minimumValue will be rejected. - - By default, no value is set. - - \sa minimumInclusive -*/ -QVariant RangeFilter::minimumValue() const -{ - return m_minimumValue; -} - -void RangeFilter::setMinimumValue(QVariant minimumValue) -{ - if (m_minimumValue == minimumValue) - return; - - m_minimumValue = minimumValue; - Q_EMIT minimumValueChanged(); - invalidate(); -} - -/*! - \qmlproperty int RangeFilter::minimumInclusive - - This property holds whether the \l minimumValue is inclusive. - - By default, the \l minimumValue is inclusive. - - \sa minimumValue -*/ -bool RangeFilter::minimumInclusive() const -{ - return m_minimumInclusive; -} - -void RangeFilter::setMinimumInclusive(bool minimumInclusive) -{ - if (m_minimumInclusive == minimumInclusive) - return; - - m_minimumInclusive = minimumInclusive; - Q_EMIT minimumInclusiveChanged(); - invalidate(); -} - -/*! - \qmlproperty int RangeFilter::maximumValue - - This property holds the maximumValue of the filter. - Rows with a value higher than \c maximumValue will be rejected. - - By default, no value is set. - - \sa maximumInclusive -*/ -QVariant RangeFilter::maximumValue() const -{ - return m_maximumValue; -} - -void RangeFilter::setMaximumValue(QVariant maximumValue) -{ - if (m_maximumValue == maximumValue) - return; - - m_maximumValue = maximumValue; - Q_EMIT maximumValueChanged(); - invalidate(); -} - -/*! - \qmlproperty int RangeFilter::maximumInclusive - - This property holds whether the \l minimumValue is inclusive. - - By default, the \l minimumValue is inclusive. - - \sa minimumValue -*/ -bool RangeFilter::maximumInclusive() const -{ - return m_maximumInclusive; -} - -void RangeFilter::setMaximumInclusive(bool maximumInclusive) -{ - if (m_maximumInclusive == maximumInclusive) - return; - - m_maximumInclusive = maximumInclusive; - Q_EMIT maximumInclusiveChanged(); - invalidate(); -} - -bool RangeFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const -{ - QVariant value = sourceData(sourceIndex, proxyModel); - bool lessThanMin = m_minimumValue.isValid() && - (m_minimumInclusive ? value < m_minimumValue : value <= m_minimumValue); - bool moreThanMax = m_maximumValue.isValid() && - (m_maximumInclusive ? value > m_maximumValue : value >= m_maximumValue); - return !(lessThanMin || moreThanMax); -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/rangefilter.h b/client/3rd/SortFilterProxyModel/filters/rangefilter.h deleted file mode 100644 index 2de2f8ca..00000000 --- a/client/3rd/SortFilterProxyModel/filters/rangefilter.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef RANGEFILTER_H -#define RANGEFILTER_H - -#include "rolefilter.h" -#include - -namespace qqsfpm { - -class RangeFilter : public RoleFilter -{ - Q_OBJECT - Q_PROPERTY(QVariant minimumValue READ minimumValue WRITE setMinimumValue NOTIFY minimumValueChanged) - Q_PROPERTY(bool minimumInclusive READ minimumInclusive WRITE setMinimumInclusive NOTIFY minimumInclusiveChanged) - Q_PROPERTY(QVariant maximumValue READ maximumValue WRITE setMaximumValue NOTIFY maximumValueChanged) - Q_PROPERTY(bool maximumInclusive READ maximumInclusive WRITE setMaximumInclusive NOTIFY maximumInclusiveChanged) - -public: - using RoleFilter::RoleFilter; - - QVariant minimumValue() const; - void setMinimumValue(QVariant minimumValue); - bool minimumInclusive() const; - void setMinimumInclusive(bool minimumInclusive); - - QVariant maximumValue() const; - void setMaximumValue(QVariant maximumValue); - bool maximumInclusive() const; - void setMaximumInclusive(bool maximumInclusive); - -protected: - bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override; - -Q_SIGNALS: - void minimumValueChanged(); - void minimumInclusiveChanged(); - void maximumValueChanged(); - void maximumInclusiveChanged(); - -private: - QVariant m_minimumValue; - bool m_minimumInclusive = true; - QVariant m_maximumValue; - bool m_maximumInclusive = true; -}; - -} - -#endif // RANGEFILTER_H diff --git a/client/3rd/SortFilterProxyModel/filters/regexpfilter.cpp b/client/3rd/SortFilterProxyModel/filters/regexpfilter.cpp deleted file mode 100644 index f308765e..00000000 --- a/client/3rd/SortFilterProxyModel/filters/regexpfilter.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "regexpfilter.h" -#include - -namespace qqsfpm { - -/*! - \qmltype RegExpFilter - \inherits RoleFilter - \inqmlmodule SortFilterProxyModel - \ingroup Filters - \brief Filters rows matching a regular expression. - - A RegExpFilter is a \l RoleFilter that accepts rows matching a regular rexpression. - - In the following example, only rows with their \c lastName role beggining with the content of textfield the will be accepted: - \code - TextField { - id: nameTextField - } - - SortFilterProxyModel { - sourceModel: contactModel - filters: RegExpFilter { - roleName: "lastName" - pattern: "^" + nameTextField.displayText - } - } - \endcode -*/ - -/*! - \qmlproperty bool RegExpFilter::pattern - - The pattern used to filter the contents of the source model. - - \sa syntax -*/ -QString RegExpFilter::pattern() const -{ - return m_pattern; -} - -void RegExpFilter::setPattern(const QString& pattern) -{ - if (m_pattern == pattern) - return; - - m_pattern = pattern; - m_regExp.setPattern(pattern); - Q_EMIT patternChanged(); - invalidate(); -} - -/*! - \qmlproperty enum RegExpFilter::syntax - - The pattern used to filter the contents of the source model. - - Only the source model's value having their \l RoleFilter::roleName data matching this \l pattern with the specified \l syntax will be kept. - - \value RegExpFilter.RegExp A rich Perl-like pattern matching syntax. This is the default. - \value RegExpFilter.Wildcard This provides a simple pattern matching syntax similar to that used by shells (command interpreters) for "file globbing". - \value RegExpFilter.FixedString The pattern is a fixed string. This is equivalent to using the RegExp pattern on a string in which all metacharacters are escaped. - \value RegExpFilter.RegExp2 Like RegExp, but with greedy quantifiers. - \value RegExpFilter.WildcardUnix This is similar to Wildcard but with the behavior of a Unix shell. The wildcard characters can be escaped with the character "\". - \value RegExpFilter.W3CXmlSchema11 The pattern is a regular expression as defined by the W3C XML Schema 1.1 specification. - - \sa pattern -*/ -RegExpFilter::PatternSyntax RegExpFilter::syntax() const -{ - return m_syntax; -} - -void RegExpFilter::setSyntax(RegExpFilter::PatternSyntax syntax) -{ - if (m_syntax == syntax) - return; - - m_syntax = syntax; - m_regExp.setPatternSyntax(static_cast(syntax)); - Q_EMIT syntaxChanged(); - invalidate(); -} - -/*! - \qmlproperty Qt::CaseSensitivity RegExpFilter::caseSensitivity - - This property holds the caseSensitivity of the filter. -*/ -Qt::CaseSensitivity RegExpFilter::caseSensitivity() const -{ - return m_caseSensitivity; -} - -void RegExpFilter::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity) -{ - if (m_caseSensitivity == caseSensitivity) - return; - - m_caseSensitivity = caseSensitivity; - m_regExp.setCaseSensitivity(caseSensitivity); - Q_EMIT caseSensitivityChanged(); - invalidate(); -} - -bool RegExpFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const -{ - QString string = sourceData(sourceIndex, proxyModel).toString(); - return m_regExp.indexIn(string) != -1; -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/regexpfilter.h b/client/3rd/SortFilterProxyModel/filters/regexpfilter.h deleted file mode 100644 index 2c20a6a8..00000000 --- a/client/3rd/SortFilterProxyModel/filters/regexpfilter.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef REGEXPFILTER_H -#define REGEXPFILTER_H - -#include "rolefilter.h" - -namespace qqsfpm { - -class RegExpFilter : public RoleFilter { - Q_OBJECT - Q_PROPERTY(QString pattern READ pattern WRITE setPattern NOTIFY patternChanged) - Q_PROPERTY(PatternSyntax syntax READ syntax WRITE setSyntax NOTIFY syntaxChanged) - Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged) - -public: - enum PatternSyntax { - RegExp = QRegExp::RegExp, - Wildcard = QRegExp::Wildcard, - FixedString = QRegExp::FixedString, - RegExp2 = QRegExp::RegExp2, - WildcardUnix = QRegExp::WildcardUnix, - W3CXmlSchema11 = QRegExp::W3CXmlSchema11 }; - Q_ENUMS(PatternSyntax) - - using RoleFilter::RoleFilter; - - QString pattern() const; - void setPattern(const QString& pattern); - - PatternSyntax syntax() const; - void setSyntax(PatternSyntax syntax); - - Qt::CaseSensitivity caseSensitivity() const; - void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity); - -protected: - bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override; - -Q_SIGNALS: - void patternChanged(); - void syntaxChanged(); - void caseSensitivityChanged(); - -private: - QRegExp m_regExp; - Qt::CaseSensitivity m_caseSensitivity = m_regExp.caseSensitivity(); - PatternSyntax m_syntax = static_cast(m_regExp.patternSyntax()); - QString m_pattern = m_regExp.pattern(); -}; - -} - -#endif // REGEXPFILTER_H diff --git a/client/3rd/SortFilterProxyModel/filters/rolefilter.cpp b/client/3rd/SortFilterProxyModel/filters/rolefilter.cpp deleted file mode 100644 index 3532c878..00000000 --- a/client/3rd/SortFilterProxyModel/filters/rolefilter.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "rolefilter.h" -#include "qqmlsortfilterproxymodel.h" - -namespace qqsfpm { - -/*! - \qmltype RoleFilter - \qmlabstract - \inherits Filter - \inqmlmodule SortFilterProxyModel - \ingroup Filters - \brief Base type for filters based on a source model role. - - The RoleFilter type cannot be used directly in a QML file. - It exists to provide a set of common properties and methods, - available across all the other filter types that inherit from it. - Attempting to use the RoleFilter type directly will result in an error. -*/ - -/*! - \qmlproperty string RoleFilter::roleName - - This property holds the role name that the filter is using to query the source model's data when filtering items. -*/ -const QString& RoleFilter::roleName() const -{ - return m_roleName; -} - -void RoleFilter::setRoleName(const QString& roleName) -{ - if (m_roleName == roleName) - return; - - m_roleName = roleName; - Q_EMIT roleNameChanged(); - invalidate(); -} - -QVariant RoleFilter::sourceData(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const -{ - return proxyModel.sourceData(sourceIndex, m_roleName); -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/rolefilter.h b/client/3rd/SortFilterProxyModel/filters/rolefilter.h deleted file mode 100644 index 920ed6cc..00000000 --- a/client/3rd/SortFilterProxyModel/filters/rolefilter.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef ROLEFILTER_H -#define ROLEFILTER_H - -#include "filter.h" - -namespace qqsfpm { - -class RoleFilter : public Filter -{ - Q_OBJECT - Q_PROPERTY(QString roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged) - -public: - using Filter::Filter; - - const QString& roleName() const; - void setRoleName(const QString& roleName); - -Q_SIGNALS: - void roleNameChanged(); - -protected: - QVariant sourceData(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const; - -private: - QString m_roleName; -}; - -} - -#endif // ROLEFILTER_H diff --git a/client/3rd/SortFilterProxyModel/filters/valuefilter.cpp b/client/3rd/SortFilterProxyModel/filters/valuefilter.cpp deleted file mode 100644 index 09e9434a..00000000 --- a/client/3rd/SortFilterProxyModel/filters/valuefilter.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "valuefilter.h" - -namespace qqsfpm { - -/*! - \qmltype ValueFilter - \inherits RoleFilter - \inqmlmodule SortFilterProxyModel - \ingroup Filters - \brief Filters rows matching exactly a value. - - A ValueFilter is a simple \l RoleFilter that accepts rows matching exactly the filter's value - - In the following example, only rows with their \c favorite role set to \c true will be accepted when the checkbox is checked : - \code - CheckBox { - id: showOnlyFavoriteCheckBox - } - - SortFilterProxyModel { - sourceModel: contactModel - filters: ValueFilter { - roleName: "favorite" - value: true - enabled: showOnlyFavoriteCheckBox.checked - } - } - \endcode - -*/ - -/*! - \qmlproperty variant ValueFilter::value - - This property holds the value used to filter the contents of the source model. -*/ -const QVariant &ValueFilter::value() const -{ - return m_value; -} - -void ValueFilter::setValue(const QVariant& value) -{ - if (m_value == value) - return; - - m_value = value; - Q_EMIT valueChanged(); - invalidate(); -} - -bool ValueFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const -{ - return !m_value.isValid() || m_value == sourceData(sourceIndex, proxyModel); -} - -} diff --git a/client/3rd/SortFilterProxyModel/filters/valuefilter.h b/client/3rd/SortFilterProxyModel/filters/valuefilter.h deleted file mode 100644 index 2436094c..00000000 --- a/client/3rd/SortFilterProxyModel/filters/valuefilter.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef VALUEFILTER_H -#define VALUEFILTER_H - -#include "rolefilter.h" -#include - -namespace qqsfpm { - -class ValueFilter : public RoleFilter { - Q_OBJECT - Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) - -public: - using RoleFilter::RoleFilter; - - const QVariant& value() const; - void setValue(const QVariant& value); - -protected: - bool filterRow(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override; - -Q_SIGNALS: - void valueChanged(); - -private: - QVariant m_value; -}; - -} - -#endif // VALUEFILTER_H diff --git a/client/3rd/SortFilterProxyModel/index.qdoc b/client/3rd/SortFilterProxyModel/index.qdoc deleted file mode 100644 index e4917bb2..00000000 --- a/client/3rd/SortFilterProxyModel/index.qdoc +++ /dev/null @@ -1,27 +0,0 @@ -/*! - \page index.html overview - - \title SortFilterProxyModel QML Module - - SortFilterProxyModel is an implementation of QSortFilterProxyModel conveniently exposed for QML. - \annotatedlist SortFilterProxyModel - - \section1 Filters - \annotatedlist Filters - - \section2 Related attached types - \annotatedlist FilterAttached - - \section1 Sorters - \annotatedlist Sorters - - \section2 Related attached types - \annotatedlist SorterAttached - - \section1 ProxyRoles - \annotatedlist ProxyRoles -*/ - -/*! - \qmlmodule SortFilterProxyModel -*/ diff --git a/client/3rd/SortFilterProxyModel/offline.css b/client/3rd/SortFilterProxyModel/offline.css deleted file mode 100644 index 13c94270..00000000 --- a/client/3rd/SortFilterProxyModel/offline.css +++ /dev/null @@ -1,805 +0,0 @@ -body { - font: normal 400 14px/1.2 Arial; - margin-top: 85px; - font-family: Arial, Helvetica; - text-align: left; - margin-left: 5px; - margin-right: 5px; - background-color: #fff; -} - -p { - line-height: 20px -} - -img { - margin-left: 0px; - max-width: 800px; - height: auto; -} - -.content .border img { - box-shadow:3px 3px 8px 3px rgba(200,200,200,0.5) -} - -.content .border .player { - box-shadow:3px 3px 8px 3px rgba(200,200,200,0.5) -} - -.content .indexboxcont li { - font: normal bold 13px/1 Verdana - } - -.content .normallist li { - font: normal 13px/1 Verdana - } - -.descr { - margin-top: 35px; - margin-bottom: 45px; - margin-left: 5px; - text-align: left; - vertical-align: top; -} - -.name { - max-width: 75%; - font-weight: 100; -} - -tt { - text-align: left -} - -/* ------------ -links ------------ -*/ - -a:link { - color: #007330; - text-decoration: none; - text-align: left; -} - -a.qa-mark:target:before { - content: "***"; - color: #ff0000; -} - -a:hover { - color: #44a51c; - text-align: left; -} - -a:visited { - color: #007330; - text-align: left; -} - -a:visited:hover { - color: #44a51c; - text-align: left; -} - -/* ------------ -offline viewing: HTML links display an icon ------------ -*/ - -a[href*="http://"], a[href*="ftp://"], a[href*="https://"] { - text-decoration: none; - background-image: url(../images/ico_out.png); - background-repeat: no-repeat; - background-position: left; - padding-left: 20px; - text-align: left; -} - -.flags { - text-decoration: none; - text-height: 24px; -} - -.flags:target { - background-color: #FFFFD6; -} - -/* -------------------------------- -NOTE styles -------------------------------- -*/ - -.notetitle, .tiptitle, .fastpathtitle { - font-weight: bold -} - -.attentiontitle, .cautiontitle, .dangertitle, .importanttitle, .remembertitle, .restrictiontitle { - font-weight: bold -} - -.note, .tip, .fastpath { - background: #F2F2F2 url(../images/ico_note.png); - background-repeat: no-repeat; - background-position: top left; - padding: 5px; - padding-left: 40px; - padding-bottom: 10px; - border: #999 1px dotted; - color: #666666; - margin: 5px; -} - -.attention, .caution, .danger, .important, .remember, .restriction { - background: #F2F2F2 url(../images/ico_note_attention.png); - background-repeat: no-repeat; - background-position: top left; - padding: 5px; - padding-left: 40px; - padding-bottom: 10px; - border: #999 1px dotted; - color: #666666; - margin: 5px; -} - -/* -------------------------------- -Top navigation -------------------------------- -*/ - -.qtref { - display: block; - position: relative; - height: 15px; - z-index: 1; - font-size: 11px; - padding-right: 10px; - float: right; -} - -.naviNextPrevious { - clear: both; - display: block; - position: relative; - text-align: right; - top: -47px; - float: right; - height: 20px; - z-index: 1; - padding-right: 10px; - padding-top: 2px; - vertical-align: top; - margin: 0px; -} - -.naviNextPrevious > a:first-child { - background-image: url(../images/btn_prev.png); - background-repeat: no-repeat; - background-position: left; - padding-left: 20px; - height: 20px; - padding-left: 20px; - } - -.naviNextPrevious > a:last-child { - background-image: url(../images/btn_next.png); - background-repeat: no-repeat; - background-position: right; - padding-right: 20px; - height: 20px; - margin-left: 30px; - } - -.naviSeparator { display: none } -/* ------------ -footer and license ------------ -*/ - -.footer { - text-align: left; - padding-top: 45px; - padding-left: 5px; - margin-top: 45px; - margin-bottom: 45px; - font-size: 10px; - border-top: 1px solid #999; -} - -.footer p { - line-height: 14px; - font-size: 11px; - padding: 0; - margin: 0; -} - -.footer a[href*="http://"], a[href*="ftp://"], a[href*="https://"] { - font-weight: bold; -} - -.footerNavi { - width: auto; - text-align: right; - margin-top: 50px; - z-index: 1; -} - -.navigationbar { - display: block; - position: relative; - top: -20px; - border-top: 1px solid #cecece; - border-bottom: 1px solid #cecece; - background-color: #F2F2F2; - z-index: 1; - height: 20px; - padding-left: 7px; - margin: 0px; - padding-top: 2px; - margin-left: -5px; - margin-right: -5px; -} - -.navigationbar .first { - background: url(../images/home.png); - background-position: left; - background-repeat: no-repeat; - padding-left: 20px; - } - -.navigationbar ul { - margin: 0px; - padding: 0px; - } - - .navigationbar ul li { - list-style-type: none; - padding-top: 2px; - padding-left: 4px; - margin: 0; - height: 20px; - } - -.navigationbar li { - float: left - } - - .navigationbar li a, .navigationbar td a { - display: block; - text-decoration: none; - background: url(../images/arrow_bc.png); - background-repeat: no-repeat; - background-position: right; - padding-right: 17px; - } - -table.buildversion { - float: right; - margin-top: -18px !important; -} - -.navigationbar table { - border-radius: 0; - border: 0 none; - background-color: #F2F2F2; - margin: 0; -} - -.navigationbar table td { - padding: 0; - border: 0 none; -} - -#buildversion { - font-style: italic; - font-size: small; - float: right; - margin-right: 5px; -} - -/* - -/* table of content -no display -*/ - -/* ------------ -headers ------------ -*/ - -@media screen { - .title { - color: #313131; - font-size: 24px; - font-weight: normal; - left: 0; - padding-bottom: 20px; - padding-left: 10px; - padding-top: 20px; - position: absolute; - right: 0; - top: 0; - background-color: #E6E6E6; - border-bottom: 1px #CCC solid; - border-top: 2px #CCC solid; - font-weight: bold; - margin-left: 0px; - margin-right: 0px; - } - .subtitle, .small-subtitle { - display: block; - clear: left; - } -} - -h1 { - margin: 0 -} - -h2, p.h2 { - font: 500 16px/1.2 Arial; - font-weight: 100; - background-color: #F2F3F4; - padding: 4px; - margin-bottom: 30px; - margin-top: 30px; - border-top: #E0E0DE 1px solid; - border-bottom: #E0E0DE 1px solid; - max-width: 99%; -} - -h2:target { - background-color: #F2F3D4; -} - -h3 { - font: 500 14px/1.2 Arial; - font-weight: 100; - text-decoration: underline; - margin-bottom: 30px; - margin-top: 30px; -} - -h3.fn, span.fn { - border-width: 1px; - border-style: solid; - border-color: #E6E6E6; - -moz-border-radius: 7px 7px 7px 7px; - -webkit-border-radius: 7px 7px 7px 7px; - border-radius: 7px 7px 7px 7px; - background-color: #F6F6F6; - word-spacing: 3px; - padding: 5px 5px; - text-decoration: none; - font-weight: bold; - max-width: 75%; - font-size: 14px; - margin: 0px; - margin-top: 45px; -} -.fngroup h3.fngroupitem { - margin-bottom: 5px; -} -h3.fn code { - float: right; -} -h3.fn:target { - background-color: #F6F6D6; -} - -.name { - color: #1A1A1A -} - -.type { - color: #808080 -} - -@media print { - .title { - color: #0066CB; - font-family: Arial, Helvetica; - font-size: 32px; - font-weight: normal; - left: 0; - position: absolute; - right: 0; - top: 0; - } -} - -/* ------------------ -table styles ------------------ -*/ - -.table img { - border: none; - margin-left: 0px; - -moz-box-shadow: 0px 0px 0px #fff; - -webkit-box-shadow: 0px 0px 0px #fff; - box-shadow: 0px 0px 0px #fff; -} - -/* table with border alternative colours*/ - -table, pre, .LegaleseLeft { - -moz-border-radius: 7px 7px 7px 7px; - -webkit-border-radius: 7px 7px 7px 7px; - border-radius: 7px 7px 7px 7px; - background-color: #F6F6F6; - border: 1px solid #E6E6E6; - border-collapse: separate; - margin-bottom: 25px; - margin-left: 15px; - font-size: 12px; - line-height: 1.2; -} - - table tr.even { - background-color: white; - color: #66666E; - } - - table tr.odd { - background-color: #F6F6F6; - color: #66666E; - } - - table tr:target { - background-color: #F6F6D6; - } - - table thead { - text-align: left; - padding-left: 20px; - background-color: #e1e0e0; - border-left: none; - border-right: none; - } - - table thead th { - padding-top: 5px; - padding-left: 10px; - padding-bottom: 5px; - border-bottom: 2px solid #D1D1D1; - padding-right: 10px; - } - - table th { - text-align: left; - padding-left: 20px; - } - - table td { - padding: 3px 15px 3px 20px; - border-bottom: #CCC dotted 1px; - } - - table p { - margin: 0px - } - -.LegaleseLeft { - font-family: monospace; - white-space: pre-wrap; -} -/* table bodless & white*/ - -.borderless { - border-radius: 0px 0px 0px 0px; - background-color: #fff; - border: 1px solid #fff; -} - -.borderless tr { - background-color: #FFF; - color: #66666E; - } - -.borderless td { - border: none; - border-bottom: #fff dotted 1px; - } - -/* ------------ -List ------------ -*/ - -ul { - margin-top: 10px; -} - -li { - margin-bottom: 10px; - padding-left: 8px; - list-style: outside; - text-align: left; -} - - ul > li { - list-style-type: square; - } - -ol { - margin: 10px; - padding: 0; -} - -ol.A > li { - list-style-type: upper-alpha; -} - -ol.a > li{ - list-style-type: lower-alpha; -} - - ol > li { - margin-left: 30px; - padding-left: 8px; - list-style: decimal; - } - -.centerAlign { - text-align: left -} - -.cpp, .LegaleseLeft { - display: block; - margin: 10px; - overflow: auto; - padding: 20px 20px 20px 20px; -} - -.js { - display: block; - margin: 10px; - overflow: auto; - padding: 20px 20px 20px 20px; -} - -.memItemLeft { - padding-right: 3px -} - -.memItemRight { - padding: 3px 15px 3px 0 -} - -.qml { - display: block; - margin: 10px; - overflow: auto; - padding: 20px 20px 20px 20px; -} - -.qmldefault { - padding-left: 5px; - float: right; - color: red; -} - -.qmlreadonly { - padding-left: 5px; - float: right; - color: #254117; -} - -.rightAlign { - padding: 3px 5px 3px 10px; - text-align: right; -} - -.qmldoc { - margin-left: 15px -} - -.flowList { - padding: 25px -} -.flowList dd { - display: inline-block; - margin-left: 10px; - width: 255px; - line-height: 1.15em; - overflow-x: hidden; - text-overflow: ellipsis -} -.alphaChar { - font-size: 2em; - position: relative -} -/* ------------ -Content table ------------ -*/ - -@media print { - .toc { - float: right; - clear: right; - padding-bottom: 10px; - padding-top: 50px; - width: 100%; - background-image: url(../images/bgrContent.png); - background-position: top; - background-repeat: no-repeat; - } -} - -@media screen { - .toc { - float: right; - clear: right; - vertical-align: top; - -moz-border-radius: 7px 7px 7px 7px; - -webkit-border-radius: 7px 7px 7px 7px; - border-radius: 7px 7px 7px 7px; - background: #FFF url('../images/bgrContent.png'); - background-position: top; - background-repeat: repeat-x; - border: 1px solid #E6E6E6; - padding-left: 5px; - padding-bottom: 10px; - height: auto; - width: 200px; - text-align: left; - margin-left: 20px; - } -} - - -.toc h3 { - text-decoration: none -} - -.toc h3 { - font: 500 14px/1.2 Arial; - font-weight: 100; - padding: 0px; - margin: 0px; - padding-top: 5px; - padding-left: 5px; -} - -.toc ul { - padding-left: 10px; - padding-right: 5px; -} - -.toc ul li { - margin-left: 15px; - list-style-image: url(../images/bullet_dn.png); - marker-offset: 0px; - margin-bottom: 8px; - padding-left: 0px; - } - -.toc .level1 { - border: none -} - -.toc .level2 { - border: none; - margin-left: 25px; -} - -.level3 { - border: none; - margin-left: 30px; -} - -.clearfix { - clear: both -} - -/* ------------ -Landing page ------------ -*/ - -.col-group { - white-space: nowrap; - vertical-align: top; -} - - -.landing h2 { - background-color: transparent; - border: none; - margin-bottom: 0px; - font-size: 18px; -} - -.landing a, .landing li { - font-size: 13px; - font-weight: bold !important; -} - -.col-1 { - display: inline-block; - white-space: normal; - width: 70%; - height: 100%; - float: left; -} - -.col-2 { - display: inline-block; - white-space: normal; - width: 20%; - margin-left: 5%; - position: relative; - top: -20px; -} - -.col-1 h1 { - margin: 20px 0 0 0; - } - -.col-1 h2 { - font-size: 18px; - font-weight: bold !important; -} - -.landingicons { - display: inline-block; - width: 100%; -} - -.icons1of3 { - display: inline-block; - width: 33.3333%; - float: left; -} - -.icons1of3 h2, .doc-column h2 { - font-size: 15px; - margin: 0px; - padding: 0px; -} - -div.multi-column { - position: relative; -} - -div.multi-column div { - display: -moz-inline-box; - display: inline-block; - vertical-align: top; - margin-top: 1em; - margin-right: 4em; - width: 24em; -} - -.mainContent .video { - width:40%; - max-width:640px; - margin: 15px 0 0 15px; - position:relative; - display:table -} - -.mainContent .video > .vspan { - padding-top:60%; - display:block -} -.mainContent .video iframe { - width:100%; - height:100%; - position:absolute; - top:0; - left:0 -} diff --git a/client/3rd/SortFilterProxyModel/proxyroles/expressionrole.cpp b/client/3rd/SortFilterProxyModel/proxyroles/expressionrole.cpp deleted file mode 100644 index 70af4fa2..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/expressionrole.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "expressionrole.h" -#include "qqmlsortfilterproxymodel.h" -#include - -namespace qqsfpm { - -/*! - \qmltype ExpressionRole - \inherits SingleRole - \inqmlmodule SortFilterProxyModel - \ingroup ProxyRoles - \brief A custom role computed from a javascript expression. - - An ExpressionRole is a \l ProxyRole allowing to implement a custom role based on a javascript expression. - - In the following example, the \c c role is computed by adding the \c a role and \c b role of the model : - \code - SortFilterProxyModel { - sourceModel: numberModel - proxyRoles: ExpressionRole { - name: "c" - expression: model.a + model.b - } - } - \endcode -*/ - -/*! - \qmlproperty expression ExpressionRole::expression - - An expression to implement a custom role. - It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding} except it will be evaluated for each of the source model's rows. - The data for this role will be the retuned valued of the expression. - Data for each row is exposed like for a delegate of a QML View. - - This expression is reevaluated for a row every time its model data changes. - When an external property (not \c index or in \c model) the expression depends on changes, the expression is reevaluated for every row of the source model. - To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine. - This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes. - - A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression. -*/ -const QQmlScriptString& ExpressionRole::expression() const -{ - return m_scriptString; -} - -void ExpressionRole::setExpression(const QQmlScriptString& scriptString) -{ - if (m_scriptString == scriptString) - return; - - m_scriptString = scriptString; - updateExpression(); - - Q_EMIT expressionChanged(); - invalidate(); -} - -void ExpressionRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) -{ - updateContext(proxyModel); -} - -QVariant ExpressionRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) -{ - if (!m_scriptString.isEmpty()) { - QVariantMap modelMap; - QHash roles = proxyModel.roleNames(); - - QQmlContext context(qmlContext(this)); - auto addToContext = [&] (const QString &name, const QVariant& value) { - context.setContextProperty(name, value); - modelMap.insert(name, value); - }; - - for (auto it = roles.cbegin(); it != roles.cend(); ++it) - addToContext(it.value(), proxyModel.sourceData(sourceIndex, it.key())); - addToContext("index", sourceIndex.row()); - - context.setContextProperty("model", modelMap); - - QQmlExpression expression(m_scriptString, &context); - QVariant result = expression.evaluate(); - - if (expression.hasError()) { - qWarning() << expression.error(); - return true; - } - return result; - } - return QVariant(); -} - -void ExpressionRole::updateContext(const QQmlSortFilterProxyModel& proxyModel) -{ - delete m_context; - m_context = new QQmlContext(qmlContext(this), this); - // what about roles changes ? - QVariantMap modelMap; - - auto addToContext = [&] (const QString &name, const QVariant& value) { - m_context->setContextProperty(name, value); - modelMap.insert(name, value); - }; - - for (const QByteArray& roleName : proxyModel.roleNames().values()) - addToContext(roleName, QVariant()); - - addToContext("index", -1); - - m_context->setContextProperty("model", modelMap); - updateExpression(); -} - -void ExpressionRole::updateExpression() -{ - if (!m_context) - return; - - delete m_expression; - m_expression = new QQmlExpression(m_scriptString, m_context, 0, this); - connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionRole::invalidate); - m_expression->setNotifyOnValueChanged(true); - m_expression->evaluate(); -} - -} diff --git a/client/3rd/SortFilterProxyModel/proxyroles/expressionrole.h b/client/3rd/SortFilterProxyModel/proxyroles/expressionrole.h deleted file mode 100644 index eda3d51f..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/expressionrole.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef EXPRESSIONROLE_H -#define EXPRESSIONROLE_H - -#include "singlerole.h" -#include - -class QQmlExpression; - -namespace qqsfpm { - -class ExpressionRole : public SingleRole -{ - Q_OBJECT - Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged) - -public: - using SingleRole::SingleRole; - - const QQmlScriptString& expression() const; - void setExpression(const QQmlScriptString& scriptString); - - void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override; - -Q_SIGNALS: - void expressionChanged(); - -private: - QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override; - void updateContext(const QQmlSortFilterProxyModel& proxyModel); - void updateExpression(); - - QQmlScriptString m_scriptString; - QQmlExpression* m_expression = nullptr; - QQmlContext* m_context = nullptr; -}; - -} - -#endif // EXPRESSIONROLE_H diff --git a/client/3rd/SortFilterProxyModel/proxyroles/filterrole.cpp b/client/3rd/SortFilterProxyModel/proxyroles/filterrole.cpp deleted file mode 100644 index 84dcbcd3..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/filterrole.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "filterrole.h" -#include "filters/filter.h" - -namespace qqsfpm { - -/*! - \qmltype FilterRole - \inherits SingleRole - \inqmlmodule SortFilterProxyModel - \ingroup ProxyRoles - \ingroup FilterContainer - \brief A role resolving to \c true for rows matching all its filters. - - A FilterRole is a \l ProxyRole that returns \c true for rows matching all its filters. - - In the following example, the \c isAdult role will be equal to \c true if the \c age role is superior or equal to 18. - \code - SortFilterProxyModel { - sourceModel: personModel - proxyRoles: FilterRole { - name: "isAdult" - RangeFilter { roleName: "age"; minimumValue: 18; minimumInclusive: true } - } - } - \endcode - \sa FilterContainer -*/ - -/*! - \qmlproperty list FilterRole::filters - \default - - This property holds the list of filters for this filter role. - The data of this role will be equal to the \c true if all its filters match the model row, \c false otherwise. - - \sa Filter, FilterContainer -*/ - -void FilterRole::onFilterAppended(Filter* filter) -{ - connect(filter, &Filter::invalidated, this, &FilterRole::invalidate); - invalidate(); -} - -void FilterRole::onFilterRemoved(Filter* filter) -{ - disconnect(filter, &Filter::invalidated, this, &FilterRole::invalidate); - invalidate(); -} - -void FilterRole::onFiltersCleared() -{ - invalidate(); -} - -QVariant FilterRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) -{ - return std::all_of(m_filters.begin(), m_filters.end(), - [&] (Filter* filter) { - return filter->filterAcceptsRow(sourceIndex, proxyModel); - } - ); -} - -} diff --git a/client/3rd/SortFilterProxyModel/proxyroles/filterrole.h b/client/3rd/SortFilterProxyModel/proxyroles/filterrole.h deleted file mode 100644 index dd1285a3..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/filterrole.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef FILTERROLE_H -#define FILTERROLE_H - -#include "singlerole.h" -#include "filters/filtercontainer.h" - -namespace qqsfpm { - -class FilterRole : public SingleRole, public FilterContainer -{ - Q_OBJECT - Q_INTERFACES(qqsfpm::FilterContainer) - Q_PROPERTY(QQmlListProperty filters READ filtersListProperty) - Q_CLASSINFO("DefaultProperty", "filters") - -public: - using SingleRole::SingleRole; - -private: - void onFilterAppended(Filter* filter) override; - void onFilterRemoved(Filter* filter) override; - void onFiltersCleared() override; - - QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override; -}; - -} - -#endif // FILTERROLE_H diff --git a/client/3rd/SortFilterProxyModel/proxyroles/joinrole.cpp b/client/3rd/SortFilterProxyModel/proxyroles/joinrole.cpp deleted file mode 100644 index c252be31..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/joinrole.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "joinrole.h" -#include "qqmlsortfilterproxymodel.h" - -namespace qqsfpm { - -/*! - \qmltype JoinRole - \inherits SingleRole - \inqmlmodule SortFilterProxyModel - \ingroup ProxyRoles - \brief a role made from concatenating other roles. - - A JoinRole is a simple \l ProxyRole that concatenates other roles. - - In the following example, the \c fullName role is computed by the concatenation of the \c firstName role and the \c lastName role separated by a space : - \code - SortFilterProxyModel { - sourceModel: contactModel - proxyRoles: JoinRole { - name: "fullName" - roleNames: ["firstName", "lastName"] - } - } - \endcode - -*/ - -/*! - \qmlproperty list JoinRole::roleNames - - This property holds the role names that are joined by this role. -*/ -QStringList JoinRole::roleNames() const -{ - return m_roleNames; -} - -void JoinRole::setRoleNames(const QStringList& roleNames) -{ - if (m_roleNames == roleNames) - return; - - m_roleNames = roleNames; - Q_EMIT roleNamesChanged(); - invalidate(); -} - -/*! - \qmlproperty string JoinRole::separator - - This property holds the separator that is used to join the roles specified in \l roleNames. - - By default, it's a space. -*/ -QString JoinRole::separator() const -{ - return m_separator; -} - -void JoinRole::setSeparator(const QString& separator) -{ - if (m_separator == separator) - return; - - m_separator = separator; - Q_EMIT separatorChanged(); - invalidate(); -} - -QVariant JoinRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) -{ - QString result; - - for (const QString& roleName : m_roleNames) - result += proxyModel.sourceData(sourceIndex, roleName).toString() + m_separator; - - if (!m_roleNames.isEmpty()) - result.chop(m_separator.length()); - - return result; -} - -} diff --git a/client/3rd/SortFilterProxyModel/proxyroles/joinrole.h b/client/3rd/SortFilterProxyModel/proxyroles/joinrole.h deleted file mode 100644 index 520b861b..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/joinrole.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef JOINROLE_H -#define JOINROLE_H - -#include "singlerole.h" - -namespace qqsfpm { - -class JoinRole : public SingleRole -{ - Q_OBJECT - Q_PROPERTY(QStringList roleNames READ roleNames WRITE setRoleNames NOTIFY roleNamesChanged) - Q_PROPERTY(QString separator READ separator WRITE setSeparator NOTIFY separatorChanged) - -public: - using SingleRole::SingleRole; - - QStringList roleNames() const; - void setRoleNames(const QStringList& roleNames); - - QString separator() const; - void setSeparator(const QString& separator); - -Q_SIGNALS: - void roleNamesChanged(); - - void separatorChanged(); - -private: - QStringList m_roleNames; - QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override; - QString m_separator = " "; -}; - -} - -#endif // JOINROLE_H diff --git a/client/3rd/SortFilterProxyModel/proxyroles/proxyrole.cpp b/client/3rd/SortFilterProxyModel/proxyroles/proxyrole.cpp deleted file mode 100644 index 172b0c2e..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/proxyrole.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "proxyrole.h" -#include -#include -#include -#include -#include -#include -#include "filters/filter.h" -#include "qqmlsortfilterproxymodel.h" - -namespace qqsfpm { - -/*! - \qmltype ProxyRole - \inqmlmodule SortFilterProxyModel - \ingroup ProxyRoles - \brief Base type for the \l SortFilterProxyModel proxy roles. - - The ProxyRole type cannot be used directly in a QML file. - It exists to provide a set of common properties and methods, - available across all the other proxy role types that inherit from it. - Attempting to use the ProxyRole type directly will result in an error. -*/ - -QVariant ProxyRole::roleData(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString &name) -{ - if (m_mutex.tryLock()) { - QVariant result = data(sourceIndex, proxyModel, name); - m_mutex.unlock(); - return result; - } else { - return {}; - } -} - -void ProxyRole::proxyModelCompleted(const QQmlSortFilterProxyModel &proxyModel) -{ - Q_UNUSED(proxyModel) -} - -void ProxyRole::invalidate() -{ - Q_EMIT invalidated(); -} - -} diff --git a/client/3rd/SortFilterProxyModel/proxyroles/proxyrole.h b/client/3rd/SortFilterProxyModel/proxyroles/proxyrole.h deleted file mode 100644 index 5c1ff93c..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/proxyrole.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef PROXYROLE_H -#define PROXYROLE_H - -#include -#include - -namespace qqsfpm { - -class QQmlSortFilterProxyModel; - -class ProxyRole : public QObject -{ - Q_OBJECT - -public: - using QObject::QObject; - virtual ~ProxyRole() = default; - - QVariant roleData(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString& name); - virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel); - - virtual QStringList names() = 0; - -protected: - void invalidate(); - -Q_SIGNALS: - void invalidated(); - void namesAboutToBeChanged(); - void namesChanged(); - -private: - virtual QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString& name) = 0; - - QMutex m_mutex; -}; - -} - -#endif // PROXYROLE_H diff --git a/client/3rd/SortFilterProxyModel/proxyroles/proxyrolecontainer.cpp b/client/3rd/SortFilterProxyModel/proxyroles/proxyrolecontainer.cpp deleted file mode 100644 index f8ea6652..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/proxyrolecontainer.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "proxyrolecontainer.h" - -namespace qqsfpm { - -QList ProxyRoleContainer::proxyRoles() const -{ - return m_proxyRoles; -} - -void ProxyRoleContainer::appendProxyRole(ProxyRole* proxyRole) -{ - m_proxyRoles.append(proxyRole); - onProxyRoleAppended(proxyRole); -} - -void ProxyRoleContainer::removeProxyRole(ProxyRole* proxyRole) -{ - m_proxyRoles.removeOne(proxyRole); - onProxyRoleRemoved(proxyRole); -} - -void ProxyRoleContainer::clearProxyRoles() -{ - m_proxyRoles.clear(); - onProxyRolesCleared(); -} - -QQmlListProperty ProxyRoleContainer::proxyRolesListProperty() -{ - return QQmlListProperty(reinterpret_cast(this), &m_proxyRoles, - &ProxyRoleContainer::append_proxyRole, - &ProxyRoleContainer::count_proxyRole, - &ProxyRoleContainer::at_proxyRole, - &ProxyRoleContainer::clear_proxyRoles); -} - -void ProxyRoleContainer::append_proxyRole(QQmlListProperty* list, ProxyRole* proxyRole) -{ - if (!proxyRole) - return; - - ProxyRoleContainer* that = reinterpret_cast(list->object); - that->appendProxyRole(proxyRole); -} - -int ProxyRoleContainer::count_proxyRole(QQmlListProperty* list) -{ - QList* ProxyRoles = static_cast*>(list->data); - return ProxyRoles->count(); -} - -ProxyRole* ProxyRoleContainer::at_proxyRole(QQmlListProperty* list, int index) -{ - QList* ProxyRoles = static_cast*>(list->data); - return ProxyRoles->at(index); -} - -void ProxyRoleContainer::clear_proxyRoles(QQmlListProperty *list) -{ - ProxyRoleContainer* that = reinterpret_cast(list->object); - that->clearProxyRoles(); -} - -} diff --git a/client/3rd/SortFilterProxyModel/proxyroles/proxyrolecontainer.h b/client/3rd/SortFilterProxyModel/proxyroles/proxyrolecontainer.h deleted file mode 100644 index bcd932e4..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/proxyrolecontainer.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef PROXYROLECONTAINER_H -#define PROXYROLECONTAINER_H - -#include -#include - -namespace qqsfpm { - -class ProxyRole; -class QQmlSortFilterProxyModel; - -class ProxyRoleContainer { -public: - virtual ~ProxyRoleContainer() = default; - - QList proxyRoles() const; - void appendProxyRole(ProxyRole* proxyRole); - void removeProxyRole(ProxyRole* proxyRole); - void clearProxyRoles(); - - QQmlListProperty proxyRolesListProperty(); - -protected: - QList m_proxyRoles; - -private: - virtual void onProxyRoleAppended(ProxyRole* proxyRole) = 0; - virtual void onProxyRoleRemoved(ProxyRole* proxyRole) = 0; - virtual void onProxyRolesCleared() = 0; - - static void append_proxyRole(QQmlListProperty* list, ProxyRole* proxyRole); - static int count_proxyRole(QQmlListProperty* list); - static ProxyRole* at_proxyRole(QQmlListProperty* list, int index); - static void clear_proxyRoles(QQmlListProperty* list); -}; - -} - -#define ProxyRoleContainer_iid "fr.grecko.SortFilterProxyModel.ProxyRoleContainer" -Q_DECLARE_INTERFACE(qqsfpm::ProxyRoleContainer, ProxyRoleContainer_iid) - -#endif // PROXYROLECONTAINER_H diff --git a/client/3rd/SortFilterProxyModel/proxyroles/proxyrolesqmltypes.cpp b/client/3rd/SortFilterProxyModel/proxyroles/proxyrolesqmltypes.cpp deleted file mode 100644 index efea2563..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/proxyrolesqmltypes.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "proxyrole.h" -#include "joinrole.h" -#include "switchrole.h" -#include "expressionrole.h" -#include "regexprole.h" -#include "filterrole.h" -#include -#include - -namespace qqsfpm { - -void registerProxyRoleTypes() { - qmlRegisterUncreatableType("SortFilterProxyModel", 0, 2, "ProxyRole", "ProxyRole is an abstract class"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "JoinRole"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "SwitchRole"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "ExpressionRole"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "RegExpRole"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "FilterRole"); -} - -Q_COREAPP_STARTUP_FUNCTION(registerProxyRoleTypes) - -} diff --git a/client/3rd/SortFilterProxyModel/proxyroles/regexprole.cpp b/client/3rd/SortFilterProxyModel/proxyroles/regexprole.cpp deleted file mode 100644 index fc9784d6..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/regexprole.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "regexprole.h" -#include "qqmlsortfilterproxymodel.h" -#include - -namespace qqsfpm { - -/*! - \qmltype RegExpRole - \inherits ProxyRole - \inqmlmodule SortFilterProxyModel - \ingroup ProxyRoles - \brief A ProxyRole extracting data from a source role via a regular expression. - - A RegExpRole is a \l ProxyRole that provides a role for each named capture group of its regular expression \l pattern. - - In the following example, the \c date role of the source model will be extracted in 3 roles in the proxy moodel: \c year, \c month and \c day. - \code - SortFilterProxyModel { - sourceModel: eventModel - proxyRoles: RegExpRole { - roleName: "date" - pattern: "(?\\d{4})-(?\\d{2})-(?\\d{2})" - } - } - \endcode -*/ - -/*! - \qmlproperty QString RegExpRole::roleName - - This property holds the role name that the RegExpRole is using to query the source model's data to extract new roles from. -*/ -QString RegExpRole::roleName() const -{ - return m_roleName; -} - -void RegExpRole::setRoleName(const QString& roleName) -{ - if (m_roleName == roleName) - return; - - m_roleName = roleName; - Q_EMIT roleNameChanged(); -} - -/*! - \qmlproperty QString RegExpRole::pattern - - This property holds the pattern of the regular expression of this RegExpRole. - The RegExpRole will expose a role for each of the named capture group of the pattern. -*/ -QString RegExpRole::pattern() const -{ - return m_regularExpression.pattern(); -} - -void RegExpRole::setPattern(const QString& pattern) -{ - if (m_regularExpression.pattern() == pattern) - return; - - Q_EMIT namesAboutToBeChanged(); - m_regularExpression.setPattern(pattern); - invalidate(); - Q_EMIT patternChanged(); - Q_EMIT namesChanged(); -} - -/*! - \qmlproperty Qt::CaseSensitivity RegExpRole::caseSensitivity - - This property holds the caseSensitivity of the regular expression. -*/ -Qt::CaseSensitivity RegExpRole::caseSensitivity() const -{ - return m_regularExpression.patternOptions() & QRegularExpression::CaseInsensitiveOption ? - Qt::CaseInsensitive : Qt::CaseSensitive; -} - -void RegExpRole::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity) -{ - if (this->caseSensitivity() == caseSensitivity) - return; - - m_regularExpression.setPatternOptions(m_regularExpression.patternOptions() ^ QRegularExpression::CaseInsensitiveOption); //toggle the option - Q_EMIT caseSensitivityChanged(); -} - -QStringList RegExpRole::names() -{ - QStringList nameCaptureGroups = m_regularExpression.namedCaptureGroups(); - nameCaptureGroups.removeAll(""); - return nameCaptureGroups; -} - -QVariant RegExpRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString &name) -{ - QString text = proxyModel.sourceData(sourceIndex, m_roleName).toString(); - QRegularExpressionMatch match = m_regularExpression.match(text); - return match.hasMatch() ? (match.captured(name)) : QVariant{}; -} - -} diff --git a/client/3rd/SortFilterProxyModel/proxyroles/regexprole.h b/client/3rd/SortFilterProxyModel/proxyroles/regexprole.h deleted file mode 100644 index d0948b78..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/regexprole.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef REGEXPROLE_H -#define REGEXPROLE_H - -#include "proxyrole.h" -#include - -namespace qqsfpm { - -class RegExpRole : public ProxyRole -{ - Q_OBJECT - Q_PROPERTY(QString roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged) - Q_PROPERTY(QString pattern READ pattern WRITE setPattern NOTIFY patternChanged) - Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged) - -public: - using ProxyRole::ProxyRole; - - QString roleName() const; - void setRoleName(const QString& roleName); - - QString pattern() const; - void setPattern(const QString& pattern); - - Qt::CaseSensitivity caseSensitivity() const; - void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity); - - QStringList names() override; - -Q_SIGNALS: - void roleNameChanged(); - void patternChanged(); - void caseSensitivityChanged(); - -private: - QString m_roleName; - QRegularExpression m_regularExpression; - QVariant data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel, const QString &name) override; -}; - -} - -#endif // REGEXPROLE_H diff --git a/client/3rd/SortFilterProxyModel/proxyroles/singlerole.cpp b/client/3rd/SortFilterProxyModel/proxyroles/singlerole.cpp deleted file mode 100644 index fb2ddde8..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/singlerole.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "singlerole.h" -#include - -namespace qqsfpm { - -/*! - \qmltype SingleRole - \qmlabstract - \inherits ProxyRole - \inqmlmodule SortFilterProxyModel - \ingroup ProxyRoles - \brief Base type for the \l SortFilterProxyModel proxy roles defining a single role. - - SingleRole is a convenience base class for proxy roles who define a single role. - It cannot be used directly in a QML file. - It exists to provide a set of common properties and methods, - available across all the other proxy role types that inherit from it. - Attempting to use the SingleRole type directly will result in an error. -*/ -/*! - \qmlproperty string SingleRole::name - - This property holds the role name of the proxy role. -*/ -QString SingleRole::name() const -{ - return m_name; -} - -void SingleRole::setName(const QString& name) -{ - if (m_name == name) - return; - - Q_EMIT namesAboutToBeChanged(); - m_name = name; - Q_EMIT nameChanged(); - Q_EMIT namesChanged(); -} - -QStringList SingleRole::names() -{ - return QStringList { m_name }; -} - -QVariant SingleRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel, const QString &name) -{ - Q_UNUSED(name); - return data(sourceIndex, proxyModel); -} - -} diff --git a/client/3rd/SortFilterProxyModel/proxyroles/singlerole.h b/client/3rd/SortFilterProxyModel/proxyroles/singlerole.h deleted file mode 100644 index 32b2f2b0..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/singlerole.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef SINGLEROLE_H -#define SINGLEROLE_H - -#include "proxyrole.h" - -namespace qqsfpm { - -class SingleRole : public ProxyRole -{ - Q_OBJECT - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) - -public: - using ProxyRole::ProxyRole; - - QString name() const; - void setName(const QString& name); - - QStringList names() override; - -Q_SIGNALS: - void nameChanged(); - -private: - QString m_name; - -private: - QVariant data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel, const QString &name) final; - virtual QVariant data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel) = 0; -}; - -} - -#endif // SINGLEROLE_H diff --git a/client/3rd/SortFilterProxyModel/proxyroles/switchrole.cpp b/client/3rd/SortFilterProxyModel/proxyroles/switchrole.cpp deleted file mode 100644 index f21de849..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/switchrole.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include "switchrole.h" -#include "qqmlsortfilterproxymodel.h" -#include "filters/filter.h" -#include - -namespace qqsfpm { - -/*! - \qmltype SwitchRole - \inherits SingleRole - \inqmlmodule SortFilterProxyModel - \ingroup ProxyRoles - \ingroup FilterContainer - \brief A role using \l Filter to conditionnaly compute its data. - - A SwitchRole is a \l ProxyRole that computes its data with the help of \l Filter. - Each top level filters specified in the \l SwitchRole is evaluated on the rows of the model, if a \l Filter evaluates to true, the data of the \l SwitchRole for this row will be the one of the attached \l {value} {SwitchRole.value} property. - If no top level filters evaluate to true, the data will default to the one of the \l defaultRoleName (or the \l defaultValue if no \l defaultRoleName is specified). - - In the following example, the \c favoriteOrFirstNameSection role is equal to \c * if the \c favorite role of a row is true, otherwise it's the same as the \c firstName role : - \code - SortFilterProxyModel { - sourceModel: contactModel - proxyRoles: SwitchRole { - name: "favoriteOrFirstNameSection" - filters: ValueFilter { - roleName: "favorite" - value: true - SwitchRole.value: "*" - } - defaultRoleName: "firstName" - } - } - \endcode - \sa FilterContainer -*/ -SwitchRoleAttached::SwitchRoleAttached(QObject* parent) : QObject (parent) -{ - if (!qobject_cast(parent)) - qmlInfo(parent) << "SwitchRole must be attached to a Filter"; -} - -/*! - \qmlattachedproperty var SwitchRole::value - - This property attaches a value to a \l Filter. -*/ -QVariant SwitchRoleAttached::value() const -{ - return m_value; -} - -void SwitchRoleAttached::setValue(QVariant value) -{ - if (m_value == value) - return; - - m_value = value; - Q_EMIT valueChanged(); -} - -/*! - \qmlproperty string SwitchRole::defaultRoleName - - This property holds the default role name of the role. - If no filter match a row, the data of this role will be the data of the role whose name is \c defaultRoleName. -*/ -QString SwitchRole::defaultRoleName() const -{ - return m_defaultRoleName; -} - -void SwitchRole::setDefaultRoleName(const QString& defaultRoleName) -{ - if (m_defaultRoleName == defaultRoleName) - return; - - m_defaultRoleName = defaultRoleName; - Q_EMIT defaultRoleNameChanged(); - invalidate(); -} - -/*! - \qmlproperty var SwitchRole::defaultValue - - This property holds the default value of the role. - If no filter match a row, and no \l defaultRoleName is set, the data of this role will be \c defaultValue. -*/ -QVariant SwitchRole::defaultValue() const -{ - return m_defaultValue; -} - -void SwitchRole::setDefaultValue(const QVariant& defaultValue) -{ - if (m_defaultValue == defaultValue) - return; - - m_defaultValue = defaultValue; - Q_EMIT defaultValueChanged(); - invalidate(); -} - -/*! - \qmlproperty list SwitchRole::filters - \default - - This property holds the list of filters for this proxy role. - The data of this role will be equal to the attached \l {value} {SwitchRole.value} property of the first filter that matches the model row. - - \sa Filter, FilterContainer -*/ - -void SwitchRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) -{ - for (Filter* filter : m_filters) - filter->proxyModelCompleted(proxyModel); -} - -SwitchRoleAttached* SwitchRole::qmlAttachedProperties(QObject* object) -{ - return new SwitchRoleAttached(object); -} - -QVariant SwitchRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel) -{ - for (auto filter: m_filters) { - if (!filter->enabled()) - continue; - if (filter->filterAcceptsRow(sourceIndex, proxyModel)) { - auto attached = static_cast(qmlAttachedPropertiesObject(filter, false)); - if (!attached) { - qWarning() << "No SwitchRole.value provided for this filter" << filter; - continue; - } - QVariant value = attached->value(); - if (!value.isValid()) { - qWarning() << "No SwitchRole.value provided for this filter" << filter; - continue; - } - return value; - } - } - if (!m_defaultRoleName.isEmpty()) - return proxyModel.sourceData(sourceIndex, m_defaultRoleName); - return m_defaultValue; -} - -void SwitchRole::onFilterAppended(Filter *filter) -{ - connect(filter, &Filter::invalidated, this, &SwitchRole::invalidate); - auto attached = static_cast(qmlAttachedPropertiesObject(filter, true)); - connect(attached, &SwitchRoleAttached::valueChanged, this, &SwitchRole::invalidate); - invalidate(); -} - -void SwitchRole::onFilterRemoved(Filter *filter) -{ - Q_UNUSED(filter) - invalidate(); -} - -void SwitchRole::onFiltersCleared() -{ - invalidate(); -} - -} diff --git a/client/3rd/SortFilterProxyModel/proxyroles/switchrole.h b/client/3rd/SortFilterProxyModel/proxyroles/switchrole.h deleted file mode 100644 index 203a8ebd..00000000 --- a/client/3rd/SortFilterProxyModel/proxyroles/switchrole.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef SWITCHROLE_H -#define SWITCHROLE_H - -#include "singlerole.h" -#include "filters/filtercontainer.h" -#include - -namespace qqsfpm { - -class SwitchRoleAttached : public QObject -{ - Q_OBJECT - Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) -public: - SwitchRoleAttached(QObject* parent); - - QVariant value() const; - void setValue(QVariant value); - -Q_SIGNALS: - void valueChanged(); - -private: - QVariant m_value; -}; - -class SwitchRole : public SingleRole, public FilterContainer -{ - Q_OBJECT - Q_INTERFACES(qqsfpm::FilterContainer) - Q_PROPERTY(QString defaultRoleName READ defaultRoleName WRITE setDefaultRoleName NOTIFY defaultRoleNameChanged) - Q_PROPERTY(QVariant defaultValue READ defaultValue WRITE setDefaultValue NOTIFY defaultValueChanged) - Q_PROPERTY(QQmlListProperty filters READ filtersListProperty) - Q_CLASSINFO("DefaultProperty", "filters") - -public: - using SingleRole::SingleRole; - - QString defaultRoleName() const; - void setDefaultRoleName(const QString& defaultRoleName); - - QVariant defaultValue() const; - void setDefaultValue(const QVariant& defaultValue); - - void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override; - - static SwitchRoleAttached* qmlAttachedProperties(QObject* object); - -Q_SIGNALS: - void defaultRoleNameChanged(); - void defaultValueChanged(); - -private: - QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override; - - void onFilterAppended(Filter *filter) override; - void onFilterRemoved(Filter *filter) override; - void onFiltersCleared() override; - - QString m_defaultRoleName; - QVariant m_defaultValue; -}; - -} - -QML_DECLARE_TYPEINFO(qqsfpm::SwitchRole, QML_HAS_ATTACHED_PROPERTIES) - -#endif // SWITCHROLE_H diff --git a/client/3rd/SortFilterProxyModel/qqmlsortfilterproxymodel.cpp b/client/3rd/SortFilterProxyModel/qqmlsortfilterproxymodel.cpp deleted file mode 100644 index bd064357..00000000 --- a/client/3rd/SortFilterProxyModel/qqmlsortfilterproxymodel.cpp +++ /dev/null @@ -1,579 +0,0 @@ -#include "qqmlsortfilterproxymodel.h" -#include -#include -#include "filters/filter.h" -#include "sorters/sorter.h" -#include "proxyroles/proxyrole.h" - -namespace qqsfpm { - -/*! - \qmltype SortFilterProxyModel - \inqmlmodule SortFilterProxyModel - \ingroup SortFilterProxyModel - \ingroup FilterContainer - \ingroup SorterContainer - \brief Filters and sorts data coming from a source \l {http://doc.qt.io/qt-5/qabstractitemmodel.html} {QAbstractItemModel}. - - The SortFilterProxyModel type provides support for filtering and sorting data coming from a source model. - \sa FilterContainer, SorterContainer -*/ -QQmlSortFilterProxyModel::QQmlSortFilterProxyModel(QObject *parent) : - QSortFilterProxyModel(parent), -#ifdef SFPM_DELAYED - m_delayed(true) -#else - m_delayed(false) -#endif -{ - connect(this, &QAbstractProxyModel::sourceModelChanged, this, &QQmlSortFilterProxyModel::updateRoles); - connect(this, &QAbstractItemModel::modelReset, this, &QQmlSortFilterProxyModel::updateRoles); - connect(this, &QAbstractItemModel::rowsInserted, this, &QQmlSortFilterProxyModel::countChanged); - connect(this, &QAbstractItemModel::rowsRemoved, this, &QQmlSortFilterProxyModel::countChanged); - connect(this, &QAbstractItemModel::modelReset, this, &QQmlSortFilterProxyModel::countChanged); - connect(this, &QAbstractItemModel::layoutChanged, this, &QQmlSortFilterProxyModel::countChanged); - connect(this, &QAbstractItemModel::dataChanged, this, &QQmlSortFilterProxyModel::onDataChanged); - setDynamicSortFilter(true); -} - -/*! - \qmlproperty QAbstractItemModel* SortFilterProxyModel::sourceModel - - The source model of this proxy model -*/ - -/*! - \qmlproperty int SortFilterProxyModel::count - - The number of rows in the proxy model (not filtered out the source model) -*/ -int QQmlSortFilterProxyModel::count() const -{ - return rowCount(); -} - -/*! - \qmlproperty bool SortFilterProxyModel::delayed - - Delay the execution of filters, sorters and proxyRoles until the next event loop. - This can be used as an optimization when multiple filters, sorters or proxyRoles are changed in a single event loop. - They will be executed once in a single batch at the next event loop instead of being executed in multiple sequential batches. - - By default, the SortFilterProxyModel is not delayed, unless the \c SFPM_DELAYED environment variable is defined at compile time. -*/ -bool QQmlSortFilterProxyModel::delayed() const -{ - return m_delayed; -} - -void QQmlSortFilterProxyModel::setDelayed(bool delayed) -{ - if (m_delayed == delayed) - return; - - m_delayed = delayed; - Q_EMIT delayedChanged(); -} - -const QString& QQmlSortFilterProxyModel::filterRoleName() const -{ - return m_filterRoleName; -} - -void QQmlSortFilterProxyModel::setFilterRoleName(const QString& filterRoleName) -{ - if (m_filterRoleName == filterRoleName) - return; - - m_filterRoleName = filterRoleName; - updateFilterRole(); - Q_EMIT filterRoleNameChanged(); -} - -QString QQmlSortFilterProxyModel::filterPattern() const -{ - return filterRegExp().pattern(); -} - -void QQmlSortFilterProxyModel::setFilterPattern(const QString& filterPattern) -{ - QRegExp regExp = filterRegExp(); - if (regExp.pattern() == filterPattern) - return; - - regExp.setPattern(filterPattern); - QSortFilterProxyModel::setFilterRegExp(regExp); - Q_EMIT filterPatternChanged(); -} - -QQmlSortFilterProxyModel::PatternSyntax QQmlSortFilterProxyModel::filterPatternSyntax() const -{ - return static_cast(filterRegExp().patternSyntax()); -} - -void QQmlSortFilterProxyModel::setFilterPatternSyntax(QQmlSortFilterProxyModel::PatternSyntax patternSyntax) -{ - QRegExp regExp = filterRegExp(); - QRegExp::PatternSyntax patternSyntaxTmp = static_cast(patternSyntax); - if (regExp.patternSyntax() == patternSyntaxTmp) - return; - - regExp.setPatternSyntax(patternSyntaxTmp); - QSortFilterProxyModel::setFilterRegExp(regExp); - Q_EMIT filterPatternSyntaxChanged(); -} - -const QVariant& QQmlSortFilterProxyModel::filterValue() const -{ - return m_filterValue; -} - -void QQmlSortFilterProxyModel::setFilterValue(const QVariant& filterValue) -{ - if (m_filterValue == filterValue) - return; - - m_filterValue = filterValue; - queueInvalidateFilter(); - Q_EMIT filterValueChanged(); -} - -/*! - \qmlproperty string SortFilterProxyModel::sortRoleName - - The role name of the source model's data used for the sorting. - - \sa {http://doc.qt.io/qt-5/qsortfilterproxymodel.html#sortRole-prop} {sortRole}, roleForName -*/ -const QString& QQmlSortFilterProxyModel::sortRoleName() const -{ - return m_sortRoleName; -} - -void QQmlSortFilterProxyModel::setSortRoleName(const QString& sortRoleName) -{ - if (m_sortRoleName == sortRoleName) - return; - - m_sortRoleName = sortRoleName; - updateSortRole(); - Q_EMIT sortRoleNameChanged(); -} - -bool QQmlSortFilterProxyModel::ascendingSortOrder() const -{ - return m_ascendingSortOrder; -} - -void QQmlSortFilterProxyModel::setAscendingSortOrder(bool ascendingSortOrder) -{ - if (m_ascendingSortOrder == ascendingSortOrder) - return; - - m_ascendingSortOrder = ascendingSortOrder; - Q_EMIT ascendingSortOrderChanged(); - queueInvalidate(); -} - -/*! - \qmlproperty list SortFilterProxyModel::filters - - This property holds the list of filters for this proxy model. To be included in the model, a row of the source model has to be accepted by all the top level filters of this list. - - \sa Filter, FilterContainer -*/ - -/*! - \qmlproperty list SortFilterProxyModel::sorters - - This property holds the list of sorters for this proxy model. The rows of the source model are sorted by the sorters of this list, in their order of insertion. - - \sa Sorter, SorterContainer -*/ - -/*! - \qmlproperty list SortFilterProxyModel::proxyRoles - - This property holds the list of proxy roles for this proxy model. Each proxy role adds a new custom role to the model. - - \sa ProxyRole -*/ - -void QQmlSortFilterProxyModel::classBegin() -{ - -} - -void QQmlSortFilterProxyModel::componentComplete() -{ - m_completed = true; - - for (const auto& filter : m_filters) - filter->proxyModelCompleted(*this); - for (const auto& sorter : m_sorters) - sorter->proxyModelCompleted(*this); - for (const auto& proxyRole : m_proxyRoles) - proxyRole->proxyModelCompleted(*this); - - invalidate(); - sort(0); -} - -QVariant QQmlSortFilterProxyModel::sourceData(const QModelIndex& sourceIndex, const QString& roleName) const -{ - int role = roleNames().key(roleName.toUtf8()); - return sourceData(sourceIndex, role); -} - -QVariant QQmlSortFilterProxyModel::sourceData(const QModelIndex &sourceIndex, int role) const -{ - QPair proxyRolePair = m_proxyRoleMap[role]; - if (ProxyRole* proxyRole = proxyRolePair.first) - return proxyRole->roleData(sourceIndex, *this, proxyRolePair.second); - else - return sourceModel()->data(sourceIndex, role); -} - -QVariant QQmlSortFilterProxyModel::data(const QModelIndex &index, int role) const -{ - return sourceData(mapToSource(index), role); -} - -QHash QQmlSortFilterProxyModel::roleNames() const -{ - return m_roleNames.isEmpty() && sourceModel() ? sourceModel()->roleNames() : m_roleNames; -} - -/*! - \qmlmethod int SortFilterProxyModel::roleForName(string roleName) - - Returns the role number for the given \a roleName. - If no role is found for this \a roleName, \c -1 is returned. -*/ - -int QQmlSortFilterProxyModel::roleForName(const QString& roleName) const -{ - return m_roleNames.key(roleName.toUtf8(), -1); -} - -/*! - \qmlmethod object SortFilterProxyModel::get(int row) - - Return the item at \a row in the proxy model as a map of all its roles. This allows the item data to be read (not modified) from JavaScript. -*/ -QVariantMap QQmlSortFilterProxyModel::get(int row) const -{ - QVariantMap map; - QModelIndex modelIndex = index(row, 0); - QHash roles = roleNames(); - for (QHash::const_iterator it = roles.begin(); it != roles.end(); ++it) - map.insert(it.value(), data(modelIndex, it.key())); - return map; -} - -/*! - \qmlmethod variant SortFilterProxyModel::get(int row, string roleName) - - Return the data for the given \a roleName of the item at \a row in the proxy model. This allows the role data to be read (not modified) from JavaScript. - This equivalent to calling \c {data(index(row, 0), roleForName(roleName))}. -*/ -QVariant QQmlSortFilterProxyModel::get(int row, const QString& roleName) const -{ - return data(index(row, 0), roleForName(roleName)); -} - -/*! - \qmlmethod index SortFilterProxyModel::mapToSource(index proxyIndex) - - Returns the source model index corresponding to the given \a proxyIndex from the SortFilterProxyModel. -*/ -QModelIndex QQmlSortFilterProxyModel::mapToSource(const QModelIndex& proxyIndex) const -{ - return QSortFilterProxyModel::mapToSource(proxyIndex); -} - -/*! - \qmlmethod int SortFilterProxyModel::mapToSource(int proxyRow) - - Returns the source model row corresponding to the given \a proxyRow from the SortFilterProxyModel. - Returns -1 if there is no corresponding row. -*/ -int QQmlSortFilterProxyModel::mapToSource(int proxyRow) const -{ - QModelIndex proxyIndex = index(proxyRow, 0); - QModelIndex sourceIndex = mapToSource(proxyIndex); - return sourceIndex.isValid() ? sourceIndex.row() : -1; -} - -/*! - \qmlmethod QModelIndex SortFilterProxyModel::mapFromSource(QModelIndex sourceIndex) - - Returns the model index in the SortFilterProxyModel given the \a sourceIndex from the source model. -*/ -QModelIndex QQmlSortFilterProxyModel::mapFromSource(const QModelIndex& sourceIndex) const -{ - return QSortFilterProxyModel::mapFromSource(sourceIndex); -} - -/*! - \qmlmethod int SortFilterProxyModel::mapFromSource(int sourceRow) - - Returns the row in the SortFilterProxyModel given the \a sourceRow from the source model. - Returns -1 if there is no corresponding row. -*/ -int QQmlSortFilterProxyModel::mapFromSource(int sourceRow) const -{ - QModelIndex proxyIndex; - if (QAbstractItemModel* source = sourceModel()) { - QModelIndex sourceIndex = source->index(sourceRow, 0); - proxyIndex = mapFromSource(sourceIndex); - } - return proxyIndex.isValid() ? proxyIndex.row() : -1; -} - -bool QQmlSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const -{ - if (!m_completed) - return true; - QModelIndex sourceIndex = sourceModel()->index(source_row, 0, source_parent); - bool valueAccepted = !m_filterValue.isValid() || ( m_filterValue == sourceModel()->data(sourceIndex, filterRole()) ); - bool baseAcceptsRow = valueAccepted && QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); - baseAcceptsRow = baseAcceptsRow && std::all_of(m_filters.begin(), m_filters.end(), - [=, &source_parent] (Filter* filter) { - return filter->filterAcceptsRow(sourceIndex, *this); - } - ); - return baseAcceptsRow; -} - -bool QQmlSortFilterProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const -{ - if (m_completed) { - if (!m_sortRoleName.isEmpty()) { - if (QSortFilterProxyModel::lessThan(source_left, source_right)) - return m_ascendingSortOrder; - if (QSortFilterProxyModel::lessThan(source_right, source_left)) - return !m_ascendingSortOrder; - } - auto sortedSorters = m_sorters; - std::stable_sort(sortedSorters.begin(), - sortedSorters.end(), - [] (Sorter* a, Sorter* b) { - return a->priority() > b->priority(); - }); - for(auto sorter : sortedSorters) { - if (sorter->enabled()) { - int comparison = sorter->compareRows(source_left, source_right, *this); - if (comparison != 0) - return comparison < 0; - } - } - } - return source_left.row() < source_right.row(); -} - -void QQmlSortFilterProxyModel::resetInternalData() -{ - QSortFilterProxyModel::resetInternalData(); - updateRoleNames(); -} - -void QQmlSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) -{ - if (sourceModel && sourceModel->roleNames().isEmpty()) { // workaround for when a model has no roles and roles are added when the model is populated (ListModel) - // QTBUG-57971 - connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &QQmlSortFilterProxyModel::initRoles); - } - QSortFilterProxyModel::setSourceModel(sourceModel); -} - -void QQmlSortFilterProxyModel::queueInvalidateFilter() -{ - if (m_delayed) { - if (!m_invalidateFilterQueued && !m_invalidateQueued) { - m_invalidateFilterQueued = true; - QMetaObject::invokeMethod(this, "invalidateFilter", Qt::QueuedConnection); - } - } else { - invalidateFilter(); - } -} - -void QQmlSortFilterProxyModel::invalidateFilter() -{ - m_invalidateFilterQueued = false; - if (m_completed && !m_invalidateQueued) - QSortFilterProxyModel::invalidateFilter(); -} - -void QQmlSortFilterProxyModel::queueInvalidate() -{ - if (m_delayed) { - if (!m_invalidateQueued) { - m_invalidateQueued = true; - QMetaObject::invokeMethod(this, "invalidate", Qt::QueuedConnection); - } - } else { - invalidate(); - } -} - -void QQmlSortFilterProxyModel::invalidate() -{ - m_invalidateQueued = false; - if (m_completed) - QSortFilterProxyModel::invalidate(); -} - -void QQmlSortFilterProxyModel::updateRoleNames() -{ - if (!sourceModel()) - return; - m_roleNames = sourceModel()->roleNames(); - m_proxyRoleMap.clear(); - m_proxyRoleNumbers.clear(); - - auto roles = m_roleNames.keys(); - auto maxIt = std::max_element(roles.cbegin(), roles.cend()); - int maxRole = maxIt != roles.cend() ? *maxIt : -1; - for (auto proxyRole : m_proxyRoles) { - for (auto roleName : proxyRole->names()) { - ++maxRole; - m_roleNames[maxRole] = roleName.toUtf8(); - m_proxyRoleMap[maxRole] = {proxyRole, roleName}; - m_proxyRoleNumbers.append(maxRole); - } - } -} - -void QQmlSortFilterProxyModel::updateFilterRole() -{ - QList filterRoles = roleNames().keys(m_filterRoleName.toUtf8()); - if (!filterRoles.empty()) - { - setFilterRole(filterRoles.first()); - } -} - -void QQmlSortFilterProxyModel::updateSortRole() -{ - QList sortRoles = roleNames().keys(m_sortRoleName.toUtf8()); - if (!sortRoles.empty()) - { - setSortRole(sortRoles.first()); - queueInvalidate(); - } -} - -void QQmlSortFilterProxyModel::updateRoles() -{ - updateFilterRole(); - updateSortRole(); -} - -void QQmlSortFilterProxyModel::initRoles() -{ - disconnect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &QQmlSortFilterProxyModel::initRoles); - resetInternalData(); - updateRoles(); -} - -void QQmlSortFilterProxyModel::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) -{ - Q_UNUSED(roles); - if (!roles.isEmpty() && !m_proxyRoleNumbers.empty() && roles != m_proxyRoleNumbers) - Q_EMIT dataChanged(topLeft, bottomRight, m_proxyRoleNumbers); -} - -void QQmlSortFilterProxyModel::queueInvalidateProxyRoles() -{ - queueInvalidate(); - if (m_delayed) { - if (!m_invalidateProxyRolesQueued) { - m_invalidateProxyRolesQueued = true; - QMetaObject::invokeMethod(this, "invalidateProxyRoles", Qt::QueuedConnection); - } - } else { - invalidateProxyRoles(); - } -} - -void QQmlSortFilterProxyModel::invalidateProxyRoles() -{ - m_invalidateProxyRolesQueued = false; - if (m_completed) - Q_EMIT dataChanged(index(0,0), index(rowCount() - 1, columnCount() - 1), m_proxyRoleNumbers); -} - -QVariantMap QQmlSortFilterProxyModel::modelDataMap(const QModelIndex& modelIndex) const -{ - QVariantMap map; - QHash roles = roleNames(); - for (QHash::const_iterator it = roles.begin(); it != roles.end(); ++it) - map.insert(it.value(), sourceModel()->data(modelIndex, it.key())); - return map; -} - -void QQmlSortFilterProxyModel::onFilterAppended(Filter* filter) -{ - connect(filter, &Filter::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidateFilter); - queueInvalidateFilter(); -} - -void QQmlSortFilterProxyModel::onFilterRemoved(Filter* filter) -{ - Q_UNUSED(filter) - queueInvalidateFilter(); -} - -void QQmlSortFilterProxyModel::onFiltersCleared() -{ - queueInvalidateFilter(); -} - -void QQmlSortFilterProxyModel::onSorterAppended(Sorter* sorter) -{ - connect(sorter, &Sorter::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidate); - queueInvalidate(); -} - -void QQmlSortFilterProxyModel::onSorterRemoved(Sorter* sorter) -{ - Q_UNUSED(sorter) - queueInvalidate(); -} - -void QQmlSortFilterProxyModel::onSortersCleared() -{ - queueInvalidate(); -} - -void QQmlSortFilterProxyModel::onProxyRoleAppended(ProxyRole *proxyRole) -{ - beginResetModel(); - connect(proxyRole, &ProxyRole::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidateProxyRoles); - connect(proxyRole, &ProxyRole::namesAboutToBeChanged, this, &QQmlSortFilterProxyModel::beginResetModel); - connect(proxyRole, &ProxyRole::namesChanged, this, &QQmlSortFilterProxyModel::endResetModel); - endResetModel(); -} - -void QQmlSortFilterProxyModel::onProxyRoleRemoved(ProxyRole *proxyRole) -{ - Q_UNUSED(proxyRole) - beginResetModel(); - endResetModel(); -} - -void QQmlSortFilterProxyModel::onProxyRolesCleared() -{ - beginResetModel(); - endResetModel(); -} - -void registerQQmlSortFilterProxyModelTypes() { - qmlRegisterType("SortFilterProxyModel", 0, 2, "SortFilterProxyModel"); -} - -Q_COREAPP_STARTUP_FUNCTION(registerQQmlSortFilterProxyModelTypes) - -} diff --git a/client/3rd/SortFilterProxyModel/qqmlsortfilterproxymodel.h b/client/3rd/SortFilterProxyModel/qqmlsortfilterproxymodel.h deleted file mode 100644 index dbe0229b..00000000 --- a/client/3rd/SortFilterProxyModel/qqmlsortfilterproxymodel.h +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef QQMLSORTFILTERPROXYMODEL_H -#define QQMLSORTFILTERPROXYMODEL_H - -#include -#include -#include "filters/filtercontainer.h" -#include "sorters/sortercontainer.h" -#include "proxyroles/proxyrolecontainer.h" - -namespace qqsfpm { - -class QQmlSortFilterProxyModel : public QSortFilterProxyModel, - public QQmlParserStatus, - public FilterContainer, - public SorterContainer, - public ProxyRoleContainer -{ - Q_OBJECT - Q_INTERFACES(QQmlParserStatus) - Q_INTERFACES(qqsfpm::FilterContainer) - Q_INTERFACES(qqsfpm::SorterContainer) - Q_INTERFACES(qqsfpm::ProxyRoleContainer) - - Q_PROPERTY(int count READ count NOTIFY countChanged) - Q_PROPERTY(bool delayed READ delayed WRITE setDelayed NOTIFY delayedChanged) - - Q_PROPERTY(QString filterRoleName READ filterRoleName WRITE setFilterRoleName NOTIFY filterRoleNameChanged) - Q_PROPERTY(QString filterPattern READ filterPattern WRITE setFilterPattern NOTIFY filterPatternChanged) - Q_PROPERTY(PatternSyntax filterPatternSyntax READ filterPatternSyntax WRITE setFilterPatternSyntax NOTIFY filterPatternSyntaxChanged) - Q_PROPERTY(QVariant filterValue READ filterValue WRITE setFilterValue NOTIFY filterValueChanged) - - Q_PROPERTY(QString sortRoleName READ sortRoleName WRITE setSortRoleName NOTIFY sortRoleNameChanged) - Q_PROPERTY(bool ascendingSortOrder READ ascendingSortOrder WRITE setAscendingSortOrder NOTIFY ascendingSortOrderChanged) - - Q_PROPERTY(QQmlListProperty filters READ filtersListProperty) - Q_PROPERTY(QQmlListProperty sorters READ sortersListProperty) - Q_PROPERTY(QQmlListProperty proxyRoles READ proxyRolesListProperty) - -public: - enum PatternSyntax { - RegExp = QRegExp::RegExp, - Wildcard = QRegExp::Wildcard, - FixedString = QRegExp::FixedString, - RegExp2 = QRegExp::RegExp2, - WildcardUnix = QRegExp::WildcardUnix, - W3CXmlSchema11 = QRegExp::W3CXmlSchema11 }; - Q_ENUMS(PatternSyntax) - - QQmlSortFilterProxyModel(QObject* parent = 0); - - int count() const; - - bool delayed() const; - void setDelayed(bool delayed); - - const QString& filterRoleName() const; - void setFilterRoleName(const QString& filterRoleName); - - QString filterPattern() const; - void setFilterPattern(const QString& filterPattern); - - PatternSyntax filterPatternSyntax() const; - void setFilterPatternSyntax(PatternSyntax patternSyntax); - - const QVariant& filterValue() const; - void setFilterValue(const QVariant& filterValue); - - const QString& sortRoleName() const; - void setSortRoleName(const QString& sortRoleName); - - bool ascendingSortOrder() const; - void setAscendingSortOrder(bool ascendingSortOrder); - - void classBegin() override; - void componentComplete() override; - - QVariant sourceData(const QModelIndex& sourceIndex, const QString& roleName) const; - QVariant sourceData(const QModelIndex& sourceIndex, int role) const; - - QVariant data(const QModelIndex& index, int role) const override; - QHash roleNames() const override; - - Q_INVOKABLE int roleForName(const QString& roleName) const; - - Q_INVOKABLE QVariantMap get(int row) const; - Q_INVOKABLE QVariant get(int row, const QString& roleName) const; - - Q_INVOKABLE QModelIndex mapToSource(const QModelIndex& proxyIndex) const override; - Q_INVOKABLE int mapToSource(int proxyRow) const; - Q_INVOKABLE QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override; - Q_INVOKABLE int mapFromSource(int sourceRow) const; - - void setSourceModel(QAbstractItemModel *sourceModel) override; - -Q_SIGNALS: - void countChanged(); - void delayedChanged(); - - void filterRoleNameChanged(); - void filterPatternSyntaxChanged(); - void filterPatternChanged(); - void filterValueChanged(); - - void sortRoleNameChanged(); - void ascendingSortOrderChanged(); - -protected: - bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; - bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; - -protected Q_SLOTS: - void resetInternalData(); - -private Q_SLOTS: - void queueInvalidateFilter(); - void invalidateFilter(); - void queueInvalidate(); - void invalidate(); - void updateRoleNames(); - void updateFilterRole(); - void updateSortRole(); - void updateRoles(); - void initRoles(); - void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles); - void queueInvalidateProxyRoles(); - void invalidateProxyRoles(); - -private: - QVariantMap modelDataMap(const QModelIndex& modelIndex) const; - - void onFilterAppended(Filter* filter) override; - void onFilterRemoved(Filter* filter) override; - void onFiltersCleared() override; - - void onSorterAppended(Sorter* sorter) override; - void onSorterRemoved(Sorter* sorter) override; - void onSortersCleared() override; - - void onProxyRoleAppended(ProxyRole *proxyRole) override; - void onProxyRoleRemoved(ProxyRole *proxyRole) override; - void onProxyRolesCleared() override; - - bool m_delayed; - QString m_filterRoleName; - QVariant m_filterValue; - QString m_sortRoleName; - bool m_ascendingSortOrder = true; - bool m_completed = false; - QHash m_roleNames; - QHash> m_proxyRoleMap; - QVector m_proxyRoleNumbers; - - bool m_invalidateFilterQueued = false; - bool m_invalidateQueued = false; - bool m_invalidateProxyRolesQueued = false; -}; - -} - -#endif // QQMLSORTFILTERPROXYMODEL_H diff --git a/client/3rd/SortFilterProxyModel/sorters/expressionsorter.cpp b/client/3rd/SortFilterProxyModel/sorters/expressionsorter.cpp deleted file mode 100644 index 7bb92067..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/expressionsorter.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "expressionsorter.h" -#include "qqmlsortfilterproxymodel.h" -#include - -namespace qqsfpm { - -/*! - \qmltype ExpressionSorter - \inherits Sorter - \inqmlmodule SortFilterProxyModel - \ingroup Sorters - \brief Sorts row with a custom javascript expression. - - An ExpressionSorter is a \l Sorter allowing to implement custom sorting based on a javascript expression. -*/ - -/*! - \qmlproperty expression ExpressionSorter::expression - - An expression to implement custom sorting. It must evaluate to a bool. - It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding}, except that it will be evaluated for each of the source model's rows. - Model data is accessible for both rows with the \c modelLeft, and \c modelRight properties: - - \code - sorters: ExpressionSorter { - expression: { - return modelLeft.someRole < modelRight.someRole; - } - } - \endcode - - The \c index of the row is also available through \c modelLeft and \c modelRight. - - The expression should return \c true if the value of the left item is less than the value of the right item, otherwise returns false. - - This expression is reevaluated for a row every time its model data changes. - When an external property (not \c index* or in \c model*) the expression depends on changes, the expression is reevaluated for every row of the source model. - To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine. - This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes. - - A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression. -*/ -const QQmlScriptString& ExpressionSorter::expression() const -{ - return m_scriptString; -} - -void ExpressionSorter::setExpression(const QQmlScriptString& scriptString) -{ - if (m_scriptString == scriptString) - return; - - m_scriptString = scriptString; - updateExpression(); - - Q_EMIT expressionChanged(); - invalidate(); -} - -void ExpressionSorter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) -{ - updateContext(proxyModel); -} - -bool evaluateBoolExpression(QQmlExpression& expression) -{ - QVariant variantResult = expression.evaluate(); - if (expression.hasError()) { - qWarning() << expression.error(); - return false; - } - if (variantResult.canConvert()) { - return variantResult.toBool(); - } else { - qWarning("%s:%i:%i : Can't convert result to bool", - expression.sourceFile().toUtf8().data(), - expression.lineNumber(), - expression.columnNumber()); - return false; - } -} - -int ExpressionSorter::compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const -{ - if (!m_scriptString.isEmpty()) { - QVariantMap modelLeftMap, modelRightMap; - QHash roles = proxyModel.roleNames(); - - QQmlContext context(qmlContext(this)); - - for (auto it = roles.cbegin(); it != roles.cend(); ++it) { - modelLeftMap.insert(it.value(), proxyModel.sourceData(sourceLeft, it.key())); - modelRightMap.insert(it.value(), proxyModel.sourceData(sourceRight, it.key())); - } - modelLeftMap.insert("index", sourceLeft.row()); - modelRightMap.insert("index", sourceRight.row()); - - QQmlExpression expression(m_scriptString, &context); - - context.setContextProperty("modelLeft", modelLeftMap); - context.setContextProperty("modelRight", modelRightMap); - if (evaluateBoolExpression(expression)) - return -1; - - context.setContextProperty("modelLeft", modelRightMap); - context.setContextProperty("modelRight", modelLeftMap); - if (evaluateBoolExpression(expression)) - return 1; - } - return 0; -} - -void ExpressionSorter::updateContext(const QQmlSortFilterProxyModel& proxyModel) -{ - delete m_context; - m_context = new QQmlContext(qmlContext(this), this); - - QVariantMap modelLeftMap, modelRightMap; - // what about roles changes ? - - for (const QByteArray& roleName : proxyModel.roleNames().values()) { - modelLeftMap.insert(roleName, QVariant()); - modelRightMap.insert(roleName, QVariant()); - } - modelLeftMap.insert("index", -1); - modelRightMap.insert("index", -1); - - m_context->setContextProperty("modelLeft", modelLeftMap); - m_context->setContextProperty("modelRight", modelRightMap); - - updateExpression(); -} - -void ExpressionSorter::updateExpression() -{ - if (!m_context) - return; - - delete m_expression; - m_expression = new QQmlExpression(m_scriptString, m_context, 0, this); - connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionSorter::invalidate); - m_expression->setNotifyOnValueChanged(true); - m_expression->evaluate(); -} - -} diff --git a/client/3rd/SortFilterProxyModel/sorters/expressionsorter.h b/client/3rd/SortFilterProxyModel/sorters/expressionsorter.h deleted file mode 100644 index 6ca17fd0..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/expressionsorter.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef EXPRESSIONSORTER_H -#define EXPRESSIONSORTER_H - -#include "sorter.h" -#include - -class QQmlExpression; - -namespace qqsfpm { - -class QQmlSortFilterProxyModel; - -class ExpressionSorter : public Sorter -{ - Q_OBJECT - Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged) - -public: - using Sorter::Sorter; - - const QQmlScriptString& expression() const; - void setExpression(const QQmlScriptString& scriptString); - - void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override; - -Q_SIGNALS: - void expressionChanged(); - -protected: - int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override; - -private: - void updateContext(const QQmlSortFilterProxyModel& proxyModel); - void updateExpression(); - - QQmlScriptString m_scriptString; - QQmlExpression* m_expression = nullptr; - QQmlContext* m_context = nullptr; -}; - -} - -#endif // EXPRESSIONSORTER_H diff --git a/client/3rd/SortFilterProxyModel/sorters/filtersorter.cpp b/client/3rd/SortFilterProxyModel/sorters/filtersorter.cpp deleted file mode 100644 index 185c5043..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/filtersorter.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "filtersorter.h" -#include "filters/filter.h" - -namespace qqsfpm { - -/*! - \qmltype FilterSorter - \inherits Sorter - \inqmlmodule SortFilterProxyModel - \ingroup Sorters - \ingroup FilterContainer - \brief Sorts rows based on if they match filters. - - A FilterSorter is a \l Sorter that orders row matching its filters before the rows not matching the filters. - - In the following example, rows with their \c favorite role set to \c true will be ordered at the beginning : - \code - SortFilterProxyModel { - sourceModel: contactModel - sorters: FilterSorter { - ValueFilter { roleName: "favorite"; value: true } - } - } - \endcode - \sa FilterContainer -*/ - -/*! - \qmlproperty list FilterSorter::filters - \default - - This property holds the list of filters for this filter sorter. - If a row match all this FilterSorter's filters, it will be ordered before rows not matching all the filters. - - \sa Filter, FilterContainer -*/ - -int FilterSorter::compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel &proxyModel) const -{ - bool leftIsAccepted = indexIsAccepted(sourceLeft, proxyModel); - bool rightIsAccepted = indexIsAccepted(sourceRight, proxyModel); - - if (leftIsAccepted == rightIsAccepted) - return 0; - - return leftIsAccepted ? -1 : 1; -} - -void FilterSorter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) -{ - for (Filter* filter : m_filters) - filter->proxyModelCompleted(proxyModel); -} - -void FilterSorter::onFilterAppended(Filter* filter) -{ - connect(filter, &Filter::invalidated, this, &FilterSorter::invalidate); - invalidate(); -} - -void FilterSorter::onFilterRemoved(Filter* filter) -{ - disconnect(filter, &Filter::invalidated, this, &FilterSorter::invalidate); - invalidate(); -} - -void FilterSorter::onFiltersCleared() -{ - invalidate(); -} - -bool FilterSorter::indexIsAccepted(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const -{ - return std::all_of(m_filters.begin(), m_filters.end(), - [&] (Filter* filter) { - return filter->filterAcceptsRow(sourceIndex, proxyModel); - } - ); -} - -} diff --git a/client/3rd/SortFilterProxyModel/sorters/filtersorter.h b/client/3rd/SortFilterProxyModel/sorters/filtersorter.h deleted file mode 100644 index b1406438..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/filtersorter.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef FILTERSORTER_H -#define FILTERSORTER_H - -#include "sorter.h" -#include "filters/filtercontainer.h" - -namespace qqsfpm { - -class FilterSorter : public Sorter, public FilterContainer -{ - Q_OBJECT - Q_INTERFACES(qqsfpm::FilterContainer) - Q_PROPERTY(QQmlListProperty filters READ filtersListProperty) - Q_CLASSINFO("DefaultProperty", "filters") - -public: - using Sorter::Sorter; - -protected: - int compare(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel &proxyModel) const override; - -private: - void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override; - void onFilterAppended(Filter *filter) override; - void onFilterRemoved(Filter *filter) override; - void onFiltersCleared() override; - - bool indexIsAccepted(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel) const; -}; - -} - -#endif // FILTERSORTER_H diff --git a/client/3rd/SortFilterProxyModel/sorters/rolesorter.cpp b/client/3rd/SortFilterProxyModel/sorters/rolesorter.cpp deleted file mode 100644 index db2d4464..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/rolesorter.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "rolesorter.h" -#include "qqmlsortfilterproxymodel.h" - -namespace qqsfpm { - -/*! - \qmltype RoleSorter - \inherits Sorter - \inqmlmodule SortFilterProxyModel - \ingroup Sorters - \brief Sorts rows based on a source model role. - - A RoleSorter is a simple \l Sorter that sorts rows based on a source model role. - - In the following example, rows with be sorted by their \c lastName role : - \code - SortFilterProxyModel { - sourceModel: contactModel - sorters: RoleSorter { roleName: "lastName" } - } - \endcode -*/ - -/*! - \qmlproperty string RoleSorter::roleName - - This property holds the role name that the sorter is using to query the source model's data when sorting items. -*/ -const QString& RoleSorter::roleName() const -{ - return m_roleName; -} - -void RoleSorter::setRoleName(const QString& roleName) -{ - if (m_roleName == roleName) - return; - - m_roleName = roleName; - Q_EMIT roleNameChanged(); - invalidate(); -} - -QPair RoleSorter::sourceData(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const -{ - QPair pair; - int role = proxyModel.roleForName(m_roleName); - - if (role == -1) - return pair; - - pair.first = proxyModel.sourceData(sourceLeft, role); - pair.second = proxyModel.sourceData(sourceRight, role); - return pair; -} - -int RoleSorter::compare(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const -{ - QPair pair = sourceData(sourceLeft, sourceRight, proxyModel); - QVariant leftValue = pair.first; - QVariant rightValue = pair.second; - if (leftValue < rightValue) - return -1; - if (leftValue > rightValue) - return 1; - return 0; -} - -} diff --git a/client/3rd/SortFilterProxyModel/sorters/rolesorter.h b/client/3rd/SortFilterProxyModel/sorters/rolesorter.h deleted file mode 100644 index 9cc11bd1..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/rolesorter.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef ROLESORTER_H -#define ROLESORTER_H - -#include "sorter.h" - -namespace qqsfpm { - -class RoleSorter : public Sorter -{ - Q_OBJECT - Q_PROPERTY(QString roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged) - -public: - using Sorter::Sorter; - - const QString& roleName() const; - void setRoleName(const QString& roleName); - -Q_SIGNALS: - void roleNameChanged(); - -protected: - QPair sourceData(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const; - int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override; - -private: - QString m_roleName; -}; - -} - -#endif // ROLESORTER_H diff --git a/client/3rd/SortFilterProxyModel/sorters/sorter.cpp b/client/3rd/SortFilterProxyModel/sorters/sorter.cpp deleted file mode 100644 index e9d05479..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/sorter.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "sorter.h" -#include "qqmlsortfilterproxymodel.h" - -namespace qqsfpm { - -/*! - \qmltype Sorter - \qmlabstract - \inqmlmodule SortFilterProxyModel - \ingroup Sorters - \brief Base type for the \l SortFilterProxyModel sorters. - - The Sorter type cannot be used directly in a QML file. - It exists to provide a set of common properties and methods, - available across all the other sorters types that inherit from it. - Attempting to use the Sorter type directly will result in an error. -*/ - -Sorter::Sorter(QObject *parent) : QObject(parent) -{ -} - -Sorter::~Sorter() = default; - -/*! - \qmlproperty bool Sorter::enabled - - This property holds whether the sorter is enabled. - A disabled sorter will not change the order of the rows. - - By default, sorters are enabled. -*/ -bool Sorter::enabled() const -{ - return m_enabled; -} - -void Sorter::setEnabled(bool enabled) -{ - if (m_enabled == enabled) - return; - - m_enabled = enabled; - Q_EMIT enabledChanged(); - Q_EMIT invalidated(); -} - -bool Sorter::ascendingOrder() const -{ - return sortOrder() == Qt::AscendingOrder; -} - -void Sorter::setAscendingOrder(bool ascendingOrder) -{ - setSortOrder(ascendingOrder ? Qt::AscendingOrder : Qt::DescendingOrder); -} - - -/*! - \qmlproperty Qt::SortOrder Sorter::sortOrder - - This property holds the sort order of this sorter. - - \value Qt.AscendingOrder The items are sorted ascending e.g. starts with 'AAA' ends with 'ZZZ' in Latin-1 locales - \value Qt.DescendingOrder The items are sorted descending e.g. starts with 'ZZZ' ends with 'AAA' in Latin-1 locales - - By default, sorting is in ascending order. -*/ -Qt::SortOrder Sorter::sortOrder() const -{ - return m_sortOrder; -} - -void Sorter::setSortOrder(Qt::SortOrder sortOrder) -{ - if (m_sortOrder == sortOrder) - return; - - m_sortOrder = sortOrder; - Q_EMIT sortOrderChanged(); - invalidate(); -} - -/*! - \qmlproperty int Sorter::priority - - This property holds the sort priority of this sorter. - Sorters with a higher priority are applied first. - In case of equal priority, Sorters are ordered by their insertion order. - - By default, the priority is 0. -*/ -int Sorter::priority() const -{ - return m_priority; -} - -void Sorter::setPriority(int priority) -{ - if (m_priority == priority) - return; - - m_priority = priority; - Q_EMIT priorityChanged(); - invalidate(); -} - -int Sorter::compareRows(const QModelIndex &source_left, const QModelIndex &source_right, const QQmlSortFilterProxyModel& proxyModel) const -{ - int comparison = compare(source_left, source_right, proxyModel); - return (m_sortOrder == Qt::AscendingOrder) ? comparison : -comparison; -} - -int Sorter::compare(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const -{ - if (lessThan(sourceLeft, sourceRight, proxyModel)) - return -1; - if (lessThan(sourceRight, sourceLeft, proxyModel)) - return 1; - return 0; -} - -void Sorter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) -{ - Q_UNUSED(proxyModel) -} - -bool Sorter::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const -{ - Q_UNUSED(sourceLeft) - Q_UNUSED(sourceRight) - Q_UNUSED(proxyModel) - return false; -} - -void Sorter::invalidate() -{ - if (m_enabled) - Q_EMIT invalidated(); -} - -} diff --git a/client/3rd/SortFilterProxyModel/sorters/sorter.h b/client/3rd/SortFilterProxyModel/sorters/sorter.h deleted file mode 100644 index f7ecd886..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/sorter.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef SORTER_H -#define SORTER_H - -#include - -namespace qqsfpm { - -class QQmlSortFilterProxyModel; - -class Sorter : public QObject -{ - Q_OBJECT - Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) - Q_PROPERTY(bool ascendingOrder READ ascendingOrder WRITE setAscendingOrder NOTIFY sortOrderChanged) - Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged) - Q_PROPERTY(int priority READ priority WRITE setPriority NOTIFY priorityChanged) - -public: - Sorter(QObject* parent = nullptr); - virtual ~Sorter() = 0; - - bool enabled() const; - void setEnabled(bool enabled); - - bool ascendingOrder() const; - void setAscendingOrder(bool ascendingOrder); - - Qt::SortOrder sortOrder() const; - void setSortOrder(Qt::SortOrder sortOrder); - - int priority() const; - void setPriority(int priority); - - int compareRows(const QModelIndex& source_left, const QModelIndex& source_right, const QQmlSortFilterProxyModel& proxyModel) const; - - virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel); - -Q_SIGNALS: - void enabledChanged(); - void sortOrderChanged(); - void priorityChanged(); - - void invalidated(); - -protected: - virtual int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const; - virtual bool lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const; - void invalidate(); - -private: - bool m_enabled = true; - Qt::SortOrder m_sortOrder = Qt::AscendingOrder; - int m_priority = 0; -}; - -} - -#endif // SORTER_H diff --git a/client/3rd/SortFilterProxyModel/sorters/sortercontainer.cpp b/client/3rd/SortFilterProxyModel/sorters/sortercontainer.cpp deleted file mode 100644 index f986e371..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/sortercontainer.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "sortercontainer.h" -#include "sorter.h" -#include - -namespace qqsfpm { - -/*! - \qmltype SorterContainer - \qmlabstract - \inqmlmodule SortFilterProxyModel - \ingroup SorterAttached - \brief Abstract interface for types containing \l {Sorter}{Sorters}. - - \section2 Types implementing this interface: - \annotatedlist SorterContainer -*/ - -QList SorterContainer::sorters() const -{ - return m_sorters; -} - -void SorterContainer::appendSorter(Sorter* sorter) -{ - m_sorters.append(sorter); - onSorterAppended(sorter); -} - -void SorterContainer::removeSorter(Sorter *sorter) -{ - m_sorters.removeOne(sorter); - onSorterRemoved(sorter); -} - -void SorterContainer::clearSorters() -{ - m_sorters.clear(); - onSortersCleared(); -} - -QQmlListProperty SorterContainer::sortersListProperty() -{ - return QQmlListProperty(reinterpret_cast(this), &m_sorters, - &SorterContainer::append_sorter, - &SorterContainer::count_sorter, - &SorterContainer::at_sorter, - &SorterContainer::clear_sorters); -} - -void SorterContainer::append_sorter(QQmlListProperty* list, Sorter* sorter) -{ - if (!sorter) - return; - - SorterContainer* that = reinterpret_cast(list->object); - that->appendSorter(sorter); -} - -int SorterContainer::count_sorter(QQmlListProperty* list) -{ - QList* sorters = static_cast*>(list->data); - return sorters->count(); -} - -Sorter* SorterContainer::at_sorter(QQmlListProperty* list, int index) -{ - QList* sorters = static_cast*>(list->data); - return sorters->at(index); -} - -void SorterContainer::clear_sorters(QQmlListProperty *list) -{ - SorterContainer* that = reinterpret_cast(list->object); - that->clearSorters(); -} - -SorterContainerAttached::SorterContainerAttached(QObject* object) : QObject(object), - m_sorter(qobject_cast(object)) -{ - if (!m_sorter) - qmlWarning(object) << "SorterContainerAttached must be attached to a Sorter"; -} - -SorterContainerAttached::~SorterContainerAttached() -{ - if (m_sorter && m_container) { - SorterContainer* container = qobject_cast(m_container.data()); - container->removeSorter(m_sorter); - } -} - -/*! - \qmlattachedproperty bool SorterContainer::container - This attached property allows you to include in a \l SorterContainer a \l Sorter that - has been instantiated outside of the \l SorterContainer, for example in an Instantiator. -*/ -QObject* SorterContainerAttached::container() const -{ - return m_container; -} - -void SorterContainerAttached::setContainer(QObject* object) -{ - if (m_container == object) - return; - - SorterContainer* container = qobject_cast(object); - if (object && !container) - qmlWarning(parent()) << "container must inherits from SorterContainer, " << object->metaObject()->className() << " provided"; - - if (m_container && m_sorter) - qobject_cast(m_container.data())->removeSorter(m_sorter); - - m_container = container ? object : nullptr; - if (container && m_sorter) - container->appendSorter(m_sorter); - - Q_EMIT containerChanged(); -} - -SorterContainerAttached* SorterContainerAttached::qmlAttachedProperties(QObject* object) -{ - return new SorterContainerAttached(object); -} - -} diff --git a/client/3rd/SortFilterProxyModel/sorters/sortercontainer.h b/client/3rd/SortFilterProxyModel/sorters/sortercontainer.h deleted file mode 100644 index 016cc6db..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/sortercontainer.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef SORTERSSORTERCONTAINER_H -#define SORTERSSORTERCONTAINER_H - -#include -#include -#include -#include - -namespace qqsfpm { - -class Sorter; -class QQmlSortFilterProxyModel; - -class SorterContainer { -public: - virtual ~SorterContainer() = default; - - QList sorters() const; - void appendSorter(Sorter* sorter); - void removeSorter(Sorter* sorter); - void clearSorters(); - - QQmlListProperty sortersListProperty(); - -protected: - QList m_sorters; - -private: - virtual void onSorterAppended(Sorter* sorter) = 0; - virtual void onSorterRemoved(Sorter* sorter) = 0; - virtual void onSortersCleared() = 0; - - static void append_sorter(QQmlListProperty* list, Sorter* sorter); - static int count_sorter(QQmlListProperty* list); - static Sorter* at_sorter(QQmlListProperty* list, int index); - static void clear_sorters(QQmlListProperty* list); -}; - -class SorterContainerAttached : public QObject -{ - Q_OBJECT - Q_PROPERTY(QObject* container READ container WRITE setContainer NOTIFY containerChanged) - -public: - SorterContainerAttached(QObject* object); - ~SorterContainerAttached(); - - QObject* container() const; - void setContainer(QObject* object); - - static SorterContainerAttached* qmlAttachedProperties(QObject* object); - -Q_SIGNALS: - void containerChanged(); - -private: - QPointer m_container = nullptr; - Sorter* m_sorter = nullptr; -}; - -} - -#define SorterContainer_iid "fr.grecko.SortFilterProxyModel.SorterContainer" -Q_DECLARE_INTERFACE(qqsfpm::SorterContainer, SorterContainer_iid) - -QML_DECLARE_TYPEINFO(qqsfpm::SorterContainerAttached, QML_HAS_ATTACHED_PROPERTIES) - -#endif // SORTERSSORTERCONTAINER_H diff --git a/client/3rd/SortFilterProxyModel/sorters/sortersqmltypes.cpp b/client/3rd/SortFilterProxyModel/sorters/sortersqmltypes.cpp deleted file mode 100644 index ceba4237..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/sortersqmltypes.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "sorter.h" -#include "rolesorter.h" -#include "stringsorter.h" -#include "filtersorter.h" -#include "expressionsorter.h" -#include "sortercontainer.h" -#include -#include - -namespace qqsfpm { - -void registerSorterTypes() { - qmlRegisterUncreatableType("SortFilterProxyModel", 0, 2, "Sorter", "Sorter is an abstract class"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "RoleSorter"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "StringSorter"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "FilterSorter"); - qmlRegisterType("SortFilterProxyModel", 0, 2, "ExpressionSorter"); - qmlRegisterUncreatableType("SortFilterProxyModel", 0, 2, "SorterContainer", "SorterContainer can only be used as an attaching type"); -} - -Q_COREAPP_STARTUP_FUNCTION(registerSorterTypes) - -} diff --git a/client/3rd/SortFilterProxyModel/sorters/stringsorter.cpp b/client/3rd/SortFilterProxyModel/sorters/stringsorter.cpp deleted file mode 100644 index 0c331d05..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/stringsorter.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "stringsorter.h" - -namespace qqsfpm { - -/*! - \qmltype StringSorter - \inherits RoleSorter - \inqmlmodule SortFilterProxyModel - \ingroup Sorters - \brief Sorts rows based on a source model string role. - - \l StringSorter is a specialized \l RoleSorter that sorts rows based on a source model string role. - \l StringSorter compares strings according to a localized collation algorithm. - - In the following example, rows with be sorted by their \c lastName role : - \code - SortFilterProxyModel { - sourceModel: contactModel - sorters: StringSorter { roleName: "lastName" } - } - \endcode -*/ - -/*! - \qmlproperty Qt.CaseSensitivity StringSorter::caseSensitivity - - This property holds the case sensitivity of the sorter. -*/ -Qt::CaseSensitivity StringSorter::caseSensitivity() const -{ - return m_collator.caseSensitivity(); -} - -void StringSorter::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity) -{ - if (m_collator.caseSensitivity() == caseSensitivity) - return; - - m_collator.setCaseSensitivity(caseSensitivity); - Q_EMIT caseSensitivityChanged(); - invalidate(); -} - -/*! - \qmlproperty bool StringSorter::ignorePunctation - - This property holds whether the sorter ignores punctation. - if \c ignorePunctuation is \c true, punctuation characters and symbols are ignored when determining sort order. - - \note This property is not currently supported on Apple platforms or if Qt is configured to not use ICU on Linux. -*/ -bool StringSorter::ignorePunctation() const -{ - return m_collator.ignorePunctuation(); -} - -void StringSorter::setIgnorePunctation(bool ignorePunctation) -{ - if (m_collator.ignorePunctuation() == ignorePunctation) - return; - - m_collator.setIgnorePunctuation(ignorePunctation); - Q_EMIT ignorePunctationChanged(); - invalidate(); -} - -/*! - \qmlproperty Locale StringSorter::locale - - This property holds the locale of the sorter. -*/ -QLocale StringSorter::locale() const -{ - return m_collator.locale(); -} - -void StringSorter::setLocale(const QLocale &locale) -{ - if (m_collator.locale() == locale) - return; - - m_collator.setLocale(locale); - Q_EMIT localeChanged(); - invalidate(); -} - -/*! - \qmlproperty bool StringSorter::numericMode - - This property holds whether the numeric mode of the sorter is enabled. - This will enable proper sorting of numeric digits, so that e.g. 100 sorts after 99. - By default this mode is off. -*/ -bool StringSorter::numericMode() const -{ - return m_collator.numericMode(); -} - -void StringSorter::setNumericMode(bool numericMode) -{ - if (m_collator.numericMode() == numericMode) - return; - - m_collator.setNumericMode(numericMode); - Q_EMIT numericModeChanged(); - invalidate(); -} - -int StringSorter::compare(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const -{ - QPair pair = sourceData(sourceLeft, sourceRight, proxyModel); - QString leftValue = pair.first.toString(); - QString rightValue = pair.second.toString(); - return m_collator.compare(leftValue, rightValue); -} - -} diff --git a/client/3rd/SortFilterProxyModel/sorters/stringsorter.h b/client/3rd/SortFilterProxyModel/sorters/stringsorter.h deleted file mode 100644 index 3368482a..00000000 --- a/client/3rd/SortFilterProxyModel/sorters/stringsorter.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef STRINGSORTER_H -#define STRINGSORTER_H - -#include "rolesorter.h" -#include - -namespace qqsfpm { - -class StringSorter : public RoleSorter -{ - Q_OBJECT - Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged) - Q_PROPERTY(bool ignorePunctation READ ignorePunctation WRITE setIgnorePunctation NOTIFY ignorePunctationChanged) - Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged) - Q_PROPERTY(bool numericMode READ numericMode WRITE setNumericMode NOTIFY numericModeChanged) - -public: - using RoleSorter::RoleSorter; - - Qt::CaseSensitivity caseSensitivity() const; - void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity); - - bool ignorePunctation() const; - void setIgnorePunctation(bool ignorePunctation); - - QLocale locale() const; - void setLocale(const QLocale& locale); - - bool numericMode() const; - void setNumericMode(bool numericMode); - -Q_SIGNALS: - void caseSensitivityChanged(); - void ignorePunctationChanged(); - void localeChanged(); - void numericModeChanged(); - -protected: - int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override; - -private: - QCollator m_collator; -}; - -} - -#endif // STRINGSORTER_H diff --git a/client/3rd/SortFilterProxyModel/sortfilterproxymodel.qdocconf b/client/3rd/SortFilterProxyModel/sortfilterproxymodel.qdocconf deleted file mode 100644 index 7a3a6532..00000000 --- a/client/3rd/SortFilterProxyModel/sortfilterproxymodel.qdocconf +++ /dev/null @@ -1,13 +0,0 @@ -project = SortFilterProxyModel - -sourcedirs = . - -sources.fileextensions = "*.cpp *.qdoc *.qml" -headers.fileextensions = "*.h" - -outputdir = docs/ - -HTML.templatedir = . -HTML.stylesheets = "offline.css" - -HTML.headerstyles = " \n" diff --git a/client/3rd/qrcodegen/qrcodegen.cmake b/client/3rd/qrcodegen/qrcodegen.cmake new file mode 100644 index 00000000..4be9056d --- /dev/null +++ b/client/3rd/qrcodegen/qrcodegen.cmake @@ -0,0 +1,4 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/qrcodegen.hpp) +set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/qrcodegen.cpp) diff --git a/client/3rd/qtkeychain b/client/3rd/qtkeychain index f197cdb9..c6f0b663 160000 --- a/client/3rd/qtkeychain +++ b/client/3rd/qtkeychain @@ -1 +1 @@ -Subproject commit f197cdb935b0cfd9881fdc6860874cb8379d1238 +Subproject commit c6f0b66318f8da6917fb4681103f7303b1836194 diff --git a/client/AmneziaVPN.entitlements b/client/AmneziaVPN.entitlements new file mode 100644 index 00000000..dd0d73f2 --- /dev/null +++ b/client/AmneziaVPN.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + group.org.amnezia.AmneziaVPN + + keychain-access-groups + + $(AppIdentifierPrefix)group.org.amnezia.AmneziaVPN + + + diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt new file mode 100644 index 00000000..605a4d09 --- /dev/null +++ b/client/CMakeLists.txt @@ -0,0 +1,611 @@ +cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) + +set(PROJECT AmneziaVPN) +project(${PROJECT} VERSION 2.1.2) +set(BUILD_ID 1) +SET(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.17) + cmake_policy(SET CMP0099 OLD) +endif() + +if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12) + cmake_policy(SET CMP0114 NEW) +endif() + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen") +set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen") +set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen") + +find_package(Qt6 REQUIRED COMPONENTS + Widgets Core Gui Network Xml + RemoteObjects Quick Svg QuickControls2 + Core5Compat Concurrent +) +set(LIBS ${LIBS} + Qt6::Widgets Qt6::Core Qt6::Gui + Qt6::Network Qt6::Xml Qt6::RemoteObjects + Qt6::Quick Qt6::Svg Qt6::QuickControls2 + Qt6::Core5Compat Qt6::Concurrent +) + +qt_standard_project_setup() + +if(IOS) + execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/scripts/openvpn.sh args + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) +endif() + +set(IS_CI ${CI}) +if(IS_CI) + message("Detected CI env") + find_program(CCACHE "ccache") + if(CCACHE) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE}") + endif() +endif() + +include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake) + +include_directories( + ${CMAKE_CURRENT_LIST_DIR}/../ipc + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/migrations.h + ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h + ${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h + ${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h + ${CMAKE_CURRENT_LIST_DIR}/core/defs.h + ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h + ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h + ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h + ${CMAKE_CURRENT_LIST_DIR}/core/servercontroller.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/linux/leakdetector.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h + ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h + ${CMAKE_CURRENT_LIST_DIR}/ui/pages.h + ${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h + ${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.h + ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h +) + +if(NOT IOS) + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h + ) +endif() + +set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/migrations.cpp + ${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp + ${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp + ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp + ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp + ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp + ${CMAKE_CURRENT_LIST_DIR}/core/servercontroller.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/linux/leakdetector.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp +) + +if(NOT IOS) + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp + ) +endif() + +file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h) +file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp) + +file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.h) +file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.cpp) + +file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h) +file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp) + +file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h) +file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp) + +set(HEADERS ${HEADERS} + ${COMMON_FILES_H} + ${PAGE_LOGIC_H} + ${CONFIGURATORS_H} + ${UI_MODELS_H} +) +set(SOURCES ${SOURCES} + ${COMMON_FILES_CPP} + ${PAGE_LOGIC_CPP} + ${CONFIGURATORS_CPP} + ${UI_MODELS_CPP} +) + +qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) + +if(WIN32) + add_compile_definitions(MVPN_WINDOWS) + + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h + ) + + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp + ) + + set(RESOURCES ${RESOURCES} + ${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc + ) + + set(LIBS ${LIBS} + user32 + rasapi32 + shlwapi + iphlpapi + ws2_32 + gdi32 + ) + + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup") + + if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + message("Windows x86_64 build") + link_directories(${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/windows/x86_64) + set(LIBS ${LIBS} + libssl + libcrypto + ) + else() + message("Windows x86 build") + link_directories(${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/windows/x86) + set(LIBS ${LIBS} + libssl + libcrypto + ) + endif() +endif() + +if(APPLE) + if(NOT BUILD_OSX_APP_IDENTIFIER) + set(BUILD_OSX_APP_IDENTIFIER org.amnezia.AmneziaVPN CACHE STRING "OSX Application identifier") + endif() + if(NOT BUILD_IOS_APP_IDENTIFIER) + set(BUILD_IOS_APP_IDENTIFIER org.amnezia.AmneziaVPN CACHE STRING "iOS Application identifier") + endif() + if(NOT BUILD_IOS_GROUP_IDENTIFIER) + set(BUILD_IOS_GROUP_IDENTIFIER group.org.amnezia.AmneziaVPN.Guardian CACHE STRING "iOS Group identifier") + endif() + if(NOT BUILD_VPN_DEVELOPMENT_TEAM) + set(BUILD_VPN_DEVELOPMENT_TEAM X7UJ388FXK CACHE STRING "Amnezia VPN Development Team") + endif() + + set(CMAKE_XCODE_GENERATE_SCHEME FALSE) + set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM}) + set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER}) + + #set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../build) + if(NOT IOS) + set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE) + + message("MAC build") + set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ui/macos_util.h) + set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ui/macos_util.mm) + + # set(CMAKE_OSX_DEPLOYMENT_TARGET 10.14) + add_compile_definitions(MVPN_MACOS) + # ICON = $$PWD/images/app.icns + find_library(LIB_LIBCRYPTO NAMES "libcrypto.a" + PATHS ${PROJECT_SOURCE_DIR}/3rd/OpenSSL/lib/macos/x86_64/ NO_DEFAULT_PATH) + + find_library(LIB_SSL NAMES "libssl.a" + PATHS ${PROJECT_SOURCE_DIR}/3rd/OpenSSL/lib/macos/x86_64/ NO_DEFAULT_PATH) + + + find_library(FW_COCOA Cocoa) + find_library(FW_APPLICATIONSERVICES ApplicationServices) + find_library(FW_CORESERVICES CoreServices) + find_library(FW_FOUNDATION Foundation) + find_library(FW_APPKIT AppKit) + find_library(FW_SECURITY Security) + + set(LIBS ${LIBS} + ${FW_COCOA} ${FW_APPLICATIONSERVICES} + ${FW_FOUNDATION} ${FW_APPKIT} + ${FW_SECURITY} ${FW_CORESERVICES} + ${LIB_LIBCRYPTO} ${LIB_SSL} + ) + endif() +endif() + + +if(LINUX AND NOT ANDROID) + add_compile_definitions(MVPN_LINUX) + + set(OPENSSL_USE_STATIC_LIBS TRUE) + find_package(OpenSSL REQUIRED) + set(LIBS ${LIBS} + OpenSSL::Crypto + OpenSSL::SSL + ) + link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux) +endif() + +if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) + message("Client desktop build") + add_compile_definitions(AMNEZIA_DESKTOP) + + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h + ${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h + ${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h + ) + + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.cpp + ${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp + ) +endif() + +if(ANDROID) + message("Client android ${CMAKE_ANDROID_ARCH_ABI} build") + # We need to include qtprivate api's + # As QAndroidBinder is not yet implemented with a public api + set(LIBS ${LIBS} Qt6::CorePrivate) + + add_compile_definitions(MVPN_ANDROID) + + link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/android) + + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_controller.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.h + ) + + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_controller.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.cpp + ) +endif() + + + +if(IOS) + message("Client iOS build") + + find_package(Qt6 REQUIRED COMPONENTS ShaderTools) + set(LIBS ${LIBS} Qt6::ShaderTools) + + find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices) + find_library(FW_UIKIT UIKit) + find_library(FW_AVFOUNDATION AVFoundation) + find_library(FW_FOUNDATION Foundation) + find_library(FW_STOREKIT StoreKit) + find_library(FW_USERNOTIFICATIONS UserNotifications) + + set(LIBS ${LIBS} + ${FW_AUTHENTICATIONSERVICES} ${FW_UIKIT} + ${FW_AVFOUNDATION} ${FW_FOUNDATION} ${FW_STOREKIT} + ${FW_USERNOTIFICATIONS} + ) + + add_compile_definitions(MVPN_IOS) + + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/protocols/ios_vpnprotocol.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/iosnotificationhandler.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/json.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/bigint.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/bigintipv6addr.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddress.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddressrange.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QtAppDelegate.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QtAppDelegate-C-Interface.h + ) + + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/protocols/ios_vpnprotocol.mm + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/iosnotificationhandler.mm + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/json.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/iosglue.mm + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddress.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddressrange.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.mm + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QtAppDelegate.mm + ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.mm + ) +endif() + +if(CMAKE_OSX_SYSROOT STREQUAL "iphoneos") + message("Building for iPhone OS") + set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0) + +endif() + +qt_add_executable(${PROJECT} ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC}) +qt_add_translations(${PROJECT} TS_FILES + ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts) + +if(APPLE AND NOT IOS) + set_target_properties(AmneziaVPN PROPERTIES + MACOSX_BUNDLE TRUE + ) +endif() + +if(IOS) + enable_language(OBJC) + enable_language(OBJCXX) + enable_language(Swift) + + #disbale in cicd + include(cmake/osxtools.cmake) + # set(CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY TRUE) + + set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") + set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon") + + set_target_properties(${PROJECT} PROPERTIES XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION) + set(CMAKE_XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks") + set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos) + + + #need to change for debug and relase + set_target_properties(${PROJECT} + PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}" + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1" + XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" + ) + + set_target_properties(${PROJECT} + PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}" + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1" + XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development" + ) + + set(LIBS ${LIBS} + ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/ios/iphone/libcrypto.a + ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/ios/iphone/libssl.a + ) + + target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + + set_target_properties(${PROJECT} PROPERTIES + XCODE_ATTRIBUTE_SWIFT_VERSION "5.0" + XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES" + XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_LIST_DIR}/platforms/ios/WireGuard-Bridging-Header.h" + XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO" + XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone" + XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h" + ) + + set_target_properties(${PROJECT} PROPERTIES + OUTPUT_NAME "AmneziaVPN" + MACOSX_BUNDLE ON + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_LIST_DIR}/ios/app/Info.plist.in + MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN" + MACOSX_BUNDLE_BUNDLE_VERSION "${BUILD_ID}" + MACOSX_BUNDLE_COPYRIGHT "MPL-2.0" + MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}" + MACOSX_BUNDLE_INFO_STRING "AmneziaVPN" + MACOSX_BUNDLE_LONG_VERSION_STRING "${CMAKE_PROJECT_VERSION}-${BUILD_ID}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION}" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}" + XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_LIST_DIR}/ios/app/main.entitlements" + XCODE_ATTRIBUTE_MARKETING_VERSION "${CMAKE_PROJECT_VERSION}" + XCODE_GENERATE_SCHEME TRUE + MACOSX_BUNDLE_ICON_FILE "AppIcon" + + ) + + target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) + + target_compile_options(${PROJECT} PRIVATE + -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\" + -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" + ) + + + target_sources(${PROJECT} PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Keychain.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardApp/LocalizationHelper.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c + ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift + ) + + target_sources(${PROJECT} PRIVATE + platforms/ios/iosvpnprotocol.swift + platforms/ios/ioslogger.swift + ) + + target_sources(${PROJECT} PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/ios/app/launch.png + ${CMAKE_CURRENT_LIST_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard + ) + set_source_files_properties( + ${CMAKE_CURRENT_LIST_DIR}/ios/app/launch.png + ${CMAKE_CURRENT_LIST_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard + PROPERTIES MACOSX_PACKAGE_LOCATION "Resources" + ) + + target_sources(${PROJECT} PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/ios/Media.xcassets + ) + set_source_files_properties( + ${CMAKE_CURRENT_LIST_DIR}/ios/Media.xcassets + PROPERTIES MACOSX_PACKAGE_LOCATION "Resources" + ) + + add_subdirectory(ios/networkextension) + add_dependencies(${PROJECT} networkextension) + set_target_properties(${PROJECT} PROPERTIES XCODE_EMBED_APP_EXTENSIONS networkextension) + + set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS + "${CMAKE_CURRENT_LIST_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework" + ) + set_target_properties(${PROJECT} PROPERTIES XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON) + set_target_properties(${PROJECT} PROPERTIES XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION) + + + set_target_properties("networkextension" + PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}.network-extension" + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1" + XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" + ) + + set_target_properties("networkextension" + PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}.network-extension" + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1" + XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development" + ) + + set_target_properties (${PROJECT} PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual) + set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN") + set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN") + + set_target_properties("networkextension" PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual) + set_target_properties("networkextension" PROPERTIES XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN.network-extension") + set_target_properties("networkextension" PROPERTIES XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN.network-extension") + +endif() + +if(ANDROID) + add_custom_command( + TARGET ${PROJECT} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_LIST_DIR}/android/AndroidManifest.xml + ${CMAKE_CURRENT_LIST_DIR}/android/build.gradle + ${CMAKE_CURRENT_LIST_DIR}/android/gradle/wrapper/gradle-wrapper.jar + ${CMAKE_CURRENT_LIST_DIR}/android/gradle/wrapper/gradle-wrapper.properties + ${CMAKE_CURRENT_LIST_DIR}/android/gradlew + ${CMAKE_CURRENT_LIST_DIR}/android/gradlew.bat + ${CMAKE_CURRENT_LIST_DIR}/android/gradle.properties + ${CMAKE_CURRENT_LIST_DIR}/android/res/values/libs.xml + ${CMAKE_CURRENT_LIST_DIR}/android/res/xml/fileprovider.xml + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/AuthHelper.java + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/IPCContract.kt + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/NotificationUtil.kt + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/Prefs.kt + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNLogger.kt + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNService.kt + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNServiceBinder.kt + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/AmneziaApp.kt + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNActivity.kt + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNApplication.java + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt + ${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNPermissionHelper.kt + ${CMAKE_CURRENT_BINARY_DIR} + ) + + set_property(TARGET ${PROJECT} PROPERTY + QT_ANDROID_PACKAGE_SOURCE_DIR + ${CMAKE_CURRENT_LIST_DIR}/android + ) + + foreach(abi IN ITEMS ${QT_ANDROID_ABIS}) + if(CMAKE_ANDROID_ARCH_ABI STREQUAL ${abi}) + set(LIBS ${LIBS} + ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/android/${abi}/libcrypto.a + ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/android/${abi}/libssl.a + ) + endif() + + set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS + ${CMAKE_CURRENT_LIST_DIR}/android/lib/wireguard/${abi}/libwg.so + ${CMAKE_CURRENT_LIST_DIR}/android/lib/wireguard/${abi}/libwg-go.so + ${CMAKE_CURRENT_LIST_DIR}/android/lib/wireguard/${abi}/libwg-quick.so + + ${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libjbcrypto.so + ${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libopenvpn.so + ${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libopvpnutil.so + ${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libovpn3.so + ${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libovpnexec.so + ) + endforeach() + +endif() + +target_link_libraries(${PROJECT} PRIVATE ${LIBS}) + +if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) + qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) + qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep) +endif() + +# deploy artifacts required to run the application to the debug build folder +if(WIN32) + if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + set(DEPLOY_ARTIFACT_PATH "windows/x64") + else() + set(DEPLOY_ARTIFACT_PATH "windows/x32") + endif() +elseif(LINUX) + set(DEPLOY_ARTIFACT_PATH "linux/client") +elseif(APPLE AND NOT IOS) + set(DEPLOY_ARTIFACT_PATH "macos") +endif() + +if(NOT IOS) + add_custom_command( + TARGET ${PROJECT} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E $,copy_directory,true> + ${CMAKE_SOURCE_DIR}/deploy/data/${DEPLOY_ARTIFACT_PATH} + $ + COMMAND_EXPAND_LISTS + ) +endif() + +if(WIN32) + add_custom_command( + TARGET ${PROJECT} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E $,copy,true> + $/../service/wireguard-service/wireguard-service.exe + $/wireguard/wireguard-service.exe + COMMAND_EXPAND_LISTS + ) +endif() +if(IOS) + #include(cmake/ios-arch-fixup.cmake) +endif() diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 334582cb..588854d3 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -7,8 +7,9 @@ #include "core/servercontroller.h" -#include "debug.h" +#include "logger.h" #include "defines.h" +#include #include "platforms/ios/QRCodeReaderBase.h" @@ -98,7 +99,7 @@ void AmneziaApplication::init() QCoreApplication::exit(-1); }, Qt::QueuedConnection); - m_engine->rootContext()->setContextProperty("Debug", &Debug::Instance()); + m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); m_uiLogic->registerPagesLogic(); #if defined(Q_OS_IOS) @@ -112,7 +113,7 @@ void AmneziaApplication::init() } if (m_settings->isSaveLogs()) { - if (!Debug::init()) { + if (!Logger::init()) { qWarning() << "Initialization of debug subsystem failed"; } } @@ -168,6 +169,8 @@ void AmneziaApplication::registerTypes() void AmneziaApplication::loadFonts() { + QQuickStyle::setStyle("Basic"); + QFontDatabase::addApplicationFont(":/fonts/Lato-Black.ttf"); QFontDatabase::addApplicationFont(":/fonts/Lato-BlackItalic.ttf"); QFontDatabase::addApplicationFont(":/fonts/Lato-Bold.ttf"); @@ -203,7 +206,7 @@ bool AmneziaApplication::parseCommands() m_parser.process(*this); if (m_parser.isSet(c_cleanup)) { - Debug::cleanUp(); + Logger::cleanUp(); QTimer::singleShot(100, this, [this]{ quit(); }); diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 55cffe20..be67c8ba 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -21,13 +21,20 @@ Remove the comment if you do not require these default features. --> - + + android:exported="true"> + + - - - + + + + + + + + + + + + + + + + + + + + + + - + @@ -58,14 +97,14 @@ - + - - - + + + - + @@ -73,14 +112,14 @@ - + - - - + + + - + @@ -88,102 +127,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + + + + + - - + + - - - + diff --git a/client/android/build.gradle b/client/android/build.gradle index 12129720..c54784d9 100644 --- a/client/android/build.gradle +++ b/client/android/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.github.ben-manes.versions' buildscript { ext{ - kotlin_version = "1.4.30-M1" + kotlin_version = "1.7.22" // for libwg appcompatVersion = '1.1.0' annotationsVersion = '1.0.1' @@ -20,7 +20,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0' + classpath 'com.android.tools.build:gradle:7.2.1' classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0' classpath 'com.vanniktech:gradle-maven-publish-plugin:0.8.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" @@ -28,12 +28,6 @@ buildscript { } } -repositories { - google() - jcenter() - mavenCentral() -} - apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' @@ -42,14 +36,34 @@ apply plugin: 'kotlin-kapt' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) - implementation 'androidx.core:core-ktx:1.1.0' + + implementation 'androidx.core:core-ktx:1.7.0' + + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation "androidx.security:security-crypto:1.1.0-alpha03" implementation "androidx.security:security-identity-credential:1.0.0-alpha02" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0" + coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" + implementation project(path: ':shadowsocks') + + // CameraX core library using the camera2 implementation + def camerax_version = "1.2.1" + implementation("androidx.camera:camera-core:${camerax_version}") + implementation("androidx.camera:camera-camera2:${camerax_version}") + implementation("androidx.camera:camera-lifecycle:${camerax_version}") + implementation("androidx.camera:camera-view:${camerax_version}") + implementation("androidx.camera:camera-extensions:${camerax_version}") + + def camerax_ml_version = "1.2.0-beta02" + def ml_kit_version = "17.0.3" + implementation("androidx.camera:camera-mlkit-vision:${camerax_ml_version}") + implementation("com.google.mlkit:barcode-scanning:${ml_kit_version}") } androidExtensions { @@ -61,7 +75,7 @@ android { * The following variables: * - androidBuildToolsVersion, * - androidCompileSdkVersion - * - qt5AndroidDir - holds the path to qt android files + * - qtAndroidDir - holds the path to qt android files * needed to build any Qt application * on Android. * @@ -72,7 +86,11 @@ android { compileSdkVersion androidCompileSdkVersion.toInteger() - //buildToolsVersion '28.0.3' + buildToolsVersion androidBuildToolsVersion + ndkVersion androidNdkVersion + + // Extract native libraries from the APK + packagingOptions.jniLibs.useLegacyPackaging true dexOptions { javaMaxHeapSize "3g" @@ -81,14 +99,14 @@ android { sourceSets { main { manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] - aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] - res.srcDirs = [qt5AndroidDir + '/res', 'res'] + java.srcDirs = [qtAndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qtAndroidDir + '/res', 'res'] resources.srcDirs = ['resources'] renderscript.srcDirs = ['src'] assets.srcDirs = ['assets'] jniLibs.srcDirs = ['libs'] - androidTest.assets.srcDirs += files("${qt5AndroidDir}/schemas".toString()) + androidTest.assets.srcDirs += files("${qtAndroidDir}/schemas".toString()) } } @@ -117,12 +135,12 @@ android { defaultConfig { resConfig "en" minSdkVersion = 24 - targetSdkVersion = 30 + targetSdkVersion = 31 versionCode 10 // Change to a higher number versionName "2.0.10" // Change to a higher number javaCompileOptions.annotationProcessorOptions.arguments = [ - "room.schemaLocation": "${qt5AndroidDir}/schemas".toString() + "room.schemaLocation": "${qtAndroidDir}/schemas".toString() ] } @@ -139,6 +157,7 @@ android { debug { //applicationIdSuffix ".debug" //versionNameSuffix "-debug" + minifyEnabled false externalNativeBuild { cmake { arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}" diff --git a/client/android/gradle/wrapper/gradle-wrapper.properties b/client/android/gradle/wrapper/gradle-wrapper.properties index 4e1cc9db..669386b8 100644 --- a/client/android/gradle/wrapper/gradle-wrapper.properties +++ b/client/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/client/android/res/layout/activity_camera.xml b/client/android/res/layout/activity_camera.xml new file mode 100644 index 00000000..8d0653e6 --- /dev/null +++ b/client/android/res/layout/activity_camera.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/client/android/res/layout/activity_import_config.xml b/client/android/res/layout/activity_import_config.xml new file mode 100644 index 00000000..2a1d3660 --- /dev/null +++ b/client/android/res/layout/activity_import_config.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/client/android/res/values/libs.xml b/client/android/res/values/libs.xml index 6b1a4a2a..fe63866f 100644 --- a/client/android/res/values/libs.xml +++ b/client/android/res/values/libs.xml @@ -1,11 +1,6 @@ - - https://download.qt.io/ministro/android/qt5/qt-5.14 - - - + @@ -19,4 +14,8 @@ + + + + diff --git a/client/android/settings.gradle b/client/android/settings.gradle index ba31e000..9256d846 100644 --- a/client/android/settings.gradle +++ b/client/android/settings.gradle @@ -1 +1,19 @@ +pluginManagement { + repositories { + google() + mavenCentral() + jcenter() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + jcenter() + } +} + include ':shadowsocks' diff --git a/client/android/shadowsocks/build.gradle b/client/android/shadowsocks/build.gradle index d35daa11..f1d738b2 100644 --- a/client/android/shadowsocks/build.gradle +++ b/client/android/shadowsocks/build.gradle @@ -1,21 +1,9 @@ - -allprojects { - repositories { - google() - jcenter() - mavenCentral() - } -} - - apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' //apply plugin: 'com.novoda.bintray-release' - - android { compileSdkVersion 30 defaultConfig { @@ -47,7 +35,7 @@ androidExtensions { } //def lifecycleVersion = '2.0.0' -//def roomVersion = '2.0.0' +def roomVersion = "2.4.3" //def preferencexVersion = '1.0.0' dependencies { @@ -57,15 +45,14 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0" - implementation "androidx.core:core-ktx:1.2.0" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:2.4.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" - implementation "androidx.room:room-runtime:2.2.5" // runtime + implementation "androidx.room:room-runtime:$roomVersion" // runtime implementation "androidx.preference:preference:1.1.0" - implementation "androidx.work:work-runtime-ktx:2.3.4" + implementation "androidx.work:work-runtime-ktx:2.7.1" implementation "androidx.browser:browser:1.3.0-alpha01" implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "com.google.android.material:material:1.2.0-alpha05" @@ -77,6 +64,7 @@ dependencies { // api "com.takisoft.preferencex:preferencex:1.0.0" implementation 'com.takisoft.preferencex:preferencex:1.1.0' api 'org.connectbot.jsocks:jsocks:1.0.0' - kapt "androidx.room:room-compiler:2.2.5" + + kapt "androidx.room:room-compiler:$roomVersion" kapt "androidx.lifecycle:lifecycle-compiler:2.4.0" } diff --git a/client/android/shadowsocks/src/main/AndroidManifest.xml b/client/android/shadowsocks/src/main/AndroidManifest.xml index 9769ab7a..ee498ab8 100644 --- a/client/android/shadowsocks/src/main/AndroidManifest.xml +++ b/client/android/shadowsocks/src/main/AndroidManifest.xml @@ -68,7 +68,8 @@ android:name="org.amnezia.vpn.shadowsocks.core.BootReceiver" android:directBootAware="true" android:enabled="false" - android:process=":QtOnlyProcess"> + android:process=":QtOnlyProcess" + android:exported="true"> diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/Core.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/Core.kt index 170bfb75..93bfcacc 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/Core.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/Core.kt @@ -84,8 +84,9 @@ object Core { fun init(app: Application, configureClass: KClass) { Core.app = app configureIntent = { - PendingIntent.getActivity(it, 0, Intent(it, configureClass.java) - .setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0) + PendingIntent.getActivity(it, 0, + Intent(it, configureClass.java).setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) } if (Build.VERSION.SDK_INT >= 24) { // migrate old files @@ -99,7 +100,6 @@ object Core { // overhead of debug mode is minimal: https://github.com/Kotlin/kotlinx.coroutines/blob/f528898/docs/debugging.md#debug-mode System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON) - WorkManager.initialize(deviceStorage, Configuration.Builder().build()) // handle data restored/crash if (Build.VERSION.SDK_INT >= 24 && DataStore.directBootAware && diff --git a/client/android/src/org/amnezia/vpn/AuthHelper.java b/client/android/src/org/amnezia/vpn/AuthHelper.java index b30aa8f5..940d03c2 100644 --- a/client/android/src/org/amnezia/vpn/AuthHelper.java +++ b/client/android/src/org/amnezia/vpn/AuthHelper.java @@ -3,7 +3,7 @@ package org.amnezia.vpn; import android.content.Context; import android.app.KeyguardManager; import android.content.Intent; -import org.qtproject.qt5.android.bindings.QtActivity; +import org.qtproject.qt.android.bindings.QtActivity; import static android.content.Context.KEYGUARD_SERVICE; diff --git a/client/android/src/org/amnezia/vpn/NotificationUtil.kt b/client/android/src/org/amnezia/vpn/NotificationUtil.kt index 15e706ed..d2808d5d 100644 --- a/client/android/src/org/amnezia/vpn/NotificationUtil.kt +++ b/client/android/src/org/amnezia/vpn/NotificationUtil.kt @@ -99,7 +99,7 @@ object NotificationUtil { val mainActivityName = "org.amnezia.vpn.qt.VPNActivity" val activity = Class.forName(mainActivityName) val intent = Intent(service, activity) - val pendingIntent = PendingIntent.getActivity(service, 0, intent, 0) + val pendingIntent = PendingIntent.getActivity(service, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) // Build our notification sNotificationBuilder?.let { it.setSmallIcon(org.amnezia.vpn.R.drawable.ic_amnezia_round) diff --git a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt index f40f9536..39e077dd 100644 --- a/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt +++ b/client/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt @@ -31,6 +31,31 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna private var mAlreadyInitialised = false private var mService: VPNService = service + private var bytesInIndex = -1 + private var bytesOutIndex = -1 + + init { + findConfigIndicies() + } + + private fun findConfigIndicies() { + val n: Int = stats_n() + + for (i in 0 until n) { + val name: String = stats_name(i) + if (name == "BYTES_IN") bytesInIndex = i + if (name == "BYTES_OUT") bytesOutIndex = i + } + } + + fun getTotalRxBytes(): Long { + return stats_value(bytesInIndex) + } + + fun getTotalTxBytes(): Long { + return stats_value(bytesOutIndex) + } + override fun run() { val config: ClientAPI_Config = ClientAPI_Config() diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index 23e9ff0c..412b1e45 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -41,6 +41,7 @@ import java.io.Closeable import java.io.File import java.io.FileDescriptor import java.io.IOException +import java.lang.Exception import android.net.VpnService as BaseVpnService @@ -153,31 +154,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { private var flags = 0 private var startId = 0 - private lateinit var mMessenger: Messenger - - internal class ExternalConfigImportHandler( - context: Context, - private val serviceBinder: VPNServiceBinder, - private val applicationContext: Context = context.applicationContext - ) : Handler() { - - override fun handleMessage(msg: Message) { - when (msg.what) { - IMPORT_COMMAND_CODE -> { - val data = msg.data.getString(IMPORT_CONFIG_KEY) - - if (data != null) { - serviceBinder.importConfig(data) - } - } - - else -> { - super.handleMessage(msg) - } - } - } - } - fun init() { if (mAlreadyInitialised) { return @@ -216,13 +192,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { override fun onBind(intent: Intent): IBinder { Log.v(tag, "Aman: onBind....................") - if (intent.action != null && intent.action == IMPORT_ACTION_CODE) { - Log.v(tag, "Service bind for import of config") - mMessenger = Messenger(ExternalConfigImportHandler(this, mBinder)) - return mMessenger.binder - } - - Log.v(tag, "Regular service bind") when (mProtocol) { "shadowsocks" -> { when (intent.action) { @@ -306,11 +275,20 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { return mConnectionTime } - var isUp: Boolean + var isUp: Boolean = false get() { - return currentTunnelHandle >= 0 + return when (mProtocol) { + "openvpn" -> { + field + } + else -> { + currentTunnelHandle >= 0 + } + } } set(value) { + field = value + if (value) { mBinder.dispatchEvent(VPNServiceBinder.EVENTS.connected, "") mConnectionTime = System.currentTimeMillis() @@ -319,17 +297,52 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "") mConnectionTime = 0 } + val status: JSONObject get() { val deviceIpv4: String = "" + + val status = when (mProtocol) { + "openvpn" -> { + if (mOpenVPNThreadv3 == null) { + Status(null, null, null, null) + } else { + val rx = mOpenVPNThreadv3?.getTotalRxBytes() ?: "" + val tx = mOpenVPNThreadv3?.getTotalTxBytes() ?: "" + + Status( + rx.toString(), + tx.toString(), + if (mConfig!!.has("server")) { mConfig?.getJSONObject("server")?.getString("ipv4Gateway") } else {""}, + if (mConfig!!.has("device")) { mConfig?.getJSONObject("device")?.getString("ipv4Address") } else {""} + ) + } + } + else -> { + Status( + getConfigValue("rx_bytes"), + getConfigValue("tx_bytes"), + if (mConfig!!.has("server")) { mConfig?.getJSONObject("server")?.getString("ipv4Gateway") } else {""}, + if (mConfig!!.has("server")) {mConfig?.getJSONObject("device")?.getString("ipv4Address") } else {""} + ) + } + } + return JSONObject().apply { - putOpt("rx_bytes", getConfigValue("rx_bytes")) - putOpt("tx_bytes", getConfigValue("tx_bytes")) - putOpt("endpoint", mConfig?.getJSONObject("server")?.getString("ipv4Gateway")) - putOpt("deviceIpv4", mConfig?.getJSONObject("device")?.getString("ipv4Address")) + putOpt("rx_bytes", status.rxBytes) + putOpt("tx_bytes", status.txBytes) + putOpt("endpoint", status.endpoint) + putOpt("deviceIpv4", status.device) } } + data class Status( + var rxBytes: String?, + var txBytes: String?, + var endpoint: String?, + var device: String? + ) + /* * Checks if the VPN Permission is given. * If the permission is given, returns true @@ -677,6 +690,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { private fun startOpenVpn() { mOpenVPNThreadv3 = OpenVPNThreadv3(this) + Thread({ mOpenVPNThreadv3?.run() }).start() @@ -874,45 +888,4 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { class CloseableFd(val fd: FileDescriptor) : Closeable { override fun close() = Os.close(fd) } - - fun saveAsFile(configContent: String?, suggestedFileName: String): String { - val rootDirPath = cacheDir.absolutePath - val rootDir = File(rootDirPath) - - if (!rootDir.exists()) { - rootDir.mkdirs() - } - - val fileName = if (!TextUtils.isEmpty(suggestedFileName)) suggestedFileName else "amnezia.cfg" - - val file = File(rootDir, fileName) - - try { - file.bufferedWriter().use { out -> out.write(configContent) } - return file.toString() - } catch (e: Exception) { - e.printStackTrace() - } - - return "" - } - - fun shareFile(attachmentFile: String?) { - try { - val intent = Intent(Intent.ACTION_SEND) - intent.type = "text/*" - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - - val file = File(attachmentFile) - val uri = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.fileprovider", file) - intent.putExtra(Intent.EXTRA_STREAM, uri) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - val createChooser = Intent.createChooser(intent, "Config sharing") - createChooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(createChooser) - } catch (e: Exception) { - Log.i(tag, e.message.toString()) - } - } } diff --git a/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt b/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt index 640a0657..bc44217e 100644 --- a/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt +++ b/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt @@ -32,7 +32,7 @@ class VPNServiceBinder(service: VPNService) : Binder() { const val resumeActivate = 7 const val setNotificationText = 8 const val setFallBackNotification = 9 - const val shareConfig = 10 + const val importConfig = 11 } /** @@ -75,13 +75,14 @@ class VPNServiceBinder(service: VPNService) : Binder() { ACTIONS.resumeActivate -> { // [data] is empty // Activate the current tunnel - try { - mResumeConfig?.let { this.mService.turnOn(it) } + Log.i(tag, "resume activate") + try { + mResumeConfig?.let { this.mService.turnOn(it) } } catch (e: Exception) { Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}") } return true - } + } ACTIONS.deactivate -> { // [data] here is empty @@ -90,6 +91,7 @@ class VPNServiceBinder(service: VPNService) : Binder() { } ACTIONS.registerEventListener -> { + Log.i(tag, "register: start") // [data] contains the Binder that we need to dispatch the Events val binder = data.readStrongBinder() mListener = binder @@ -136,18 +138,21 @@ class VPNServiceBinder(service: VPNService) : Binder() { return true } - ACTIONS.shareConfig -> { - val byteArray = data.createByteArray() - val json = byteArray?.let { String(it) } - val config = JSONObject(json) - val configContent = config.getString("data") - val suggestedName = config.getString("suggestedName") + ACTIONS.importConfig -> { + val buffer = data.readString() - val filePath = mService.saveAsFile(configContent, suggestedName) - Log.i(tag, "save file: $filePath") + val obj = JSONObject() + obj.put("config", buffer) - mService.shareFile(filePath) - return true + val resultString = obj.toString() + + Log.i(tag, "Transact import config request") + + if (mListener != null) { + dispatchEvent(EVENTS.configImport, resultString) + } else { + mImportedConfig = resultString + } } IBinder.LAST_CALL_TRANSACTION -> { @@ -179,6 +184,8 @@ class VPNServiceBinder(service: VPNService) : Binder() { val data = Parcel.obtain() data.writeByteArray(payload?.toByteArray(charset("UTF-8"))) it.transact(code, data, Parcel.obtain(), 0) + } else { + Log.i(tag, "Dispatching event: binder NOT alive") } } } catch (e: DeadObjectException) { @@ -197,23 +204,7 @@ class VPNServiceBinder(service: VPNService) : Binder() { const val statisticUpdate = 3 const val backendLogs = 4 const val activationError = 5 - const val configImport = 6 - } - - fun importConfig(config: String) { - val obj = JSONObject() - obj.put("config", config) - - val resultString = obj.toString() - - Log.i(tag, "Transact import config request") - - if (mListener != null) { - Log.i(tag, "binder alive") - dispatchEvent(EVENTS.configImport, resultString) - } else { - Log.i(tag, "binder NOT alive") - mImportedConfig = resultString - } + const val permissionRequired = 6 + const val configImport = 7 } } diff --git a/client/android/src/org/amnezia/vpn/qt/AmneziaApp.kt b/client/android/src/org/amnezia/vpn/qt/AmneziaApp.kt index 7888f6d6..24c33ffc 100644 --- a/client/android/src/org/amnezia/vpn/qt/AmneziaApp.kt +++ b/client/android/src/org/amnezia/vpn/qt/AmneziaApp.kt @@ -3,8 +3,8 @@ package org.amnezia.vpn.qt import android.content.res.Configuration import org.amnezia.vpn.shadowsocks.core.Core import org.amnezia.vpn.shadowsocks.core.VpnManager -import org.qtproject.qt5.android.bindings.QtActivity -import org.qtproject.qt5.android.bindings.QtApplication +import org.qtproject.qt.android.bindings.QtActivity +import org.qtproject.qt.android.bindings.QtApplication import android.app.Application class AmneziaApp: Application() { diff --git a/client/android/src/org/amnezia/vpn/qt/CameraActivity.kt b/client/android/src/org/amnezia/vpn/qt/CameraActivity.kt new file mode 100644 index 00000000..8733efa5 --- /dev/null +++ b/client/android/src/org/amnezia/vpn/qt/CameraActivity.kt @@ -0,0 +1,170 @@ +package org.amnezia.vpn.qt + +import android.Manifest +import android.annotation.SuppressLint +import android.content.pm.PackageManager +import android.os.Bundle +import android.util.Log +import android.view.MotionEvent +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.core.* +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.content.ContextCompat +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.barcode.common.Barcode +import com.google.mlkit.vision.common.InputImage +import org.amnezia.vpn.R +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class CameraActivity : AppCompatActivity() { + + private val CAMERA_REQUEST = 100 + + private lateinit var cameraExecutor: ExecutorService + private lateinit var analyzerExecutor: ExecutorService + + private lateinit var viewFinder: PreviewView + + companion object { + private lateinit var instance: CameraActivity + + @JvmStatic fun getInstance(): CameraActivity { + return instance + } + + @JvmStatic fun stopQrCodeReader() { + CameraActivity.getInstance().finish() + } + } + + external fun passDataToDecoder(data: String) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_camera) + + viewFinder = findViewById(R.id.viewFinder) + + cameraExecutor = Executors.newSingleThreadExecutor() + analyzerExecutor = Executors.newSingleThreadExecutor() + + instance = this + + checkPermissions() + + configureVideoPreview() + } + + private fun checkPermissions() { + if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(arrayOf(Manifest.permission.CAMERA), CAMERA_REQUEST) + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == CAMERA_REQUEST) { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, "CameraX permission granted", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "CameraX permission denied", Toast.LENGTH_SHORT).show(); + } + } + } + + @SuppressLint("UnsafeOptInUsageError", "ClickableViewAccessibility") + private fun configureVideoPreview() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(this) + val imageCapture = ImageCapture.Builder().build() + + cameraProviderFuture.addListener({ + val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() + + val preview = Preview.Builder().build() + + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + val imageAnalyzer = BarCodeAnalyzer() + + val analysisUseCase = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + + analysisUseCase.setAnalyzer(analyzerExecutor, imageAnalyzer) + + try { + preview.setSurfaceProvider(viewFinder.surfaceProvider) + cameraProvider.unbindAll() + val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, analysisUseCase) + viewFinder.setOnTouchListener(View.OnTouchListener { view: View, motionEvent: MotionEvent -> + when (motionEvent.action) { + MotionEvent.ACTION_DOWN -> return@OnTouchListener true + MotionEvent.ACTION_UP -> { + val factory = viewFinder.meteringPointFactory + val point = factory.createPoint(motionEvent.x, motionEvent.y) + val action = FocusMeteringAction.Builder(point).build() + camera.cameraControl.startFocusAndMetering(action) + return@OnTouchListener true + } + else -> return@OnTouchListener false + } + }) + } catch(exc: Exception) { + Log.e("WUTT", "Use case binding failed", exc) + } + }, ContextCompat.getMainExecutor(this)) + } + + override fun onDestroy() { + cameraExecutor.shutdown() + analyzerExecutor.shutdown() + + super.onDestroy() + } + + val barcodesSet = mutableSetOf() + + private inner class BarCodeAnalyzer(): ImageAnalysis.Analyzer { + + private val options = BarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_QR_CODE) + .build() + + private val scanner = BarcodeScanning.getClient(options) + + @SuppressLint("UnsafeOptInUsageError") + override fun analyze(imageProxy: ImageProxy) { + val mediaImage = imageProxy.image + + if (mediaImage != null) { + val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) + + scanner.process(image) + .addOnSuccessListener { barcodes -> + if (barcodes.isNotEmpty()) { + val barcode = barcodes[0] + if (barcode != null) { + val str = barcode?.displayValue ?: "" + if (str.isNotEmpty()) { + val isAdded = barcodesSet.add(str) + if (isAdded) { + passDataToDecoder(str) + } + } + } + } + imageProxy.close() + } + .addOnFailureListener { + imageProxy.close() + } + } + } + } +} \ No newline at end of file diff --git a/client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt new file mode 100644 index 00000000..de175a98 --- /dev/null +++ b/client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt @@ -0,0 +1,140 @@ +package org.amnezia.vpn.qt + +import android.Manifest +import android.app.Activity +import android.content.pm.PackageManager +import android.content.ContentResolver +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.provider.MediaStore +import android.widget.Toast +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat + +import java.io.* + +import org.amnezia.vpn.R + + +const val INTENT_ACTION_IMPORT_CONFIG = "org.amnezia.vpn.qt.IMPORT_CONFIG" + +class ImportConfigActivity : Activity() { + + private val STORAGE_PERMISSION_CODE = 42 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_import_config) + startReadConfig(intent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + startReadConfig(intent) + } + + private fun startMainActivity(config: String?) { + + if (config == null || config.length == 0) { + return + } + + val activityIntent = Intent(applicationContext, VPNActivity::class.java) + activityIntent.action = INTENT_ACTION_IMPORT_CONFIG + activityIntent.addCategory("android.intent.category.DEFAULT") + activityIntent.putExtra("CONFIG", config) + + startActivity(activityIntent) + finish() + } + + private fun startReadConfig(intent: Intent?) { + val newIntent = intent + val newIntentAction: String = newIntent?.action ?: "" + + if (newIntent != null && newIntentAction == Intent.ACTION_VIEW) { + readConfig(newIntent, newIntentAction) + } + } + + private fun readConfig(newIntent: Intent, newIntentAction: String) { + if (isReadStorageAllowed()) { + val configString = processIntent(newIntent, newIntentAction) + startMainActivity(configString) + } else { + requestStoragePermission() + } + } + + private fun requestStoragePermission() { + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE) + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + if (requestCode == STORAGE_PERMISSION_CODE) { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + val configString = processIntent(intent, intent.action!!) + + if (configString != null) { + startMainActivity(configString) + } + } else { + Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show() + } + } + } + + private fun processIntent(intent: Intent, action: String): String? { + val scheme = intent.scheme + + if (scheme == null) { + return null + } + + if (action.compareTo(Intent.ACTION_VIEW) == 0) { + val resolver = contentResolver + + if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) { + val uri = intent.data + val name: String? = getContentName(resolver, uri) + + println("Content intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name) + + val input = resolver.openInputStream(uri!!) + + return input?.bufferedReader()?.use(BufferedReader::readText) + } else if (scheme.compareTo(ContentResolver.SCHEME_FILE) == 0) { + val uri = intent.data + val name = uri!!.lastPathSegment + + println("File intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name) + + val input = resolver.openInputStream(uri) + + return input?.bufferedReader()?.use(BufferedReader::readText) + } + } + + return null + } + + private fun isReadStorageAllowed(): Boolean { + val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) + return permissionStatus == PackageManager.PERMISSION_GRANTED + } + + private fun getContentName(resolver: ContentResolver?, uri: Uri?): String? { + val cursor = resolver!!.query(uri!!, null, null, null, null) + + cursor.use { + cursor!!.moveToFirst() + val nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) + return if (nameIndex >= 0) { + return cursor.getString(nameIndex) + } else { + null + } + } + } +} \ No newline at end of file diff --git a/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt b/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt index c5b5107e..d2b5b7ab 100644 --- a/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt +++ b/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt @@ -1,6 +1,12 @@ +/* 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/. */ + package org.amnezia.vpn.qt; import android.Manifest +import android.content.ClipData +import android.content.ClipboardManager import android.content.ComponentName import android.content.ContentResolver import android.content.Context @@ -20,161 +26,190 @@ import org.amnezia.vpn.VPNServiceBinder import org.amnezia.vpn.IMPORT_COMMAND_CODE import org.amnezia.vpn.IMPORT_ACTION_CODE import org.amnezia.vpn.IMPORT_CONFIG_KEY -import org.qtproject.qt5.android.bindings.QtActivity +import org.qtproject.qt.android.bindings.QtActivity import java.io.* -class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() { +class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() { private var configString: String? = null - private var vpnServiceBinder: Messenger? = null + private var vpnServiceBinder: IBinder? = null private var isBound = false + set(value) { + field = value - private val TAG = "VPNActivity" - private val STORAGE_PERMISSION_CODE = 42 - - - override fun onCreate(savedInstanceState: Bundle?) { - val newIntent = intent - val newIntentAction = newIntent.action - - if (newIntent != null && newIntentAction != null) { - configString = processIntent(newIntent, newIntentAction) - } - - super.onCreate(savedInstanceState) - } - - override fun onNewIntent(newIntent: Intent) { - intent = newIntent - - val newIntentAction = newIntent.action - - if (newIntent != null && newIntentAction != null && newIntentAction != Intent.ACTION_MAIN) { - if (isReadStorageAllowed()) { - configString = processIntent(newIntent, newIntentAction) - } else { - requestStoragePermission() + if (value && configString != null) { + sendImportConfigCommand() } } + private val TAG = "VPNActivity" + + private val CAMERA_ACTION_CODE = 101 + private val CREATE_FILE_ACTION_CODE = 102 + + private var tmpFileContentToSave: String = "" + + private val delayedCommands: ArrayList> = ArrayList() + + companion object { + private lateinit var instance: VPNActivity + + @JvmStatic fun getInstance(): VPNActivity { + return instance + } + + @JvmStatic fun connectService() { + VPNActivity.getInstance().initServiceConnection() + } + + @JvmStatic fun startQrCodeReader() { + VPNActivity.getInstance().startQrCodeActivity() + } + + @JvmStatic fun sendToService(actionCode: Int, body: String) { + VPNActivity.getInstance().dispatchParcel(actionCode, body) + } + + @JvmStatic fun saveFileAs(fileContent: String, suggestedName: String) { + VPNActivity.getInstance().saveFile(fileContent, suggestedName) + } + + @JvmStatic fun putTextToClipboard(text: String) { + VPNActivity.getInstance().putToClipboard(text) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + instance = this + + val newIntent = intent + val newIntentAction: String? = newIntent.action + + if (newIntent != null && newIntentAction != null && newIntentAction == "org.amnezia.vpn.qt.IMPORT_CONFIG") { + configString = newIntent.getStringExtra("CONFIG") + } + } + + private fun startQrCodeActivity() { + val intent = Intent(this, CameraActivity::class.java) + startActivityForResult(intent, CAMERA_ACTION_CODE) + } + + private fun saveFile(fileContent: String, suggestedName: String) { + tmpFileContentToSave = fileContent + + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "text/*" + putExtra(Intent.EXTRA_TITLE, suggestedName) + } + + startActivityForResult(intent, CREATE_FILE_ACTION_CODE) + } + + override fun getSystemService(name: String): Any? { + return if (Build.VERSION.SDK_INT >= 29 && name == "clipboard") { + // QT will always attempt to read the clipboard if content is there. + // since we have no use of the clipboard in android 10+ + // we _can_ return null + // And we defnitly should since android 12 displays clipboard access. + null + } else { + super.getSystemService(name) + } + } + + external fun handleBackButton(): Boolean + + external fun onServiceMessage(actionCode: Int, body: String?) + external fun qtOnServiceConnected() + external fun qtOnServiceDisconnected() + external fun onActivityMessage(actionCode: Int, body: String?) + + private fun dispatchParcel(actionCode: Int, body: String) { + if (!isBound) { + Log.d(TAG, "dispatchParcel: not bound") + delayedCommands.add(Pair(actionCode, body)) + return + } + + if (delayedCommands.size > 0) { + for (command in delayedCommands) { + processCommand(command.first, command.second) + } + + delayedCommands.clear() + } + + processCommand(actionCode, body) + } + + private fun processCommand(actionCode: Int, body: String) { + val out: Parcel = Parcel.obtain() + out.writeByteArray(body.toByteArray()) + + try { + vpnServiceBinder?.transact(actionCode, out, Parcel.obtain(), 0) + } catch (e: DeadObjectException) { + isBound = false + vpnServiceBinder = null + qtOnServiceDisconnected() + } catch (e: RemoteException) { + e.printStackTrace() + } + } + + override fun onNewIntent(newIntent: Intent) { super.onNewIntent(intent) + + setIntent(newIntent) + + val newIntentAction = newIntent.action + + if (newIntent != null && newIntentAction != null && newIntentAction == INTENT_ACTION_IMPORT_CONFIG) { + configString = newIntent.getStringExtra("CONFIG") + } } override fun onResume() { super.onResume() - if (configString != null && !isBound) { - bindVpnService() + if (configString != null && isBound) { + sendImportConfigCommand() } } - override fun onPause() { - if (vpnServiceBinder != null && isBound) { - unbindService(connection) - isBound = false - } - super.onPause() - } + private fun sendImportConfigCommand() { + if (configString != null) { + val msg: Parcel = Parcel.obtain() + msg.writeString(configString!!) - private fun isReadStorageAllowed(): Boolean { - val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) - return permissionStatus == PackageManager.PERMISSION_GRANTED - } - - private fun requestStoragePermission() { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE) - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (requestCode == STORAGE_PERMISSION_CODE) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Storage read permission granted") - - if (configString != null) { - bindVpnService() - } - } else { - Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show() + try { + vpnServiceBinder?.transact(ACTION_IMPORT_CONFIG, msg, Parcel.obtain(), 0) + } catch (e: RemoteException) { + e.printStackTrace() } + + configString = null } } - private fun bindVpnService() { - try { - val intent = Intent(this, VPNService::class.java) - intent.action = IMPORT_ACTION_CODE - - bindService(intent, connection, Context.BIND_AUTO_CREATE) - } catch (e: Exception) { - e.printStackTrace() - } - } - - private fun processIntent(intent: Intent, action: String): String? { - val scheme = intent.scheme - - if (scheme == null) { - return null - } - - if (action.compareTo(Intent.ACTION_VIEW) == 0) { - val resolver = contentResolver - - if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) { - val uri = intent.data - val name: String? = getContentName(resolver, uri) - - Log.d(TAG, "Content intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name) - - val input = resolver.openInputStream(uri!!) - - return input?.bufferedReader()?.use(BufferedReader::readText) - } else if (scheme.compareTo(ContentResolver.SCHEME_FILE) == 0) { - val uri = intent.data - val name = uri!!.lastPathSegment - - Log.d(TAG, "File intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name) - - val input = resolver.openInputStream(uri) - - return input?.bufferedReader()?.use(BufferedReader::readText) - } - } - - return null - } - - private fun getContentName(resolver: ContentResolver?, uri: Uri?): String? { - val cursor = resolver!!.query(uri!!, null, null, null, null) - - cursor.use { - cursor!!.moveToFirst() - val nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) - return if (nameIndex >= 0) { - return cursor.getString(nameIndex) - } else { - null - } - } - } - - private var connection: ServiceConnection = object : ServiceConnection { + private fun createConnection() = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, binder: IBinder) { - vpnServiceBinder = Messenger(binder) + vpnServiceBinder = binder - if (configString != null) { - val msg: Message = Message.obtain(null, IMPORT_COMMAND_CODE, 0, 0) - val bundle = Bundle() - bundle.putString(IMPORT_CONFIG_KEY, configString!!) - msg.data = bundle - - try { - vpnServiceBinder?.send(msg) - } catch (e: RemoteException) { - e.printStackTrace() - } - - configString = null + // This is called when the connection with the service has been + // established, giving us the object we can use to + // interact with the service. We are communicating with the + // service using a Messenger, so here we get a client-side + // representation of that from the raw IBinder object. + if (registerBinder()){ + qtOnServiceConnected(); + } else { + qtOnServiceDisconnected(); + return } isBound = true @@ -183,14 +218,114 @@ class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() { override fun onServiceDisconnected(className: ComponentName) { vpnServiceBinder = null isBound = false + qtOnServiceDisconnected(); } } - override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { - if (keyCode == KeyEvent.KEYCODE_BACK && event.repeatCount == 0) { - onBackPressed() + private var connection: ServiceConnection = createConnection() + + private fun registerBinder(): Boolean { + val binder = VPNClientBinder() + val out: Parcel = Parcel.obtain() + out.writeStrongBinder(binder) + + try { + // Register our IBinder Listener + vpnServiceBinder?.transact(ACTION_REGISTER_LISTENER, out, Parcel.obtain(), 0) return true + } catch (e: DeadObjectException) { + isBound = false + vpnServiceBinder = null + } catch (e: RemoteException) { + e.printStackTrace() + } + return false + } + + private fun initServiceConnection() { + // We already have a connection to the service, + // just need to re-register the binder + if (isBound && vpnServiceBinder!!.isBinderAlive() && registerBinder()) { + qtOnServiceConnected() + return + } + + bindService(Intent(this, VPNService::class.java), connection, Context.BIND_AUTO_CREATE) + } + + // TODO: Move all ipc codes into a shared lib. + // this is getting out of hand. + private val PERMISSION_TRANSACTION = 1337 + private val ACTION_REGISTER_LISTENER = 3 + private val ACTION_RESUME_ACTIVATE = 7 + private val ACTION_IMPORT_CONFIG = 11 + private val EVENT_PERMISSION_REQURED = 6 + private val EVENT_DISCONNECTED = 2 + + private val UI_EVENT_QR_CODE_RECEIVED = 0 + + fun onPermissionRequest(code: Int, data: Parcel?) { + if (code != EVENT_PERMISSION_REQURED) { + return + } + + val x = Intent() + x.readFromParcel(data) + + startActivityForResult(x, PERMISSION_TRANSACTION) + } + + override protected fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == PERMISSION_TRANSACTION) { + // THATS US! + if (resultCode == RESULT_OK) { + // Prompt accepted, tell service to retry. + dispatchParcel(ACTION_RESUME_ACTIVATE, "") + } else { + // Tell the Client we've disconnected + onServiceMessage(EVENT_DISCONNECTED, "") + } + return + } + + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == CAMERA_ACTION_CODE && resultCode == RESULT_OK) { + val extra = data?.getStringExtra("result") ?: "" + onActivityMessage(UI_EVENT_QR_CODE_RECEIVED, extra) + } + + if (requestCode == CREATE_FILE_ACTION_CODE && resultCode == RESULT_OK) { + data?.data?.also { uri -> + alterDocument(uri) + } + } + } + + private fun alterDocument(uri: Uri) { + try { + applicationContext.contentResolver.openFileDescriptor(uri, "w")?.use { fd -> + FileOutputStream(fd.fileDescriptor).use { fos -> + fos.write(tmpFileContentToSave.toByteArray()) + } + } + } catch (e: FileNotFoundException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + + tmpFileContentToSave = "" + } + + private fun putToClipboard(text: String) { + this.runOnUiThread { + val clipboard = applicationContext.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager? + + if (clipboard != null) { + val clip: ClipData = ClipData.newPlainText("", text) + clipboard.setPrimaryClip(clip) + } } - return super.onKeyDown(keyCode, event) } } diff --git a/client/android/src/org/amnezia/vpn/qt/VPNApplication.java b/client/android/src/org/amnezia/vpn/qt/VPNApplication.java index 29514633..639b5a1e 100644 --- a/client/android/src/org/amnezia/vpn/qt/VPNApplication.java +++ b/client/android/src/org/amnezia/vpn/qt/VPNApplication.java @@ -5,7 +5,7 @@ import androidx.annotation.NonNull; import org.amnezia.vpn.shadowsocks.core.Core; import org.amnezia.vpn.shadowsocks.core.VpnManager; -public class VPNApplication extends org.qtproject.qt5.android.bindings.QtApplication { +public class VPNApplication extends org.qtproject.qt.android.bindings.QtApplication { private static VPNApplication instance; @Override diff --git a/client/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt b/client/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt new file mode 100644 index 00000000..6a8fa086 --- /dev/null +++ b/client/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt @@ -0,0 +1,27 @@ +/* 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/. */ + +package org.amnezia.vpn.qt + +import android.os.Binder +import android.os.Parcel +import android.util.Log + +const val permissionRequired = 6 + +class VPNClientBinder() : Binder() { + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { + if (code == permissionRequired) { + VPNActivity.getInstance().onPermissionRequest(code, data) + return true + } + + val buffer = data.createByteArray() + val stringData = buffer?.let { String(it) } + VPNActivity.getInstance().onServiceMessage(code, stringData) + + return true + } +} diff --git a/client/android/src/org/ftylitak/qzxing/QZXingLiveActivity.java b/client/android/src/org/ftylitak/qzxing/QZXingLiveActivity.java index 37ccb6ce..b6a4649d 100644 --- a/client/android/src/org/ftylitak/qzxing/QZXingLiveActivity.java +++ b/client/android/src/org/ftylitak/qzxing/QZXingLiveActivity.java @@ -2,7 +2,7 @@ package org.ftylitak.qzxing; import android.Manifest; import android.content.pm.PackageManager; -import org.qtproject.qt5.android.bindings.QtActivity; +import org.qtproject.qt.android.bindings.QtActivity; import static org.ftylitak.qzxing.Utilities.REQUEST_CAMERA; public class QZXingLiveActivity extends QtActivity { diff --git a/client/client.pro b/client/client.pro index 9ef13ea6..4810d0ba 100644 --- a/client/client.pro +++ b/client/client.pro @@ -1,4 +1,5 @@ -QT += widgets core gui network xml remoteobjects quick svg +QT += widgets core gui network xml remoteobjects quick svg quickcontrols2 +equals(QT_MAJOR_VERSION, 6): QT += core5compat TARGET = AmneziaVPN TEMPLATE = app @@ -7,7 +8,7 @@ TEMPLATE = app IS_CI=$$(CI) !isEmpty(IS_CI){ message("Detected CI env") - CONFIG += silent ccache + CONFIG += silent #ccache } CONFIG += qtquickcompiler @@ -16,6 +17,7 @@ include("3rd/QtSsh/src/ssh/qssh.pri") include("3rd/QtSsh/src/botan/botan.pri") !android:!ios:include("3rd/SingleApplication/singleapplication.pri") include ("3rd/SortFilterProxyModel/SortFilterProxyModel.pri") + include("3rd/qrcodegen/qrcodegen.pri") include("3rd/QSimpleCrypto/QSimpleCrypto.pri") include("3rd/qtkeychain/qtkeychain.pri") @@ -41,8 +43,8 @@ HEADERS += \ core/scripts_registry.h \ core/server_defs.h \ core/servercontroller.h \ - debug.h \ defines.h \ + logger.h \ managementserver.h \ platforms/ios/MobileUtils.h \ platforms/linux/leakdetector.h \ @@ -80,12 +82,9 @@ HEADERS += \ ui/uilogic.h \ ui/qautostart.h \ ui/models/sites_model.h \ - utils.h \ + utilities.h \ vpnconnection.h \ protocols/vpnprotocol.h \ - logger.h \ - loghandler.h \ - loglevel.h \ constants.h \ platforms/ios/QRCodeReaderBase.h @@ -104,7 +103,7 @@ SOURCES += \ core/scripts_registry.cpp \ core/server_defs.cpp \ core/servercontroller.cpp \ - debug.cpp \ + logger.cpp \ main.cpp \ managementserver.cpp \ platforms/ios/MobileUtils.cpp \ @@ -141,11 +140,9 @@ SOURCES += \ ui/uilogic.cpp \ ui/qautostart.cpp \ ui/models/sites_model.cpp \ - utils.cpp \ + utilities.cpp \ vpnconnection.cpp \ protocols/vpnprotocol.cpp \ - logger.cpp \ - loghandler.cpp \ platforms/ios/QRCodeReaderBase.cpp RESOURCES += \ @@ -162,11 +159,9 @@ win32 { HEADERS += \ protocols/ikev2_vpn_protocol_windows.h \ - ui/framelesswindow.h SOURCES += \ protocols/ikev2_vpn_protocol_windows.cpp \ - ui/framelesswindow.cpp VERSION = 2.0.0.0 QMAKE_TARGET_COMPANY = "AmneziaVPN" @@ -181,6 +176,7 @@ win32 { -lws2_32 \ -lgdi32 + QMAKE_LFLAGS_WINDOWS += /entry:mainCRTStartup !contains(QMAKE_TARGET.arch, x86_64) { message("Windows x86 build") @@ -240,7 +236,15 @@ win32|macx|linux:!android { } android { - QT += androidextras + message(Platform: android) + message("$$ANDROID_TARGET_ARCH") + versionAtLeast(QT_VERSION, 6.0.0) { + # We need to include qtprivate api's + # As QAndroidBinder is not yet implemented with a public api + QT += core-private + ANDROID_ABIS = $$ANDROID_TARGET_ARCH + } + DEFINES += MVPN_ANDROID INCLUDEPATH += platforms/android @@ -248,13 +252,16 @@ android { HEADERS += \ platforms/android/android_controller.h \ platforms/android/android_notificationhandler.h \ - protocols/android_vpnprotocol.h + protocols/android_vpnprotocol.h \ + platforms/android/androidutils.h \ + platforms/android/androidvpnactivity.h SOURCES += \ platforms/android/android_controller.cpp \ platforms/android/android_notificationhandler.cpp \ - protocols/android_vpnprotocol.cpp - + protocols/android_vpnprotocol.cpp \ + platforms/android/androidutils.cpp \ + platforms/android/androidvpnactivity.cpp DISTFILES += \ android/AndroidManifest.xml \ @@ -283,6 +290,7 @@ android { ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android for (abi, ANDROID_ABIS): { + equals(ANDROID_TARGET_ARCH,$$abi) { LIBS += $$PWD/3rd/OpenSSL/lib/android/$${abi}/libcrypto.a LIBS += $$PWD/3rd/OpenSSL/lib/android/$${abi}/libssl.a @@ -352,21 +360,21 @@ ios { message("Building for iPhone OS") QMAKE_TARGET_BUNDLE_PREFIX = org.amnezia QMAKE_BUNDLE = AmneziaVPN - QMAKE_IOS_DEPLOYMENT_TARGET = 12.0 + QMAKE_IOS_DEPLOYMENT_TARGET = 13.0 QMAKE_APPLE_TARGETED_DEVICE_FAMILY = 1 QMAKE_DEVELOPMENT_TEAM = X7UJ388FXK QMAKE_PROVISIONING_PROFILE = f2fefb59-14aa-4aa9-ac14-1d5531b06dcc QMAKE_XCODE_CODE_SIGN_IDENTITY = "Apple Distribution" QMAKE_INFO_PLIST = $$PWD/ios/app/Info.plist - + XCODEBUILD_FLAGS += -allowProvisioningUpdates - + DEFINES += iphoneos - + contains(QT_ARCH, arm64) { message("Building for iOS/ARM v8 64-bit architecture") ARCH_TAG = "ios_armv8_64" - + LIBS += $$PWD/3rd/OpenSSL/lib/ios/iphone/libcrypto.a LIBS += $$PWD/3rd/OpenSSL/lib/ios/iphone/libssl.a } else { @@ -375,15 +383,15 @@ ios { } } # } - + # CONFIG(iphonesimulator, iphoneos|iphonesimulator) { # iphonesimulator { # message("Building for iPhone Simulator") # ARCH_TAG = "ios_x86_64" -# +# # DEFINES += iphonesimulator -# +# # LIBS += $$PWD/3rd/OpenSSL/lib/ios/simulator/libcrypto.a # LIBS += $$PWD/3rd/OpenSSL/lib/ios/simulator/libssl.a # } diff --git a/client/cmake/3rdparty.cmake b/client/cmake/3rdparty.cmake new file mode 100644 index 00000000..a87b4df5 --- /dev/null +++ b/client/cmake/3rdparty.cmake @@ -0,0 +1,26 @@ +set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..) + +include(${CLIENT_ROOT_DIR}/3rd/QtSsh/src/ssh/qssh.cmake) + +include(${CLIENT_ROOT_DIR}/3rd/QtSsh/src/botan/botan.cmake) +if(NOT IOS AND NOT ANDROID) + include(${CLIENT_ROOT_DIR}/3rd/SingleApplication/singleapplication.cmake) +endif() + +add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel) +set(LIBS ${LIBS} SortFilterProxyModel) + +include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake) +include(${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/QSimpleCrypto.cmake) + +set(BUILD_WITH_QT6 ON) +set(BUILD_SHARED_LIBS OFF) +add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain) +set(LIBS ${LIBS} qt6keychain) + +include_directories( + ${CLIENT_ROOT_DIR}/3rd/OpenSSL/include + ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include + ${CLIENT_ROOT_DIR}/3rd/qtkeychain + ${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain +) diff --git a/client/cmake/golang.cmake b/client/cmake/golang.cmake new file mode 100644 index 00000000..21cc2725 --- /dev/null +++ b/client/cmake/golang.cmake @@ -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/. + +## Find the absolute path to the go build tool. +find_program(GOLANG_BUILD_TOOL NAMES go REQUIRED) + +## Build a library file from a golang project. +function(build_go_archive OUTPUT_NAME MODULE_FILE) + cmake_parse_arguments(GOBUILD + "" + "GOOS;GOARCH" + "CGO_CFLAGS;CGO_LDFLAGS;SOURCES" + ${ARGN}) + + string(REGEX REPLACE "\\.[^/]*$" ".h" GOBUILD_HEADER_FILE ${OUTPUT_NAME}) + get_filename_component(GOBUILD_MODULE_ABS ${MODULE_FILE} ABSOLUTE) + get_filename_component(GOBUILD_MODULE_DIR ${GOBUILD_MODULE_ABS} DIRECTORY) + set(GOBUILD_ARGS -buildmode=c-archive -trimpath -v) + if(IS_DIRECTORY ${GOBUILD_MODULE_DIR}/vendor) + list(APPEND GOBUILD_ARGS -mod vendor) + endif() + + ## Collect arguments, or find their defaults. + if(NOT GOBUILD_CGO_CFLAGS) + execute_process(OUTPUT_VARIABLE GOBUILD_CGO_CFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env CGO_CFLAGS) + separate_arguments(GOBUILD_CGO_CFLAGS NATIVE_COMMAND ${GOBUILD_CGO_CFLAGS}) + endif() + if(NOT GOBUILD_CGO_LDFLAGS) + execute_process(OUTPUT_VARIABLE GOBUILD_CGO_LDFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env CGO_LDFLAGS) + separate_arguments(GOBUILD_CGO_LDFLAGS NATIVE_COMMAND ${GOBUILD_CGO_LDFLAGS}) + endif() + if(NOT GOBUILD_GOOS) + execute_process(OUTPUT_VARIABLE GOBUILD_GOOS OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env GOOS) + endif() + if(NOT GOBUILD_GOARCH) + execute_process(OUTPUT_VARIABLE GOBUILD_GOARCH OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env GOARCH) + endif() + + ## Use a go-cache isolated to our project + set(GOCACHE ${CMAKE_BINARY_DIR}/go-cache) + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_BINARY_DIR}/go-cache) + + ## The command that does the building + get_filename_component(ABS_OUTPUT_NAME ${OUTPUT_NAME} ABSOLUTE) + add_custom_command( + OUTPUT ${OUTPUT_NAME} ${GOBUILD_HEADER_FILE} + DEPENDS ${MODULE_FILE} ${GOBUILD_SOURCES} + WORKING_DIRECTORY ${GOBUILD_MODULE_DIR} + COMMAND ${CMAKE_COMMAND} -E env GOCACHE=${GOCACHE} + CGO_ENABLED=1 + CGO_CFLAGS="${GOBUILD_CGO_CFLAGS}" + CGO_LDFLAGS="${GOBUILD_CGO_LDFLAGS}" + GOOS=${GOBUILD_GOOS} + GOARCH=${GOBUILD_GOARCH} + ${GOLANG_BUILD_TOOL} build ${GOBUILD_ARGS} -o ${ABS_OUTPUT_NAME} + ) +endfunction(build_go_archive) + +## Create a library target built from a golang c-archive. +function(add_go_library GOTARGET SOURCE) + cmake_parse_arguments(GOLANG + "" + "GOOS;GOARCH" + "CGO_CFLAGS;CGO_LDFLAGS" + ${ARGN}) + get_filename_component(SRC_NAME ${SOURCE} NAME) + get_filename_component(DIR_NAME ${SOURCE} DIRECTORY) + get_filename_component(DIR_ABSOLUTE ${DIR_NAME} ABSOLUTE) + + file(GLOB_RECURSE SRC_DEPS ${DIR_NAME}/*.go) + set(HEADER_NAME "${GOTARGET}.h") + set(ARCHIVE_NAME "${GOTARGET}${CMAKE_STATIC_LIBRARY_SUFFIX}") + + set(GOCACHE ${CMAKE_BINARY_DIR}/go-cache) + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_BINARY_DIR}/go-cache) + set(GOFLAGS -buildmode=c-archive -trimpath -v) + if(IS_DIRECTORY ${DIR_NAME}/vendor) + set(GOFLAGS ${GOFLAGS} -mod vendor) + endif() + + ## Add extras to the CGO compiler and linker flags. + execute_process(OUTPUT_VARIABLE DEFAULT_CGO_CFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env CGO_CFLAGS) + execute_process(OUTPUT_VARIABLE DEFAULT_CGO_LDFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env CGO_LDFLAGS) + separate_arguments(DEFAULT_CGO_CFLAGS NATIVE_COMMAND ${DEFAULT_CGO_CFLAGS}) + separate_arguments(DEFAULT_CGO_LDFLAGS NATIVE_COMMAND ${DEFAULT_CGO_LDFLAGS}) + list(PREPEND GOLANG_CGO_CFLAGS ${DEFAULT_CGO_CFLAGS}) + list(PREPEND GOLANG_CGO_LDFLAGS ${DEFAULT_CGO_LDFLAGS}) + if(NOT GOLANG_GOOS) + execute_process(OUTPUT_VARIABLE GOLANG_GOOS OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env GOOS) + endif() + if(NOT GOLANG_GOARCH) + execute_process(OUTPUT_VARIABLE GOLANG_GOARCH OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env GOARCH) + endif() + + if(APPLE AND CMAKE_OSX_SYSROOT) + execute_process(OUTPUT_VARIABLE SDKROOT OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND xcrun --sdk ${CMAKE_OSX_SYSROOT} --show-sdk-path) + list(APPEND GOLANG_CGO_CFLAGS -isysroot ${SDKROOT}) + list(APPEND GOLANG_CGO_LDFLAGS -isysroot ${SDKROOT}) + endif() + + ## The actual commands that do the building. + add_custom_target(golang_${GOTARGET} + BYPRODUCTS ${ARCHIVE_NAME} ${HEADER_NAME} + WORKING_DIRECTORY ${DIR_ABSOLUTE} + SOURCES ${SRC_DEPS} ${DIR_NAME}/go.mod + COMMAND ${CMAKE_COMMAND} -E env GOCACHE=${GOCACHE} + CGO_ENABLED=1 + CGO_CFLAGS="${GOLANG_CGO_CFLAGS}" + CGO_LDFLAGS="${GOLANG_CGO_LDFLAGS}" + GOOS="${GOLANG_GOOS}" + GOARCH="${GOLANG_GOARCH}" + ${GOLANG_BUILD_TOOL} build ${GOFLAGS} -o ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME} ${SRC_NAME} + ) + set_target_properties(golang_${GOTARGET} PROPERTIES FOLDER "Libs") + + ## Wrap up the built library as an imported target. + add_library(${GOTARGET} STATIC IMPORTED GLOBAL) + add_dependencies(${GOTARGET} golang_${GOTARGET}) + set_target_properties(${GOTARGET} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR} + INTERFACE_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME} + IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}) +endfunction(add_go_library) \ No newline at end of file diff --git a/client/cmake/ios-arch-fixup.cmake b/client/cmake/ios-arch-fixup.cmake new file mode 100644 index 00000000..0f349f8d --- /dev/null +++ b/client/cmake/ios-arch-fixup.cmake @@ -0,0 +1,43 @@ +if(NOT XCODE) + return() +endif() + +## Enumerate all the targets in the project +get_directory_property(IOS_SUBDIRS SUBDIRECTORIES) +get_directory_property(IOS_TARGETS BUILDSYSTEM_TARGETS) +while(IOS_SUBDIRS) + list(POP_FRONT IOS_SUBDIRS SUBDIR) + + get_directory_property(SUBDIR_TARGETS DIRECTORY ${SUBDIR} BUILDSYSTEM_TARGETS) + list(APPEND IOS_TARGETS ${SUBDIR_TARGETS}) + + get_directory_property(SUBDIR_NESTED DIRECTORY ${SUBDIR} SUBDIRECTORIES) + list(APPEND IOS_SUBDIRS ${SUBDIR_NESTED}) +endwhile() + +## The set of target types that we want to modify. +set(IOS_TARGET_COMPILED_TYPES + STATIC_LIBRARY + MODULE_LIBRARY + SHARED_LIBRARY + OBJECT_LIBRARY + EXECUTABLE +) + +## Inspect all the targets, and add extra properties if necessary. +while(IOS_TARGETS) + list(POP_FRONT IOS_TARGETS TARGET_NAME) + + get_target_property(TARGET_TYPE ${TARGET_NAME} TYPE) + list(FIND IOS_TARGET_COMPILED_TYPES ${TARGET_TYPE} IOS_TARGET_TYPE_INDEX) + if(IOS_TARGET_TYPE_INDEX LESS 0) + continue() + endif() + + ## I just want to say it's amazing this doesn't explode with syntax errors. + message("Patching architectures for ${TARGET_NAME}") + set_target_properties(${TARGET_NAME} PROPERTIES + XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64" + XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64" + ) +endwhile() \ No newline at end of file diff --git a/client/cmake/osxtools.cmake b/client/cmake/osxtools.cmake new file mode 100644 index 00000000..6e595ebe --- /dev/null +++ b/client/cmake/osxtools.cmake @@ -0,0 +1,150 @@ +if(NOT APPLE) + message(FATAL_ERROR "OSX Tools are only supported on Apple targets") +endif() + +set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..) + +if(CMAKE_COLOR_MAKEFILE) + set(COMMENT_ECHO_COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --blue --bold) +else() + set(COMMENT_ECHO_COMMAND ${CMAKE_COMMAND} -E echo) +endif() + +if(CODE_SIGN_IDENTITY) + find_program(CODESIGN_BIN NAMES codesign) + if(NOT CODESIGN_BIN) + messsage(FATAL_ERROR "Cannot sign code, could not find 'codesign' executable") + endif() + set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ${CODE_SIGN_IDENTITY}) +endif() + +## A helper to copy files into the application bundle +function(osx_bundle_files TARGET) + cmake_parse_arguments(BUNDLE + "" + "DESTINATION" + "FILES" + ${ARGN}) + + if(NOT BUNDLE_DESTINATION) + set(BUNDLE_DESTINATION Resources) + endif() + + foreach(FILE ${BUNDLE_FILES}) + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND_EXPAND_LISTS + COMMAND ${COMMENT_ECHO_COMMAND} "Bundling ${FILE}" + COMMAND ${CMAKE_COMMAND} -E make_directory $/${BUNDLE_DESTINATION} + COMMAND ${CMAKE_COMMAND} -E copy ${FILE} $/${BUNDLE_DESTINATION}/ + ) + endforeach() +endfunction(osx_bundle_files) + +## A helper to bundle an asset catalog. +function(osx_bundle_assetcatalog TARGET) + cmake_parse_arguments(XCASSETS + "" + "CATALOG;PLATFORM" + "DEVICES" + ${ARGN}) + + if(XCASSETS_PLATFORM) + set(XCASSETS_TARGET_ARGS --platform ${XCASSETS_PLATFORM}) + elseif(IOS) + set(XCASSETS_TARGET_ARGS --platform iphoneos) + else() + set(XCASSETS_TARGET_ARGS --platform macosx) + endif() + foreach(DEVNAME ${XCASSETS_DEVICES}) + list(APPEND XCASSETS_TARGET_ARGS --target-device ${DEVNAME}) + endforeach() + list(APPEND XCASSETS_TARGET_ARGS --minimum-deployment-target ${CMAKE_OSX_DEPLOYMENT_TARGET}) + + ## Compile the asset catalog + set(XCASSETS_GEN_PLIST ${CMAKE_CURRENT_BINARY_DIR}/xcassets_generated_info.plist) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xcassets/Assets.car ${XCASSETS_GEN_PLIST} + MAIN_DEPENDENCY ${XCASSETS_CATALOG}/Contents.json + COMMENT "Building asset catalog" + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/xcassets + COMMAND actool --output-format human-readable-text --notices --warnings + ${XCASSETS_TARGET_ARGS} + --app-icon AppIcon + --output-partial-info-plist ${XCASSETS_GEN_PLIST} + --development-region en --enable-on-demand-resources NO + --compile ${CMAKE_CURRENT_BINARY_DIR}/xcassets ${XCASSETS_CATALOG} + ) + + ## Patch the asset catalog into the target bundle. + if(NOT IOS) + set(XCASSETS_RESOURCE_DIR "Resources") + endif() + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMENT "Bundling asset catalog" + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/xcassets $/${XCASSETS_RESOURCE_DIR} + COMMAND ${CLIENT_ROOT_DIR}/scripts/macos/merge_plist.py ${XCASSETS_GEN_PLIST} -o $/Info.plist + ) + + target_sources(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/xcassets/Assets.car) + set_source_files_properties( + ${CMAKE_CURRENT_BINARY_DIR}/xcassets/Assets.car + ${XCASSETS_GEN_PLIST} + PROPERTIES + GENERATED TRUE + HEADER_FILE_ONLY TRUE + ) + + target_sources(${TARGET} PRIVATE ${XCASSETS_GEN_PLIST}) + set_source_files_properties(${XCASSETS_GEN_PLIST} PROPERTIES GENERATED TRUE) +endfunction() + +## A helper to code-sign an executable. +function(osx_codesign_target TARGET) + ## Xcode should perform automatic code-signing for us. + if(XCODE) + return() + endif() + + if(CODE_SIGN_IDENTITY) + cmake_parse_arguments(CODESIGN + "FORCE" + "" + "OPTIONS;FILES" + ${ARGN}) + + set(CODESIGN_ARGS --timestamp -s "${CODE_SIGN_IDENTITY}") + if(CODESIGN_FORCE) + list(APPEND CODESIGN_ARGS -f) + endif() + if(CODESIGN_OPTIONS) + list(JOIN CODESIGN_OPTIONS , CODESIGN_OPTIONS_JOINED) + list(APPEND CODESIGN_ARGS "--option=${CODESIGN_OPTIONS_JOINED}") + endif() + + ## Process the entitlements as though Xcode has done variable expansion. + ## This only supports the PRODUCT_BUNDLE_IDENTIFIER and DEVELOPMENT_TEAM + ## for now. + get_target_property(CODESIGN_ENTITLEMENTS ${TARGET} XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS) + if(CODESIGN_ENTITLEMENTS) + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CLIENT_ROOT_DIR}/scripts/utils/make_template.py ${CODESIGN_ENTITLEMENTS} + -k PRODUCT_BUNDLE_IDENTIFIER=$ + -k DEVELOPMENT_TEAM=$ + -o ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_codesign.entitlements + ) + list(APPEND CODESIGN_ARGS --entitlements ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_codesign.entitlements) + endif() + + ## If no files were specified, sign the target itself. + if(NOT CODESIGN_FILES) + set(CODESIGN_FILES $) + endif() + + foreach(FILE ${CODESIGN_FILES}) + add_custom_command(TARGET ${TARGET} POST_BUILD VERBATIM + COMMAND ${COMMENT_ECHO_COMMAND} "Signing ${TARGET}: ${FILE}" + COMMAND ${CODESIGN_BIN} ${CODESIGN_ARGS} ${FILE} + ) + endforeach() + endif() +endfunction() \ No newline at end of file diff --git a/client/configurators/cloak_configurator.cpp b/client/configurators/cloak_configurator.cpp index 50f554dc..0cfd74fc 100644 --- a/client/configurators/cloak_configurator.cpp +++ b/client/configurators/cloak_configurator.cpp @@ -43,7 +43,7 @@ QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials, config.insert("StreamTimeout", 300); // transfer params to protocol runner - config.insert(config_key::transport_proto, "$OPENVPN_TRANSPORT_PROTO"); + config.insert(config_key::transport_proto, "tcp"); config.insert(config_key::remote, credentials.hostName); config.insert(config_key::port, "$CLOAK_SERVER_PORT"); diff --git a/client/configurators/ikev2_configurator.cpp b/client/configurators/ikev2_configurator.cpp index db6b53a2..9b702824 100644 --- a/client/configurators/ikev2_configurator.cpp +++ b/client/configurators/ikev2_configurator.cpp @@ -11,8 +11,9 @@ #include "containers/containers_defs.h" #include "core/server_defs.h" #include "core/scripts_registry.h" +#include "utilities.h" #include "core/servercontroller.h" -#include "utils.h" + Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, std::shared_ptr serverController, QObject *parent): ConfiguratorBase(settings, serverController, parent) diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index a7719108..441c15f6 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -12,8 +12,8 @@ #include "core/server_defs.h" #include "core/servercontroller.h" #include "core/scripts_registry.h" +#include "utilities.h" #include "settings.h" -#include "utils.h" #include #include diff --git a/client/configurators/ssh_configurator.cpp b/client/configurators/ssh_configurator.cpp index ac867c70..f8dc9869 100644 --- a/client/configurators/ssh_configurator.cpp +++ b/client/configurators/ssh_configurator.cpp @@ -12,7 +12,7 @@ #include #include "core/server_defs.h" -#include "utils.h" +#include "utilities.h" #include "sftpdefs.h" @@ -66,7 +66,7 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials) { #ifndef Q_OS_IOS QProcess *p = new QProcess(); - p->setReadChannelMode(QProcess::SeparateChannels); + p->setProcessChannelMode(QProcess::SeparateChannels); #ifdef Q_OS_WIN p->setProcessEnvironment(prepareEnv()); diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index 42d3063e..1433eeaa 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -11,8 +11,8 @@ #include #include "containers/containers_defs.h" +#include "utilities.h" #include "settings.h" -#include "utils.h" VpnConfigurator::VpnConfigurator(std::shared_ptr settings, std::shared_ptr serverController, QObject *parent): diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 913299c7..532d89f6 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -17,9 +17,9 @@ #include "containers/containers_defs.h" #include "core/server_defs.h" #include "core/scripts_registry.h" +#include "utilities.h" #include "core/servercontroller.h" #include "settings.h" -#include "utils.h" WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, std::shared_ptr serverController, QObject *parent): ConfiguratorBase(settings, serverController, parent) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 3313a772..cca77e7d 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -27,6 +27,16 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c){ return "amnezia-" + containerKey.toLower(); } +QString ContainerProps::containerTypeToString(amnezia::DockerContainer c){ + if (c == DockerContainer::None) return "none"; + if (c == DockerContainer::Ipsec) return "ikev2"; + + QMetaEnum metaEnum = QMetaEnum::fromType(); + QString containerKey = metaEnum.valueToKey(static_cast(c)); + + return containerKey.toLower(); +} + QVector ContainerProps::protocolsForContainer(amnezia::DockerContainer container) { switch (container) { @@ -70,7 +80,7 @@ QList ContainerProps::allContainers() QMap ContainerProps::containerHumanNames() { return { - {DockerContainer::None, "Unknown (Old version)"}, + {DockerContainer::None, "Not installed"}, {DockerContainer::OpenVpn, "OpenVPN"}, {DockerContainer::ShadowSocks, "OpenVpn over ShadowSocks"}, {DockerContainer::Cloak, "OpenVpn over Cloak"}, @@ -171,3 +181,10 @@ return false; #endif } +QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) +{ + switch (c) { + case DockerContainer::Ipsec : return QStringList{"500", "4500"}; + default: return {}; + } +} diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index f1891864..33e9a4ae 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -37,24 +37,26 @@ class ContainerProps : public QObject Q_OBJECT public: - Q_INVOKABLE static DockerContainer containerFromString(const QString &container); - Q_INVOKABLE static QString containerToString(DockerContainer container); + Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container); + Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container); + Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c); - Q_INVOKABLE static QList allContainers(); + Q_INVOKABLE static QList allContainers(); - Q_INVOKABLE static QMap containerHumanNames(); - Q_INVOKABLE static QMap containerDescriptions(); + Q_INVOKABLE static QMap containerHumanNames(); + Q_INVOKABLE static QMap containerDescriptions(); // these protocols will be displayed in container settings - Q_INVOKABLE static QVector protocolsForContainer(DockerContainer container); + Q_INVOKABLE static QVector protocolsForContainer(amnezia::DockerContainer container); - Q_INVOKABLE static ServiceType containerService(DockerContainer c); + Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c); // binding between Docker container and main protocol of given container // it may be changed fot future containers :) - Q_INVOKABLE static Proto defaultProtocol(DockerContainer c); + Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c); - Q_INVOKABLE static bool isSupportedByCurrentPlatform(DockerContainer c); + Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c); + Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c); }; diff --git a/client/core/defs.h b/client/core/defs.h index 5845fd3c..3f861401 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -31,6 +31,7 @@ enum ErrorCode ServerPortAlreadyAllocatedError, ServerContainerMissingError, ServerDockerFailedError, + ServerCancelInstallation, // Ssh connection errors SshSocketError, SshTimeoutError, SshProtocolError, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 722dd4b4..1e7eb395 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -15,6 +15,7 @@ QString errorString(ErrorCode code){ case(ServerPortAlreadyAllocatedError): return QObject::tr("Server port already used. Check for another software"); case(ServerContainerMissingError): return QObject::tr("Server error: Docker container missing"); case(ServerDockerFailedError): return QObject::tr("Server error: Docker failed"); + case(ServerCancelInstallation): return QObject::tr("Installation canceled by user"); // Ssh connection errors case(SshSocketError): return QObject::tr("Ssh connection error"); diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 77c056bd..d33881a2 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -31,6 +31,7 @@ QString amnezia::scriptName(SharedScriptType type) case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh"); case SharedScriptType::setup_host_firewall: return QLatin1String("setup_host_firewall.sh"); case SharedScriptType::check_connection: return QLatin1String("check_connection.sh"); + case SharedScriptType::check_server_is_busy: return QLatin1String("check_server_is_busy.sh"); } } diff --git a/client/core/scripts_registry.h b/client/core/scripts_registry.h index 3d69d95a..9528d763 100644 --- a/client/core/scripts_registry.h +++ b/client/core/scripts_registry.h @@ -15,7 +15,8 @@ enum SharedScriptType { remove_container, remove_all_containers, setup_host_firewall, - check_connection + check_connection, + check_server_is_busy }; enum ProtocolScriptType { // Protocol scripts diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 2828d2b0..2be9d38f 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include "sftpchannel.h" #include "sshconnectionmanager.h" @@ -20,7 +22,7 @@ #include "server_defs.h" #include "settings.h" #include "scripts_registry.h" -#include "utils.h" +#include "utilities.h" #include @@ -36,7 +38,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr const std::function)> &cbReadStdOut, const std::function)> &cbReadStdErr) { - SshConnection *client = connectToHost(sshParams(credentials)); + QSharedPointer client = connectToHost(sshParams(credentials)); if (client->state() == SshConnection::State::Connecting) { qDebug() << "ServerController::runScript aborted, connectToHost in progress"; return ErrorCode::SshTimeoutError; @@ -51,7 +53,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr qDebug() << "Run script"; QString totalLine; - const QStringList &lines = script.split("\n", QString::SkipEmptyParts); + const QStringList &lines = script.split("\n", Qt::SkipEmptyParts); for (int i = 0; i < lines.count(); i++) { QString currentLine = lines.at(i); QString nextLine; @@ -79,7 +81,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr } qDebug().noquote() << "EXEC" << lineToExec; - Debug::appendSshLog("Run command:" + lineToExec); + Logger::appendSshLog("Run command:" + lineToExec); QSharedPointer proc = client->createRemoteProcess(lineToExec.toUtf8()); @@ -105,7 +107,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr QString s = proc->readAllStandardOutput(); if (s != "." && !s.isEmpty()) { - Debug::appendSshLog("Output: " + s); + Logger::appendSshLog("Output: " + s); qDebug().noquote() << "stdout" << s; } if (cbReadStdOut) cbReadStdOut(s, proc); @@ -114,7 +116,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, &wait, [proc, cbReadStdErr](){ QString s = proc->readAllStandardError(); if (s != "." && !s.isEmpty()) { - Debug::appendSshLog("Output: " + s); + Logger::appendSshLog("Output: " + s); qDebug().noquote() << "stderr" << s; } if (cbReadStdErr) cbReadStdErr(s, proc); @@ -140,7 +142,7 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti const std::function)> &cbReadStdErr) { QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh"; - Debug::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script); + Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script); ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName); if (e) return e; @@ -227,7 +229,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container, qDebug().noquote() << "Copy file from container\n" << script; - SshConnection *client = connectToHost(sshParams(credentials)); + QSharedPointer client = connectToHost(sshParams(credentials)); if (client->state() != SshConnection::State::Connected) { if (errorCode) *errorCode = fromSshConnectionErrorCode(client->errorState()); return {}; @@ -286,7 +288,7 @@ ErrorCode ServerController::checkOpenVpnServer(DockerContainer container, const ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, QSsh::SftpOverwriteMode overwriteMode) { - SshConnection *client = connectToHost(sshParams(credentials)); + QSharedPointer client = connectToHost(sshParams(credentials)); if (client->state() != SshConnection::State::Connected) { return fromSshConnectionErrorCode(client->errorState()); } @@ -407,12 +409,18 @@ ErrorCode ServerController::removeContainer(const ServerCredentials &credentials genVarsForScript(credentials, container))); } -ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) +ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config, bool isUpdate) { qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container); //qDebug().noquote() << QJsonDocument(config).toJson(); ErrorCode e = ErrorCode::NoError; + if (!isUpdate) { + e = isServerPortBusy(credentials, container, config); + if (e) return e; + } + e = installDockerWorker(credentials, container); if (e) return e; qDebug().noquote() << "ServerController::setupContainer installDockerWorker finished"; @@ -449,7 +457,7 @@ ErrorCode ServerController::updateContainer(const ServerCredentials &credentials qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequred; if (reinstallRequred) { - return setupContainer(credentials, container, newConfig); + return setupContainer(credentials, container, newConfig, true); } else { ErrorCode e = configureContainerWorker(credentials, container, newConfig); @@ -528,14 +536,44 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent stdOut += data + "\n"; }; - ErrorCode e = runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::install_docker), - genVarsForScript(credentials)), - cbReadStdOut, cbReadStdErr); + QFutureWatcher watcher; + + QFuture future = QtConcurrent::run([this, &stdOut, &cbReadStdOut, &cbReadStdErr, &credentials]() { + do { + if (m_cancelInstallation) { + return ErrorCode::ServerCancelInstallation; + } + stdOut.clear(); + runScript(credentials, + replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), + genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); + if (!stdOut.isEmpty() || stdOut.contains("Unable to acquire the dpkg frontend lock")) { + emit serverIsBusy(true); + QThread::msleep(1000); + } + } while (!stdOut.isEmpty()); + return ErrorCode::NoError; + }); + + QEventLoop wait; + QObject::connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + watcher.setFuture(future); + wait.exec(); + + m_cancelInstallation = false; + emit serverIsBusy(false); + + if (future.result() != ErrorCode::NoError) { + return future.result(); + } + + ErrorCode error = runScript(credentials, + replaceVars(amnezia::scriptData(SharedScriptType::install_docker), + genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); if (stdOut.contains("command not found")) return ErrorCode::ServerDockerFailedError; - return e; + return error; } ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) @@ -742,23 +780,23 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential return stdOut; } -SshConnection *ServerController::connectToHost(const SshConnectionParameters &sshParams) +QSharedPointer ServerController::connectToHost(const SshConnectionParameters &sshParams) { - SshConnection *client = acquireConnection(sshParams); - if (!client) return nullptr; + QSharedPointer client(new SshConnection(sshParams)); + if (!client.get()) return nullptr; QEventLoop waitssh; - QObject::connect(client, &SshConnection::connected, &waitssh, [&]() { + QObject::connect(client.get(), &SshConnection::connected, &waitssh, [&]() { qDebug() << "Server connected by ssh"; waitssh.quit(); }); - QObject::connect(client, &SshConnection::disconnected, &waitssh, [&]() { + QObject::connect(client.get(), &SshConnection::disconnected, &waitssh, [&]() { qDebug() << "Server disconnected by ssh"; waitssh.quit(); }); - QObject::connect(client, &SshConnection::error, &waitssh, [&](QSsh::SshError error) { + QObject::connect(client.get(), &SshConnection::error, &waitssh, [&](QSsh::SshError error) { qCritical() << "Ssh error:" << error << client->errorString(); waitssh.quit(); }); @@ -796,10 +834,9 @@ SshConnection *ServerController::connectToHost(const SshConnectionParameters &ss return client; } -void ServerController::disconnectFromHost(const ServerCredentials &credentials) +void ServerController::setCancelInstallation(const bool cancel) { - SshConnection *client = acquireConnection(sshParams(credentials)); - if (client) client->disconnectFromHost(); + m_cancelInstallation = cancel; } ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials) @@ -819,3 +856,89 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars) //qDebug().noquote() << script; return s; } + +ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +{ + if (container == DockerContainer::Dns) { + return ErrorCode::NoError; + } + + QString stdOut; + auto cbReadStdOut = [&](const QString &data, QSharedPointer proc) { + stdOut += data + "\n"; + }; + auto cbReadStdErr = [&](const QString &data, QSharedPointer ) { + stdOut += data + "\n"; + }; + + const Proto protocol = ContainerProps::defaultProtocol(container); + const QString containerString = ProtocolProps::protoToString(protocol); + const QJsonObject containerConfig = config.value(containerString).toObject(); + + QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container); + + QString defaultPort("%1"); + QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); + QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); + QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto); + + QString script = QString("sudo lsof -i -P -n | grep -E ':%1 ").arg(port); + for (auto &port : fixedPorts) { + script = script.append("|:%1").arg(port); + } + script = script.append("' | grep -i %1").arg(transportProto); + ErrorCode errorCode = runScript(credentials, + replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + if (!stdOut.isEmpty()) { + return ErrorCode::ServerPortAlreadyAllocatedError; + } + return ErrorCode::NoError; +} + +ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers) +{ + QString stdOut; + auto cbReadStdOut = [&](const QString &data, QSharedPointer proc) { + stdOut += data + "\n"; + }; + auto cbReadStdErr = [&](const QString &data, QSharedPointer ) { + stdOut += data + "\n"; + }; + + QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'"); + + ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + auto containersInfo = stdOut.split("\n"); + for (auto &containerInfo : containersInfo) { + if (containerInfo.isEmpty()) { + continue; + } + const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*"); + QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo); + if (containerAndPortMatch.hasMatch()) { + QString name = containerAndPortMatch.captured(1); + QString port = containerAndPortMatch.captured(2); + QString transportProto = containerAndPortMatch.captured(3); + DockerContainer container = ContainerProps::containerFromString(name); + Proto mainProto = ContainerProps::defaultProtocol(container); + QJsonObject config { + { config_key::container, name }, + { ProtocolProps::protoToString(mainProto), QJsonObject { + { config_key::port, port }, + { config_key::transport_proto, transportProto }} + } + }; + installedContainers.insert(container, config); + } + } + + return ErrorCode::NoError; +} diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index 091eaa52..68d47417 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -5,7 +5,7 @@ #include #include "sshconnection.h" #include "sshremoteprocess.h" -#include "debug.h" +#include "logger.h" #include "defs.h" #include "containers/containers_defs.h" @@ -31,13 +31,13 @@ public: ErrorCode fromSshProcessExitStatus(int exitStatus); QSsh::SshConnectionParameters sshParams(const ServerCredentials &credentials); - void disconnectFromHost(const ServerCredentials &credentials); ErrorCode removeAllContainers(const ServerCredentials &credentials); ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); - ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); + ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config, bool isUpdate = false); ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &oldConfig, QJsonObject &newConfig); + const QJsonObject &oldConfig, QJsonObject &newConfig); // create initial config - generate passwords, etc QJsonObject createContainerInitialConfig(DockerContainer container, int port, TransportProto tp); @@ -71,8 +71,10 @@ public: Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject()); QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); - QSsh::SshConnection *connectToHost(const QSsh::SshConnectionParameters &sshParams); + QSharedPointer connectToHost(const QSsh::SshConnectionParameters &sshParams); + void setCancelInstallation(const bool cancel); + ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); private: ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); @@ -81,9 +83,14 @@ private: ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); std::shared_ptr m_settings; std::shared_ptr m_configurator; + + bool m_cancelInstallation = false; +signals: + void serverIsBusy(const bool isBusy); }; #endif // SERVERCONTROLLER_H diff --git a/client/debug.cpp b/client/debug.cpp deleted file mode 100644 index dfdb15dd..00000000 --- a/client/debug.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include "debug.h" -#include "defines.h" -#include "utils.h" - -#ifdef AMNEZIA_DESKTOP -#include -#endif - -QFile Debug::m_file; -QTextStream Debug::m_textStream; -QString Debug::m_logFileName = QString("%1.log").arg(APPLICATION_NAME); - -void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) -{ - if (msg.simplified().isEmpty()) { - return; - } - - // Skip annoying messages from Qt - if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font")) { - return; - } - - Debug::m_textStream << qFormatLogMessage(type, context, msg) << endl << flush; - Debug::appendAllLog(qFormatLogMessage(type, context, msg)); - - std::cout << qFormatLogMessage(type, context, msg).toStdString() << std::endl << std::flush; -} - -Debug &Debug::Instance() -{ - static Debug s; - return s; -} - -void Debug::appendSshLog(const QString &log) -{ - QString dt = QDateTime::currentDateTime().toString(); - Instance().m_sshLog.append(dt + ": " + log + "\n"); - emit Instance().sshLogChanged(Instance().sshLog()); -} - -void Debug::appendAllLog(const QString &log) -{ - Instance().m_allLog.append(log + "\n"); - emit Instance().allLogChanged(Instance().allLog()); -} - -bool Debug::init() -{ - qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} %{type} %{message}"); - - QString path = userLogsDir(); - QDir appDir(path); - if (!appDir.mkpath(path)) { - return false; - } - - m_file.setFileName(appDir.filePath(m_logFileName)); - if (!m_file.open(QIODevice::Append)) { - qWarning() << "Cannot open log file:" << m_logFileName; - return false; - } - m_file.setTextModeEnabled(true); - m_textStream.setDevice(&m_file); - -#ifndef QT_DEBUG - qInstallMessageHandler(debugMessageHandler); -#endif - - return true; -} - -QString Debug::userLogsDir() -{ - return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/log"; -} - -QString Debug::userLogsFilePath() -{ - return userLogsDir() + QDir::separator() + m_logFileName; -} - -QString Debug::getLogFile() -{ - m_file.flush(); - QFile file(userLogsFilePath()); - - file.open(QIODevice::ReadOnly); - return file.readAll(); -} - -bool Debug::openLogsFolder() -{ - QString path = userLogsDir(); -#ifdef Q_OS_WIN - path = "file:///" + path; -#endif - if (!QDesktopServices::openUrl(QUrl::fromLocalFile(path))) { - qWarning() << "Can't open url:" << path; - return false; - } - return true; -} - -bool Debug::openServiceLogsFolder() -{ - QString path = Utils::systemLogPath(); - path = "file:///" + path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); - return true; -} - -QString Debug::appLogFileNamePath() -{ - return m_file.fileName(); -} - -void Debug::clearLogs() -{ - bool isLogActive = m_file.isOpen(); - m_file.close(); - - QFile file(userLogsFilePath()); - - file.open(QIODevice::WriteOnly | QIODevice::Truncate); - file.resize(0); - file.close(); - - if (isLogActive) { - init(); - } -} - -void Debug::clearServiceLogs() -{ -#ifdef AMNEZIA_DESKTOP - IpcClient *m_IpcClient = new IpcClient; - - if (!m_IpcClient->isSocketConnected()) { - if (!IpcClient::init(m_IpcClient)) { - qWarning() << "Error occured when init IPC client"; - return; - } - } - - if (m_IpcClient->Interface()) { - m_IpcClient->Interface()->setLogsEnabled(false); - m_IpcClient->Interface()->cleanUp(); - } - else { - qWarning() << "Error occured cleaning up service logs"; - } -#endif -} - -void Debug::cleanUp() -{ - clearLogs(); - QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); - dir.removeRecursively(); - - clearServiceLogs(); -} diff --git a/client/debug.h b/client/debug.h deleted file mode 100644 index e58a42ca..00000000 --- a/client/debug.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef DEBUG_H -#define DEBUG_H - -#include -#include -#include -#include -#include - -#include "ui/property_helper.h" - -class Debug : public QObject -{ - Q_OBJECT - AUTO_PROPERTY(QString, sshLog) - AUTO_PROPERTY(QString, allLog) - -public: - static Debug& Instance(); - - static void appendSshLog(const QString &log); - static void appendAllLog(const QString &log); - - - static bool init(); - static bool openLogsFolder(); - static bool openServiceLogsFolder(); - static QString appLogFileNamePath(); - static void clearLogs(); - static void clearServiceLogs(); - static void cleanUp(); - - static QString userLogsFilePath(); - static QString getLogFile(); - -private: - Debug() {} - Debug(Debug const &) = delete; - Debug& operator= (Debug const&) = delete; - - static QString userLogsDir(); - - static QFile m_file; - static QTextStream m_textStream; - static QString m_logFileName; - - friend void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); -}; - -#endif // DEBUG_H diff --git a/client/ios/app/Info.plist.in b/client/ios/app/Info.plist.in new file mode 100644 index 00000000..97f88953 --- /dev/null +++ b/client/ios/app/Info.plist.in @@ -0,0 +1,85 @@ + + + + + CFBundleAllowMixedLocalizations + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${QT_INTERNAL_DOLLAR_VAR}{PRODUCT_NAME} + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + ITSAppUsesNonExemptEncryption + + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + + UILaunchStoryboardName + AmneziaVPNLaunchScreen + UIRequiredDeviceCapabilities + + UIRequiresFullScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIUserInterfaceStyle + Light + com.wireguard.ios.app_group_id + group.org.amnezia.AmneziaVPN + UIViewControllerBasedStatusBarAppearance + + NSCameraUsageDescription + Amnezia VPN needs access to the camera for reading QR-codes. + CFBundleIcons + + CFBundleIcons~ipad + + UTImportedTypeDeclarations + + + UTTypeConformsTo + + public.data + + UTTypeDescription + Amnezia VPN config + UTTypeIconFiles + + UTTypeIdentifier + org.amnezia.AmneziaVPN.amnezia-config + UTTypeTagSpecification + + public.filename-extension + + vpn + + public.mime-type + + text/plain + + + + + + \ No newline at end of file diff --git a/client/ios/app/launch.png b/client/ios/app/launch.png index 33bb9d7d..050e18bf 100644 Binary files a/client/ios/app/launch.png and b/client/ios/app/launch.png differ diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt new file mode 100644 index 00000000..bbe591c8 --- /dev/null +++ b/client/ios/networkextension/CMakeLists.txt @@ -0,0 +1,114 @@ +enable_language(Swift) + +set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) + +add_executable(networkextension) +set_target_properties(networkextension PROPERTIES + OUTPUT_NAME "AmneziaVPNNetworkExtension" + XCODE_PRODUCT_TYPE com.apple.product-type.app-extension + BUNDLE_EXTENSION appex + MACOSX_BUNDLE ON + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in + MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension" + MACOSX_BUNDLE_BUNDLE_VERSION "${BUILD_ID}" + MACOSX_BUNDLE_COPYRIGHT "MPL-2.0" + MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension" + MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension" + MACOSX_BUNDLE_LONG_VERSION_STRING "${CMAKE_PROJECT_VERSION}-${BUILD_ID}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION}" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension" + XCODE_ATTRIBUTE_SWIFT_VERSION "5.0" + XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES" + XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h" + XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO" + XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES" + XCODE_ATTRIBUTE_ENABLE_BITCODE "NO" + XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements +) + +find_library(FW_ASSETS_LIBRARY AssetsLibrary) +find_library(FW_MOBILE_CORE MobileCoreServices) +find_library(FW_UI_KIT UIKit) + +target_link_libraries(networkextension PRIVATE ${FW_ASSETS_LIBRARY}) +target_link_libraries(networkextension PRIVATE ${FW_MOBILE_CORE}) +target_link_libraries(networkextension PRIVATE ${FW_UI_KIT}) + +target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\") +target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) + +set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources) + +target_sources(networkextension PRIVATE + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift + ${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift + ${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift + ${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift + ${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift + ${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift + ${CLIENT_ROOT_DIR}/platforms/ios/iostunnel.swift + ${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm + ${CLIENT_ROOT_DIR}/platforms/ios/ioslogger.swift +) + +## Build wireguard-go-version.h +execute_process( + COMMAND go list -m golang.zx2c4.com/wireguard + WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo + OUTPUT_VARIABLE WG_VERSION_FULL +) +string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in + ${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h) +target_sources(networkextension PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h) + +target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR}) +target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + +include(${CLIENT_ROOT_DIR}/cmake/golang.cmake) +## Build the wireguard go library for iOS simulation. +## TODO: Some special handling around GOARCH for +execute_process(OUTPUT_VARIABLE SIM_SDKROOT OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND xcrun --sdk iphonesimulator --show-sdk-path) +build_go_archive(${CMAKE_CURRENT_BINARY_DIR}/libwg-sim.a ${CMAKE_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo/go.mod + GOOS ios + GOARCH amd64 + CGO_CFLAGS -arch x86_64 -isysroot ${SIM_SDKROOT} + CGO_LDFLAGS -arch x86_64 -isysroot ${SIM_SDKROOT} +) + +## Build the wireguard go library for iOS devices. +execute_process(OUTPUT_VARIABLE IOS_SDKROOT OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND xcrun --sdk ${CMAKE_OSX_SYSROOT} --show-sdk-path) +build_go_archive(${CMAKE_CURRENT_BINARY_DIR}/libwg-dev.a ${CMAKE_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo/go.mod + GOOS ios + GOARCH arm64 + CGO_CFLAGS -arch arm64 -isysroot ${IOS_SDKROOT} + CGO_LDFLAGS -arch arm64 -isysroot ${IOS_SDKROOT} +) + +## Unify the wireguard go libraries. +add_custom_target(libwg_builder + DEPENDS + ${CMAKE_CURRENT_BINARY_DIR}/libwg-dev.a + ${CMAKE_CURRENT_BINARY_DIR}/libwg-sim.a + BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/libwg-unified.a + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND lipo -create -output libwg-unified.a libwg-dev.a libwg-sim.a +) + +## Link and depend on the wireguard library. +add_dependencies(networkextension libwg_builder) +target_link_libraries(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libwg-unified.a) \ No newline at end of file diff --git a/client/ios/networkextension/Info.plist b/client/ios/networkextension/Info.plist new file mode 100644 index 00000000..6941af53 --- /dev/null +++ b/client/ios/networkextension/Info.plist @@ -0,0 +1,52 @@ + + + + + CFBundleExecutable + $(EXECUTABLE_NAME) + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + + CFBundleInfoDictionaryVersion + 6.0 + + CFBundleName + $(PRODUCT_NAME) + + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + + CFBundleShortVersionString + $(MARKETING_VERSION) + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + ITSAppUsesNonExemptEncryption + + + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + + CFBundleDisplayName + MozillaVPNNetworkExtension + + NSExtension + + NSExtensionPointIdentifier + com.apple.networkextension.packet-tunnel + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).PacketTunnelProvider + + + com.wireguard.ios.app_group_id + group.$(APP_ID_IOS) + + com.wireguard.macos.app_group_id + $(DEVELOPMENT_TEAM).group.$(APP_ID_MACOS) + + \ No newline at end of file diff --git a/client/ios/networkextension/Info.plist.in b/client/ios/networkextension/Info.plist.in new file mode 100644 index 00000000..54d9d8d9 --- /dev/null +++ b/client/ios/networkextension/Info.plist.in @@ -0,0 +1,46 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + + ITSAppUsesNonExemptEncryption + + + LSMinimumSystemVersion + ${CMAKE_OSX_DEPLOYMENT_TARGET} + + CFBundleDisplayName + ${MACOSX_BUNDLE_INFO_STRING} + + NSExtension + + NSExtensionPointIdentifier + com.apple.networkextension.packet-tunnel + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).PacketTunnelProvider + + + com.wireguard.ios.app_group_id + group.${BUILD_IOS_APP_IDENTIFIER} + + com.wireguard.macos.app_group_id + ${BUILD_VPN_DEVELOPMENT_TEAM}.group.${BUILD_OSX_APP_IDENTIFIER} + + \ No newline at end of file diff --git a/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h new file mode 100644 index 00000000..03a987ad --- /dev/null +++ b/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -0,0 +1,21 @@ +#include "wireguard-go-version.h" +#include "3rd/wireguard-apple/Sources/WireGuardKitGo/wireguard.h" +#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" + +#include +#include + +#define WG_KEY_LEN (32) +#define WG_KEY_LEN_BASE64 (45) +#define WG_KEY_LEN_HEX (65) + +void key_to_base64(char base64[WG_KEY_LEN_BASE64], + const uint8_t key[WG_KEY_LEN]); +bool key_from_base64(uint8_t key[WG_KEY_LEN], const char* base64); + +void key_to_hex(char hex[WG_KEY_LEN_HEX], const uint8_t key[WG_KEY_LEN]); +bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex); + +bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]); + +void write_msg_to_log(const char* tag, const char* msg); diff --git a/client/ios/networkextension/wireguard-go-version.h.in b/client/ios/networkextension/wireguard-go-version.h.in new file mode 100644 index 00000000..860bc3c3 --- /dev/null +++ b/client/ios/networkextension/wireguard-go-version.h.in @@ -0,0 +1,3 @@ +#ifndef WIREGUARD_GO_VERSION +#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@" +#endif // WIREGUARD_GO_VERSION \ No newline at end of file diff --git a/client/logger.cpp b/client/logger.cpp index 06a7c6c8..d734e2d8 100644 --- a/client/logger.cpp +++ b/client/logger.cpp @@ -1,59 +1,182 @@ -/* 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 "logger.h" -#include "loghandler.h" -Logger::Logger(const QString& module, const QString& className) - : Logger(QStringList({module}), className) {} +#include +#include +#include +#include +#include +#include -Logger::Logger(const QStringList& modules, const QString& className) - : m_modules(modules), m_className(className) {} +#include -Logger::Log Logger::error() { return Log(this, LogLevel::Error); } -Logger::Log Logger::warning() { return Log(this, LogLevel::Warning); } -Logger::Log Logger::info() { return Log(this, LogLevel::Info); } -Logger::Log Logger::debug() { return Log(this, LogLevel::Debug); } +#include "defines.h" +#include "utilities.h" -Logger::Log::Log(Logger* logger, LogLevel logLevel) - : m_logger(logger), m_logLevel(logLevel), m_data(new Data()) {} +#ifdef AMNEZIA_DESKTOP +#include +#endif -Logger::Log::~Log() { - LogHandler::messageHandler(m_logLevel, m_logger->modules(), - m_logger->className(), m_data->m_buffer.trimmed()); - delete m_data; +QFile Logger::m_file; +QTextStream Logger::m_textStream; +QString Logger::m_logFileName = QString("%1.log").arg(APPLICATION_NAME); + +void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) +{ + if (msg.simplified().isEmpty()) { + return; + } + + // Skip annoying messages from Qt + if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font")) { + return; + } + + Logger::m_textStream << qFormatLogMessage(type, context, msg) << Qt::endl << Qt::flush; + Logger::appendAllLog(qFormatLogMessage(type, context, msg)); + + std::cout << qFormatLogMessage(type, context, msg).toStdString() << std::endl << std::flush; } -#define CREATE_LOG_OP_REF(x) \ - Logger::Log& Logger::Log::operator<<(x t) { \ - m_data->m_ts << t << ' '; \ - return *this; \ - } - -CREATE_LOG_OP_REF(uint64_t); -CREATE_LOG_OP_REF(const char*); -CREATE_LOG_OP_REF(const QString&); -CREATE_LOG_OP_REF(const QByteArray&); -CREATE_LOG_OP_REF(void*); - -#undef CREATE_LOG_OP_REF - -Logger::Log& Logger::Log::operator<<(const QStringList& t) { - m_data->m_ts << '[' << t.join(",") << ']' << ' '; - return *this; +Logger &Logger::Instance() +{ + static Logger s; + return s; } -Logger::Log& Logger::Log::operator<<(QTextStreamFunction t) { - m_data->m_ts << t; - return *this; +void Logger::appendSshLog(const QString &log) +{ + QString dt = QDateTime::currentDateTime().toString(); + Instance().m_sshLog.append(dt + ": " + log + "\n"); + emit Instance().sshLogChanged(Instance().sshLog()); } -// static -QString Logger::sensitive(const QString& input) { -#ifdef QT_DEBUG - return input; -#else - return QString(input.length(), 'X'); +void Logger::appendAllLog(const QString &log) +{ + Instance().m_allLog.append(log + "\n"); + emit Instance().allLogChanged(Instance().allLog()); +} + +bool Logger::init() +{ + qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} %{type} %{message}"); + + QString path = userLogsDir(); + QDir appDir(path); + if (!appDir.mkpath(path)) { + return false; + } + + m_file.setFileName(appDir.filePath(m_logFileName)); + if (!m_file.open(QIODevice::Append)) { + qWarning() << "Cannot open log file:" << m_logFileName; + return false; + } + m_file.setTextModeEnabled(true); + m_textStream.setDevice(&m_file); + +#ifndef QT_DEBUG + qInstallMessageHandler(debugMessageHandler); +#endif + + return true; +} + +void Logger::deInit() +{ + qInstallMessageHandler(nullptr); + qSetMessagePattern("%{message}"); + m_textStream.setDevice(nullptr); + m_file.close(); +} + +QString Logger::userLogsDir() +{ + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/log"; +} + +QString Logger::userLogsFilePath() +{ + return userLogsDir() + QDir::separator() + m_logFileName; +} + +QString Logger::getLogFile() +{ + m_file.flush(); + QFile file(userLogsFilePath()); + + file.open(QIODevice::ReadOnly); + return file.readAll(); +} + +bool Logger::openLogsFolder() +{ + QString path = userLogsDir(); +#ifdef Q_OS_WIN + path = "file:///" + path; +#endif + if (!QDesktopServices::openUrl(QUrl::fromLocalFile(path))) { + qWarning() << "Can't open url:" << path; + return false; + } + return true; +} + +bool Logger::openServiceLogsFolder() +{ + QString path = Utils::systemLogPath(); + path = "file:///" + path; + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + return true; +} + +QString Logger::appLogFileNamePath() +{ + return m_file.fileName(); +} + +void Logger::clearLogs() +{ + bool isLogActive = m_file.isOpen(); + m_file.close(); + + QFile file(userLogsFilePath()); + + file.open(QIODevice::WriteOnly | QIODevice::Truncate); + file.resize(0); + file.close(); + + if (isLogActive) { + init(); + } +} + +void Logger::clearServiceLogs() +{ +#ifdef AMNEZIA_DESKTOP + IpcClient *m_IpcClient = new IpcClient; + + if (!m_IpcClient->isSocketConnected()) { + if (!IpcClient::init(m_IpcClient)) { + qWarning() << "Error occured when init IPC client"; + return; + } + } + + if (m_IpcClient->Interface()) { + m_IpcClient->Interface()->setLogsEnabled(false); + m_IpcClient->Interface()->cleanUp(); + } + else { + qWarning() << "Error occured cleaning up service logs"; + } #endif } + +void Logger::cleanUp() +{ + clearLogs(); + QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + dir.removeRecursively(); + + clearServiceLogs(); +} diff --git a/client/logger.h b/client/logger.h index 87bae4ce..bea5213d 100644 --- a/client/logger.h +++ b/client/logger.h @@ -1,88 +1,51 @@ -/* 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 LOGGER_H #define LOGGER_H -#include "loglevel.h" - +#include +#include +#include #include #include -constexpr const char* LOG_CAPTIVEPORTAL = "captiveportal"; -constexpr const char* LOG_CONTROLLER = "controller"; -constexpr const char* LOG_IAP = "iap"; -constexpr const char* LOG_INSPECTOR = "inspector"; -constexpr const char* LOG_MAIN = "main"; -constexpr const char* LOG_MODEL = "model"; -constexpr const char* LOG_NETWORKING = "networking"; -constexpr const char* LOG_SERVER = "server"; +#include "ui/property_helper.h" -#if defined(MVPN_LINUX) || defined(MVPN_ANDROID) -constexpr const char* LOG_LINUX = "linux"; -#endif +class Logger : public QObject +{ + Q_OBJECT + AUTO_PROPERTY(QString, sshLog) + AUTO_PROPERTY(QString, allLog) -#ifdef MVPN_WINDOWS -constexpr const char* LOG_WINDOWS = "windows"; -#endif +public: + static Logger& Instance(); -#if __APPLE__ || defined(MVPN_WASM) -constexpr const char* LOG_MACOS = "macos"; -constexpr const char* LOG_IOS = "ios"; -#endif + static void appendSshLog(const QString &log); + static void appendAllLog(const QString &log); -#if defined(MVPN_ANDROID) || defined(UNIT_TEST) -constexpr const char* LOG_ANDROID = "android"; -#endif -class Logger { - public: - Logger(const QString& module, const QString& className); - Logger(const QStringList& modules, const QString& className); + static bool init(); + static void deInit(); + static bool openLogsFolder(); + static bool openServiceLogsFolder(); + static QString appLogFileNamePath(); + static void clearLogs(); + static void clearServiceLogs(); + static void cleanUp(); - const QStringList& modules() const { return m_modules; } - const QString& className() const { return m_className; } + static QString userLogsFilePath(); + static QString getLogFile(); - class Log { - public: - Log(Logger* logger, LogLevel level); - ~Log(); +private: + Logger() {} + Logger(Logger const &) = delete; + Logger& operator= (Logger const&) = delete; - Log& operator<<(uint64_t t); - Log& operator<<(const char* t); - Log& operator<<(const QString& t); - Log& operator<<(const QStringList& t); - Log& operator<<(const QByteArray& t); - Log& operator<<(QTextStreamFunction t); - Log& operator<<(void* t); + static QString userLogsDir(); - private: - Logger* m_logger; - LogLevel m_logLevel; + static QFile m_file; + static QTextStream m_textStream; + static QString m_logFileName; - struct Data { - Data() : m_ts(&m_buffer, QIODevice::WriteOnly) {} - - QString m_buffer; - QTextStream m_ts; - }; - - Data* m_data; - }; - - Log error(); - Log warning(); - Log info(); - Log debug(); - - // Use this to log sensitive data such as IP address, session tokens, and so - // on. - QString sensitive(const QString& input); - - private: - QStringList m_modules; - QString m_className; + friend void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); }; -#endif // LOGGER_H +#endif // LOGGER_H diff --git a/client/loghandler.cpp b/client/loghandler.cpp deleted file mode 100644 index cce3c234..00000000 --- a/client/loghandler.cpp +++ /dev/null @@ -1,346 +0,0 @@ -/* 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 "loghandler.h" -#include "constants.h" -#include "logger.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef MVPN_ANDROID -# include -#endif - -constexpr qint64 LOG_MAX_FILE_SIZE = 204800; -constexpr const char* LOG_FILENAME = "mozillavpn.txt"; - -namespace { -QMutex s_mutex; -QString s_location = - QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); -LogHandler* s_instance = nullptr; - -LogLevel qtTypeToLogLevel(QtMsgType type) { - switch (type) { - case QtDebugMsg: - return Debug; - case QtInfoMsg: - return Info; - case QtWarningMsg: - return Warning; - case QtCriticalMsg: - [[fallthrough]]; - case QtFatalMsg: - return Error; - default: - return Debug; - } -} - -} // namespace - -// static -LogHandler* LogHandler::instance() { - QMutexLocker lock(&s_mutex); - return maybeCreate(lock); -} - -// static -void LogHandler::messageQTHandler(QtMsgType type, - const QMessageLogContext& context, - const QString& message) { - QMutexLocker lock(&s_mutex); - maybeCreate(lock)->addLog(Log(qtTypeToLogLevel(type), context.file, - context.function, context.line, message), - lock); -} - -// static -void LogHandler::messageHandler(LogLevel logLevel, const QStringList& modules, - const QString& className, - const QString& message) { - QMutexLocker lock(&s_mutex); - maybeCreate(lock)->addLog(Log(logLevel, modules, className, message), lock); -} - -// static -LogHandler* LogHandler::maybeCreate(const QMutexLocker& proofOfLock) { - if (!s_instance) { - LogLevel minLogLevel = Debug; // TODO: in prod, we should log >= warning - QStringList modules; - QProcessEnvironment pe = QProcessEnvironment::systemEnvironment(); - if (pe.contains("MOZVPN_LEVEL")) { - QString level = pe.value("MOZVPN_LEVEL"); - if (level == "info") - minLogLevel = Info; - else if (level == "warning") - minLogLevel = Warning; - else if (level == "error") - minLogLevel = Error; - } - - if (pe.contains("MOZVPN_LOG")) { - QStringList parts = pe.value("MOZVPN_LOG").split(","); - for (const QString& part : parts) { - modules.append(part.trimmed()); - } - } - - s_instance = new LogHandler(minLogLevel, modules, proofOfLock); - } - - return s_instance; -} - -// static -void LogHandler::prettyOutput(QTextStream& out, const LogHandler::Log& log) { - out << "[" << log.m_dateTime.toString("dd.MM.yyyy hh:mm:ss.zzz") << "] "; - - switch (log.m_logLevel) { - case Debug: - out << "Debug: "; - break; - case Info: - out << "Info: "; - break; - case Warning: - out << "Warning: "; - break; - case Error: - out << "Error: "; - break; - default: - out << "?!?: "; - break; - } - - if (log.m_fromQT) { - out << log.m_message; - - if (!log.m_file.isEmpty() || !log.m_function.isEmpty()) { - out << " ("; - - if (!log.m_file.isEmpty()) { - int pos = log.m_file.lastIndexOf("/"); - out << log.m_file.right(log.m_file.length() - pos - 1); - - if (log.m_line >= 0) { - out << ":" << log.m_line; - } - - if (!log.m_function.isEmpty()) { - out << ", "; - } - } - - if (!log.m_function.isEmpty()) { - out << log.m_function; - } - - out << ")"; - } - } else { - out << "(" << log.m_modules.join("|") << " - " << log.m_className << ") " - << log.m_message; - } - - out << Qt::endl; -} - -// static -void LogHandler::enableDebug() { - QMutexLocker lock(&s_mutex); - maybeCreate(lock)->m_showDebug = true; -} - -LogHandler::LogHandler(LogLevel minLogLevel, const QStringList& modules, - const QMutexLocker& proofOfLock) - : m_minLogLevel(minLogLevel), m_modules(modules) { - Q_UNUSED(proofOfLock); - -#if defined(QT_DEBUG) || defined(MVPN_WASM) - m_showDebug = true; -#endif - - if (!s_location.isEmpty()) { - openLogFile(proofOfLock); - } -} - -void LogHandler::addLog(const Log& log, const QMutexLocker& proofOfLock) { - if (!matchLogLevel(log, proofOfLock)) { - return; - } - - if (!matchModule(log, proofOfLock)) { - return; - } - - if (m_output) { - prettyOutput(*m_output, log); - } - - if ((log.m_logLevel != LogLevel::Debug) || m_showDebug) { - QTextStream out(stderr); - prettyOutput(out, log); - } - - QByteArray buffer; - { - QTextStream out(&buffer); - prettyOutput(out, log); - } - - emit logEntryAdded(buffer); - -#if defined(MVPN_ANDROID) && defined(QT_DEBUG) - const char* str = buffer.constData(); - if (str) { - __android_log_write(ANDROID_LOG_DEBUG, "mozillavpn", str); - } -#endif -} - -bool LogHandler::matchModule(const Log& log, - const QMutexLocker& proofOfLock) const { - Q_UNUSED(proofOfLock); - - // Let's include QT logs always. - if (log.m_fromQT) { - return true; - } - - // If no modules has been specified, let's include all. - if (m_modules.isEmpty()) { - return true; - } - - for (const QString& module : log.m_modules) { - if (m_modules.contains(module)) { - return true; - } - } - - return false; -} - -bool LogHandler::matchLogLevel(const Log& log, - const QMutexLocker& proofOfLock) const { - Q_UNUSED(proofOfLock); - return log.m_logLevel >= m_minLogLevel; -} - -// static -void LogHandler::writeLogs(QTextStream& out) { - QMutexLocker lock(&s_mutex); - - if (!s_instance || !s_instance->m_logFile) { - return; - } - - QString logFileName = s_instance->m_logFile->fileName(); - s_instance->closeLogFile(lock); - - { - QFile file(logFileName); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - return; - } - - out << file.readAll(); - } - - s_instance->openLogFile(lock); -} - -// static -void LogHandler::cleanupLogs() { - QMutexLocker lock(&s_mutex); - cleanupLogFile(lock); -} - -// static -void LogHandler::cleanupLogFile(const QMutexLocker& proofOfLock) { - if (!s_instance || !s_instance->m_logFile) { - return; - } - - QString logFileName = s_instance->m_logFile->fileName(); - s_instance->closeLogFile(proofOfLock); - - { - QFile file(logFileName); - file.remove(); - } - - s_instance->openLogFile(proofOfLock); -} - -// static -void LogHandler::setLocation(const QString& path) { - QMutexLocker lock(&s_mutex); - s_location = path; - - if (s_instance && s_instance->m_logFile) { - cleanupLogFile(lock); - } -} - -void LogHandler::openLogFile(const QMutexLocker& proofOfLock) { - Q_UNUSED(proofOfLock); - Q_ASSERT(!m_logFile); - Q_ASSERT(!m_output); - - QDir appDataLocation(s_location); - if (!appDataLocation.exists()) { - QDir tmp(s_location); - tmp.cdUp(); - if (!tmp.exists()) { - return; - } - if (!tmp.mkdir(appDataLocation.dirName())) { - return; - } - } - - QString logFileName = appDataLocation.filePath(LOG_FILENAME); - m_logFile = new QFile(logFileName); - if (m_logFile->size() > LOG_MAX_FILE_SIZE) { - m_logFile->remove(); - } - - if (!m_logFile->open(QIODevice::WriteOnly | QIODevice::Append | - QIODevice::Text)) { - delete m_logFile; - m_logFile = nullptr; - return; - } - - m_output = new QTextStream(m_logFile); - - addLog(Log(Debug, QStringList{LOG_MAIN}, "LogHandler", - QString("Log file: %1").arg(logFileName)), - proofOfLock); -} - -void LogHandler::closeLogFile(const QMutexLocker& proofOfLock) { - Q_UNUSED(proofOfLock); - - if (m_logFile) { - delete m_output; - m_output = nullptr; - - delete m_logFile; - m_logFile = nullptr; - } -} diff --git a/client/loghandler.h b/client/loghandler.h deleted file mode 100644 index 653027c8..00000000 --- a/client/loghandler.h +++ /dev/null @@ -1,102 +0,0 @@ -/* 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 LOGHANDLER_H -#define LOGHANDLER_H - -#include "loglevel.h" - -#include -#include -#include - -class QFile; -class QMutexLocker; -class QTextStream; - -class LogHandler final : public QObject { - Q_OBJECT - - public: - struct Log { - Log() = default; - - Log(LogLevel logLevel, const QStringList& modules, const QString& className, - const QString& message) - : m_logLevel(logLevel), - m_dateTime(QDateTime::currentDateTime()), - m_modules(modules), - m_className(className), - m_message(message), - m_fromQT(false) {} - - Log(LogLevel logLevel, const QString& file, const QString& function, - uint32_t line, const QString& message) - : m_logLevel(logLevel), - m_dateTime(QDateTime::currentDateTime()), - m_file(file), - m_function(function), - m_message(message), - m_line(line), - m_fromQT(true) {} - - LogLevel m_logLevel = LogLevel::Debug; - QDateTime m_dateTime; - QString m_file; - QString m_function; - QStringList m_modules; - QString m_className; - QString m_message; - int32_t m_line = -1; - bool m_fromQT = false; - }; - - static LogHandler* instance(); - - static void messageQTHandler(QtMsgType type, - const QMessageLogContext& context, - const QString& message); - - static void messageHandler(LogLevel logLevel, const QStringList& modules, - const QString& className, const QString& message); - - static void prettyOutput(QTextStream& out, const LogHandler::Log& log); - - static void writeLogs(QTextStream& out); - - static void cleanupLogs(); - - static void setLocation(const QString& path); - - static void enableDebug(); - - signals: - void logEntryAdded(const QByteArray& log); - - private: - LogHandler(LogLevel m_minLogLevel, const QStringList& modules, - const QMutexLocker& proofOfLock); - - static LogHandler* maybeCreate(const QMutexLocker& proofOfLock); - - void addLog(const Log& log, const QMutexLocker& proofOfLock); - - bool matchLogLevel(const Log& log, const QMutexLocker& proofOfLock) const; - bool matchModule(const Log& log, const QMutexLocker& proofOfLock) const; - - void openLogFile(const QMutexLocker& proofOfLock); - - void closeLogFile(const QMutexLocker& proofOfLock); - - static void cleanupLogFile(const QMutexLocker& proofOfLock); - - const LogLevel m_minLogLevel; - const QStringList m_modules; - bool m_showDebug = false; - - QFile* m_logFile = nullptr; - QTextStream* m_output = nullptr; -}; - -#endif // LOGHANDLER_H diff --git a/client/loglevel.h b/client/loglevel.h deleted file mode 100644 index 0bdec85b..00000000 --- a/client/loglevel.h +++ /dev/null @@ -1,15 +0,0 @@ -/* 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 LOGLEVEL_H -#define LOGLEVEL_H - -enum LogLevel { - Debug, - Info, - Warning, - Error, -}; - -#endif // LOGLEVEL_H diff --git a/client/main.cpp b/client/main.cpp index 62685953..f20c5dd1 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -4,6 +4,7 @@ #include "amnezia_application.h" #include "defines.h" +#include "migrations.h" #ifdef Q_OS_WIN #include "Windows.h" @@ -16,6 +17,9 @@ int main(int argc, char *argv[]) { + Migrations migrationsManager; + migrationsManager.doMigrations(); + QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")); QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); diff --git a/client/migrations.cpp b/client/migrations.cpp new file mode 100644 index 00000000..6fac6be7 --- /dev/null +++ b/client/migrations.cpp @@ -0,0 +1,86 @@ +#include "migrations.h" + +#include +#include +#include +#include + +#include "defines.h" + +Migrations::Migrations(QObject *parent) + : QObject{parent} +{ + QString version(APP_MAJOR_VERSION); + + QStringList versionDigits = version.split("."); + + if (versionDigits.size() >= 3) { + currentMajor = versionDigits[0].toInt(); + currentMinor = versionDigits[1].toInt(); + currentMicro = versionDigits[2].toInt(); + } + + if (versionDigits.size() == 4) { + currentPatch = versionDigits[3].toInt(); + } +} + +void Migrations::doMigrations() +{ + if (currentMajor == 3) { + migrateV3(); + } +} + +void Migrations::migrateV3() +{ +#ifdef Q_OS_ANDROID + qDebug() << "Migration to V3 on Android..."; + + QString packageName = "org.amnezia.vpn"; + + QDir dir("."); + QString currentDir = dir.absolutePath(); + + int packageNameIndex = currentDir.indexOf(packageName); + + if (packageNameIndex == -1) { + return; + } + + QString rootLocation = currentDir.left(packageNameIndex + packageName.size()); + + if (rootLocation.isEmpty()) { + return; + } + + QString location = rootLocation + "/files/.config/AmneziaVPN.ORG/AmneziaVPN.conf"; + + QFile oldConfig(location); + + if (oldConfig.exists()) { + QString newConfigPath = rootLocation + "/files/settings"; + + QDir newConfigDir(newConfigPath); + + newConfigPath += "/AmneziaVPN.ORG"; + + bool mkPathRes = newConfigDir.mkpath(newConfigPath); + + if (!mkPathRes) { + return; + } + + QFile newConfigFile(newConfigPath + "/AmneziaVPN.conf"); + + if (!newConfigFile.exists()) { + bool cpResult = QFile::copy(oldConfig.fileName(), newConfigFile.fileName()); + if (cpResult) { + oldConfig.remove(); + QDir oldConfigDir(rootLocation + "/files/.config"); + oldConfigDir.rmdir("AmneziaVPN.ORG"); + } + } + } +#endif +} diff --git a/client/migrations.h b/client/migrations.h new file mode 100644 index 00000000..ea6bae92 --- /dev/null +++ b/client/migrations.h @@ -0,0 +1,24 @@ +#ifndef MIGRATIONS_H +#define MIGRATIONS_H + +#include + +class Migrations : public QObject +{ + Q_OBJECT +public: + explicit Migrations(QObject *parent = nullptr); + + void doMigrations(); + +private: + void migrateV3(); + +private: + int currentMajor = 0; + int currentMinor = 0; + int currentMicro = 0; + int currentPatch = 0; +}; + +#endif // MIGRATIONS_H diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index af30fe05..2e5641c9 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -1,9 +1,7 @@ -#include -#include -#include -#include -#include -#include +/* 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 #include @@ -12,51 +10,137 @@ #include #include #include -#include #include "android_controller.h" -#include "core/errorstrings.h" +#include "private/qandroidextras_p.h" #include "ui/pages_logic/StartPageLogic.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_REGISTER_LISTENER = 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; -const int ACTION_SHARE_CONFIG = 10; - -// 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; -const int EVENT_CONFIG_IMPORT = 6; +#include "androidvpnactivity.h" +#include "androidutils.h" namespace { AndroidController* s_instance = nullptr; constexpr auto PERMISSIONHELPER_CLASS = "org/amnezia/vpn/qt/VPNPermissionHelper"; - } // namespace -AndroidController::AndroidController(): - m_binder(this) +AndroidController::AndroidController() : QObject() { + connect(this, &AndroidController::scheduleStatusCheckSignal, this, &AndroidController::scheduleStatusCheckSlot); + s_instance = this; + + auto activity = AndroidVPNActivity::instance(); + + connect(activity, &AndroidVPNActivity::serviceConnected, this, []() { + qDebug() << "Transact: service connected"; + AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, ""); + }, Qt::QueuedConnection); + + connect(activity, &AndroidVPNActivity::eventInitialized, this, + [this](const QString& parcelBody) { + // We might get multiple Init events as widgets, or fragments + // might query this. + if (m_init) { + return; + } + + qDebug() << "Transact: init"; + + m_init = true; + + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + qlonglong time = doc.object()["time"].toVariant().toLongLong(); + + isConnected = doc.object()["connected"].toBool(); + + if (isConnected) { + emit scheduleStatusCheckSignal(); + } + + emit initialized( + true, isConnected, + time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime()); + + setFallbackConnectedNotification(); + }, Qt::QueuedConnection); + + connect(activity, &AndroidVPNActivity::eventConnected, this, + [this](const QString& parcelBody) { + Q_UNUSED(parcelBody); + qDebug() << "Transact: connected"; + + if (!isConnected) { + emit scheduleStatusCheckSignal(); + } + + isConnected = true; + + emit connectionStateChanged(VpnProtocol::Connected); + }, Qt::QueuedConnection); + + connect(activity, &AndroidVPNActivity::eventDisconnected, this, + [this]() { + qDebug() << "Transact: disconnected"; + + isConnected = false; + + emit connectionStateChanged(VpnProtocol::Disconnected); + }, Qt::QueuedConnection); + + connect(activity, &AndroidVPNActivity::eventStatisticUpdate, this, + [this](const QString& parcelBody) { + qDebug() << "Transact: update"; + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + + QString rx = doc.object()["rx_bytes"].toString(); + QString tx = doc.object()["tx_bytes"].toString(); + QString endpoint = doc.object()["endpoint"].toString(); + QString deviceIPv4 = doc.object()["deviceIpv4"].toString(); + + emit statusUpdated(rx, tx, endpoint, deviceIPv4); + }, Qt::QueuedConnection); + + connect(activity, &AndroidVPNActivity::eventBackendLogs, this, + [this](const QString& parcelBody) { + qDebug() << "Transact: backend logs"; + + QString buffer = parcelBody.toUtf8(); + if (m_logCallback) { + m_logCallback(buffer); + } + }, Qt::QueuedConnection); + + connect(activity, &AndroidVPNActivity::eventActivationError, this, + [this](const QString& parcelBody) { + Q_UNUSED(parcelBody) + qDebug() << "Transact: error"; + emit connectionStateChanged(VpnProtocol::Error); + }, Qt::QueuedConnection); + + connect(activity, &AndroidVPNActivity::eventConfigImport, this, + [this](const QString& parcelBody) { + qDebug() << "Transact: config import"; + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + + QString buffer = doc.object()["config"].toString(); + qDebug() << "Transact: config string" << buffer; + importConfig(buffer); + }, Qt::QueuedConnection); + + connect(activity, &AndroidVPNActivity::serviceDisconnected, this, + [this]() { + qDebug() << "Transact: service disconnected"; + m_serviceConnected = false; + }, Qt::QueuedConnection); } AndroidController* AndroidController::instance() { - if (!s_instance) s_instance = new AndroidController(); + if (!s_instance) { + s_instance = new AndroidController(); + } + return s_instance; } @@ -70,71 +154,43 @@ bool AndroidController::initialize(StartPageLogic *startPageLogic) JNINativeMethod methods[]{{"startActivityForResult", "(Landroid/content/Intent;)V", reinterpret_cast(startActivityForResult)}}; - QAndroidJniObject javaClass(PERMISSIONHELPER_CLASS); - QAndroidJniEnvironment env; + QJniObject javaClass(PERMISSIONHELPER_CLASS); + QJniEnvironment env; jclass objectClass = env->GetObjectClass(javaClass.object()); - env->RegisterNatives(objectClass, methods, - sizeof(methods) / sizeof(methods[0])); + env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); env->DeleteLocalRef(objectClass); - auto appContext = QtAndroid::androidActivity().callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); + AndroidVPNActivity::connectService(); - 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; + return true; } ErrorCode AndroidController::start() { - - //qDebug().noquote() << "AndroidController::start" << QJsonDocument(m_rawConfig).toJson(); qDebug() << "Prompting for VPN permission"; - auto appContext = QtAndroid::androidActivity().callObjectMethod( + QJniObject activity = AndroidUtils::getActivity(); + auto appContext = activity.callObjectMethod( "getApplicationContext", "()Landroid/content/Context;"); - QAndroidJniObject::callStaticMethod( + QJniObject::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); - } + QJsonDocument doc(m_vpnConfig); + AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson()); - return activateResult ? NoError : UnknownError; + return NoError; } 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); + AndroidVPNActivity::sendToService(ServiceAction::ACTION_DEACTIVATE, QString()); } // Activates the tunnel that is currently set // in the VPN Service void AndroidController::resumeStart() { - QAndroidParcel nullData; - m_serviceBinder.transact(ACTION_RESUME_ACTIVATE, nullData, nullptr); + AndroidVPNActivity::sendToService(ServiceAction::ACTION_RESUME_ACTIVATE, QString()); } /* @@ -143,24 +199,17 @@ void AndroidController::resumeStart() { 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); + QJsonObject args; + args["title"] = title; + args["message"] = message; + args["sec"] = timerSec; + QJsonDocument doc(args); + + AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_TEXT, doc.toJson()); } void AndroidController::shareConfig(const QString& configContent, const QString& suggestedName) { - QJsonObject rootObject; - rootObject["data"] = configContent; - rootObject["suggestedName"] = suggestedName; - QJsonDocument doc(rootObject); - QAndroidParcel parcel; - parcel.writeData(doc.toJson()); - m_serviceBinder.transact(ACTION_SHARE_CONFIG, parcel, nullptr); + AndroidVPNActivity::saveFileAs(configContent, suggestedName); } /* @@ -169,62 +218,38 @@ void AndroidController::shareConfig(const QString& configContent, const QString& * 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); + 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); + + AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_FALLBACK, doc.toJson()); } void AndroidController::checkStatus() { - qDebug() << "check status"; + qDebug() << "check status"; - QAndroidParcel nullParcel; - m_serviceBinder.transact(ACTION_REQUEST_STATISTIC, nullParcel, nullptr); + AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, QString()); } void AndroidController::getBackendLogs(std::function&& a_callback) { - qDebug() << "get logs"; + qDebug() << "get logs"; - m_logCallback = std::move(a_callback); - QAndroidParcel nullData, replyData; - m_serviceBinder.transact(ACTION_REQUEST_GET_LOG, nullData, &replyData); + m_logCallback = std::move(a_callback); + + AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_GET_LOG, QString()); } void AndroidController::cleanupBackendLogs() { - qDebug() << "cleanup logs"; + qDebug() << "cleanup logs"; - QAndroidParcel nullParcel; - m_serviceBinder.transact(ACTION_REQUEST_CLEANUP_LOG, nullParcel, nullptr); + AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_CLEANUP_LOG, QString()); } void AndroidController::importConfig(const QString& data){ - m_startPageLogic->importConnectionFromCode(data); -} - -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_REGISTER_LISTENER, binderParcel, nullptr); -} - -void AndroidController::onServiceDisconnected(const QString& name) { - qDebug() << "Server disconnected"; - m_serviceConnected = false; - Q_UNUSED(name); - // TODO: Maybe restart? Or crash? + m_startPageLogic->selectConfigFormat(data); } const QJsonObject &AndroidController::vpnConfig() const @@ -237,86 +262,24 @@ 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); - break; - case EVENT_CONFIG_IMPORT: - qDebug() << "Transact: config import"; - doc = QJsonDocument::fromJson(data.readData()); - buffer = doc.object()["config"].toString(); - qDebug() << "Transact: config string" << buffer; - m_controller->importConfig(buffer); - break; - default: - qWarning() << "Transact: Invalid!"; - break; - } - - return true; +void AndroidController::startQrReaderActivity() +{ + AndroidVPNActivity::instance()->startQrCodeReader(); } -QString AndroidController::VPNBinder::readUTF8Parcel(QAndroidParcel data) { - // 106 is the Code for UTF-8 - return QTextCodec::codecForMib(106)->toUnicode(data.readData()); +void AndroidController::copyTextToClipboard(QString text) +{ + AndroidVPNActivity::instance()->copyTextToClipboard(text); +} + +void AndroidController::scheduleStatusCheckSlot() +{ + QTimer::singleShot(1000, [this]() { + if (isConnected) { + checkStatus(); + emit scheduleStatusCheckSignal(); + } + }); } const int ACTIVITY_RESULT_OK = 0xffffffff; @@ -327,34 +290,34 @@ const int ACTIVITY_RESULT_OK = 0xffffffff; */ void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject intent) { - qDebug() << "start activity"; + qDebug() << "start vpnPermissionHelper"; 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; - } + QtAndroidPrivate::startActivity(intent, 1337, + [](int receiverRequestCode, int resultCode, + const QJniObject& 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); - 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); - }); + 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 index c2a65381..00b37225 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -1,17 +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/. */ + #ifndef ANDROID_CONTROLLER_H #define ANDROID_CONTROLLER_H -#include -#include +#include +#include -#include "ui/uilogic.h" #include "ui/pages_logic/StartPageLogic.h" #include "protocols/vpnprotocol.h" + using namespace amnezia; -class AndroidController : public QObject, public QAndroidServiceConnection +class AndroidController : public QObject { Q_OBJECT @@ -35,13 +39,12 @@ public: void cleanupBackendLogs(); void importConfig(const QString& data); - // 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); + void startQrReaderActivity(); + void copyTextToClipboard(QString text); + signals: void connectionStateChanged(VpnProtocol::VpnConnectionState state); @@ -50,16 +53,17 @@ signals: // 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); + void initialized(bool status, bool connected, const QDateTime& connectionDate); + + void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4); + void scheduleStatusCheckSignal(); protected slots: - -protected: - + void scheduleStatusCheckSlot(); private: - //Protocol m_protocol; + bool m_init = false; + QJsonObject m_vpnConfig; StartPageLogic *m_startPageLogic; @@ -67,24 +71,11 @@ private: 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); + + bool isConnected = false; + + void scheduleStatusCheck(); }; #endif // ANDROID_CONTROLLER_H diff --git a/client/platforms/android/androidutils.cpp b/client/platforms/android/androidutils.cpp new file mode 100644 index 00000000..5e9f094c --- /dev/null +++ b/client/platforms/android/androidutils.cpp @@ -0,0 +1,174 @@ +/* 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 "androidutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jni.h" + +namespace { + AndroidUtils* s_instance = nullptr; +} // namespace + +// static +QString AndroidUtils::GetDeviceName() { + QJniEnvironment env; + jclass BUILD = env->FindClass("android/os/Build"); + jfieldID model = env->GetStaticFieldID(BUILD, "MODEL", "Ljava/lang/String;"); + jstring value = (jstring)env->GetStaticObjectField(BUILD, model); + + if (!value) { + return QString("Android Device"); + } + + const char* buffer = env->GetStringUTFChars(value, nullptr); + if (!buffer) { + return QString("Android Device"); + } + + QString res(buffer); + env->ReleaseStringUTFChars(value, buffer); + + return res; +}; + +// static +AndroidUtils* AndroidUtils::instance() { + if (!s_instance) { + Q_ASSERT(qApp); + s_instance = new AndroidUtils(qApp); + } + + return s_instance; +} + +AndroidUtils::AndroidUtils(QObject* parent) : QObject(parent) { + Q_ASSERT(!s_instance); + s_instance = this; +} + +AndroidUtils::~AndroidUtils() { + Q_ASSERT(s_instance == this); + s_instance = nullptr; +} + +// static +void AndroidUtils::dispatchToMainThread(std::function callback) { + QTimer* timer = new QTimer(); + timer->moveToThread(qApp->thread()); + timer->setSingleShot(true); + QObject::connect(timer, &QTimer::timeout, [=]() { + callback(); + timer->deleteLater(); + }); + QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection); +} + +// static +QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv* env, jstring data) { + const char* buffer = env->GetStringUTFChars(data, nullptr); + if (!buffer) { + qDebug() << "getQByteArrayFromJString - failed to parse data."; + return QByteArray(); + } + + QByteArray out(buffer); + env->ReleaseStringUTFChars(data, buffer); + return out; +} + +// static +QString AndroidUtils::getQStringFromJString(JNIEnv* env, jstring data) { + const char* buffer = env->GetStringUTFChars(data, nullptr); + if (!buffer) { + qDebug() << "getQStringFromJString - failed to parse data."; + return QString(); + } + + QString out(buffer); + env->ReleaseStringUTFChars(data, buffer); + return out; +} + +// static +QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv* env, jstring data) { + QByteArray raw(getQByteArrayFromJString(env, data)); + QJsonParseError jsonError; + QJsonDocument json = QJsonDocument::fromJson(raw, &jsonError); + if (QJsonParseError::NoError != jsonError.error) { + qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " + << jsonError.error << "Offset: " << jsonError.offset + << "Message: " << jsonError.errorString() + << "Data: " << raw; + return QJsonObject(); + } + + if (!json.isObject()) { + qDebug() << "getQJsonObjectFromJString - object expected."; + return QJsonObject(); + } + + return json.object(); +} + +QJniObject AndroidUtils::getActivity() { + return QNativeInterface::QAndroidApplication::context(); +} + +int AndroidUtils::GetSDKVersion() { + QJniEnvironment env; + jclass versionClass = env->FindClass("android/os/Build$VERSION"); + jfieldID sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I"); + int sdk = env->GetStaticIntField(versionClass, sdkIntFieldID); + + return sdk; +} + +QString AndroidUtils::GetManufacturer() { + QJniEnvironment env; + jclass buildClass = env->FindClass("android/os/Build"); + jfieldID manuFacturerField = + env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;"); + jstring value = + (jstring)env->GetStaticObjectField(buildClass, manuFacturerField); + + const char* buffer = env->GetStringUTFChars(value, nullptr); + + if (!buffer) { + qDebug() << "Failed to fetch MANUFACTURER"; + return QByteArray(); + } + + QString res(buffer); + qDebug() << "MANUFACTURER: " << res; + env->ReleaseStringUTFChars(value, buffer); + return res; +} + +void AndroidUtils::runOnAndroidThreadSync(const std::function runnable) { + QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable) + .waitForFinished(); +} + +void AndroidUtils::runOnAndroidThreadAsync(const std::function runnable) { + QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable); +} + +// Static +// Creates a copy of the passed QByteArray in the JVM and passes back a ref +jbyteArray AndroidUtils::tojByteArray(const QByteArray& data) { + QJniEnvironment env; + jbyteArray out = env->NewByteArray(data.size()); + env->SetByteArrayRegion(out, 0, data.size(), + reinterpret_cast(data.constData())); + return out; +} diff --git a/client/platforms/android/androidutils.h b/client/platforms/android/androidutils.h new file mode 100644 index 00000000..8559400c --- /dev/null +++ b/client/platforms/android/androidutils.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 ANDROIDUTILS_H +#define ANDROIDUTILS_H + +#include + +#include +#include +#include +#include +#include + +class AndroidUtils final : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(AndroidUtils) + +public: + static QString GetDeviceName(); + + static int GetSDKVersion(); + static QString GetManufacturer(); + + static AndroidUtils* instance(); + + static void dispatchToMainThread(std::function callback); + + static QByteArray getQByteArrayFromJString(JNIEnv* env, jstring data); + + static jbyteArray tojByteArray(const QByteArray& data); + + static QString getQStringFromJString(JNIEnv* env, jstring data); + + static QJsonObject getQJsonObjectFromJString(JNIEnv* env, jstring data); + + static QJniObject getActivity(); + + static void runOnAndroidThreadSync(const std::function runnable); + static void runOnAndroidThreadAsync(const std::function runnable); + +private: + AndroidUtils(QObject* parent); + ~AndroidUtils(); +}; + +#endif // ANDROIDUTILS_H diff --git a/client/platforms/android/androidvpnactivity.cpp b/client/platforms/android/androidvpnactivity.cpp new file mode 100644 index 00000000..9431597b --- /dev/null +++ b/client/platforms/android/androidvpnactivity.cpp @@ -0,0 +1,184 @@ +/* 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 "androidvpnactivity.h" + +#include +#include +#include +#include +#include + +#include "androidutils.h" +#include "jni.h" + +namespace { + AndroidVPNActivity* s_instance = nullptr; + constexpr auto CLASSNAME = "org.amnezia.vpn.qt.VPNActivity"; +} + +AndroidVPNActivity::AndroidVPNActivity() { + AndroidUtils::runOnAndroidThreadAsync([]() { + JNINativeMethod methods[]{ + {"handleBackButton", "()Z", reinterpret_cast(handleBackButton)}, + {"onServiceMessage", "(ILjava/lang/String;)V", reinterpret_cast(onServiceMessage)}, + {"qtOnServiceConnected", "()V", reinterpret_cast(onServiceConnected)}, + {"qtOnServiceDisconnected", "()V", reinterpret_cast(onServiceDisconnected)}, + {"onActivityMessage", "(ILjava/lang/String;)V", reinterpret_cast(onAndroidVpnActivityMessage)} + }; + + QJniObject javaClass(CLASSNAME); + QJniEnvironment env; + jclass objectClass = env->GetObjectClass(javaClass.object()); + env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); + }); +} + +void AndroidVPNActivity::maybeInit() { + if (s_instance == nullptr) { + s_instance = new AndroidVPNActivity(); + } +} + +// static +bool AndroidVPNActivity::handleBackButton(JNIEnv* env, jobject thiz) { + Q_UNUSED(env); + Q_UNUSED(thiz); +} + +void AndroidVPNActivity::connectService() { + QJniObject::callStaticMethod(CLASSNAME, "connectService", "()V"); +} + +void AndroidVPNActivity::startQrCodeReader() +{ + QJniObject::callStaticMethod(CLASSNAME, "startQrCodeReader", "()V"); +} + +void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) { + QJniObject::callStaticMethod( + CLASSNAME, + "saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V", + QJniObject::fromString(fileContent).object(), + QJniObject::fromString(suggestedFilename).object()); +} + +void AndroidVPNActivity::copyTextToClipboard(QString text) +{ + QJniObject::callStaticMethod( + CLASSNAME, + "putTextToClipboard", "(Ljava/lang/String;)V", + QJniObject::fromString(text).object()); +} + +// static +AndroidVPNActivity* AndroidVPNActivity::instance() { + if (s_instance == nullptr) { + AndroidVPNActivity::maybeInit(); + } + + return s_instance; +} + +// static +void AndroidVPNActivity::sendToService(ServiceAction type, const QString& data) { + int messageType = (int)type; + + QJniObject::callStaticMethod( + CLASSNAME, + "sendToService", "(ILjava/lang/String;)V", + static_cast(messageType), + QJniObject::fromString(data).object()); +} + +// static +void AndroidVPNActivity::onServiceMessage(JNIEnv* env, jobject thiz, + jint messageType, jstring body) { + Q_UNUSED(thiz); + const char* buffer = env->GetStringUTFChars(body, nullptr); + if (!buffer) { + return; + } + + QString parcelBody(buffer); + env->ReleaseStringUTFChars(body, buffer); + AndroidUtils::dispatchToMainThread([messageType, parcelBody] { + AndroidVPNActivity::instance()->handleServiceMessage(messageType, + parcelBody); + }); +} + +void AndroidVPNActivity::handleServiceMessage(int code, const QString& data) { + auto mode = (ServiceEvents)code; + + switch (mode) { + case ServiceEvents::EVENT_INIT: + emit eventInitialized(data); + break; + case ServiceEvents::EVENT_CONNECTED: + emit eventConnected(data); + break; + case ServiceEvents::EVENT_DISCONNECTED: + emit eventDisconnected(data); + break; + case ServiceEvents::EVENT_STATISTIC_UPDATE: + emit eventStatisticUpdate(data); + break; + case ServiceEvents::EVENT_BACKEND_LOGS: + emit eventBackendLogs(data); + break; + case ServiceEvents::EVENT_ACTIVATION_ERROR: + emit eventActivationError(data); + break; + case ServiceEvents::EVENT_CONFIG_IMPORT: + emit eventConfigImport(data); + break; + default: + Q_ASSERT(false); + } +} + +void AndroidVPNActivity::handleActivityMessage(int code, const QString &data) +{ + auto mode = (UIEvents)code; + + switch (mode) { + case UIEvents::QR_CODED_DECODED: + emit eventQrCodeReceived(data); + break; + default: + Q_ASSERT(false); + } +} + +void AndroidVPNActivity::onServiceConnected(JNIEnv* env, jobject thiz) { + Q_UNUSED(env); + Q_UNUSED(thiz); + + emit AndroidVPNActivity::instance()->serviceConnected(); +} + +void AndroidVPNActivity::onServiceDisconnected(JNIEnv* env, jobject thiz) { + Q_UNUSED(env); + Q_UNUSED(thiz); + + emit AndroidVPNActivity::instance()->serviceDisconnected(); +} + +void AndroidVPNActivity::onAndroidVpnActivityMessage(JNIEnv *env, jobject thiz, jint messageType, jstring message) +{ + Q_UNUSED(thiz); + const char* buffer = env->GetStringUTFChars(message, nullptr); + if (!buffer) { + return; + } + + QString parcelBody(buffer); + env->ReleaseStringUTFChars(message, buffer); + + AndroidUtils::dispatchToMainThread([messageType, parcelBody] { + AndroidVPNActivity::instance()->handleActivityMessage(messageType, parcelBody); + }); +} diff --git a/client/platforms/android/androidvpnactivity.h b/client/platforms/android/androidvpnactivity.h new file mode 100644 index 00000000..8eeb5598 --- /dev/null +++ b/client/platforms/android/androidvpnactivity.h @@ -0,0 +1,104 @@ +/* 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 ANDROIDVPNACTIVITY_H +#define ANDROIDVPNACTIVITY_H + +#include + +#include "jni.h" + +// Binder Codes for VPNServiceBinder +// See also - VPNServiceBinder.kt +// Actions that are Requestable +enum ServiceAction { + // Activate the vpn. Body requires a json wg-conf + ACTION_ACTIVATE = 1, + // Deactivate the vpn. Body is empty + ACTION_DEACTIVATE = 2, + // Register an IBinder to recieve events body is an Ibinder + ACTION_REGISTERLISTENER = 3, + // Requests an EVENT_STATISTIC_UPDATE to be send + ACTION_REQUEST_STATISTIC = 4, + ACTION_REQUEST_GET_LOG = 5, + // Requests to clean up the internal log + ACTION_REQUEST_CLEANUP_LOG = 6, + // Retry activation using the last config + // Used when the activation is aborted for VPN-Permission prompt + ACTION_RESUME_ACTIVATE = 7, + // Sets the current notification text. + // Does nothing if there is no notification + ACTION_SET_NOTIFICATION_TEXT = 8, + // Sets the fallback text if the OS triggered the VPN-Service + // to show a notification + ACTION_SET_NOTIFICATION_FALLBACK = 9, + // Share used config + ACTION_SHARE_CONFIG = 10, +}; +typedef enum ServiceAction ServiceAction; + +// Event Types that will be Dispatched after registration +enum ServiceEvents { + // The Service has Accecpted our Binder + // Responds with the current status of the vpn. + EVENT_INIT = 0, + // WG-Go has enabled the adapter (empty response) + EVENT_CONNECTED = 1, + // WG-Go has disabled the adapter (empty response) + EVENT_DISCONNECTED = 2, + // Contains the Current transfered bytes to endpoint x. + EVENT_STATISTIC_UPDATE = 3, + EVENT_BACKEND_LOGS = 4, + // An Error happened during activation + // Contains the error message + EVENT_ACTIVATION_ERROR = 5, + EVENT_NEED_PERMISSION = 6, + // Import of existing config + EVENT_CONFIG_IMPORT = 7, +}; +typedef enum ServiceEvents ServiceEvents; + +enum UIEvents { + QR_CODED_DECODED = 0, +}; +typedef enum UIEvents UIEvents; + +class AndroidVPNActivity : public QObject +{ + Q_OBJECT + +public: + static void maybeInit(); + static AndroidVPNActivity* instance(); + static bool handleBackButton(JNIEnv* env, jobject thiz); + static void sendToService(ServiceAction type, const QString& data); + static void connectService(); + static void startQrCodeReader(); + static void saveFileAs(QString fileContent, QString suggestedFilename); + static void copyTextToClipboard(QString text); + +signals: + void serviceConnected(); + void serviceDisconnected(); + void eventInitialized(const QString& data); + void eventConnected(const QString& data); + void eventDisconnected(const QString& data); + void eventStatisticUpdate(const QString& data); + void eventBackendLogs(const QString& data); + void eventActivationError(const QString& data); + void eventConfigImport(const QString& data); + void eventQrCodeReceived(const QString& data); + +private: + AndroidVPNActivity(); + + static void onServiceMessage(JNIEnv* env, jobject thiz, jint messageType, jstring body); + static void onServiceConnected(JNIEnv* env, jobject thiz); + static void onServiceDisconnected(JNIEnv* env, jobject thiz); + static void onAndroidVpnActivityMessage(JNIEnv* env, jobject thiz, jint messageType, jstring message); + void handleServiceMessage(int code, const QString& data); + void handleActivityMessage(int code, const QString& data); +}; + +#endif // ANDROIDVPNACTIVITY_H diff --git a/client/platforms/ios/WireGuard-Bridging-Header.h b/client/platforms/ios/WireGuard-Bridging-Header.h index 40b6c89d..e5dfa39f 100644 --- a/client/platforms/ios/WireGuard-Bridging-Header.h +++ b/client/platforms/ios/WireGuard-Bridging-Header.h @@ -2,7 +2,6 @@ * 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 "wireguard-go-version.h" #include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include diff --git a/client/platforms/ios/ioslogger.swift b/client/platforms/ios/ioslogger.swift index e364d365..644f7581 100644 --- a/client/platforms/ios/ioslogger.swift +++ b/client/platforms/ios/ioslogger.swift @@ -37,7 +37,7 @@ public class Logger { appVersion += " (\(appBuild))" } - let goBackendVersion = WIREGUARD_GO_VERSION + let goBackendVersion = "1" Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)") } } diff --git a/client/platforms/ios/iostunnel.swift b/client/platforms/ios/iostunnel.swift index 6d87a18d..1f3f452b 100644 --- a/client/platforms/ios/iostunnel.swift +++ b/client/platforms/ios/iostunnel.swift @@ -4,7 +4,6 @@ import os import Darwin import OpenVPNAdapter //import Tun2socks - enum TunnelProtoType: String { case wireguard, openvpn, shadowsocks, none } @@ -415,9 +414,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } } } - // MARK: -- Leaf provider methods - private func prepareConfig(onInterface iface: String, fromSSConfig ssConfig: Data, andOvpnConfig ovpnConfig: Data) -> UnsafePointer? { guard let ssConfig = try? JSONSerialization.jsonObject(with: ssConfig, options: []) as? [String: Any] else { self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", @@ -425,7 +422,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { userInfo: [NSLocalizedDescriptionKey: "Cannot parse json for ss in tunnel"])) return nil } - guard let remoteHost = ssConfig[Constants.ssRemoteHost] as? String, let remotePort = ssConfig[Constants.ssRemotePort] as? Int, let method = ssConfig[Constants.ssCipherKey] as? String, @@ -435,19 +431,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider { userInfo: [NSLocalizedDescriptionKey: "Cannot asign profile params for ss in tunnel"])) return nil } - var insettings: [String: Any] = .init() insettings["name"] = iface insettings["address"] = "127.0.0.2" insettings["netmask"] = "255.255.255.0" insettings["gateway"] = "127.0.0.1" insettings["mtu"] = 1600 - var inbounds: [String: Any] = .init() inbounds["protocol"] = "tun" inbounds["settings"] = insettings inbounds["tag"] = "tun_in" - var outbounds: [String: Any] = .init() var outsettings: [String: Any] = .init() outsettings["address"] = remoteHost @@ -457,18 +450,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider { outbounds["protocol"] = "shadowsocks" outbounds["settings"] = outsettings outbounds["tag"] = "shadowsocks_out" - var params: [String: Any] = .init() params["inbounds"] = [inbounds] params["outbounds"] = [outbounds] - wg_log(.error, message: "Config dictionary: \(params)") - guard let jsonData = try? JSONSerialization.data(withJSONObject: params, options: .prettyPrinted), let jsonString = String(data: jsonData, encoding: .utf8) else { return nil } - wg_log(.error, message: "JSON String: \(jsonString)") - var path = "" if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { @@ -538,7 +526,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } } - private func stopLeafRedirector(completion: @escaping () -> Void) { leafProvider?.stopTunnel { error in // TODO: handle errors @@ -571,7 +558,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { wg_log(.error, message: "Error starting ShadowSocks: \(String(describing: errorCode))") return } - // self.setupAndHandleOpenVPNOverSSConnection(withConfig: ovpnConfig) self.startAndHandleTunnelOverSS(completionHandler: completion) } @@ -735,12 +721,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider { startHandler = completionHandler ovpnAdapter.connect(using: packetFlow) + +// let ifaces = Interface.allInterfaces() +// .filter { $0.family == .ipv4 } +// .map { iface in iface.name } - let ifaces = Interface.allInterfaces() - .filter { $0.family == .ipv4 } - .map { iface in iface.name } - - wg_log(.error, message: "Available TUN Interfaces: \(ifaces)") +// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)") } // MARK: -- Network observing methods diff --git a/client/protocols/android_vpnprotocol.cpp b/client/protocols/android_vpnprotocol.cpp index 66e2eaf5..521b08f2 100644 --- a/client/protocols/android_vpnprotocol.cpp +++ b/client/protocols/android_vpnprotocol.cpp @@ -1,9 +1,3 @@ -#include -#include -#include -#include -#include -#include #include #include #include @@ -12,10 +6,8 @@ #include #include #include -#include #include "android_vpnprotocol.h" -#include "core/errorstrings.h" #include "platforms/android/android_controller.h" @@ -23,9 +15,7 @@ AndroidVpnProtocol::AndroidVpnProtocol(Proto protocol, const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent), m_protocol(protocol) -{ - -} +{ } ErrorCode AndroidVpnProtocol::start() { @@ -39,3 +29,11 @@ void AndroidVpnProtocol::stop() AndroidController::instance()->stop(); } +void AndroidVpnProtocol::connectionDataUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4) +{ + quint64 rxBytes = totalRx.toLongLong(); + quint64 txBytes = totalTx.toLongLong(); + + setBytesChanged(rxBytes, txBytes); +} + diff --git a/client/protocols/android_vpnprotocol.h b/client/protocols/android_vpnprotocol.h index cdcff2c6..ea87679a 100644 --- a/client/protocols/android_vpnprotocol.h +++ b/client/protocols/android_vpnprotocol.h @@ -1,16 +1,11 @@ #ifndef ANDROID_VPNPROTOCOL_H #define ANDROID_VPNPROTOCOL_H -#include -#include - #include "vpnprotocol.h" #include "protocols/protocols_defs.h" using namespace amnezia; - - class AndroidVpnProtocol : public VpnProtocol { Q_OBJECT @@ -25,6 +20,9 @@ public: signals: +public slots: + void connectionDataUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4); + protected slots: protected: @@ -32,7 +30,6 @@ protected: private: Proto m_protocol; - }; #endif // ANDROID_VPNPROTOCOL_H diff --git a/client/protocols/ikev2_vpn_protocol_windows.cpp b/client/protocols/ikev2_vpn_protocol_windows.cpp index 47bc3d0c..66c861d3 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.cpp +++ b/client/protocols/ikev2_vpn_protocol_windows.cpp @@ -6,9 +6,9 @@ #include -#include "debug.h" +#include "logger.h" #include "ikev2_vpn_protocol_windows.h" -#include "utils.h" +#include "utilities.h" static Ikev2Protocol* self = nullptr; static std::mutex rasDialFuncMutex; diff --git a/client/protocols/ios_vpnprotocol.h b/client/protocols/ios_vpnprotocol.h index 2b2971f1..7f55be19 100644 --- a/client/protocols/ios_vpnprotocol.h +++ b/client/protocols/ios_vpnprotocol.h @@ -1,9 +1,9 @@ #ifndef IOS_VPNPROTOCOL_H #define IOS_VPNPROTOCOL_H +#include "platforms/ios/json.h" #include "vpnprotocol.h" #include "protocols/protocols_defs.h" -#include "json.h" using namespace amnezia; @@ -36,6 +36,7 @@ public: void cleanupBackendLogs(); signals: + void newTransmitedDataCount(quint64 rxBytes, quint64 txBytes); protected slots: diff --git a/client/protocols/ios_vpnprotocol.mm b/client/protocols/ios_vpnprotocol.mm index 8decd885..f314f5fb 100644 --- a/client/protocols/ios_vpnprotocol.mm +++ b/client/protocols/ios_vpnprotocol.mm @@ -10,10 +10,12 @@ #include -#include "ipaddressrange.h" +#include "platforms/ios/ipaddressrange.h" #include "ios_vpnprotocol.h" #include "core/errorstrings.h" #include "AmneziaVPN-Swift.h" +#include "UIKit/UIKit.h" + namespace { @@ -23,8 +25,10 @@ Proto currentProto = amnezia::Proto::Any; } IOSVpnProtocol::IOSVpnProtocol(Proto proto, const QJsonObject &configuration, QObject* parent) -: VpnProtocol(configuration, parent), -m_protocol(proto) {} +: VpnProtocol(configuration, parent), m_protocol(proto) +{ + connect(this, &IOSVpnProtocol::newTransmitedDataCount, this, &IOSVpnProtocol::setBytesChanged); +} IOSVpnProtocol* IOSVpnProtocol::instance() { return s_instance; @@ -205,8 +209,7 @@ void IOSVpnProtocol::checkStatus() qDebug() << "ServerIpv4Gateway:" << QString::fromNSString(serverIpv4Gateway) << "DeviceIpv4Address:" << QString::fromNSString(deviceIpv4Address) << "RxBytes:" << rxBytes << "TxBytes:" << txBytes; - emit bytesChanged(rxBytes, txBytes); - + emit newTransmitedDataCount(rxBytes, txBytes); }]; } diff --git a/client/protocols/openvpnovercloakprotocol.cpp b/client/protocols/openvpnovercloakprotocol.cpp index 5695b3dc..55939895 100644 --- a/client/protocols/openvpnovercloakprotocol.cpp +++ b/client/protocols/openvpnovercloakprotocol.cpp @@ -1,6 +1,6 @@ #include "openvpnovercloakprotocol.h" -#include "utils.h" +#include "utilities.h" #include "containers/containers_defs.h" #include @@ -109,8 +109,6 @@ QString OpenVpnOverCloakProtocol::cloakExecPath() { #ifdef Q_OS_WIN return Utils::executable(QString("cloak/ck-client"), true); -#elif defined Q_OS_LINUX - return Utils::usrExecutable("ck-client"); #else return Utils::executable(QString("/ck-client"), true); #endif diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index cfd13a7e..150e84be 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -5,9 +5,9 @@ #include #include -#include "debug.h" +#include "logger.h" #include "defines.h" -#include "utils.h" +#include "utilities.h" #include "openvpnprotocol.h" @@ -141,9 +141,9 @@ uint OpenVpnProtocol::selectMgmtPort() void OpenVpnProtocol::updateRouteGateway(QString line) { // TODO: fix for macos - line = line.split("ROUTE_GATEWAY", QString::SkipEmptyParts).at(1); + line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); if (!line.contains("/")) return; - m_routeGateway = line.split("/", QString::SkipEmptyParts).first(); + m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); m_routeGateway.replace(" ", ""); qDebug() << "Set VPN route gateway" << m_routeGateway; } diff --git a/client/protocols/shadowsocksvpnprotocol.cpp b/client/protocols/shadowsocksvpnprotocol.cpp index 3de0bfe8..82ae08b8 100644 --- a/client/protocols/shadowsocksvpnprotocol.cpp +++ b/client/protocols/shadowsocksvpnprotocol.cpp @@ -1,7 +1,7 @@ #include "shadowsocksvpnprotocol.h" -#include "debug.h" -#include "utils.h" +#include "logger.h" +#include "utilities.h" #include "containers/containers_defs.h" #include @@ -109,8 +109,6 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath() { #ifdef Q_OS_WIN return Utils::executable(QString("ss/ss-local"), true); -#elif defined Q_OS_LINUX - return Utils::usrExecutable(QString("ss-local")); #else return Utils::executable(QString("/ss-local"), true); #endif @@ -118,5 +116,17 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath() void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration) { - m_shadowSocksConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::ShadowSocks)).toObject(); + QJsonObject shadowSocksConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::ShadowSocks)).toObject(); + bool isLocalPortConvertOk = false; + bool isServerPortConvertOk = false; + int localPort = shadowSocksConfig.value("local_port").toString().toInt(&isLocalPortConvertOk); + int serverPort = shadowSocksConfig.value("server_port").toString().toInt(&isServerPortConvertOk); + if (!isLocalPortConvertOk) { + qDebug() << "Error when converting local_port field in ShadowSocks config"; + } else if (!isServerPortConvertOk) { + qDebug() << "Error when converting server_port field in ShadowSocks config"; + } + shadowSocksConfig["local_port"] = localPort; + shadowSocksConfig["server_port"] = serverPort; + m_shadowSocksConfig = shadowSocksConfig; } diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index 2d304cdf..a8f392e9 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -67,7 +67,10 @@ VpnProtocol::VpnConnectionState VpnProtocol::connectionState() const void VpnProtocol::setBytesChanged(quint64 receivedBytes, quint64 sentBytes) { - emit bytesChanged(receivedBytes - m_receivedBytes, sentBytes - m_sentBytes); + quint64 rxDiff = receivedBytes - m_receivedBytes; + quint64 txDiff = sentBytes - m_sentBytes; + + emit bytesChanged(rxDiff, txDiff); m_receivedBytes = receivedBytes; m_sentBytes = sentBytes; diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index e6ccef6c..666bf80d 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -4,15 +4,14 @@ #include #include -#include "debug.h" +#include "logger.h" #include "wireguardprotocol.h" -#include "utils.h" +#include "utilities.h" -WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject* parent) : - VpnProtocol(configuration, parent) +WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent) { m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf"); - readWireguardConfiguration(configuration); + writeWireguardConfiguration(configuration); } WireguardProtocol::~WireguardProtocol() @@ -47,11 +46,8 @@ void WireguardProtocol::stop() m_wireguardStopProcess->setProgram(PermittedProcess::Wireguard); - - QStringList arguments({"--remove", configPath()}); - m_wireguardStopProcess->setArguments(arguments); - - qDebug() << arguments.join(" "); + m_wireguardStopProcess->setArguments(stopArgs()); + qDebug() << stopArgs().join(" "); connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error; @@ -62,15 +58,28 @@ void WireguardProtocol::stop() qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; }); +#ifdef Q_OS_LINUX + if (IpcClient::Interface()) { + QRemoteObjectPendingReply result = IpcClient::Interface()->isWireguardRunning(); + if (result.returnValue()) { + setConnectionState(VpnProtocol::Disconnected); + return; + } + } else { + qCritical() << "IPC client not initialized"; + setConnectionState(VpnProtocol::Disconnected); + return; + } +#endif + m_wireguardStopProcess->start(); m_wireguardStopProcess->waitForFinished(10000); setConnectionState(VpnProtocol::Disconnected); #endif - } -void WireguardProtocol::readWireguardConfiguration(const QJsonObject &configuration) +void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration) { QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toObject(); @@ -79,13 +88,28 @@ void WireguardProtocol::readWireguardConfiguration(const QJsonObject &configurat return; } - m_isConfigLoaded = true; - m_configFile.write(jConfig.value(config_key::config).toString().toUtf8()); m_configFile.close(); - m_configFileName = m_configFile.fileName(); - qDebug().noquote() << QString("Set config data") << m_configFileName; +#ifdef Q_OS_LINUX + if (IpcClient::Interface()) { + QRemoteObjectPendingReply result = IpcClient::Interface()->copyWireguardConfig(m_configFile.fileName()); + if (result.returnValue()) { + qCritical() << "Failed to copy wireguard config"; + return; + } + } else { + qCritical() << "IPC client not initialized"; + return; + } + m_configFileName = "/etc/wireguard/wg99.conf"; +#else + m_configFileName = m_configFile.fileName(); +#endif + + m_isConfigLoaded = true; + + qDebug().noquote() << QString("Set config data") << configPath(); qDebug().noquote() << QString("Set config data") << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); } @@ -98,9 +122,9 @@ QString WireguardProtocol::configPath() const void WireguardProtocol::updateRouteGateway(QString line) { // TODO: fix for macos - line = line.split("ROUTE_GATEWAY", QString::SkipEmptyParts).at(1); + line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); if (!line.contains("/")) return; - m_routeGateway = line.split("/", QString::SkipEmptyParts).first(); + m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); m_routeGateway.replace(" ", ""); qDebug() << "Set VPN route gateway" << m_routeGateway; } @@ -120,8 +144,15 @@ ErrorCode WireguardProtocol::start() return lastError(); } - if (!QFileInfo::exists(configPath())) { - setLastError(ErrorCode::ConfigMissing); + if (IpcClient::Interface()) { + QRemoteObjectPendingReply result = IpcClient::Interface()->isWireguardConfigExists(configPath()); + if (result.returnValue()) { + setLastError(ErrorCode::ConfigMissing); + return lastError(); + } + } else { + qCritical() << "IPC client not initialized"; + setLastError(ErrorCode::InternalError); return lastError(); } @@ -143,11 +174,8 @@ ErrorCode WireguardProtocol::start() m_wireguardStartProcess->setProgram(PermittedProcess::Wireguard); - - QStringList arguments({"--add", configPath()}); - m_wireguardStartProcess->setArguments(arguments); - - qDebug() << arguments.join(" "); + m_wireguardStartProcess->setArguments(startArgs()); + qDebug() << startArgs().join(" "); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error; @@ -176,7 +204,7 @@ ErrorCode WireguardProtocol::start() connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyReadStandardError, this, [this]() { QRemoteObjectPendingReply reply = m_wireguardStartProcess->readAllStandardError(); - reply.waitForFinished(1000); + reply.waitForFinished(10); qDebug() << "WireguardProtocol::WireguardProtocol readAllStandardError" << reply.returnValue(); }); @@ -204,10 +232,33 @@ void WireguardProtocol::updateVpnGateway(const QString &line) // qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); // } // } - // } +// } } QString WireguardProtocol::serviceName() const { return "AmneziaVPN.WireGuard0"; } + +QStringList WireguardProtocol::stopArgs() +{ +#ifdef Q_OS_WIN + return {"--remove", configPath()}; +#elif defined Q_OS_LINUX + return {"down", "wg99"}; +#else + return {"--remove", configPath()}; +#endif +} + +QStringList WireguardProtocol::startArgs() +{ +#ifdef Q_OS_WIN + return {"--add", configPath()}; +#elif defined Q_OS_LINUX + return {"up", "wg99"}; +#else + return {"--add", configPath()}; +#endif +} + diff --git a/client/protocols/wireguardprotocol.h b/client/protocols/wireguardprotocol.h index 3a091cac..880417b4 100644 --- a/client/protocols/wireguardprotocol.h +++ b/client/protocols/wireguardprotocol.h @@ -23,12 +23,13 @@ public: private: QString configPath() const; - void readWireguardConfiguration(const QJsonObject &configuration); + void writeWireguardConfiguration(const QJsonObject &configuration); void updateRouteGateway(QString line); void updateVpnGateway(const QString &line); QString serviceName() const; - + QStringList stopArgs(); + QStringList startArgs(); private: QString m_configFileName; diff --git a/client/resources.qrc b/client/resources.qrc index 0c325062..67937bda 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -62,7 +62,6 @@ server_scripts/website_tor/configure_container.sh server_scripts/website_tor/run_container.sh ui/qml/main.qml - ui/qml/TitleBar.qml ui/qml/Pages/PageBase.qml ui/qml/Pages/PageAppSetting.qml ui/qml/Pages/PageGeneralSettings.qml @@ -82,7 +81,6 @@ ui/qml/Pages/PageSites.qml ui/qml/Pages/PageStart.qml ui/qml/Pages/PageVPN.qml - ui/qml/Pages/PageQrDecoder.qml ui/qml/Pages/PageAbout.qml ui/qml/Pages/PageQrDecoderIos.qml ui/qml/Pages/PageViewConfig.qml @@ -130,6 +128,7 @@ ui/qml/Controls/SvgButtonType.qml ui/qml/Config/GlobalConfig.qml ui/qml/Config/qmldir + server_scripts/check_server_is_busy.sh server_scripts/dns/configure_container.sh server_scripts/dns/Dockerfile server_scripts/dns/run_container.sh @@ -163,5 +162,8 @@ images/svg/control_point_black_24dp.svg images/svg/settings_suggest_black_24dp.svg server_scripts/website_tor/Dockerfile + ui/qml/Controls/PopupWithQuestion.qml + ui/qml/Pages/PageAdvancedServerSettings.qml + ui/qml/Controls/PopupWarning.qml diff --git a/client/scripts/openvpn.sh b/client/scripts/openvpn.sh new file mode 100644 index 00000000..2da9e1e8 --- /dev/null +++ b/client/scripts/openvpn.sh @@ -0,0 +1,21 @@ + +XCODEBUILD="/usr/bin/xcodebuild" +WORKINGDIR=`pwd` +PATCH="/usr/bin/patch" + + cat $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/Project.xcconfig > $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig + cat << EOF >> $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig + PROJECT_TEMP_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/OpenVPNAdapter.build + CONFIGURATION_BUILD_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-iphoneos + BUILT_PRODUCTS_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-iphoneos +EOF + + + cd 3rd/OpenVPNAdapter + if $XCODEBUILD -scheme OpenVPNAdapter -configuration Release -xcconfig Configuration/amnezia.xcconfig -sdk iphoneos -destination 'generic/platform=iOS' -project OpenVPNAdapter.xcodeproj ; then + echo "OpenVPNAdapter built successfully" + else + echo "OpenVPNAdapter build failed" + fi + cd ../../ + diff --git a/client/secure_qsettings.cpp b/client/secure_qsettings.cpp index d038be6b..e71eff48 100644 --- a/client/secure_qsettings.cpp +++ b/client/secure_qsettings.cpp @@ -4,11 +4,12 @@ #include #include #include +#include #include #include #include #include -#include "utils.h" +#include "utilities.h" #include #include "QAead.h" #include "QBlockCipher.h" diff --git a/client/server_scripts/check_server_is_busy.sh b/client/server_scripts/check_server_is_busy.sh new file mode 100644 index 00000000..17c27864 --- /dev/null +++ b/client/server_scripts/check_server_is_busy.sh @@ -0,0 +1,4 @@ +pm_apt="/usr/bin/apt-get";\ +if [[ -f "$pm_apt" ]]; then pm=$pm_apt; else exit; fi;\ +if [[ ! -f "/usr/bin/sudo" ]]; then $pm update -y -q; $pm install -y -q sudo; fi;\ +sudo fuser /var/lib/dpkg/lock-frontend \ No newline at end of file diff --git a/client/server_scripts/install_docker.sh b/client/server_scripts/install_docker.sh index baff7cb6..bb14e4cf 100644 --- a/client/server_scripts/install_docker.sh +++ b/client/server_scripts/install_docker.sh @@ -6,4 +6,5 @@ if [[ -f "$pm_apt" ]]; then export DEBIAN_FRONTEND=noninteractive; fi;\ if [[ -z "$docker_service" ]]; then sudo $pm update -y -q; sudo $pm install -y -q curl $docker_pkg; fi;\ docker_service=$(systemctl list-units --full -all | grep docker.service | grep -v inactive | grep -v dead | grep -v failed);\ if [[ -z "$docker_service" ]]; then sleep 5 && sudo systemctl start docker && sleep 5; fi;\ +if [[ -f "$pm_yum" ]]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\ docker --version diff --git a/client/server_scripts/openvpn_cloak/Dockerfile b/client/server_scripts/openvpn_cloak/Dockerfile index b3a6810d..3cb14e82 100644 --- a/client/server_scripts/openvpn_cloak/Dockerfile +++ b/client/server_scripts/openvpn_cloak/Dockerfile @@ -1,5 +1,4 @@ FROM alpine:3.15 - LABEL maintainer="AmneziaVPN" ARG SS_RELEASE="v1.13.1" diff --git a/client/server_scripts/openvpn_cloak/configure_container.sh b/client/server_scripts/openvpn_cloak/configure_container.sh index 26b514a4..5e2deb45 100644 --- a/client/server_scripts/openvpn_cloak/configure_container.sh +++ b/client/server_scripts/openvpn_cloak/configure_container.sh @@ -1,6 +1,6 @@ cat > /opt/amnezia/openvpn/server.conf < /opt/amnezia/cloak/ck-config.json < -#include -#include -#include -#include - -#ifdef Q_OS_WIN - -#include -#include -#include -#include -#include // Fixes error C2504: 'IUnknown' : base class undefined -#include -#include -#pragma comment (lib,"Dwmapi.lib") // Adds missing library, fixes error LNK2019: unresolved external symbol __imp__DwmExtendFrameIntoClientArea -#pragma comment (lib,"user32.lib") - -CFramelessWindow::CFramelessWindow(QWidget *parent) - : QMainWindow(parent), - m_titlebar(Q_NULLPTR), - m_borderWidth(5), - m_bJustMaximized(false), - m_bResizeable(true) -{ -// setWindowFlag(Qt::Window,true); -// setWindowFlag(Qt::FramelessWindowHint, true); -// setWindowFlag(Qt::WindowSystemMenuHint, true); -// setWindowFlag() is not avaliable before Qt v5.9, so we should use setWindowFlags instead - - if (QOperatingSystemVersion::current() > QOperatingSystemVersion::Windows7) { - setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint); - setResizeable(m_bResizeable); - } -} - -void CFramelessWindow::setResizeable(bool resizeable) -{ - bool visible = isVisible(); - m_bResizeable = resizeable; - if (m_bResizeable){ - setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); -// setWindowFlag(Qt::WindowMaximizeButtonHint); - - //此行代码可以带回Aero效果,同时也带回了标题栏和边框,在nativeEvent()会再次去掉标题栏 - // - //this line will get titlebar/thick frame/Aero back, which is exactly what we want - //we will get rid of titlebar and thick frame again in nativeEvent() later - HWND hwnd = (HWND)this->winId(); - DWORD style = ::GetWindowLong(hwnd, GWL_STYLE); - ::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION); - }else{ - setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint); -// setWindowFlag(Qt::WindowMaximizeButtonHint,false); - - HWND hwnd = (HWND)this->winId(); - DWORD style = ::GetWindowLong(hwnd, GWL_STYLE); - ::SetWindowLong(hwnd, GWL_STYLE, style & ~WS_MAXIMIZEBOX & ~WS_CAPTION); - } - - //保留一个像素的边框宽度,否则系统不会绘制边框阴影 - // - //we better left 1 piexl width of border untouch, so OS can draw nice shadow around it - const MARGINS shadow = { 1, 1, 1, 1 }; - DwmExtendFrameIntoClientArea(HWND(winId()), &shadow); - - setVisible(visible); -} - -void CFramelessWindow::setResizeableAreaWidth(int width) -{ - if (1 > width) width = 1; - m_borderWidth = width; -} - -void CFramelessWindow::setTitleBar(QWidget* titlebar) -{ - m_titlebar = titlebar; - if (!titlebar) return; - connect(titlebar, SIGNAL(destroyed(QObject*)), this, SLOT(onTitleBarDestroyed())); -} - -void CFramelessWindow::onTitleBarDestroyed() -{ - if (m_titlebar == QObject::sender()) - { - m_titlebar = Q_NULLPTR; - } -} - -void CFramelessWindow::addIgnoreWidget(QWidget* widget) -{ - if (!widget) return; - if (m_whiteList.contains(widget)) return; - m_whiteList.append(widget); -} - -bool CFramelessWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) -{ - if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::Windows7) { - return QMainWindow::nativeEvent(eventType, message, result); - } - - //Workaround for known bug -> check Qt forum : https://forum.qt.io/topic/93141/qtablewidget-itemselectionchanged/13 - #if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1)) - MSG* msg = *reinterpret_cast(message); - #else - MSG* msg = reinterpret_cast(message); - #endif - - switch (msg->message) - { - case WM_NCCALCSIZE: - { - NCCALCSIZE_PARAMS& params = *reinterpret_cast(msg->lParam); - if (params.rgrc[0].top != 0) - params.rgrc[0].top -= 1; - - //this kills the window frame and title bar we added with WS_THICKFRAME and WS_CAPTION - *result = WVR_REDRAW; - return true; - } - case WM_NCHITTEST: - { - *result = 0; - - const LONG border_width = m_borderWidth; - RECT winrect; - GetWindowRect(HWND(winId()), &winrect); - - long x = GET_X_LPARAM(msg->lParam); - long y = GET_Y_LPARAM(msg->lParam); - - if(m_bResizeable) - { - - bool resizeWidth = minimumWidth() != maximumWidth(); - bool resizeHeight = minimumHeight() != maximumHeight(); - - if(resizeWidth) - { - //left border - if (x >= winrect.left && x < winrect.left + border_width) - { - *result = HTLEFT; - } - //right border - if (x < winrect.right && x >= winrect.right - border_width) - { - *result = HTRIGHT; - } - } - if(resizeHeight) - { - //bottom border - if (y < winrect.bottom && y >= winrect.bottom - border_width) - { - *result = HTBOTTOM; - } - //top border - if (y >= winrect.top && y < winrect.top + border_width) - { - *result = HTTOP; - } - } - if(resizeWidth && resizeHeight) - { - //bottom left corner - if (x >= winrect.left && x < winrect.left + border_width && - y < winrect.bottom && y >= winrect.bottom - border_width) - { - *result = HTBOTTOMLEFT; - } - //bottom right corner - if (x < winrect.right && x >= winrect.right - border_width && - y < winrect.bottom && y >= winrect.bottom - border_width) - { - *result = HTBOTTOMRIGHT; - } - //top left corner - if (x >= winrect.left && x < winrect.left + border_width && - y >= winrect.top && y < winrect.top + border_width) - { - *result = HTTOPLEFT; - } - //top right corner - if (x < winrect.right && x >= winrect.right - border_width && - y >= winrect.top && y < winrect.top + border_width) - { - *result = HTTOPRIGHT; - } - } - } - if (0!=*result) return true; - - //*result still equals 0, that means the cursor locate OUTSIDE the frame area - //but it may locate in titlebar area - if (!m_titlebar) return false; - - //support highdpi - double dpr = this->devicePixelRatioF(); - QPoint pos = m_titlebar->mapFromGlobal(QPoint(x/dpr,y/dpr)); - - if (!m_titlebar->rect().contains(pos)) return false; - QWidget* child = m_titlebar->childAt(pos); - if (!child) - { - *result = HTCAPTION; - return true; - }else{ - if (m_whiteList.contains(child)) - { - *result = HTCAPTION; - return true; - } - } - return false; - } //end case WM_NCHITTEST - case WM_GETMINMAXINFO: - { - if (::IsZoomed(msg->hwnd)) { - RECT frame = { 0, 0, 0, 0 }; - AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0); - - //record frame area data - double dpr = this->devicePixelRatioF(); - - m_frames.setLeft(abs(frame.left)/dpr+0.5); - m_frames.setTop(abs(frame.bottom)/dpr+0.5); - m_frames.setRight(abs(frame.right)/dpr+0.5); - m_frames.setBottom(abs(frame.bottom)/dpr+0.5); - - QMainWindow::setContentsMargins(m_frames.left()+m_margins.left(), \ - m_frames.top()+m_margins.top(), \ - m_frames.right()+m_margins.right(), \ - m_frames.bottom()+m_margins.bottom()); - m_bJustMaximized = true; - }else { - if (m_bJustMaximized) - { - QMainWindow::setContentsMargins(m_margins); - m_frames = QMargins(); - m_bJustMaximized = false; - } - } - return false; - } - default: - return QMainWindow::nativeEvent(eventType, message, result); - } -} - -void CFramelessWindow::setContentsMargins(const QMargins &margins) -{ - QMainWindow::setContentsMargins(margins+m_frames); - m_margins = margins; -} -void CFramelessWindow::setContentsMargins(int left, int top, int right, int bottom) -{ - QMainWindow::setContentsMargins(left+m_frames.left(),\ - top+m_frames.top(), \ - right+m_frames.right(), \ - bottom+m_frames.bottom()); - m_margins.setLeft(left); - m_margins.setTop(top); - m_margins.setRight(right); - m_margins.setBottom(bottom); -} -QMargins CFramelessWindow::contentsMargins() const -{ - QMargins margins = QMainWindow::contentsMargins(); - margins -= m_frames; - return margins; -} -void CFramelessWindow::getContentsMargins(int *left, int *top, int *right, int *bottom) const -{ - QMainWindow::getContentsMargins(left,top,right,bottom); - if (!(left&&top&&right&&bottom)) return; - if (isMaximized()) - { - *left -= m_frames.left(); - *top -= m_frames.top(); - *right -= m_frames.right(); - *bottom -= m_frames.bottom(); - } -} -QRect CFramelessWindow::contentsRect() const -{ - QRect rect = QMainWindow::contentsRect(); - int width = rect.width(); - int height = rect.height(); - rect.setLeft(rect.left() - m_frames.left()); - rect.setTop(rect.top() - m_frames.top()); - rect.setWidth(width); - rect.setHeight(height); - return rect; -} -void CFramelessWindow::showFullScreen() -{ - if (isMaximized()) - { - QMainWindow::setContentsMargins(m_margins); - m_frames = QMargins(); - } - QMainWindow::showFullScreen(); -} - -#endif //Q_OS_WIN diff --git a/client/ui/framelesswindow.h b/client/ui/framelesswindow.h deleted file mode 100644 index ac560231..00000000 --- a/client/ui/framelesswindow.h +++ /dev/null @@ -1,158 +0,0 @@ -// This code is a part of Qt-Nice-Frameless-Window -// https://github.com/Bringer-of-Light/Qt-Nice-Frameless-Window -// Licensed by MIT License - https://github.com/Bringer-of-Light/Qt-Nice-Frameless-Window/blob/master/LICENSE - - -#ifndef CFRAMELESSWINDOW_H -#define CFRAMELESSWINDOW_H -#include "qsystemdetection.h" -#include -#include - -//A nice frameless window for both Windows and OS X -//Author: Bringer-of-Light -//Github: https://github.com/Bringer-of-Light/Qt-Nice-Frameless-Window -// Usage: use "CFramelessWindow" as base class instead of "QMainWindow", and enjoy -#ifdef Q_OS_WIN -#include -#include -#include -#include -class CFramelessWindow : public QMainWindow -{ - Q_OBJECT -public: - explicit CFramelessWindow(QWidget *parent = 0); -public: - - //设置是否可以通过鼠标调整窗口大小 - //if resizeable is set to false, then the window can not be resized by mouse - //but still can be resized programtically - void setResizeable(bool resizeable=true); - bool isResizeable(){return m_bResizeable;} - - //设置可调整大小区域的宽度,在此区域内,可以使用鼠标调整窗口大小 - //set border width, inside this aera, window can be resized by mouse - void setResizeableAreaWidth(int width = 5); -protected: - //设置一个标题栏widget,此widget会被当做标题栏对待 - //set a widget which will be treat as SYSTEM titlebar - void setTitleBar(QWidget* titlebar); - - //在标题栏控件内,也可以有子控件如标签控件“label1”,此label1遮盖了标题栏,导致不能通过label1拖动窗口 - //要解决此问题,使用addIgnoreWidget(label1) - //generally, we can add widget say "label1" on titlebar, and it will cover the titlebar under it - //as a result, we can not drag and move the MainWindow with this "label1" again - //we can fix this by add "label1" to a ignorelist, just call addIgnoreWidget(label1) - void addIgnoreWidget(QWidget* widget); - - bool nativeEvent(const QByteArray &eventType, void *message, long *result); -private slots: - void onTitleBarDestroyed(); -public: - void setContentsMargins(const QMargins &margins); - void setContentsMargins(int left, int top, int right, int bottom); - QMargins contentsMargins() const; - QRect contentsRect() const; - void getContentsMargins(int *left, int *top, int *right, int *bottom) const; -public slots: - void showFullScreen(); -private: - QWidget* m_titlebar; - QList m_whiteList; - int m_borderWidth; - - QMargins m_margins; - QMargins m_frames; - bool m_bJustMaximized; - - bool m_bResizeable; -}; - -#elif defined Q_OS_MAC || defined Q_OS_LINUX -#include -#include -#include -class CFramelessWindow : public QMainWindow -{ - Q_OBJECT -public: - explicit CFramelessWindow(QWidget *parent = 0); -private: - void initUI(); -public: - //设置可拖动区域的高度,在此区域内,可以通过鼠标拖动窗口, 0表示整个窗口都可拖动 - //In draggable area, window can be moved by mouse, (height = 0) means that the whole window is draggable - void setDraggableAreaHeight(int height = 0); - - //只有OS X10.10及以后系统,才支持OS X原生样式包括:三个系统按钮、窗口圆角、窗口阴影 - //类初始化完成后,可以通过此函数查看是否已经启用了原生样式。如果未启动,需要自定义关闭按钮、最小化按钮、最大化按钮 - //Native style(three system button/ round corner/ drop shadow) works only on OS X 10.10 or later - //after init, we should check whether NativeStyle is OK with this function - //if NOT ok, we should implement close button/ min button/ max button ourself - bool isNativeStyleOK() {return m_bNativeSystemBtn;} - - //如果设置setCloseBtnQuit(false),那么点击关闭按钮后,程序不会退出,而是会隐藏,只有在OS X 10.10 及以后系统中有效 - //if setCloseBtnQuit(false), then when close button is clicked, the application will hide itself instead of quit - //be carefull, after you set this to false, you can NOT change it to true again - //this function should be called inside of the constructor function of derived classes, and can NOT be called more than once - //only works for OS X 10.10 or later - void setCloseBtnQuit(bool bQuit = true); - - //启用或禁用关闭按钮,只有在isNativeStyleOK()返回true的情况下才有效 - //enable or disable Close button, only worked if isNativeStyleOK() returns true - void setCloseBtnEnabled(bool bEnable = true); - - //启用或禁用最小化按钮,只有在isNativeStyleOK()返回true的情况下才有效 - //enable or disable Miniaturize button, only worked if isNativeStyleOK() returns true - void setMinBtnEnabled(bool bEnable = true); - - //启用或禁用zoom(最大化)按钮,只有在isNativeStyleOK()返回true的情况下才有效 - //enable or disable Zoom button(fullscreen button), only worked if isNativeStyleOK() returns true - void setZoomBtnEnabled(bool bEnable = true); - - bool isCloseBtnEnabled() {return m_bIsCloseBtnEnabled;} - bool isMinBtnEnabled() {return m_bIsMinBtnEnabled;} - bool isZoomBtnEnabled() {return m_bIsZoomBtnEnabled;} -protected: - void mousePressEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); - void mouseMoveEvent(QMouseEvent *event); -private: - int m_draggableHeight; - bool m_bWinMoving; - bool m_bMousePressed; - QPoint m_MousePos; - QPoint m_WindowPos; - bool m_bCloseBtnQuit; - bool m_bNativeSystemBtn; - bool m_bIsCloseBtnEnabled, m_bIsMinBtnEnabled, m_bIsZoomBtnEnabled; - - //=============================================== - //TODO - //下面的代码是试验性质的 - //tentative code - - //窗口从全屏状态恢复正常大小时,标题栏又会出现,原因未知。 - //默认情况下,系统的最大化按钮(zoom button)是进入全屏,为了避免标题栏重新出现的问题, - //以上代码已经重新定义了系统zoom button的行为,是其功能变为最大化而不是全屏 - //以下代码尝试,每次窗口从全屏状态恢复正常大小时,都再次进行设置,以消除标题栏 - //after the window restore from fullscreen mode, the titlebar will show again, it looks like a BUG - //on OS X 10.10 and later, click the system green button (zoom button) will make the app become fullscreen - //so we have override it's action to "maximized" in the CFramelessWindow Constructor function - //but we may try something else such as delete the titlebar again and again... -private: - bool m_bTitleBarVisible; - - void setTitlebarVisible(bool bTitlebarVisible = false); - bool isTitlebarVisible() {return m_bTitleBarVisible;} -private slots: - void onRestoreFromFullScreen(); -signals: - void restoreFromFullScreen(); -protected: - void resizeEvent(QResizeEvent *event); -}; -#endif - -#endif // CFRAMELESSWINDOW_H diff --git a/client/ui/pages.h b/client/ui/pages.h index 69f417fa..dfc9a509 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -24,7 +24,7 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, - ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig}; + ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp b/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp new file mode 100644 index 00000000..182e8bed --- /dev/null +++ b/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp @@ -0,0 +1,87 @@ +#include "AdvancedServerSettingsLogic.h" + +#include "VpnLogic.h" +#include "ui/uilogic.h" +#include "core/errorstrings.h" +#include "core/servercontroller.h" + +AdvancedServerSettingsLogic::AdvancedServerSettingsLogic(UiLogic *uiLogic, QObject *parent): PageLogicBase(uiLogic, parent), + m_labelWaitInfoVisible{true}, + m_pushButtonClearVisible{true}, + m_pushButtonClearText{tr("Clear server from Amnezia software")} +{ +} + +void AdvancedServerSettingsLogic::onUpdatePage() +{ + set_labelWaitInfoVisible(false); + set_labelWaitInfoText(""); + set_pushButtonClearVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); + const QJsonObject &server = m_settings->server(uiLogic()->m_selectedServerIndex); + const QString &port = server.value(config_key::port).toString(); + + const QString &userName = server.value(config_key::userName).toString(); + const QString &hostName = server.value(config_key::hostName).toString(); + QString name = QString("%1%2%3%4%5").arg(userName, + userName.isEmpty() ? "" : "@", + hostName, + port.isEmpty() ? "" : ":", + port); + + set_labelServerText(name); + + DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); + QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); + set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); +} + +void AdvancedServerSettingsLogic::onPushButtonClearServerClicked() +{ + set_pageEnabled(false); + set_pushButtonClearText(tr("Uninstalling Amnezia software...")); + + if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { + uiLogic()->pageLogic()->onDisconnect(); + } + + ErrorCode e = m_serverController->removeAllContainers(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex)); + if (e) { + emit uiLogic()->showWarningMessage(tr("Error occurred while cleaning the server.") + "\n" + + tr("Error message: ") + errorString(e) + "\n" + + tr("See logs for details.")); + } else { + set_labelWaitInfoVisible(true); + set_labelWaitInfoText(tr("Amnezia server successfully uninstalled")); + } + + m_settings->setContainers(uiLogic()->m_selectedServerIndex, {}); + m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); + + set_pageEnabled(true); + set_pushButtonClearText(tr("Clear server from Amnezia software")); +} + +void AdvancedServerSettingsLogic::onPushButtonScanServerClicked() +{ + set_labelWaitInfoVisible(false); + set_pageEnabled(false); + + bool isServerCreated; + auto containersCount = m_settings->containers(uiLogic()->m_selectedServerIndex).size(); + ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(isServerCreated); + if (errorCode != ErrorCode::NoError) { + emit uiLogic()->showWarningMessage(tr("Error occurred while scanning the server.") + "\n" + + tr("Error message: ") + errorString(errorCode) + "\n" + + tr("See logs for details.")); + } + auto newContainersCount = m_settings->containers(uiLogic()->m_selectedServerIndex).size(); + if (containersCount != newContainersCount) { + emit uiLogic()->showWarningMessage(tr("All containers installed on the server are added to the GUI")); + } else { + emit uiLogic()->showWarningMessage(tr("No installed containers found on the server")); + } + + + onUpdatePage(); + set_pageEnabled(true); +} diff --git a/client/ui/pages_logic/AdvancedServerSettingsLogic.h b/client/ui/pages_logic/AdvancedServerSettingsLogic.h new file mode 100644 index 00000000..692968f1 --- /dev/null +++ b/client/ui/pages_logic/AdvancedServerSettingsLogic.h @@ -0,0 +1,31 @@ +#ifndef ADVANCEDSERVERSETTINGSLOGIC_H +#define ADVANCEDSERVERSETTINGSLOGIC_H + +#include "PageLogicBase.h" + +class UiLogic; + +class AdvancedServerSettingsLogic : public PageLogicBase +{ + Q_OBJECT + + AUTO_PROPERTY(bool, labelWaitInfoVisible) + AUTO_PROPERTY(QString, labelWaitInfoText) + + AUTO_PROPERTY(QString, pushButtonClearText) + AUTO_PROPERTY(bool, pushButtonClearVisible) + + AUTO_PROPERTY(QString, labelServerText) + AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) + +public: + explicit AdvancedServerSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); + ~AdvancedServerSettingsLogic() = default; + + Q_INVOKABLE void onUpdatePage() override; + + Q_INVOKABLE void onPushButtonClearServerClicked(); + Q_INVOKABLE void onPushButtonScanServerClicked(); +}; + +#endif // ADVANCEDSERVERSETTINGSLOGIC_H diff --git a/client/ui/pages_logic/AppSettingsLogic.cpp b/client/ui/pages_logic/AppSettingsLogic.cpp index b22918b2..e86201e7 100644 --- a/client/ui/pages_logic/AppSettingsLogic.cpp +++ b/client/ui/pages_logic/AppSettingsLogic.cpp @@ -1,14 +1,14 @@ #include "AppSettingsLogic.h" -#include "debug.h" +#include "logger.h" #include "defines.h" #include "ui/qautostart.h" #include "ui/uilogic.h" #include #include -#include #include +#include using namespace amnezia; using namespace PageEnumNS; @@ -62,18 +62,18 @@ void AppSettingsLogic::onCheckBoxSaveLogsCheckedToggled(bool checked) void AppSettingsLogic::onPushButtonOpenLogsClicked() { - Debug::openLogsFolder(); + Logger::openLogsFolder(); } void AppSettingsLogic::onPushButtonExportLogsClicked() { - uiLogic()->saveTextFile(tr("Save log"), "AmneziaVPN.log", ".log", Debug::getLogFile()); + uiLogic()->saveTextFile(tr("Save log"), "AmneziaVPN.log", ".log", Logger::getLogFile()); } void AppSettingsLogic::onPushButtonClearLogsClicked() { - Debug::clearLogs(); - Debug::clearServiceLogs(); + Logger::clearLogs(); + Logger::clearServiceLogs(); } void AppSettingsLogic::onPushButtonBackupAppConfigClicked() @@ -83,8 +83,8 @@ void AppSettingsLogic::onPushButtonBackupAppConfigClicked() void AppSettingsLogic::onPushButtonRestoreAppConfigClicked() { - QString fileName = QFileDialog::getOpenFileName(Q_NULLPTR, tr("Open backup"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); + QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open backup"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); if (fileName.isEmpty()) return; @@ -96,11 +96,8 @@ void AppSettingsLogic::onPushButtonRestoreAppConfigClicked() if (ok) { emit uiLogic()->goToPage(Page::Vpn); emit uiLogic()->setStartPage(Page::Vpn); + } else { + emit uiLogic()->showWarningMessage(tr("Can't import config, file is corrupted.")); } - else { - QMessageBox::warning(nullptr, APPLICATION_NAME, - tr("Can't import config, file is corrupted.")); - } - } diff --git a/client/ui/pages_logic/GeneralSettingsLogic.cpp b/client/ui/pages_logic/GeneralSettingsLogic.cpp index a71a7f91..0e92f8c9 100644 --- a/client/ui/pages_logic/GeneralSettingsLogic.cpp +++ b/client/ui/pages_logic/GeneralSettingsLogic.cpp @@ -12,28 +12,29 @@ GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent): void GeneralSettingsLogic::onUpdatePage() { - uiLogic()->selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); + uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); + set_existsAnyServer(m_settings->serversCount() > 0); + uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); set_pushButtonGeneralSettingsShareConnectionEnable(m_settings->haveAuthData(m_settings->defaultServerIndex())); } void GeneralSettingsLogic::onPushButtonGeneralSettingsServerSettingsClicked() { - uiLogic()->selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); + uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); + uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); emit uiLogic()->goToPage(Page::ServerSettings); } void GeneralSettingsLogic::onPushButtonGeneralSettingsShareConnectionClicked() { - uiLogic()->selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->selectedDockerContainer = m_settings->defaultContainer(uiLogic()->selectedServerIndex); + uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); + uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - qobject_cast(uiLogic()->protocolsModel())->setSelectedServerIndex(uiLogic()->selectedServerIndex); - qobject_cast(uiLogic()->protocolsModel())->setSelectedDockerContainer(uiLogic()->selectedDockerContainer); + qobject_cast(uiLogic()->protocolsModel())->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); + qobject_cast(uiLogic()->protocolsModel())->setSelectedDockerContainer(uiLogic()->m_selectedDockerContainer); - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer); + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); emit uiLogic()->goToPage(Page::ShareConnection); } diff --git a/client/ui/pages_logic/GeneralSettingsLogic.h b/client/ui/pages_logic/GeneralSettingsLogic.h index 85671590..a0cff333 100644 --- a/client/ui/pages_logic/GeneralSettingsLogic.h +++ b/client/ui/pages_logic/GeneralSettingsLogic.h @@ -10,6 +10,7 @@ class GeneralSettingsLogic : public PageLogicBase Q_OBJECT AUTO_PROPERTY(bool, pushButtonGeneralSettingsShareConnectionEnable) + AUTO_PROPERTY(bool, existsAnyServer) public: Q_INVOKABLE void onUpdatePage() override; diff --git a/client/ui/pages_logic/NetworkSettingsLogic.cpp b/client/ui/pages_logic/NetworkSettingsLogic.cpp index 4038d089..fa49df38 100644 --- a/client/ui/pages_logic/NetworkSettingsLogic.cpp +++ b/client/ui/pages_logic/NetworkSettingsLogic.cpp @@ -1,8 +1,8 @@ #include "NetworkSettingsLogic.h" #include "defines.h" +#include "utilities.h" #include "settings.h" -#include "utils.h" NetworkSettingsLogic::NetworkSettingsLogic(UiLogic *logic, QObject *parent): PageLogicBase(logic, parent), @@ -22,14 +22,14 @@ void NetworkSettingsLogic::onUpdatePage() void NetworkSettingsLogic::onLineEditDns1EditFinished(const QString &text) { - if (ipAddressRegex().exactMatch(text)) { + if (ipAddressRegex().match(text).hasMatch()) { m_settings->setPrimaryDns(text); } } void NetworkSettingsLogic::onLineEditDns2EditFinished(const QString &text) { - if (ipAddressRegex().exactMatch(text)) { + if (ipAddressRegex().match(text).hasMatch()) { m_settings->setSecondaryDns(text); } } diff --git a/client/ui/pages_logic/NetworkSettingsLogic.h b/client/ui/pages_logic/NetworkSettingsLogic.h index b70bc143..237706eb 100644 --- a/client/ui/pages_logic/NetworkSettingsLogic.h +++ b/client/ui/pages_logic/NetworkSettingsLogic.h @@ -3,6 +3,8 @@ #include "PageLogicBase.h" +#include + class UiLogic; class NetworkSettingsLogic : public PageLogicBase @@ -13,7 +15,7 @@ class NetworkSettingsLogic : public PageLogicBase AUTO_PROPERTY(QString, lineEditDns1Text) AUTO_PROPERTY(QString, lineEditDns2Text) - READONLY_PROPERTY(QRegExp, ipAddressRegex) + READONLY_PROPERTY(QRegularExpression, ipAddressRegex) public: Q_INVOKABLE void onUpdatePage() override; diff --git a/client/ui/pages_logic/NewServerProtocolsLogic.cpp b/client/ui/pages_logic/NewServerProtocolsLogic.cpp index cabe1bc0..a1db7565 100644 --- a/client/ui/pages_logic/NewServerProtocolsLogic.cpp +++ b/client/ui/pages_logic/NewServerProtocolsLogic.cpp @@ -17,7 +17,6 @@ void NewServerProtocolsLogic::onUpdatePage() void NewServerProtocolsLogic::onPushButtonConfigureClicked(DockerContainer c, int port, TransportProto tp) { - QMap containers; Proto mainProto = ContainerProps::defaultProtocol(c); QJsonObject config { @@ -28,8 +27,8 @@ void NewServerProtocolsLogic::onPushButtonConfigureClicked(DockerContainer c, in } }; - containers.insert(c, config); + QPair container(c, config); - uiLogic()->installServer(containers); + uiLogic()->installServer(container); } diff --git a/client/ui/pages_logic/QrDecoderLogic.cpp b/client/ui/pages_logic/QrDecoderLogic.cpp index 0c24ca1c..e1845c77 100644 --- a/client/ui/pages_logic/QrDecoderLogic.cpp +++ b/client/ui/pages_logic/QrDecoderLogic.cpp @@ -3,19 +3,70 @@ #include "ui/uilogic.h" #include "ui/pages_logic/StartPageLogic.h" -#if defined(Q_OS_ANDROID) -#include "android_controller.h" +#ifdef Q_OS_ANDROID +#include +#include +#include "../../platforms/android/androidutils.h" #endif using namespace amnezia; using namespace PageEnumNS; +namespace { + QrDecoderLogic* mInstance = nullptr; + constexpr auto CLASSNAME = "org.amnezia.vpn.qt.CameraActivity"; +} + QrDecoderLogic::QrDecoderLogic(UiLogic *logic, QObject *parent): PageLogicBase(logic, parent) { + mInstance = this; + #if (defined(Q_OS_ANDROID)) + AndroidUtils::runOnAndroidThreadAsync([]() { + JNINativeMethod methods[]{ + {"passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast(onNewDataChunk)}, + }; + + QJniObject javaClass(CLASSNAME); + QJniEnvironment env; + jclass objectClass = env->GetObjectClass(javaClass.object()); + env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); + }); + #endif } +void QrDecoderLogic::stopDecodingQr() +{ + #if (defined(Q_OS_ANDROID)) + QJniObject::callStaticMethod(CLASSNAME, "stopQrCodeReader", "()V"); + #endif + + emit stopDecode(); +} + +#ifdef Q_OS_ANDROID +void QrDecoderLogic::onNewDataChunk(JNIEnv *env, jobject thiz, jstring data) +{ + Q_UNUSED(thiz); + const char* buffer = env->GetStringUTFChars(data, nullptr); + if (!buffer) { + return; + } + + QString parcelBody(buffer); + env->ReleaseStringUTFChars(data, buffer); + + if (mInstance != nullptr) { + if (!mInstance->m_detectingEnabled) { + mInstance->onUpdatePage(); + } + mInstance->onDetectedQrCode(parcelBody); + } +} +#endif + void QrDecoderLogic::onUpdatePage() { m_chunks.clear(); @@ -28,7 +79,6 @@ void QrDecoderLogic::onUpdatePage() void QrDecoderLogic::onDetectedQrCode(const QString &code) { //qDebug() << code; - if (!detectingEnabled()) return; // check if chunk received @@ -36,12 +86,12 @@ void QrDecoderLogic::onDetectedQrCode(const QString &code) QDataStream s(&ba, QIODevice::ReadOnly); qint16 magic; s >> magic; - if (magic == amnezia::qrMagicCode) { quint8 chunksCount; s >> chunksCount; if (totalChunksCount() != chunksCount) { m_chunks.clear(); } + set_totalChunksCount(chunksCount); quint8 chunkId; s >> chunkId; @@ -50,6 +100,7 @@ void QrDecoderLogic::onDetectedQrCode(const QString &code) if (m_chunks.size() == totalChunksCount()) { QByteArray data; + for (int i = 0; i < totalChunksCount(); ++i) { data.append(m_chunks.value(i)); } @@ -57,21 +108,18 @@ void QrDecoderLogic::onDetectedQrCode(const QString &code) bool ok = uiLogic()->pageLogic()->importConnectionFromQr(data); if (ok) { set_detectingEnabled(false); - emit stopDecode(); - } - else { + stopDecodingQr(); + } else { m_chunks.clear(); set_totalChunksCount(0); set_receivedChunksCount(0); } } - } - else { + } else { bool ok = uiLogic()->pageLogic()->importConnectionFromQr(ba); if (ok) { set_detectingEnabled(false); - emit stopDecode(); + stopDecodingQr(); } } } - diff --git a/client/ui/pages_logic/QrDecoderLogic.h b/client/ui/pages_logic/QrDecoderLogic.h index 243da95b..2b24bf27 100644 --- a/client/ui/pages_logic/QrDecoderLogic.h +++ b/client/ui/pages_logic/QrDecoderLogic.h @@ -3,6 +3,10 @@ #include "PageLogicBase.h" +#ifdef Q_OS_ANDROID +#include "jni.h" +#endif + class UiLogic; class QrDecoderLogic : public PageLogicBase @@ -16,10 +20,17 @@ public: Q_INVOKABLE void onUpdatePage() override; Q_INVOKABLE void onDetectedQrCode(const QString &code); +#ifdef Q_OS_ANDROID + static void onNewDataChunk(JNIEnv *env, jobject thiz, jstring data); +#endif + public: explicit QrDecoderLogic(UiLogic *uiLogic, QObject *parent = nullptr); ~QrDecoderLogic() = default; +private: + void stopDecodingQr(); + signals: void startDecode(); void stopDecode(); diff --git a/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp b/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp index 32ae366d..f6a8abdb 100644 --- a/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp +++ b/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp @@ -3,7 +3,8 @@ #include "core/errorstrings.h" #include #include -#include + +#include "core/servercontroller.h" ServerConfiguringProgressLogic::ServerConfiguringProgressLogic(UiLogic *logic, QObject *parent): PageLogicBase(logic, parent), @@ -13,7 +14,9 @@ ServerConfiguringProgressLogic::ServerConfiguringProgressLogic(UiLogic *logic, Q m_progressBarVisible{true}, m_progressBarMaximium{100}, m_progressBarTextVisible{true}, - m_progressBarText{tr("Configuring...")} + m_progressBarText{tr("Configuring...")}, + m_labelServerBusyVisible{false}, + m_labelServerBusyText{""} { } @@ -30,14 +33,14 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function void { set_pageEnabled(enabled); }; - ButtonFunc button; - LabelFunc info; + ButtonFunc noButton; + LabelFunc noWaitInfo; ProgressFunc progress; - progress.setVisibleFunc = [this] (bool visible) ->void { + progress.setVisibleFunc = [this] (bool visible) -> void { set_progressBarVisible(visible); }; - progress.setValueFunc = [this] (int value) ->void { + progress.setValueFunc = [this] (int value) -> void { set_progressBarValue(value); }; progress.getValueFunc = [this] (void) -> int { @@ -47,20 +50,42 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function void { + set_labelServerBusyText(text); + }; + busyInfo.setVisibleFunc = [this] (bool visible) -> void { + set_labelServerBusyVisible(visible); + }; + ButtonFunc cancelButton; + cancelButton.setVisibleFunc = [this] (bool visible) -> void { + set_pushButtonCancelVisible(visible); + }; + return doInstallAction(action, page, progress, noButton, noWaitInfo, busyInfo, cancelButton); +} + +ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function &action, + const PageFunc &page, + const ProgressFunc &progress, + const ButtonFunc &saveButton, + const LabelFunc &waitInfo, + const LabelFunc &serverBusyInfo, + const ButtonFunc &cancelButton) +{ progress.setVisibleFunc(true); if (page.setEnabledFunc) { page.setEnabledFunc(false); } - if (button.setVisibleFunc) { - button.setVisibleFunc(false); + if (saveButton.setVisibleFunc) { + saveButton.setVisibleFunc(false); } - if (info.setVisibleFunc) { - info.setVisibleFunc(true); + if (waitInfo.setVisibleFunc) { + waitInfo.setVisibleFunc(true); } - if (info.setTextFunc) { - info.setTextFunc(tr("Please wait, configuring process may take up to 5 minutes")); + if (waitInfo.setTextFunc) { + waitInfo.setTextFunc(tr("Please wait, configuring process may take up to 5 minutes")); } QTimer timer; @@ -71,22 +96,51 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function 0) { + if (remainingVal > 0) { QTimer timer1; QEventLoop loop1; @@ -114,14 +168,19 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function &action); + Q_INVOKABLE void onPushButtonCancelClicked(); private: struct ProgressFunc { @@ -48,5 +47,27 @@ private: std::function setTextFunc; }; +public: + explicit ServerConfiguringProgressLogic(UiLogic *uiLogic, QObject *parent = nullptr); + ~ServerConfiguringProgressLogic() = default; + + friend class OpenVpnLogic; + friend class ShadowSocksLogic; + friend class CloakLogic; + friend class UiLogic; + + void onUpdatePage() override; + ErrorCode doInstallAction(const std::function &action); + ErrorCode doInstallAction(const std::function &action, + const PageFunc &page, + const ProgressFunc &progress, + const ButtonFunc &saveButton, + const LabelFunc &waitInfo, + const LabelFunc &serverBusyInfo, + const ButtonFunc &cancelButton); + +signals: + void cancelDoInstallAction(const bool cancel); + }; #endif // SERVER_CONFIGURING_PROGRESS_LOGIC_H diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp index 4a7d2343..9df750e1 100644 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ b/client/ui/pages_logic/ServerContainersLogic.cpp @@ -4,9 +4,7 @@ #include -#include "protocols/CloakLogic.h" -#include "protocols/OpenVpnLogic.h" -#include "protocols/ShadowSocksLogic.h" +#include "protocols/PageProtocolLogicBase.h" #include "core/servercontroller.h" #include @@ -14,6 +12,7 @@ #include "../uilogic.h" #include "../pages_logic/VpnLogic.h" #include "vpnconnection.h" +#include "core/errorstrings.h" ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent): @@ -24,34 +23,35 @@ ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent): void ServerContainersLogic::onUpdatePage() { ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); - c_model->setSelectedServerIndex(uiLogic()->selectedServerIndex); + c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); ProtocolsModel *p_model = qobject_cast(uiLogic()->protocolsModel()); - p_model->setSelectedServerIndex(uiLogic()->selectedServerIndex); + p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); - set_isManagedServer(m_settings->haveAuthData(uiLogic()->selectedServerIndex)); + set_isManagedServer(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); + uiLogic()->m_installCredentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); emit updatePage(); } void ServerContainersLogic::onPushButtonProtoSettingsClicked(DockerContainer c, Proto p) { qDebug()<< "ServerContainersLogic::onPushButtonProtoSettingsClicked" << c << p; - uiLogic()->selectedDockerContainer = c; - uiLogic()->protocolLogic(p)->updateProtocolPage(m_settings->protocolConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer, p), - uiLogic()->selectedDockerContainer, - m_settings->haveAuthData(uiLogic()->selectedServerIndex)); + uiLogic()->m_selectedDockerContainer = c; + uiLogic()->protocolLogic(p)->updateProtocolPage(m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, p), + uiLogic()->m_selectedDockerContainer, + m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); emit uiLogic()->goToProtocolPage(p); } void ServerContainersLogic::onPushButtonDefaultClicked(DockerContainer c) { - if (m_settings->defaultContainer(uiLogic()->selectedServerIndex) == c) return; + if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == c) return; - m_settings->setDefaultContainer(uiLogic()->selectedServerIndex, c); + m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c); uiLogic()->onUpdateAllPages(); - if (uiLogic()->selectedServerIndex != m_settings->defaultServerIndex()) return; + if (uiLogic()->m_selectedServerIndex != m_settings->defaultServerIndex()) return; if (!uiLogic()->m_vpnConnection) return; if (!uiLogic()->m_vpnConnection->isConnected()) return; @@ -61,21 +61,21 @@ void ServerContainersLogic::onPushButtonDefaultClicked(DockerContainer c) void ServerContainersLogic::onPushButtonShareClicked(DockerContainer c) { - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->selectedServerIndex, c); + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, c); emit uiLogic()->goToPage(Page::ShareConnection); } void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) { //buttonSetEnabledFunc(false); - ErrorCode e = m_serverController->removeContainer(m_settings->serverCredentials(uiLogic()->selectedServerIndex), container); - m_settings->removeContainerConfig(uiLogic()->selectedServerIndex, container); + ErrorCode e = m_serverController->removeContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), container); + m_settings->removeContainerConfig(uiLogic()->m_selectedServerIndex, container); //buttonSetEnabledFunc(true); - if (m_settings->defaultContainer(uiLogic()->selectedServerIndex) == container) { - const auto &c = m_settings->containers(uiLogic()->selectedServerIndex); - if (c.isEmpty()) m_settings->setDefaultContainer(uiLogic()->selectedServerIndex, DockerContainer::None); - else m_settings->setDefaultContainer(uiLogic()->selectedServerIndex, c.keys().first()); + if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == container) { + const auto &c = m_settings->containers(uiLogic()->m_selectedServerIndex); + if (c.isEmpty()) m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); + else m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c.keys().first()); } uiLogic()->onUpdateAllPages(); } @@ -87,17 +87,33 @@ void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int p emit uiLogic()->goToPage(Page::ServerConfiguringProgress); qApp->processEvents(); - ErrorCode e = uiLogic()->pageLogic()->doInstallAction([this, c, &config](){ - return m_serverController->setupContainer(m_settings->serverCredentials(uiLogic()->selectedServerIndex), c, config); - }); + bool isServerCreated = false; + ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(isServerCreated); - if (!e) { - m_settings->setContainerConfig(uiLogic()->selectedServerIndex, c, config); - if (ContainerProps::containerService(c) == ServiceType::Vpn) { - m_settings->setDefaultContainer(uiLogic()->selectedServerIndex, c); + if (errorCode == ErrorCode::NoError) { + if (!uiLogic()->isContainerAlreadyAddedToGui(c)) { + auto installAction = [this, c, &config]() { + return m_serverController->setupContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), c, config); + }; + errorCode = uiLogic()->pageLogic()->doInstallAction(installAction); + + if (errorCode == ErrorCode::NoError) { + m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, c, config); + if (ContainerProps::containerService(c) == ServiceType::Vpn) { + m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c); + } + } + } else { + emit uiLogic()->showWarningMessage("Attention! The container you are trying to install is already installed on the server. " + "All installed containers have been added to the application "); } - } - uiLogic()->onUpdateAllPages(); + uiLogic()->onUpdateAllPages(); + } + if (errorCode != ErrorCode::NoError) { + emit uiLogic()->showWarningMessage(tr("Error occurred while configuring server.") + "\n" + + tr("Error message: ") + errorString(errorCode) + "\n" + + tr("See logs for details.")); + } emit uiLogic()->closePage(); } diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp index 42a629b3..79d13c8b 100644 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ b/client/ui/pages_logic/ServerListLogic.cpp @@ -20,7 +20,7 @@ void ServerListLogic::onServerListPushbuttonDefaultClicked(int index) void ServerListLogic::onServerListPushbuttonSettingsClicked(int index) { - uiLogic()->selectedServerIndex = index; + uiLogic()->m_selectedServerIndex = index; uiLogic()->goToPage(Page::ServerSettings); } diff --git a/client/ui/pages_logic/ServerSettingsLogic.cpp b/client/ui/pages_logic/ServerSettingsLogic.cpp index 22555a91..92b093a5 100644 --- a/client/ui/pages_logic/ServerSettingsLogic.cpp +++ b/client/ui/pages_logic/ServerSettingsLogic.cpp @@ -2,7 +2,6 @@ #include "vpnconnection.h" #include "../uilogic.h" -#include "ServerListLogic.h" #include "ShareConnectionLogic.h" #include "VpnLogic.h" @@ -11,31 +10,24 @@ #include #if defined(Q_OS_ANDROID) -#include -#include -#include +#include "../../platforms/android/androidutils.h" #endif ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent): PageLogicBase(logic, parent), m_labelWaitInfoVisible{true}, - m_pushButtonClearVisible{true}, m_pushButtonClearClientCacheVisible{true}, m_pushButtonShareFullVisible{true}, - m_pushButtonClearText{tr("Clear server from Amnezia software")}, m_pushButtonClearClientCacheText{tr("Clear client cached profile")} -{ - -} +{ } void ServerSettingsLogic::onUpdatePage() { set_labelWaitInfoVisible(false); set_labelWaitInfoText(""); - set_pushButtonClearVisible(m_settings->haveAuthData(uiLogic()->selectedServerIndex)); - set_pushButtonClearClientCacheVisible(m_settings->haveAuthData(uiLogic()->selectedServerIndex)); - set_pushButtonShareFullVisible(m_settings->haveAuthData(uiLogic()->selectedServerIndex)); - const QJsonObject &server = m_settings->server(uiLogic()->selectedServerIndex); + set_pushButtonClearClientCacheVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); + set_pushButtonShareFullVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); + const QJsonObject &server = m_settings->server(uiLogic()->m_selectedServerIndex); const QString &port = server.value(config_key::port).toString(); const QString &userName = server.value(config_key::userName).toString(); @@ -50,52 +42,22 @@ void ServerSettingsLogic::onUpdatePage() set_labelServerText(name); set_lineEditDescriptionText(server.value(config_key::description).toString()); - DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->selectedServerIndex); + DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); } -void ServerSettingsLogic::onPushButtonClearServer() -{ - set_pageEnabled(false); - set_pushButtonClearText(tr("Uninstalling Amnezia software...")); - - if (m_settings->defaultServerIndex() == uiLogic()->selectedServerIndex) { - uiLogic()->pageLogic()->onDisconnect(); - } - - ErrorCode e = m_serverController->removeAllContainers(m_settings->serverCredentials(uiLogic()->selectedServerIndex)); - m_serverController->disconnectFromHost(m_settings->serverCredentials(uiLogic()->selectedServerIndex)); - if (e) { - uiLogic()->set_dialogConnectErrorText( - tr("Error occurred while configuring server.") + "\n" + - errorString(e) + "\n" + - tr("See logs for details.")); - emit uiLogic()->showConnectErrorDialog(); - } - else { - set_labelWaitInfoVisible(true); - set_labelWaitInfoText(tr("Amnezia server successfully uninstalled")); - } - - m_settings->setContainers(uiLogic()->selectedServerIndex, {}); - m_settings->setDefaultContainer(uiLogic()->selectedServerIndex, DockerContainer::None); - - set_pageEnabled(true); - set_pushButtonClearText(tr("Clear server from Amnezia software")); -} - void ServerSettingsLogic::onPushButtonForgetServer() { - if (m_settings->defaultServerIndex() == uiLogic()->selectedServerIndex && uiLogic()->m_vpnConnection->isConnected()) { + if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex && uiLogic()->m_vpnConnection->isConnected()) { uiLogic()->pageLogic()->onDisconnect(); } - m_settings->removeServer(uiLogic()->selectedServerIndex); + m_settings->removeServer(uiLogic()->m_selectedServerIndex); - if (m_settings->defaultServerIndex() == uiLogic()->selectedServerIndex) { + if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { m_settings->setDefaultServer(0); } - else if (m_settings->defaultServerIndex() > uiLogic()->selectedServerIndex) { + else if (m_settings->defaultServerIndex() > uiLogic()->m_selectedServerIndex) { m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); } @@ -104,7 +66,7 @@ void ServerSettingsLogic::onPushButtonForgetServer() } - uiLogic()->selectedServerIndex = -1; + uiLogic()->m_selectedServerIndex = -1; uiLogic()->onUpdateAllPages(); if (m_settings->serversCount() == 0) { @@ -119,9 +81,9 @@ void ServerSettingsLogic::onPushButtonClearClientCacheClicked() { set_pushButtonClearClientCacheText(tr("Cache cleared")); - const auto &containers = m_settings->containers(uiLogic()->selectedServerIndex); - for (DockerContainer container: containers.keys()) { - m_settings->clearLastConnectionConfig(uiLogic()->selectedServerIndex, container); + const auto &containers = m_settings->containers(uiLogic()->m_selectedServerIndex); + for (DockerContainer container : containers.keys()) { + m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, container); } QTimer::singleShot(3000, this, [this]() { @@ -132,15 +94,15 @@ void ServerSettingsLogic::onPushButtonClearClientCacheClicked() void ServerSettingsLogic::onLineEditDescriptionEditingFinished() { const QString &newText = lineEditDescriptionText(); - QJsonObject server = m_settings->server(uiLogic()->selectedServerIndex); + QJsonObject server = m_settings->server(uiLogic()->m_selectedServerIndex); server.insert(config_key::description, newText); - m_settings->editServer(uiLogic()->selectedServerIndex, server); + m_settings->editServer(uiLogic()->m_selectedServerIndex, server); uiLogic()->onUpdateAllPages(); } #if defined(Q_OS_ANDROID) /* Auth result handler for Android */ -void authResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) +void authResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) { qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; @@ -155,24 +117,25 @@ void ServerSettingsLogic::onPushButtonShareFullClicked() { #if defined(Q_OS_ANDROID) /* We use builtin keyguard for ssh key export protection on Android */ - auto appContext = QtAndroid::androidActivity().callObjectMethod( + QJniObject activity = AndroidUtils::getActivity(); + auto appContext = activity.callObjectMethod( "getApplicationContext", "()Landroid/content/Context;"); if (appContext.isValid()) { - QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->selectedServerIndex); - auto intent = QAndroidJniObject::callStaticObjectMethod( + QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->m_selectedServerIndex); + auto intent = QJniObject::callStaticObjectMethod( "org/amnezia/vpn/AuthHelper", "getAuthIntent", "(Landroid/content/Context;)Landroid/content/Intent;", appContext.object()); if (intent.isValid()) { if (intent.object() != nullptr) { - QtAndroid::startActivity(intent.object(), 1, receiver); + QtAndroidPrivate::startActivity(intent.object(), 1, receiver); } } else { - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->selectedServerIndex, DockerContainer::None); + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); } } #else - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->selectedServerIndex, DockerContainer::None); + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); #endif } diff --git a/client/ui/pages_logic/ServerSettingsLogic.h b/client/ui/pages_logic/ServerSettingsLogic.h index e2d57422..a75dfa47 100644 --- a/client/ui/pages_logic/ServerSettingsLogic.h +++ b/client/ui/pages_logic/ServerSettingsLogic.h @@ -4,7 +4,8 @@ #include "PageLogicBase.h" #if defined(Q_OS_ANDROID) -#include +#include +#include #endif class UiLogic; @@ -15,9 +16,7 @@ class ServerSettingsLogic : public PageLogicBase AUTO_PROPERTY(bool, labelWaitInfoVisible) AUTO_PROPERTY(QString, labelWaitInfoText) - AUTO_PROPERTY(QString, pushButtonClearText) AUTO_PROPERTY(QString, pushButtonClearClientCacheText) - AUTO_PROPERTY(bool, pushButtonClearVisible) AUTO_PROPERTY(bool, pushButtonClearClientCacheVisible) AUTO_PROPERTY(bool, pushButtonShareFullVisible) AUTO_PROPERTY(QString, labelServerText) @@ -27,7 +26,6 @@ class ServerSettingsLogic : public PageLogicBase public: Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onPushButtonClearServer(); Q_INVOKABLE void onPushButtonForgetServer(); Q_INVOKABLE void onPushButtonShareFullClicked(); Q_INVOKABLE void onPushButtonClearClientCacheClicked(); @@ -52,7 +50,7 @@ public: ~authResultReceiver() {} public: - void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override; + void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override; private: int m_serverIndex; diff --git a/client/ui/pages_logic/ShareConnectionLogic.cpp b/client/ui/pages_logic/ShareConnectionLogic.cpp index 85849391..821cc0fc 100644 --- a/client/ui/pages_logic/ShareConnectionLogic.cpp +++ b/client/ui/pages_logic/ShareConnectionLogic.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include "qrcodegen.hpp" @@ -68,8 +67,8 @@ void ShareConnectionLogic::onPushButtonShareAmneziaGenerateClicked() set_shareAmneziaQrCodeTextSeriesLength(0); QJsonObject serverConfig; - int serverIndex = uiLogic()->selectedServerIndex; - DockerContainer container = uiLogic()->selectedDockerContainer; + int serverIndex = uiLogic()->m_selectedServerIndex; + DockerContainer container = uiLogic()->m_selectedDockerContainer; // Full access if (shareFullAccess()) { @@ -127,8 +126,8 @@ void ShareConnectionLogic::onPushButtonShareAmneziaGenerateClicked() void ShareConnectionLogic::onPushButtonShareOpenVpnGenerateClicked() { - int serverIndex = uiLogic()->selectedServerIndex; - DockerContainer container = uiLogic()->selectedDockerContainer; + int serverIndex = uiLogic()->m_selectedServerIndex; + DockerContainer container = uiLogic()->m_selectedDockerContainer; ServerCredentials credentials = m_settings->serverCredentials(serverIndex); const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); @@ -142,8 +141,8 @@ void ShareConnectionLogic::onPushButtonShareOpenVpnGenerateClicked() void ShareConnectionLogic::onPushButtonShareShadowSocksGenerateClicked() { - int serverIndex = uiLogic()->selectedServerIndex; - DockerContainer container = uiLogic()->selectedDockerContainer; + int serverIndex = uiLogic()->m_selectedServerIndex; + DockerContainer container = uiLogic()->m_selectedDockerContainer; ServerCredentials credentials = m_settings->serverCredentials(serverIndex); QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, Proto::ShadowSocks); @@ -186,8 +185,8 @@ void ShareConnectionLogic::onPushButtonShareShadowSocksGenerateClicked() void ShareConnectionLogic::onPushButtonShareCloakGenerateClicked() { - int serverIndex = uiLogic()->selectedServerIndex; - DockerContainer container = uiLogic()->selectedDockerContainer; + int serverIndex = uiLogic()->m_selectedServerIndex; + DockerContainer container = uiLogic()->m_selectedDockerContainer; ServerCredentials credentials = m_settings->serverCredentials(serverIndex); QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, Proto::Cloak); @@ -209,8 +208,8 @@ void ShareConnectionLogic::onPushButtonShareCloakGenerateClicked() void ShareConnectionLogic::onPushButtonShareWireGuardGenerateClicked() { - int serverIndex = uiLogic()->selectedServerIndex; - DockerContainer container = uiLogic()->selectedDockerContainer; + int serverIndex = uiLogic()->m_selectedServerIndex; + DockerContainer container = uiLogic()->m_selectedDockerContainer; ServerCredentials credentials = m_settings->serverCredentials(serverIndex); const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); @@ -218,9 +217,9 @@ void ShareConnectionLogic::onPushButtonShareWireGuardGenerateClicked() ErrorCode e = ErrorCode::NoError; QString cfg = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, &e); if (e) { - QMessageBox::warning(nullptr, APPLICATION_NAME, - tr("Error occurred while configuring server.") + "\n" + - errorString(e)); + emit uiLogic()->showWarningMessage(tr("Error occurred while generating the config.") + "\n" + + tr("Error message: ") + errorString(e) + "\n" + + tr("See logs for details.")); return; } cfg = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, cfg); @@ -236,8 +235,8 @@ void ShareConnectionLogic::onPushButtonShareWireGuardGenerateClicked() void ShareConnectionLogic::onPushButtonShareIkev2GenerateClicked() { - int serverIndex = uiLogic()->selectedServerIndex; - DockerContainer container = uiLogic()->selectedDockerContainer; + int serverIndex = uiLogic()->m_selectedServerIndex; + DockerContainer container = uiLogic()->m_selectedDockerContainer; ServerCredentials credentials = m_settings->serverCredentials(serverIndex); Ikev2Configurator::ConnectionData connData = m_configurator->ikev2Configurator->prepareIkev2Config(credentials, container); @@ -259,8 +258,8 @@ void ShareConnectionLogic::onPushButtonShareIkev2GenerateClicked() void ShareConnectionLogic::updateSharingPage(int serverIndex, DockerContainer container) { - uiLogic()->selectedDockerContainer = container; - uiLogic()->selectedServerIndex = serverIndex; + uiLogic()->m_selectedDockerContainer = container; + uiLogic()->m_selectedServerIndex = serverIndex; set_shareFullAccess(container == DockerContainer::None); m_shareAmneziaQrCodeTextSeries.clear(); diff --git a/client/ui/pages_logic/ShareConnectionLogic.h b/client/ui/pages_logic/ShareConnectionLogic.h index 5d46be97..3b9655aa 100644 --- a/client/ui/pages_logic/ShareConnectionLogic.h +++ b/client/ui/pages_logic/ShareConnectionLogic.h @@ -50,6 +50,5 @@ public: QList genQrCodeImageSeries(const QByteArray &data); QString svgToBase64(const QString &image); - }; #endif // SHARE_CONNECTION_LOGIC_H diff --git a/client/ui/pages_logic/SitesLogic.cpp b/client/ui/pages_logic/SitesLogic.cpp index b31018c0..17357073 100644 --- a/client/ui/pages_logic/SitesLogic.cpp +++ b/client/ui/pages_logic/SitesLogic.cpp @@ -5,7 +5,7 @@ #include "SitesLogic.h" #include "VpnLogic.h" -#include "utils.h" +#include "utilities.h" #include "vpnconnection.h" #include @@ -56,7 +56,7 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() newSite.replace("http://", ""); newSite.replace("ftp://", ""); - newSite = newSite.split("/", QString::SkipEmptyParts).first(); + newSite = newSite.split("/", Qt::SkipEmptyParts).first(); } const auto &cbProcess = [this, mode](const QString &newSite, const QString &ip) { diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 98fe7c7e..ca806897 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -5,14 +5,15 @@ #include "configurators/ssh_configurator.h" #include "configurators/vpn_configurator.h" #include "../uilogic.h" -#include "utils.h" +#include "utilities.h" #include #include #ifdef Q_OS_ANDROID -#include -#include "platforms/android/android_controller.h" +#include +#include "../../platforms/android/androidutils.h" +#include "../../platforms/android/android_controller.h" #endif namespace { @@ -56,8 +57,9 @@ StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent): { #ifdef Q_OS_ANDROID // Set security screen for Android app - QtAndroid::runOnAndroidThread([]() { - QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;"); + AndroidUtils::runOnAndroidThreadSync([]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); if (window.isValid()){ const int FLAG_SECURE = 8192; window.callMethod("addFlags", "(I)V", FLAG_SECURE); @@ -154,7 +156,7 @@ void StartPageLogic::onPushButtonConnect() set_pushButtonConnectEnabled(true); set_pushButtonConnectText(tr("Connect")); - uiLogic()->installCredentials = serverCredentials; + uiLogic()->m_installCredentials = serverCredentials; if (ok) emit uiLogic()->goToPage(Page::NewServer); } @@ -165,7 +167,7 @@ void StartPageLogic::onPushButtonImport() void StartPageLogic::onPushButtonImportOpenFile() { - QString fileName = QFileDialog::getOpenFileName(Q_NULLPTR, tr("Open config file"), + QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.vpn *.ovpn *.conf"); if (fileName.isEmpty()) return; @@ -173,13 +175,25 @@ void StartPageLogic::onPushButtonImportOpenFile() file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); - auto configFormat = checkConfigFormat(QString(data)); + selectConfigFormat(QString(data)); +} + +#ifdef Q_OS_ANDROID +void StartPageLogic::startQrDecoder() +{ + AndroidController::instance()->startQrReaderActivity(); +} +#endif + +void StartPageLogic::selectConfigFormat(QString configData) +{ + auto configFormat = checkConfigFormat(configData); if (configFormat == ConfigTypes::OpenVpn) { - importConnectionFromOpenVpnConfig(QString(data)); + importConnectionFromOpenVpnConfig(configData); } else if (configFormat == ConfigTypes::WireGuard) { - importConnectionFromWireguardConfig(QString(data)); + importConnectionFromWireguardConfig(configData); } else { - importConnectionFromCode(QString(data)); + importConnectionFromCode(configData); } } @@ -221,10 +235,6 @@ bool StartPageLogic::importConnectionFromCode(QString code) return importConnection(o); } - o = QJsonDocument::fromBinaryData(ba).object(); - if (!o.isEmpty()) { - return importConnection(o); - } return false; } diff --git a/client/ui/pages_logic/StartPageLogic.h b/client/ui/pages_logic/StartPageLogic.h index d5e72edc..6f21c105 100644 --- a/client/ui/pages_logic/StartPageLogic.h +++ b/client/ui/pages_logic/StartPageLogic.h @@ -3,6 +3,8 @@ #include "PageLogicBase.h" +#include + class UiLogic; class StartPageLogic : public PageLogicBase @@ -21,7 +23,7 @@ class StartPageLogic : public PageLogicBase AUTO_PROPERTY(QString, labelWaitInfoText) AUTO_PROPERTY(bool, pushButtonBackFromStartVisible) - READONLY_PROPERTY(QRegExp, ipAddressPortRegex) + READONLY_PROPERTY(QRegularExpression, ipAddressPortRegex) public: Q_INVOKABLE void onUpdatePage() override; @@ -29,6 +31,12 @@ public: Q_INVOKABLE void onPushButtonImport(); Q_INVOKABLE void onPushButtonImportOpenFile(); +#ifdef Q_OS_ANDROID + Q_INVOKABLE void startQrDecoder(); +#endif + + void selectConfigFormat(QString configData); + bool importConnection(const QJsonObject &profile); bool importConnectionFromCode(QString code); bool importConnectionFromQr(const QByteArray &data); diff --git a/client/ui/pages_logic/ViewConfigLogic.cpp b/client/ui/pages_logic/ViewConfigLogic.cpp index 9ccd9d3e..5f711498 100644 --- a/client/ui/pages_logic/ViewConfigLogic.cpp +++ b/client/ui/pages_logic/ViewConfigLogic.cpp @@ -79,8 +79,8 @@ void ViewConfigLogic::importConfig() if (!configJson().contains(config_key::containers) || configJson().value(config_key::containers).toArray().isEmpty()) { - uiLogic()->selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->selectedDockerContainer = m_settings->defaultContainer(uiLogic()->selectedServerIndex); + uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); + uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); uiLogic()->onUpdateAllPages(); emit uiLogic()->goToPage(Page::Vpn); emit uiLogic()->setStartPage(Page::Vpn); diff --git a/client/ui/pages_logic/VpnLogic.cpp b/client/ui/pages_logic/VpnLogic.cpp index 44a37a5c..5f6cf077 100644 --- a/client/ui/pages_logic/VpnLogic.cpp +++ b/client/ui/pages_logic/VpnLogic.cpp @@ -33,6 +33,8 @@ VpnLogic::VpnLogic(UiLogic *logic, QObject *parent): connect(this, &VpnLogic::connectToVpn, uiLogic()->m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection); connect(this, &VpnLogic::disconnectFromVpn, uiLogic()->m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); + connect(m_settings.get(), &Settings::saveLogsChanged, this, &VpnLogic::onUpdatePage); + if (m_settings->isAutoConnect() && m_settings->defaultServerIndex() >= 0) { QTimer::singleShot(1000, this, [this](){ set_pushButtonConnectEnabled(false); @@ -88,6 +90,8 @@ void VpnLogic::onUpdatePage() } QString ver = QString("v. %2").arg(QString(APP_MAJOR_VERSION)); set_labelVersionText(ver); + + set_labelLogEnabledVisible(m_settings->isSaveLogs()); } diff --git a/client/ui/pages_logic/VpnLogic.h b/client/ui/pages_logic/VpnLogic.h index 5d6ef2d9..f7b21be2 100644 --- a/client/ui/pages_logic/VpnLogic.h +++ b/client/ui/pages_logic/VpnLogic.h @@ -34,6 +34,8 @@ class VpnLogic : public PageLogicBase AUTO_PROPERTY(bool, radioButtonVpnModeForwardSitesChecked) AUTO_PROPERTY(bool, radioButtonVpnModeExceptSitesChecked) + AUTO_PROPERTY(bool, labelLogEnabledVisible) + public: Q_INVOKABLE void onUpdatePage() override; diff --git a/client/ui/pages_logic/WizardLogic.cpp b/client/ui/pages_logic/WizardLogic.cpp index 5fe820f1..23a20aed 100644 --- a/client/ui/pages_logic/WizardLogic.cpp +++ b/client/ui/pages_logic/WizardLogic.cpp @@ -18,7 +18,7 @@ void WizardLogic::onUpdatePage() set_radioButtonMediumChecked(true); } -QMap WizardLogic::getInstallConfigsFromWizardPage() const +QPair WizardLogic::getInstallConfigsFromWizardPage() const { QJsonObject cloakConfig { { config_key::container, ContainerProps::containerToString(DockerContainer::Cloak) }, @@ -33,27 +33,29 @@ QMap WizardLogic::getInstallConfigsFromWizardPage( { config_key::container, ContainerProps::containerToString(DockerContainer::OpenVpn) } }; - QMap containers; + QPair container; + + DockerContainer dockerContainer; if (radioButtonHighChecked()) { - containers.insert(DockerContainer::Cloak, cloakConfig); + container = {DockerContainer::Cloak, cloakConfig}; } if (radioButtonMediumChecked()) { - containers.insert(DockerContainer::ShadowSocks, ssConfig); + container = {DockerContainer::ShadowSocks, ssConfig}; } if (radioButtonLowChecked()) { - containers.insert(DockerContainer::OpenVpn, openVpnConfig); + container = {DockerContainer::OpenVpn, openVpnConfig}; } - return containers; + return container; } void WizardLogic::onPushButtonVpnModeFinishClicked() { - auto containers = getInstallConfigsFromWizardPage(); - uiLogic()->installServer(containers); + auto container = getInstallConfigsFromWizardPage(); + uiLogic()->installServer(container); if (checkBoxVpnModeChecked()) { m_settings->setRouteMode(Settings::VpnOnlyForwardSites); } else { @@ -63,6 +65,6 @@ void WizardLogic::onPushButtonVpnModeFinishClicked() void WizardLogic::onPushButtonLowFinishClicked() { - auto containers = getInstallConfigsFromWizardPage(); - uiLogic()->installServer(containers); + auto container = getInstallConfigsFromWizardPage(); + uiLogic()->installServer(container); } diff --git a/client/ui/pages_logic/WizardLogic.h b/client/ui/pages_logic/WizardLogic.h index 3827c86e..a2e45af7 100644 --- a/client/ui/pages_logic/WizardLogic.h +++ b/client/ui/pages_logic/WizardLogic.h @@ -25,7 +25,7 @@ public: explicit WizardLogic(UiLogic *uiLogic, QObject *parent = nullptr); ~WizardLogic() = default; - QMap getInstallConfigsFromWizardPage() const; + QPair getInstallConfigsFromWizardPage() const; }; #endif // WIZARD_LOGIC_H diff --git a/client/ui/pages_logic/protocols/CloakLogic.cpp b/client/ui/pages_logic/protocols/CloakLogic.cpp index 59019990..d62f8624 100644 --- a/client/ui/pages_logic/protocols/CloakLogic.cpp +++ b/client/ui/pages_logic/protocols/CloakLogic.cpp @@ -1,7 +1,10 @@ #include "CloakLogic.h" -#include "core/servercontroller.h" + #include -#include "../../uilogic.h" + +#include "core/servercontroller.h" +#include "ui/uilogic.h" +#include "ui/pages_logic/ServerConfiguringProgressLogic.h" using namespace amnezia; using namespace PageEnumNS; @@ -52,52 +55,82 @@ QJsonObject CloakLogic::getProtocolConfigFromPage(QJsonObject oldConfig) void CloakLogic::onPushButtonSaveClicked() { - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer, Proto::Cloak); + QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::Cloak); protocolConfig = getProtocolConfigFromPage(protocolConfig); - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer); + QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); QJsonObject newContainerConfig = containerConfig; newContainerConfig.insert(ProtocolProps::protoToString(Proto::Cloak), protocolConfig); - UiLogic::PageFunc page_func; - page_func.setEnabledFunc = [this] (bool enabled) -> void { + ServerConfiguringProgressLogic::PageFunc pageFunc; + pageFunc.setEnabledFunc = [this] (bool enabled) -> void { set_pageEnabled(enabled); }; - UiLogic::ButtonFunc pushButton_save_func; - pushButton_save_func.setVisibleFunc = [this] (bool visible) ->void { + ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; + saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { set_pushButtonSaveVisible(visible); }; - UiLogic::LabelFunc label_info_func; - label_info_func.setVisibleFunc = [this] (bool visible) ->void { + ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; + waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { set_labelInfoVisible(visible); }; - label_info_func.setTextFunc = [this] (const QString& text) ->void { + waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { set_labelInfoText(text); }; - UiLogic::ProgressFunc progressBar_reset; - progressBar_reset.setVisibleFunc = [this] (bool visible) ->void { + ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; + progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { set_progressBarResetVisible(visible); }; - progressBar_reset.setValueFunc = [this] (int value) ->void { + progressBarFunc.setValueFunc = [this] (int value) -> void { set_progressBarResetValue(value); }; - progressBar_reset.getValueFunc = [this] (void) -> int { + progressBarFunc.getValueFunc = [this] (void) -> int { return progressBarResetValue(); }; - progressBar_reset.getMaximiumFunc = [this] (void) -> int { + progressBarFunc.getMaximiumFunc = [this] (void) -> int { return progressBarResetMaximium(); }; + progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { + set_progressBarTextVisible(visible); + }; + progressBarFunc.setTextFunc = [this] (const QString& text) -> void { + set_progressBarText(text); + }; - ErrorCode e = uiLogic()->doInstallAction([this, containerConfig, &newContainerConfig](){ - return m_serverController->updateContainer(m_settings->serverCredentials(uiLogic()->selectedServerIndex), uiLogic()->selectedDockerContainer, containerConfig, newContainerConfig); + ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; + busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { + set_labelServerBusyText(text); + }; + busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { + set_labelServerBusyVisible(visible); + }; + + ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; + cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { + set_pushButtonCancelVisible(visible); + }; + + progressBarFunc.setTextVisibleFunc(true); + progressBarFunc.setTextFunc(QString("Configuring...")); + ErrorCode e = uiLogic()->pageLogic()->doInstallAction([this, containerConfig, &newContainerConfig](){ + return m_serverController->updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), + uiLogic()->m_selectedDockerContainer, + containerConfig, + newContainerConfig); }, - page_func, progressBar_reset, - pushButton_save_func, label_info_func); + pageFunc, progressBarFunc, + saveButtonFunc, waitInfoFunc, + busyInfoFuncy, cancelButtonFunc); if (!e) { - m_settings->setContainerConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer); + m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); + m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); } - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->selectedServerIndex << uiLogic()->selectedDockerContainer; + qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; +} + +void CloakLogic::onPushButtonCancelClicked() +{ + emit uiLogic()->pageLogic()->cancelDoInstallAction(true); } diff --git a/client/ui/pages_logic/protocols/CloakLogic.h b/client/ui/pages_logic/protocols/CloakLogic.h index dc16f13c..cb5f9b00 100644 --- a/client/ui/pages_logic/protocols/CloakLogic.h +++ b/client/ui/pages_logic/protocols/CloakLogic.h @@ -20,9 +20,17 @@ class CloakLogic : public PageProtocolLogicBase AUTO_PROPERTY(QString, labelInfoText) AUTO_PROPERTY(int, progressBarResetValue) AUTO_PROPERTY(int, progressBarResetMaximium) + AUTO_PROPERTY(bool, progressBarTextVisible) + AUTO_PROPERTY(QString, progressBarText) + + AUTO_PROPERTY(bool, labelServerBusyVisible) + AUTO_PROPERTY(QString, labelServerBusyText) + + AUTO_PROPERTY(bool, pushButtonCancelVisible) public: Q_INVOKABLE void onPushButtonSaveClicked(); + Q_INVOKABLE void onPushButtonCancelClicked(); public: explicit CloakLogic(UiLogic *uiLogic, QObject *parent = nullptr); diff --git a/client/ui/pages_logic/protocols/OpenVpnLogic.cpp b/client/ui/pages_logic/protocols/OpenVpnLogic.cpp index 0ebfe9cb..d5deecf3 100644 --- a/client/ui/pages_logic/protocols/OpenVpnLogic.cpp +++ b/client/ui/pages_logic/protocols/OpenVpnLogic.cpp @@ -1,7 +1,10 @@ #include "OpenVpnLogic.h" -#include "core/servercontroller.h" + #include -#include "../../uilogic.h" + +#include "core/servercontroller.h" +#include "ui/uilogic.h" +#include "ui/pages_logic/ServerConfiguringProgressLogic.h" using namespace amnezia; using namespace PageEnumNS; @@ -48,9 +51,15 @@ void OpenVpnLogic::updateProtocolPage(const QJsonObject &openvpnConfig, DockerCo set_lineEditSubnetText(openvpnConfig.value(config_key::subnet_address). toString(protocols::openvpn::defaultSubnetAddress)); - QString trasnsport = openvpnConfig.value(config_key::transport_proto). - toString(protocols::openvpn::defaultTransportProto); - + QString trasnsport; + if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { + trasnsport = "tcp"; + set_radioButtonUdpEnabled(false); + set_radioButtonTcpEnabled(false); + } else { + trasnsport = openvpnConfig.value(config_key::transport_proto). + toString(protocols::openvpn::defaultTransportProto); + } set_radioButtonUdpChecked(trasnsport == protocols::openvpn::defaultTransportProto); set_radioButtonTcpChecked(trasnsport != protocols::openvpn::defaultTransportProto); @@ -77,12 +86,6 @@ void OpenVpnLogic::updateProtocolPage(const QJsonObject &openvpnConfig, DockerCo toString(protocols::openvpn::defaultAdditionalServerConfig); set_textAreaAdditionalServerConfig(additionalServerConfig); - if (container == DockerContainer::ShadowSocks) { - set_radioButtonUdpEnabled(false); - set_radioButtonTcpEnabled(false); - set_radioButtonTcpChecked(true); - } - set_lineEditPortText(openvpnConfig.value(config_key::port). toString(protocols::openvpn::defaultPort)); @@ -100,55 +103,80 @@ void OpenVpnLogic::updateProtocolPage(const QJsonObject &openvpnConfig, DockerCo set_isThirdPartyConfig(openvpnConfig.value(config_key::isThirdPartyConfig).isBool()); } -void OpenVpnLogic::onPushButtonProtoOpenVpnSaveClicked() +void OpenVpnLogic::onPushButtonSaveClicked() { - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer, Proto::OpenVpn); + QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::OpenVpn); protocolConfig = getProtocolConfigFromPage(protocolConfig); - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer); + QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); QJsonObject newContainerConfig = containerConfig; newContainerConfig.insert(ProtocolProps::protoToString(Proto::OpenVpn), protocolConfig); - UiLogic::PageFunc page_proto_openvpn; - page_proto_openvpn.setEnabledFunc = [this] (bool enabled) -> void { + ServerConfiguringProgressLogic::PageFunc pageFunc; + pageFunc.setEnabledFunc = [this] (bool enabled) -> void { set_pageEnabled(enabled); }; - UiLogic::ButtonFunc pushButton_proto_openvpn_save; - pushButton_proto_openvpn_save.setVisibleFunc = [this] (bool visible) ->void { + ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; + saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { set_pushButtonSaveVisible(visible); }; - UiLogic::LabelFunc label_proto_openvpn_info; - label_proto_openvpn_info.setVisibleFunc = [this] (bool visible) ->void { + ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; + waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { set_labelProtoOpenVpnInfoVisible(visible); }; - label_proto_openvpn_info.setTextFunc = [this] (const QString& text) ->void { + waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { set_labelProtoOpenVpnInfoText(text); }; - UiLogic::ProgressFunc progressBar_proto_openvpn_reset; - progressBar_proto_openvpn_reset.setVisibleFunc = [this] (bool visible) ->void { + ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; + progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { set_progressBarResetVisible(visible); }; - progressBar_proto_openvpn_reset.setValueFunc = [this] (int value) ->void { + progressBarFunc.setValueFunc = [this] (int value) -> void { set_progressBarResetValue(value); }; - progressBar_proto_openvpn_reset.getValueFunc = [this] (void) -> int { + progressBarFunc.getValueFunc = [this] (void) -> int { return progressBarResetValue(); }; - progressBar_proto_openvpn_reset.getMaximiumFunc = [this] (void) -> int { + progressBarFunc.getMaximiumFunc = [this] (void) -> int { return progressBarResetMaximium(); }; + progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { + set_progressBarTextVisible(visible); + }; + progressBarFunc.setTextFunc = [this] (const QString& text) -> void { + set_progressBarText(text); + }; - ErrorCode e = uiLogic()->doInstallAction([this, containerConfig, &newContainerConfig](){ - return m_serverController->updateContainer(m_settings->serverCredentials(uiLogic()->selectedServerIndex), uiLogic()->selectedDockerContainer, containerConfig, newContainerConfig); + ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; + busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { + set_labelServerBusyText(text); + }; + busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { + set_labelServerBusyVisible(visible); + }; + + ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; + cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { + set_pushButtonCancelVisible(visible); + }; + + progressBarFunc.setTextVisibleFunc(true); + progressBarFunc.setTextFunc(QString("Configuring...")); + ErrorCode e = uiLogic()->pageLogic()->doInstallAction([this, containerConfig, &newContainerConfig](){ + return m_serverController->updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), + uiLogic()->m_selectedDockerContainer, + containerConfig, + newContainerConfig); }, - page_proto_openvpn, progressBar_proto_openvpn_reset, - pushButton_proto_openvpn_save, label_proto_openvpn_info); + pageFunc, progressBarFunc, + saveButtonFunc, waitInfoFunc, + busyInfoFuncy, cancelButtonFunc); if (!e) { - m_settings->setContainerConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer); + m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); + m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); } - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->selectedServerIndex << uiLogic()->selectedDockerContainer; + qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; } QJsonObject OpenVpnLogic::getProtocolConfigFromPage(QJsonObject oldConfig) @@ -167,3 +195,8 @@ QJsonObject OpenVpnLogic::getProtocolConfigFromPage(QJsonObject oldConfig) oldConfig.insert(config_key::additional_server_config, textAreaAdditionalServerConfig()); return oldConfig; } + +void OpenVpnLogic::onPushButtonCancelClicked() +{ + emit uiLogic()->pageLogic()->cancelDoInstallAction(true); +} diff --git a/client/ui/pages_logic/protocols/OpenVpnLogic.h b/client/ui/pages_logic/protocols/OpenVpnLogic.h index 472e10bc..7e8f1417 100644 --- a/client/ui/pages_logic/protocols/OpenVpnLogic.h +++ b/client/ui/pages_logic/protocols/OpenVpnLogic.h @@ -34,12 +34,20 @@ class OpenVpnLogic : public PageProtocolLogicBase AUTO_PROPERTY(QString, labelProtoOpenVpnInfoText) AUTO_PROPERTY(int, progressBarResetValue) AUTO_PROPERTY(int, progressBarResetMaximium) + AUTO_PROPERTY(bool, progressBarTextVisible) + AUTO_PROPERTY(QString, progressBarText) + + AUTO_PROPERTY(bool, labelServerBusyVisible) + AUTO_PROPERTY(QString, labelServerBusyText) + + AUTO_PROPERTY(bool, pushButtonCancelVisible) AUTO_PROPERTY(QString, openVpnLastConfigText) AUTO_PROPERTY(bool, isThirdPartyConfig) public: - Q_INVOKABLE void onPushButtonProtoOpenVpnSaveClicked(); + Q_INVOKABLE void onPushButtonSaveClicked(); + Q_INVOKABLE void onPushButtonCancelClicked(); public: explicit OpenVpnLogic(UiLogic *uiLogic, QObject *parent = nullptr); diff --git a/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp b/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp index 50b17a46..965a3baf 100644 --- a/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp +++ b/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp @@ -7,7 +7,7 @@ #include "OtherProtocolsLogic.h" #include #include "../../uilogic.h" -#include "utils.h" +#include "utilities.h" #ifdef Q_OS_WINDOWS #include @@ -81,7 +81,7 @@ void OtherProtocolsLogic::onPushButtonSftpMountDriveClicked() { QString mountPath; QString cmd; - QString host = m_settings->serverCredentials(uiLogic()->selectedServerIndex).hostName; + QString host = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex).hostName; #ifdef Q_OS_WINDOWS @@ -146,7 +146,7 @@ void OtherProtocolsLogic::onPushButtonSftpMountDriveClicked() //#ifndef Q_OS_WIN // args.replace("reconnect-orellinks", ""); //#endif - p->setArguments(args.split(" ", QString::SkipEmptyParts)); + p->setArguments(args.split(" ", Qt::SkipEmptyParts)); p->start(); p->waitForStarted(50); if (p->state() != QProcess::Running) { diff --git a/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp b/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp index 1d5da936..5259a6e0 100644 --- a/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp +++ b/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp @@ -1,7 +1,10 @@ #include "ShadowSocksLogic.h" -#include "core/servercontroller.h" + #include -#include "../../uilogic.h" + +#include "core/servercontroller.h" +#include "ui/pages_logic/ServerConfiguringProgressLogic.h" +#include "ui/uilogic.h" using namespace amnezia; using namespace PageEnumNS; @@ -11,12 +14,12 @@ ShadowSocksLogic::ShadowSocksLogic(UiLogic *logic, QObject *parent): m_comboBoxCipherText{"chacha20-poly1305"}, m_lineEditPortText{}, m_pushButtonSaveVisible{false}, - m_progressBaResetVisible{false}, + m_progressBarResetVisible{false}, m_lineEditPortEnabled{false}, m_labelInfoVisible{true}, m_labelInfoText{}, - m_progressBaResetValue{0}, - m_progressBaResetMaximium{100} + m_progressBarResetValue{0}, + m_progressBarResetMaximium{100} { } @@ -25,7 +28,7 @@ void ShadowSocksLogic::updateProtocolPage(const QJsonObject &ssConfig, DockerCon { set_pageEnabled(haveAuthData); set_pushButtonSaveVisible(haveAuthData); - set_progressBaResetVisible(haveAuthData); + set_progressBarResetVisible(haveAuthData); set_comboBoxCipherText(ssConfig.value(config_key::cipher). toString(protocols::shadowsocks::defaultCipher)); @@ -46,49 +49,79 @@ QJsonObject ShadowSocksLogic::getProtocolConfigFromPage(QJsonObject oldConfig) void ShadowSocksLogic::onPushButtonSaveClicked() { - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer, Proto::ShadowSocks); + QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::ShadowSocks); - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer); + QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); QJsonObject newContainerConfig = containerConfig; newContainerConfig.insert(ProtocolProps::protoToString(Proto::ShadowSocks), protocolConfig); - UiLogic::PageFunc page_proto_shadowsocks; - page_proto_shadowsocks.setEnabledFunc = [this] (bool enabled) -> void { + ServerConfiguringProgressLogic::PageFunc pageFunc; + pageFunc.setEnabledFunc = [this] (bool enabled) -> void { set_pageEnabled(enabled); }; - UiLogic::ButtonFunc pushButton_proto_shadowsocks_save; - pushButton_proto_shadowsocks_save.setVisibleFunc = [this] (bool visible) ->void { + ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; + saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { set_pushButtonSaveVisible(visible); }; - UiLogic::LabelFunc label_proto_shadowsocks_info; - label_proto_shadowsocks_info.setVisibleFunc = [this] (bool visible) ->void { + ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; + waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { set_labelInfoVisible(visible); }; - label_proto_shadowsocks_info.setTextFunc = [this] (const QString& text) ->void { + waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { set_labelInfoText(text); }; - UiLogic::ProgressFunc progressBar_reset; - progressBar_reset.setVisibleFunc = [this] (bool visible) ->void { - set_progressBaResetVisible(visible); + ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; + progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { + set_progressBarResetVisible(visible); }; - progressBar_reset.setValueFunc = [this] (int value) ->void { - set_progressBaResetValue(value); + progressBarFunc.setValueFunc = [this] (int value) -> void { + set_progressBarResetValue(value); }; - progressBar_reset.getValueFunc = [this] (void) -> int { - return progressBaResetValue(); + progressBarFunc.getValueFunc = [this] (void) -> int { + return progressBarResetValue(); }; - progressBar_reset.getMaximiumFunc = [this] (void) -> int { - return progressBaResetMaximium(); + progressBarFunc.getMaximiumFunc = [this] (void) -> int { + return progressBarResetMaximium(); + }; + progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { + set_progressBarTextVisible(visible); + }; + progressBarFunc.setTextFunc = [this] (const QString& text) -> void { + set_progressBarText(text); }; - ErrorCode e = uiLogic()->doInstallAction([this, containerConfig, &newContainerConfig](){ - return m_serverController->updateContainer(m_settings->serverCredentials(uiLogic()->selectedServerIndex), uiLogic()->selectedDockerContainer, containerConfig, newContainerConfig); + ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; + busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { + set_labelServerBusyText(text); + }; + busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { + set_labelServerBusyVisible(visible); + }; + + ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; + cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { + set_pushButtonCancelVisible(visible); + }; + + progressBarFunc.setTextVisibleFunc(true); + progressBarFunc.setTextFunc(QString("Configuring...")); + ErrorCode e = uiLogic()->pageLogic()->doInstallAction([this, containerConfig, &newContainerConfig](){ + return m_serverController->updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), + uiLogic()->m_selectedDockerContainer, + containerConfig, + newContainerConfig); }, - page_proto_shadowsocks, progressBar_reset, - pushButton_proto_shadowsocks_save, label_proto_shadowsocks_info); + pageFunc, progressBarFunc, + saveButtonFunc, waitInfoFunc, + busyInfoFuncy, cancelButtonFunc); if (!e) { - m_settings->setContainerConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->selectedServerIndex, uiLogic()->selectedDockerContainer); + m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); + m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); } - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->selectedServerIndex << uiLogic()->selectedDockerContainer; + qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; +} + +void ShadowSocksLogic::onPushButtonCancelClicked() +{ + emit uiLogic()->pageLogic()->cancelDoInstallAction(true); } diff --git a/client/ui/pages_logic/protocols/ShadowSocksLogic.h b/client/ui/pages_logic/protocols/ShadowSocksLogic.h index 4e566825..da859959 100644 --- a/client/ui/pages_logic/protocols/ShadowSocksLogic.h +++ b/client/ui/pages_logic/protocols/ShadowSocksLogic.h @@ -12,15 +12,23 @@ class ShadowSocksLogic : public PageProtocolLogicBase AUTO_PROPERTY(QString, comboBoxCipherText) AUTO_PROPERTY(QString, lineEditPortText) AUTO_PROPERTY(bool, pushButtonSaveVisible) - AUTO_PROPERTY(bool, progressBaResetVisible) + AUTO_PROPERTY(bool, progressBarResetVisible) AUTO_PROPERTY(bool, lineEditPortEnabled) AUTO_PROPERTY(bool, labelInfoVisible) AUTO_PROPERTY(QString, labelInfoText) - AUTO_PROPERTY(int, progressBaResetValue) - AUTO_PROPERTY(int, progressBaResetMaximium) + AUTO_PROPERTY(int, progressBarResetValue) + AUTO_PROPERTY(int, progressBarResetMaximium) + AUTO_PROPERTY(bool, progressBarTextVisible) + AUTO_PROPERTY(QString, progressBarText) + + AUTO_PROPERTY(bool, labelServerBusyVisible) + AUTO_PROPERTY(QString, labelServerBusyText) + + AUTO_PROPERTY(bool, pushButtonCancelVisible) public: Q_INVOKABLE void onPushButtonSaveClicked(); + Q_INVOKABLE void onPushButtonCancelClicked(); public: explicit ShadowSocksLogic(UiLogic *uiLogic, QObject *parent = nullptr); diff --git a/client/ui/qautostart.cpp b/client/ui/qautostart.cpp index 3a9e2a79..a7f49b2d 100644 --- a/client/ui/qautostart.cpp +++ b/client/ui/qautostart.cpp @@ -123,9 +123,9 @@ void Autostart::setAutostart(bool autostart) { if (file.open(QIODevice::ReadWrite)) { QTextStream stream(&file); - stream << "[Desktop Entry]" << endl; - stream << "Exec=" << appPath() << endl; - stream << "Type=Application" << endl; + stream << "[Desktop Entry]" << Qt::endl; + stream << "Exec=" << appPath() << Qt::endl; + stream << "Type=Application" << Qt::endl; } } } diff --git a/client/ui/qml/Config/GlobalConfig.qml b/client/ui/qml/Config/GlobalConfig.qml index 493252ed..5bb71b6f 100644 --- a/client/ui/qml/Config/GlobalConfig.qml +++ b/client/ui/qml/Config/GlobalConfig.qml @@ -1,12 +1,12 @@ pragma Singleton -import QtQuick 2.12 +import QtQuick Item { readonly property string screenHome: "qrc:/ScreenHome.qml" readonly property string screenHomeIntroGifEx1: "qrc:/ScreenHomeIntroGifEx1.qml" readonly property int screenWidth: 380 - readonly property int screenHeight: 640 + readonly property int screenHeight: 680 readonly property int defaultMargin: 20 @@ -26,20 +26,4 @@ Item { } return false } - - function trX(x) { - return x - } - - function trY(y) { - return y - } - - function trW(w) { - return w - } - - function trH(h) { - return h - } } diff --git a/client/ui/qml/Controls/BackButton.qml b/client/ui/qml/Controls/BackButton.qml index 5395a21e..47f0970c 100644 --- a/client/ui/qml/Controls/BackButton.qml +++ b/client/ui/qml/Controls/BackButton.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls Button { id: root diff --git a/client/ui/qml/Controls/BasicButtonType.qml b/client/ui/qml/Controls/BasicButtonType.qml index ce72509f..e115df29 100644 --- a/client/ui/qml/Controls/BasicButtonType.qml +++ b/client/ui/qml/Controls/BasicButtonType.qml @@ -1,10 +1,13 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls Button { id: root - hoverEnabled: true property bool containsMouse: hovered + hoverEnabled: true + flat: true + highlighted: false + MouseArea { id: mouseArea anchors.fill: parent diff --git a/client/ui/qml/Controls/BlueButtonType.qml b/client/ui/qml/Controls/BlueButtonType.qml index 6036d37d..a3602c4a 100644 --- a/client/ui/qml/Controls/BlueButtonType.qml +++ b/client/ui/qml/Controls/BlueButtonType.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import "../Config" diff --git a/client/ui/qml/Controls/Caption.qml b/client/ui/qml/Controls/Caption.qml index 41090e91..50fc9aca 100644 --- a/client/ui/qml/Controls/Caption.qml +++ b/client/ui/qml/Controls/Caption.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls Text { font.family: "Lato" diff --git a/client/ui/qml/Controls/CheckBoxType.qml b/client/ui/qml/Controls/CheckBoxType.qml index 07fad529..0331706c 100644 --- a/client/ui/qml/Controls/CheckBoxType.qml +++ b/client/ui/qml/Controls/CheckBoxType.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls CheckBox { id: root diff --git a/client/ui/qml/Controls/ComboBoxType.qml b/client/ui/qml/Controls/ComboBoxType.qml index cdfdaf3e..090ca9de 100644 --- a/client/ui/qml/Controls/ComboBoxType.qml +++ b/client/ui/qml/Controls/ComboBoxType.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls ComboBox { id: root diff --git a/client/ui/qml/Controls/ContextMenu.qml b/client/ui/qml/Controls/ContextMenu.qml index a0d0202d..867fcb10 100644 --- a/client/ui/qml/Controls/ContextMenu.qml +++ b/client/ui/qml/Controls/ContextMenu.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import Qt.labs.platform 1.0 +import QtQuick +import QtQuick.Controls +import Qt.labs.platform Menu { property var textObj diff --git a/client/ui/qml/Controls/FadeBehavior.qml b/client/ui/qml/Controls/FadeBehavior.qml index d40a2ddc..e523061f 100644 --- a/client/ui/qml/Controls/FadeBehavior.qml +++ b/client/ui/qml/Controls/FadeBehavior.qml @@ -1,5 +1,5 @@ -import QtQuick 2.15 -import QtQml 2.15 +import QtQuick +import QtQml Behavior { id: root @@ -32,4 +32,4 @@ Behavior { } } -} \ No newline at end of file +} diff --git a/client/ui/qml/Controls/ImageButtonType.qml b/client/ui/qml/Controls/ImageButtonType.qml index 8bf3c398..74b90c6e 100644 --- a/client/ui/qml/Controls/ImageButtonType.qml +++ b/client/ui/qml/Controls/ImageButtonType.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls BasicButtonType { id: root diff --git a/client/ui/qml/Controls/LabelType.qml b/client/ui/qml/Controls/LabelType.qml index ce1072d8..9cce61b1 100644 --- a/client/ui/qml/Controls/LabelType.qml +++ b/client/ui/qml/Controls/LabelType.qml @@ -1,4 +1,4 @@ -import QtQuick 2.12 +import QtQuick import "../Config" Text { diff --git a/client/ui/qml/Controls/Logo.qml b/client/ui/qml/Controls/Logo.qml index 9368002c..74d82872 100644 --- a/client/ui/qml/Controls/Logo.qml +++ b/client/ui/qml/Controls/Logo.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls Image { anchors.horizontalCenter: parent.horizontalCenter diff --git a/client/ui/qml/Controls/PopupWarning.qml b/client/ui/qml/Controls/PopupWarning.qml new file mode 100644 index 00000000..57c332eb --- /dev/null +++ b/client/ui/qml/Controls/PopupWarning.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Popup { + id: root + + property string popupWarningText + + anchors.centerIn: Overlay.overlay + modal: true + closePolicy: Popup.NoAutoClose + width: parent.width - 20 + + ColumnLayout { + width: parent.width + Text { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + wrapMode: Text.WordWrap + font.pixelSize: 16 + text: root.popupWarningText + } + + BlueButtonType { + Layout.preferredWidth: parent.width / 2 + Layout.fillWidth: true + text: "Continue" + onClicked: { + root.close() + } + } + } +} diff --git a/client/ui/qml/Controls/PopupWithQuestion.qml b/client/ui/qml/Controls/PopupWithQuestion.qml new file mode 100644 index 00000000..4eafd359 --- /dev/null +++ b/client/ui/qml/Controls/PopupWithQuestion.qml @@ -0,0 +1,57 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Popup { + id: root + + property string questionText + property string yesText: "yes" + property string noText: "no" + property var yesFunc + property var noFunc + + anchors.centerIn: Overlay.overlay + modal: true + closePolicy: Popup.NoAutoClose + + width: parent.width - 20 + + ColumnLayout { + width: parent.width + Text { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + wrapMode: Text.WordWrap + font.pixelSize: 16 + text: questionText + } + + RowLayout { + BlueButtonType { + id: yesButton + Layout.preferredWidth: parent.width / 2 + Layout.fillWidth: true + text: yesText + onClicked: { + root.enabled = false + if (yesFunc && typeof yesFunc === "function") { + yesFunc() + } + root.enabled = true + } + } + BlueButtonType { + id: noButton + Layout.preferredWidth: parent.width / 2 + Layout.fillWidth: true + text: noText + onClicked: { + if (noFunc && typeof noFunc === "function") { + noFunc() + } + } + } + } + } +} diff --git a/client/ui/qml/Controls/RadioButtonType.qml b/client/ui/qml/Controls/RadioButtonType.qml index e31525fe..cda28ea5 100644 --- a/client/ui/qml/Controls/RadioButtonType.qml +++ b/client/ui/qml/Controls/RadioButtonType.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls RadioButton { id: root diff --git a/client/ui/qml/Controls/RichLabelType.qml b/client/ui/qml/Controls/RichLabelType.qml index f7dfdeed..f354f974 100644 --- a/client/ui/qml/Controls/RichLabelType.qml +++ b/client/ui/qml/Controls/RichLabelType.qml @@ -1,4 +1,4 @@ -import QtQuick 2.12 +import QtQuick LabelType { id: label_connection_code diff --git a/client/ui/qml/Controls/SettingButtonType.qml b/client/ui/qml/Controls/SettingButtonType.qml index d1e91735..6166793d 100644 --- a/client/ui/qml/Controls/SettingButtonType.qml +++ b/client/ui/qml/Controls/SettingButtonType.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls BasicButtonType { id: root @@ -8,11 +8,11 @@ BasicButtonType { background: Item {} contentItem: Item { - anchors.fill: parent SvgImageType { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter svg.source: root.icon.source + enabled: root.enabled color: "#100A44" width: 25 height: 25 diff --git a/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml b/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml index eccf1bb5..31b3591e 100644 --- a/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml +++ b/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls ShareConnectionButtonType { property string start_text: qsTr("Copy") diff --git a/client/ui/qml/Controls/ShareConnectionButtonType.qml b/client/ui/qml/Controls/ShareConnectionButtonType.qml index 5609a29d..77ebbac0 100644 --- a/client/ui/qml/Controls/ShareConnectionButtonType.qml +++ b/client/ui/qml/Controls/ShareConnectionButtonType.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls BasicButtonType { diff --git a/client/ui/qml/Controls/ShareConnectionContent.qml b/client/ui/qml/Controls/ShareConnectionContent.qml index 22b274b0..99427aef 100644 --- a/client/ui/qml/Controls/ShareConnectionContent.qml +++ b/client/ui/qml/Controls/ShareConnectionContent.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtGraphicalEffects 1.12 +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes 1.4 Item { id: root @@ -17,16 +17,15 @@ Item { color: "transparent" clip: true radius: 2 - LinearGradient { - anchors.fill: parent - start: Qt.point(0, 0) - end: Qt.point(0, height) - gradient: Gradient { - GradientStop { position: 0.0; color: "#E1E1E1" } - GradientStop { position: 0.4; color: "#DDDDDD" } - GradientStop { position: 0.5; color: "#D8D8D8" } + gradient: LinearGradient { + x1: 0 ; y1: 0 + x2: 0 ; y2: height + stops: [ + GradientStop { position: 0.0; color: "#E1E1E1" }, + GradientStop { position: 0.4; color: "#DDDDDD" }, + GradientStop { position: 0.5; color: "#D8D8D8" }, GradientStop { position: 1.0; color: "#D3D3D3" } - } + ] } Image { anchors.verticalCenter: parent.verticalCenter diff --git a/client/ui/qml/Controls/SvgButtonType.qml b/client/ui/qml/Controls/SvgButtonType.qml index af55939c..e6f78c87 100644 --- a/client/ui/qml/Controls/SvgButtonType.qml +++ b/client/ui/qml/Controls/SvgButtonType.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import "." BasicButtonType { diff --git a/client/ui/qml/Controls/SvgImageType.qml b/client/ui/qml/Controls/SvgImageType.qml index 651b7330..aee928ba 100644 --- a/client/ui/qml/Controls/SvgImageType.qml +++ b/client/ui/qml/Controls/SvgImageType.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtGraphicalEffects 1.15 +import QtQuick +import QtQuick.Controls +import Qt5Compat.GraphicalEffects Item { id: root @@ -18,6 +18,6 @@ Item { ColorOverlay { anchors.fill: image source: image - color: root.color + color: root.enabled ? root.color : "grey" } } diff --git a/client/ui/qml/Controls/TextAreaType.qml b/client/ui/qml/Controls/TextAreaType.qml index 5e83e787..2f6e0843 100644 --- a/client/ui/qml/Controls/TextAreaType.qml +++ b/client/ui/qml/Controls/TextAreaType.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import Qt.labs.platform 1.0 +import QtQuick +import QtQuick.Controls +import Qt.labs.platform import "../Config" diff --git a/client/ui/qml/Controls/TextFieldType.qml b/client/ui/qml/Controls/TextFieldType.qml index 276bc743..5d7b2a65 100644 --- a/client/ui/qml/Controls/TextFieldType.qml +++ b/client/ui/qml/Controls/TextFieldType.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import Qt.labs.platform 1.0 +import QtQuick +import QtQuick.Controls +import Qt.labs.platform import "../Config" TextField { diff --git a/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml b/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml index 69fee7f3..8e04c605 100644 --- a/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml +++ b/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import "./" import "../../Controls" import "../../Config" diff --git a/client/ui/qml/Pages/InstallSettings/SelectContainer.qml b/client/ui/qml/Pages/InstallSettings/SelectContainer.qml index 000ad0e6..59e8f464 100644 --- a/client/ui/qml/Pages/InstallSettings/SelectContainer.qml +++ b/client/ui/qml/Pages/InstallSettings/SelectContainer.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import SortFilterProxyModel 0.2 import ProtocolEnum 1.0 import "./" @@ -11,8 +11,6 @@ Drawer { signal containerSelected(int c_index) property int selectedIndex: -1 - z: -3 - y: 0 x: 0 edge: Qt.RightEdge @@ -116,6 +114,7 @@ Drawer { MouseArea { anchors.fill: parent + cursorShape: Qt.PointingHandCursor onClicked: { tb.currentIndex = index tb_other.currentIndex = -1 @@ -180,6 +179,7 @@ Drawer { MouseArea { anchors.fill: parent + cursorShape: Qt.PointingHandCursor onClicked: { tb_other.currentIndex = index tb.currentIndex = -1 diff --git a/client/ui/qml/Pages/PageAbout.qml b/client/ui/qml/Pages/PageAbout.qml index 4910243f..ab25c23e 100644 --- a/client/ui/qml/Pages/PageAbout.qml +++ b/client/ui/qml/Pages/PageAbout.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import PageEnum 1.0 import "./" import "../Controls" diff --git a/client/ui/qml/Pages/PageAdvancedServerSettings.qml b/client/ui/qml/Pages/PageAdvancedServerSettings.qml new file mode 100644 index 00000000..e00f7326 --- /dev/null +++ b/client/ui/qml/Pages/PageAdvancedServerSettings.qml @@ -0,0 +1,109 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import PageEnum 1.0 +import "./" +import "../Controls" +import "../Config" + +PageBase { + id: root + page: PageEnum.AdvancedServerSettings + logic: AdvancedServerSettingsLogic + + enabled: AdvancedServerSettingsLogic.pageEnabled + + BackButton { + id: back + } + + Caption { + id: caption + text: qsTr("Advanced server settings") + anchors.horizontalCenter: parent.horizontalCenter + } + + BusyIndicator { + z: 99 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + visible: !AdvancedServerSettingsLogic.pageEnabled + running: !AdvancedServerSettingsLogic.pageEnabled + } + + FlickableType { + id: fl + anchors.top: caption.bottom + anchors.bottom: logo.top + contentHeight: content.height + + ColumnLayout { + id: content + enabled: logic.pageEnabled + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 15 + + LabelType { + Layout.fillWidth: true + font.pixelSize: 20 + horizontalAlignment: Text.AlignHCenter + text: AdvancedServerSettingsLogic.labelCurrentVpnProtocolText + } + + TextFieldType { + Layout.fillWidth: true + font.pixelSize: 20 + horizontalAlignment: Text.AlignHCenter + text: AdvancedServerSettingsLogic.labelServerText + readOnly: true + background: Item {} + } + + LabelType { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + text: AdvancedServerSettingsLogic.labelWaitInfoText + visible: AdvancedServerSettingsLogic.labelWaitInfoVisible + } + + BlueButtonType { + Layout.fillWidth: true + Layout.topMargin: 10 + text: "Scan the server for installed containers" + visible: AdvancedServerSettingsLogic.pushButtonClearVisible + onClicked: { + AdvancedServerSettingsLogic.onPushButtonScanServerClicked() + } + } + + BlueButtonType { + Layout.fillWidth: true + Layout.topMargin: 10 + text: AdvancedServerSettingsLogic.pushButtonClearText + visible: AdvancedServerSettingsLogic.pushButtonClearVisible + onClicked: { + popupClearServer.open() + } + } + + PopupWithQuestion { + id: popupClearServer + questionText: "Attention! All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. Continue?" + yesFunc: function() { + close() + AdvancedServerSettingsLogic.onPushButtonClearServerClicked() + } + noFunc: function() { + close() + } + } + } + } + + Logo { + id : logo + anchors.bottom: parent.bottom + } +} diff --git a/client/ui/qml/Pages/PageAppSetting.qml b/client/ui/qml/Pages/PageAppSetting.qml index a2fad5cc..2bf0e306 100644 --- a/client/ui/qml/Pages/PageAppSetting.qml +++ b/client/ui/qml/Pages/PageAppSetting.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" @@ -22,7 +22,6 @@ PageBase { FlickableType { id: fl anchors.top: caption.bottom - anchors.bottom: logo.top contentHeight: content.height ColumnLayout { @@ -150,9 +149,4 @@ PageBase { } } } - - Logo { - id: logo - anchors.bottom: parent.bottom - } } diff --git a/client/ui/qml/Pages/PageBase.qml b/client/ui/qml/Pages/PageBase.qml index 48e823a2..c398515b 100644 --- a/client/ui/qml/Pages/PageBase.qml +++ b/client/ui/qml/Pages/PageBase.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import PageEnum 1.0 import "./" import "../Controls" diff --git a/client/ui/qml/Pages/PageGeneralSettings.qml b/client/ui/qml/Pages/PageGeneralSettings.qml index cdc952fe..c85aa8a7 100644 --- a/client/ui/qml/Pages/PageGeneralSettings.qml +++ b/client/ui/qml/Pages/PageGeneralSettings.qml @@ -1,7 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 -import QtGraphicalEffects 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" @@ -79,6 +78,7 @@ PageBase { Layout.preferredHeight: 30 icon.source: "qrc:/images/svg/vpn_key_black_24dp.svg" text: qsTr("Server Settings") + enabled: GeneralSettingsLogic.existsAnyServer onClicked: { GeneralSettingsLogic.onPushButtonGeneralSettingsServerSettingsClicked() } @@ -95,7 +95,8 @@ PageBase { Layout.preferredHeight: 30 icon.source: "qrc:/images/svg/share_black_24dp.svg" text: qsTr("Share connection") - enabled: GeneralSettingsLogic.pushButtonGeneralSettingsShareConnectionEnable + enabled: GeneralSettingsLogic.pushButtonGeneralSettingsShareConnectionEnable && + GeneralSettingsLogic.existsAnyServer onClicked: { GeneralSettingsLogic.onPushButtonGeneralSettingsShareConnectionClicked() } @@ -112,6 +113,7 @@ PageBase { Layout.preferredHeight: 30 icon.source: "qrc:/images/svg/format_list_bulleted_black_24dp.svg" text: qsTr("Servers") + enabled: GeneralSettingsLogic.existsAnyServer onClicked: { UiLogic.goToPage(PageEnum.ServersList) } @@ -129,7 +131,12 @@ PageBase { icon.source: "qrc:/images/svg/control_point_black_24dp.svg" text: qsTr("Add server") onClicked: { - UiLogic.goToPage(PageEnum.Start) + if(GeneralSettingsLogic.existsAnyServer) + // If there is any server set we will go to Start Page + UiLogic.goToPage(PageEnum.Start) + else + // Else just come back to start page + UiLogic.closePage() } } @@ -156,6 +163,4 @@ PageBase { } } } - - } diff --git a/client/ui/qml/Pages/PageNetworkSetting.qml b/client/ui/qml/Pages/PageNetworkSetting.qml index 80cadcc2..e14c04a6 100644 --- a/client/ui/qml/Pages/PageNetworkSetting.qml +++ b/client/ui/qml/Pages/PageNetworkSetting.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" @@ -63,8 +63,8 @@ If AmneziaDNS service is not installed on the same server, or this option is unc NetworkSettingsLogic.onLineEditDns1EditFinished(text) UiLogic.onUpdateAllPages() } - validator: RegExpValidator { - regExp: NetworkSettingsLogic.ipAddressRegex + validator: RegularExpressionValidator { + regularExpression: NetworkSettingsLogic.ipAddressRegex } } @@ -92,8 +92,8 @@ If AmneziaDNS service is not installed on the same server, or this option is unc NetworkSettingsLogic.onLineEditDns2EditFinished(text) UiLogic.onUpdateAllPages() } - validator: RegExpValidator { - regExp: NetworkSettingsLogic.ipAddressRegex + validator: RegularExpressionValidator { + regularExpression: NetworkSettingsLogic.ipAddressRegex } } @@ -110,6 +110,4 @@ If AmneziaDNS service is not installed on the same server, or this option is unc } } } - - } diff --git a/client/ui/qml/Pages/PageNewServer.qml b/client/ui/qml/Pages/PageNewServer.qml index f32ac928..00cb51bc 100644 --- a/client/ui/qml/Pages/PageNewServer.qml +++ b/client/ui/qml/Pages/PageNewServer.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import PageEnum 1.0 import "./" import "../Controls" diff --git a/client/ui/qml/Pages/PageNewServerProtocols.qml b/client/ui/qml/Pages/PageNewServerProtocols.qml index e05b2644..0ce2090f 100644 --- a/client/ui/qml/Pages/PageNewServerProtocols.qml +++ b/client/ui/qml/Pages/PageNewServerProtocols.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.3 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ContainerProps 1.0 import ProtocolProps 1.0 import PageEnum 1.0 @@ -63,7 +63,7 @@ PageBase { pageLoader.focus = true } - onContainerSelected: { + onContainerSelected: function(c_index){ var containerProto = ContainerProps.defaultProtocol(c_index) tf_port_num.text = ProtocolProps.defaultPort(containerProto) diff --git a/client/ui/qml/Pages/PageQrDecoder.qml b/client/ui/qml/Pages/PageQrDecoder.qml deleted file mode 100644 index 61ce368f..00000000 --- a/client/ui/qml/Pages/PageQrDecoder.qml +++ /dev/null @@ -1,164 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import PageEnum 1.0 -import QtMultimedia 5.5 -import QZXing 3.2 - -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.QrDecoder - logic: QrDecoderLogic - - onDeactivated: { - console.debug("Stopping QR decoder") - loader.sourceComponent = undefined - } - - BackButton { - } - Caption { - id: caption - text: qsTr("Import configuration") - } - - Connections { - target: Qt.platform.os != "ios" ? QrDecoderLogic : null - function onStartDecode() { - console.debug("Starting QR decoder") - loader.sourceComponent = component - } - function onStopDecode() { - console.debug("Stopping QR decoder") - loader.sourceComponent = undefined - } - } - - Loader { - id: loader - - anchors.top: caption.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - } - - Component { - id: component - - Item { - anchors.fill: parent - - Camera - { - id:camera - focus { - focusMode: CameraFocus.FocusContinuous - focusPointMode: CameraFocus.FocusPointAuto - } - } - - VideoOutput - { - id: videoOutput - source: camera - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - autoOrientation: true - fillMode: VideoOutput.PreserveAspectFit -// filters: [ zxingFilter ] - - - Rectangle { - color: "black" - opacity: 0.5 - width: videoOutput.contentRect.width * 0.15 - height: videoOutput.contentRect.height - x: (videoOutput.width - videoOutput.contentRect.width)/2 - anchors.verticalCenter: videoOutput.verticalCenter - } - - Rectangle { - color: "black" - opacity: 0.5 - width: videoOutput.contentRect.width * 0.15 - height: videoOutput.contentRect.height - x: videoOutput.width/2 + videoOutput.contentRect.width/2 - videoOutput.contentRect.width * 0.15 - anchors.verticalCenter: videoOutput.verticalCenter - } - - Rectangle { - color: "black" - opacity: 0.5 - width: videoOutput.contentRect.width * 0.7 - height: videoOutput.contentRect.height * 0.15 - x: (videoOutput.width - videoOutput.contentRect.width)/2 + videoOutput.contentRect.width * 0.15 - y: (videoOutput.height - videoOutput.contentRect.height)/2 - } - - Rectangle { - color: "black" - opacity: 0.5 - width: videoOutput.contentRect.width * 0.7 - height: videoOutput.contentRect.height * 0.15 - x: (videoOutput.width - videoOutput.contentRect.width)/2 + videoOutput.contentRect.width * 0.15 - y: videoOutput.height/2 + videoOutput.contentRect.height/2 - videoOutput.contentRect.height * 0.15 - } - - LabelType { - width: parent.width - text: qsTr("Decoded QR chunks " + QrDecoderLogic.receivedChunksCount + "/" + QrDecoderLogic.totalChunksCount) - horizontalAlignment: Text.AlignLeft - visible: QrDecoderLogic.totalChunksCount > 0 - anchors.horizontalCenter: videoOutput.horizontalCenter - y: videoOutput.height/2 + videoOutput.contentRect.height/2 - } - } - - QZXingFilter - { - id: zxingFilter - orientation: videoOutput.orientation - captureRect: { - // setup bindings - videoOutput.contentRect; - videoOutput.sourceRect; - return videoOutput.mapRectToSource(videoOutput.mapNormalizedRectToItem(Qt.rect( - 0.15, 0.15, 0.7, 0.7 //0, 0, 1.0, 1.0 - ))); - } - - decoder { - enabledDecoders: QZXing.DecoderFormat_QR_CODE - - onTagFound: { - QrDecoderLogic.onDetectedQrCode(tag) - } - - tryHarder: true - } - - property int framesDecoded: 0 - property real timePerFrameDecode: 0 - - onDecodingFinished: - { - timePerFrameDecode = (decodeTime + framesDecoded * timePerFrameDecode) / (framesDecoded + 1); - framesDecoded++; - if(succeeded) - console.log("frame finished: " + succeeded, decodeTime, timePerFrameDecode, framesDecoded); - } - } - - - } - - } - - -} diff --git a/client/ui/qml/Pages/PageQrDecoderIos.qml b/client/ui/qml/Pages/PageQrDecoderIos.qml index 7e5783e8..21bdbfe7 100644 --- a/client/ui/qml/Pages/PageQrDecoderIos.qml +++ b/client/ui/qml/Pages/PageQrDecoderIos.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import PageEnum 1.0 import QRCodeReader 1.0 diff --git a/client/ui/qml/Pages/PageServerConfiguringProgress.qml b/client/ui/qml/Pages/PageServerConfiguringProgress.qml index e731ee08..87c602c3 100644 --- a/client/ui/qml/Pages/PageServerConfiguringProgress.qml +++ b/client/ui/qml/Pages/PageServerConfiguringProgress.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import PageEnum 1.0 import "./" import "../Controls" @@ -10,7 +10,6 @@ PageBase { page: PageEnum.ServerConfiguringProgress logic: ServerConfiguringProgressLogic - enabled: ServerConfiguringProgressLogic.pageEnabled Caption { id: caption text: qsTr("Configuring...") @@ -27,6 +26,22 @@ PageBase { horizontalAlignment: Text.AlignHCenter } + LabelType { + id: labelServerBusy + x: 0 + anchors.top: label.bottom + anchors.topMargin: 30 + + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + + width: parent.width - 40 + height: 41 + + text: ServerConfiguringProgressLogic.labelServerBusyText + visible: ServerConfiguringProgressLogic.labelServerBusyVisible + } + LabelType { anchors.bottom: pr.top anchors.bottomMargin: 20 @@ -40,14 +55,27 @@ PageBase { visible: ServerConfiguringProgressLogic.labelWaitInfoVisible } - ProgressBar { - id: pr + + BlueButtonType { + id: pb_cancel + z: 1 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: logo.bottom anchors.bottomMargin: 40 - width: parent.width - 40 + width: root.width - 60 height: 40 + text: qsTr("Cancel") + visible: ServerConfiguringProgressLogic.pushButtonCancelVisible + enabled: ServerConfiguringProgressLogic.pushButtonCancelVisible + onClicked: { + ServerConfiguringProgressLogic.onPushButtonCancelClicked() + } + } + ProgressBar { + id: pr + enabled: ServerConfiguringProgressLogic.pageEnabled + anchors.fill: pb_cancel from: 0 to: ServerConfiguringProgressLogic.progressBarMaximium value: ServerConfiguringProgressLogic.progressBarValue diff --git a/client/ui/qml/Pages/PageServerContainers.qml b/client/ui/qml/Pages/PageServerContainers.qml index 9ce89a8b..ddd607b0 100644 --- a/client/ui/qml/Pages/PageServerContainers.qml +++ b/client/ui/qml/Pages/PageServerContainers.qml @@ -1,7 +1,7 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Dialogs 1.1 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import Qt.labs.platform +import QtQuick.Layouts import SortFilterProxyModel 0.2 import ContainerProps 1.0 import ProtocolProps 1.0 @@ -46,7 +46,7 @@ PageBase { pageLoader.focus = true } - onContainerSelected: { + onContainerSelected: function(c_index) { var containerProto = ContainerProps.defaultProtocol(c_index) @@ -55,7 +55,6 @@ PageBase { tf_port_num.text = qsTr("Default") } else tf_port_num.text = ProtocolProps.defaultPort(containerProto) - cb_port_proto.currentIndex = ProtocolProps.defaultTransportProto(containerProto) tf_port_num.enabled = ProtocolProps.defaultPortChangeable(containerProto) @@ -190,12 +189,12 @@ PageBase { left: parent.left; right: parent.right; } - topPadding: 20 spacing: 10 Caption { id: cap1 text: qsTr("Installed Protocols and Services") + leftPadding: -20 font.pixelSize: 20 } @@ -297,23 +296,24 @@ PageBase { implicitHeight: 30 checked: default_role - - MessageDialog { - id: dialogRemove - standardButtons: StandardButton.Yes | StandardButton.Cancel - title: "AmneziaVPN" - text: qsTr("Remove container") + " " + name_role + "?" + "\n" + qsTr("This action will erase all data of this container on the server.") - onAccepted: { - tb_c.currentIndex = -1 - ServerContainersLogic.onPushButtonRemoveClicked(proxyContainersModel.mapToSource(index)) - } - } - - onClicked: dialogRemove.open() + onClicked: popupRemove.open() VisibleBehavior on visible { } } + PopupWithQuestion { + id: popupRemove + questionText: qsTr("Remove container") + " " + name_role + "?" + "\n" + qsTr("This action will erase all data of this container on the server.") + yesFunc: function() { + tb_c.currentIndex = -1 + ServerContainersLogic.onPushButtonRemoveClicked(proxyContainersModel.mapToSource(index)) + close() + } + noFunc: function() { + close() + } + } + ImageButtonType { id: button_share visible: (index === tb_c.currentIndex) && ServerContainersLogic.isManagedServer @@ -418,7 +418,7 @@ PageBase { BlueButtonType { id: pb_add_container - visible: container_selector.selectedIndex < 0 + visible: container_selector.selectedIndex < 0 && ServerContainersLogic.isManagedServer anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom @@ -430,6 +430,5 @@ PageBase { text: qsTr("Install new service") font.pixelSize: 16 onClicked: container_selector.visible ? container_selector.close() : container_selector.open() - } } diff --git a/client/ui/qml/Pages/PageServerList.qml b/client/ui/qml/Pages/PageServerList.qml index 9ed7040a..80ac9a1b 100644 --- a/client/ui/qml/Pages/PageServerList.qml +++ b/client/ui/qml/Pages/PageServerList.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtGraphicalEffects 1.12 +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes 1.4 import PageEnum 1.0 import "../Controls" import "./" @@ -69,26 +69,29 @@ PageBase { mouseExitAni.start() } } - LinearGradient { - visible: !ms.containsMouse + Rectangle { anchors.fill: parent - start: Qt.point(0, 0) - end: Qt.point(0, height) - gradient: Gradient { - GradientStop { position: 0.0; color: "#FAFBFE" } - GradientStop { position: 1.0; color: "#ECEEFF" } - } - } - LinearGradient { - visible: ms.containsMouse - anchors.fill: parent - start: Qt.point(0, 0) - end: Qt.point(0, height) - gradient: Gradient { - GradientStop { position: 0.0; color: "#FAFBFE" } - GradientStop { position: 1.0; color: "#DCDEDF" } + gradient: ms.containsMouse ? gradient_containsMouse : gradient_notContainsMouse + LinearGradient { + id: gradient_notContainsMouse + x1: 0 ; y1:0 + x2: 0 ; y2: height + stops: [ + GradientStop { position: 0.0; color: "#FAFBFE" }, + GradientStop { position: 1.0; color: "#ECEEFF" } + ] + } + LinearGradient { + id: gradient_containsMouse + x1: 0 ; y1:0 + x2: 0 ; y2: height + stops: [ + GradientStop { position: 0.0; color: "#FAFBFE" }, + GradientStop { position: 1.0; color: "#DCDEDF" } + ] } } + LabelType { id: label_address x: 20 diff --git a/client/ui/qml/Pages/PageServerSettings.qml b/client/ui/qml/Pages/PageServerSettings.qml index 8ce01d21..60459955 100644 --- a/client/ui/qml/Pages/PageServerSettings.qml +++ b/client/ui/qml/Pages/PageServerSettings.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" @@ -74,6 +74,7 @@ PageBase { UiLogic.goToPage(PageEnum.ServerContainers) } } + BlueButtonType { Layout.fillWidth: true Layout.topMargin: 10 @@ -86,35 +87,47 @@ PageBase { BlueButtonType { Layout.fillWidth: true - Layout.topMargin: 60 - text: ServerSettingsLogic.pushButtonClearText - visible: ServerSettingsLogic.pushButtonClearVisible + Layout.topMargin: 10 + text: qsTr("Advanced server settings") + visible: ServerSettingsLogic.pushButtonShareFullVisible //todo onClicked: { - ServerSettingsLogic.onPushButtonClearServer() + UiLogic.goToPage(PageEnum.AdvancedServerSettings) } } + BlueButtonType { Layout.fillWidth: true - Layout.topMargin: 10 + Layout.topMargin: 60 text: ServerSettingsLogic.pushButtonClearClientCacheText visible: ServerSettingsLogic.pushButtonClearClientCacheVisible onClicked: { ServerSettingsLogic.onPushButtonClearClientCacheClicked() } } + BlueButtonType { Layout.fillWidth: true Layout.topMargin: 10 text: qsTr("Forget this server") onClicked: { - ServerSettingsLogic.onPushButtonForgetServer() + popupForgetServer.open() } } + PopupWithQuestion { + id: popupForgetServer + questionText: "Attention! This action will not remove the container on the server, it will only remove the container information from the application. Continue?" + yesFunc: function() { + ServerSettingsLogic.onPushButtonForgetServer() + close() + } + noFunc: function() { + close() + } + } } } - Logo { id : logo anchors.bottom: parent.bottom diff --git a/client/ui/qml/Pages/PageSetupWizard.qml b/client/ui/qml/Pages/PageSetupWizard.qml index a44cec0b..a7e93a3a 100644 --- a/client/ui/qml/Pages/PageSetupWizard.qml +++ b/client/ui/qml/Pages/PageSetupWizard.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" diff --git a/client/ui/qml/Pages/PageSetupWizardHighLevel.qml b/client/ui/qml/Pages/PageSetupWizardHighLevel.qml index 2f4ed9c9..e0f194ff 100644 --- a/client/ui/qml/Pages/PageSetupWizardHighLevel.qml +++ b/client/ui/qml/Pages/PageSetupWizardHighLevel.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" @@ -54,12 +54,12 @@ You SHOULD set this website address to some foreign website which is not blocked text: WizardLogic.lineEditHighWebsiteMaskingText onEditingFinished: { let _text = website_masking.text - _text.replace("http://", ""); - _text.replace("https://", ""); + _text = _text.replace("http://", ""); + _text = _text.replace("https://", ""); if (!_text) { return } - _text = _text.split("/").first(); + _text = _text.split("/")[0]; WizardLogic.lineEditHighWebsiteMaskingText = _text } onAccepted: { diff --git a/client/ui/qml/Pages/PageSetupWizardLowLevel.qml b/client/ui/qml/Pages/PageSetupWizardLowLevel.qml index 8fe693a7..d4e590c1 100644 --- a/client/ui/qml/Pages/PageSetupWizardLowLevel.qml +++ b/client/ui/qml/Pages/PageSetupWizardLowLevel.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" diff --git a/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml b/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml index d86af2da..6e1a45c1 100644 --- a/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml +++ b/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" diff --git a/client/ui/qml/Pages/PageSetupWizardVPNMode.qml b/client/ui/qml/Pages/PageSetupWizardVPNMode.qml index c6d6be3c..5cc5be00 100644 --- a/client/ui/qml/Pages/PageSetupWizardVPNMode.qml +++ b/client/ui/qml/Pages/PageSetupWizardVPNMode.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" diff --git a/client/ui/qml/Pages/PageShareConnection.qml b/client/ui/qml/Pages/PageShareConnection.qml index 182dbab8..b5439b47 100644 --- a/client/ui/qml/Pages/PageShareConnection.qml +++ b/client/ui/qml/Pages/PageShareConnection.qml @@ -1,8 +1,7 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Dialogs 1.1 -import QtQuick.Layouts 1.15 -import QtGraphicalEffects 1.12 +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Layouts import SortFilterProxyModel 0.2 import ContainerProps 1.0 import ProtocolProps 1.0 diff --git a/client/ui/qml/Pages/PageSites.qml b/client/ui/qml/Pages/PageSites.qml index f558586b..673e7e7a 100644 --- a/client/ui/qml/Pages/PageSites.qml +++ b/client/ui/qml/Pages/PageSites.qml @@ -1,8 +1,8 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQml.Models 2.15 -import Qt.labs.platform 1.0 -import QtQuick.Dialogs 1.0 +import QtQuick +import QtQuick.Controls +import QtQml.Models +import Qt.labs.platform +import QtQuick.Dialogs import PageEnum 1.0 import "./" import "../Controls" @@ -101,7 +101,7 @@ PageBase { id: fileDialog title: qsTr("Import IP addresses") visible: false - folder: StandardPaths.writableLocation(StandardPaths.DocumentsLocation) + currentFolder: StandardPaths.writableLocation(StandardPaths.DocumentsLocation) onAccepted: { SitesLogic.onPushButtonSitesImportClicked(fileUrl) } diff --git a/client/ui/qml/Pages/PageStart.qml b/client/ui/qml/Pages/PageStart.qml index d5c29a9a..eacb607d 100644 --- a/client/ui/qml/Pages/PageStart.qml +++ b/client/ui/qml/Pages/PageStart.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import PageEnum 1.0 import "./" import "../Controls" @@ -15,6 +15,23 @@ PageBase { visible: pageLoader.depth > 1 } + ImageButtonType { + anchors { + right: parent.right + top: parent.top + } + + width: 41 + height: 41 + imgMarginHover: 8 + imgMargin: 9 + icon.source: "qrc:/images/settings_grey.png" + visible: !GeneralSettingsLogic.existsAnyServer + onClicked: { + UiLogic.goToPage(PageEnum.GeneralSettings) + } + } + Caption { id: caption text: start_switch_page.checked ? @@ -134,10 +151,10 @@ PageBase { text: qsTr("Scan QR code") onClicked: { - if (Qt.platform.os == "ios") { + if (Qt.platform.os === "ios") { UiLogic.goToPage(PageEnum.QrDecoderIos) } else { - UiLogic.goToPage(PageEnum.QrDecoder) + StartPageLogic.startQrDecoder() } } enabled: StartPageLogic.pushButtonConnectEnabled @@ -148,7 +165,7 @@ PageBase { anchors.horizontalCenter: parent.horizontalCenter anchors.top: qr_code_import.bottom anchors.topMargin: 30 - visible: UiLogic.pagesStackDepth == 1 + visible: UiLogic.pagesStackDepth === 1 enabled: StartPageLogic.pushButtonConnectEnabled text: qsTr("Restore app config") @@ -212,8 +229,8 @@ PageBase { StartPageLogic.lineEditIpText = text } - validator: RegExpValidator { - regExp: StartPageLogic.ipAddressPortRegex + validator: RegularExpressionValidator { + regularExpression: StartPageLogic.ipAddressPortRegex } } diff --git a/client/ui/qml/Pages/PageVPN.qml b/client/ui/qml/Pages/PageVPN.qml index b9f71e57..342fb129 100644 --- a/client/ui/qml/Pages/PageVPN.qml +++ b/client/ui/qml/Pages/PageVPN.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" @@ -31,6 +31,7 @@ PageBase { } UrlButtonType { + id: button_donate y: 10 anchors.horizontalCenter: parent.horizontalCenter height: 21 @@ -55,6 +56,21 @@ PageBase { } } + LabelType { + id: lb_log_enabled + anchors.top: button_donate.bottom + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width + height: 21 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + text: "Logging enabled!" + color: "#D4D4D4" + + visible: VpnLogic.labelLogEnabledVisible + } + AnimatedImage { id: connect_anim source: "qrc:/images/animation.gif" diff --git a/client/ui/qml/Pages/PageViewConfig.qml b/client/ui/qml/Pages/PageViewConfig.qml index bdf729aa..24ccda45 100644 --- a/client/ui/qml/Pages/PageViewConfig.qml +++ b/client/ui/qml/Pages/PageViewConfig.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import "./" import "../Controls" diff --git a/client/ui/qml/Pages/Protocols/PageProtoCloak.qml b/client/ui/qml/Pages/Protocols/PageProtoCloak.qml index c98aa6e7..bbccbad3 100644 --- a/client/ui/qml/Pages/Protocols/PageProtoCloak.qml +++ b/client/ui/qml/Pages/Protocols/PageProtoCloak.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ProtocolEnum 1.0 import "../" import "../../Controls" @@ -11,9 +11,9 @@ PageProtocolBase { protocol: ProtocolEnum.Cloak logic: UiLogic.protocolLogic(protocol) - enabled: logic.pageEnabled BackButton { id: back + enabled: !logic.pushButtonCancelVisible } Caption { @@ -110,17 +110,24 @@ PageProtocolBase { } } + LabelType { + id: label_server_busy + horizontalAlignment: Text.AlignHCenter + Layout.maximumWidth: parent.width + Layout.fillWidth: true + visible: logic.labelServerBusyVisible + text: logic.labelServerBusyText + } + LabelType { id: label_proto_cloak_info - x: 30 - anchors.bottom: pb_save.top - anchors.bottomMargin: 10 - width: parent.width - 40 + horizontalAlignment: Text.AlignHCenter + Layout.maximumWidth: parent.width + Layout.fillWidth: true visible: logic.labelInfoVisible text: logic.labelInfoText } - ProgressBar { id: progressBar_proto_cloak_reset anchors.horizontalCenter: parent.horizontalCenter @@ -146,6 +153,19 @@ PageProtocolBase { } } visible: logic.progressBarResetVisible + LabelType { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: logic.progressBarText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Lato" + font.styleName: "normal" + font.pixelSize: 16 + color: "#D4D4D4" + visible: logic.progressBarTextVisible + } } BlueButtonType { id: pb_save @@ -162,4 +182,13 @@ PageProtocolBase { } } + BlueButtonType { + anchors.fill: pb_save + text: qsTr("Cancel") + visible: logic.pushButtonCancelVisible + enabled: logic.pushButtonCancelVisible + onClicked: { + logic.onPushButtonCancelClicked() + } + } } diff --git a/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml b/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml index 35d0f250..590501d5 100644 --- a/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml +++ b/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ProtocolEnum 1.0 import "../" import "../../Controls" @@ -13,7 +13,9 @@ PageProtocolBase { BackButton { id: back + enabled: !logic.pushButtonCancelVisible } + Caption { id: caption text: qsTr("OpenVPN Settings") @@ -33,16 +35,17 @@ PageProtocolBase { ColumnLayout { visible: !logic.isThirdPartyConfig - enabled: logic.pageEnabled LabelType { id: lb_subnet + enabled: logic.pageEnabled height: 21 text: qsTr("VPN Addresses Subnet") } + TextFieldType { id: tf_subnet - + enabled: logic.pageEnabled implicitWidth: parent.width height: 31 text: logic.lineEditSubnetText @@ -51,15 +54,17 @@ PageProtocolBase { } } - // LabelType { id: lb_proto + enabled: logic.pageEnabled Layout.topMargin: 20 height: 21 text: qsTr("Network protocol") } + Rectangle { id: rect_proto + enabled: logic.pageEnabled implicitWidth: parent.width height: 71 border.width: 1 @@ -91,8 +96,8 @@ PageProtocolBase { } } - // RowLayout { + enabled: logic.pageEnabled Layout.topMargin: 10 Layout.fillWidth: true LabelType { @@ -114,12 +119,9 @@ PageProtocolBase { } } - - - // CheckBoxType { id: check_auto_enc - + enabled: logic.pageEnabled implicitWidth: parent.width height: 21 text: qsTr("Auto-negotiate encryption") @@ -132,15 +134,16 @@ PageProtocolBase { } } - // LabelType { id: lb_cipher + enabled: logic.pageEnabled height: 21 text: qsTr("Cipher") } ComboBoxType { id: cb_cipher + enabled: logic.pageEnabled && !check_auto_enc.checked implicitWidth: parent.width height: 31 @@ -167,18 +170,19 @@ PageProtocolBase { onCurrentTextChanged: { logic.comboBoxVpnCipherText = currentText } - enabled: !check_auto_enc.checked } - // LabelType { id: lb_hash + enabled: logic.pageEnabled height: 21 Layout.topMargin: 20 text: qsTr("Hash") } + ComboBoxType { id: cb_hash + enabled: logic.pageEnabled && !check_auto_enc.checked height: 31 implicitWidth: parent.width model: [ @@ -204,11 +208,11 @@ PageProtocolBase { onCurrentTextChanged: { logic.comboBoxVpnHashText = currentText } - enabled: !check_auto_enc.checked } CheckBoxType { id: check_tls + enabled: logic.pageEnabled implicitWidth: parent.width Layout.topMargin: 20 height: 21 @@ -222,6 +226,7 @@ PageProtocolBase { CheckBoxType { id: check_block_dns + enabled: logic.pageEnabled implicitWidth: parent.width height: 21 text: qsTr("Block DNS requests outside of VPN") @@ -231,10 +236,9 @@ PageProtocolBase { } } - BasicButtonType { id: pb_client_config - + enabled: logic.pageEnabled implicitWidth: parent.width height: 21 text: qsTr("Additional client config commands →") @@ -259,6 +263,7 @@ PageProtocolBase { Rectangle { id: rect_client_conf + enabled: logic.pageEnabled implicitWidth: root.width - 60 height: 101 border.width: 1 @@ -280,14 +285,11 @@ PageProtocolBase { } } } - - } - BasicButtonType { id: pb_server_config - + enabled: logic.pageEnabled implicitWidth: parent.width height: 21 text: qsTr("Additional server config commands →") @@ -312,6 +314,7 @@ PageProtocolBase { Rectangle { id: rect_server_conf + enabled: logic.pageEnabled implicitWidth: root.width - 60 height: 101 border.width: 1 @@ -333,13 +336,24 @@ PageProtocolBase { } } } + } - + LabelType { + id: label_server_busy + enabled: logic.pageEnabled + horizontalAlignment: Text.AlignHCenter + Layout.maximumWidth: parent.width + Layout.fillWidth: true + visible: logic.labelServerBusyVisible + text: logic.labelServerBusyText } LabelType { id: label_proto_openvpn_info - + enabled: logic.pageEnabled + horizontalAlignment: Text.AlignHCenter + Layout.maximumWidth: parent.width + Layout.fillWidth: true height: 41 visible: logic.labelProtoOpenVpnInfoVisible text: logic.labelProtoOpenVpnInfoText @@ -353,13 +367,25 @@ PageProtocolBase { BlueButtonType { id: pb_save + enabled: logic.pageEnabled z: 1 height: 40 text: qsTr("Save and restart VPN") width: parent.width visible: logic.pushButtonSaveVisible onClicked: { - logic.onPushButtonProtoOpenVpnSaveClicked() + logic.onPushButtonSaveClicked() + } + } + + BlueButtonType { + z: 1 + anchors.fill: pb_save + text: qsTr("Cancel") + visible: logic.pushButtonCancelVisible + enabled: logic.pushButtonCancelVisible + onClicked: { + logic.onPushButtonCancelClicked() } } @@ -389,6 +415,19 @@ PageProtocolBase { } } + LabelType { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: logic.progressBarText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Lato" + font.styleName: "normal" + font.pixelSize: 16 + color: "#D4D4D4" + visible: logic.progressBarTextVisible + } } } @@ -412,5 +451,4 @@ PageProtocolBase { } } } - } diff --git a/client/ui/qml/Pages/Protocols/PageProtoSftp.qml b/client/ui/qml/Pages/Protocols/PageProtoSftp.qml index 2ad5436b..c6a0602f 100644 --- a/client/ui/qml/Pages/Protocols/PageProtoSftp.qml +++ b/client/ui/qml/Pages/Protocols/PageProtoSftp.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import ProtocolEnum 1.0 import "../" import "../../Controls" diff --git a/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml b/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml index be198ec5..160c3ef6 100644 --- a/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml +++ b/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ProtocolEnum 1.0 import "../" import "../../Controls" @@ -13,6 +13,7 @@ PageProtocolBase { BackButton { id: back + enabled: !logic.pushButtonCancelVisible } Caption { @@ -88,27 +89,33 @@ PageProtocolBase { Item { Layout.fillHeight: true } - } + LabelType { + id: label_server_busy + horizontalAlignment: Text.AlignHCenter + Layout.maximumWidth: parent.width + Layout.fillWidth: true + visible: logic.labelServerBusyVisible + text: logic.labelServerBusyText + } - LabelType { - id: label_proto_shadowsocks_info - x: 30 - anchors.bottom: pb_save.top - anchors.bottomMargin: 10 - width: parent.width - 40 - height: 41 - visible: logic.labelInfoVisible - text: logic.labelInfoText + LabelType { + id: label_proto_shadowsocks_info + horizontalAlignment: Text.AlignHCenter + Layout.maximumWidth: parent.width + Layout.fillWidth: true + visible: logic.labelInfoVisible + text: logic.labelInfoText + } } ProgressBar { id: progressBar_reset anchors.fill: pb_save from: 0 - to: logic.progressBaResetMaximium - value: logic.progressBaResetValue - visible: logic.progressBaResetVisible + to: logic.progressBarResetMaximium + value: logic.progressBarResetValue + visible: logic.progressBarResetVisible background: Rectangle { implicitWidth: parent.width implicitHeight: parent.height @@ -126,6 +133,19 @@ PageProtocolBase { color: Qt.rgba(255, 255, 255, 0.15); } } + LabelType { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: logic.progressBarText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Lato" + font.styleName: "normal" + font.pixelSize: 16 + color: "#D4D4D4" + visible: logic.progressBarTextVisible + } } BlueButtonType { @@ -142,4 +162,14 @@ PageProtocolBase { logic.onPushButtonSaveClicked() } } + + BlueButtonType { + anchors.fill: pb_save + text: qsTr("Cancel") + visible: logic.pushButtonCancelVisible + enabled: logic.pushButtonCancelVisible + onClicked: { + logic.onPushButtonCancelClicked() + } + } } diff --git a/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml b/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml index 50f4bd4e..aa4d35d4 100644 --- a/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml +++ b/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ProtocolEnum 1.0 import "../" import "../../Controls" diff --git a/client/ui/qml/Pages/Protocols/PageProtocolBase.qml b/client/ui/qml/Pages/Protocols/PageProtocolBase.qml index 3ab95af6..97a0f1eb 100644 --- a/client/ui/qml/Pages/Protocols/PageProtocolBase.qml +++ b/client/ui/qml/Pages/Protocols/PageProtocolBase.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import PageEnum 1.0 import ProtocolEnum 1.0 import "./.." diff --git a/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml b/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml index 3cc654f0..4e52e501 100644 --- a/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml +++ b/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ProtocolEnum 1.0 import "../" import "../../Controls" diff --git a/client/ui/qml/Pages/Share/PageShareProtoCloak.qml b/client/ui/qml/Pages/Share/PageShareProtoCloak.qml index ae60d21e..92bcb832 100644 --- a/client/ui/qml/Pages/Share/PageShareProtoCloak.qml +++ b/client/ui/qml/Pages/Share/PageShareProtoCloak.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ProtocolEnum 1.0 import "../" import "../../Controls" diff --git a/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml b/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml index f8b976c4..7e41650a 100644 --- a/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml +++ b/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ProtocolEnum 1.0 import "../" import "../../Controls" diff --git a/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml b/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml index 3de8a887..4246be21 100644 --- a/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml +++ b/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ProtocolEnum 1.0 import "../" import "../../Controls" diff --git a/client/ui/qml/Pages/Share/PageShareProtoSftp.qml b/client/ui/qml/Pages/Share/PageShareProtoSftp.qml index 5cb137f9..2c2c97cc 100644 --- a/client/ui/qml/Pages/Share/PageShareProtoSftp.qml +++ b/client/ui/qml/Pages/Share/PageShareProtoSftp.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import ProtocolEnum 1.0 import "../" import "../../Controls" diff --git a/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml b/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml index 09f929fc..91f5e8d2 100644 --- a/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml +++ b/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ProtocolEnum 1.0 import "../" import "../../Controls" diff --git a/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml b/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml index 35caf05b..1c68f6cb 100644 --- a/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml +++ b/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import ProtocolEnum 1.0 import "../" import "../../Controls" diff --git a/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml b/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml index fab23ed0..7265de81 100644 --- a/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml +++ b/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import ProtocolEnum 1.0 import "../" import "../../Controls" diff --git a/client/ui/qml/Pages/Share/PageShareProtocolBase.qml b/client/ui/qml/Pages/Share/PageShareProtocolBase.qml index 92659b58..603abdfa 100644 --- a/client/ui/qml/Pages/Share/PageShareProtocolBase.qml +++ b/client/ui/qml/Pages/Share/PageShareProtocolBase.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import PageEnum 1.0 import ProtocolEnum 1.0 import "./.." diff --git a/client/ui/qml/TitleBar.qml b/client/ui/qml/TitleBar.qml deleted file mode 100644 index deb9e1a0..00000000 --- a/client/ui/qml/TitleBar.qml +++ /dev/null @@ -1,38 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import "./" -import "Config" - -Rectangle { - id: root - color: "#F5F5F5" - width: GC.screenWidth - height: 30 - signal closeButtonClicked() - - Button { - id: closeButton - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: 5 - icon.source: "qrc:/images/close.png" - icon.width: 16 - icon.height: 16 - width: height - height: 20 - background: Item {} - contentItem: Image { - source: closeButton.icon.source - anchors.fill: closeButton - anchors.margins: ms.containsMouse ? 3 : 4 - } - MouseArea { - id: ms - hoverEnabled: true - anchors.fill: closeButton - } - onClicked: { - root.closeButtonClicked() - } - } -} diff --git a/client/ui/qml/main.qml b/client/ui/qml/main.qml index 11b9d26a..d91c013f 100644 --- a/client/ui/qml/main.qml +++ b/client/ui/qml/main.qml @@ -1,14 +1,13 @@ -import QtQuick 2.14 -import QtQuick.Window 2.14 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 -import QtQuick.Controls.Material 2.12 +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Layouts import PageEnum 1.0 import PageType 1.0 -import Qt.labs.platform 1.1 -import Qt.labs.folderlistmodel 2.12 -import QtQuick.Dialogs 1.1 -import "./" +import Qt.labs.platform +import Qt.labs.folderlistmodel +import QtQuick.Dialogs +import QtQuick.Controls.Basic import "Controls" import "Pages" import "Pages/Protocols" @@ -23,10 +22,10 @@ Window { id: root visible: true width: GC.screenWidth - height: GC.isDesktop() ? GC.screenHeight + titleBar.height : GC.screenHeight - minimumWidth: 360 + height: GC.screenHeight + minimumWidth: GC.isDesktop() ? 360 : 0 minimumHeight: GC.isDesktop() ? 640 : 0 - onClosing: { + onClosing: function() { console.debug("QML onClosing signal") UiLogic.onCloseWindow() } @@ -62,8 +61,12 @@ Window { function close_page() { if (pageLoader.depth <= 1) { + if (GC.isMobile()) { + root.close() + } return } + pageLoader.currentItem.deactivated() pageLoader.pop() } @@ -85,51 +88,27 @@ Window { } } - TitleBar { - id: titleBar - anchors.top: root.top - visible: GC.isDesktop() - DragHandler { - grabPermissions: TapHandler.CanTakeOverFromAnything - onActiveChanged: { - if (active) { - root.startSystemMove(); - } - } - target: null - } - onCloseButtonClicked: { - if (UiLogic.currentPageValue === PageEnum.Start || - UiLogic.currentPageValue === PageEnum.NewServer) { - Qt.quit() - } else { - root.hide() - } - } - } - Rectangle { - y: GC.isDesktop() ? titleBar.height : 0 + y: 0 anchors.fill: parent color: "white" } StackView { id: pageLoader - y: GC.isDesktop() ? titleBar.height : 0 + y: 0 anchors.fill: parent focus: true - onCurrentItemChanged: { - //console.debug("QML onCurrentItemChanged " + pageLoader.currentItem) + onCurrentItemChanged: function() { UiLogic.currentPageValue = currentItem.page } - onDepthChanged: { + onDepthChanged: function() { UiLogic.pagesStackDepth = depth } - Keys.onPressed: { + Keys.onPressed: function(event) { UiLogic.keyPressEvent(event.key) event.accepted = true } @@ -199,7 +178,7 @@ Window { sharePages[obj.protocol] = obj } - //console.debug("Created compenent " + component.url + " for " + type); +// console.debug("Created compenent " + component.url + " for " + type); } } else if (component.status === Component.Error) { console.debug("Error loading component:", component.errorString()); @@ -209,7 +188,7 @@ Window { if (c.status === Component.Ready) finishCreation(c); else { - console.debug("Warning: Pages components are not ready"); + console.debug("Warning: " + file + " page components are not ready " + c.errorString()); } } @@ -255,37 +234,22 @@ Window { function onToggleLogPanel() { drawer_log.visible = !drawer_log.visible } + function onShowWarningMessage(message) { + popupWarning.popupWarningText = message + popupWarning.open() + } } - MessageDialog { - id: closePrompt -// x: (root.width - width) / 2 -// y: (root.height - height) / 2 - title: qsTr("Exit") - text: qsTr("Do you really want to quit?") - standardButtons: StandardButton.Yes | StandardButton.No - onYes: { - Qt.quit() - } - visible: false - } MessageDialog { id: publicKeyWarning title: "AmneziaVPN" text: qsTr("It's public key. Private key required") visible: false } - MessageDialog { - id: connectErrorDialog - title: "AmneziaVPN" - text: UiLogic.dialogConnectErrorText - visible: false - } Drawer { id: drawer_log - z: -3 y: 0 x: 0 edge: Qt.BottomEdge @@ -391,4 +355,8 @@ Window { } } } + + PopupWarning { + id: popupWarning + } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index e5b1e533..683213e5 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -3,20 +3,19 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #include #include #include #include #include +#include #include "amnezia_application.h" @@ -34,10 +33,10 @@ #include "ui/qautostart.h" -#include "debug.h" +#include "logger.h" #include "defines.h" #include "uilogic.h" -#include "utils.h" +#include "utilities.h" #include "vpnconnection.h" #include @@ -66,6 +65,7 @@ #include "pages_logic/ViewConfigLogic.h" #include "pages_logic/VpnLogic.h" #include "pages_logic/WizardLogic.h" +#include "pages_logic/AdvancedServerSettingsLogic.h" #include "pages_logic/protocols/CloakLogic.h" #include "pages_logic/protocols/OpenVpnLogic.h" @@ -132,6 +132,7 @@ void UiLogic::initalizeUiLogic() connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) { if (connected) { pageLogic()->onConnectionStateChanged(VpnProtocol::Connected); + m_vpnConnection->restoreConnection(); } }); if (!AndroidController::instance()->initialize(pageLogic())) { @@ -156,7 +157,7 @@ void UiLogic::initalizeUiLogic() emit goToPage(Page::Start, true, false); } - selectedServerIndex = m_settings->defaultServerIndex(); + m_selectedServerIndex = m_settings->defaultServerIndex(); qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME).arg(APP_VERSION); qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName()).arg(QSysInfo::currentCpuArchitecture()); @@ -189,17 +190,17 @@ void UiLogic::keyPressEvent(Qt::Key key) case Qt::Key_AsciiTilde: case Qt::Key_QuoteLeft: emit toggleLogPanel(); break; - case Qt::Key_L: Debug::openLogsFolder(); + case Qt::Key_L: Logger::openLogsFolder(); break; - case Qt::Key_K: Debug::openServiceLogsFolder(); + case Qt::Key_K: Logger::openServiceLogsFolder(); break; #ifdef QT_DEBUG case Qt::Key_Q: qApp->quit(); break; case Qt::Key_H: - selectedServerIndex = m_settings->defaultServerIndex(); - selectedDockerContainer = m_settings->defaultContainer(selectedServerIndex); + m_selectedServerIndex = m_settings->defaultServerIndex(); + m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); //updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); emit goToPage(Page::ShareConnection); @@ -213,7 +214,7 @@ void UiLogic::keyPressEvent(Qt::Key key) emit goToPage(Page::Start); break; case Qt::Key_S: - selectedServerIndex = m_settings->defaultServerIndex(); + m_selectedServerIndex = m_settings->defaultServerIndex(); emit goToPage(Page::ServerSettings); break; case Qt::Key_P: @@ -223,9 +224,10 @@ void UiLogic::keyPressEvent(Qt::Key key) m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex())); break; case Qt::Key_Escape: - case Qt::Key_Back: if (currentPage() == Page::Vpn) break; if (currentPage() == Page::ServerConfiguringProgress) break; + case Qt::Key_Back: + // if (currentPage() == Page::Start && pagesStack.size() < 2) break; // if (currentPage() == Page::Sites && // ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { @@ -242,10 +244,16 @@ void UiLogic::keyPressEvent(Qt::Key key) void UiLogic::onCloseWindow() { - if (m_settings->serversCount() == 0) qApp->quit(); - else { - hide(); +#ifdef Q_OS_ANDROID + qApp->quit(); +#else + if (m_settings->serversCount() == 0) + { + qApp->quit(); + } else { + emit hide(); } +#endif } QString UiLogic::containerName(int container) @@ -261,285 +269,114 @@ QString UiLogic::containerDesc(int container) void UiLogic::onGotoCurrentProtocolsPage() { - selectedServerIndex = m_settings->defaultServerIndex(); - selectedDockerContainer = m_settings->defaultContainer(selectedServerIndex); + m_selectedServerIndex = m_settings->defaultServerIndex(); + m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); emit goToPage(Page::ServerContainers); } -//void UiLogic::showEvent(QShowEvent *event) -//{ -//#if defined Q_OS_MACX -// if (!event->spontaneous()) { -// setDockIconVisible(true); -// } -// if (needToHideCustomTitlebar) { -// ui->widget_tittlebar->hide(); -// resize(width(), 640); -// ui->stackedWidget_main->move(0,0); -// } -//#endif -//} - -//void UiLogic::hideEvent(QHideEvent *event) -//{ -//#if defined Q_OS_MACX -// if (!event->spontaneous()) { -// setDockIconVisible(false); -// } -//#endif -//} - - - - -void UiLogic::installServer(QMap &containers) +void UiLogic::installServer(QPair &container) { - if (containers.isEmpty()) return; - emit goToPage(Page::ServerConfiguringProgress); QEventLoop loop; QTimer::singleShot(500, &loop, SLOT(quit())); loop.exec(); qApp->processEvents(); - PageFunc page_new_server_configuring; - page_new_server_configuring.setEnabledFunc = [this] (bool enabled) -> void { + ServerConfiguringProgressLogic::PageFunc pageFunc; + pageFunc.setEnabledFunc = [this] (bool enabled) -> void { pageLogic()->set_pageEnabled(enabled); }; - ButtonFunc no_button; - LabelFunc label_new_server_configuring_wait_info; - label_new_server_configuring_wait_info.setTextFunc = [this] (const QString& text) -> void { + + ServerConfiguringProgressLogic::ButtonFunc noButton; + + ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; + waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { pageLogic()->set_labelWaitInfoText(text); }; - label_new_server_configuring_wait_info.setVisibleFunc = [this] (bool visible) ->void { + waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { pageLogic()->set_labelWaitInfoVisible(visible); }; - ProgressFunc progressBar_new_server_configuring; - progressBar_new_server_configuring.setVisibleFunc = [this] (bool visible) ->void { + + ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; + progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { pageLogic()->set_progressBarVisible(visible); }; - progressBar_new_server_configuring.setValueFunc = [this] (int value) ->void { + progressBarFunc.setValueFunc = [this] (int value) -> void { pageLogic()->set_progressBarValue(value); }; - progressBar_new_server_configuring.getValueFunc = [this] (void) -> int { + progressBarFunc.getValueFunc = [this] (void) -> int { return pageLogic()->progressBarValue(); }; - progressBar_new_server_configuring.getMaximiumFunc = [this] (void) -> int { + progressBarFunc.getMaximiumFunc = [this] (void) -> int { return pageLogic()->progressBarMaximium(); }; - progressBar_new_server_configuring.setTextVisibleFunc = [this] (bool visible) ->void { + progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { pageLogic()->set_progressBarTextVisible(visible); }; - progressBar_new_server_configuring.setTextFunc = [this] (const QString& text) ->void { + progressBarFunc.setTextFunc = [this] (const QString& text) -> void { pageLogic()->set_progressBarText(text); }; - bool ok = installContainers(installCredentials, containers, - page_new_server_configuring, - progressBar_new_server_configuring, - no_button, - label_new_server_configuring_wait_info); - if (ok) { - QJsonObject server; - server.insert(config_key::hostName, installCredentials.hostName); - server.insert(config_key::userName, installCredentials.userName); - server.insert(config_key::password, installCredentials.password); - server.insert(config_key::port, installCredentials.port); - server.insert(config_key::description, m_settings->nextAvailableServerName()); + ServerConfiguringProgressLogic::LabelFunc busyInfoFunc; + busyInfoFunc.setTextFunc = [this] (const QString& text) -> void { + pageLogic()->set_labelServerBusyText(text); + }; + busyInfoFunc.setVisibleFunc = [this] (bool visible) -> void { + pageLogic()->set_labelServerBusyVisible(visible); + }; - QJsonArray containerConfigs; - for (const QJsonObject &cfg : containers) { - containerConfigs.append(cfg); - } - server.insert(config_key::containers, containerConfigs); - server.insert(config_key::defaultContainer, ContainerProps::containerToString(containers.firstKey())); + ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; + cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { + pageLogic()->set_pushButtonCancelVisible(visible); + }; - m_settings->addServer(server); - m_settings->setDefaultServer(m_settings->serversCount() - 1); - onUpdateAllPages(); + bool isServerCreated = false; + ErrorCode errorCode = addAlreadyInstalledContainersGui(isServerCreated); + if (errorCode == ErrorCode::NoError) { + if (!isContainerAlreadyAddedToGui(container.first)) { + progressBarFunc.setTextFunc(QString("Installing %1").arg(ContainerProps::containerToString(container.first))); + auto installAction = [&] () { + return m_serverController->setupContainer(m_installCredentials, container.first, container.second); + }; + errorCode = pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, + noButton, waitInfoFunc, + busyInfoFunc, cancelButtonFunc); + if (errorCode == ErrorCode::NoError) { + if (!isServerCreated) { + QJsonObject server; + server.insert(config_key::hostName, m_installCredentials.hostName); + server.insert(config_key::userName, m_installCredentials.userName); + server.insert(config_key::password, m_installCredentials.password); + server.insert(config_key::port, m_installCredentials.port); + server.insert(config_key::description, m_settings->nextAvailableServerName()); - emit setStartPage(Page::Vpn); - qApp->processEvents(); - } - else { - emit closePage(); - } -} + server.insert(config_key::containers, QJsonArray{container.second}); + server.insert(config_key::defaultContainer, ContainerProps::containerToString(container.first)); -bool UiLogic::installContainers(ServerCredentials credentials, - QMap &containers, - const PageFunc &page, - const ProgressFunc &progress, - const ButtonFunc &button, - const LabelFunc &info) -{ - if (!progress.setValueFunc) return false; - - if (page.setEnabledFunc) { - page.setEnabledFunc(false); - } - if (button.setVisibleFunc) { - button.setVisibleFunc(false); - } - - if (info.setVisibleFunc) { - info.setVisibleFunc(true); - } - if (info.setTextFunc) { - info.setTextFunc(tr("Please wait, configuring process may take up to 5 minutes")); - } - - int cnt = 0; - for (QMap::iterator i = containers.begin(); i != containers.end(); i++, cnt++) { - QTimer timer; - connect(&timer, &QTimer::timeout, [progress](){ - progress.setValueFunc(progress.getValueFunc() + 1); - }); - - progress.setValueFunc(0); - timer.start(1000); - - progress.setTextVisibleFunc(true); - progress.setTextFunc(QString("Installing %1 %2 %3").arg(cnt+1).arg(tr("of")).arg(containers.size())); - - ErrorCode e = m_serverController->setupContainer(credentials, i.key(), i.value()); - qDebug() << "Setup server finished with code" << e; - m_serverController->disconnectFromHost(credentials); - - if (e) { - if (page.setEnabledFunc) { - page.setEnabledFunc(true); - } - if (button.setVisibleFunc) { - button.setVisibleFunc(true); - } - if (info.setVisibleFunc) { - info.setVisibleFunc(false); - } - - QMessageBox::warning(nullptr, APPLICATION_NAME, - tr("Error occurred while configuring server.") + "\n" + - errorString(e)); - - return false; - } - - // just ui progressbar tweak - timer.stop(); - - int remaining_val = progress.getMaximiumFunc() - progress.getValueFunc(); - - if (remaining_val > 0) { - QTimer timer1; - QEventLoop loop1; - - connect(&timer1, &QTimer::timeout, [&](){ - progress.setValueFunc(progress.getValueFunc() + 1); - if (progress.getValueFunc() >= progress.getMaximiumFunc()) { - loop1.quit(); + m_settings->addServer(server); + m_settings->setDefaultServer(m_settings->serversCount() - 1); + } else { + m_settings->setContainerConfig(m_settings->serversCount() - 1, container.first, container.second); + m_settings->setDefaultContainer(m_settings->serversCount() - 1, container.first); } - }); + onUpdateAllPages(); - timer1.start(5); - loop1.exec(); - } - } - - - if (button.setVisibleFunc) { - button.setVisibleFunc(true); - } - if (page.setEnabledFunc) { - page.setEnabledFunc(true); - } - if (info.setTextFunc) { - info.setTextFunc(tr("Amnezia server installed")); - } - - return true; -} - -ErrorCode UiLogic::doInstallAction(const std::function &action, - const PageFunc &page, - const ProgressFunc &progress, - const ButtonFunc &button, - const LabelFunc &info) -{ - progress.setVisibleFunc(true); - if (page.setEnabledFunc) { - page.setEnabledFunc(false); - } - if (button.setVisibleFunc) { - button.setVisibleFunc(false); - } - if (info.setVisibleFunc) { - info.setVisibleFunc(true); - } - if (info.setTextFunc) { - info.setTextFunc(tr("Please wait, configuring process may take up to 5 minutes")); - } - - QTimer timer; - connect(&timer, &QTimer::timeout, [progress](){ - progress.setValueFunc(progress.getValueFunc() + 1); - }); - - progress.setValueFunc(0); - timer.start(1000); - - ErrorCode e = action(); - qDebug() << "doInstallAction finished with code" << e; - - if (e) { - if (page.setEnabledFunc) { - page.setEnabledFunc(true); - } - if (button.setVisibleFunc) { - button.setVisibleFunc(true); - } - if (info.setVisibleFunc) { - info.setVisibleFunc(false); - } - QMessageBox::warning(nullptr, APPLICATION_NAME, - tr("Error occurred while configuring server.") + "\n" + - errorString(e)); - - progress.setVisibleFunc(false); - return e; - } - - // just ui progressbar tweak - timer.stop(); - - int remaining_val = progress.getMaximiumFunc() - progress.getValueFunc(); - - if (remaining_val > 0) { - QTimer timer1; - QEventLoop loop1; - - connect(&timer1, &QTimer::timeout, [&](){ - progress.setValueFunc(progress.getValueFunc() + 1); - if (progress.getValueFunc() >= progress.getMaximiumFunc()) { - loop1.quit(); + emit setStartPage(Page::Vpn); + qApp->processEvents(); + return; } - }); - - timer1.start(5); - loop1.exec(); + } else { + onUpdateAllPages(); + emit showWarningMessage("Attention! The container you are trying to install is already installed on the server. " + "All installed containers have been added to the application "); + emit setStartPage(Page::Vpn); + return; + } } - - - progress.setVisibleFunc(false); - if (button.setVisibleFunc) { - button.setVisibleFunc(true); - } - if (page.setEnabledFunc) { - page.setEnabledFunc(true); - } - if (info.setTextFunc) { - info.setTextFunc(tr("Operation finished")); - } - return ErrorCode::NoError; + emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + + tr("Error message: ") + errorString(errorCode) + "\n" + + tr("See logs for details.")); + emit closePage(); } PageProtocolLogicBase *UiLogic::protocolLogic(Proto p) @@ -634,7 +471,11 @@ void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &da void UiLogic::copyToClipboard(const QString &text) { +#ifdef Q_OS_ANDROID + AndroidController::instance()->copyTextToClipboard(text); +#else qApp->clipboard()->setText(text); +#endif } void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString& data) { @@ -656,6 +497,24 @@ void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QSt MobileUtils::shareText(filesToSend); } +QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, + const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); + +#ifdef Q_OS_ANDROID + // patch for files containing spaces etc + const QString sep {"raw%3A%2F"}; + if (fileName.startsWith("content://") && fileName.contains(sep)) { + QString contentUrl = fileName.split(sep).at(0); + QString rawUrl = fileName.split(sep).at(1); + rawUrl.replace(" ", "%20"); + fileName = contentUrl + sep + rawUrl; + } +#endif + return fileName; +} + void UiLogic::registerPagesLogic() { amnApp->qmlEngine()->rootContext()->setContextProperty("UiLogic", this); @@ -675,4 +534,79 @@ void UiLogic::registerPagesLogic() registerPageLogic(); registerPageLogic(); registerPageLogic(); + registerPageLogic(); +} + +ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) +{ + isServerCreated = false; + ServerCredentials installCredentials = m_installCredentials; + bool createNewServer = true; + int serverIndex; + + for (int i = 0; i < m_settings->serversCount(); i++) { + const ServerCredentials credentials = m_settings->serverCredentials(i); + if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { + createNewServer = false; + isServerCreated = true; + installCredentials = credentials; + serverIndex = i; + break; + } + } + + QMap installedContainers; + ErrorCode errorCode = m_serverController->getAlreadyInstalledContainers(installCredentials, installedContainers); + if (errorCode != ErrorCode::NoError) { + return errorCode; + } + + if (!installedContainers.empty()) { + QJsonObject server; + QJsonArray containerConfigs; + if (createNewServer) { + server.insert(config_key::hostName, installCredentials.hostName); + server.insert(config_key::userName, installCredentials.userName); + server.insert(config_key::password, installCredentials.password); + server.insert(config_key::port, installCredentials.port); + server.insert(config_key::description, m_settings->nextAvailableServerName()); + } + + for (auto container = installedContainers.begin(); container != installedContainers.end(); container++) { + if (isContainerAlreadyAddedToGui(container.key())) { + continue; + } + + if (createNewServer) { + containerConfigs.append(container.value()); + server.insert(config_key::containers, containerConfigs); + } else { + m_settings->setContainerConfig(serverIndex, container.key(), container.value()); + m_settings->setDefaultContainer(serverIndex, installedContainers.firstKey()); + } + } + + if (createNewServer) { + server.insert(config_key::defaultContainer, ContainerProps::containerToString(installedContainers.firstKey())); + m_settings->addServer(server); + m_settings->setDefaultServer(m_settings->serversCount() - 1); + isServerCreated = true; + } + } + + return ErrorCode::NoError; +} + +bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) +{ + for (int i = 0; i < m_settings->serversCount(); i++) { + const ServerCredentials credentials = m_settings->serverCredentials(i); + if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { + const QJsonObject containerConfig = m_settings->containerConfig(i, container); + if (!containerConfig.isEmpty()) { + return true; + } + } + } + return false; } diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h index b5b6092b..4daa42a7 100644 --- a/client/ui/uilogic.h +++ b/client/ui/uilogic.h @@ -1,12 +1,13 @@ #ifndef UILOGIC_H #define UILOGIC_H -#include -#include -#include +#include #include +#include #include +#include +#include #include #include #include @@ -42,6 +43,7 @@ class StartPageLogic; class ViewConfigLogic; class VpnLogic; class WizardLogic; +class AdvancedServerSettingsLogic; class PageProtocolLogicBase; class OpenVpnLogic; @@ -52,6 +54,7 @@ class OtherProtocolsLogic; class VpnConnection; +class CreateServerTest; class UiLogic : public QObject { @@ -60,7 +63,7 @@ class UiLogic : public QObject AUTO_PROPERTY(bool, pageEnabled) AUTO_PROPERTY(int, pagesStackDepth) AUTO_PROPERTY(int, currentPageValue) - AUTO_PROPERTY(QString, dialogConnectErrorText) + AUTO_PROPERTY(QString, popupWarningText) READONLY_PROPERTY(QObject *, containersModel) READONLY_PROPERTY(QObject *, protocolsModel) @@ -87,6 +90,7 @@ public: friend class ViewConfigLogic; friend class VpnLogic; friend class WizardLogic; + friend class AdvancedServerSettingsLogic; friend class PageProtocolLogicBase; friend class OpenVpnLogic; @@ -95,6 +99,8 @@ public: friend class OtherProtocolsLogic; + friend class CreateServerTest; + Q_INVOKABLE virtual void onUpdatePage() {} // UiLogic is set as logic class for some qml pages Q_INVOKABLE void onUpdateAllPages(); @@ -112,11 +118,16 @@ public: Q_INVOKABLE void saveBinaryFile(const QString& desc, QString ext, const QString& data); Q_INVOKABLE void copyToClipboard(const QString& text); + Q_INVOKABLE amnezia::ErrorCode addAlreadyInstalledContainersGui(bool &isServerCreated); + void shareTempFile(const QString &suggestedName, QString ext, const QString& data); - + static QString getOpenFileName(QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = nullptr, + QFileDialog::Options options = QFileDialog::Options()); signals: - 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); @@ -129,45 +140,15 @@ signals: void hide(); void raise(); void toggleLogPanel(); + void showWarningMessage(QString message); private slots: // containers - INOUT arg - void installServer(QMap &containers); + void installServer(QPair &container); private: PageEnumNS::Page currentPage(); - struct ProgressFunc { - std::function setVisibleFunc; - std::function setValueFunc; - std::function getValueFunc; - std::function getMaximiumFunc; - std::function setTextVisibleFunc; - std::function setTextFunc; - }; - struct PageFunc { - std::function setEnabledFunc; - }; - struct ButtonFunc { - std::function setVisibleFunc; - }; - struct LabelFunc { - std::function setVisibleFunc; - std::function setTextFunc; - }; - - bool installContainers(ServerCredentials credentials, - QMap &containers, - const PageFunc& page, - const ProgressFunc& progress, - const ButtonFunc& button, - const LabelFunc& info); - - ErrorCode doInstallAction(const std::function &action, - const PageFunc& page, - const ProgressFunc& progress, - const ButtonFunc& button, - const LabelFunc& info); - + bool isContainerAlreadyAddedToGui(DockerContainer container); public: Q_INVOKABLE PageProtocolLogicBase *protocolLogic(Proto p); @@ -210,8 +191,8 @@ private: NotificationHandler* m_notificationHandler; - int selectedServerIndex = -1; // server index to use when proto settings page opened - DockerContainer selectedDockerContainer; // same - ServerCredentials installCredentials; // used to save cred between pages new_server and new_server_protocols and wizard + int m_selectedServerIndex = -1; // server index to use when proto settings page opened + DockerContainer m_selectedDockerContainer; // same + ServerCredentials m_installCredentials; // used to save cred between pages new_server and new_server_protocols and wizard }; #endif // UILOGIC_H diff --git a/client/utils.cpp b/client/utilities.cpp similarity index 95% rename from client/utils.cpp rename to client/utilities.cpp index 67552513..e5d2fd52 100644 --- a/client/utils.cpp +++ b/client/utilities.cpp @@ -5,10 +5,11 @@ #include #include #include +#include #include #include "defines.h" -#include "utils.h" +#include "utilities.h" QString Utils::getRandomString(int len) { @@ -86,7 +87,7 @@ bool Utils::processIsRunning(const QString& fileName) process.waitForStarted(); process.waitForFinished(); QString processData(process.readAll()); - QStringList processList = processData.split(QRegExp("[\r\n]"),QString::SkipEmptyParts); + QStringList processList = processData.split(QRegularExpression("[\r\n]"),Qt::SkipEmptyParts); foreach (const QString& rawLine, processList) { const QString line = rawLine.simplified(); if (line.isEmpty()) { @@ -115,7 +116,7 @@ bool Utils::processIsRunning(const QString& fileName) QString Utils::getIPAddress(const QString& host) { - if (ipAddressRegExp().exactMatch(host)) { + if (ipAddressRegExp().match(host).hasMatch()) { return host; } @@ -164,6 +165,7 @@ bool Utils::checkIpSubnetFormat(const QString &ip) void Utils::killProcessByName(const QString &name) { qDebug().noquote() << "Kill process" << name; + qDebug() << "Hello"; #ifdef Q_OS_WIN QProcess::execute(QString("taskkill /im %1 /f").arg(name)); #elif defined Q_OS_IOS @@ -229,7 +231,7 @@ QString Utils::wireguardExecPath() #ifdef Q_OS_WIN return Utils::executable("wireguard/wireguard-service", true); #elif defined Q_OS_LINUX - return Utils::usrExecutable("wg"); + return Utils::usrExecutable("wg-quick"); #else return Utils::executable("/wireguard", true); #endif @@ -239,7 +241,7 @@ QString Utils::certUtilPath() { #ifdef Q_OS_WIN QString winPath = QString::fromUtf8(qgetenv("windir")); - return winPath + "system32\\certutil.exe"; + return winPath + "\\system32\\certutil.exe"; #else return ""; #endif diff --git a/client/utils.h b/client/utilities.h similarity index 82% rename from client/utils.h rename to client/utilities.h index 61981db7..aeb06865 100644 --- a/client/utils.h +++ b/client/utilities.h @@ -1,14 +1,16 @@ -#ifndef UTILS_H -#define UTILS_H +#ifndef UTILITIES_H +#define UTILITIES_H #include #include +#include #ifdef Q_OS_WIN #include "Windows.h" #endif -class Utils { +class Utils : public QObject { + Q_OBJECT public: static QString getRandomString(int len); @@ -23,8 +25,8 @@ public: static QString getStringBetween(const QString& s, const QString& a, const QString& b); static bool checkIPv4Format(const QString& ip); static bool checkIpSubnetFormat(const QString& ip); - static QRegExp ipAddressRegExp() { return QRegExp("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); } - static QRegExp ipAddressPortRegExp() { return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + static QRegularExpression ipAddressRegExp() { return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); } + static QRegularExpression ipAddressPortRegExp() { return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); } static QRegExp ipAddressWithSubnetRegExp() { return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" @@ -48,10 +50,9 @@ public: static QString wireguardExecPath(); static QString certUtilPath(); - #ifdef Q_OS_WIN static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); #endif }; -#endif // UTILS_H +#endif // UTILITIES_H diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 286ffc8c..21643ae5 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -19,15 +19,14 @@ #endif #ifdef Q_OS_ANDROID -#include "android_controller.h" -#include "protocols/android_vpnprotocol.h" +#include "../../platforms/android/android_controller.h" #endif #ifdef Q_OS_IOS #include #endif -#include "utils.h" +#include "utilities.h" #include "vpnconnection.h" VpnConnection::VpnConnection(std::shared_ptr settings, @@ -36,8 +35,6 @@ VpnConnection::VpnConnection(std::shared_ptr settings, m_settings(settings), m_configurator(configurator), m_serverController(serverController), - m_receivedBytes(0), - m_sentBytes(0), m_isIOSConnected(false) { } @@ -52,11 +49,7 @@ VpnConnection::~VpnConnection() void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes) { - emit bytesChanged(receivedBytes - m_receivedBytes, sentBytes - m_sentBytes); - - m_receivedBytes = receivedBytes; - m_sentBytes = sentBytes; - + emit bytesChanged(receivedBytes, sentBytes); } void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) @@ -111,8 +104,8 @@ void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState sta } else { m_isIOSConnected = false; - m_receivedBytes = 0; - m_sentBytes = 0; +// m_receivedBytes = 0; +// m_sentBytes = 0; } #endif emit connectionStateChanged(state); @@ -359,9 +352,8 @@ void VpnConnection::connectToVpn(int serverIndex, } m_vpnProtocol->prepare(); #elif defined Q_OS_ANDROID - Proto proto = ContainerProps::defaultProtocol(container); - AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration); - connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState); + androidVpnProtocol = createDefaultAndroidVpnProtocol(container); + createAndroidConnections(container); m_vpnProtocol.reset(androidVpnProtocol); #elif defined Q_OS_IOS @@ -378,16 +370,52 @@ void VpnConnection::connectToVpn(int serverIndex, m_vpnProtocol.reset(iosVpnProtocol); #endif - connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); - 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))); - - m_serverController->disconnectFromHost(credentials); + createProtocolConnections(); e = m_vpnProtocol.data()->start(); if (e) emit VpnProtocol::Error; } +void VpnConnection::createProtocolConnections() { + connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); + 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))); +} + +#ifdef Q_OS_ANDROID +void VpnConnection::restoreConnection() { + createAndroidConnections(); + + m_vpnProtocol.reset(androidVpnProtocol); + + createProtocolConnections(); +} + +void VpnConnection::createAndroidConnections() +{ + int serverIndex = m_settings->defaultServerIndex(); + DockerContainer container = m_settings->defaultContainer(serverIndex); + + createAndroidConnections(container); +} + +void VpnConnection::createAndroidConnections(DockerContainer container) +{ + androidVpnProtocol = createDefaultAndroidVpnProtocol(container); + + connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState); + connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated); +} + +AndroidVpnProtocol* VpnConnection::createDefaultAndroidVpnProtocol(DockerContainer container) +{ + Proto proto = ContainerProps::defaultProtocol(container); + AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration); + + return androidVpnProtocol; +} +#endif + QString VpnConnection::bytesPerSecToText(quint64 bytes) { double mbps = bytes * 8 / 1e6; @@ -406,8 +434,6 @@ void VpnConnection::disconnectFromVpn() } #endif - - if (!m_vpnProtocol.data()) { emit connectionStateChanged(VpnProtocol::Disconnected); #ifdef Q_OS_ANDROID @@ -420,11 +446,8 @@ void VpnConnection::disconnectFromVpn() VpnProtocol::VpnConnectionState VpnConnection::connectionState() { - - if (!m_vpnProtocol) return VpnProtocol::Disconnected; return m_vpnProtocol->connectionState(); - } bool VpnConnection::isConnected() const diff --git a/client/vpnconnection.h b/client/vpnconnection.h index c8ebdfad..ab8f2de0 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -18,6 +18,10 @@ #include "core/ipcclient.h" #endif +#ifdef Q_OS_ANDROID +#include "protocols/android_vpnprotocol.h" +#endif + class VpnConfigurator; class ServerController; @@ -61,6 +65,10 @@ public: const QString &remoteAddress() const; void addSitesRoutes(const QString &gw, Settings::RouteMode mode); +#ifdef Q_OS_ANDROID + void restoreConnection(); +#endif + public slots: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); @@ -93,8 +101,6 @@ private: QJsonObject m_vpnConfiguration; QJsonObject m_routeMode; QString m_remoteAddress; - quint64 m_receivedBytes; - quint64 m_sentBytes; bool m_isIOSConnected; //remove later move to isConnected, #ifdef AMNEZIA_DESKTOP @@ -103,6 +109,15 @@ private: #ifdef Q_OS_IOS IOSVpnProtocol * iosVpnProtocol{nullptr}; #endif +#ifdef Q_OS_ANDROID + AndroidVpnProtocol* androidVpnProtocol = nullptr; + + AndroidVpnProtocol* createDefaultAndroidVpnProtocol(DockerContainer container); + void createAndroidConnections(); + void createAndroidConnections(DockerContainer container); +#endif + + void createProtocolConnections(); }; #endif // VPNCONNECTION_H diff --git a/deploy/build_android.sh b/deploy/build_android.sh index 75e8c46c..d9d8f5c7 100644 --- a/deploy/build_android.sh +++ b/deploy/build_android.sh @@ -20,47 +20,39 @@ APP_DOMAIN=org.amneziavpn.package OUT_APP_DIR=$BUILD_DIR/client BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME -INSTALLER_DATA_DIR=$BUILD_DIR/installer/packages/$APP_DOMAIN/data -INSTALLER_BUNDLE_DIR=$BUILD_DIR/installer/$APP_FILENAME - -PRO_FILE_PATH=$PROJECT_DIR/$APP_NAME.pro -QMAKE_STASH_FILE=$PROJECT_DIR/.qmake_stash - # Seacrh Qt if [ -z "${QT_VERSION+x}" ]; then -QT_VERSION=5.15.2; -QT_BIN_DIR=$HOME/Qt/$QT_VERSION/android/bin +QT_VERSION=6.4.1; +QT_BIN_DIR=$HOME/Qt/$QT_VERSION/$ANDROID_CURRENT_ARCH/bin fi echo "Using Qt in $QT_BIN_DIR" echo "Using Android SDK in $ANDROID_SDK_ROOT" echo "Using Android NDK in $ANDROID_NDK_ROOT" - -# Checking env -$QT_BIN_DIR/qmake -v -$ANDROID_NDK_HOME/prebuilt/linux-x86_64/bin/make -v - # Build App echo "Building App..." cd $BUILD_DIR -$QT_BIN_DIR/qmake -r -spec android-clang CONFIG+=qtquickcompiler ANDROID_ABIS="armeabi-v7a arm64-v8a x86 x86_64" $PROJECT_DIR/AmneziaVPN.pro -echo "Executing make... may take long time" -$ANDROID_NDK_HOME/prebuilt/linux-x86_64/bin/make -j2 -echo "Make install..." -$ANDROID_NDK_HOME/prebuilt/linux-x86_64/bin/make install INSTALL_ROOT=android -echo "Build OK" +echo "HOST Qt: $QT_HOST_PATH" -echo "............Deploy.................." +$QT_BIN_DIR/qt-cmake -S $PROJECT_DIR \ + -DQT_NO_GLOBAL_APK_TARGET_PART_OF_ALL="ON" \ + -DQT_HOST_PATH=$QT_HOST_PATH \ + -DCMAKE_BUILD_TYPE="Release" + +cmake --build . --config release + +echo "............APK generation.................." cd $OUT_APP_DIR -$QT_BIN_DIR/androiddeployqt \ - --output $OUT_APP_DIR/android \ +$QT_HOST_PATH/bin/androiddeployqt \ + --output $OUT_APP_DIR/android-build \ --gradle \ --release \ - --input android-AmneziaVPN-deployment-settings.json - + --input android-AmneziaVPN-deployment-settings.json \ + --android-platform android-33 + echo "............Copy apk.................." -cp $OUT_APP_DIR/android/build/outputs/apk/release/android-release-unsigned.apk \ +cp $OUT_APP_DIR/android-build/build/outputs/apk/release/android-build-release-unsigned.apk \ $PROJECT_DIR/AmneziaVPN-release-unsigned.apk diff --git a/deploy/build_linux.sh b/deploy/build_linux.sh index f72f1a0c..2eab7ca6 100755 --- a/deploy/build_linux.sh +++ b/deploy/build_linux.sh @@ -47,16 +47,15 @@ echo "Using Qt in $QT_BIN_DIR" # Checking env -$QT_BIN_DIR/qmake -v -make -v +$QT_BIN_DIR/qt-cmake --version gcc -v # Build App echo "Building App..." cd $BUILD_DIR -$QT_BIN_DIR/qmake $PROJECT_DIR/AmneziaVPN.pro 'CONFIG+=release CONFIG+=x86_64' -make +$QT_BIN_DIR/qt-cmake -S $PROJECT_DIR +cmake --build . --config release # Build and run tests here diff --git a/deploy/build_macos.sh b/deploy/build_macos.sh index fa955215..9b80a958 100755 --- a/deploy/build_macos.sh +++ b/deploy/build_macos.sh @@ -38,9 +38,9 @@ DMG_FILENAME=$PROJECT_DIR/${APP_NAME}.dmg # Seacrh Qt if [ -z "${QT_VERSION+x}" ]; then -QT_VERSION=5.15.2; +QT_VERSION=6.4.1; QIF_VERSION=4.1 -QT_BIN_DIR=$HOME/Qt/$QT_VERSION/clang_64/bin +QT_BIN_DIR=$HOME/Qt/$QT_VERSION/macos/bin QIF_BIN_DIR=$QT_BIN_DIR/../../../Tools/QtInstallerFramework/$QIF_VERSION/bin fi @@ -49,16 +49,16 @@ echo "Using QIF in $QIF_BIN_DIR" # Checking env -$QT_BIN_DIR/qmake -v -make -v +$QT_BIN_DIR/qt-cmake --version +cmake --version clang -v # Build App echo "Building App..." cd $BUILD_DIR -$QT_BIN_DIR/qmake $PROJECT_DIR/AmneziaVPN.pro 'CONFIG+=release CONFIG+=x86_64' -make -j `sysctl -n hw.ncpu` +$QT_BIN_DIR/qt-cmake -S $PROJECT_DIR -B $BUILD_DIR +cmake --build . --config release --target all # Build and run tests here @@ -72,7 +72,7 @@ echo "Packaging ..." #cd $DEPLOY_DIR $QT_BIN_DIR/macdeployqt $OUT_APP_DIR/$APP_FILENAME -always-overwrite -qmldir=$PROJECT_DIR -cp -av $BUILD_DIR/service/server/$APP_NAME-service.app/Contents/macOS/$APP_NAME-service $BUNDLE_DIR/Contents/macOS +cp -av $BUILD_DIR/service/server/$APP_NAME-service $BUNDLE_DIR/Contents/macOS cp -Rv $PROJECT_DIR/deploy/data/macos/* $BUNDLE_DIR/Contents/macOS rm -f $BUNDLE_DIR/Contents/macOS/post_install.sh $BUNDLE_DIR/Contents/macOS/post_uninstall.sh diff --git a/deploy/build_windows.bat b/deploy/build_windows.bat index b58c4286..8384d210 100644 --- a/deploy/build_windows.bat +++ b/deploy/build_windows.bat @@ -25,21 +25,18 @@ set RELEASE_DIR=%WORK_DIR:"=% set OUT_APP_DIR=%RELEASE_DIR:"=%\client\release set DEPLOY_DATA_DIR=%SCRIPT_DIR:"=%\data\windows\x%BUILD_ARCH:"=% set INSTALLER_DATA_DIR=%RELEASE_DIR:"=%\installer\packages\%APP_DOMAIN:"=%\data -set PRO_FILE_PATH=%PROJECT_DIR:"=%\%APP_NAME:"=%.pro -set QMAKE_STASH_FILE=%PROJECT_DIR:"=%\.qmake_stash set TARGET_FILENAME=%PROJECT_DIR:"=%\%APP_NAME:"=%_x%BUILD_ARCH:"=%.exe echo "Environment:" -echo "APP_FILENAME: %APP_FILENAME%" -echo "PROJECT_DIR: %PROJECT_DIR%" -echo "SCRIPT_DIR: %SCRIPT_DIR%" -echo "RELEASE_DIR: %RELEASE_DIR%" -echo "OUT_APP_DIR: %OUT_APP_DIR%" -echo "DEPLOY_DATA_DIR: %DEPLOY_DATA_DIR%" -echo "INSTALLER_DATA_DIR: %INSTALLER_DATA_DIR%" -echo "PRO_FILE_PATH: %PRO_FILE_PATH%" -echo "QMAKE_STASH_FILE: %QMAKE_STASH_FILE%" -echo "TARGET_FILENAME: %TARGET_FILENAME%" +echo "APP_FILENAME: %APP_FILENAME%" +echo "PROJECT_DIR: %PROJECT_DIR%" +echo "SCRIPT_DIR: %SCRIPT_DIR%" +echo "RELEASE_DIR: %RELEASE_DIR%" +echo "OUT_APP_DIR: %OUT_APP_DIR%" +echo "DEPLOY_DATA_DIR: %DEPLOY_DATA_DIR%" +echo "INSTALLER_DATA_DIR: %INSTALLER_DATA_DIR%" +echo "QMAKE_STASH_FILE: %QMAKE_STASH_FILE%" +echo "TARGET_FILENAME: %TARGET_FILENAME%" rem Signing staff powershell Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope LocalMachine @@ -49,22 +46,20 @@ powershell Import-PfxCertificate -FilePath %SCRIPT_DIR:"=%\PrivacyTechWindowsCer echo "Cleanup..." Rmdir /Q /S %RELEASE_DIR% -Del %QMAKE_STASH_FILE% Del %TARGET_FILENAME% -"%QT_BIN_DIR:"=%\qmake" -v +call "%QT_BIN_DIR:"=%\qt-cmake" --version "%QT_BIN_DIR:"=%\windeployqt" -v -nmake /? +cmake --version cd %PROJECT_DIR% -"%QT_BIN_DIR:"=%\qmake" -spec win32-msvc -o "%WORK_DIR:"=%\Makefile" +call "%QT_BIN_DIR:"=%\qt-cmake" . -B %WORK_DIR% cd %WORK_DIR% -set CL=/MP -nmake /A /NOLOGO +cmake --build . --config release if %errorlevel% neq 0 exit /b %errorlevel% -nmake clean +cmake --build . --target clean rem if not exist "%OUT_APP_DIR:"=%\%APP_FILENAME:"=%" break echo "Deploying..." diff --git a/deploy/data/linux/client/bin/ck-client b/deploy/data/linux/client/bin/ck-client new file mode 100755 index 00000000..a6ef310c Binary files /dev/null and b/deploy/data/linux/client/bin/ck-client differ diff --git a/deploy/data/linux/client/bin/ss-local b/deploy/data/linux/client/bin/ss-local new file mode 100755 index 00000000..292abf6f Binary files /dev/null and b/deploy/data/linux/client/bin/ss-local differ diff --git a/deploy/installer/config/controlscript.js b/deploy/installer/config/controlscript.js index 9be76c15..bf22f8f6 100644 --- a/deploy/installer/config/controlscript.js +++ b/deploy/installer/config/controlscript.js @@ -26,7 +26,7 @@ function appInstalled() } else if (runningOnMacOS()){ appInstalledUninstallerPath = "/Applications/" + appName() + ".app/maintenancetool.app/Contents/MacOS/maintenancetool"; } else if (runningOnLinux()){ - allInstalledUninstallerPath = "/opt/" + appName(); + appInstalledUninstallerPath = "/opt/" + appName() + "/maintenancetool"; } return installer.fileExists(appInstalledUninstallerPath) || installer.fileExists(appInstalledUninstallerPath_x86); @@ -49,7 +49,7 @@ function runningOnMacOS() function runningOnLinux() { - return (installer.value("os") === "linux"); + return ((installer.value("os") === "linux") || (installer.value("os") === "x11")); } function sleep(miliseconds) { diff --git a/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js b/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js index d752d5a3..aae48338 100644 --- a/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js +++ b/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js @@ -93,7 +93,7 @@ Component.prototype.createOperations = function() } else if (runningOnMacOS()) { component.addElevatedOperation("Execute", "@TargetDir@/post_install.sh", "UNDOEXECUTE", "@TargetDir@/post_uninstall.sh"); } else if (runningOnLinux()) { - component.addElevatedOperation("Execute", "bash", "@TargetDir@/post_install.sh", "UNDOEXECUTE", "bash", "@TargetDir@/post_uninstall.sh"); + component.addElevatedOperation("Execute", "bash", "@TargetDir@/post_install.sh", "UNDOEXECUTE", "bash", "@TargetDir@/post_uninstall.sh"); } } diff --git a/ipc/ipc.h b/ipc/ipc.h index d69f6517..cb6a1088 100644 --- a/ipc/ipc.h +++ b/ipc/ipc.h @@ -4,7 +4,7 @@ #include #include -#include "../client/utils.h" +#include "../client/utilities.h" #define IPC_SERVICE_URL "local:AmneziaVpnIpcInterface" @@ -20,13 +20,12 @@ inline QString permittedProcessPath(PermittedProcess pid) { if (pid == PermittedProcess::OpenVPN) { return Utils::openVpnExecPath(); - } - if (pid == PermittedProcess::Wireguard) { + } else if (pid == PermittedProcess::Wireguard) { return Utils::wireguardExecPath(); - } - else if (pid == PermittedProcess::CertUtil) { + } else if (pid == PermittedProcess::CertUtil) { return Utils::certUtilPath(); } + return ""; } diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index 2c9f4746..8970f7c8 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -18,5 +18,9 @@ class IpcInterface SLOT( void cleanUp() ); SLOT( void setLogsEnabled(bool enabled) ); + + SLOT( bool copyWireguardConfig(const QString &sourcePath) ); + SLOT( bool isWireguardRunning() ); + SLOT( bool isWireguardConfigExists(const QString &configPath) ); }; diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index d71d7c7d..ddbc646c 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -3,9 +3,10 @@ #include #include #include +#include #include "router.h" -#include "log.h" +#include "logger.h" #ifdef Q_OS_WIN #include "tapcontroller_win.h" @@ -111,16 +112,63 @@ QStringList IpcServer::getTapList() void IpcServer::cleanUp() { qDebug() << "IpcServer::cleanUp"; - Log::deinit(); - Log::cleanUp(); + Logger::deinit(); + Logger::cleanUp(); } void IpcServer::setLogsEnabled(bool enabled) { if (enabled) { - Log::init(); + Logger::init(); } else { - Log::deinit(); + Logger::deinit(); } } + +bool IpcServer::copyWireguardConfig(const QString &sourcePath) +{ +#ifdef Q_OS_LINUX + const QString wireguardConfigPath = "/etc/wireguard/wg99.conf"; + if (QFile::exists(wireguardConfigPath)) + { + QFile::remove(wireguardConfigPath); + } + + if (!QFile::copy(sourcePath, wireguardConfigPath)) { + qDebug() << "WireguardProtocol::WireguardProtocol error occured while copying wireguard config:"; + return false; + } + return true; +#else + return false; +#endif +} + +bool IpcServer::isWireguardRunning() +{ +#ifdef Q_OS_LINUX + QProcess checkWireguardStatusProcess; + + connect(&checkWireguardStatusProcess, &QProcess::errorOccurred, this, [](QProcess::ProcessError error) { + qDebug() << "WireguardProtocol::WireguardProtocol error occured while checking wireguard status: " << error; + }); + + checkWireguardStatusProcess.setProgram("/bin/wg"); + checkWireguardStatusProcess.setArguments(QStringList{"show"}); + checkWireguardStatusProcess.start(); + checkWireguardStatusProcess.waitForFinished(10000); + QString output = checkWireguardStatusProcess.readAllStandardOutput(); + if (!output.isEmpty()) { + return true; + } + return false; +#else + return false; +#endif +} + +bool IpcServer::isWireguardConfigExists(const QString &configPath) +{ + return QFileInfo::exists(configPath); +} diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index a5b52ed1..d5706784 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -25,6 +25,9 @@ public: virtual QStringList getTapList() override; virtual void cleanUp() override; virtual void setLogsEnabled(bool enabled) override; + virtual bool copyWireguardConfig(const QString &sourcePath) override; + virtual bool isWireguardRunning() override; + virtual bool isWireguardConfigExists(const QString &configPath) override; private: int m_localpid = 0; diff --git a/service/CMakeLists.txt b/service/CMakeLists.txt new file mode 100644 index 00000000..567e9d49 --- /dev/null +++ b/service/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) + +set(PROJECT service) +project(${PROJECT}) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT IOS AND NOT ANDROID) + #include(common.cmake) +#if (qtservice-uselib) +# add_subdirectory(buildlib) +#endif() + add_subdirectory(server) +endif() + +if(WIN32) + add_subdirectory(wireguard-service) +endif() diff --git a/service/common.cmake b/service/common.cmake new file mode 100644 index 00000000..2d497e05 --- /dev/null +++ b/service/common.cmake @@ -0,0 +1,18 @@ +set(QTSERVICE_LIBNAME QtSolutions_Service-head) +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + if(APPLE) + set(QTSERVICE_LIBNAME ${QTSERVICE_LIBNAME}_debug) + elseif(WIN32) + set(QTSERVICE_LIBNAME ${QTSERVICE_LIBNAME}_d) + endif() +endif() + +set(QTSERVICE_LIBDIR ${CMAKE_CURRENT_LIST_DIR}/lib) + +if(UNIX) + if(qtservice-uselib) + if(NOT qtservice-buildlib) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} ${QTSERVICE_LIBDIR}) + endif() + endif() +endif() diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt new file mode 100644 index 00000000..16fe77ec --- /dev/null +++ b/service/server/CMakeLists.txt @@ -0,0 +1,110 @@ +cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) + +set(PROJECT AmneziaVPN-service) +project(${PROJECT}) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat) +qt_standard_project_setup() + +set(HEADERS + ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.h + ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h + ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.h + ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.h + ${CMAKE_CURRENT_LIST_DIR}/localserver.h + ${CMAKE_CURRENT_LIST_DIR}/logger.h + ${CMAKE_CURRENT_LIST_DIR}/router.h + ${CMAKE_CURRENT_LIST_DIR}/systemservice.h +) + +set(SOURCES + ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.cpp + ${CMAKE_CURRENT_LIST_DIR}/localserver.cpp + ${CMAKE_CURRENT_LIST_DIR}/logger.cpp + ${CMAKE_CURRENT_LIST_DIR}/main.cpp + ${CMAKE_CURRENT_LIST_DIR}/router.cpp + ${CMAKE_CURRENT_LIST_DIR}/systemservice.cpp +) + +if(WIN32) + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/tapcontroller_win.h + ${CMAKE_CURRENT_LIST_DIR}/router_win.h + ) + + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/tapcontroller_win.cpp + ${CMAKE_CURRENT_LIST_DIR}/router_win.cpp + ) + + set(LIBS + user32 + rasapi32 + shlwapi + iphlpapi + ws2_32 + iphlpapi + gdi32 + Advapi32 + Kernel32 + ) + + add_compile_definitions(_WINSOCKAPI_) +endif() + +if(APPLE) + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/helper_route_mac.h + ${CMAKE_CURRENT_LIST_DIR}/router_mac.h + ) + + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/helper_route_mac.c + ${CMAKE_CURRENT_LIST_DIR}/router_mac.cpp + ) +endif() + +if(LINUX) + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/router_linux.h + ) + + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/router_linux.cpp + ) +endif() + +include(${CMAKE_CURRENT_LIST_DIR}/../src/qtservice.cmake) + +include_directories( + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/../../client + ${CMAKE_CURRENT_LIST_DIR}/../../ipc + ${CMAKE_CURRENT_BINARY_DIR} +) + +add_executable(${PROJECT} ${SOURCES} ${HEADERS}) +target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat ${LIBS}) + +qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_interface.rep) +if(NOT IOS) + qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_process_interface.rep) +endif() + +# deploy artifacts required to run the application to the debug build folder +if(LINUX) + set(DEPLOY_ARTIFACT_PATH "linux/service") +endif() + +add_custom_command( + TARGET ${PROJECT} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E $,copy_directory,true> + ${CMAKE_SOURCE_DIR}/deploy/data/${DEPLOY_ARTIFACT_PATH} + $ + COMMAND_EXPAND_LISTS +) diff --git a/service/server/localserver.cpp b/service/server/localserver.cpp index f94d2dca..9971ad43 100644 --- a/service/server/localserver.cpp +++ b/service/server/localserver.cpp @@ -5,7 +5,7 @@ #include "ipc.h" #include "localserver.h" -#include "utils.h" +#include "utilities.h" #include "router.h" diff --git a/service/server/log.cpp b/service/server/logger.cpp similarity index 82% rename from service/server/log.cpp rename to service/server/logger.cpp index 978a6aff..8b44e0c6 100644 --- a/service/server/log.cpp +++ b/service/server/logger.cpp @@ -1,15 +1,16 @@ +#include "logger.h" + #include #include #include -#include "log.h" #include "defines.h" -#include "utils.h" +#include "utilities.h" -QFile Log::m_file; -QTextStream Log::m_textStream; -QString Log::m_logFileName = QString("%1.log").arg(SERVICE_NAME); +QFile Logger::m_file; +QTextStream Logger::m_textStream; +QString Logger::m_logFileName = QString("%1.log").arg(SERVICE_NAME); void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) { @@ -17,12 +18,12 @@ void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons return; } - Log::m_textStream << qFormatLogMessage(type, context, msg) << endl << flush; + Logger::m_textStream << qFormatLogMessage(type, context, msg) << Qt::endl << Qt::flush; std::cout << qFormatLogMessage(type, context, msg).toStdString() << std::endl << std::flush; } -bool Log::init() +bool Logger::init() { if (m_file.isOpen()) return true; @@ -46,19 +47,19 @@ bool Log::init() return true; } -void Log::deinit() +void Logger::deinit() { m_file.close(); m_textStream.setDevice(nullptr); qInstallMessageHandler(nullptr); } -QString Log::serviceLogFileNamePath() +QString Logger::serviceLogFileNamePath() { return m_file.fileName(); } -void Log::clearLogs() +void Logger::clearLogs() { bool isLogActive = m_file.isOpen(); m_file.close(); @@ -78,7 +79,7 @@ void Log::clearLogs() } } -void Log::cleanUp() +void Logger::cleanUp() { clearLogs(); deinit(); diff --git a/service/server/log.h b/service/server/logger.h similarity index 87% rename from service/server/log.h rename to service/server/logger.h index e87dda1d..59044470 100644 --- a/service/server/log.h +++ b/service/server/logger.h @@ -1,12 +1,12 @@ -#ifndef LOG_H -#define LOG_H +#ifndef LOGGER_H +#define LOGGER_H #include #include #include #include -class Log +class Logger { public: static bool init(); @@ -25,4 +25,4 @@ private: static QTextStream m_textStream; }; -#endif // LOG_H +#endif // LOGGER_H diff --git a/service/server/main.cpp b/service/server/main.cpp index 9f15195c..fe566652 100644 --- a/service/server/main.cpp +++ b/service/server/main.cpp @@ -2,9 +2,9 @@ #include "defines.h" #include "localserver.h" -#include "log.h" +#include "logger.h" #include "systemservice.h" -#include "utils.h" +#include "utilities.h" int runApplication(int argc, char** argv) @@ -20,7 +20,7 @@ int main(int argc, char **argv) { Utils::initializePath(Utils::systemLogPath()); - Log::init(); + Logger::init(); if (argc == 2) { qInfo() << "Started as console application"; diff --git a/service/server/router_linux.cpp b/service/server/router_linux.cpp index 0ec6250f..169e9475 100644 --- a/service/server/router_linux.cpp +++ b/service/server/router_linux.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include diff --git a/service/server/router_mac.cpp b/service/server/router_mac.cpp index 13b9d448..6de6b99d 100644 --- a/service/server/router_mac.cpp +++ b/service/server/router_mac.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include RouterMac &RouterMac::Instance() { diff --git a/service/server/router_win.cpp b/service/server/router_win.cpp index df431805..0c771f7c 100644 --- a/service/server/router_win.cpp +++ b/service/server/router_win.cpp @@ -1,5 +1,5 @@ #include "router_win.h" -#include "../client/utils.h" +#include "../client/utilities.h" #include #include diff --git a/service/server/server.pro b/service/server/server.pro index 2c7097ec..ca7d85cb 100644 --- a/service/server/server.pro +++ b/service/server/server.pro @@ -2,23 +2,24 @@ TARGET = AmneziaVPN-service TEMPLATE = app CONFIG += console qt no_batch QT += core network remoteobjects +equals(QT_MAJOR_VERSION, 6): QT += core5compat HEADERS = \ - ../../client/utils.h \ + ../../client/utilities.h \ ../../ipc/ipc.h \ ../../ipc/ipcserver.h \ ../../ipc/ipcserverprocess.h \ localserver.h \ - log.h \ + logger.h \ router.h \ systemservice.h SOURCES = \ - ../../client/utils.cpp \ + ../../client/utilities.cpp \ ../../ipc/ipcserver.cpp \ ../../ipc/ipcserverprocess.cpp \ localserver.cpp \ - log.cpp \ + logger.cpp \ main.cpp \ router.cpp \ systemservice.cpp diff --git a/service/server/tapcontroller_win.cpp b/service/server/tapcontroller_win.cpp index b388997a..fca86d71 100644 --- a/service/server/tapcontroller_win.cpp +++ b/service/server/tapcontroller_win.cpp @@ -87,12 +87,12 @@ QStringList TapController::getTapList() return QStringList(); } - QStringList l = output.split("\n", QString::SkipEmptyParts); + QStringList l = output.split("\n", Qt::SkipEmptyParts); if (l.size() > 0) l.removeLast(); QStringList tapList; for (QString s : l) { - if (s.contains(" ")) tapList.append(s.split(" ", QString::SkipEmptyParts).first()); + if (s.contains(" ")) tapList.append(s.split(" ", Qt::SkipEmptyParts).first()); else tapList.append(s); } diff --git a/service/src/qtservice.cmake b/service/src/qtservice.cmake new file mode 100644 index 00000000..4a429fab --- /dev/null +++ b/service/src/qtservice.cmake @@ -0,0 +1,37 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +#include(${CMAKE_CURRENT_LIST_DIR}/../common.cmake) + +if(NOT WIN32) + set(LIBS ${LIBS} Qt6::Network) +elseif(WIN32) + set(LIBS ${LIBS} user32) +endif() + +set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/qtservice.h + ${CMAKE_CURRENT_LIST_DIR}/qtservice_p.h +) + +set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/qtservice.cpp +) + +if(UNIX) + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/qtunixsocket.h + ${CMAKE_CURRENT_LIST_DIR}/qtunixserversocket.h + ) + + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/qtservice_unix.cpp + ${CMAKE_CURRENT_LIST_DIR}/qtunixsocket.cpp + ${CMAKE_CURRENT_LIST_DIR}/qtunixserversocket.cpp + ) +endif() + +if(WIN32) + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/qtservice_win.cpp + ) +endif() diff --git a/service/src/qtservice.cpp b/service/src/qtservice.cpp index 5eae058e..fcae9a12 100644 --- a/service/src/qtservice.cpp +++ b/service/src/qtservice.cpp @@ -663,7 +663,7 @@ QtServiceBase::QtServiceBase(int argc, char **argv, const QString &name) d_ptr = new QtServiceBasePrivate(nm); d_ptr->q_ptr = this; - d_ptr->serviceFlags = 0; + d_ptr->serviceFlags = QtServiceBase::Default; d_ptr->sysd = 0; for (int i = 0; i < argc; ++i) d_ptr->args.append(QString::fromLocal8Bit(argv[i])); diff --git a/service/src/qtservice.h b/service/src/qtservice.h index 01d5b07f..bea140b4 100644 --- a/service/src/qtservice.h +++ b/service/src/qtservice.h @@ -59,7 +59,6 @@ # define QT_QTSERVICE_EXPORT #endif -class QStringList; class QtServiceControllerPrivate; class QT_QTSERVICE_EXPORT QtServiceController diff --git a/service/src/qtservice_win.cpp b/service/src/qtservice_win.cpp index e5b7ecc5..c48194b2 100644 --- a/service/src/qtservice_win.cpp +++ b/service/src/qtservice_win.cpp @@ -737,10 +737,10 @@ class QtServiceAppEventFilter : public QAbstractNativeEventFilter { public: QtServiceAppEventFilter() {} - bool nativeEventFilter(const QByteArray &eventType, void *message, long *result); + bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result); }; -bool QtServiceAppEventFilter::nativeEventFilter(const QByteArray &, void *message, long *result) +bool QtServiceAppEventFilter::nativeEventFilter(const QByteArray &, void *message, qintptr *result) { MSG *winMessage = (MSG*)message; if (winMessage->message == WM_ENDSESSION && (winMessage->lParam & ENDSESSION_LOGOFF)) { diff --git a/service/wireguard-service/CMakeLists.txt b/service/wireguard-service/CMakeLists.txt new file mode 100644 index 00000000..33a3d584 --- /dev/null +++ b/service/wireguard-service/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) + +set(PROJECT wireguard-service) +project(${PROJECT} LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Core) +qt_standard_project_setup() + +set(SOURCES + ${CMAKE_CURRENT_LIST_DIR}/main.cpp + ${CMAKE_CURRENT_LIST_DIR}/wireguardtunnelservice.cpp +) + +set(HEADERS + ${CMAKE_CURRENT_LIST_DIR}/wireguardtunnelservice.h +) + +set(LIBS + user32 + rasapi32 + shlwapi + iphlpapi + ws2_32 + iphlpapi + gdi32 + Advapi32 + Kernel32 +) + +add_executable(${PROJECT} ${SOURCES} ${HEADERS}) +target_link_libraries(${PROJECT} PRIVATE Qt6::Core ${LIBS})