Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94b8f80141 | ||
|
|
9d96b1cd13 | ||
|
|
1d721ffb9a | ||
|
|
2130131a9d | ||
|
|
e0b091b474 | ||
|
|
8547de82ea | ||
|
|
aa871bd1c9 | ||
|
|
23806e1def | ||
|
|
31867993ce | ||
|
|
7b7a922d92 | ||
|
|
09bd958d8d | ||
|
|
576e2226fe | ||
|
|
1533270e4e | ||
|
|
e7b25719e4 | ||
|
|
7183e8541c | ||
|
|
9e71e64cbd | ||
|
|
4f3bae4a9a | ||
|
|
990059f8a6 | ||
|
|
af55af5e76 | ||
|
|
82d96a9691 | ||
|
|
9f3f215452 | ||
|
|
2dfc6a87b8 | ||
|
|
7261a86c48 | ||
|
|
2946dd2278 | ||
|
|
5065262aac | ||
|
|
4685d3b543 | ||
|
|
7a389e8755 | ||
|
|
4e5daf22a3 | ||
|
|
3bf9c10d7d | ||
|
|
2e175cb9fc | ||
|
|
823c1b5d3a | ||
|
|
92bc1a6f09 | ||
|
|
d511220f8b | ||
|
|
923e358aaa | ||
|
|
92b19eccf6 | ||
|
|
5358aaeb00 | ||
|
|
e31a2066c0 | ||
|
|
928c4f18c9 | ||
|
|
628e22869d | ||
|
|
c9cd860654 | ||
|
|
17984adae5 | ||
|
|
5601bc4fdf | ||
|
|
e14681801e | ||
|
|
f106b4d367 | ||
|
|
74802f30ed | ||
|
|
d63bf15011 | ||
|
|
60de146f03 | ||
|
|
c4f32eed31 | ||
|
|
2c9067b0de | ||
|
|
6844a2375b | ||
|
|
7b838e77a0 | ||
|
|
694e781beb | ||
|
|
399a8c6d28 | ||
|
|
dce08b3ecc | ||
|
|
2763da960f | ||
|
|
d4fff4af3c | ||
|
|
f0903c32f3 | ||
|
|
ea8875478e | ||
|
|
4c08e9f3bc | ||
|
|
e8736102bf | ||
|
|
371cadcc02 | ||
|
|
02a98b9d68 | ||
|
|
1542adba82 | ||
|
|
3aa8a46f6e | ||
|
|
1f08d78b43 |
No files matched your search
@@ -16,7 +16,10 @@ jobs:
|
||||
QT_VERSION: 6.6.2
|
||||
QIF_VERSION: 4.7
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install Qt'
|
||||
@@ -83,7 +86,10 @@ jobs:
|
||||
QIF_VERSION: 4.7
|
||||
BUILD_ARCH: 64
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
@@ -146,7 +152,10 @@ jobs:
|
||||
CC: cc
|
||||
CXX: c++
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
@@ -208,7 +217,11 @@ jobs:
|
||||
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 PATH=$PATH:~/go/bin
|
||||
sh deploy/build_ios.sh
|
||||
sh deploy/build_ios.sh | \
|
||||
sed -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DPROD_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DDEV_AGW_PUBLIC_KEY/d'
|
||||
env:
|
||||
IOS_TRUST_CERT_BASE64: ${{ secrets.IOS_TRUST_CERT_BASE64 }}
|
||||
IOS_SIGNING_CERT_BASE64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
|
||||
@@ -238,13 +251,16 @@ jobs:
|
||||
QT_VERSION: 6.4.3
|
||||
QIF_VERSION: 4.6
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '14.3.1'
|
||||
xcode-version: '15.4.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -301,10 +317,13 @@ jobs:
|
||||
|
||||
env:
|
||||
ANDROID_BUILD_PLATFORM: android-34
|
||||
QT_VERSION: 6.7.2
|
||||
QT_VERSION: 6.7.3
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
|
||||
@@ -16,7 +16,10 @@ jobs:
|
||||
QT_VERSION: 6.4.1
|
||||
QIF_VERSION: 4.5
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
|
||||
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.8.1.1
|
||||
project(${PROJECT} VERSION 4.8.2.4
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 63)
|
||||
set(APP_ANDROID_VERSION_CODE 2071)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -4,27 +4,23 @@
|
||||
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
|
||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
|
||||

