mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-15 14:53:11 +03:00
Compare commits
12 Commits
server_scr
...
feat/switc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65664d69b9 | ||
|
|
dbabac3bd5 | ||
|
|
c203540754 | ||
|
|
cff1e2962c | ||
|
|
586e1f0b71 | ||
|
|
65ba95f344 | ||
|
|
d7f301ea3b | ||
|
|
a1e28ba9af | ||
|
|
60686fde24 | ||
|
|
bd0747296e | ||
|
|
ba61019a50 | ||
|
|
113f967006 |
3
.github/workflows/deploy.yml
vendored
3
.github/workflows/deploy.yml
vendored
@@ -23,6 +23,9 @@ jobs:
|
||||
- 'recipes/**'
|
||||
- 'conanfile.py'
|
||||
- '.github/workflows/deploy.yml'
|
||||
- 'cmake/conan_provider.cmake'
|
||||
- 'cmake/platform_settings.cmake'
|
||||
- 'cmake/recipes_bootstrap.cmake'
|
||||
|
||||
Bake-Prebuilts-Linux:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -18,9 +18,9 @@ project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
||||
# trigger conan to kick off `conan install` globally
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if (PREBUILTS_ONLY)
|
||||
# trigger conan to kick off `conan install`
|
||||
find_package(OpenSSL REQUIRED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -212,11 +212,32 @@ endif()
|
||||
|
||||
install(TARGETS ${PROJECT}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
RUNTIME_DEPENDENCY_SET client_deps
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
|
||||
if(APPLE)
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR}/AmneziaVPN.app/Contents/Frameworks)
|
||||
else()
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
install(RUNTIME_DEPENDENCY_SET client_deps
|
||||
PRE_EXCLUDE_REGEXES
|
||||
[[api-ms-win-.*]]
|
||||
[[ext-ms-.*]]
|
||||
[[kernel32\.dll]]
|
||||
[[hvsifiletrust\.dll]]
|
||||
[[libc\.so\..*]] [[libgcc_s\.so\..*]] [[libm\.so\..*]] [[libstdc\+\+\.so\..*]]
|
||||
[[.*\.framework]]
|
||||
[[^[Qq]t.*]]
|
||||
POST_EXCLUDE_REGEXES
|
||||
[[^.*[\\/]system32[\\/].*\.dll$]]
|
||||
[[^/lib.*]]
|
||||
[[^/usr/lib.*]]
|
||||
DIRECTORIES ${CONAN_RUNTIME_LIB_DIRS}
|
||||
COMPONENT AmneziaVPN
|
||||
DESTINATION "${RUNTIME_DEPS_DIR}"
|
||||
)
|
||||
|
||||
set(deploy_tool_options "")
|
||||
|
||||
@@ -54,7 +54,6 @@ target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
|
||||
@@ -418,7 +418,9 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString serviceProtocol = apiV2->serviceProtocol();
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
// Auto mode (empty) defaults to AWG — gateway requires public_key for all requests
|
||||
const QString effectiveProtocol = serviceProtocol.isEmpty() ? configKey::awg : serviceProtocol;
|
||||
ProtocolData protocolData = generateProtocolData(effectiveProtocol);
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
@@ -432,7 +434,7 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
appendProtocolDataToApiPayload(effectiveProtocol, protocolData, apiPayload);
|
||||
|
||||
if (isConnectEvent) {
|
||||
apiPayload[apiDefs::key::isConnectEvent] = true;
|
||||
@@ -451,11 +453,11 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
}
|
||||
|
||||
QJsonObject serverConfigJson;
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, effectiveProtocol, protocolData, serverConfigJson);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, apiV2->apiConfig.serviceType, serviceProtocol, apiV2->apiConfig.userCountryCode, responseBody);
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
@@ -741,6 +743,9 @@ void SubscriptionController::setCurrentProtocol(const QString &serverId, const Q
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (apiV2.has_value()) {
|
||||
apiV2->apiConfig.serviceProtocol = protocolName;
|
||||
if (protocolName.isEmpty()) {
|
||||
apiV2->defaultContainer = DockerContainer::Awg;
|
||||
}
|
||||
m_serversRepository->editServer(serverId, apiV2->toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2->toJson()));
|
||||
}
|
||||
@@ -752,6 +757,12 @@ bool SubscriptionController::isVlessProtocol(const QString &serverId) const
|
||||
return apiV2.has_value() && apiV2->serviceProtocol() == "vless";
|
||||
}
|
||||
|
||||
bool SubscriptionController::isAwgProtocol(const QString &serverId) const
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
return apiV2.has_value() && apiV2->serviceProtocol() == "awg";
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &productId,
|
||||
int *duplicateServerIndex)
|
||||
|
||||
@@ -86,6 +86,7 @@ public:
|
||||
|
||||
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
|
||||
bool isVlessProtocol(const QString &serverId) const;
|
||||
bool isAwgProtocol(const QString &serverId) const;
|
||||
|
||||
ErrorCode getAccountInfo(const QString &serverId, QJsonObject &accountInfo);
|
||||
QFuture<QPair<ErrorCode, QString>> getRenewalLink(const QString &serverId);
|
||||
|
||||
@@ -225,6 +225,22 @@ void CoreController::initControllers()
|
||||
m_connectionController, this);
|
||||
setQmlContextProperty("SubscriptionUiController", m_subscriptionUiController);
|
||||
|
||||
connect(m_connectionUiController, &ConnectionUiController::requestSetCurrentProtocol,
|
||||
m_subscriptionUiController, &SubscriptionUiController::setCurrentProtocol, Qt::QueuedConnection);
|
||||
connect(m_connectionUiController, &ConnectionUiController::requestUpdateServiceFromGateway,
|
||||
m_subscriptionUiController, &SubscriptionUiController::updateServiceFromGateway, Qt::QueuedConnection);
|
||||
connect(m_subscriptionUiController, &SubscriptionUiController::updateServiceFromGatewayCompleted,
|
||||
m_connectionUiController, &ConnectionUiController::onUpdateServiceFromGatewayCompleted,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_connectionUiController, &ConnectionUiController::requestSetProcessedServer,
|
||||
this, [this](const QString &serverId) {
|
||||
m_serversUiController->setProcessedServerId(serverId);
|
||||
}, Qt::QueuedConnection);
|
||||
connect(m_installUiController, &InstallUiController::updateContainerFinished,
|
||||
m_connectionUiController, [this](const QString &, bool) {
|
||||
m_connectionUiController->onCurrentContainerUpdated();
|
||||
});
|
||||
|
||||
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
|
||||
setQmlContextProperty("ApiNewsController", m_apiNewsUiController);
|
||||
|
||||
|
||||
@@ -82,11 +82,33 @@
|
||||
#endif
|
||||
|
||||
class CoreSignalHandlers;
|
||||
class TestMultipleImports;
|
||||
class TestAdminSelfHostedExport;
|
||||
class TestServerEdit;
|
||||
class TestDefaultServerChange;
|
||||
class TestServerEdgeCases;
|
||||
class TestSignalOrder;
|
||||
class TestServersModelSync;
|
||||
class TestComplexOperations;
|
||||
class TestSettingsSignals;
|
||||
class TestUiServersModelAndController;
|
||||
class TestSelfHostedServerSetup;
|
||||
|
||||
class CoreController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class CoreSignalHandlers;
|
||||
friend class TestMultipleImports;
|
||||
friend class TestAdminSelfHostedExport;
|
||||
friend class TestServerEdit;
|
||||
friend class TestDefaultServerChange;
|
||||
friend class TestServerEdgeCases;
|
||||
friend class TestSignalOrder;
|
||||
friend class TestServersModelSync;
|
||||
friend class TestComplexOperations;
|
||||
friend class TestSettingsSignals;
|
||||
friend class TestUiServersModelAndController;
|
||||
friend class TestSelfHostedServerSetup;
|
||||
|
||||
public:
|
||||
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
|
||||
@@ -161,7 +161,12 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
return encRequestData.errorCode;
|
||||
}
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
QNetworkAccessManager *nam = amnApp ? amnApp->networkManager() : nullptr;
|
||||
if (!nam) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
QNetworkReply *reply = nam->post(encRequestData.request, encRequestData.requestBody);
|
||||
|
||||
QEventLoop wait;
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
@@ -236,7 +241,14 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
QNetworkAccessManager *nam = amnApp ? amnApp->networkManager() : nullptr;
|
||||
if (!nam) {
|
||||
promise->addResult(qMakePair(ErrorCode::InternalError, QByteArray()));
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QNetworkReply *reply = nam->post(encRequestData.request, encRequestData.requestBody);
|
||||
|
||||
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||
|
||||
@@ -378,9 +390,14 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
return {};
|
||||
}
|
||||
|
||||
QNetworkAccessManager *nam = amnApp ? amnApp->networkManager() : nullptr;
|
||||
if (!nam) {
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||
request.setUrl(proxyStorageUrl);
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
reply = nam->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
|
||||
@@ -486,7 +486,7 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = configKey::amneziaOpenvpn;
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
||||
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data);
|
||||
@@ -645,7 +645,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = containerName;
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp(
|
||||
"DNS = "
|
||||
@@ -699,7 +699,7 @@ QJsonObject ImportController::extractXrayConfig(const QString &data, ConfigTypes
|
||||
? configKey::amneziaSsxray
|
||||
: configKey::amneziaXray;
|
||||
if (description.isEmpty()) {
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
} else {
|
||||
config[configKey::description] = description;
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ void InstallController::addEmptyServer(const ServerCredentials &credentials)
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
serverConfig.port = credentials.port;
|
||||
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
|
||||
serverConfig.description = m_serversRepository->nextAvailableServerName();
|
||||
serverConfig.displayName = serverConfig.description.isEmpty() ? serverConfig.hostName : serverConfig.description;
|
||||
serverConfig.defaultContainer = DockerContainer::None;
|
||||
|
||||
@@ -1170,7 +1170,7 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials,
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
serverConfig.port = credentials.port;
|
||||
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
|
||||
serverConfig.description = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
for (auto iterator = preparedContainers.begin(); iterator != preparedContainers.end(); iterator++) {
|
||||
serverConfig.containers.insert(iterator.key(), iterator.value());
|
||||
@@ -1240,28 +1240,26 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::checkSshConnection(const ServerCredentials &credentials, QString &output,
|
||||
ErrorCode InstallController::checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback)
|
||||
{
|
||||
SshSession sshSession(this);
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
ServerCredentials processedCredentials = credentials;
|
||||
|
||||
if (processedCredentials.secretData.contains("BEGIN") && processedCredentials.secretData.contains("PRIVATE KEY")) {
|
||||
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
|
||||
if (!passphraseCallback) {
|
||||
return ErrorCode::SshPrivateKeyError;
|
||||
}
|
||||
|
||||
QString decryptedPrivateKey;
|
||||
errorCode = sshSession.getDecryptedPrivateKey(processedCredentials, decryptedPrivateKey, passphraseCallback);
|
||||
errorCode = sshSession.getDecryptedPrivateKey(credentials, decryptedPrivateKey, passphraseCallback);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
processedCredentials.secretData = decryptedPrivateKey;
|
||||
credentials.secretData = decryptedPrivateKey;
|
||||
}
|
||||
|
||||
output = sshSession.checkSshConnection(processedCredentials, errorCode);
|
||||
output = sshSession.checkSshConnection(credentials, errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,8 @@ public:
|
||||
|
||||
bool isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode checkSshConnection(const ServerCredentials &credentials, QString &output, std::function<QString()> passphraseCallback = nullptr);
|
||||
ErrorCode checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback = nullptr);
|
||||
|
||||
bool isServerAlreadyExists(const ServerCredentials &credentials, int &existingServerIndex);
|
||||
|
||||
|
||||
@@ -363,6 +363,6 @@ void SettingsController::disablePremV1MigrationReminder()
|
||||
|
||||
QString SettingsController::nextAvailableServerName() const
|
||||
{
|
||||
return m_appSettingsRepository->nextAvailableServerName();
|
||||
return m_serversRepository->nextAvailableServerName();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "version.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/errorStrings.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
|
||||
namespace
|
||||
@@ -109,7 +108,7 @@ void UpdateController::fetchGatewayUrl()
|
||||
.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [err, gatewayResponse] = result;
|
||||
if (err != ErrorCode::NoError) {
|
||||
logger.error() << errorString(err);
|
||||
logger.error() << "Gateway request failed, error code:" << static_cast<int>(err);
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
@@ -250,17 +249,9 @@ void UpdateController::runInstaller()
|
||||
runLinuxInstaller(kInstallerLocalPath);
|
||||
#endif
|
||||
} else {
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << QString::fromUtf8(reply->readAll());
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
logger.error() << "Installer download failed, network error:" << static_cast<int>(reply->error())
|
||||
<< reply->errorString();
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
@@ -426,26 +426,6 @@ void SecureAppSettingsRepository::clearSettings()
|
||||
emit settingsCleared();
|
||||
}
|
||||
|
||||
QString SecureAppSettingsRepository::nextAvailableServerName() const
|
||||
{
|
||||
int i = 0;
|
||||
bool nameExist = false;
|
||||
|
||||
do {
|
||||
i++;
|
||||
nameExist = false;
|
||||
QJsonArray servers = QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
|
||||
for (const QJsonValue &server : servers) {
|
||||
if (server.toObject().value(configKey::description).toString() == QString("Server") + " " + QString::number(i)) {
|
||||
nameExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (nameExist);
|
||||
|
||||
return QString("Server") + " " + QString::number(i);
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setInstallationUuid(const QString &uuid)
|
||||
{
|
||||
m_settings->setValue("Conf/installationUuid", uuid);
|
||||
|
||||
@@ -90,8 +90,6 @@ public:
|
||||
bool restoreAppConfig(const QByteArray &cfg);
|
||||
void clearSettings();
|
||||
|
||||
QString nextAvailableServerName() const;
|
||||
|
||||
QByteArray xraySavedConfigs() const;
|
||||
void setXraySavedConfigs(const QByteArray &data);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonValue>
|
||||
#include <QSet>
|
||||
#include <QUuid>
|
||||
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
@@ -32,6 +33,45 @@ QJsonObject embedStorageServerId(const QString &serverId, const QJsonObject &pay
|
||||
return o;
|
||||
}
|
||||
|
||||
QString storedServerDisplayName(const SecureServersRepository *repository, const QString &serverId)
|
||||
{
|
||||
using Kind = serverConfigUtils::ConfigType;
|
||||
switch (repository->serverKind(serverId)) {
|
||||
case Kind::SelfHostedAdmin:
|
||||
if (const auto cfg = repository->selfHostedAdminConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::SelfHostedUser:
|
||||
if (const auto cfg = repository->selfHostedUserConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::Native:
|
||||
if (const auto cfg = repository->nativeConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::AmneziaPremiumV2:
|
||||
case Kind::AmneziaFreeV3:
|
||||
case Kind::ExternalPremium:
|
||||
if (const auto cfg = repository->apiV2Config(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::AmneziaPremiumV1:
|
||||
case Kind::AmneziaFreeV2:
|
||||
if (const auto cfg = repository->legacyApiConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::Invalid:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SecureServersRepository::SecureServersRepository(SecureQSettings *settings, QObject *parent)
|
||||
@@ -153,6 +193,28 @@ void SecureServersRepository::clearServers()
|
||||
syncToStorage();
|
||||
}
|
||||
|
||||
QString SecureServersRepository::nextAvailableServerName() const
|
||||
{
|
||||
QSet<QString> usedNames;
|
||||
usedNames.reserve(m_orderedServerIds.size());
|
||||
|
||||
for (const QString &serverId : m_orderedServerIds) {
|
||||
const QString displayName = storedServerDisplayName(this, serverId);
|
||||
if (!displayName.isEmpty()) {
|
||||
usedNames.insert(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
QString candidate;
|
||||
do {
|
||||
i++;
|
||||
candidate = QStringLiteral("Server %1").arg(i);
|
||||
} while (usedNames.contains(candidate));
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
QString SecureServersRepository::addServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind)
|
||||
{
|
||||
const QString id = normalizedOrGeneratedServerId(serverId);
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
|
||||
void clearServers();
|
||||
|
||||
QString nextAvailableServerName() const;
|
||||
|
||||
void invalidateCache();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -26,6 +26,8 @@ set_target_properties(networkextension PROPERTIES
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
@@ -114,10 +116,20 @@ target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
|
||||
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
find_package(openvpnadapter REQUIRED)
|
||||
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
|
||||
target_link_libraries(networkextension PRIVATE amnezia::openvpnadapter)
|
||||
|
||||
find_package(awg-apple REQUIRED)
|
||||
target_link_libraries(networkextension PRIVATE amnezia::awg-apple)
|
||||
|
||||
find_package(hev-socks5-tunnel REQUIRED)
|
||||
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
|
||||
target_link_libraries(networkextension PRIVATE heiher::hev-socks5-tunnel)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
if which apt-get > /dev/null 2>&1 || command -v apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
|
||||
elif which dnf > /dev/null 2>&1 || command -v dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
|
||||
elif which yum > /dev/null 2>&1 || command -v yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
|
||||
elif which zypper > /dev/null 2>&1 || command -v zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
|
||||
elif which pacman > /dev/null 2>&1 || command -v pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
|
||||
else echo "Packet manager not found"; echo "Internal error"; exit 1;\
|
||||
fi;\
|
||||
if sudo -n which $LOCK_CMD > /dev/null 2>&1 || command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
|
||||
if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
|
||||
elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
|
||||
elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
|
||||
elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
|
||||
elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
|
||||
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
|
||||
if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then opt="--version";\
|
||||
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
|
||||
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
|
||||
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
|
||||
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\
|
||||
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
|
||||
else pm="uname"; opt="-a";\
|
||||
fi;\
|
||||
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\
|
||||
|
||||
@@ -2042,6 +2042,16 @@ Thank you for staying with us!</source>
|
||||
<source>Cannot change protocol during active connection</source>
|
||||
<translation>Невозможно изменить протокол во время активного соединения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="289"/>
|
||||
<source>Connection</source>
|
||||
<translation>Соединение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="290"/>
|
||||
<source>Protocol selection and local proxy setup</source>
|
||||
<translation>Выбор протокола и настройка локального прокси</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsApiServerInfo.qml" line="319"/>
|
||||
<source>Subscription Key</source>
|
||||
@@ -5399,4 +5409,52 @@ FileZilla или другие SFTP-клиенты, а также смонтир
|
||||
<translation>Сохранить</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageSettingsConnectionProtocols</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="142"/>
|
||||
<source>VPN Protocol</source>
|
||||
<translation>VPN-протокол</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="161"/>
|
||||
<source>Choose automatically</source>
|
||||
<translation>Выбирать автоматически</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="162"/>
|
||||
<source>AmneziaWG is used by default. If the connection is unstable, the app will switch to VLESS. On the next launch, AmneziaWG will be tried again</source>
|
||||
<translation>По умолчанию используется AmneziaWG. Если соединение нестабильно, приложение переключится на VLESS. При следующем запуске снова будет использован AmneziaWG</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="166"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="196"/>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="227"/>
|
||||
<source>Cannot change protocol during active connection</source>
|
||||
<translation>Невозможно изменить протокол во время активного соединения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="192"/>
|
||||
<source>AmneziaWG</source>
|
||||
<translation>AmneziaWG</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionProtocols.qml" line="223"/>
|
||||
<source>XRay VLESS Reality</source>
|
||||
<translation>XRay VLESS Reality</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageSettingsConnectionType</name>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionType.qml" line="46"/>
|
||||
<source>Connection</source>
|
||||
<translation>Соединение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/qml/Pages2/PageSettingsConnectionType.qml" line="63"/>
|
||||
<source>VPN protocol</source>
|
||||
<translation>VPN-протокол</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
||||
@@ -436,6 +436,7 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
|
||||
} else {
|
||||
emit changeApiCountryFinished(tr("Successfully changed the country of connection to %1").arg(newCountryName));
|
||||
}
|
||||
emit updateServiceFromGatewayCompleted(true, serverId);
|
||||
return true;
|
||||
} else {
|
||||
if (errorCode == ErrorCode::ApiSubscriptionExpiredError) {
|
||||
@@ -443,6 +444,7 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
|
||||
} else {
|
||||
emit errorOccurred(errorCode);
|
||||
}
|
||||
emit updateServiceFromGatewayCompleted(false, serverId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -508,6 +510,11 @@ bool SubscriptionUiController::isVlessProtocol(const QString &serverId)
|
||||
return m_subscriptionController->isVlessProtocol(serverId);
|
||||
}
|
||||
|
||||
bool SubscriptionUiController::isAwgProtocol(const QString &serverId)
|
||||
{
|
||||
return m_subscriptionController->isAwgProtocol(serverId);
|
||||
}
|
||||
|
||||
|
||||
void SubscriptionUiController::removeApiConfig(const QString &serverId)
|
||||
{
|
||||
|
||||
@@ -57,6 +57,7 @@ public slots:
|
||||
|
||||
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
|
||||
bool isVlessProtocol(const QString &serverId);
|
||||
bool isAwgProtocol(const QString &serverId);
|
||||
|
||||
bool isCaptchaAwaitingUser() const;
|
||||
void onCaptchaSolved(const QString &captchaId, const QString &solution);
|
||||
@@ -83,6 +84,7 @@ signals:
|
||||
void changeApiCountryFinished(const QString &message);
|
||||
void reloadServerFromApiFinished(const QString &message);
|
||||
void updateServerFromApiFinished();
|
||||
void updateServiceFromGatewayCompleted(bool success, const QString &serverId);
|
||||
void subscriptionRefreshNeeded();
|
||||
|
||||
void apiConfigRemoved(const QString &message);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
|
||||
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
|
||||
ServersController* serversController,
|
||||
@@ -20,6 +21,9 @@ ConnectionUiController::ConnectionUiController(ConnectionController* connectionC
|
||||
|
||||
connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection);
|
||||
|
||||
m_awgStateTimer.setSingleShot(true);
|
||||
connect(&m_awgStateTimer, &QTimer::timeout, this, &ConnectionUiController::onAwgStateTimeout);
|
||||
|
||||
m_state = Vpn::ConnectionState::Disconnected;
|
||||
}
|
||||
|
||||
@@ -56,6 +60,9 @@ void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state
|
||||
m_connectionStateText = tr("Connecting...");
|
||||
switch (state) {
|
||||
case Vpn::ConnectionState::Connected: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
amnApp->networkManager()->clearConnectionCache();
|
||||
|
||||
m_isConnectionInProgress = false;
|
||||
@@ -64,36 +71,55 @@ void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Connecting: {
|
||||
checkAndStartAwgStateTimer();
|
||||
m_isConnectionInProgress = true;
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Reconnecting: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = true;
|
||||
m_connectionStateText = tr("Reconnecting...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Disconnected: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = false;
|
||||
m_connectionStateText = tr("Connect");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Disconnecting: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = true;
|
||||
m_connectionStateText = tr("Disconnecting...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Preparing: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = true;
|
||||
m_connectionStateText = tr("Preparing...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Error: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = false;
|
||||
m_connectionStateText = tr("Connect");
|
||||
emit connectionErrorOccurred(getLastConnectionError());
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Unknown: {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
m_isConnectionInProgress = false;
|
||||
m_connectionStateText = tr("Connect");
|
||||
emit connectionErrorOccurred(getLastConnectionError());
|
||||
@@ -143,3 +169,113 @@ bool ConnectionUiController::isConnected() const
|
||||
{
|
||||
return m_isConnected;
|
||||
}
|
||||
|
||||
void ConnectionUiController::onCurrentContainerUpdated()
|
||||
{
|
||||
if (m_isConnected || m_isConnectionInProgress) {
|
||||
emit reconnectWithUpdatedContainer(tr("Settings updated successfully, reconnecting..."));
|
||||
openConnection();
|
||||
} else {
|
||||
emit reconnectWithUpdatedContainer(tr("Settings updated successfully"));
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionUiController::checkAndStartAwgStateTimer()
|
||||
{
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const DockerContainer container = m_serversController->getDefaultContainer(serverId);
|
||||
const Proto proto = ContainerUtils::defaultProtocol(container);
|
||||
if (proto == Proto::Awg) {
|
||||
const auto v2Config = m_serversController->apiV2Config(serverId);
|
||||
if (v2Config.has_value() && (v2Config->isPremium() || v2Config->isExternalPremium())) {
|
||||
const bool isAutoMode = v2Config->serviceProtocol().isEmpty();
|
||||
if (isAutoMode) {
|
||||
if (!m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.start(kAwgSwitchTimeoutMs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (m_serversController->isLegacyApiV1Server(serverId)) {
|
||||
if (!m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.start(kAwgSwitchTimeoutMs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (m_awgStateTimer.isActive()) {
|
||||
m_awgStateTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionUiController::onAwgStateTimeout()
|
||||
{
|
||||
if (m_state != Vpn::ConnectionState::Connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const DockerContainer container = m_serversController->getDefaultContainer(serverId);
|
||||
const Proto proto = ContainerUtils::defaultProtocol(container);
|
||||
if (proto != Proto::Awg) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeConnection();
|
||||
|
||||
QTimer::singleShot(1000, this, [this, serverId]() {
|
||||
if (m_isConnected || m_isConnectionInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug().noquote() << "AWG connect timeout: trying to switch API protocol to VLESS and reload config from gateway";
|
||||
|
||||
m_pendingApiServerId = serverId;
|
||||
m_apiSwitched = false;
|
||||
m_waitingForApiUpdate = true;
|
||||
|
||||
emit requestSetProcessedServer(serverId);
|
||||
emit requestSetCurrentProtocol(serverId, QStringLiteral("vless"));
|
||||
emit requestUpdateServiceFromGateway(serverId, QString(), QString(), true);
|
||||
});
|
||||
}
|
||||
|
||||
void ConnectionUiController::onUpdateServiceFromGatewayCompleted(bool success, const QString &serverId)
|
||||
{
|
||||
if (!m_waitingForApiUpdate || m_pendingApiServerId != serverId) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_waitingForApiUpdate = false;
|
||||
m_apiSwitched = success;
|
||||
|
||||
if (success) {
|
||||
const QMap<DockerContainer, ContainerConfig> containersMap = m_serversController->getServerContainersMap(serverId);
|
||||
if (containersMap.contains(DockerContainer::Xray)) {
|
||||
qDebug().noquote() << "AWG connect timeout (10s), switching default container to Xray and reconnecting";
|
||||
m_serversController->setDefaultContainer(serverId, DockerContainer::Xray);
|
||||
emit requestSetCurrentProtocol(serverId, QStringLiteral("vless"));
|
||||
m_pendingApiServerId.clear();
|
||||
|
||||
if (!m_isConnected && !m_isConnectionInProgress) {
|
||||
emit prepareConfig();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug().noquote() << "AWG connect timeout: no Xray available (API switch success ="
|
||||
<< (m_apiSwitched ? "YES" : "NO") << ")";
|
||||
m_pendingApiServerId.clear();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define CONNECTIONUICONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
@@ -40,6 +41,14 @@ public slots:
|
||||
|
||||
void onTranslationsUpdated();
|
||||
|
||||
public slots:
|
||||
void checkAndStartAwgStateTimer();
|
||||
void onUpdateServiceFromGatewayCompleted(bool success, const QString &serverId);
|
||||
void onCurrentContainerUpdated();
|
||||
|
||||
private slots:
|
||||
void onAwgStateTimeout();
|
||||
|
||||
signals:
|
||||
void connectionStateChanged();
|
||||
|
||||
@@ -49,9 +58,19 @@ signals:
|
||||
void preparingConfig();
|
||||
void prepareConfig();
|
||||
|
||||
// serverId + protocol — both carried so the receiver doesn't need to re-read default server
|
||||
void requestSetCurrentProtocol(const QString &serverId, const QString &protocol);
|
||||
void requestUpdateServiceFromGateway(const QString &serverId, const QString &newCountryCode,
|
||||
const QString &newCountryName, bool reloadServiceConfig);
|
||||
void requestSetProcessedServer(const QString &serverId);
|
||||
void reconnectWithUpdatedContainer(const QString &message);
|
||||
|
||||
private:
|
||||
Vpn::ConnectionState getCurrentConnectionState();
|
||||
|
||||
static constexpr int kAwgSwitchTimeoutMs = 10000;
|
||||
|
||||
QTimer m_awgStateTimer;
|
||||
ConnectionController* m_connectionController;
|
||||
ServersController* m_serversController;
|
||||
|
||||
@@ -60,6 +79,10 @@ private:
|
||||
QString m_connectionStateText = tr("Connect");
|
||||
|
||||
Vpn::ConnectionState m_state;
|
||||
|
||||
QString m_pendingApiServerId;
|
||||
bool m_apiSwitched = false;
|
||||
bool m_waitingForApiUpdate = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace PageLoader
|
||||
PageSettingsApiDevices,
|
||||
PageSettingsApiSubscriptionKey,
|
||||
PageSettingsKillSwitchExceptions,
|
||||
PageSettingsConnectionType,
|
||||
PageSettingsConnectionProtocols,
|
||||
|
||||
PageServiceSftpSettings,
|
||||
PageServiceTorWebsiteSettings,
|
||||
|
||||
@@ -263,46 +263,16 @@ PageType {
|
||||
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
id: switcher
|
||||
|
||||
readonly property bool isVlessProtocol: SubscriptionUiController.isVlessProtocol(ServersUiController.processedServerId)
|
||||
readonly property bool isProtocolSwitchBlocked: ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
visible: ApiAccountInfoModel.data("isProtocolSelectionSupported")
|
||||
enabled: !switcher.isProtocolSwitchBlocked
|
||||
|
||||
text: qsTr("Use VLESS protocol")
|
||||
checked: switcher.isVlessProtocol
|
||||
onToggled: function() {
|
||||
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
|
||||
} else {
|
||||
PageController.showBusyIndicator(true)
|
||||
SubscriptionUiController.setCurrentProtocol(ServersUiController.processedServerId, switcher.isVlessProtocol ? "awg" : "vless")
|
||||
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, "", "", true)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
visible: footer.isVisibleForAmneziaFree
|
||||
}
|
||||
|
||||
WarningType {
|
||||
id: warning
|
||||
|
||||
Layout.topMargin: 24
|
||||
Layout.topMargin: visible ? 24 : 0
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
|
||||
backGroundColor: AmneziaStyle.color.translucentRichBrown
|
||||
|
||||
@@ -320,11 +290,25 @@ PageType {
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnKey
|
||||
id: connectionSwitcher
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: warning.visible ? 16 : 0
|
||||
text: qsTr("Connection")
|
||||
descriptionText: qsTr("Protocol selection and local proxy setup")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsConnectionType)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnKey
|
||||
|
||||
Layout.fillWidth: true
|
||||
visible: footer.isVisibleForAmneziaFree
|
||||
|
||||
text: qsTr("Subscription Key")
|
||||
|
||||
244
client/ui/qml/Pages2/PageSettingsConnectionProtocols.qml
Normal file
244
client/ui/qml/Pages2/PageSettingsConnectionProtocols.qml
Normal file
@@ -0,0 +1,244 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
// Protocol to re-assert after updateServiceFromGateway completes (empty = auto)
|
||||
property string pendingProtocol: ""
|
||||
property bool waitingForGatewayUpdate: false
|
||||
|
||||
Timer {
|
||||
id: updateProtocolTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
var serverId = ServersUiController.getServerId(ServersUiController.processedServerIndex)
|
||||
root.waitingForGatewayUpdate = true
|
||||
SubscriptionUiController.updateServiceFromGateway(serverId, "", "", true)
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentProtocol() {
|
||||
try {
|
||||
if (SubscriptionUiController.isVlessProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex))) {
|
||||
return "vless"
|
||||
}
|
||||
|
||||
if (SubscriptionUiController.isAwgProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex))) {
|
||||
return "awg"
|
||||
}
|
||||
|
||||
// If neither VLESS nor AWG, it's auto mode
|
||||
return "auto"
|
||||
} catch (e) {
|
||||
console.log("Error getting current protocol:", e)
|
||||
return "auto"
|
||||
}
|
||||
}
|
||||
|
||||
property string currentProtocol: "auto"
|
||||
|
||||
Connections {
|
||||
target: ServersModel
|
||||
function onDataChanged() {
|
||||
if (!root || !root.visible) {
|
||||
return
|
||||
}
|
||||
Qt.callLater(function() {
|
||||
try {
|
||||
if (root && root.visible && typeof root.getCurrentProtocol === "function") {
|
||||
root.currentProtocol = root.getCurrentProtocol()
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error in ServersModel.onDataChanged:", e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SubscriptionUiController
|
||||
function onUpdateServerFromApiFinished() {
|
||||
if (!root.waitingForGatewayUpdate) {
|
||||
return
|
||||
}
|
||||
root.waitingForGatewayUpdate = false
|
||||
|
||||
// Re-assert the protocol the user chose (gateway reload may have reset it)
|
||||
if (root.pendingProtocol !== "") {
|
||||
var protocolToSet = root.pendingProtocol === "auto" ? "" : root.pendingProtocol
|
||||
SubscriptionUiController.setCurrentProtocol(
|
||||
ServersUiController.getServerId(ServersUiController.processedServerIndex),
|
||||
protocolToSet)
|
||||
}
|
||||
|
||||
root.currentProtocol = root.getCurrentProtocol()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.currentProtocol = root.getCurrentProtocol()
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
try {
|
||||
if (typeof root.getCurrentProtocol === "function") {
|
||||
root.currentProtocol = root.getCurrentProtocol()
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error in onVisibleChanged:", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
flickable.contentY = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: flickable
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
contentHeight: content.height
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
|
||||
headerText: qsTr("VPN Protocol")
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: protocolButtonGroup
|
||||
}
|
||||
|
||||
VerticalRadioButton {
|
||||
id: autoProtocolButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
ButtonGroup.group: protocolButtonGroup
|
||||
checked: root.currentProtocol === "auto"
|
||||
enabled: !ConnectionController.isConnected || !ServersUiController.isDefaultServerCurrentlyProcessed()
|
||||
|
||||
text: qsTr("Choose automatically")
|
||||
descriptionText: qsTr("AmneziaWG is used by default. If the connection is unstable, the app will switch to VLESS. On the next launch, AmneziaWG will be tried again")
|
||||
|
||||
onClicked: function() {
|
||||
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
|
||||
return
|
||||
}
|
||||
|
||||
SubscriptionUiController.setCurrentProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex), "")
|
||||
root.pendingProtocol = "auto"
|
||||
updateProtocolTimer.start()
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: this.clicked()
|
||||
Keys.onReturnPressed: this.clicked()
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
VerticalRadioButton {
|
||||
id: awgProtocolButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
ButtonGroup.group: protocolButtonGroup
|
||||
checked: root.currentProtocol === "awg"
|
||||
enabled: !ConnectionController.isConnected || !ServersUiController.isDefaultServerCurrentlyProcessed()
|
||||
|
||||
text: qsTr("AmneziaWG")
|
||||
|
||||
onClicked: function() {
|
||||
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
|
||||
return
|
||||
}
|
||||
|
||||
root.currentProtocol = "awg"
|
||||
root.pendingProtocol = "awg"
|
||||
SubscriptionUiController.setCurrentProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex), "awg")
|
||||
updateProtocolTimer.start()
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: this.clicked()
|
||||
Keys.onReturnPressed: this.clicked()
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
VerticalRadioButton {
|
||||
id: vlessProtocolButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
ButtonGroup.group: protocolButtonGroup
|
||||
checked: root.currentProtocol === "vless"
|
||||
enabled: !ConnectionController.isConnected || !ServersUiController.isDefaultServerCurrentlyProcessed()
|
||||
|
||||
text: qsTr("XRay VLESS Reality")
|
||||
|
||||
onClicked: function() {
|
||||
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
|
||||
return
|
||||
}
|
||||
|
||||
root.currentProtocol = "vless"
|
||||
root.pendingProtocol = "vless"
|
||||
SubscriptionUiController.setCurrentProtocol(ServersUiController.getServerId(ServersUiController.processedServerIndex), "vless")
|
||||
updateProtocolTimer.start()
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: this.clicked()
|
||||
Keys.onReturnPressed: this.clicked()
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
client/ui/qml/Pages2/PageSettingsConnectionType.qml
Normal file
84
client/ui/qml/Pages2/PageSettingsConnectionType.qml
Normal file
@@ -0,0 +1,84 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Config"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("Connection")
|
||||
}
|
||||
}
|
||||
|
||||
model: 1
|
||||
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
LabelWithButtonType {
|
||||
id: vpnProtocolButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
text: qsTr("VPN protocol")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
PageController.goToPage(PageEnum.PageSettingsConnectionProtocols)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Local proxy
|
||||
// DividerType {}
|
||||
// LabelWithButtonType {
|
||||
// id: localProxyButton
|
||||
// Layout.fillWidth: true
|
||||
// Layout.leftMargin: 16
|
||||
// Layout.rightMargin: 16
|
||||
// text: qsTr("Local proxy")
|
||||
// descriptionText: qsTr("Running: 127.0.0.1:1080")
|
||||
// rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
// clickedFunction: function() {}
|
||||
// }
|
||||
// DividerType {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,8 @@
|
||||
<file>Pages2/PageSettingsAppSplitTunneling.qml</file>
|
||||
<file>Pages2/PageSettingsBackup.qml</file>
|
||||
<file>Pages2/PageSettingsConnection.qml</file>
|
||||
<file>Pages2/PageSettingsConnectionProtocols.qml</file>
|
||||
<file>Pages2/PageSettingsConnectionType.qml</file>
|
||||
<file>Pages2/PageSettingsDns.qml</file>
|
||||
<file>Pages2/PageSettingsKillSwitch.qml</file>
|
||||
<file>Pages2/PageSettingsKillSwitchExceptions.qml</file>
|
||||
|
||||
@@ -20,8 +20,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Android")
|
||||
set(_CONAN_INSTALL_ARGS
|
||||
"-c=tools.android:cmake_legacy_toolchain=false"
|
||||
"-c=tools.build:sharedlinkflags=['-Wl,-z,max-page-size=16384']"
|
||||
"-c=tools.build:exelinkflags=['-Wl,-z,max-page-size=16384']"
|
||||
"-o=openssl/*:shared=True")
|
||||
"-c=tools.build:exelinkflags=['-Wl,-z,max-page-size=16384']")
|
||||
set(CMAKE_ANDROID_STL_TYPE "c++_shared" CACHE STRING "")
|
||||
endif()
|
||||
|
||||
@@ -29,6 +28,12 @@ if (WIN32 OR APPLE)
|
||||
set(CMAKE_INSTALL_BINDIR ".")
|
||||
endif()
|
||||
|
||||
# Apple NE-based apps do not support any dylibs or variations
|
||||
# So Qt would use the openssl bundled with system, not application
|
||||
if (NOT(CMAKE_SYSTEM_NAME STREQUAL "iOS" OR (APPLE AND MACOS_NE)))
|
||||
list(APPEND _CONAN_INSTALL_ARGS "-o=openssl/*:shared=True")
|
||||
endif()
|
||||
|
||||
list(PREPEND _CONAN_INSTALL_ARGS "--build=missing")
|
||||
list(JOIN _CONAN_INSTALL_ARGS ";" _CONAN_INSTALL_ARGS_JOINED)
|
||||
set(CONAN_INSTALL_ARGS ${_CONAN_INSTALL_ARGS_JOINED} CACHE STRING "" FORCE)
|
||||
|
||||
@@ -5,6 +5,7 @@ from conan.errors import ConanInvalidConfiguration
|
||||
from conan.tools.scm import Git
|
||||
from conan.internal.model.pkg_type import PackageType
|
||||
from conan.tools.files import chdir
|
||||
from conan.tools.apple import XCRun
|
||||
|
||||
import os
|
||||
import shutil
|
||||
@@ -49,7 +50,10 @@ class OpenVPNAdapter(ConanFile):
|
||||
|
||||
def build(self):
|
||||
with chdir(self, self.source_folder):
|
||||
self.run("xcrun xcodebuild"
|
||||
xcrun = XCRun(self)
|
||||
|
||||
xcodebuild = xcrun.find("xcodebuild")
|
||||
self.run(f"{xcodebuild}"
|
||||
" -project OpenVPNAdapter.xcodeproj"
|
||||
" -scheme OpenVPNAdapter"
|
||||
" -configuration Release"
|
||||
@@ -57,10 +61,20 @@ class OpenVPNAdapter(ConanFile):
|
||||
f" -sdk {self._sdk}"
|
||||
f' "CONFIGURATION_BUILD_DIR={self.build_folder}"'
|
||||
f' "BUILT_PRODUCTS_DIR={self.build_folder}"'
|
||||
" MACH_O_TYPE=staticlib"
|
||||
" BUILD_LIBRARY_FOR_DISTRIBUTION=YES"
|
||||
" CODE_SIGNING_ALLOWED=NO"
|
||||
)
|
||||
|
||||
openvpnadapter = os.path.join(self.build_folder, "OpenVPNAdapter.framework", "OpenVPNAdapter")
|
||||
self.run(f"{xcrun.libtool} -static -o"
|
||||
f" {openvpnadapter}"
|
||||
f" {openvpnadapter}"
|
||||
f' {os.path.join(self.build_folder, "OpenVPNClient.framework", "OpenVPNClient")}'
|
||||
f' {os.path.join(self.build_folder, "LZ4.framework", "LZ4")}'
|
||||
f' {os.path.join(self.build_folder, "mbedTLS.framework", "mbedTLS")}'
|
||||
)
|
||||
|
||||
def package(self):
|
||||
shutil.copytree(os.path.join(self.build_folder, "OpenVPNAdapter.framework"),
|
||||
os.path.join(self.package_folder, "OpenVPNAdapter.framework"))
|
||||
@@ -70,3 +84,4 @@ class OpenVPNAdapter(ConanFile):
|
||||
self.cpp_info.type = PackageType.STATIC
|
||||
self.cpp_info.package_framework = True
|
||||
self.cpp_info.location = os.path.join(self.package_folder, "OpenVPNAdapter.framework")
|
||||
self.cpp_info.frameworks = ["SystemConfiguration"]
|
||||
|
||||
@@ -316,12 +316,9 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
INSTALL_RPATH "@executable_path/../Frameworks"
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
)
|
||||
endif()
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
INSTALL_RPATH "@executable_path/../Frameworks"
|
||||
)
|
||||
|
||||
find_library(FW_COREFOUNDATION CoreFoundation)
|
||||
find_library(FW_SYSTEMCONFIG SystemConfiguration)
|
||||
@@ -428,11 +425,32 @@ endif()
|
||||
# install target
|
||||
install(TARGETS ${PROJECT}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
RUNTIME_DEPENDENCY_SET service_deps
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
|
||||
if(APPLE)
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR}/../Frameworks)
|
||||
else()
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
install(RUNTIME_DEPENDENCY_SET service_deps
|
||||
PRE_EXCLUDE_REGEXES
|
||||
[[api-ms-win-.*]]
|
||||
[[ext-ms-.*]]
|
||||
[[kernel32\.dll]]
|
||||
[[hvsifiletrust\.dll]]
|
||||
[[libc\.so\..*]] [[libgcc_s\.so\..*]] [[libm\.so\..*]] [[libstdc\+\+\.so\..*]]
|
||||
[[.*\.framework]]
|
||||
[[^[Qq]t.*]]
|
||||
POST_EXCLUDE_REGEXES
|
||||
[[^.*[\\/]system32[\\/].*\.dll$]]
|
||||
[[^/lib.*]]
|
||||
[[^/usr/lib.*]]
|
||||
DIRECTORIES ${CONAN_RUNTIME_LIB_DIRS}
|
||||
COMPONENT AmneziaVPN
|
||||
DESTINATION "${RUNTIME_DEPS_DIR}"
|
||||
)
|
||||
|
||||
qt_generate_deploy_app_script(
|
||||
|
||||
Reference in New Issue
Block a user