Merge branch 'sudo_permission_check' into wheel_group_suppor
This commit is contained in:
commit
f1152ccad3
27 changed files with 130 additions and 93 deletions
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
|
|
@ -256,7 +256,7 @@ jobs:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
uses: maxim-lobanov/setup-xcode@v1
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
with:
|
with:
|
||||||
xcode-version: '14.3.1'
|
xcode-version: '15.4.0'
|
||||||
|
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.8.2.1
|
project(${PROJECT} VERSION 4.8.2.4
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
|
|
@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2068)
|
set(APP_ANDROID_VERSION_CODE 2071)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|
|
||||||
12
README.md
12
README.md
|
|
@ -10,21 +10,17 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<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://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download.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://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>
|
<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>
|
||||||
|
|
||||||
|
[Alternative download link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org/downloads)
|
||||||
|
|
||||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
[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
|
## Features
|
||||||
|
|
||||||
|
|
@ -37,7 +33,7 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
|
||||||
|
|
||||||
## Links
|
## 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://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
- [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_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)
|
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<!-- To request network state -->
|
<!-- 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.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package org.amnezia.vpn.protocol.wireguard
|
package org.amnezia.vpn.protocol.wireguard
|
||||||
|
|
||||||
import android.net.VpnService.Builder
|
import android.net.VpnService.Builder
|
||||||
import java.io.IOException
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import java.util.Locale
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.launch
|
||||||
import org.amnezia.awg.GoBackend
|
import org.amnezia.awg.GoBackend
|
||||||
import org.amnezia.vpn.protocol.Protocol
|
import org.amnezia.vpn.protocol.Protocol
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
|
|
@ -27,6 +28,8 @@ open class Wireguard : Protocol() {
|
||||||
|
|
||||||
private var tunnelHandle: Int = -1
|
private var tunnelHandle: Int = -1
|
||||||
protected open val ifName: String = "amn0"
|
protected open val ifName: String = "amn0"
|
||||||
|
private lateinit var scope: CoroutineScope
|
||||||
|
private var statusJob: Job? = null
|
||||||
|
|
||||||
override val statistics: Statistics
|
override val statistics: Statistics
|
||||||
get() {
|
get() {
|
||||||
|
|
@ -49,46 +52,17 @@ open class Wireguard : Protocol() {
|
||||||
|
|
||||||
override fun internalInit() {
|
override fun internalInit() {
|
||||||
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
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) {
|
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
val wireguardConfig = parseConfig(config)
|
val wireguardConfig = parseConfig(config)
|
||||||
val startTime = System.currentTimeMillis()
|
|
||||||
start(wireguardConfig, vpnBuilder, protect)
|
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.v(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 {
|
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
||||||
val configData = config.getJSONObject("wireguard_config_data")
|
val configData = config.getJSONObject("wireguard_config_data")
|
||||||
return WireguardConfig.build {
|
return WireguardConfig.build {
|
||||||
|
|
@ -178,6 +152,43 @@ open class Wireguard : Protocol() {
|
||||||
tunnelHandle = -1
|
tunnelHandle = -1
|
||||||
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
|
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() {
|
override fun stopVpn() {
|
||||||
|
|
@ -185,6 +196,8 @@ open class Wireguard : Protocol() {
|
||||||
Log.w(TAG, "Tunnel already down")
|
Log.w(TAG, "Tunnel already down")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
statusJob?.cancel()
|
||||||
|
statusJob = null
|
||||||
val handleToClose = tunnelHandle
|
val handleToClose = tunnelHandle
|
||||||
tunnelHandle = -1
|
tunnelHandle = -1
|
||||||
GoBackend.awgTurnOff(handleToClose)
|
GoBackend.awgTurnOff(handleToClose)
|
||||||
|
|
|
||||||
|
|
@ -404,7 +404,9 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||||
}
|
}
|
||||||
apiPayload[configKey::serviceType] = serviceType;
|
apiPayload[configKey::serviceType] = serviceType;
|
||||||
apiPayload[configKey::uuid] = installationUuid;
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
apiPayload[configKey::authData] = authData;
|
if (!authData.isEmpty()) {
|
||||||
|
apiPayload[configKey::authData] = authData;
|
||||||
|
}
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||||
|
|
@ -457,7 +459,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||||
|
|
||||||
auto encryptedResponseBody = reply->readAll();
|
auto encryptedResponseBody = reply->readAll();
|
||||||
|
|
||||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true)) {
|
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
m_proxyUrls = getProxyUrls();
|
m_proxyUrls = getProxyUrls();
|
||||||
std::random_device randomDevice;
|
std::random_device randomDevice;
|
||||||
std::mt19937 generator(randomDevice());
|
std::mt19937 generator(randomDevice());
|
||||||
|
|
@ -473,7 +475,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||||
wait.exec();
|
wait.exec();
|
||||||
|
|
||||||
encryptedResponseBody = reply->readAll();
|
encryptedResponseBody = reply->readAll();
|
||||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, false)) {
|
if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -751,10 +751,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
||||||
|
|
||||||
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
||||||
{
|
{
|
||||||
if (credentials.userName == "root") {
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
|
|
@ -768,7 +764,7 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
|
||||||
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
||||||
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
if (!stdOut.contains("sudo") && !stdOut.contains("wheel"))
|
if (!stdOut.contains("root :") && !stdOut.contains(" sudo") && !stdOut.contains(" wheel"))
|
||||||
return ErrorCode::ServerUserNotInSudo;
|
return ErrorCode::ServerUserNotInSudo;
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
|
|
|
||||||
|
|
@ -848,7 +848,6 @@ bool InstallController::updateServiceFromApi(const int serverIndex, const QStrin
|
||||||
|
|
||||||
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
||||||
newServerConfig.insert(configKey::authData, authData);
|
newServerConfig.insert(configKey::authData, authData);
|
||||||
newServerConfig.insert(config_key::crc, serverConfig.value(config_key::crc));
|
|
||||||
m_serversModel->editServer(newServerConfig, serverIndex);
|
m_serversModel->editServer(newServerConfig, serverIndex);
|
||||||
|
|
||||||
if (reloadServiceConfig) {
|
if (reloadServiceConfig) {
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ DrawerType2 {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
text: qsTr("Share")
|
text: qsTr("Share")
|
||||||
imageSource: "qrc:/images/controls/share-2.svg"
|
leftImageSource: "qrc:/images/controls/share-2.svg"
|
||||||
|
|
||||||
KeyNavigation.tab: copyConfigTextButton
|
KeyNavigation.tab: copyConfigTextButton
|
||||||
|
|
||||||
|
|
@ -120,7 +120,7 @@ DrawerType2 {
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
|
|
||||||
text: qsTr("Copy")
|
text: qsTr("Copy")
|
||||||
imageSource: "qrc:/images/controls/copy.svg"
|
leftImageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
|
||||||
Keys.onReturnPressed: { copyConfigTextButton.clicked() }
|
Keys.onReturnPressed: { copyConfigTextButton.clicked() }
|
||||||
Keys.onEnterPressed: { copyConfigTextButton.clicked() }
|
Keys.onEnterPressed: { copyConfigTextButton.clicked() }
|
||||||
|
|
@ -143,7 +143,7 @@ DrawerType2 {
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
|
|
||||||
text: qsTr("Copy config string")
|
text: qsTr("Copy config string")
|
||||||
imageSource: "qrc:/images/controls/copy.svg"
|
leftImageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
|
||||||
KeyNavigation.tab: showSettingsButton
|
KeyNavigation.tab: showSettingsButton
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@ Button {
|
||||||
property int borderWidth: 0
|
property int borderWidth: 0
|
||||||
property int borderFocusedWidth: 1
|
property int borderFocusedWidth: 1
|
||||||
|
|
||||||
property string imageSource
|
property string leftImageSource
|
||||||
property string rightImageSource
|
property string rightImageSource
|
||||||
property string leftImageColor: textColor
|
property string leftImageColor
|
||||||
|
property bool changeLeftImageSize: true
|
||||||
|
|
||||||
property bool squareLeftSide: false
|
property bool squareLeftSide: false
|
||||||
|
|
||||||
|
|
@ -127,18 +128,23 @@ Button {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
Layout.preferredHeight: 20
|
id: leftImage
|
||||||
Layout.preferredWidth: 20
|
source: root.leftImageSource
|
||||||
|
visible: root.leftImageSource === "" ? false : true
|
||||||
source: root.imageSource
|
|
||||||
visible: root.imageSource === "" ? false : true
|
|
||||||
|
|
||||||
layer {
|
layer {
|
||||||
enabled: true
|
enabled: leftImageColor !== "" ? true : false
|
||||||
effect: ColorOverlay {
|
effect: ColorOverlay {
|
||||||
color: leftImageColor
|
color: leftImageColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (root.changeLeftImageSize) {
|
||||||
|
leftImage.Layout.preferredHeight = 20
|
||||||
|
leftImage.Layout.preferredWidth = 20
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonTextType {
|
ButtonTextType {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Popup {
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
Overlay.modal: Rectangle {
|
Overlay.modal: Rectangle {
|
||||||
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
|
color: AmneziaStyle.color.translucentMidnightBlack
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ RadioButton {
|
||||||
|
|
||||||
property string textColor: AmneziaStyle.color.midnightBlack
|
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 selectedBorderColor: AmneziaStyle.color.goldenApricot
|
||||||
property string defaultBodredColor: AmneziaStyle.color.transparent
|
property string defaultBodredColor: AmneziaStyle.color.transparent
|
||||||
property int borderWidth: 0
|
property int borderWidth: 0
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ Item {
|
||||||
id: background
|
id: background
|
||||||
|
|
||||||
anchors.fill: parent
|
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 {
|
Behavior on color {
|
||||||
PropertyAnimation { duration: 200 }
|
PropertyAnimation { duration: 200 }
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ Popup {
|
||||||
|
|
||||||
Overlay.modal: Rectangle {
|
Overlay.modal: Rectangle {
|
||||||
visible: root.closeButtonVisible
|
visible: root.closeButtonVisible
|
||||||
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
|
color: AmneziaStyle.color.translucentMidnightBlack
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ Item {
|
||||||
|
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.NoFocus
|
||||||
text: root.buttonText
|
text: root.buttonText
|
||||||
imageSource: root.buttonImageSource
|
leftImageSource: root.buttonImageSource
|
||||||
|
|
||||||
anchors.top: content.top
|
anchors.top: content.top
|
||||||
anchors.bottom: content.bottom
|
anchors.bottom: content.bottom
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Popup {
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
Overlay.modal: Rectangle {
|
Overlay.modal: Rectangle {
|
||||||
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
|
color: AmneziaStyle.color.translucentMidnightBlack
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,9 @@ QtObject {
|
||||||
readonly property color sheerWhite: Qt.rgba(1, 1, 1, 0.12)
|
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 translucentWhite: Qt.rgba(1, 1, 1, 0.08)
|
||||||
readonly property color barelyTranslucentWhite: Qt.rgba(1, 1, 1, 0.05)
|
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
|
pressedColor: AmneziaStyle.color.sheerWhite
|
||||||
disabledColor: AmneziaStyle.color.mutedGray
|
disabledColor: AmneziaStyle.color.mutedGray
|
||||||
textColor: AmneziaStyle.color.mutedGray
|
textColor: AmneziaStyle.color.mutedGray
|
||||||
leftImageColor: AmneziaStyle.color.transparent
|
|
||||||
borderWidth: 0
|
borderWidth: 0
|
||||||
|
|
||||||
buttonTextLabel.lineHeight: 20
|
buttonTextLabel.lineHeight: 20
|
||||||
|
|
@ -110,7 +109,7 @@ PageType {
|
||||||
|
|
||||||
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
|
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"
|
rightImageSource: "qrc:/images/controls/chevron-down.svg"
|
||||||
|
|
||||||
Keys.onEnterPressed: splitTunnelingButton.clicked()
|
Keys.onEnterPressed: splitTunnelingButton.clicked()
|
||||||
|
|
@ -166,6 +165,7 @@ PageType {
|
||||||
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
drawer.collapsedHeight = collapsed.implicitHeight
|
drawer.collapsedHeight = collapsed.implicitHeight
|
||||||
|
|
@ -267,18 +267,39 @@ PageType {
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
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
|
spacing: 0
|
||||||
|
|
||||||
Image {
|
BasicButtonType {
|
||||||
Layout.rightMargin: 8
|
enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsed
|
||||||
visible: source !== ""
|
hoverEnabled: enabled
|
||||||
source: ServersModel.defaultServerImagePathCollapsed
|
|
||||||
}
|
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
|
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
|
rootButtonImageColor: AmneziaStyle.color.midnightBlack
|
||||||
rootButtonBackgroundColor: AmneziaStyle.color.paleGray
|
rootButtonBackgroundColor: AmneziaStyle.color.paleGray
|
||||||
rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8)
|
rootButtonBackgroundHoveredColor: AmneziaStyle.color.mistyGray
|
||||||
rootButtonBackgroundPressedColor: Qt.rgba(215, 216, 219, 0.65)
|
rootButtonBackgroundPressedColor: AmneziaStyle.color.cloudyGray
|
||||||
rootButtonHoveredBorderColor: AmneziaStyle.color.transparent
|
rootButtonHoveredBorderColor: AmneziaStyle.color.transparent
|
||||||
rootButtonDefaultBorderColor: AmneziaStyle.color.transparent
|
rootButtonDefaultBorderColor: AmneziaStyle.color.transparent
|
||||||
rootButtonTextTopMargin: 8
|
rootButtonTextTopMargin: 8
|
||||||
|
|
|
||||||
|
|
@ -132,8 +132,8 @@ PageType {
|
||||||
implicitHeight: 32
|
implicitHeight: 32
|
||||||
|
|
||||||
defaultColor: "transparent"
|
defaultColor: "transparent"
|
||||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
pressedColor: AmneziaStyle.color.sheerWhite
|
||||||
textColor: AmneziaStyle.color.vibrantRed
|
textColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|
||||||
text: qsTr("Reload API config")
|
text: qsTr("Reload API config")
|
||||||
|
|
@ -172,8 +172,8 @@ PageType {
|
||||||
implicitHeight: 32
|
implicitHeight: 32
|
||||||
|
|
||||||
defaultColor: "transparent"
|
defaultColor: "transparent"
|
||||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
pressedColor: AmneziaStyle.color.sheerWhite
|
||||||
textColor: AmneziaStyle.color.vibrantRed
|
textColor: AmneziaStyle.color.vibrantRed
|
||||||
|
|
||||||
text: qsTr("Remove from application")
|
text: qsTr("Remove from application")
|
||||||
|
|
|
||||||
|
|
@ -573,7 +573,7 @@ PageType {
|
||||||
visible: accessTypeSelector.currentIndex === 0
|
visible: accessTypeSelector.currentIndex === 0
|
||||||
|
|
||||||
text: qsTr("Share")
|
text: qsTr("Share")
|
||||||
imageSource: "qrc:/images/controls/share-2.svg"
|
leftImageSource: "qrc:/images/controls/share-2.svg"
|
||||||
|
|
||||||
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ PageType {
|
||||||
Layout.topMargin: 40
|
Layout.topMargin: 40
|
||||||
|
|
||||||
text: qsTr("Share")
|
text: qsTr("Share")
|
||||||
imageSource: "qrc:/images/controls/share-2.svg"
|
leftImageSource: "qrc:/images/controls/share-2.svg"
|
||||||
|
|
||||||
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
BIN
metadata/img-readme/download.png
Normal file
BIN
metadata/img-readme/download.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.3 KiB |
BIN
metadata/img-readme/testiny.png
Normal file
BIN
metadata/img-readme/testiny.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Loading…
Add table
Add a link
Reference in a new issue