|
||||
[](https://amnezia.org)
|
||||
|
||||
<br>
|
||||
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/kldscp/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_4.7.0.0_x64.exe"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/win.png" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_4.7.0.0.dmg"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/mac.png" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_Linux_4.7.0.0.tar.zip"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/lin.png" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/tag/4.7.0.0"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/andr.png" width="150" style="max-width: 100%;"></a>
|
||||
|
||||
<br>
|
||||
|
||||
<a href="https://play.google.com/store/search?q=amnezia+vpn&c=apps"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/play.png" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://apps.apple.com/us/app/amneziavpn/id1600529900"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/apl.png" width="150" style="max-width: 100%;"></a>
|
||||
> [!TIP]
|
||||
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/kldscp/amnezia.org).
|
||||
|
||||
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||
|
||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
<br>
|
||||
<br/>
|
||||
|
||||
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
|
||||
|
||||
## Features
|
||||
|
||||
@@ -37,7 +33,8 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
|
||||
|
||||
## Links
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org) - project website
|
||||
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
|
||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||
@@ -186,8 +183,8 @@ Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn
|
||||
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
||||
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
|
||||
## Acknowledgments
|
||||
|
||||
This project is tested with BrowserStack.
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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()
|
||||
@@ -1,274 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QSharedMemory>
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program
|
||||
* if another instance already exists
|
||||
* @param argc
|
||||
* @param argv
|
||||
* @param allowSecondary Whether to enable secondary instance support
|
||||
* @param options Optional flags to toggle specific behaviour
|
||||
* @param timeout Maximum time blocking functions are allowed during app load
|
||||
*/
|
||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData )
|
||||
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
// On Android and iOS since the library is not supported fallback to
|
||||
// standard QApplication behaviour by simply returning at this point.
|
||||
qWarning() << "SingleApplication is not supported on Android and iOS systems.";
|
||||
return;
|
||||
#endif
|
||||
|
||||
// Store the current mode of the program
|
||||
d->options = options;
|
||||
|
||||
// Add any unique user data
|
||||
if ( ! userData.isEmpty() )
|
||||
d->addAppData( userData );
|
||||
|
||||
// Generating an application ID used for identifying the shared memory
|
||||
// block and QLocalServer
|
||||
d->genBlockServerName();
|
||||
|
||||
// To mitigate QSharedMemory issues with large amount of processes
|
||||
// attempting to attach at the same time
|
||||
SingleApplicationPrivate::randomSleep();
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
// By explicitly attaching it and then deleting it we make sure that the
|
||||
// memory is deleted even after the process has crashed on Unix.
|
||||
d->memory = new QSharedMemory( d->blockServerName );
|
||||
d->memory->attach();
|
||||
delete d->memory;
|
||||
#endif
|
||||
// Guarantee thread safe behaviour with a shared memory block.
|
||||
d->memory = new QSharedMemory( d->blockServerName );
|
||||
|
||||
// Create a shared memory block
|
||||
if( d->memory->create( sizeof( InstancesInfo ) )){
|
||||
// Initialize the shared memory block
|
||||
if( ! d->memory->lock() ){
|
||||
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
||||
abortSafely();
|
||||
}
|
||||
d->initializeMemoryBlock();
|
||||
} else {
|
||||
if( d->memory->error() == QSharedMemory::AlreadyExists ){
|
||||
// Attempt to attach to the memory segment
|
||||
if( ! d->memory->attach() ){
|
||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
||||
abortSafely();
|
||||
}
|
||||
if( ! d->memory->lock() ){
|
||||
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
|
||||
abortSafely();
|
||||
}
|
||||
} else {
|
||||
qCritical() << "SingleApplication: Unable to create block.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Make sure the shared memory block is initialised and in consistent state
|
||||
while( true ){
|
||||
// If the shared memory block's checksum is valid continue
|
||||
if( d->blockChecksum() == inst->checksum ) break;
|
||||
|
||||
// If more than 5s have elapsed, assume the primary instance crashed and
|
||||
// assume it's position
|
||||
if( time.elapsed() > 5000 ){
|
||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
|
||||
// Otherwise wait for a random period and try again. The random sleep here
|
||||
// limits the probability of a collision between two racing apps and
|
||||
// allows the app to initialise faster
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
SingleApplicationPrivate::randomSleep();
|
||||
if( ! d->memory->lock() ){
|
||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
if( inst->primary == false ){
|
||||
d->startPrimary();
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if another instance can be started
|
||||
if( allowSecondary ){
|
||||
d->startSecondary();
|
||||
if( d->options & Mode::SecondaryNotification ){
|
||||
d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
|
||||
}
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if( ! d->memory->unlock() ){
|
||||
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
||||
qDebug() << d->memory->errorString();
|
||||
}
|
||||
|
||||
d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
|
||||
|
||||
delete d;
|
||||
|
||||
::exit( EXIT_SUCCESS );
|
||||
}
|
||||
|
||||
SingleApplication::~SingleApplication()
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
delete d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is primary.
|
||||
* @return Returns true if the instance is primary, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::isPrimary() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->server != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is secondary.
|
||||
* @return Returns true if the instance is secondary, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::isSecondary() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->server == nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to identify an instance by returning unique consecutive instance
|
||||
* ids. It is reset when the first (primary) instance of your app starts and
|
||||
* only incremented afterwards.
|
||||
* @return Returns a unique instance id.
|
||||
*/
|
||||
quint32 SingleApplication::instanceId() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->instanceNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OS PID (Process Identifier) of the process running the primary
|
||||
* instance. Especially useful when SingleApplication is coupled with OS.
|
||||
* specific APIs.
|
||||
* @return Returns the primary instance PID.
|
||||
*/
|
||||
qint64 SingleApplication::primaryPid() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->primaryPid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the primary instance is running as.
|
||||
* @return Returns the username the primary instance is running as.
|
||||
*/
|
||||
QString SingleApplication::primaryUser() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->primaryUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the current instance is running as.
|
||||
* @return Returns the username the current instance is running as.
|
||||
*/
|
||||
QString SingleApplication::currentUser() const
|
||||
{
|
||||
return SingleApplicationPrivate::getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message to the Primary Instance.
|
||||
* @param message The message to send.
|
||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||
* @return true if the message was sent successfuly, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
|
||||
// Nobody to connect to
|
||||
if( isPrimary() ) return false;
|
||||
|
||||
// Make sure the socket is connected
|
||||
if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
|
||||
return false;
|
||||
|
||||
d->socket->write( message );
|
||||
bool dataWritten = d->socket->waitForBytesWritten( timeout );
|
||||
d->socket->flush();
|
||||
return dataWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the shared memory block and exits with a failure.
|
||||
* This function halts program execution.
|
||||
*/
|
||||
void SingleApplication::abortSafely()
|
||||
{
|
||||
Q_D( SingleApplication );
|
||||
|
||||
qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
|
||||
delete d;
|
||||
::exit( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
QStringList SingleApplication::userData() const
|
||||
{
|
||||
Q_D( const SingleApplication );
|
||||
return d->appData();
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef SINGLE_APPLICATION_H
|
||||
#define SINGLE_APPLICATION_H
|
||||
|
||||
#include <QtCore/QtGlobal>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#ifndef QAPPLICATION_CLASS
|
||||
#define QAPPLICATION_CLASS QApplication
|
||||
#endif
|
||||
|
||||
#include QT_STRINGIFY(QAPPLICATION_CLASS)
|
||||
|
||||
class SingleApplicationPrivate;
|
||||
|
||||
/**
|
||||
* @brief The SingleApplication class handles multiple instances of the same
|
||||
* Application
|
||||
* @see QCoreApplication
|
||||
*/
|
||||
class SingleApplication : public QAPPLICATION_CLASS
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using app_t = QAPPLICATION_CLASS;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Mode of operation of SingleApplication.
|
||||
* Whether the block should be user-wide or system-wide and whether the
|
||||
* primary instance should be notified when a secondary instance had been
|
||||
* started.
|
||||
* @note Operating system can restrict the shared memory blocks to the same
|
||||
* user, in which case the User/System modes will have no effect and the
|
||||
* block will be user wide.
|
||||
* @enum
|
||||
*/
|
||||
enum Mode {
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
ExcludeAppVersion = 1 << 3,
|
||||
ExcludeAppPath = 1 << 4
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Mode)
|
||||
|
||||
/**
|
||||
* @brief Intitializes a SingleApplication instance with argc command line
|
||||
* arguments in argv
|
||||
* @arg {int &} argc - Number of arguments in argv
|
||||
* @arg {const char *[]} argv - Supplied command line arguments
|
||||
* @arg {bool} allowSecondary - Whether to start the instance as secondary
|
||||
* if there is already a primary instance.
|
||||
* @arg {Mode} mode - Whether for the SingleApplication block to be applied
|
||||
* User wide or System wide.
|
||||
* @arg {int} timeout - Timeout to wait in milliseconds.
|
||||
* @note argc and argv may be changed as Qt removes arguments that it
|
||||
* recognizes
|
||||
* @note Mode::SecondaryNotification only works if set on both the primary
|
||||
* instance and the secondary instance.
|
||||
* @note The timeout is just a hint for the maximum time of blocking
|
||||
* operations. It does not guarantee that the SingleApplication
|
||||
* initialisation will be completed in given time, though is a good hint.
|
||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
||||
*/
|
||||
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} );
|
||||
~SingleApplication() override;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is the primary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isPrimary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is a secondary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isSecondary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns a unique identifier for the current instance
|
||||
* @returns {qint32}
|
||||
*/
|
||||
quint32 instanceId() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the process ID (PID) of the primary instance
|
||||
* @returns {qint64}
|
||||
*/
|
||||
qint64 primaryPid() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the user running the primary instance
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString primaryUser() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the current user
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString currentUser() const;
|
||||
|
||||
/**
|
||||
* @brief Sends a message to the primary instance. Returns true on success.
|
||||
* @param {int} timeout - Timeout for connecting
|
||||
* @returns {bool}
|
||||
* @note sendMessage() will return false if invoked from the primary
|
||||
* instance.
|
||||
*/
|
||||
bool sendMessage( const QByteArray &message, int timeout = 100 );
|
||||
|
||||
/**
|
||||
* @brief Get the set user data.
|
||||
* @returns {QStringList}
|
||||
*/
|
||||
QStringList userData() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void instanceStarted();
|
||||
void receivedMessage( quint32 instanceId, QByteArray message );
|
||||
|
||||
private:
|
||||
SingleApplicationPrivate *d_ptr;
|
||||
Q_DECLARE_PRIVATE(SingleApplication)
|
||||
void abortSafely();
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
||||
|
||||
#endif // SINGLE_APPLICATION_H
|
||||
@@ -1,15 +0,0 @@
|
||||
QT += core network
|
||||
CONFIG += c++11
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/singleapplication.h \
|
||||
$$PWD/singleapplication_p.h
|
||||
SOURCES += $$PWD/singleapplication.cpp \
|
||||
$$PWD/singleapplication_p.cpp
|
||||
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
win32 {
|
||||
msvc:LIBS += Advapi32.lib
|
||||
gcc:LIBS += -ladvapi32
|
||||
}
|
||||
@@ -1,486 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// 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.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This file is not part of the SingleApplication API. It is used purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or may even be removed.
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstddef>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
#include <QtCore/QRandomGenerator>
|
||||
#else
|
||||
#include <QtCore/QDateTime>
|
||||
#endif
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX 1
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <lmcons.h>
|
||||
#endif
|
||||
|
||||
SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
|
||||
: q_ptr( q_ptr )
|
||||
{
|
||||
server = nullptr;
|
||||
socket = nullptr;
|
||||
memory = nullptr;
|
||||
instanceNumber = 0;
|
||||
}
|
||||
|
||||
SingleApplicationPrivate::~SingleApplicationPrivate()
|
||||
{
|
||||
if( socket != nullptr ){
|
||||
socket->close();
|
||||
delete socket;
|
||||
}
|
||||
|
||||
if( memory != nullptr ){
|
||||
memory->lock();
|
||||
auto *inst = static_cast<InstancesInfo*>(memory->data());
|
||||
if( server != nullptr ){
|
||||
server->close();
|
||||
delete server;
|
||||
inst->primary = false;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
memory->unlock();
|
||||
|
||||
delete memory;
|
||||
}
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::getUsername()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
wchar_t username[UNLEN + 1];
|
||||
// Specifies size of the buffer on input
|
||||
DWORD usernameLength = UNLEN + 1;
|
||||
if( GetUserNameW( username, &usernameLength ) )
|
||||
return QString::fromWCharArray( username );
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
|
||||
#else
|
||||
return qEnvironmentVariable( "USERNAME" );
|
||||
#endif
|
||||
#endif
|
||||
#ifdef Q_OS_UNIX
|
||||
QString username;
|
||||
uid_t uid = geteuid();
|
||||
struct passwd *pw = getpwuid( uid );
|
||||
if( pw )
|
||||
username = QString::fromLocal8Bit( pw->pw_name );
|
||||
if ( username.isEmpty() ){
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
|
||||
#else
|
||||
username = qEnvironmentVariable( "USER" );
|
||||
#endif
|
||||
}
|
||||
return username;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::genBlockServerName()
|
||||
{
|
||||
QCryptographicHash appData( QCryptographicHash::Sha256 );
|
||||
appData.addData( "SingleApplication", 17 );
|
||||
appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
|
||||
appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
|
||||
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
|
||||
|
||||
if ( ! appDataList.isEmpty() )
|
||||
appData.addData( appDataList.join( "" ).toUtf8() );
|
||||
|
||||
if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
|
||||
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
|
||||
}
|
||||
|
||||
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
|
||||
#ifdef Q_OS_WIN
|
||||
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
|
||||
#else
|
||||
appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
|
||||
#endif
|
||||
}
|
||||
|
||||
// User level block requires a user specific data in the hash
|
||||
if( options & SingleApplication::Mode::User ){
|
||||
appData.addData( getUsername().toUtf8() );
|
||||
}
|
||||
|
||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
|
||||
// server naming requirements.
|
||||
blockServerName = appData.result().toBase64().replace("/", "_");
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::initializeMemoryBlock() const
|
||||
{
|
||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
inst->primary = false;
|
||||
inst->secondary = 0;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startPrimary()
|
||||
{
|
||||
// Reset the number of connections
|
||||
auto *inst = static_cast <InstancesInfo*>( memory->data() );
|
||||
|
||||
inst->primary = true;
|
||||
inst->primaryPid = QCoreApplication::applicationPid();
|
||||
qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber = 0;
|
||||
// Successful creation means that no main process exists
|
||||
// So we start a QLocalServer to listen for connections
|
||||
QLocalServer::removeServer( blockServerName );
|
||||
server = new QLocalServer();
|
||||
|
||||
// Restrict access to the socket according to the
|
||||
// SingleApplication::Mode::User flag on User level or no restrictions
|
||||
if( options & SingleApplication::Mode::User ){
|
||||
server->setSocketOptions( QLocalServer::UserAccessOption );
|
||||
} else {
|
||||
server->setSocketOptions( QLocalServer::WorldAccessOption );
|
||||
}
|
||||
|
||||
server->listen( blockServerName );
|
||||
QObject::connect(
|
||||
server,
|
||||
&QLocalServer::newConnection,
|
||||
this,
|
||||
&SingleApplicationPrivate::slotConnectionEstablished
|
||||
);
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startSecondary()
|
||||
{
|
||||
auto *inst = static_cast <InstancesInfo*>( memory->data() );
|
||||
|
||||
inst->secondary += 1;
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber = inst->secondary;
|
||||
}
|
||||
|
||||
bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
|
||||
{
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Connect to the Local Server of the Primary Instance if not already
|
||||
// connected.
|
||||
if( socket == nullptr ){
|
||||
socket = new QLocalSocket();
|
||||
}
|
||||
|
||||
if( socket->state() == QLocalSocket::ConnectedState ) return true;
|
||||
|
||||
if( socket->state() != QLocalSocket::ConnectedState ){
|
||||
|
||||
while( true ){
|
||||
randomSleep();
|
||||
|
||||
if( socket->state() != QLocalSocket::ConnectingState )
|
||||
socket->connectToServer( blockServerName );
|
||||
|
||||
if( socket->state() == QLocalSocket::ConnectingState ){
|
||||
socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) );
|
||||
}
|
||||
|
||||
// If connected break out of the loop
|
||||
if( socket->state() == QLocalSocket::ConnectedState ) break;
|
||||
|
||||
// If elapsed time since start is longer than the method timeout return
|
||||
if( time.elapsed() >= msecs ) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisation message according to the SingleApplication protocol
|
||||
QByteArray initMsg;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
||||
#endif
|
||||
|
||||
writeStream << blockServerName.toLatin1();
|
||||
writeStream << static_cast<quint8>(connectionType);
|
||||
writeStream << instanceNumber;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||
#else
|
||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||
#endif
|
||||
writeStream << checksum;
|
||||
|
||||
// The header indicates the message length that follows
|
||||
QByteArray header;
|
||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
||||
#endif
|
||||
headerStream << static_cast <quint64>( initMsg.length() );
|
||||
|
||||
socket->write( header );
|
||||
socket->write( initMsg );
|
||||
bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
|
||||
socket->flush();
|
||||
return result;
|
||||
}
|
||||
|
||||
quint16 SingleApplicationPrivate::blockChecksum() const
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
|
||||
#else
|
||||
quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
|
||||
#endif
|
||||
return checksum;
|
||||
}
|
||||
|
||||
qint64 SingleApplicationPrivate::primaryPid() const
|
||||
{
|
||||
qint64 pid;
|
||||
|
||||
memory->lock();
|
||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
pid = inst->primaryPid;
|
||||
memory->unlock();
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::primaryUser() const
|
||||
{
|
||||
QByteArray username;
|
||||
|
||||
memory->lock();
|
||||
auto *inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
username = inst->primaryUser;
|
||||
memory->unlock();
|
||||
|
||||
return QString::fromUtf8( username );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executed when a connection has been made to the LocalServer
|
||||
*/
|
||||
void SingleApplicationPrivate::slotConnectionEstablished()
|
||||
{
|
||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
||||
[nextConnSocket, this](){
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed,
|
||||
[nextConnSocket, this](){
|
||||
connectionMap.remove(nextConnSocket);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
||||
[nextConnSocket, this](){
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
switch(info.stage){
|
||||
case StageHeader:
|
||||
readInitMessageHeader(nextConnSocket);
|
||||
break;
|
||||
case StageBody:
|
||||
readInitMessageBody(nextConnSocket);
|
||||
break;
|
||||
case StageConnected:
|
||||
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
|
||||
{
|
||||
if (!connectionMap.contains( sock )){
|
||||
return;
|
||||
}
|
||||
|
||||
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
|
||||
return;
|
||||
}
|
||||
|
||||
QDataStream headerStream( sock );
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
headerStream.setVersion( QDataStream::Qt_5_6 );
|
||||
#endif
|
||||
|
||||
// Read the header to know the message length
|
||||
quint64 msgLen = 0;
|
||||
headerStream >> msgLen;
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
info.stage = StageBody;
|
||||
info.msgLen = msgLen;
|
||||
|
||||
if ( sock->bytesAvailable() >= (qint64) msgLen ){
|
||||
readInitMessageBody( sock );
|
||||
}
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
||||
{
|
||||
Q_Q(SingleApplication);
|
||||
|
||||
if (!connectionMap.contains( sock )){
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the message body
|
||||
QByteArray msgBytes = sock->read(info.msgLen);
|
||||
QDataStream readStream(msgBytes);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
readStream.setVersion( QDataStream::Qt_5_6 );
|
||||
#endif
|
||||
|
||||
// server name
|
||||
QByteArray latin1Name;
|
||||
readStream >> latin1Name;
|
||||
|
||||
// connection type
|
||||
ConnectionType connectionType = InvalidConnection;
|
||||
quint8 connTypeVal = InvalidConnection;
|
||||
readStream >> connTypeVal;
|
||||
connectionType = static_cast <ConnectionType>( connTypeVal );
|
||||
|
||||
// instance id
|
||||
quint32 instanceId = 0;
|
||||
readStream >> instanceId;
|
||||
|
||||
// checksum
|
||||
quint16 msgChecksum = 0;
|
||||
readStream >> msgChecksum;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
|
||||
#else
|
||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
||||
#endif
|
||||
|
||||
bool isValid = readStream.status() == QDataStream::Ok &&
|
||||
QLatin1String(latin1Name) == blockServerName &&
|
||||
msgChecksum == actualChecksum;
|
||||
|
||||
if( !isValid ){
|
||||
sock->close();
|
||||
return;
|
||||
}
|
||||
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnected;
|
||||
|
||||
if( connectionType == NewInstance ||
|
||||
( connectionType == SecondaryInstance &&
|
||||
options & SingleApplication::Mode::SecondaryNotification ) )
|
||||
{
|
||||
Q_EMIT q->instanceStarted();
|
||||
}
|
||||
|
||||
if (sock->bytesAvailable() > 0){
|
||||
Q_EMIT this->slotDataAvailable( sock, instanceId );
|
||||
}
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
|
||||
{
|
||||
Q_Q(SingleApplication);
|
||||
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
|
||||
{
|
||||
if( closedSocket->bytesAvailable() > 0 )
|
||||
Q_EMIT slotDataAvailable( closedSocket, instanceId );
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::randomSleep()
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
|
||||
QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
|
||||
#else
|
||||
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
|
||||
QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
|
||||
#endif
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::addAppData(const QString &data)
|
||||
{
|
||||
appDataList.push_back(data);
|
||||
}
|
||||
|
||||
QStringList SingleApplicationPrivate::appData() const
|
||||
{
|
||||
return appDataList;
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// 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.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This file is not part of the SingleApplication API. It is used purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or may even be removed.
|
||||
//
|
||||
|
||||
#ifndef SINGLEAPPLICATION_P_H
|
||||
#define SINGLEAPPLICATION_P_H
|
||||
|
||||
#include <QtCore/QSharedMemory>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
#include "singleapplication.h"
|
||||
|
||||
struct InstancesInfo {
|
||||
bool primary;
|
||||
quint32 secondary;
|
||||
qint64 primaryPid;
|
||||
char primaryUser[128];
|
||||
quint16 checksum; // Must be the last field
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
qint64 msgLen = 0;
|
||||
quint32 instanceId = 0;
|
||||
quint8 stage = 0;
|
||||
};
|
||||
|
||||
class SingleApplicationPrivate : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ConnectionType : quint8 {
|
||||
InvalidConnection = 0,
|
||||
NewInstance = 1,
|
||||
SecondaryInstance = 2,
|
||||
Reconnect = 3
|
||||
};
|
||||
enum ConnectionStage : quint8 {
|
||||
StageHeader = 0,
|
||||
StageBody = 1,
|
||||
StageConnected = 2,
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleApplication)
|
||||
|
||||
SingleApplicationPrivate( SingleApplication *q_ptr );
|
||||
~SingleApplicationPrivate() override;
|
||||
|
||||
static QString getUsername();
|
||||
void genBlockServerName();
|
||||
void initializeMemoryBlock() const;
|
||||
void startPrimary();
|
||||
void startSecondary();
|
||||
bool connectToPrimary( int msecs, ConnectionType connectionType );
|
||||
quint16 blockChecksum() const;
|
||||
qint64 primaryPid() const;
|
||||
QString primaryUser() const;
|
||||
void readInitMessageHeader(QLocalSocket *socket);
|
||||
void readInitMessageBody(QLocalSocket *socket);
|
||||
static void randomSleep();
|
||||
void addAppData(const QString &data);
|
||||
QStringList appData() const;
|
||||
|
||||
SingleApplication *q_ptr;
|
||||
QSharedMemory *memory;
|
||||
QLocalSocket *socket;
|
||||
QLocalServer *server;
|
||||
quint32 instanceNumber;
|
||||
QString blockServerName;
|
||||
SingleApplication::Options options;
|
||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
||||
QStringList appDataList;
|
||||
|
||||
public Q_SLOTS:
|
||||
void slotConnectionEstablished();
|
||||
void slotDataAvailable( QLocalSocket*, quint32 );
|
||||
void slotClientConnectionClosed( QLocalSocket*, quint32 );
|
||||
};
|
||||
|
||||
#endif // SINGLEAPPLICATION_P_H
|
||||
@@ -25,10 +25,11 @@ execute_process(
|
||||
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
||||
|
||||
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
|
||||
add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}")
|
||||
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
|
||||
|
||||
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
||||
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||
|
||||
if(IOS)
|
||||
set(PACKAGES ${PACKAGES} Multimedia)
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <QTextDocument>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include <QLocalSocket>
|
||||
#include <QLocalServer>
|
||||
|
||||
#include "logger.h"
|
||||
#include "ui/models/installedAppsModel.h"
|
||||
@@ -28,13 +30,7 @@
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||
#else
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, int timeout,
|
||||
const QString &userData)
|
||||
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
|
||||
#endif
|
||||
{
|
||||
setQuitOnLastWindowClosed(false);
|
||||
|
||||
@@ -115,10 +111,11 @@ void AmneziaApplication::init()
|
||||
qFatal("Android controller initialization failed");
|
||||
}
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
|
||||
m_pageController->goToPageHome();
|
||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_importController->extractConfigFromData(data);
|
||||
m_pageController->goToPageViewConfig();
|
||||
data.clear();
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||
@@ -126,16 +123,16 @@ void AmneziaApplication::init()
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->initialize();
|
||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
|
||||
m_pageController->goToPageHome();
|
||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_importController->extractConfigFromData(data);
|
||||
m_pageController->goToPageViewConfig();
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
|
||||
m_pageController->goToPageHome();
|
||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_pageController->goToPageSettingsBackup();
|
||||
m_settingsController->importBackupFromOutside(filePath);
|
||||
emit m_settingsController->importBackupFromOutside(filePath);
|
||||
});
|
||||
|
||||
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||
@@ -180,16 +177,6 @@ void AmneziaApplication::init()
|
||||
m_pageController->showOnStartup();
|
||||
#endif
|
||||
|
||||
// TODO - fix
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (isPrimary()) {
|
||||
QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
|
||||
qDebug() << "Secondary instance started, showing this window instead";
|
||||
emit m_pageController->raiseMainWindow();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
// Android TextArea clipboard workaround
|
||||
// Text from TextArea always has "text/html" mime-type:
|
||||
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
|
||||
@@ -294,6 +281,24 @@ bool AmneziaApplication::parseCommands()
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
QLocalServer* server = new QLocalServer(this);
|
||||
server->listen(serverName);
|
||||
|
||||
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
|
||||
if (server) {
|
||||
QLocalSocket* clientConnection = server->nextPendingConnection();
|
||||
clientConnection->deleteLater();
|
||||
}
|
||||
emit m_pageController->raiseMainWindow();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||
{
|
||||
return m_engine;
|
||||
|
||||
@@ -53,22 +53,14 @@
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||
#else
|
||||
#define AMNEZIA_BASE_CLASS SingleApplication
|
||||
#define QAPPLICATION_CLASS QApplication
|
||||
#include "singleapplication.h"
|
||||
#define AMNEZIA_BASE_CLASS QApplication
|
||||
#endif
|
||||
|
||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
AmneziaApplication(int &argc, char *argv[]);
|
||||
#else
|
||||
AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
|
||||
SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
|
||||
const QString &userData = {});
|
||||
#endif
|
||||
virtual ~AmneziaApplication();
|
||||
|
||||
void init();
|
||||
@@ -78,6 +70,10 @@ public:
|
||||
void updateTranslator(const QLocale &locale);
|
||||
bool parseCommands();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void startLocalServer();
|
||||
#endif
|
||||
|
||||
QQmlApplicationEngine *qmlEngine() const;
|
||||
QNetworkAccessManager *manager() { return m_nam; }
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- To request network state -->
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
@@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false
|
||||
# For development copy and set local values for these parameters in local.properties
|
||||
#androidCompileSdkVersion=android-34
|
||||
#androidBuildToolsVersion=34.0.0
|
||||
#qtMinSdkVersion=24
|
||||
#qtMinSdkVersion=26
|
||||
#qtTargetSdkVersion=34
|
||||
#androidNdkVersion=26.1.10909125
|
||||
#qtTargetAbiList=x86_64
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.amnezia.vpn.protocol
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.net.IpPrefix
|
||||
import android.net.VpnService
|
||||
@@ -8,9 +7,6 @@ import android.net.VpnService.Builder
|
||||
import android.os.Build
|
||||
import android.system.OsConstants
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.zip.ZipFile
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.os.Messenger
|
||||
import android.provider.Settings
|
||||
import android.view.MotionEvent
|
||||
import android.view.WindowManager.LayoutParams
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
@@ -158,7 +159,7 @@ class AmneziaActivity : QtActivity() {
|
||||
*/
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Create Amnezia activity: $intent")
|
||||
Log.d(TAG, "Create Amnezia activity")
|
||||
loadLibs()
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
@@ -200,7 +201,7 @@ class AmneziaActivity : QtActivity() {
|
||||
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
|
||||
)
|
||||
) {
|
||||
Log.d(
|
||||
Log.v(
|
||||
TAG, "Notification state changed: ${it?.action}, blocked = " +
|
||||
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
|
||||
)
|
||||
@@ -214,7 +215,7 @@ class AmneziaActivity : QtActivity() {
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
Log.d(TAG, "onNewIntent: $intent")
|
||||
Log.v(TAG, "onNewIntent: $intent")
|
||||
intent?.let(::processIntent)
|
||||
}
|
||||
|
||||
@@ -403,7 +404,7 @@ class AmneziaActivity : QtActivity() {
|
||||
@MainThread
|
||||
private fun startVpn(vpnConfig: String) {
|
||||
getVpnProto(vpnConfig)?.let { proto ->
|
||||
Log.d(TAG, "Proto from config: $proto, current proto: $vpnProto")
|
||||
Log.v(TAG, "Proto from config: $proto, current proto: $vpnProto")
|
||||
if (isServiceConnected) {
|
||||
if (proto.serviceClass == vpnProto?.serviceClass) {
|
||||
vpnProto = proto
|
||||
@@ -516,7 +517,7 @@ class AmneziaActivity : QtActivity() {
|
||||
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
onSuccess = {
|
||||
it?.data?.let { uri ->
|
||||
Log.d(TAG, "Save file to $uri")
|
||||
Log.v(TAG, "Save file to $uri")
|
||||
try {
|
||||
contentResolver.openOutputStream(uri)?.use { os ->
|
||||
os.bufferedWriter().use { it.write(data) }
|
||||
@@ -565,7 +566,7 @@ class AmneziaActivity : QtActivity() {
|
||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
onAny = {
|
||||
val uri = it?.data?.toString() ?: ""
|
||||
Log.d(TAG, "Open file: $uri")
|
||||
Log.v(TAG, "Open file: $uri")
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onFileOpened(uri)
|
||||
@@ -720,6 +721,66 @@ class AmneziaActivity : QtActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// workaround for a bug in Qt that causes the mouse click event not to be handled
|
||||
// also disable right-click, as it causes the application to crash
|
||||
private var lastButtonState = 0
|
||||
private fun MotionEvent.fixCopy(): MotionEvent = MotionEvent.obtain(
|
||||
downTime,
|
||||
eventTime,
|
||||
action,
|
||||
pointerCount,
|
||||
(0 until pointerCount).map { i ->
|
||||
MotionEvent.PointerProperties().apply {
|
||||
getPointerProperties(i, this)
|
||||
}
|
||||
}.toTypedArray(),
|
||||
(0 until pointerCount).map { i ->
|
||||
MotionEvent.PointerCoords().apply {
|
||||
getPointerCoords(i, this)
|
||||
}
|
||||
}.toTypedArray(),
|
||||
metaState,
|
||||
MotionEvent.BUTTON_PRIMARY,
|
||||
xPrecision,
|
||||
yPrecision,
|
||||
deviceId,
|
||||
edgeFlags,
|
||||
source,
|
||||
flags
|
||||
)
|
||||
|
||||
private fun handleMouseEvent(ev: MotionEvent, superDispatch: (MotionEvent?) -> Boolean): Boolean {
|
||||
when (ev.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
lastButtonState = ev.buttonState
|
||||
if (ev.buttonState == MotionEvent.BUTTON_SECONDARY) return true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
when (lastButtonState) {
|
||||
MotionEvent.BUTTON_SECONDARY -> return true
|
||||
MotionEvent.BUTTON_PRIMARY -> {
|
||||
val modEvent = ev.fixCopy()
|
||||
return superDispatch(modEvent).apply { modEvent.recycle() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return superDispatch(ev)
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||
if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
|
||||
return handleMouseEvent(ev) { super.dispatchTouchEvent(it) }
|
||||
}
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
|
||||
override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
|
||||
ev?.let { return handleMouseEvent(ev) { super.dispatchTrackballEvent(it) }}
|
||||
return super.dispatchTrackballEvent(ev)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utils methods
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.annotation.MainThread
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import java.net.UnknownHostException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
@@ -127,6 +128,8 @@ open class AmneziaVpnService : VpnService() {
|
||||
|
||||
is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}")
|
||||
|
||||
is UnknownHostException -> onError("Unknown host")
|
||||
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
@@ -297,7 +300,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
|
||||
) {
|
||||
it?.action?.let { action ->
|
||||
Log.d(TAG, "Broadcast request received: $action")
|
||||
Log.v(TAG, "Broadcast request received: $action")
|
||||
when (action) {
|
||||
ACTION_CONNECT -> connect()
|
||||
ACTION_DISCONNECT -> disconnect()
|
||||
@@ -314,7 +317,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
)
|
||||
) {
|
||||
val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
|
||||
Log.d(TAG, "Notification state changed: ${it?.action}, blocked = $state")
|
||||
Log.v(TAG, "Notification state changed: ${it?.action}, blocked = $state")
|
||||
if (state == false) {
|
||||
enableNotification()
|
||||
} else {
|
||||
@@ -447,7 +450,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
serviceNotification.isNotificationEnabled() &&
|
||||
getSystemService<PowerManager>()?.isInteractive != false
|
||||
) {
|
||||
Log.d(TAG, "Launch traffic stats update")
|
||||
Log.v(TAG, "Launch traffic stats update")
|
||||
trafficStats.reset()
|
||||
startTrafficStatsUpdateJob()
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ class AuthActivity : FragmentActivity() {
|
||||
object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
Log.d(TAG, "Authentication succeeded")
|
||||
Log.v(TAG, "Authentication succeeded")
|
||||
QtAndroidController.onAuthResult(true)
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Create Import Config Activity: $intent")
|
||||
Log.v(TAG, "Create Import Config Activity: $intent")
|
||||
intent?.let(::readConfig)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
Log.d(TAG, "onNewIntent: $intent")
|
||||
Log.v(TAG, "onNewIntent: $intent")
|
||||
intent.let(::readConfig)
|
||||
}
|
||||
|
||||
private fun readConfig(intent: Intent) {
|
||||
when (intent.action) {
|
||||
ACTION_SEND -> {
|
||||
Log.d(TAG, "Process SEND action, type: ${intent.type}")
|
||||
Log.v(TAG, "Process SEND action, type: ${intent.type}")
|
||||
when (intent.type) {
|
||||
"application/octet-stream" -> {
|
||||
intent.getUriCompat()?.let { uri ->
|
||||
@@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
ACTION_VIEW -> {
|
||||
Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}")
|
||||
Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
|
||||
when (intent.scheme) {
|
||||
"file", "content" -> {
|
||||
intent.data?.let { uri ->
|
||||
|
||||
@@ -62,7 +62,7 @@ class ServiceNotification(private val context: Context) {
|
||||
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
|
||||
val speedString = if (state == CONNECTED) zeroSpeed else null
|
||||
|
||||
Log.d(TAG, "Build notification: $serverName, $state")
|
||||
Log.v(TAG, "Build notification: $serverName, $state")
|
||||
|
||||
return notificationBuilder
|
||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
||||
@@ -88,17 +88,15 @@ class ServiceNotification(private val context: Context) {
|
||||
fun isNotificationEnabled(): Boolean {
|
||||
if (!context.isNotificationPermissionGranted()) return false
|
||||
if (!notificationManager.areNotificationsEnabled()) return false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
|
||||
?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true
|
||||
}
|
||||
return true
|
||||
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)?.let {
|
||||
it.importance != NotificationManager.IMPORTANCE_NONE
|
||||
} ?: true
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
|
||||
if (context.isNotificationPermissionGranted()) {
|
||||
Log.d(TAG, "Update notification: $serverName, $state")
|
||||
Log.v(TAG, "Update notification: $serverName, $state")
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ object LibraryLoader {
|
||||
System.loadLibrary(libraryName)
|
||||
return
|
||||
} catch (_: UnsatisfiedLinkError) {
|
||||
Log.d(TAG, "Failed to load library, try to extract it from apk")
|
||||
Log.w(TAG, "Failed to load library, try to extract it from apk")
|
||||
}
|
||||
var tempFile: File? = null
|
||||
try {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.amnezia.vpn.util
|
||||
|
||||
import android.content.Context
|
||||
import android.icu.text.DateFormat
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import java.io.File
|
||||
@@ -12,8 +10,6 @@ import java.nio.channels.FileChannel
|
||||
import java.nio.channels.FileLock
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import org.amnezia.vpn.util.Log.Priority.D
|
||||
import org.amnezia.vpn.util.Log.Priority.E
|
||||
@@ -41,11 +37,7 @@ private const val LOG_MAX_FILE_SIZE = 1024 * 1024
|
||||
* | | | create a report and/or terminate the process |
|
||||
*/
|
||||
object Log {
|
||||
private val dateTimeFormat: Any =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
|
||||
else object : ThreadLocal<DateFormat>() {
|
||||
override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
|
||||
}
|
||||
private val dateTimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
|
||||
|
||||
private lateinit var logDir: File
|
||||
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
|
||||
@@ -143,12 +135,7 @@ object Log {
|
||||
}
|
||||
|
||||
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
|
||||
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
|
||||
} else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(dateTimeFormat as ThreadLocal<DateFormat>).get()?.format(Date())
|
||||
}
|
||||
val date = LocalDateTime.now().format(dateTimeFormat)
|
||||
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||
"$tag: $msg\n"
|
||||
}
|
||||
|
||||
@@ -42,18 +42,12 @@ class NetworkState(
|
||||
private val networkCallback: NetworkCallback by lazy(NONE) {
|
||||
object : NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
Log.d(TAG, "onAvailable: $network")
|
||||
Log.v(TAG, "onAvailable: $network")
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
checkNetworkState(network, networkCapabilities)
|
||||
} else {
|
||||
handler.post {
|
||||
checkNetworkState(network, networkCapabilities)
|
||||
}
|
||||
}
|
||||
Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
|
||||
checkNetworkState(network, networkCapabilities)
|
||||
}
|
||||
|
||||
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
@@ -73,11 +67,11 @@ class NetworkState(
|
||||
}
|
||||
|
||||
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
|
||||
Log.d(TAG, "onBlockedStatusChanged: $network, $blocked")
|
||||
Log.v(TAG, "onBlockedStatusChanged: $network, $blocked")
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
Log.d(TAG, "onLost: $network")
|
||||
Log.v(TAG, "onLost: $network")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +81,7 @@ class NetworkState(
|
||||
Log.d(TAG, "Bind network listener")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
} else {
|
||||
val numberAttempts = 300
|
||||
var attemptCount = 0
|
||||
while(true) {
|
||||
@@ -108,8 +102,6 @@ class NetworkState(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
||||
}
|
||||
isListenerBound = true
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.amnezia.vpn.protocol.wireguard
|
||||
|
||||
import android.net.VpnService.Builder
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.launch
|
||||
import org.amnezia.awg.GoBackend
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
@@ -27,6 +28,8 @@ open class Wireguard : Protocol() {
|
||||
|
||||
private var tunnelHandle: Int = -1
|
||||
protected open val ifName: String = "amn0"
|
||||
private lateinit var scope: CoroutineScope
|
||||
private var statusJob: Job? = null
|
||||
|
||||
override val statistics: Statistics
|
||||
get() {
|
||||
@@ -49,46 +52,17 @@ open class Wireguard : Protocol() {
|
||||
|
||||
override fun internalInit() {
|
||||
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
||||
if (this::scope.isInitialized) {
|
||||
scope.cancel()
|
||||
}
|
||||
scope = CoroutineScope(Dispatchers.IO)
|
||||
}
|
||||
|
||||
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
val wireguardConfig = parseConfig(config)
|
||||
val startTime = System.currentTimeMillis()
|
||||
start(wireguardConfig, vpnBuilder, protect)
|
||||
waitForConnection(startTime)
|
||||
state.value = CONNECTED
|
||||
}
|
||||
|
||||
private suspend fun waitForConnection(startTime: Long) {
|
||||
Log.d(TAG, "Waiting for connection")
|
||||
withContext(Dispatchers.IO) {
|
||||
val time = String.format(Locale.ROOT,"%.3f", startTime / 1000.0)
|
||||
try {
|
||||
delay(1000)
|
||||
var log = getLogcat(time)
|
||||
Log.d(TAG, "First waiting log: $log")
|
||||
// check that there is a connection log,
|
||||
// to avoid infinite connection
|
||||
if (!log.contains("Attaching to interface")) {
|
||||
Log.w(TAG, "Logs do not contain a connection log")
|
||||
return@withContext
|
||||
}
|
||||
while (!log.contains("Received handshake response")) {
|
||||
delay(1000)
|
||||
log = getLogcat(time)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to get logcat: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLogcat(time: String): String =
|
||||
ProcessBuilder("logcat", "--buffer=main", "--format=raw", "*:S AmneziaWG/awg0", "-t", time)
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
.inputStream.reader().readText()
|
||||
|
||||
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
||||
val configData = config.getJSONObject("wireguard_config_data")
|
||||
return WireguardConfig.build {
|
||||
@@ -178,6 +152,43 @@ open class Wireguard : Protocol() {
|
||||
tunnelHandle = -1
|
||||
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
|
||||
}
|
||||
launchStatusJob()
|
||||
}
|
||||
|
||||
private fun launchStatusJob() {
|
||||
Log.d(TAG, "Launch status job")
|
||||
statusJob = scope.launch {
|
||||
while (true) {
|
||||
val lastHandshake = getLastHandshake()
|
||||
Log.v(TAG, "lastHandshake=$lastHandshake")
|
||||
if (lastHandshake == 0L) {
|
||||
delay(1000)
|
||||
continue
|
||||
}
|
||||
if (lastHandshake == -2L || lastHandshake > 0L) state.value = CONNECTED
|
||||
else if (lastHandshake == -1L) state.value = DISCONNECTED
|
||||
statusJob = null
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLastHandshake(): Long {
|
||||
if (tunnelHandle == -1) {
|
||||
Log.e(TAG, "Trying to get config of a non-existent tunnel")
|
||||
return -1
|
||||
}
|
||||
val config = GoBackend.awgGetConfig(tunnelHandle)
|
||||
if (config == null) {
|
||||
Log.e(TAG, "Failed to get tunnel config")
|
||||
return -2
|
||||
}
|
||||
val lastHandshake = config.lines().find { it.startsWith("last_handshake_time_sec=") }?.substring(24)?.toLong()
|
||||
if (lastHandshake == null) {
|
||||
Log.e(TAG, "Failed to get last_handshake_time_sec")
|
||||
return -2
|
||||
}
|
||||
return lastHandshake
|
||||
}
|
||||
|
||||
override fun stopVpn() {
|
||||
@@ -185,6 +196,8 @@ open class Wireguard : Protocol() {
|
||||
Log.w(TAG, "Tunnel already down")
|
||||
return
|
||||
}
|
||||
statusJob?.cancel()
|
||||
statusJob = null
|
||||
val handleToClose = tunnelHandle
|
||||
tunnelHandle = -1
|
||||
GoBackend.awgTurnOff(handleToClose)
|
||||
|
||||
@@ -130,8 +130,8 @@ class Xray : Protocol() {
|
||||
LibXray.initXray(assetsPath)
|
||||
val geoDir = File(assetsPath, "geo").absolutePath
|
||||
val configPath = File(context.cacheDir, "config.json")
|
||||
Log.d(TAG, "xray.location.asset: $geoDir")
|
||||
Log.d(TAG, "config: $configPath")
|
||||
Log.v(TAG, "xray.location.asset: $geoDir")
|
||||
Log.v(TAG, "config: $configPath")
|
||||
try {
|
||||
configPath.writeText(configJson)
|
||||
} catch (e: IOException) {
|
||||
|
||||
@@ -2,10 +2,6 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
|
||||
|
||||
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}/cmake/QSimpleCrypto.cmake)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
||||
|
||||
set(APP_ANDROID_MIN_SDK 24)
|
||||
set(APP_ANDROID_MIN_SDK 26)
|
||||
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
|
||||
"The minimum API level supported by the application or library" FORCE)
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "apiController.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
@@ -11,6 +14,7 @@
|
||||
#include "amnezia_application.h"
|
||||
#include "configurators/wireguard_configurator.h"
|
||||
#include "core/enums/apiEnums.h"
|
||||
#include "utilities.h"
|
||||
#include "version.h"
|
||||
|
||||
namespace
|
||||
@@ -33,6 +37,7 @@ namespace
|
||||
constexpr char userCountryCode[] = "user_country_code";
|
||||
constexpr char serverCountryCode[] = "server_country_code";
|
||||
constexpr char serviceType[] = "service_type";
|
||||
constexpr char serviceInfo[] = "service_info";
|
||||
|
||||
constexpr char aesKey[] = "aes_key";
|
||||
constexpr char aesIv[] = "aes_iv";
|
||||
@@ -40,9 +45,10 @@ namespace
|
||||
|
||||
constexpr char apiPayload[] = "api_payload";
|
||||
constexpr char keyPayload[] = "key_payload";
|
||||
}
|
||||
|
||||
const QStringList proxyStorageUrl = { "" };
|
||||
constexpr char apiConfig[] = "api_config";
|
||||
constexpr char authData[] = "auth_data";
|
||||
}
|
||||
|
||||
ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||
{
|
||||
@@ -63,6 +69,28 @@ namespace
|
||||
return ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
||||
const QByteArray &iv = "", const QByteArray &salt = "")
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << "Timeout occurred";
|
||||
return true;
|
||||
} else if (responseBody.contains("html")) {
|
||||
qDebug() << "The response contains an html tag";
|
||||
return true;
|
||||
} else if (checkEncryption) {
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
|
||||
} catch (...) {
|
||||
qDebug() << "Failed to decrypt the data";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ApiController::ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent)
|
||||
@@ -94,8 +122,8 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle
|
||||
configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
||||
} else if (protocol == configKey::awg) {
|
||||
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
||||
auto serverConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
auto containers = serverConfig.value(config_key::containers).toArray();
|
||||
auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
auto containers = newServerConfig.value(config_key::containers).toArray();
|
||||
if (containers.isEmpty()) {
|
||||
return; // todo process error
|
||||
}
|
||||
@@ -114,25 +142,35 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle
|
||||
containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader);
|
||||
container[containerName] = containerConfig;
|
||||
containers.replace(0, container);
|
||||
serverConfig[config_key::containers] = containers;
|
||||
configStr = QString(QJsonDocument(serverConfig).toJson());
|
||||
newServerConfig[config_key::containers] = containers;
|
||||
configStr = QString(QJsonDocument(newServerConfig).toJson());
|
||||
}
|
||||
|
||||
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
|
||||
serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
|
||||
serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
|
||||
serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
|
||||
QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1);
|
||||
serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2);
|
||||
serverConfig[config_key::containers] = newServerConfig.value(config_key::containers);
|
||||
serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName);
|
||||
|
||||
if (apiConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
||||
serverConfig[config_key::configVersion] = apiConfig.value(config_key::configVersion);
|
||||
serverConfig[config_key::description] = apiConfig.value(config_key::description);
|
||||
serverConfig[config_key::name] = apiConfig.value(config_key::name);
|
||||
if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
||||
serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion);
|
||||
serverConfig[config_key::description] = newServerConfig.value(config_key::description);
|
||||
serverConfig[config_key::name] = newServerConfig.value(config_key::name);
|
||||
}
|
||||
|
||||
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
||||
auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString();
|
||||
serverConfig[config_key::defaultContainer] = defaultContainer;
|
||||
|
||||
QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap();
|
||||
map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap());
|
||||
auto apiConfig = QJsonObject::fromVariantMap(map);
|
||||
|
||||
if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
||||
apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject());
|
||||
}
|
||||
|
||||
serverConfig[configKey::apiConfig] = apiConfig;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,6 +184,15 @@ QStringList ApiController::getProxyUrls()
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
QStringList proxyStorageUrl;
|
||||
if (m_isDevEnvironment) {
|
||||
proxyStorageUrl = QStringList { DEV_S3_ENDPOINT };
|
||||
} else {
|
||||
proxyStorageUrl = QStringList { PROD_S3_ENDPOINT };
|
||||
}
|
||||
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrl) {
|
||||
request.setUrl(proxyStorageUrl);
|
||||
reply = amnApp->manager()->get(request);
|
||||
@@ -166,11 +213,23 @@ QStringList ApiController::getProxyUrls()
|
||||
EVP_PKEY *privateKey = nullptr;
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
QByteArray key = PROD_PROXY_STORAGE_KEY;
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
privateKey = rsa.getPrivateKeyFromByteArray(key, "");
|
||||
responseBody = rsa.decrypt(encryptedResponseBody, privateKey, RSA_PKCS1_PADDING);
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray hashResult = hash.result().toHex();
|
||||
|
||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||
|
||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||
} else {
|
||||
responseBody = encryptedResponseBody;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading private key from environment variables or decrypting payload";
|
||||
return {};
|
||||
}
|
||||
@@ -292,38 +351,44 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) {
|
||||
responseBody = reply->readAll();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
|
||||
m_proxyUrls = getProxyUrls();
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator);
|
||||
for (const QString &proxyUrl : m_proxyUrls) {
|
||||
qDebug() << "Go to the next endpoint";
|
||||
request.setUrl(QString("%1v1/services").arg(proxyUrl));
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = amnApp->manager()->get(request);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
if (reply->error() != QNetworkReply::NetworkError::TimeoutError
|
||||
&& reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
|
||||
|
||||
responseBody = reply->readAll();
|
||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, responseBody, false)) {
|
||||
break;
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
responseBody = reply->readAll();
|
||||
auto errorCode = checkErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig)
|
||||
const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData,
|
||||
QJsonObject &serverConfig)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(7000);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
@@ -339,6 +404,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||
}
|
||||
apiPayload[configKey::serviceType] = serviceType;
|
||||
apiPayload[configKey::uuid] = installationUuid;
|
||||
if (!authData.isEmpty()) {
|
||||
apiPayload[configKey::authData] = authData;
|
||||
}
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||
@@ -357,10 +425,11 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||
|
||||
EVP_PKEY *publicKey = nullptr;
|
||||
try {
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
publicKey = rsa.getPublicKeyFromByteArray(key);
|
||||
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading public key from environment variables";
|
||||
return ErrorCode::ApiMissingAgwPublicKey;
|
||||
}
|
||||
@@ -370,14 +439,16 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
qCritical() << "error when encrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
QJsonObject requestBody;
|
||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||
|
||||
QNetworkReply *reply = manager.post(request, QJsonDocument(requestBody).toJson());
|
||||
QNetworkReply *reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
|
||||
QEventLoop wait;
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
@@ -386,37 +457,43 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) {
|
||||
if (m_proxyUrls.isEmpty()) {
|
||||
m_proxyUrls = getProxyUrls();
|
||||
}
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
m_proxyUrls = getProxyUrls();
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator);
|
||||
for (const QString &proxyUrl : m_proxyUrls) {
|
||||
qDebug() << "Go to the next endpoint";
|
||||
request.setUrl(QString("%1v1/config").arg(proxyUrl));
|
||||
reply = manager.post(request, QJsonDocument(requestBody).toJson());
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
if (reply->error() != QNetworkReply::NetworkError::TimeoutError
|
||||
&& reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
|
||||
|
||||
encryptedResponseBody = reply->readAll();
|
||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
break;
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
auto errorCode = checkErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
reply->deleteLater();
|
||||
try {
|
||||
auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig);
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
qCritical() << "error when decrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
|
||||
@@ -21,7 +21,7 @@ public slots:
|
||||
|
||||
ErrorCode getServicesList(QByteArray &responseBody);
|
||||
ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig);
|
||||
const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig);
|
||||
|
||||
signals:
|
||||
void errorOccurred(ErrorCode errorCode);
|
||||
|
||||
@@ -96,6 +96,7 @@ namespace amnezia
|
||||
|
||||
// import and install errors
|
||||
ImportInvalidConfigError = 900,
|
||||
ImportOpenConfigError = 901,
|
||||
|
||||
// Android errors
|
||||
AndroidError = 1000,
|
||||
@@ -107,6 +108,7 @@ namespace amnezia
|
||||
ApiConfigTimeoutError = 1103,
|
||||
ApiConfigSslError = 1104,
|
||||
ApiMissingAgwPublicKey = 1105,
|
||||
ApiConfigDecryptionError = 1106,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
|
||||
@@ -50,6 +50,7 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
|
||||
|
||||
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
||||
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
|
||||
|
||||
// Android errors
|
||||
case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
||||
@@ -61,6 +62,7 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break;
|
||||
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
|
||||
case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break;
|
||||
case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break;
|
||||
|
||||
// QFile errors
|
||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
|
||||
@@ -78,7 +78,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (supportDnsUtils() && !dnsutils()->restoreResolvers()) {
|
||||
if (!dnsutils()->restoreResolvers()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -165,10 +165,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||
}
|
||||
|
||||
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
||||
if (!supportDnsUtils()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
||||
QList<QHostAddress> resolvers;
|
||||
@@ -423,13 +419,8 @@ bool Daemon::deactivate(bool emitSignals) {
|
||||
}
|
||||
|
||||
// Cleanup DNS
|
||||
if (supportDnsUtils() && !dnsutils()->restoreResolvers()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!wgutils()->interfaceExists()) {
|
||||
logger.warning() << "Wireguard interface does not exist.";
|
||||
return false;
|
||||
if (!dnsutils()->restoreResolvers()) {
|
||||
logger.warning() << "Failed to restore DNS resolvers.";
|
||||
}
|
||||
|
||||
// Cleanup peers and routing
|
||||
@@ -449,13 +440,9 @@ bool Daemon::deactivate(bool emitSignals) {
|
||||
}
|
||||
m_excludedAddrSet.clear();
|
||||
|
||||
// Delete the interface
|
||||
if (!wgutils()->deleteInterface()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connections.clear();
|
||||
return true;
|
||||
// Delete the interface
|
||||
return wgutils()->deleteInterface();
|
||||
}
|
||||
|
||||
QString Daemon::logs() {
|
||||
|
||||
@@ -69,7 +69,6 @@ class Daemon : public QObject {
|
||||
virtual WireguardUtils* wgutils() const = 0;
|
||||
virtual bool supportIPUtils() const { return false; }
|
||||
virtual IPUtils* iputils() { return nullptr; }
|
||||
virtual bool supportDnsUtils() const { return false; }
|
||||
virtual DnsUtils* dnsutils() { return nullptr; }
|
||||
|
||||
static bool parseStringList(const QJsonObject& obj, const QString& name,
|
||||
|
||||
@@ -92,6 +92,17 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||
|
||||
logger.debug() << "Command received:" << type;
|
||||
|
||||
// It is expected that sometimes the client will request backend logs
|
||||
// before the first authentication. In these cases we just return empty
|
||||
// logs.
|
||||
if (type == "logs") {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "logs");
|
||||
obj.insert("logs", "");
|
||||
write(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "activate") {
|
||||
InterfaceConfig config;
|
||||
if (!Daemon::parseConfig(obj, config)) {
|
||||
@@ -115,8 +126,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||
if (type == "status") {
|
||||
QJsonObject obj = Daemon::instance()->getStatus();
|
||||
obj.insert("type", "status");
|
||||
m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
||||
m_socket->write("\n");
|
||||
write(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,8 +134,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "logs");
|
||||
obj.insert("logs", Daemon::instance()->logs().replace("\n", "|"));
|
||||
m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
||||
m_socket->write("\n");
|
||||
write(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,13 +15,24 @@
|
||||
#include "platforms/ios/QtAppDelegate-C-Interface.h"
|
||||
#endif
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
bool isAnotherInstanceRunning()
|
||||
{
|
||||
QLocalSocket socket;
|
||||
socket.connectToServer("AmneziaVPNInstance");
|
||||
if (socket.waitForConnected(500)) {
|
||||
qWarning() << "AmneziaVPN is already running";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Migrations migrationsManager;
|
||||
migrationsManager.doMigrations();
|
||||
|
||||
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
AllowSetForegroundWindow(ASFW_ANY);
|
||||
#endif
|
||||
@@ -32,16 +43,14 @@ int main(int argc, char *argv[])
|
||||
qputenv("ANDROID_OPENSSL_SUFFIX", "_3");
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
AmneziaApplication app(argc, argv);
|
||||
#else
|
||||
AmneziaApplication app(argc, argv, true,
|
||||
SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification);
|
||||
|
||||
if (!app.isPrimary()) {
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (isAnotherInstanceRunning()) {
|
||||
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
|
||||
return app.exec();
|
||||
}
|
||||
app.startLocalServer();
|
||||
#endif
|
||||
|
||||
// Allow to raise app window if secondary instance launched
|
||||
|
||||
@@ -34,8 +34,8 @@ LocalSocketController::LocalSocketController() {
|
||||
m_socket = new QLocalSocket(this);
|
||||
connect(m_socket, &QLocalSocket::connected, this,
|
||||
&LocalSocketController::daemonConnected);
|
||||
connect(m_socket, &QLocalSocket::disconnected, this,
|
||||
&LocalSocketController::disconnected);
|
||||
connect(m_socket, &QLocalSocket::disconnected, this,
|
||||
[&] { errorOccurred(QLocalSocket::PeerClosedError); });
|
||||
connect(m_socket, &QLocalSocket::errorOccurred, this,
|
||||
&LocalSocketController::errorOccurred);
|
||||
connect(m_socket, &QLocalSocket::readyRead, this,
|
||||
|
||||
@@ -22,7 +22,6 @@ class LinuxDaemon final : public Daemon {
|
||||
|
||||
protected:
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
bool supportDnsUtils() const override { return true; }
|
||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||
bool supportIPUtils() const override { return true; }
|
||||
IPUtils* iputils() override { return m_iputils; }
|
||||
|
||||
@@ -21,7 +21,6 @@ class MacOSDaemon final : public Daemon {
|
||||
|
||||
protected:
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
bool supportDnsUtils() const override { return true; }
|
||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||
bool supportIPUtils() const override { return true; }
|
||||
IPUtils* iputils() override { return m_iputils; }
|
||||
|
||||
@@ -26,7 +26,6 @@ class WindowsDaemon final : public Daemon {
|
||||
protected:
|
||||
bool run(Op op, const InterfaceConfig& config) override;
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
bool supportDnsUtils() const override { return true; }
|
||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -502,7 +502,7 @@ QString WindowsSplitTunnel::convertPath(const QString& path) {
|
||||
// device should contain : for e.g C:
|
||||
return "";
|
||||
}
|
||||
QByteArray buffer(2048, 0xFF);
|
||||
QByteArray buffer(2048, 0xFFu);
|
||||
auto ok = QueryDosDeviceW(qUtf16Printable(driveLetter),
|
||||
(wchar_t*)buffer.data(), buffer.size() / 2);
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
|
||||
}
|
||||
if (result != NO_ERROR) {
|
||||
logger.error() << "Failed to create route to"
|
||||
<< logger.sensitive(prefix.toString())
|
||||
<< prefix.toString()
|
||||
<< "result:" << result;
|
||||
}
|
||||
return result == NO_ERROR;
|
||||
@@ -265,7 +265,7 @@ bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
|
||||
}
|
||||
if (result != NO_ERROR) {
|
||||
logger.error() << "Failed to delete route to"
|
||||
<< logger.sensitive(prefix.toString())
|
||||
<< prefix.toString()
|
||||
<< "result:" << result;
|
||||
}
|
||||
return result == NO_ERROR;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include "platforms/windows/windowsutils.h"
|
||||
|
||||
constexpr const char* VPN_NAME = "AmneziaVPN";
|
||||
constexpr const char* WIREGUARD_DIR = "WireGuard";
|
||||
constexpr const char* WIREGUARD_DIR = "AmneziaWG";
|
||||
constexpr const char* DATA_DIR = "Data";
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -237,7 +237,7 @@ private:
|
||||
mutable SecureQSettings m_settings;
|
||||
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevGatewayEnv;
|
||||
bool m_isDevGatewayEnv = false;
|
||||
};
|
||||
|
||||
#endif // SETTINGS_H
|
||||
@@ -34,13 +34,13 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
|
||||
|
||||
void ConnectionController::openConnection()
|
||||
{
|
||||
// #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
// if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
|
||||
// {
|
||||
// emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
|
||||
// return;
|
||||
// }
|
||||
// #endif
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
|
||||
{
|
||||
emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||
QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||
@@ -51,6 +51,9 @@ void ConnectionController::openConnection()
|
||||
if (configVersion == ApiConfigSources::Telegram
|
||||
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||
emit updateApiConfigFromTelegram();
|
||||
} else if (configVersion == ApiConfigSources::AmneziaGateway
|
||||
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||
emit updateApiConfigFromGateway();
|
||||
} else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) {
|
||||
qDebug() << "attempt to update api config by end_date event";
|
||||
if (configVersion == ApiConfigSources::Telegram) {
|
||||
|
||||
@@ -39,11 +39,12 @@ namespace
|
||||
const QString amneziaConfigPatternUserName = "userName";
|
||||
const QString amneziaConfigPatternPassword = "password";
|
||||
const QString amneziaFreeConfigPattern = "api_key";
|
||||
const QString amneziaPremiumConfigPattern = "auth_data";
|
||||
const QString backupPattern = "Servers/serversList";
|
||||
|
||||
if (config.contains(backupPattern)) {
|
||||
return ConfigTypes::Backup;
|
||||
} else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern)
|
||||
} else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern) || config.contains(amneziaPremiumConfigPattern)
|
||||
|| (config.contains(amneziaConfigPatternHostName) && config.contains(amneziaConfigPatternUserName)
|
||||
&& config.contains(amneziaConfigPatternPassword))) {
|
||||
return ConfigTypes::Amnezia;
|
||||
@@ -84,7 +85,7 @@ bool ImportController::extractConfigFromFile(const QString &fileName)
|
||||
return extractConfigFromData(data);
|
||||
}
|
||||
|
||||
emit importErrorOccurred(tr("Unable to open file"), false);
|
||||
emit importErrorOccurred(ErrorCode::ImportOpenConfigError, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -188,12 +189,12 @@ bool ImportController::extractConfigFromData(QString data)
|
||||
if (!m_serversModel->getServersCount()) {
|
||||
emit restoreAppConfig(config.toUtf8());
|
||||
} else {
|
||||
emit importErrorOccurred(tr("Invalid configuration file"), false);
|
||||
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ConfigTypes::Invalid: {
|
||||
emit importErrorOccurred(tr("Invalid configuration file"), false);
|
||||
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ public slots:
|
||||
|
||||
signals:
|
||||
void importFinished();
|
||||
void importErrorOccurred(const QString &errorMessage, bool goToPageHome);
|
||||
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
|
||||
|
||||
void qrDecodingFinished();
|
||||
|
||||
@@ -32,32 +32,8 @@ namespace
|
||||
constexpr char availableCountries[] = "available_countries";
|
||||
|
||||
constexpr char apiConfig[] = "api_config";
|
||||
constexpr char authData[] = "auth_data";
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QString getNextDriverLetter()
|
||||
{
|
||||
QProcess drivesProc;
|
||||
drivesProc.start("wmic logicaldisk get caption");
|
||||
drivesProc.waitForFinished();
|
||||
QString drives = drivesProc.readAll();
|
||||
qDebug() << drives;
|
||||
|
||||
QString letters = "CFGHIJKLMNOPQRSTUVWXYZ";
|
||||
QString letter;
|
||||
for (int i = letters.size() - 1; i > 0; i--) {
|
||||
letter = letters.at(i);
|
||||
if (!drives.contains(letter + ":"))
|
||||
break;
|
||||
}
|
||||
if (letter == "C:") {
|
||||
// set err info
|
||||
qDebug() << "Can't find free drive letter";
|
||||
return "";
|
||||
}
|
||||
return letter;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
InstallController::InstallController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
|
||||
@@ -135,10 +111,10 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
||||
containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader;
|
||||
} else if (container == DockerContainer::Sftp) {
|
||||
containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName);
|
||||
containerConfig.insert(config_key::password, Utils::getRandomString(10));
|
||||
containerConfig.insert(config_key::password, Utils::getRandomString(16));
|
||||
} else if (container == DockerContainer::Socks5Proxy) {
|
||||
containerConfig.insert(config_key::userName, protocols::socks5Proxy::defaultUserName);
|
||||
containerConfig.insert(config_key::password, Utils::getRandomString(10));
|
||||
containerConfig.insert(config_key::password, Utils::getRandomString(16));
|
||||
}
|
||||
|
||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
||||
@@ -667,7 +643,7 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw
|
||||
QString hostname = serverCredentials.hostName;
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
mountPath = getNextDriverLetter() + ":";
|
||||
mountPath = Utils::getNextDriverLetter() + ":";
|
||||
// QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3")
|
||||
// .arg(labelTftpUserNameText())
|
||||
// .arg(labelTftpPortText())
|
||||
@@ -768,7 +744,7 @@ bool InstallController::checkSshConnection(QSharedPointer<ServerController> serv
|
||||
} else {
|
||||
if (output.contains(tr("Please login as the user"))) {
|
||||
output.replace("\n", "");
|
||||
emit installationErrorOccurred(output);
|
||||
emit wrongInstallationUser(output);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -826,7 +802,7 @@ bool InstallController::installServiceFromApi()
|
||||
|
||||
ErrorCode errorCode = apiController.getConfigForService(m_settings->getInstallationUuid(true), m_apiServicesModel->getCountryCode(),
|
||||
m_apiServicesModel->getSelectedServiceType(),
|
||||
m_apiServicesModel->getSelectedServiceProtocol(), "", serverConfig);
|
||||
m_apiServicesModel->getSelectedServiceProtocol(), "", QJsonObject(), serverConfig);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit installationErrorOccurred(errorCode);
|
||||
return false;
|
||||
@@ -853,24 +829,25 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin
|
||||
|
||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
||||
auto authData = serverConfig.value(configKey::authData).toObject();
|
||||
|
||||
QJsonObject newServerConfig;
|
||||
ErrorCode errorCode =
|
||||
apiController.getConfigForService(m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(),
|
||||
apiConfig.value(configKey::serviceType).toString(),
|
||||
apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode, newServerConfig);
|
||||
ErrorCode errorCode = apiController.getConfigForService(
|
||||
m_settings->getInstallationUuid(true), apiConfig.value(configKey::userCountryCode).toString(),
|
||||
apiConfig.value(configKey::serviceType).toString(), apiConfig.value(configKey::serviceProtocol).toString(), newCountryCode,
|
||||
authData, newServerConfig);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit installationErrorOccurred(errorCode);
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject();
|
||||
newApiConfig.insert(configKey::serviceInfo, apiConfig.value(configKey::serviceInfo));
|
||||
newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode));
|
||||
newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType));
|
||||
newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol));
|
||||
|
||||
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
||||
newServerConfig.insert(configKey::authData, authData);
|
||||
m_serversModel->editServer(newServerConfig, serverIndex);
|
||||
|
||||
if (reloadServiceConfig) {
|
||||
|
||||
@@ -75,8 +75,8 @@ signals:
|
||||
void removeAllContainersFinished(const QString &finishedMessage);
|
||||
void removeProcessedContainerFinished(const QString &finishedMessage);
|
||||
|
||||
void installationErrorOccurred(const QString &errorMessage);
|
||||
void installationErrorOccurred(ErrorCode errorCode);
|
||||
void wrongInstallationUser(const QString &message);
|
||||
|
||||
void serverAlreadyExists(int serverIndex);
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
|
||||
case CountryNameRole: {
|
||||
return countryInfo.value(configKey::serverCountryName).toString();
|
||||
}
|
||||
case CountryImageCodeRole: {
|
||||
return countryInfo.value(configKey::serverCountryCode).toString().toUpper();
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
@@ -76,5 +79,6 @@ QHash<int, QByteArray> ApiCountryModel::roleNames() const
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[CountryNameRole] = "countryName";
|
||||
roles[CountryCodeRole] = "countryCode";
|
||||
roles[CountryImageCodeRole] = "countryImageCode";
|
||||
return roles;
|
||||
}
|
||||
@@ -11,7 +11,8 @@ class ApiCountryModel : public QAbstractListModel
|
||||
public:
|
||||
enum Roles {
|
||||
CountryNameRole = Qt::UserRole + 1,
|
||||
CountryCodeRole
|
||||
CountryCodeRole,
|
||||
CountryImageCodeRole
|
||||
};
|
||||
|
||||
explicit ApiCountryModel(QObject *parent = nullptr);
|
||||
|
||||
@@ -77,6 +77,7 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
|
||||
{
|
||||
beginResetModel();
|
||||
m_clientsTable = QJsonArray();
|
||||
endResetModel();
|
||||
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
|
||||
@@ -90,10 +91,10 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
|
||||
const QByteArray clientsTableString = serverController->getTextFileFromContainer(container, credentials, clientsTableFile, error);
|
||||
if (error != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get the clientsTable file from the server";
|
||||
endResetModel();
|
||||
return error;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
|
||||
|
||||
if (m_clientsTable.isEmpty()) {
|
||||
@@ -601,5 +602,6 @@ QHash<int, QByteArray> ClientManagementModel::roleNames() const
|
||||
roles[LatestHandshakeRole] = "latestHandshake";
|
||||
roles[DataReceivedRole] = "dataReceived";
|
||||
roles[DataSentRole] = "dataSent";
|
||||
roles[AllowedIpsRole] = "allowedIps";
|
||||
return roles;
|
||||
}
|
||||
@@ -771,5 +771,5 @@ const QString ServersModel::getDefaultServerImagePathCollapsed()
|
||||
if (countryCode.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode);
|
||||
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode.toUpper());
|
||||
}
|
||||
@@ -84,7 +84,7 @@ DrawerType2 {
|
||||
Layout.topMargin: 16
|
||||
|
||||
text: qsTr("Share")
|
||||
imageSource: "qrc:/images/controls/share-2.svg"
|
||||
leftImageSource: "qrc:/images/controls/share-2.svg"
|
||||
|
||||
KeyNavigation.tab: copyConfigTextButton
|
||||
|
||||
@@ -120,7 +120,7 @@ DrawerType2 {
|
||||
borderWidth: 1
|
||||
|
||||
text: qsTr("Copy")
|
||||
imageSource: "qrc:/images/controls/copy.svg"
|
||||
leftImageSource: "qrc:/images/controls/copy.svg"
|
||||
|
||||
Keys.onReturnPressed: { copyConfigTextButton.clicked() }
|
||||
Keys.onEnterPressed: { copyConfigTextButton.clicked() }
|
||||
@@ -143,7 +143,7 @@ DrawerType2 {
|
||||
borderWidth: 1
|
||||
|
||||
text: qsTr("Copy config string")
|
||||
imageSource: "qrc:/images/controls/copy.svg"
|
||||
leftImageSource: "qrc:/images/controls/copy.svg"
|
||||
|
||||
KeyNavigation.tab: showSettingsButton
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ Button {
|
||||
property int borderWidth: 0
|
||||
property int borderFocusedWidth: 1
|
||||
|
||||
property string imageSource
|
||||
property string leftImageSource
|
||||
property string rightImageSource
|
||||
property string leftImageColor: textColor
|
||||
property string leftImageColor
|
||||
property bool changeLeftImageSize: true
|
||||
|
||||
property bool squareLeftSide: false
|
||||
|
||||
@@ -127,18 +128,23 @@ Button {
|
||||
anchors.centerIn: parent
|
||||
|
||||
Image {
|
||||
Layout.preferredHeight: 20
|
||||
Layout.preferredWidth: 20
|
||||
|
||||
source: root.imageSource
|
||||
visible: root.imageSource === "" ? false : true
|
||||
id: leftImage
|
||||
source: root.leftImageSource
|
||||
visible: root.leftImageSource === "" ? false : true
|
||||
|
||||
layer {
|
||||
enabled: true
|
||||
enabled: leftImageColor !== "" ? true : false
|
||||
effect: ColorOverlay {
|
||||
color: leftImageColor
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.changeLeftImageSize) {
|
||||
leftImage.Layout.preferredHeight = 20
|
||||
leftImage.Layout.preferredWidth = 20
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ButtonTextType {
|
||||
|
||||
@@ -14,7 +14,7 @@ Popup {
|
||||
visible: false
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
|
||||
color: AmneziaStyle.color.translucentMidnightBlack
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
|
||||
@@ -19,7 +19,7 @@ RadioButton {
|
||||
|
||||
property string textColor: AmneziaStyle.color.midnightBlack
|
||||
|
||||
property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3)
|
||||
property string pressedBorderColor: AmneziaStyle.color.softGoldenApricot
|
||||
property string selectedBorderColor: AmneziaStyle.color.goldenApricot
|
||||
property string defaultBodredColor: AmneziaStyle.color.transparent
|
||||
property int borderWidth: 0
|
||||
|
||||
@@ -145,6 +145,7 @@ Button {
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
enabled: root.enabled
|
||||
|
||||
onEntered: {
|
||||
backgroundRect.color = root.hoveredColor
|
||||
|
||||
@@ -92,7 +92,7 @@ Item {
|
||||
id: background
|
||||
|
||||
anchors.fill: parent
|
||||
color: root.isCollapsed ? AmneziaStyle.color.transparent : Qt.rgba(14/255, 14/255, 17/255, 0.8)
|
||||
color: root.isCollapsed ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack
|
||||
|
||||
Behavior on color {
|
||||
PropertyAnimation { duration: 200 }
|
||||
|
||||
@@ -24,7 +24,7 @@ Popup {
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
visible: root.closeButtonVisible
|
||||
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
|
||||
color: AmneziaStyle.color.translucentMidnightBlack
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
|
||||
@@ -183,7 +183,7 @@ Item {
|
||||
|
||||
focusPolicy: Qt.NoFocus
|
||||
text: root.buttonText
|
||||
imageSource: root.buttonImageSource
|
||||
leftImageSource: root.buttonImageSource
|
||||
|
||||
anchors.top: content.top
|
||||
anchors.bottom: content.bottom
|
||||
|
||||
@@ -14,7 +14,7 @@ Popup {
|
||||
visible: false
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
|
||||
color: AmneziaStyle.color.translucentMidnightBlack
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
|
||||
@@ -22,5 +22,9 @@ QtObject {
|
||||
readonly property color sheerWhite: Qt.rgba(1, 1, 1, 0.12)
|
||||
readonly property color translucentWhite: Qt.rgba(1, 1, 1, 0.08)
|
||||
readonly property color barelyTranslucentWhite: Qt.rgba(1, 1, 1, 0.05)
|
||||
readonly property color translucentMidnightBlack: Qt.rgba(14/255, 14/255, 17/255, 0.8)
|
||||
readonly property color softGoldenApricot: Qt.rgba(251/255, 178/255, 106/255, 0.3)
|
||||
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
|
||||
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,6 @@ PageType {
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
disabledColor: AmneziaStyle.color.mutedGray
|
||||
textColor: AmneziaStyle.color.mutedGray
|
||||
leftImageColor: AmneziaStyle.color.transparent
|
||||
borderWidth: 0
|
||||
|
||||
buttonTextLabel.lineHeight: 20
|
||||
@@ -110,7 +109,7 @@ PageType {
|
||||
|
||||
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
|
||||
|
||||
imageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
|
||||
leftImageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
|
||||
rightImageSource: "qrc:/images/controls/chevron-down.svg"
|
||||
|
||||
Keys.onEnterPressed: splitTunnelingButton.clicked()
|
||||
@@ -166,6 +165,7 @@ PageType {
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: 0
|
||||
|
||||
Component.onCompleted: {
|
||||
drawer.collapsedHeight = collapsed.implicitHeight
|
||||
@@ -267,18 +267,39 @@ PageType {
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 89 : 44
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
|
||||
spacing: 0
|
||||
|
||||
Image {
|
||||
Layout.rightMargin: 8
|
||||
visible: source !== ""
|
||||
source: ServersModel.defaultServerImagePathCollapsed
|
||||
}
|
||||
BasicButtonType {
|
||||
enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsed
|
||||
hoverEnabled: enabled
|
||||
|
||||
implicitHeight: 36
|
||||
|
||||
leftPadding: 16
|
||||
rightPadding: 16
|
||||
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
disabledColor: AmneziaStyle.color.transparent
|
||||
textColor: AmneziaStyle.color.mutedGray
|
||||
|
||||
buttonTextLabel.lineHeight: 16
|
||||
buttonTextLabel.font.pixelSize: 13
|
||||
buttonTextLabel.font.weight: 400
|
||||
|
||||
LabelTextType {
|
||||
id: collapsedServerMenuDescription
|
||||
text: drawer.isCollapsed ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded
|
||||
leftImageSource: ServersModel.defaultServerImagePathCollapsed
|
||||
changeLeftImageSize: false
|
||||
|
||||
rightImageSource: hoverEnabled ? "qrc:/images/controls/chevron-down.svg" : ""
|
||||
|
||||
onClicked: {
|
||||
ServersModel.processedIndex = ServersModel.defaultIndex
|
||||
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,8 +337,8 @@ PageType {
|
||||
|
||||
rootButtonImageColor: AmneziaStyle.color.midnightBlack
|
||||
rootButtonBackgroundColor: AmneziaStyle.color.paleGray
|
||||
rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8)
|
||||
rootButtonBackgroundPressedColor: Qt.rgba(215, 216, 219, 0.65)
|
||||
rootButtonBackgroundHoveredColor: AmneziaStyle.color.mistyGray
|
||||
rootButtonBackgroundPressedColor: AmneziaStyle.color.cloudyGray
|
||||
rootButtonHoveredBorderColor: AmneziaStyle.color.transparent
|
||||
rootButtonDefaultBorderColor: AmneziaStyle.color.transparent
|
||||
rootButtonTextTopMargin: 8
|
||||
|
||||
@@ -90,7 +90,7 @@ PageType {
|
||||
Layout.rightMargin: 32
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
source: "qrc:/countriesFlags/images/flagKit/" + countryCode + ".svg"
|
||||
source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,8 +132,8 @@ PageType {
|
||||
implicitHeight: 32
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
textColor: AmneziaStyle.color.vibrantRed
|
||||
|
||||
text: qsTr("Reload API config")
|
||||
@@ -172,8 +172,8 @@ PageType {
|
||||
implicitHeight: 32
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
textColor: AmneziaStyle.color.vibrantRed
|
||||
|
||||
text: qsTr("Remove from application")
|
||||
|
||||
@@ -16,83 +16,82 @@ PageType {
|
||||
|
||||
defaultActiveFocusItem: focusItem
|
||||
|
||||
FlickableType {
|
||||
id: fl
|
||||
ColumnLayout {
|
||||
id: header
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
contentHeight: content.height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
spacing: 0
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
Item {
|
||||
id: focusItem
|
||||
KeyNavigation.tab: backButton
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
id: focusItem
|
||||
KeyNavigation.tab: backButton
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
Layout.topMargin: 20
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
Layout.topMargin: 20
|
||||
// KeyNavigation.tab: fileButton.rightButton
|
||||
}
|
||||
}
|
||||
|
||||
HeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.bottomMargin: 32
|
||||
HeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
headerText: qsTr("VPN by Amnezia")
|
||||
descriptionText: qsTr("Choose a VPN service that suits your needs.")
|
||||
}
|
||||
headerText: qsTr("VPN by Amnezia")
|
||||
descriptionText: qsTr("Choose a VPN service that suits your needs.")
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: containers
|
||||
width: parent.width
|
||||
height: containers.contentItem.height
|
||||
spacing: 16
|
||||
ListView {
|
||||
id: servicesListView
|
||||
anchors.top: header.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: 16
|
||||
spacing: 0
|
||||
|
||||
currentIndex: 1
|
||||
interactive: false
|
||||
model: ApiServicesModel
|
||||
currentIndex: 1
|
||||
clip: true
|
||||
model: ApiServicesModel
|
||||
|
||||
delegate: Item {
|
||||
implicitWidth: containers.width
|
||||
implicitHeight: delegateContent.implicitHeight
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
ColumnLayout {
|
||||
id: delegateContent
|
||||
delegate: Item {
|
||||
implicitWidth: servicesListView.width
|
||||
implicitHeight: delegateContent.implicitHeight
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
ColumnLayout {
|
||||
id: delegateContent
|
||||
|
||||
CardWithIconsType {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
CardWithIconsType {
|
||||
id: card
|
||||
|
||||
headerText: name
|
||||
bodyText: cardDescription
|
||||
footerText: price
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
headerText: name
|
||||
bodyText: cardDescription
|
||||
footerText: price
|
||||
|
||||
onClicked: {
|
||||
if (isServiceAvailable) {
|
||||
ApiServicesModel.setServiceIndex(index)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
|
||||
}
|
||||
}
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
enabled: isServiceAvailable
|
||||
|
||||
onClicked: {
|
||||
if (isServiceAvailable) {
|
||||
ApiServicesModel.setServiceIndex(index)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,9 @@ PageType {
|
||||
KeyNavigation.tab: textKey.textField
|
||||
}
|
||||
|
||||
|
||||
HeaderType {
|
||||
property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible()
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.rightMargin: 16
|
||||
@@ -56,7 +57,7 @@ PageType {
|
||||
|
||||
headerText: qsTr("Connection")
|
||||
|
||||
actionButtonImage: PageController.isStartPageVisible() ? "qrc:/images/controls/more-vertical.svg" : ""
|
||||
actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : ""
|
||||
actionButtonFunction: function() {
|
||||
moreActionsDrawer.open()
|
||||
}
|
||||
@@ -67,18 +68,19 @@ PageType {
|
||||
parent: root
|
||||
|
||||
anchors.fill: parent
|
||||
expandedHeight: root.height * 0.35
|
||||
expandedHeight: root.height * 0.5
|
||||
|
||||
expandedContent: ColumnLayout {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("Settings")
|
||||
}
|
||||
@@ -87,9 +89,12 @@ PageType {
|
||||
id: switcher
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("Enable logs")
|
||||
|
||||
visible: PageController.isStartPageVisible()
|
||||
checked: SettingsController.isLoggingEnabled
|
||||
onCheckedChanged: {
|
||||
if (checked !== SettingsController.isLoggingEnabled) {
|
||||
@@ -98,6 +103,28 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: supportUuid
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
text: qsTr("Support tag")
|
||||
descriptionText: SettingsController.getInstallationUuid()
|
||||
|
||||
descriptionOnTop: true
|
||||
|
||||
rightImageSource: "qrc:/images/controls/copy.svg"
|
||||
rightImageColor: AmneziaStyle.color.paleGray
|
||||
|
||||
visible: SettingsController.getInstallationUuid() !== ""
|
||||
clickedFunction: function() {
|
||||
GC.copyToClipBoard(descriptionText)
|
||||
PageController.showNotificationMessage(qsTr("Copied"))
|
||||
if (!GC.isMobile()) {
|
||||
this.rightButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ PageType {
|
||||
Connections {
|
||||
target: ImportController
|
||||
|
||||
function onImportErrorOccurred(errorMessage, goToPageHome) {
|
||||
function onImportErrorOccurred(error, goToPageHome) {
|
||||
if (goToPageHome) {
|
||||
PageController.goToStartPage()
|
||||
} else {
|
||||
|
||||
@@ -573,7 +573,7 @@ PageType {
|
||||
visible: accessTypeSelector.currentIndex === 0
|
||||
|
||||
text: qsTr("Share")
|
||||
imageSource: "qrc:/images/controls/share-2.svg"
|
||||
leftImageSource: "qrc:/images/controls/share-2.svg"
|
||||
|
||||
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
||||
|
||||
@@ -772,7 +772,8 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
width: root.width
|
||||
height: root.height
|
||||
|
||||
expandedContent: ColumnLayout {
|
||||
id: expandedContent
|
||||
@@ -783,8 +784,6 @@ PageType {
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
|
||||
spacing: 8
|
||||
|
||||
onImplicitHeightChanged: {
|
||||
clientInfoDrawer.expandedHeight = expandedContent.implicitHeight + 32
|
||||
}
|
||||
@@ -797,57 +796,54 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: clientName
|
||||
}
|
||||
|
||||
ColumnLayout
|
||||
{
|
||||
id: textColumn
|
||||
property string textColor: AmneziaStyle.color.mutedGray
|
||||
Header2TextType {
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
ParagraphTextType {
|
||||
color: textColumn.textColor
|
||||
visible: creationDate
|
||||
Layout.fillWidth: true
|
||||
text: clientName
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.Wrap
|
||||
elide: Qt.ElideRight
|
||||
}
|
||||
|
||||
text: qsTr("Creation date: %1").arg(creationDate)
|
||||
}
|
||||
ParagraphTextType {
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
visible: creationDate
|
||||
Layout.fillWidth: true
|
||||
|
||||
ParagraphTextType {
|
||||
color: textColumn.textColor
|
||||
visible: latestHandshake
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Creation date: %1").arg(creationDate)
|
||||
}
|
||||
|
||||
text: qsTr("Latest handshake: %1").arg(latestHandshake)
|
||||
}
|
||||
ParagraphTextType {
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
visible: latestHandshake
|
||||
Layout.fillWidth: true
|
||||
|
||||
ParagraphTextType {
|
||||
color: textColumn.textColor
|
||||
visible: dataReceived
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Latest handshake: %1").arg(latestHandshake)
|
||||
}
|
||||
|
||||
text: qsTr("Data received: %1").arg(dataReceived)
|
||||
}
|
||||
ParagraphTextType {
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
visible: dataReceived
|
||||
Layout.fillWidth: true
|
||||
|
||||
ParagraphTextType {
|
||||
color: textColumn.textColor
|
||||
visible: dataSent
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Data received: %1").arg(dataReceived)
|
||||
}
|
||||
|
||||
text: qsTr("Data sent: %1").arg(dataSent)
|
||||
}
|
||||
ParagraphTextType {
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
visible: dataSent
|
||||
Layout.fillWidth: true
|
||||
|
||||
ParagraphTextType {
|
||||
color: textColumn.textColor
|
||||
visible: allowedIps
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Data sent: %1").arg(dataSent)
|
||||
}
|
||||
|
||||
text: qsTr("Allowed IPs: %1").arg(allowedIps)
|
||||
}
|
||||
ParagraphTextType {
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
visible: allowedIps
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Allowed IPs: %1").arg(allowedIps)
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -952,6 +948,7 @@ PageType {
|
||||
BasicButtonType {
|
||||
id: revokeButton
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
|
||||
@@ -135,7 +135,7 @@ PageType {
|
||||
Layout.topMargin: 40
|
||||
|
||||
text: qsTr("Share")
|
||||
imageSource: "qrc:/images/controls/share-2.svg"
|
||||
leftImageSource: "qrc:/images/controls/share-2.svg"
|
||||
|
||||
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
||||
|
||||
|
||||
@@ -123,6 +123,10 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
function onWrongInstallationUser(message) {
|
||||
onInstallationErrorOccurred(message)
|
||||
}
|
||||
|
||||
function onUpdateContainerFinished(message) {
|
||||
PageController.showNotificationMessage(message)
|
||||
PageController.closePage()
|
||||
|
||||
@@ -10,18 +10,72 @@
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "utilities.h"
|
||||
#include "version.h"
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QString printErrorMessage(DWORD errorCode) {
|
||||
LPVOID lpMsgBuf;
|
||||
|
||||
DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS;
|
||||
|
||||
DWORD dwLanguageId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
|
||||
|
||||
FormatMessageW(
|
||||
dwFlags,
|
||||
NULL,
|
||||
errorCode,
|
||||
dwLanguageId,
|
||||
(LPWSTR)&lpMsgBuf,
|
||||
0,
|
||||
NULL
|
||||
);
|
||||
|
||||
QString errorMsg = QString::fromWCharArray((LPCWSTR)lpMsgBuf);
|
||||
LocalFree(lpMsgBuf);
|
||||
return errorMsg.trimmed();
|
||||
}
|
||||
|
||||
QString Utils::getNextDriverLetter()
|
||||
{
|
||||
DWORD drivesBitmask = GetLogicalDrives();
|
||||
if (drivesBitmask == 0) {
|
||||
DWORD error = GetLastError();
|
||||
qDebug() << "GetLogicalDrives failed. Error code:" << error;
|
||||
return "";
|
||||
}
|
||||
|
||||
QString letters = "FGHIJKLMNOPQRSTUVWXYZ";
|
||||
QString availableLetter;
|
||||
|
||||
for (int i = letters.size() - 1; i >= 0; --i) {
|
||||
QChar letterChar = letters.at(i);
|
||||
int driveIndex = letterChar.toLatin1() - 'A';
|
||||
|
||||
if ((drivesBitmask & (1 << driveIndex)) == 0) {
|
||||
availableLetter = letterChar;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (availableLetter.isEmpty()) {
|
||||
qDebug() << "Can't find free drive letter";
|
||||
return "";
|
||||
}
|
||||
|
||||
return availableLetter;
|
||||
}
|
||||
#endif
|
||||
|
||||
QString Utils::getRandomString(int len)
|
||||
{
|
||||
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||
|
||||
const QString possibleCharacters = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||
QString randomString;
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
quint32 index = QRandomGenerator::global()->generate() % possibleCharacters.length();
|
||||
QChar nextChar = possibleCharacters.at(index);
|
||||
randomString.append(nextChar);
|
||||
randomString.append(possibleCharacters.at(QRandomGenerator::system()->bounded(possibleCharacters.length())));
|
||||
}
|
||||
|
||||
return randomString;
|
||||
}
|
||||
|
||||
@@ -109,30 +163,34 @@ QString Utils::usrExecutable(const QString &baseName)
|
||||
bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
QProcess process;
|
||||
process.setReadChannel(QProcess::StandardOutput);
|
||||
process.setProcessChannelMode(QProcess::MergedChannels);
|
||||
process.start("wmic.exe",
|
||||
QStringList() << "/OUTPUT:STDOUT"
|
||||
<< "PROCESS"
|
||||
<< "get"
|
||||
<< "Caption");
|
||||
process.waitForStarted();
|
||||
process.waitForFinished();
|
||||
QString processData(process.readAll());
|
||||
QStringList processList = processData.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts);
|
||||
foreach (const QString &rawLine, processList) {
|
||||
const QString line = rawLine.simplified();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE) {
|
||||
qWarning() << "Utils::processIsRunning error CreateToolhelp32Snapshot";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (line == fileName) {
|
||||
PROCESSENTRY32W pe32;
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32W);
|
||||
|
||||
if (!Process32FirstW(hSnapshot, &pe32)) {
|
||||
CloseHandle(hSnapshot);
|
||||
qWarning() << "Utils::processIsRunning error Process32FirstW";
|
||||
return false;
|
||||
}
|
||||
|
||||
do {
|
||||
QString exeFile = QString::fromWCharArray(pe32.szExeFile);
|
||||
|
||||
if (exeFile.compare(fileName, Qt::CaseInsensitive) == 0) {
|
||||
CloseHandle(hSnapshot);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} while (Process32NextW(hSnapshot, &pe32));
|
||||
|
||||
CloseHandle(hSnapshot);
|
||||
return false;
|
||||
#elif defined(Q_OS_IOS)
|
||||
|
||||
#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
|
||||
return false;
|
||||
#else
|
||||
QProcess process;
|
||||
@@ -150,13 +208,45 @@ bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
|
||||
#endif
|
||||
}
|
||||
|
||||
void Utils::killProcessByName(const QString &name)
|
||||
bool Utils::killProcessByName(const QString &name)
|
||||
{
|
||||
qDebug().noquote() << "Kill process" << name;
|
||||
#ifdef Q_OS_WIN
|
||||
QProcess::execute("taskkill", QStringList() << "/IM" << name << "/F");
|
||||
#elif defined Q_OS_IOS
|
||||
return;
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
|
||||
PROCESSENTRY32W pe32;
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32W);
|
||||
|
||||
bool success = false;
|
||||
|
||||
if (Process32FirstW(hSnapshot, &pe32)) {
|
||||
do {
|
||||
QString exeFile = QString::fromWCharArray(pe32.szExeFile);
|
||||
|
||||
if (exeFile.compare(name, Qt::CaseInsensitive) == 0) {
|
||||
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
|
||||
if (hProcess != NULL) {
|
||||
if (TerminateProcess(hProcess, 0)) {
|
||||
success = true;
|
||||
} else {
|
||||
DWORD error = GetLastError();
|
||||
qCritical() << "Can't terminate process" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error);
|
||||
}
|
||||
CloseHandle(hProcess);
|
||||
} else {
|
||||
DWORD error = GetLastError();
|
||||
qCritical() << "Can't open process for termination" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error);
|
||||
}
|
||||
}
|
||||
} while (Process32NextW(hSnapshot, &pe32));
|
||||
}
|
||||
|
||||
CloseHandle(hSnapshot);
|
||||
return success;
|
||||
#elif defined Q_OS_IOS || defined(Q_OS_ANDROID)
|
||||
return false;
|
||||
#else
|
||||
QProcess::execute(QString("pkill %1").arg(name));
|
||||
#endif
|
||||
@@ -244,3 +334,22 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Utils::logException(const std::exception &e)
|
||||
{
|
||||
qCritical() << e.what();
|
||||
try {
|
||||
std::rethrow_if_nested(e);
|
||||
} catch (const std::exception &nested) {
|
||||
logException(nested);
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
void Utils::logException(const std::exception_ptr &eptr)
|
||||
{
|
||||
try {
|
||||
if (eptr) std::rethrow_exception(eptr);
|
||||
} catch (const std::exception &e) {
|
||||
logException(e);
|
||||
} catch (...) {}
|
||||
}
|
||||
@@ -7,7 +7,8 @@
|
||||
#include <QJsonDocument>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "Windows.h"
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#endif
|
||||
|
||||
class Utils : public QObject
|
||||
@@ -27,15 +28,19 @@ public:
|
||||
static bool initializePath(const QString &path);
|
||||
|
||||
static bool processIsRunning(const QString &fileName, const bool fullFlag = false);
|
||||
static void killProcessByName(const QString &name);
|
||||
static bool killProcessByName(const QString &name);
|
||||
|
||||
static QString openVpnExecPath();
|
||||
static QString wireguardExecPath();
|
||||
static QString certUtilPath();
|
||||
static QString tun2socksPath();
|
||||
|
||||
static void logException(const std::exception &e);
|
||||
static void logException(const std::exception_ptr &eptr = std::current_exception());
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent);
|
||||
static QString getNextDriverLetter();
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -56,14 +56,15 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
{
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
QString proto = m_settings->defaultContainerName(m_settings->defaultServerIndex());
|
||||
auto container = m_settings->defaultContainer(m_settings->defaultServerIndex());
|
||||
|
||||
if (IpcClient::Interface()) {
|
||||
if (state == Vpn::ConnectionState::Connected) {
|
||||
IpcClient::Interface()->resetIpStack();
|
||||
IpcClient::Interface()->flushDns();
|
||||
|
||||
if (!m_vpnConfiguration.value(config_key::configVersion).toInt()) {
|
||||
if (!m_vpnConfiguration.value(config_key::configVersion).toInt() && container != DockerContainer::Awg
|
||||
&& container != DockerContainer::WireGuard) {
|
||||
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
|
||||
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ date > $LOG_FILE
|
||||
echo "Script started" >> $LOG_FILE
|
||||
sudo killall -9 $APP_NAME 2>> $LOG_FILE
|
||||
|
||||
if command -v steamos-readonly &> /dev/null; then
|
||||
sudo steamos-readonly disable >> $LOG_FILE
|
||||
echo "steamos-readonly disabled" >> $LOG_FILE
|
||||
fi
|
||||
|
||||
if sudo systemctl is-active --quiet $APP_NAME; then
|
||||
sudo systemctl stop $APP_NAME >> $LOG_FILE
|
||||
sudo systemctl disable $APP_NAME >> $LOG_FILE
|
||||
@@ -42,6 +47,11 @@ sudo chmod 555 /usr/share/applications/$APP_NAME.desktop >> $LOG_FILE
|
||||
|
||||
echo "user desktop creation loop ended" >> $LOG_FILE
|
||||
|
||||
if command -v steamos-readonly &> /dev/null; then
|
||||
sudo steamos-readonly enable >> $LOG_FILE
|
||||
echo "steamos-readonly enabled" >> $LOG_FILE
|
||||
fi
|
||||
|
||||
date >> $LOG_FILE
|
||||
echo "Service status:" >> $LOG_FILE
|
||||
sudo systemctl status $APP_NAME >> $LOG_FILE
|
||||
|
||||
@@ -13,6 +13,11 @@ date >> $LOG_FILE
|
||||
echo "Uninstall Script started" >> $LOG_FILE
|
||||
sudo killall -9 $APP_NAME 2>> $LOG_FILE
|
||||
|
||||
if command -v steamos-readonly &> /dev/null; then
|
||||
sudo steamos-readonly disable >> $LOG_FILE
|
||||
echo "steamos-readonly disabled" >> $LOG_FILE
|
||||
fi
|
||||
|
||||
ls /opt/AmneziaVPN/client/lib/* | while IFS=: read -r dir; do
|
||||
sudo unlink $dir >> $LOG_FILE
|
||||
done
|
||||
@@ -59,6 +64,11 @@ if test -f /usr/share/pixmaps/$APP_NAME.png; then
|
||||
|
||||
fi
|
||||
|
||||
if command -v steamos-readonly &> /dev/null; then
|
||||
sudo steamos-readonly enable >> $LOG_FILE
|
||||
echo "steamos-readonly enabled" >> $LOG_FILE
|
||||
fi
|
||||
|
||||
date >> $LOG_FILE
|
||||
echo "Service after uninstall status:" >> $LOG_FILE
|
||||
sudo systemctl status $APP_NAME >> $LOG_FILE
|
||||
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg width="208" height="56" viewBox="0 0 208 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="208" height="56" rx="16" fill="#FBB36A"/>
|
||||
<path d="M70.206 13.04H75.466C76.386 13.04 77.226 13.1867 77.986 13.48C78.746 13.7733 79.3927 14.2133 79.926 14.8C80.4593 15.3733 80.8727 16.1 81.166 16.98C81.4593 17.8467 81.606 18.86 81.606 20.02C81.606 21.18 81.4593 22.2 81.166 23.08C80.8727 23.9467 80.4593 24.6733 79.926 25.26C79.3927 25.8333 78.746 26.2667 77.986 26.56C77.226 26.8533 76.386 27 75.466 27H70.206V13.04ZM75.466 24.3C76.3727 24.3 77.086 24.0467 77.606 23.54C78.126 23.0333 78.386 22.22 78.386 21.1V18.94C78.386 17.82 78.126 17.0067 77.606 16.5C77.086 15.9933 76.3727 15.74 75.466 15.74H73.246V24.3H75.466ZM88.2513 27.24C87.478 27.24 86.7847 27.1133 86.1713 26.86C85.5713 26.6067 85.058 26.24 84.6313 25.76C84.218 25.28 83.898 24.7 83.6713 24.02C83.4447 23.34 83.3313 22.58 83.3313 21.74C83.3313 20.9 83.4447 20.14 83.6713 19.46C83.898 18.78 84.218 18.2067 84.6313 17.74C85.058 17.26 85.5713 16.8933 86.1713 16.64C86.7847 16.3867 87.478 16.26 88.2513 16.26C89.0247 16.26 89.7113 16.3867 90.3113 16.64C90.9247 16.8933 91.438 17.26 91.8513 17.74C92.278 18.2067 92.6047 18.78 92.8313 19.46C93.058 20.14 93.1713 20.9 93.1713 21.74C93.1713 22.58 93.058 23.34 92.8313 24.02C92.6047 24.7 92.278 25.28 91.8513 25.76C91.438 26.24 90.9247 26.6067 90.3113 26.86C89.7113 27.1133 89.0247 27.24 88.2513 27.24ZM88.2513 24.9C88.838 24.9 89.2913 24.72 89.6113 24.36C89.9313 24 90.0913 23.4867 90.0913 22.82V20.68C90.0913 20.0133 89.9313 19.5 89.6113 19.14C89.2913 18.78 88.838 18.6 88.2513 18.6C87.6647 18.6 87.2113 18.78 86.8913 19.14C86.5713 19.5 86.4113 20.0133 86.4113 20.68V22.82C86.4113 23.4867 86.5713 24 86.8913 24.36C87.2113 24.72 87.6647 24.9 88.2513 24.9ZM94.0851 16.5H96.9451L97.9651 20.74L98.6851 23.94H98.7651L99.6051 20.74L100.805 16.5H103.505L104.725 20.74L105.585 23.94H105.665L106.385 20.74L107.405 16.5H110.145L107.245 27H104.145L102.865 22.56L102.125 19.84H102.065L101.345 22.56L100.065 27H97.0251L94.0851 16.5ZM111.901 27V16.5H114.861V18.28H114.981C115.168 17.7333 115.495 17.26 115.961 16.86C116.428 16.46 117.075 16.26 117.901 16.26C118.981 16.26 119.795 16.62 120.341 17.34C120.901 18.06 121.181 19.0867 121.181 20.42V27H118.221V20.66C118.221 19.98 118.115 19.48 117.901 19.16C117.688 18.8267 117.308 18.66 116.761 18.66C116.521 18.66 116.288 18.6933 116.061 18.76C115.835 18.8133 115.628 18.9067 115.441 19.04C115.268 19.1733 115.128 19.34 115.021 19.54C114.915 19.7267 114.861 19.9467 114.861 20.2V27H111.901ZM126.756 27C125.742 27 124.996 26.7533 124.516 26.26C124.049 25.7667 123.816 25.0533 123.816 24.12V12.2H126.776V24.7H128.096V27H126.756ZM134.13 27.24C133.357 27.24 132.664 27.1133 132.05 26.86C131.45 26.6067 130.937 26.24 130.51 25.76C130.097 25.28 129.777 24.7 129.55 24.02C129.324 23.34 129.21 22.58 129.21 21.74C129.21 20.9 129.324 20.14 129.55 19.46C129.777 18.78 130.097 18.2067 130.51 17.74C130.937 17.26 131.45 16.8933 132.05 16.64C132.664 16.3867 133.357 16.26 134.13 16.26C134.904 16.26 135.59 16.3867 136.19 16.64C136.804 16.8933 137.317 17.26 137.73 17.74C138.157 18.2067 138.484 18.78 138.71 19.46C138.937 20.14 139.05 20.9 139.05 21.74C139.05 22.58 138.937 23.34 138.71 24.02C138.484 24.7 138.157 25.28 137.73 25.76C137.317 26.24 136.804 26.6067 136.19 26.86C135.59 27.1133 134.904 27.24 134.13 27.24ZM134.13 24.9C134.717 24.9 135.17 24.72 135.49 24.36C135.81 24 135.97 23.4867 135.97 22.82V20.68C135.97 20.0133 135.81 19.5 135.49 19.14C135.17 18.78 134.717 18.6 134.13 18.6C133.544 18.6 133.09 18.78 132.77 19.14C132.45 19.5 132.29 20.0133 132.29 20.68V22.82C132.29 23.4867 132.45 24 132.77 24.36C133.09 24.72 133.544 24.9 134.13 24.9ZM148.919 27C148.386 27 147.939 26.8333 147.579 26.5C147.233 26.1667 147.013 25.72 146.919 25.16H146.799C146.639 25.84 146.286 26.36 145.739 26.72C145.193 27.0667 144.519 27.24 143.719 27.24C142.666 27.24 141.859 26.96 141.299 26.4C140.739 25.84 140.459 25.1 140.459 24.18C140.459 23.0733 140.859 22.2533 141.659 21.72C142.473 21.1733 143.573 20.9 144.959 20.9H146.619V20.24C146.619 19.7333 146.486 19.3333 146.219 19.04C145.953 18.7333 145.506 18.58 144.879 18.58C144.293 18.58 143.826 18.7067 143.479 18.96C143.133 19.2133 142.846 19.5 142.619 19.82L140.859 18.26C141.286 17.6333 141.819 17.1467 142.459 16.8C143.113 16.44 143.993 16.26 145.099 16.26C146.593 16.26 147.713 16.5867 148.459 17.24C149.206 17.8933 149.579 18.8467 149.579 20.1V24.7H150.559V27H148.919ZM144.819 25.18C145.313 25.18 145.733 25.0733 146.079 24.86C146.439 24.6467 146.619 24.3 146.619 23.82V22.58H145.179C144.019 22.58 143.439 22.9733 143.439 23.76V24.06C143.439 24.4467 143.559 24.7333 143.799 24.92C144.039 25.0933 144.379 25.18 144.819 25.18ZM158.986 25.24H158.886C158.806 25.52 158.679 25.7867 158.506 26.04C158.333 26.28 158.119 26.4933 157.866 26.68C157.626 26.8533 157.346 26.9867 157.026 27.08C156.719 27.1867 156.399 27.24 156.066 27.24C154.733 27.24 153.726 26.7733 153.046 25.84C152.366 24.8933 152.026 23.5267 152.026 21.74C152.026 19.9533 152.366 18.5933 153.046 17.66C153.726 16.7267 154.733 16.26 156.066 16.26C156.746 16.26 157.346 16.4467 157.866 16.82C158.399 17.1933 1Line truncated
|
||||
<path opacity="0.8" d="M76.084 45L75.484 43.008H72.688L72.088 45H70.24L73 36.624H75.256L77.98 45H76.084ZM74.104 38.28H74.044L73.12 41.46H75.04L74.104 38.28ZM80.8068 45C80.1988 45 79.7508 44.852 79.4628 44.556C79.1828 44.26 79.0428 43.832 79.0428 43.272V36.12H80.8188V43.62H81.6108V45H80.8068ZM84.8251 45C84.2091 45 83.7411 44.844 83.4211 44.532C83.1091 44.22 82.9531 43.764 82.9531 43.164V40.08H82.0651V38.7H82.5091C82.7491 38.7 82.9131 38.644 83.0011 38.532C83.0891 38.412 83.1331 38.244 83.1331 38.028V36.996H84.7291V38.7H85.9771V40.08H84.7291V43.62H85.8811V45H84.8251ZM89.7919 45.144C89.3119 45.144 88.8839 45.068 88.5079 44.916C88.1319 44.756 87.8119 44.536 87.5479 44.256C87.2919 43.968 87.0959 43.62 86.9599 43.212C86.8319 42.804 86.7679 42.348 86.7679 41.844C86.7679 41.348 86.8319 40.9 86.9599 40.5C87.0879 40.092 87.2759 39.744 87.5239 39.456C87.7799 39.168 88.0919 38.948 88.4599 38.796C88.8279 38.636 89.2479 38.556 89.7199 38.556C90.2399 38.556 90.6839 38.644 91.0519 38.82C91.4279 38.996 91.7319 39.232 91.9639 39.528C92.2039 39.824 92.3759 40.168 92.4799 40.56C92.5919 40.944 92.6479 41.348 92.6479 41.772V42.3H88.6039V42.396C88.6039 42.812 88.7159 43.144 88.9399 43.392C89.1639 43.632 89.5159 43.752 89.9959 43.752C90.3639 43.752 90.6639 43.68 90.8959 43.536C91.1279 43.384 91.3439 43.204 91.5439 42.996L92.4319 44.1C92.1519 44.428 91.7839 44.684 91.3279 44.868C90.8799 45.052 90.3679 45.144 89.7919 45.144ZM89.7559 39.864C89.3959 39.864 89.1119 39.984 88.9039 40.224C88.7039 40.456 88.6039 40.768 88.6039 41.16V41.256H90.8119V41.148C90.8119 40.764 90.7239 40.456 90.5479 40.224C90.3799 39.984 90.1159 39.864 89.7559 39.864ZM93.9022 45V38.7H95.6782V40.068H95.7382C95.7702 39.892 95.8262 39.724 95.9062 39.564C95.9862 39.396 96.0902 39.248 96.2182 39.12C96.3542 38.992 96.5142 38.892 96.6982 38.82C96.8822 38.74 97.0982 38.7 97.3462 38.7H97.6582V40.356H97.2142C96.6942 40.356 96.3062 40.424 96.0502 40.56C95.8022 40.696 95.6782 40.948 95.6782 41.316V45H93.9022ZM98.7538 45V38.7H100.53V39.768H100.602C100.714 39.44 100.91 39.156 101.19 38.916C101.47 38.676 101.858 38.556 102.354 38.556C103.002 38.556 103.49 38.772 103.818 39.204C104.154 39.636 104.322 40.252 104.322 41.052V45H102.546V41.196C102.546 40.788 102.482 40.488 102.354 40.296C102.226 40.096 101.998 39.996 101.67 39.996C101.526 39.996 101.386 40.016 101.25 40.056C101.114 40.088 100.99 40.144 100.878 40.224C100.774 40.304 100.69 40.404 100.626 40.524C100.562 40.636 100.53 40.768 100.53 40.92V45H98.7538ZM110.558 45C110.238 45 109.97 44.9 109.754 44.7C109.546 44.5 109.414 44.232 109.358 43.896H109.286C109.19 44.304 108.978 44.616 108.65 44.832C108.322 45.04 107.918 45.144 107.438 45.144C106.806 45.144 106.322 44.976 105.986 44.64C105.65 44.304 105.482 43.86 105.482 43.308C105.482 42.644 105.722 42.152 106.202 41.832C106.69 41.504 107.35 41.34 108.182 41.34H109.178V40.944C109.178 40.64 109.098 40.4 108.938 40.224C108.778 40.04 108.51 39.948 108.134 39.948C107.782 39.948 107.502 40.024 107.294 40.176C107.086 40.328 106.914 40.5 106.778 40.692L105.722 39.756C105.978 39.38 106.298 39.088 106.682 38.88C107.074 38.664 107.602 38.556 108.266 38.556C109.162 38.556 109.834 38.752 110.282 39.144C110.73 39.536 110.954 40.108 110.954 40.86V43.62H111.542V45H110.558ZM108.098 43.908C108.394 43.908 108.646 43.844 108.854 43.716C109.07 43.588 109.178 43.38 109.178 43.092V42.348H108.314C107.618 42.348 107.27 42.584 107.27 43.056V43.236C107.27 43.468 107.342 43.64 107.486 43.752C107.63 43.856 107.834 43.908 108.098 43.908ZM114.872 45C114.256 45 113.788 44.844 113.468 44.532C113.156 44.22 113 43.764 113 43.164V40.08H112.112V38.7H112.556C112.796 38.7 112.96 38.644 113.048 38.532C113.136 38.412 113.18 38.244 113.18 38.028V36.996H114.776V38.7H116.024V40.08H114.776V43.62H115.928V45H114.872ZM118.146 38.028C117.786 38.028 117.522 37.948 117.354 37.788C117.194 37.62 117.114 37.408 117.114 37.152V36.888C117.114 36.632 117.194 36.424 117.354 36.264C117.522 36.096 117.786 36.012 118.146 36.012C118.506 36.012 118.766 36.096 118.926 36.264C119.094 36.424 119.178 36.632 119.178 36.888V37.152C119.178 37.408 119.094 37.62 118.926 37.788C118.766 37.948 118.506 38.028 118.146 38.028ZM117.258 38.7H119.034V45H117.258V38.7ZM122.035 45L119.971 38.7H121.723L122.503 41.244L123.091 43.524H123.187L123.775 41.244L124.531 38.7H126.211L124.147 45H122.035ZM129.683 45.144C129.203 45.144 128.775 45.068 128.399 44.916C128.023 44.756 127.703 44.536 127.439 44.256C127.183 43.968 126.987 43.62 126.851 43.212C126.723 42.804 126.659 42.348 126.659 41.844C126.659 41.348 126.723 40.9 126.851 40.5C126.979 40.092 127.167 39.744 127.415 39.456C127.671 39.168 127.983 38.948 128.351 38.796C128.719 38.636 129.139 38.556 129.611 38.556C130.131 38.556 130.575 38.644 130.943 38.82C131.319 38.996 131.623 39.232 131.855 39.528C132.095 39.824 132.267 40.168 132.371 40.56C132.483 40.944 132.539 41.348 132.539 41.772V42.3H128.495V42.396C128.495 42.812 128.607 43.144 128.831 43.392C129.055 43.632 129.407 43.752 129.887 43.752C130.255 43.752 130.5Line truncated
|
||||
<path d="M40.625 19.125H46.875V25.375" stroke="#472402" stroke-width="3.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M35.418 30.5833L46.8763 19.125" stroke="#472402" stroke-width="3.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M43.75 29.5417V35.7917C43.75 36.3442 43.5305 36.8741 43.1398 37.2648C42.7491 37.6555 42.2192 37.875 41.6667 37.875H30.2083C29.6558 37.875 29.1259 37.6555 28.7352 37.2648C28.3445 36.8741 28.125 36.3442 28.125 35.7917V24.3333C28.125 23.7808 28.3445 23.2509 28.7352 22.8602C29.1259 22.4695 29.6558 22.25 30.2083 22.25H36.4583" stroke="#472402" stroke-width="3.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg width="208" height="56" viewBox="0 0 208 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="208" height="56" rx="16" fill="#FBB36A"/>
|
||||
<path d="M70.206 13.04H75.466C76.386 13.04 77.226 13.1867 77.986 13.48C78.746 13.7733 79.3927 14.2133 79.926 14.8C80.4593 15.3733 80.8727 16.1 81.166 16.98C81.4593 17.8467 81.606 18.86 81.606 20.02C81.606 21.18 81.4593 22.2 81.166 23.08C80.8727 23.9467 80.4593 24.6733 79.926 25.26C79.3927 25.8333 78.746 26.2667 77.986 26.56C77.226 26.8533 76.386 27 75.466 27H70.206V13.04ZM75.466 24.3C76.3727 24.3 77.086 24.0467 77.606 23.54C78.126 23.0333 78.386 22.22 78.386 21.1V18.94C78.386 17.82 78.126 17.0067 77.606 16.5C77.086 15.9933 76.3727 15.74 75.466 15.74H73.246V24.3H75.466ZM88.2513 27.24C87.478 27.24 86.7847 27.1133 86.1713 26.86C85.5713 26.6067 85.058 26.24 84.6313 25.76C84.218 25.28 83.898 24.7 83.6713 24.02C83.4447 23.34 83.3313 22.58 83.3313 21.74C83.3313 20.9 83.4447 20.14 83.6713 19.46C83.898 18.78 84.218 18.2067 84.6313 17.74C85.058 17.26 85.5713 16.8933 86.1713 16.64C86.7847 16.3867 87.478 16.26 88.2513 16.26C89.0247 16.26 89.7113 16.3867 90.3113 16.64C90.9247 16.8933 91.438 17.26 91.8513 17.74C92.278 18.2067 92.6047 18.78 92.8313 19.46C93.058 20.14 93.1713 20.9 93.1713 21.74C93.1713 22.58 93.058 23.34 92.8313 24.02C92.6047 24.7 92.278 25.28 91.8513 25.76C91.438 26.24 90.9247 26.6067 90.3113 26.86C89.7113 27.1133 89.0247 27.24 88.2513 27.24ZM88.2513 24.9C88.838 24.9 89.2913 24.72 89.6113 24.36C89.9313 24 90.0913 23.4867 90.0913 22.82V20.68C90.0913 20.0133 89.9313 19.5 89.6113 19.14C89.2913 18.78 88.838 18.6 88.2513 18.6C87.6647 18.6 87.2113 18.78 86.8913 19.14C86.5713 19.5 86.4113 20.0133 86.4113 20.68V22.82C86.4113 23.4867 86.5713 24 86.8913 24.36C87.2113 24.72 87.6647 24.9 88.2513 24.9ZM94.0851 16.5H96.9451L97.9651 20.74L98.6851 23.94H98.7651L99.6051 20.74L100.805 16.5H103.505L104.725 20.74L105.585 23.94H105.665L106.385 20.74L107.405 16.5H110.145L107.245 27H104.145L102.865 22.56L102.125 19.84H102.065L101.345 22.56L100.065 27H97.0251L94.0851 16.5ZM111.901 27V16.5H114.861V18.28H114.981C115.168 17.7333 115.495 17.26 115.961 16.86C116.428 16.46 117.075 16.26 117.901 16.26C118.981 16.26 119.795 16.62 120.341 17.34C120.901 18.06 121.181 19.0867 121.181 20.42V27H118.221V20.66C118.221 19.98 118.115 19.48 117.901 19.16C117.688 18.8267 117.308 18.66 116.761 18.66C116.521 18.66 116.288 18.6933 116.061 18.76C115.835 18.8133 115.628 18.9067 115.441 19.04C115.268 19.1733 115.128 19.34 115.021 19.54C114.915 19.7267 114.861 19.9467 114.861 20.2V27H111.901ZM126.756 27C125.742 27 124.996 26.7533 124.516 26.26C124.049 25.7667 123.816 25.0533 123.816 24.12V12.2H126.776V24.7H128.096V27H126.756ZM134.13 27.24C133.357 27.24 132.664 27.1133 132.05 26.86C131.45 26.6067 130.937 26.24 130.51 25.76C130.097 25.28 129.777 24.7 129.55 24.02C129.324 23.34 129.21 22.58 129.21 21.74C129.21 20.9 129.324 20.14 129.55 19.46C129.777 18.78 130.097 18.2067 130.51 17.74C130.937 17.26 131.45 16.8933 132.05 16.64C132.664 16.3867 133.357 16.26 134.13 16.26C134.904 16.26 135.59 16.3867 136.19 16.64C136.804 16.8933 137.317 17.26 137.73 17.74C138.157 18.2067 138.484 18.78 138.71 19.46C138.937 20.14 139.05 20.9 139.05 21.74C139.05 22.58 138.937 23.34 138.71 24.02C138.484 24.7 138.157 25.28 137.73 25.76C137.317 26.24 136.804 26.6067 136.19 26.86C135.59 27.1133 134.904 27.24 134.13 27.24ZM134.13 24.9C134.717 24.9 135.17 24.72 135.49 24.36C135.81 24 135.97 23.4867 135.97 22.82V20.68C135.97 20.0133 135.81 19.5 135.49 19.14C135.17 18.78 134.717 18.6 134.13 18.6C133.544 18.6 133.09 18.78 132.77 19.14C132.45 19.5 132.29 20.0133 132.29 20.68V22.82C132.29 23.4867 132.45 24 132.77 24.36C133.09 24.72 133.544 24.9 134.13 24.9ZM148.919 27C148.386 27 147.939 26.8333 147.579 26.5C147.233 26.1667 147.013 25.72 146.919 25.16H146.799C146.639 25.84 146.286 26.36 145.739 26.72C145.193 27.0667 144.519 27.24 143.719 27.24C142.666 27.24 141.859 26.96 141.299 26.4C140.739 25.84 140.459 25.1 140.459 24.18C140.459 23.0733 140.859 22.2533 141.659 21.72C142.473 21.1733 143.573 20.9 144.959 20.9H146.619V20.24C146.619 19.7333 146.486 19.3333 146.219 19.04C145.953 18.7333 145.506 18.58 144.879 18.58C144.293 18.58 143.826 18.7067 143.479 18.96C143.133 19.2133 142.846 19.5 142.619 19.82L140.859 18.26C141.286 17.6333 141.819 17.1467 142.459 16.8C143.113 16.44 143.993 16.26 145.099 16.26C146.593 16.26 147.713 16.5867 148.459 17.24C149.206 17.8933 149.579 18.8467 149.579 20.1V24.7H150.559V27H148.919ZM144.819 25.18C145.313 25.18 145.733 25.0733 146.079 24.86C146.439 24.6467 146.619 24.3 146.619 23.82V22.58H145.179C144.019 22.58 143.439 22.9733 143.439 23.76V24.06C143.439 24.4467 143.559 24.7333 143.799 24.92C144.039 25.0933 144.379 25.18 144.819 25.18ZM158.986 25.24H158.886C158.806 25.52 158.679 25.7867 158.506 26.04C158.333 26.28 158.119 26.4933 157.866 26.68C157.626 26.8533 157.346 26.9867 157.026 27.08C156.719 27.1867 156.399 27.24 156.066 27.24C154.733 27.24 153.726 26.7733 153.046 25.84C152.366 24.8933 152.026 23.5267 152.026 21.74C152.026 19.9533 152.366 18.5933 153.046 17.66C153.726 16.7267 154.733 16.26 156.066 16.26C156.746 16.26 157.346 16.4467 157.866 16.82C158.399 17.1933 1Line truncated
|
||||
<path opacity="0.8" d="M70.924 45V36.624H76.48V38.244H72.748V39.96H75.928V41.568H72.748V45H70.924ZM77.4843 45V38.7H79.2603V40.068H79.3203C79.3523 39.892 79.4083 39.724 79.4883 39.564C79.5683 39.396 79.6723 39.248 79.8003 39.12C79.9363 38.992 80.0963 38.892 80.2803 38.82C80.4643 38.74 80.6803 38.7 80.9283 38.7H81.2403V40.356H80.7963C80.2763 40.356 79.8883 40.424 79.6323 40.56C79.3843 40.696 79.2603 40.948 79.2603 41.316V45H77.4843ZM84.8332 45.144C84.3692 45.144 83.9532 45.068 83.5852 44.916C83.2252 44.764 82.9172 44.544 82.6612 44.256C82.4132 43.968 82.2212 43.62 82.0852 43.212C81.9492 42.804 81.8812 42.348 81.8812 41.844C81.8812 41.34 81.9492 40.884 82.0852 40.476C82.2212 40.068 82.4132 39.724 82.6612 39.444C82.9172 39.156 83.2252 38.936 83.5852 38.784C83.9532 38.632 84.3692 38.556 84.8332 38.556C85.2972 38.556 85.7092 38.632 86.0692 38.784C86.4372 38.936 86.7452 39.156 86.9932 39.444C87.2492 39.724 87.4452 40.068 87.5812 40.476C87.7172 40.884 87.7852 41.34 87.7852 41.844C87.7852 42.348 87.7172 42.804 87.5812 43.212C87.4452 43.62 87.2492 43.968 86.9932 44.256C86.7452 44.544 86.4372 44.764 86.0692 44.916C85.7092 45.068 85.2972 45.144 84.8332 45.144ZM84.8332 43.74C85.1852 43.74 85.4572 43.632 85.6492 43.416C85.8412 43.2 85.9372 42.892 85.9372 42.492V41.208C85.9372 40.808 85.8412 40.5 85.6492 40.284C85.4572 40.068 85.1852 39.96 84.8332 39.96C84.4812 39.96 84.2092 40.068 84.0172 40.284C83.8252 40.5 83.7292 40.808 83.7292 41.208V42.492C83.7292 42.892 83.8252 43.2 84.0172 43.416C84.2092 43.632 84.4812 43.74 84.8332 43.74ZM89.0507 45V38.7H90.8267V39.768H90.8987C91.0107 39.432 91.2027 39.148 91.4747 38.916C91.7467 38.676 92.1187 38.556 92.5907 38.556C93.0227 38.556 93.3907 38.664 93.6947 38.88C93.9987 39.096 94.2187 39.412 94.3547 39.828H94.3907C94.4467 39.652 94.5307 39.488 94.6427 39.336C94.7627 39.176 94.9067 39.04 95.0747 38.928C95.2427 38.816 95.4307 38.728 95.6387 38.664C95.8467 38.592 96.0707 38.556 96.3107 38.556C96.9187 38.556 97.3827 38.772 97.7027 39.204C98.0307 39.636 98.1947 40.252 98.1947 41.052V45H96.4187V41.196C96.4187 40.396 96.1307 39.996 95.5547 39.996C95.2907 39.996 95.0507 40.072 94.8347 40.224C94.6187 40.376 94.5107 40.608 94.5107 40.92V45H92.7347V41.196C92.7347 40.396 92.4467 39.996 91.8707 39.996C91.7427 39.996 91.6147 40.016 91.4867 40.056C91.3667 40.088 91.2547 40.144 91.1507 40.224C91.0547 40.304 90.9747 40.404 90.9107 40.524C90.8547 40.636 90.8267 40.768 90.8267 40.92V45H89.0507ZM102.009 38.7H103.725L104.337 41.244L104.769 43.164H104.817L105.321 41.244L106.041 38.7H107.661L108.393 41.244L108.909 43.164H108.957L109.389 41.244L110.001 38.7H111.645L109.905 45H108.045L107.277 42.336L106.833 40.704H106.797L106.365 42.336L105.597 45H103.773L102.009 38.7ZM115.21 45.144C114.73 45.144 114.302 45.068 113.926 44.916C113.55 44.756 113.23 44.536 112.966 44.256C112.71 43.968 112.514 43.62 112.378 43.212C112.25 42.804 112.186 42.348 112.186 41.844C112.186 41.348 112.25 40.9 112.378 40.5C112.506 40.092 112.694 39.744 112.942 39.456C113.198 39.168 113.51 38.948 113.878 38.796C114.246 38.636 114.666 38.556 115.138 38.556C115.658 38.556 116.102 38.644 116.47 38.82C116.846 38.996 117.15 39.232 117.382 39.528C117.622 39.824 117.794 40.168 117.898 40.56C118.01 40.944 118.066 41.348 118.066 41.772V42.3H114.022V42.396C114.022 42.812 114.134 43.144 114.358 43.392C114.582 43.632 114.934 43.752 115.414 43.752C115.782 43.752 116.082 43.68 116.314 43.536C116.546 43.384 116.762 43.204 116.962 42.996L117.85 44.1C117.57 44.428 117.202 44.684 116.746 44.868C116.298 45.052 115.786 45.144 115.21 45.144ZM115.174 39.864C114.814 39.864 114.53 39.984 114.322 40.224C114.122 40.456 114.022 40.768 114.022 41.16V41.256H116.23V41.148C116.23 40.764 116.142 40.456 115.966 40.224C115.798 39.984 115.534 39.864 115.174 39.864ZM119.32 36.12H121.096V39.756H121.156C121.244 39.404 121.444 39.116 121.756 38.892C122.076 38.668 122.44 38.556 122.848 38.556C123.648 38.556 124.252 38.836 124.66 39.396C125.068 39.956 125.272 40.772 125.272 41.844C125.272 42.916 125.068 43.736 124.66 44.304C124.252 44.864 123.648 45.144 122.848 45.144C122.64 45.144 122.444 45.112 122.26 45.048C122.076 44.992 121.908 44.912 121.756 44.808C121.612 44.696 121.488 44.568 121.384 44.424C121.28 44.272 121.204 44.112 121.156 43.944H121.096V45H119.32V36.12ZM122.26 43.704C122.596 43.704 122.872 43.588 123.088 43.356C123.312 43.116 123.424 42.796 123.424 42.396V41.304C123.424 40.904 123.312 40.588 123.088 40.356C122.872 40.116 122.596 39.996 122.26 39.996C121.924 39.996 121.644 40.08 121.42 40.248C121.204 40.408 121.096 40.632 121.096 40.92V42.78C121.096 43.068 121.204 43.296 121.42 43.464C121.644 43.624 121.924 43.704 122.26 43.704ZM128.757 45.144C128.149 45.144 127.637 45.048 127.221 44.856C126.805 44.656 126.433 44.376 126.105 44.016L127.149 42.96C127.373 43.208 127.621 43.404 127.893 43.548C128.165 43.692 128.473 43.764 128.817 43.764C129.169 43.764 129.413 43.712 129.549 43.608C129.685 43.496 129.753 43.348 129.753 43.164C129.753 42.868 129.541 42.692 129.117 42.636LLine truncated
|
||||
<path d="M37.4987 29.5417V37.8751L33.332 33.7084" stroke="#472402" stroke-width="3.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M37.5 37.8749L41.6667 33.7083" stroke="#472402" stroke-width="3.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M29.5766 31.9052C28.7259 31.161 28.0598 30.2292 27.631 29.1833C27.2022 28.1374 27.0224 27.0062 27.1058 25.8789C27.1893 24.7516 27.5336 23.6592 28.1118 22.6879C28.6899 21.7165 29.4859 20.8929 30.437 20.282C31.388 19.6712 32.4681 19.2898 33.5919 19.1679C34.7157 19.0461 35.8524 19.1873 36.9122 19.5802C37.9721 19.9731 38.9261 20.6071 39.6989 21.432C40.4717 22.2569 41.0422 23.2502 41.3652 24.3334H43.2298C44.2423 24.3332 45.2277 24.661 46.0385 25.2676C46.8492 25.8742 47.4418 26.727 47.7274 27.6985C48.0131 28.6699 47.9765 29.7077 47.6232 30.6566C47.2698 31.6055 46.6187 32.4145 45.7673 32.9625" stroke="#472402" stroke-width="3.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 11 KiB |