Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b96cfec8c | |||
| 657b0fd60c | |||
| 1be22a3051 | |||
| 594635e5cf | |||
| f9b106cf5b | |||
| a9861d18b7 | |||
| c14138f031 | |||
| 60686fde24 |
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.1)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.2)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
@@ -28,7 +28,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 2122)
|
||||
set(APP_ANDROID_VERSION_CODE 2123)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -267,10 +267,6 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/utils/ipcClient.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/platformTheme.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayIconBackend.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/platformTrayTheme.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayIconCommon.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/openVpnProtocol.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/wireGuardProtocol.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/xrayProtocol.h
|
||||
@@ -282,81 +278,20 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
${CLIENT_ROOT_DIR}/core/utils/ipcClient.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/platformTheme.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/platformTrayTheme.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayIconCommon.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/openVpnProtocol.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/wireGuardProtocol.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/xrayProtocol.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/awgProtocol.cpp
|
||||
)
|
||||
|
||||
if(APPLE AND NOT MACOS_NE)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/mactrayiconbackend.h
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/mactraytheme.h
|
||||
)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/mactrayiconbackend.mm
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/mactraytheme.cpp
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(APPLE AND MACOS_NE)
|
||||
# Include only the tray notification handler in NE builds
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/platformTheme.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayIconBackend.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/platformTrayTheme.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayIconCommon.h
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintrayiconbackend.h
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintrayicon.h
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintraytheme.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayThemeChangeFilter.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/platformTheme.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/platformTrayTheme.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayIconCommon.cpp
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintrayiconbackend.cpp
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintrayicon.cpp
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintraytheme.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayThemeChangeFilter.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/windowsutils.h
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintrayiconbackend.h
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintrayicon.h
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintraytheme.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayThemeChangeFilter.h
|
||||
)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/windowsutils.cpp
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintrayiconbackend.cpp
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintrayicon.cpp
|
||||
${CLIENT_ROOT_DIR}/platforms/windows/wintraytheme.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayThemeChangeFilter.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(LINUX)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/platforms/linux/linuxutils.h
|
||||
${CLIENT_ROOT_DIR}/platforms/linux/linuxtrayiconbackend.h
|
||||
${CLIENT_ROOT_DIR}/platforms/linux/linuxtraytheme.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayThemeChangeFilter.h
|
||||
)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/platforms/linux/linuxutils.cpp
|
||||
${CLIENT_ROOT_DIR}/platforms/linux/linuxtrayiconbackend.cpp
|
||||
${CLIENT_ROOT_DIR}/platforms/linux/linuxtraytheme.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/trayThemeChangeFilter.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -49,14 +49,92 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
|
||||
{
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
|
||||
{
|
||||
if (serverId.isEmpty()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
if (serverConfigUtils::isLegacyApiSubscription(m_serversRepository->serverKind(serverId))) {
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
}
|
||||
|
||||
DockerContainer container = DockerContainer::None;
|
||||
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
return ErrorCode::NoInstalledContainersError;
|
||||
}
|
||||
|
||||
if (ContainerUtils::isUnsupportedContainer(container)) {
|
||||
return ErrorCode::LegacyContainerNotSupportedError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
{
|
||||
ContainerConfig containerConfigModel;
|
||||
QPair<QString, QString> dns;
|
||||
QString hostName;
|
||||
@@ -120,10 +198,6 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
|
||||
containerConfigModel, container);
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ public:
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode isConnectionSupported(const QString &serverId) const;
|
||||
|
||||
ErrorCode openConnection(const QString &serverId);
|
||||
|
||||
void closeConnection();
|
||||
@@ -73,6 +75,8 @@ signals:
|
||||
#endif
|
||||
|
||||
private:
|
||||
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
VpnConnection* m_vpnConnection;
|
||||
|
||||
@@ -191,7 +191,7 @@ void CoreController::initControllers()
|
||||
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
|
||||
setQmlContextProperty("LanguageUiController", m_languageUiController);
|
||||
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/controllers/api/apiNewsUiController.h"
|
||||
#include "ui/models/api/apiCountryModel.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
@@ -156,15 +155,17 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
void CoreSignalHandlers::initImportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
|
||||
if (!m_coreController->m_connectionController->isConnected()) {
|
||||
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
if (m_coreController->m_connectionUiController->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -176,17 +177,14 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
if (processedServerId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
|
||||
if (apiV2.has_value()) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
if (!apiV2.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
|
||||
apiV2->apiConfig.serverCountryCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -237,13 +235,16 @@ void CoreSignalHandlers::initLanguageHandler()
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
|
||||
});
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAutoConnectHandler()
|
||||
{
|
||||
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
|
||||
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +349,9 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initStrictKillSwitchHandler()
|
||||
@@ -425,12 +429,7 @@ void CoreSignalHandlers::initNotificationHandler()
|
||||
|
||||
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_coreController->m_notificationHandler);
|
||||
connect(m_coreController, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
||||
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::connectionErrorOccurred, trayHandler,
|
||||
&SystemTrayNotificationHandler::setConnectionError);
|
||||
connect(m_coreController->m_pageController, &PageController::errorMessageClosed, trayHandler,
|
||||
&SystemTrayNotificationHandler::clearConnectionError);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initUpdateFoundHandler()
|
||||
|
||||
@@ -72,6 +72,16 @@ namespace
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
|
||||
{
|
||||
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
|
||||
if (removeDataVolume) {
|
||||
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
|
||||
script = SshSession::replaceVars(script, vars);
|
||||
}
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
InstallController::InstallController(SecureServersRepository *serversRepository,
|
||||
@@ -120,14 +130,10 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return e;
|
||||
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
|
||||
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
if (!isUpdate) {
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
}
|
||||
sshSession.runScript(credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
removeContainerVars));
|
||||
const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
|
||||
|
||||
qDebug().noquote() << "buildContainerWorker start";
|
||||
@@ -152,8 +158,8 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return startupContainerWorker(credentials, container, config, sshSession);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
{
|
||||
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
@@ -185,7 +191,7 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
SshSession sshSession(this);
|
||||
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
|
||||
|
||||
bool xrayServerSettingsChanged = false;
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
@@ -213,11 +219,11 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
|
||||
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
|
||||
XrayConfigurator xrayConfigurator(&sshSession);
|
||||
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall="
|
||||
qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
|
||||
<< reinstallRequired;
|
||||
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error="
|
||||
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
}
|
||||
}
|
||||
@@ -236,6 +242,41 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig)
|
||||
{
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto config = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto config = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedUser);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto config = m_serversRepository->nativeConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::Native);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
|
||||
{
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
@@ -980,12 +1021,11 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
ErrorCode errorCode = sshSession.runScript(
|
||||
credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
|
||||
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
ErrorCode errorCode =
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
|
||||
@@ -1463,7 +1503,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = containerAndPortMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1488,7 +1528,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = torOrDnsRegMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,12 @@ public:
|
||||
~InstallController();
|
||||
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
|
||||
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
|
||||
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
|
||||
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode rebootServer(const QString &serverId);
|
||||
ErrorCode removeAllContainers(const QString &serverId);
|
||||
|
||||
@@ -29,6 +29,11 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
|
||||
@@ -27,6 +27,8 @@ struct NativeServerConfig {
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
@@ -43,6 +43,11 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void SelfHostedUserServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
|
||||
const QString &secondaryDns) const
|
||||
{
|
||||
|
||||
@@ -32,6 +32,8 @@ struct SelfHostedUserServerConfig {
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
@@ -39,33 +39,44 @@ QString OpenVpnProtocol::defaultConfigPath()
|
||||
return p;
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::stop()
|
||||
void OpenVpnProtocol::cleanupResources()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
|
||||
// TODO: need refactoring
|
||||
// sendTermSignal() will even return true while server connected ???
|
||||
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Connected)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
|
||||
if (m_openVpnProcess || openVpnProcessIsRunning()) {
|
||||
if (!sendTermSignal()) {
|
||||
killOpenVpnProcess();
|
||||
}
|
||||
QThread::msleep(10);
|
||||
m_managementServer.stop();
|
||||
}
|
||||
m_managementServer.stop();
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
|
||||
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
|
||||
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
|
||||
qWarning() << "OpenVpnProtocol::cleanupResources(): Failed to disable killswitch";
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
void OpenVpnProtocol::stop()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
|
||||
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|
||||
|| m_connectionState == Vpn::ConnectionState::Connecting
|
||||
|| m_connectionState == Vpn::ConnectionState::Connected
|
||||
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
|
||||
|
||||
if (wasActive) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
}
|
||||
|
||||
cleanupResources();
|
||||
|
||||
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnProtocol::prepare()
|
||||
@@ -168,7 +179,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
|
||||
|
||||
ErrorCode OpenVpnProtocol::start()
|
||||
{
|
||||
OpenVpnProtocol::stop();
|
||||
cleanupResources();
|
||||
|
||||
if (!QFileInfo::exists(configPath())) {
|
||||
setLastError(ErrorCode::OpenVpnConfigMissing);
|
||||
|
||||
@@ -29,6 +29,7 @@ protected slots:
|
||||
void onReadyReadDataFromManagementServer();
|
||||
|
||||
private:
|
||||
void cleanupResources();
|
||||
QString configPath() const;
|
||||
bool openVpnProcessIsRunning() const;
|
||||
bool sendTermSignal();
|
||||
|
||||
@@ -9,10 +9,13 @@
|
||||
#include "ipc.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QTimer>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkInterface>
|
||||
#include <QNetworkProxy>
|
||||
#include <QTcpSocket>
|
||||
#include <QtCore/qlogging.h>
|
||||
#include <QtCore/qobjectdefs.h>
|
||||
#include <QtCore/qprocess.h>
|
||||
@@ -56,6 +59,28 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) :
|
||||
qWarning() << "Xray config string is not a valid JSON object";
|
||||
m_xrayConfig = {};
|
||||
}
|
||||
|
||||
m_serverPort = extractServerPort();
|
||||
}
|
||||
|
||||
int XrayProtocol::extractServerPort() const
|
||||
{
|
||||
const QJsonArray outbounds = m_xrayConfig.value(amnezia::protocols::xray::outbounds).toArray();
|
||||
if (outbounds.isEmpty())
|
||||
return 0;
|
||||
|
||||
const QJsonObject settings = outbounds.first().toObject().value(amnezia::protocols::xray::settings).toObject();
|
||||
|
||||
QJsonArray servers;
|
||||
if (settings.contains(amnezia::protocols::xray::vnext))
|
||||
servers = settings.value(amnezia::protocols::xray::vnext).toArray();
|
||||
else if (settings.contains(amnezia::protocols::xray::servers))
|
||||
servers = settings.value(amnezia::protocols::xray::servers).toArray();
|
||||
|
||||
if (servers.isEmpty())
|
||||
return 0;
|
||||
|
||||
return servers.first().toObject().value(amnezia::protocols::xray::port).toInt();
|
||||
}
|
||||
|
||||
XrayProtocol::~XrayProtocol()
|
||||
@@ -68,6 +93,13 @@ ErrorCode XrayProtocol::start()
|
||||
{
|
||||
qDebug() << "XrayProtocol::start()";
|
||||
|
||||
m_connectivityProbeStarted = false;
|
||||
|
||||
if (!probeServerReachable()) {
|
||||
qCritical() << "XrayProtocol: VPN server" << m_remoteAddress << "is unreachable";
|
||||
return ErrorCode::XrayServerUnreachable;
|
||||
}
|
||||
|
||||
// Inject SOCKS5 auth into the inbound before starting xray.
|
||||
// Re-uses existing credentials if the config already has them (e.g. imported config).
|
||||
amnezia::serialization::inbounds::InboundCredentials creds;
|
||||
@@ -104,22 +136,50 @@ ErrorCode XrayProtocol::start()
|
||||
qDebug() << "XrayProtocol: patched legacy inbound listen address to 127.0.0.1";
|
||||
}
|
||||
|
||||
startTimeoutTimer();
|
||||
|
||||
return IpcClient::withInterface(
|
||||
[&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto xrayStart = iface->xrayStart(xrayConfigStr);
|
||||
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
||||
qCritical() << "Failed to start xray";
|
||||
stopTimeoutTimer();
|
||||
return ErrorCode::XrayExecutableCrashed;
|
||||
}
|
||||
return startTun2Socks();
|
||||
},
|
||||
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
||||
[this]() {
|
||||
stopTimeoutTimer();
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
});
|
||||
}
|
||||
|
||||
void XrayProtocol::stop()
|
||||
{
|
||||
qDebug() << "XrayProtocol::stop()";
|
||||
|
||||
stopTimeoutTimer();
|
||||
stopLivenessMonitor();
|
||||
|
||||
if (m_tun2socksProcess) {
|
||||
m_tun2socksProcess->blockSignals(true);
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
m_tun2socksProcess->terminate();
|
||||
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
|
||||
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
|
||||
qWarning() << "Failed to terminate tun2socks. Killing the process...";
|
||||
m_tun2socksProcess->kill();
|
||||
m_tun2socksProcess->waitForFinished(1000);
|
||||
}
|
||||
#else
|
||||
m_tun2socksProcess->kill();
|
||||
#endif
|
||||
|
||||
m_tun2socksProcess->close();
|
||||
m_tun2socksProcess.reset();
|
||||
}
|
||||
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto disableKillSwitch = iface->disableKillSwitch();
|
||||
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
|
||||
@@ -142,31 +202,13 @@ void XrayProtocol::stop()
|
||||
qWarning() << "Failed to stop xray";
|
||||
});
|
||||
|
||||
if (m_tun2socksProcess) {
|
||||
m_tun2socksProcess->blockSignals(true);
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
m_tun2socksProcess->terminate();
|
||||
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
|
||||
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
|
||||
qWarning() << "Failed to terminate tun2socks. Killing the process...";
|
||||
m_tun2socksProcess->kill();
|
||||
}
|
||||
#else
|
||||
// terminate does not do anything useful on Windows
|
||||
// so just kill the process
|
||||
m_tun2socksProcess->kill();
|
||||
#endif
|
||||
|
||||
m_tun2socksProcess->close();
|
||||
m_tun2socksProcess.reset();
|
||||
}
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
|
||||
ErrorCode XrayProtocol::startTun2Socks()
|
||||
{
|
||||
m_tunResourceBusy = false;
|
||||
|
||||
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
|
||||
if (!m_tun2socksProcess->waitForSource()) {
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
@@ -191,15 +233,31 @@ ErrorCode XrayProtocol::startTun2Socks()
|
||||
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
|
||||
qDebug() << "[tun2socks]:" << line;
|
||||
|
||||
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://")) {
|
||||
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
|
||||
if (line.contains("resource busy"))
|
||||
m_tunResourceBusy = true;
|
||||
|
||||
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
|
||||
stop();
|
||||
setLastError(res);
|
||||
} else {
|
||||
setConnectionState(Vpn::ConnectionState::Connected);
|
||||
}
|
||||
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://") && !m_connectivityProbeStarted) {
|
||||
m_connectivityProbeStarted = true;
|
||||
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
|
||||
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, this, nullptr);
|
||||
|
||||
runConnectivityProbe([this](bool ok) {
|
||||
if (!ok) {
|
||||
qCritical() << "Xray connectivity probe failed: no traffic flows through the tunnel";
|
||||
stop();
|
||||
setLastError(ErrorCode::XrayConnectivityCheckFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
|
||||
stop();
|
||||
setLastError(res);
|
||||
} else {
|
||||
stopTimeoutTimer();
|
||||
setConnectionState(Vpn::ConnectionState::Connected);
|
||||
startLivenessMonitor();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
@@ -207,15 +265,7 @@ ErrorCode XrayProtocol::startTun2Socks()
|
||||
connect(
|
||||
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this,
|
||||
[this](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
// Check stdout for "resource busy" — the TUN device was not yet released
|
||||
// by the previous tun2socks instance. Retry after a short delay.
|
||||
bool resourceBusy = false;
|
||||
if (m_tun2socksProcess) {
|
||||
auto readOut = m_tun2socksProcess->readAllStandardOutput();
|
||||
if (readOut.waitForFinished()) {
|
||||
resourceBusy = readOut.returnValue().contains("resource busy");
|
||||
}
|
||||
}
|
||||
const bool resourceBusy = m_tunResourceBusy;
|
||||
|
||||
if (resourceBusy && m_tun2socksRetryCount < maxTun2SocksRetries) {
|
||||
m_tun2socksRetryCount++;
|
||||
@@ -331,3 +381,98 @@ ErrorCode XrayProtocol::setupRouting()
|
||||
},
|
||||
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
||||
}
|
||||
|
||||
bool XrayProtocol::probeServerReachable()
|
||||
{
|
||||
if (m_remoteAddress.isEmpty() || m_serverPort <= 0) {
|
||||
qWarning() << "XrayProtocol: skipping server reachability probe (address/port unknown)";
|
||||
return true;
|
||||
}
|
||||
|
||||
QTcpSocket sock;
|
||||
sock.connectToHost(m_remoteAddress, static_cast<quint16>(m_serverPort));
|
||||
const bool ok = sock.waitForConnected(m_serverProbeTimeoutMs);
|
||||
if (!ok) {
|
||||
qWarning() << "XrayProtocol: server" << m_remoteAddress << ":" << m_serverPort
|
||||
<< "unreachable:" << sock.errorString();
|
||||
}
|
||||
sock.abort();
|
||||
return ok;
|
||||
}
|
||||
|
||||
void XrayProtocol::runConnectivityProbe(std::function<void(bool)> onResult)
|
||||
{
|
||||
if (m_remoteAddress.isEmpty() || m_serverPort <= 0) {
|
||||
qWarning() << "XrayProtocol: connectivity probe skipped (server address/port unknown)";
|
||||
onResult(true);
|
||||
return;
|
||||
}
|
||||
|
||||
auto *sock = new QTcpSocket(this);
|
||||
|
||||
QNetworkProxy proxy(QNetworkProxy::Socks5Proxy, QStringLiteral("127.0.0.1"),
|
||||
static_cast<quint16>(m_socksPort), m_socksUser, m_socksPassword);
|
||||
proxy.setCapabilities(QNetworkProxy::TunnelingCapability | QNetworkProxy::HostNameLookupCapability);
|
||||
sock->setProxy(proxy);
|
||||
|
||||
auto *timeout = new QTimer(this);
|
||||
timeout->setSingleShot(true);
|
||||
|
||||
auto done = QSharedPointer<bool>::create(false);
|
||||
|
||||
auto finish = [=](bool ok) {
|
||||
if (*done)
|
||||
return;
|
||||
*done = true;
|
||||
timeout->stop();
|
||||
timeout->deleteLater();
|
||||
sock->abort();
|
||||
sock->deleteLater();
|
||||
onResult(ok);
|
||||
};
|
||||
|
||||
connect(sock, &QTcpSocket::connected, this, [=]() { finish(true); });
|
||||
connect(sock, &QAbstractSocket::errorOccurred, this, [=](QAbstractSocket::SocketError) { finish(false); });
|
||||
connect(timeout, &QTimer::timeout, this, [=]() { finish(false); });
|
||||
|
||||
timeout->start(m_connectivityProbeTimeoutMs);
|
||||
sock->connectToHost(m_remoteAddress, static_cast<quint16>(m_serverPort));
|
||||
}
|
||||
|
||||
void XrayProtocol::startLivenessMonitor()
|
||||
{
|
||||
if (!m_livenessTimer) {
|
||||
m_livenessTimer = new QTimer(this);
|
||||
connect(m_livenessTimer, &QTimer::timeout, this, [this]() {
|
||||
if (connectionState() != Vpn::ConnectionState::Connected)
|
||||
return;
|
||||
|
||||
runConnectivityProbe([this](bool ok) {
|
||||
if (connectionState() != Vpn::ConnectionState::Connected)
|
||||
return;
|
||||
|
||||
if (ok) {
|
||||
m_livenessFailures = 0;
|
||||
} else if (++m_livenessFailures >= m_maxLivenessFailures) {
|
||||
qCritical() << "XrayProtocol: liveness check failed" << m_livenessFailures
|
||||
<< "times in a row, the tunnel is dead";
|
||||
stop();
|
||||
setLastError(ErrorCode::XrayConnectionLost);
|
||||
} else {
|
||||
qWarning() << "XrayProtocol: liveness check failed (" << m_livenessFailures << "/"
|
||||
<< m_maxLivenessFailures << ")";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
m_livenessFailures = 0;
|
||||
m_livenessTimer->start(m_livenessIntervalMs);
|
||||
}
|
||||
|
||||
void XrayProtocol::stopLivenessMonitor()
|
||||
{
|
||||
if (m_livenessTimer)
|
||||
m_livenessTimer->stop();
|
||||
m_livenessFailures = 0;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,16 @@
|
||||
#include <QHostAddress>
|
||||
#include <QList>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/utils/ipcClient.h"
|
||||
#include "vpnProtocol.h"
|
||||
|
||||
class QTimer;
|
||||
|
||||
class XrayProtocol : public VpnProtocol
|
||||
{
|
||||
public:
|
||||
@@ -25,10 +29,17 @@ private:
|
||||
ErrorCode setupRouting();
|
||||
ErrorCode startTun2Socks();
|
||||
|
||||
bool probeServerReachable();
|
||||
void runConnectivityProbe(std::function<void(bool)> onResult);
|
||||
void startLivenessMonitor();
|
||||
void stopLivenessMonitor();
|
||||
int extractServerPort() const;
|
||||
|
||||
QJsonObject m_xrayConfig;
|
||||
amnezia::RouteMode m_routeMode;
|
||||
QList<QHostAddress> m_dnsServers;
|
||||
QString m_remoteAddress;
|
||||
int m_serverPort = 0;
|
||||
|
||||
QString m_socksUser;
|
||||
QString m_socksPassword;
|
||||
@@ -38,6 +49,22 @@ private:
|
||||
int m_tun2socksRetryCount = 0;
|
||||
static constexpr int maxTun2SocksRetries = 5;
|
||||
static constexpr int tun2socksRetryDelayMs = 400;
|
||||
|
||||
bool m_connectivityProbeStarted = false;
|
||||
bool m_tunResourceBusy = false;
|
||||
|
||||
QTimer *m_livenessTimer = nullptr;
|
||||
int m_livenessFailures = 0;
|
||||
|
||||
static constexpr int defaultServerProbeTimeoutMs = 5000;
|
||||
static constexpr int defaultConnectivityProbeTimeoutMs = 7000;
|
||||
static constexpr int defaultLivenessIntervalMs = 15000;
|
||||
static constexpr int defaultMaxLivenessFailures = 3;
|
||||
|
||||
int m_serverProbeTimeoutMs = defaultServerProbeTimeoutMs;
|
||||
int m_connectivityProbeTimeoutMs = defaultConnectivityProbeTimeoutMs;
|
||||
int m_livenessIntervalMs = defaultLivenessIntervalMs;
|
||||
int m_maxLivenessFailures = defaultMaxLivenessFailures;
|
||||
};
|
||||
|
||||
#endif // XRAYPROTOCOL_H
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace amnezia
|
||||
Awg2,
|
||||
WireGuard,
|
||||
OpenVpn,
|
||||
Cloak,
|
||||
ShadowSocks,
|
||||
Ipsec,
|
||||
Xray,
|
||||
SSXray,
|
||||
|
||||
@@ -21,6 +21,8 @@ QString ContainerUtils::containerToString(DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
if (c == DockerContainer::Cloak)
|
||||
return "amnezia-openvpn-cloak";
|
||||
if (c == DockerContainer::Awg)
|
||||
return "amnezia-awg";
|
||||
if (c == DockerContainer::Awg2)
|
||||
@@ -62,6 +64,8 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
|
||||
{
|
||||
return { { DockerContainer::None, "Not installed" },
|
||||
{ DockerContainer::OpenVpn, "OpenVPN" },
|
||||
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Awg2, "AmneziaWG" },
|
||||
@@ -83,6 +87,10 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
|
||||
return { { DockerContainer::OpenVpn,
|
||||
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
|
||||
"own security protocol with SSL/TLS for key exchange.") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption.") },
|
||||
@@ -194,6 +202,9 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
|
||||
|
||||
ServiceType ContainerUtils::containerService(DockerContainer c)
|
||||
{
|
||||
if (isUnsupportedContainer(c)) {
|
||||
return ServiceType::Vpn;
|
||||
}
|
||||
return ProtocolUtils::protocolService(defaultProtocol(c));
|
||||
}
|
||||
|
||||
@@ -202,6 +213,8 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
|
||||
switch (c) {
|
||||
case DockerContainer::None: return Proto::Unknown;
|
||||
case DockerContainer::OpenVpn: return Proto::OpenVpn;
|
||||
case DockerContainer::Cloak:
|
||||
case DockerContainer::ShadowSocks: return Proto::Unknown;
|
||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||
case DockerContainer::Awg2: return Proto::Awg;
|
||||
case DockerContainer::Awg: return Proto::Awg;
|
||||
@@ -252,6 +265,8 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
// macOS build using Network Extension – allow OpenVPN for parity with iOS.
|
||||
switch (c) {
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::Cloak: return false;
|
||||
case DockerContainer::ShadowSocks: return false;
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg2: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
@@ -336,6 +351,10 @@ int ContainerUtils::easySetupOrder(DockerContainer container)
|
||||
|
||||
bool ContainerUtils::isShareable(DockerContainer container)
|
||||
{
|
||||
if (isUnsupportedContainer(container)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (container) {
|
||||
case DockerContainer::TorWebSite: return false;
|
||||
case DockerContainer::Dns: return false;
|
||||
@@ -352,6 +371,11 @@ bool ContainerUtils::isAwgContainer(DockerContainer container)
|
||||
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
|
||||
}
|
||||
|
||||
bool ContainerUtils::isUnsupportedContainer(DockerContainer container)
|
||||
{
|
||||
return container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks;
|
||||
}
|
||||
|
||||
QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||
{
|
||||
QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol))
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace amnezia
|
||||
|
||||
bool isAwgContainer(DockerContainer container);
|
||||
|
||||
bool isUnsupportedContainer(DockerContainer container);
|
||||
|
||||
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
|
||||
|
||||
int installPageOrder(DockerContainer container);
|
||||
|
||||
@@ -71,6 +71,9 @@ namespace amnezia
|
||||
OpenSslFailed = 800,
|
||||
XrayExecutableCrashed = 803,
|
||||
Tun2SockExecutableCrashed = 804,
|
||||
XrayServerUnreachable = 805,
|
||||
XrayConnectivityCheckFailed = 806,
|
||||
XrayConnectionLost = 807,
|
||||
|
||||
// import and install errors
|
||||
ImportInvalidConfigError = 900,
|
||||
@@ -79,6 +82,7 @@ namespace amnezia
|
||||
ImportBackupFileUseRestoreInstead = 903,
|
||||
RestoreBackupInvalidError = 904,
|
||||
LegacyApiV1NotSupportedError = 905,
|
||||
LegacyContainerNotSupportedError = 906,
|
||||
|
||||
// Android errors
|
||||
AndroidError = 1000,
|
||||
|
||||
@@ -59,6 +59,9 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
|
||||
case (ErrorCode::AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
|
||||
case (ErrorCode::OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
|
||||
case (ErrorCode::XrayServerUnreachable): errorMessage = QObject::tr("Can't connect: the VPN server is unreachable"); break;
|
||||
case (ErrorCode::XrayConnectivityCheckFailed): errorMessage = QObject::tr("Can't connect: no internet traffic flows through the tunnel"); break;
|
||||
case (ErrorCode::XrayConnectionLost): errorMessage = QObject::tr("Connection lost: traffic stopped flowing through the tunnel"); break;
|
||||
|
||||
// VPN errors
|
||||
case (ErrorCode::OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break;
|
||||
@@ -69,6 +72,7 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
|
||||
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
|
||||
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
|
||||
case (ErrorCode::LegacyContainerNotSupportedError): errorMessage = QObject::tr("This protocol is no longer supported. Please select another protocol or remove this container from the server settings."); break;
|
||||
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
|
||||
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <QFileInfo>
|
||||
#include <QLocalSocket>
|
||||
|
||||
#include "daemon.h"
|
||||
#include "daemonlocalserverconnection.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
@@ -58,8 +59,11 @@ bool DaemonLocalServer::initialize() {
|
||||
|
||||
DaemonLocalServerConnection* connection =
|
||||
new DaemonLocalServerConnection(&m_server, socket);
|
||||
connect(socket, &QLocalSocket::disconnected, connection,
|
||||
&DaemonLocalServerConnection::deleteLater);
|
||||
connect(socket, &QLocalSocket::disconnected, connection, [connection]() {
|
||||
logger.debug() << "Client connection dropped, deactivating daemon";
|
||||
Daemon::instance()->deactivate(true);
|
||||
connection->deleteLater();
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -65,12 +65,9 @@
|
||||
<file>controls/text-cursor.svg</file>
|
||||
<file>controls/trash.svg</file>
|
||||
<file>controls/x-circle.svg</file>
|
||||
<file>tray/off-black.svg</file>
|
||||
<file>tray/off-light.svg</file>
|
||||
<file>tray/on-black.svg</file>
|
||||
<file>tray/on-white.svg</file>
|
||||
<file>tray/error-black.svg</file>
|
||||
<file>tray/error-white.svg</file>
|
||||
<file>tray/active.png</file>
|
||||
<file>tray/default.png</file>
|
||||
<file>tray/error.png</file>
|
||||
<file>controls/monitor.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
@@ -15,10 +15,6 @@
|
||||
#include "platforms/ios/QtAppDelegate-C-Interface.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
|
||||
#include "platforms/macos/macosutils.h"
|
||||
#endif
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
bool isAnotherInstanceRunning()
|
||||
{
|
||||
@@ -50,10 +46,6 @@ int main(int argc, char *argv[])
|
||||
AmneziaApplication app(argc, argv);
|
||||
OsSignalHandler::setup();
|
||||
|
||||
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
|
||||
MacOSUtils::patchNSStatusBarSetImageForBigSur();
|
||||
#endif
|
||||
|
||||
ssh_init();
|
||||
QObject::connect(&app, &QCoreApplication::aboutToQuit, []() {
|
||||
ssh_finalize();
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
#include "linuxtrayiconbackend.h"
|
||||
|
||||
#include "ui/utils/trayIconCommon.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr int kLinuxTrayIconSizes[] = { 16, 22, 24, 32, 48, 64, 128 };
|
||||
|
||||
} // namespace
|
||||
|
||||
LinuxTrayIconBackend::LinuxTrayIconBackend(QObject *parent) : m_trayIcon(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void LinuxTrayIconBackend::setMenu(QMenu *menu)
|
||||
{
|
||||
m_trayIcon.setContextMenu(menu);
|
||||
}
|
||||
|
||||
void LinuxTrayIconBackend::setToolTip(const QString &tooltip)
|
||||
{
|
||||
m_trayIcon.setToolTip(tooltip);
|
||||
}
|
||||
|
||||
void LinuxTrayIconBackend::show()
|
||||
{
|
||||
m_trayIcon.show();
|
||||
}
|
||||
|
||||
void LinuxTrayIconBackend::applyVisual(const TrayIconVisual &visual)
|
||||
{
|
||||
if (m_hasLastVisual && visual.connectionState == m_lastState && visual.darkTheme == m_lastDarkTheme) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastState = visual.connectionState;
|
||||
m_lastDarkTheme = visual.darkTheme;
|
||||
m_hasLastVisual = true;
|
||||
|
||||
const QIcon icon = buildTrayIcon(visual.connectionState, visual.darkTheme);
|
||||
m_trayIcon.setIcon(icon);
|
||||
}
|
||||
|
||||
void LinuxTrayIconBackend::showMessage(const QString &title, const QString &message, const TrayIconVisual &visual,
|
||||
int timerMsec)
|
||||
{
|
||||
m_trayIcon.showMessage(title, message,
|
||||
buildTrayIcon(Vpn::ConnectionState::Connected, visual.darkTheme),
|
||||
timerMsec);
|
||||
}
|
||||
|
||||
void LinuxTrayIconBackend::rebuildMenu()
|
||||
{
|
||||
}
|
||||
|
||||
void LinuxTrayIconBackend::setActivatedHandler(std::function<void(QSystemTrayIcon::ActivationReason)> handler)
|
||||
{
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(&m_trayIcon, &QSystemTrayIcon::activated, m_trayIcon.parent(),
|
||||
[handler](QSystemTrayIcon::ActivationReason reason) { handler(reason); });
|
||||
}
|
||||
|
||||
QIcon LinuxTrayIconBackend::buildTrayIcon(Vpn::ConnectionState state, bool darkTheme) const
|
||||
{
|
||||
QIcon icon;
|
||||
for (int size : kLinuxTrayIconSizes) {
|
||||
icon.addPixmap(TrayIconCommon::buildPixmap(size, state, darkTheme));
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
std::unique_ptr<TrayIconBackend> createTrayIconBackend(QObject *parent)
|
||||
{
|
||||
return std::make_unique<LinuxTrayIconBackend>(parent);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#ifndef LINUXTRAYICONBACKEND_H
|
||||
#define LINUXTRAYICONBACKEND_H
|
||||
|
||||
#include "ui/utils/trayIconBackend.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QIcon>
|
||||
#include <QString>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
class LinuxTrayIconBackend final : public TrayIconBackend
|
||||
{
|
||||
public:
|
||||
explicit LinuxTrayIconBackend(QObject *parent);
|
||||
|
||||
void setMenu(QMenu *menu) override;
|
||||
void setToolTip(const QString &tooltip) override;
|
||||
void show() override;
|
||||
void applyVisual(const TrayIconVisual &visual) override;
|
||||
void showMessage(const QString &title, const QString &message, const TrayIconVisual &visual, int timerMsec) override;
|
||||
void rebuildMenu() override;
|
||||
void setActivatedHandler(std::function<void(QSystemTrayIcon::ActivationReason)> handler) override;
|
||||
|
||||
private:
|
||||
QIcon buildTrayIcon(Vpn::ConnectionState state, bool darkTheme) const;
|
||||
|
||||
QSystemTrayIcon m_trayIcon;
|
||||
Vpn::ConnectionState m_lastState = Vpn::ConnectionState::Unknown;
|
||||
bool m_lastDarkTheme = false;
|
||||
bool m_hasLastVisual = false;
|
||||
};
|
||||
|
||||
#endif // LINUXTRAYICONBACKEND_H
|
||||
@@ -1,24 +0,0 @@
|
||||
#include "linuxtraytheme.h"
|
||||
|
||||
#include "platforms/linux/linuxutils.h"
|
||||
#include "ui/utils/trayThemeChangeFilter.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGuiApplication>
|
||||
#include <QObject>
|
||||
#include <QStyleHints>
|
||||
|
||||
void LinuxTrayTheme::installThemeObserver(const std::function<void()> &onThemeChanged, QObject *parent)
|
||||
{
|
||||
if (!onThemeChanged || !parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (QStyleHints *styleHints = QGuiApplication::styleHints()) {
|
||||
QObject::connect(styleHints, &QStyleHints::colorSchemeChanged, parent, [onThemeChanged]() { onThemeChanged(); });
|
||||
}
|
||||
|
||||
qApp->installEventFilter(new TrayThemeChangeFilter(onThemeChanged, parent));
|
||||
|
||||
LinuxUtils::installThemeChangeObserver(onThemeChanged);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef LINUXTRAYTHEME_H
|
||||
#define LINUXTRAYTHEME_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
class QObject;
|
||||
|
||||
namespace LinuxTrayTheme
|
||||
{
|
||||
|
||||
void installThemeObserver(const std::function<void()> &onThemeChanged, QObject *parent);
|
||||
|
||||
} // namespace LinuxTrayTheme
|
||||
|
||||
#endif // LINUXTRAYTHEME_H
|
||||
@@ -1,128 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "linuxutils.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusVariant>
|
||||
#include <QGuiApplication>
|
||||
#include <QPalette>
|
||||
#include <QSettings>
|
||||
#include <QStyleHints>
|
||||
|
||||
namespace {
|
||||
|
||||
bool paletteSuggestsDarkTheme()
|
||||
{
|
||||
const QPalette palette = QGuiApplication::palette();
|
||||
const int windowLightness = palette.color(QPalette::Window).lightness();
|
||||
const int textLightness = palette.color(QPalette::WindowText).lightness();
|
||||
|
||||
if (textLightness - windowLightness > 50) {
|
||||
return true;
|
||||
}
|
||||
if (windowLightness - textLightness > 50) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return windowLightness < 128;
|
||||
}
|
||||
|
||||
class LinuxThemeObserver final : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LinuxThemeObserver(std::function<void()> callback, QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
, m_callback(std::move(callback))
|
||||
{
|
||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||
if (!bus.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bus.connect(QStringLiteral("org.freedesktop.portal.Desktop"),
|
||||
QStringLiteral("/org/freedesktop/portal/desktop"),
|
||||
QStringLiteral("org.freedesktop.portal.Settings"),
|
||||
QStringLiteral("SettingChanged"),
|
||||
this,
|
||||
SLOT(onPortalSettingChanged(QString, QString, QDBusVariant)));
|
||||
}
|
||||
|
||||
void setCallback(std::function<void()> callback)
|
||||
{
|
||||
m_callback = std::move(callback);
|
||||
}
|
||||
|
||||
private slots:
|
||||
void onPortalSettingChanged(const QString &namespaceName, const QString &key, const QDBusVariant &value)
|
||||
{
|
||||
Q_UNUSED(value);
|
||||
|
||||
if (namespaceName == QStringLiteral("org.freedesktop.appearance")
|
||||
&& key == QStringLiteral("color-scheme") && m_callback) {
|
||||
m_callback();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> m_callback;
|
||||
};
|
||||
|
||||
LinuxThemeObserver *g_themeObserver = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool LinuxUtils::isDarkTheme()
|
||||
{
|
||||
if (QStyleHints *styleHints = QGuiApplication::styleHints()) {
|
||||
switch (styleHints->colorScheme()) {
|
||||
case Qt::ColorScheme::Dark:
|
||||
return true;
|
||||
case Qt::ColorScheme::Light:
|
||||
return false;
|
||||
case Qt::ColorScheme::Unknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QSettings settings(QSettings::IniFormat, QSettings::UserScope, QStringLiteral("gtk-3.0"),
|
||||
QStringLiteral("settings"));
|
||||
const QString themeName = settings.value(QStringLiteral("gtk-theme-name")).toString();
|
||||
if (themeName.contains(QStringLiteral("dark"), Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
if (themeName.contains(QStringLiteral("light"), Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QSettings kdeSettings(QSettings::IniFormat, QSettings::UserScope, QStringLiteral("kdeglobals"));
|
||||
const QString colorScheme = kdeSettings.value(QStringLiteral("General/ColorScheme")).toString();
|
||||
if (colorScheme.contains(QStringLiteral("dark"), Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
if (colorScheme.contains(QStringLiteral("light"), Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return paletteSuggestsDarkTheme();
|
||||
}
|
||||
|
||||
void LinuxUtils::installThemeChangeObserver(std::function<void()> callback)
|
||||
{
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_themeObserver) {
|
||||
g_themeObserver = new LinuxThemeObserver(std::move(callback), qApp);
|
||||
return;
|
||||
}
|
||||
|
||||
g_themeObserver->setCallback(std::move(callback));
|
||||
}
|
||||
|
||||
#include "linuxutils.moc"
|
||||
@@ -1,19 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LINUXUTILS_H
|
||||
#define LINUXUTILS_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
class LinuxUtils final {
|
||||
public:
|
||||
static bool isDarkTheme();
|
||||
static void installThemeChangeObserver(std::function<void()> callback);
|
||||
|
||||
private:
|
||||
LinuxUtils() = default;
|
||||
};
|
||||
|
||||
#endif // LINUXUTILS_H
|
||||
@@ -5,12 +5,8 @@
|
||||
#ifndef MACOSSTATUSICON_H
|
||||
#define MACOSSTATUSICON_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QColor>
|
||||
#include <QMenu>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QString>
|
||||
|
||||
class MacOSStatusIcon final : public QObject {
|
||||
Q_OBJECT
|
||||
@@ -22,14 +18,10 @@ class MacOSStatusIcon final : public QObject {
|
||||
|
||||
public:
|
||||
void setIcon(const QString& iconUrl);
|
||||
void setIconFromData(const QByteArray& imageData, bool asTemplate = true);
|
||||
void setMenu(QMenu* menu);
|
||||
void rebuildNativeMenu();
|
||||
void setIndicatorColor(const QColor& indicatorColor);
|
||||
void setMenu(NSMenu* statusBarMenu);
|
||||
void setToolTip(const QString& tooltip);
|
||||
void showMessage(const QString& title, const QString& message);
|
||||
|
||||
private:
|
||||
QPointer<QMenu> m_qtMenu;
|
||||
};
|
||||
|
||||
#endif // MACOSSTATUSICON_H
|
||||
|
||||
@@ -10,38 +10,22 @@
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#import <QResource>
|
||||
|
||||
#include <QAction>
|
||||
|
||||
@interface MacOSStatusIconMenuTarget : NSObject {
|
||||
@public
|
||||
QAction* action;
|
||||
}
|
||||
- (void)triggerAction:(id)sender;
|
||||
@end
|
||||
|
||||
@implementation MacOSStatusIconMenuTarget
|
||||
- (void)triggerAction:(id)sender {
|
||||
Q_UNUSED(sender);
|
||||
if (action) {
|
||||
action->trigger();
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
/**
|
||||
* Creates a NSStatusItem that holds the tray icon. The icon is set as a
|
||||
* template image, so the system controls its effective appearance for the
|
||||
* current menu bar theme. The connection status is baked into the artwork, so
|
||||
* no separate colored indicator is drawn.
|
||||
* Creates a NSStatusItem with that can hold an icon. Additionally a NSView is
|
||||
* set as a subview to the button item of the status item. The view serves as
|
||||
* an indicator that can be displayed in color eventhough the icon is set as a
|
||||
* template. In that way we give the system control over it’s effective
|
||||
* appearance.
|
||||
*/
|
||||
@interface MacOSStatusIconDelegate : NSObject
|
||||
@property(assign) NSStatusItem* statusItem;
|
||||
@property(retain) NSMenu* nativeMenu;
|
||||
@property(retain) NSMutableArray* menuActionTargets;
|
||||
@property(assign) NSView* statusIndicator;
|
||||
|
||||
- (void)setIcon:(NSData*)imageData asTemplate:(BOOL)asTemplate;
|
||||
- (void)setIcon:(NSData*)imageData;
|
||||
- (void)setIndicator;
|
||||
- (void)setIndicatorColor:(NSColor*)color;
|
||||
- (void)setMenu:(NSMenu*)statusBarMenu;
|
||||
- (void)setToolTip:(NSString*)tooltip;
|
||||
- (void)rebuildMenuFromQMenu:(QMenu*)menu;
|
||||
@end
|
||||
|
||||
@implementation MacOSStatusIconDelegate
|
||||
@@ -52,38 +36,58 @@
|
||||
*/
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
self.menuActionTargets = [[NSMutableArray alloc] init];
|
||||
|
||||
// Create status item
|
||||
self.statusItem =
|
||||
[[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
|
||||
self.statusItem.visible = true;
|
||||
// Add the indicator as a subview
|
||||
[self setIndicator];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
self.nativeMenu = nil;
|
||||
self.menuActionTargets = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image for the status icon.
|
||||
*
|
||||
* @param imageData The data for the icon image.
|
||||
* @param asTemplate When true the icon is a template image recolored by the
|
||||
* system for the current menu bar appearance. When false the icon is
|
||||
* rendered in its original colors (used for the colored error icon).
|
||||
* @param iconPath The data for the icon image.
|
||||
*/
|
||||
- (void)setIcon:(NSData*)imageData asTemplate:(BOOL)asTemplate {
|
||||
- (void)setIcon:(NSData*)imageData {
|
||||
NSImage* image = [[NSImage alloc] initWithData:imageData];
|
||||
[image setTemplate:asTemplate];
|
||||
[image setTemplate:true];
|
||||
|
||||
[self.statusItem.button setImage:image];
|
||||
[image release];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds status indicator as a subview to the status item button.
|
||||
*/
|
||||
- (void)setIndicator {
|
||||
float viewHeight = NSHeight([self.statusItem.button bounds]);
|
||||
float dotSize = viewHeight * 0.35;
|
||||
float dotOrigin = (viewHeight - dotSize) * 0.8;
|
||||
|
||||
NSView* dot = [[NSView alloc] initWithFrame:NSMakeRect(dotOrigin, dotOrigin, dotSize, dotSize)];
|
||||
self.statusIndicator = dot;
|
||||
self.statusIndicator.wantsLayer = true;
|
||||
self.statusIndicator.layer.cornerRadius = dotSize * 0.5;
|
||||
|
||||
[self.statusItem.button addSubview:self.statusIndicator];
|
||||
[dot release];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color if the indicator.
|
||||
*
|
||||
* @param color The indicator background color.
|
||||
*/
|
||||
- (void)setIndicatorColor:(NSColor*)color {
|
||||
if (self.statusIndicator) {
|
||||
self.statusIndicator.layer.backgroundColor = color.CGColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status bar menu to the status item.
|
||||
*
|
||||
@@ -101,44 +105,6 @@
|
||||
- (void)setToolTip:(NSString*)tooltip {
|
||||
[self.statusItem.button setToolTip:tooltip];
|
||||
}
|
||||
|
||||
- (void)rebuildMenuFromQMenu:(QMenu*)menu {
|
||||
[self.menuActionTargets removeAllObjects];
|
||||
|
||||
if (self.nativeMenu) {
|
||||
[self.statusItem setMenu:nil];
|
||||
self.nativeMenu = nil;
|
||||
}
|
||||
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMenu* nsMenu = [[NSMenu alloc] initWithTitle:@""];
|
||||
for (QAction* action : menu->actions()) {
|
||||
if (action->isSeparator()) {
|
||||
[nsMenu addItem:[NSMenuItem separatorItem]];
|
||||
continue;
|
||||
}
|
||||
|
||||
MacOSStatusIconMenuTarget* target = [[MacOSStatusIconMenuTarget alloc] init];
|
||||
target->action = action;
|
||||
[self.menuActionTargets addObject:target];
|
||||
[target release];
|
||||
|
||||
NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:action->text().toNSString()
|
||||
action:@selector(triggerAction:)
|
||||
keyEquivalent:@""];
|
||||
[item setTarget:target];
|
||||
[item setEnabled:action->isEnabled()];
|
||||
[item setHidden:!action->isVisible()];
|
||||
[nsMenu addItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
self.nativeMenu = nsMenu;
|
||||
[self.statusItem setMenu:nsMenu];
|
||||
}
|
||||
@end
|
||||
|
||||
namespace {
|
||||
@@ -172,31 +138,27 @@ void MacOSStatusIcon::setIcon(const QString& iconPath) {
|
||||
QResource imageResource = QResource(iconPath);
|
||||
Q_ASSERT(imageResource.isValid());
|
||||
|
||||
[m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData() asTemplate:true];
|
||||
[m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData()];
|
||||
}
|
||||
|
||||
void MacOSStatusIcon::setIconFromData(const QByteArray& imageData, bool asTemplate) {
|
||||
logger.debug() << "Set icon from rendered data";
|
||||
void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) {
|
||||
logger.debug() << "Set indicator color";
|
||||
|
||||
if (imageData.isEmpty()) {
|
||||
if (!indicatorColor.isValid()) {
|
||||
[m_statusBarIcon setIndicatorColor:[NSColor clearColor]];
|
||||
return;
|
||||
}
|
||||
|
||||
NSData* data = [NSData dataWithBytes:imageData.constData() length:imageData.size()];
|
||||
[m_statusBarIcon setIcon:data asTemplate:asTemplate];
|
||||
NSColor* color = [NSColor colorWithCalibratedRed:indicatorColor.red() / 255.0f
|
||||
green:indicatorColor.green() / 255.0f
|
||||
blue:indicatorColor.blue() / 255.0f
|
||||
alpha:indicatorColor.alpha() / 255.0f];
|
||||
[m_statusBarIcon setIndicatorColor:color];
|
||||
}
|
||||
|
||||
void MacOSStatusIcon::setMenu(QMenu* menu) {
|
||||
m_qtMenu = menu;
|
||||
rebuildNativeMenu();
|
||||
|
||||
if (menu) {
|
||||
connect(menu, &QMenu::aboutToShow, this, [this]() { rebuildNativeMenu(); });
|
||||
}
|
||||
}
|
||||
|
||||
void MacOSStatusIcon::rebuildNativeMenu() {
|
||||
[m_statusBarIcon rebuildMenuFromQMenu:m_qtMenu.data()];
|
||||
void MacOSStatusIcon::setMenu(NSMenu* statusBarMenu) {
|
||||
logger.debug() << "Set menu";
|
||||
[m_statusBarIcon setMenu:statusBarMenu];
|
||||
}
|
||||
|
||||
void MacOSStatusIcon::setToolTip(const QString& tooltip) {
|
||||
@@ -212,7 +174,7 @@ void MacOSStatusIcon::showMessage(const QString& title, const QString& message)
|
||||
// This is a no-op is authorization has been granted.
|
||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
|
||||
UNAuthorizationOptionBadge)
|
||||
completionHandler:^(__unused BOOL granted, NSError* _Nullable error) {
|
||||
completionHandler:^(BOOL granted, NSError* _Nullable error) {
|
||||
if (error) {
|
||||
// Note: This error may happen if the application is not signed.
|
||||
NSLog(@"Error asking for permission to send notifications %@", error);
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
|
||||
class MacOSUtils final {
|
||||
public:
|
||||
static NSString* appId();
|
||||
@@ -25,9 +23,6 @@ class MacOSUtils final {
|
||||
static void showDockIcon();
|
||||
|
||||
static void patchNSStatusBarSetImageForBigSur();
|
||||
|
||||
static bool isDarkTheme();
|
||||
static void installInterfaceThemeObserver(std::function<void()> callback);
|
||||
};
|
||||
|
||||
#endif // MACOSUTILS_H
|
||||
|
||||
@@ -137,30 +137,6 @@ void MacOSUtils::showDockIcon() {
|
||||
* Original bug (and sample implementation):
|
||||
* https://bugreports.qt.io/browse/QTBUG-88600
|
||||
*/
|
||||
bool MacOSUtils::isDarkTheme() {
|
||||
if (@available(macOS 10.14, *)) {
|
||||
NSAppearanceName appearanceName = [[NSApp effectiveAppearance] name];
|
||||
return appearanceName && [appearanceName isEqualToString:NSAppearanceNameDarkAqua];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MacOSUtils::installInterfaceThemeObserver(std::function<void()> callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::function<void()> themeCallback = std::move(callback);
|
||||
|
||||
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
|
||||
[center addObserverForName:@"AppleInterfaceThemeChangedNotification"
|
||||
object:nil
|
||||
queue:[NSOperationQueue mainQueue]
|
||||
usingBlock:^(__unused NSNotification *notification) {
|
||||
themeCallback();
|
||||
}];
|
||||
}
|
||||
|
||||
void MacOSUtils::patchNSStatusBarSetImageForBigSur() {
|
||||
Method original = class_getInstanceMethod([NSStatusBarButton class], @selector(setImage:));
|
||||
Method patched = class_getInstanceMethod([NSStatusBarButton class], @selector(setImagePatched:));
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef MACTRAYICONBACKEND_H
|
||||
#define MACTRAYICONBACKEND_H
|
||||
|
||||
#include "ui/utils/trayIconBackend.h"
|
||||
|
||||
#include "macosstatusicon.h"
|
||||
|
||||
class MacTrayIconBackend final : public TrayIconBackend
|
||||
{
|
||||
public:
|
||||
explicit MacTrayIconBackend(QObject *parent);
|
||||
|
||||
void setMenu(QMenu *menu) override;
|
||||
void setToolTip(const QString &tooltip) override;
|
||||
void show() override;
|
||||
void applyVisual(const TrayIconVisual &visual) override;
|
||||
void showMessage(const QString &title, const QString &message, const TrayIconVisual &visual, int timerMsec) override;
|
||||
void rebuildMenu() override;
|
||||
void setActivatedHandler(std::function<void(QSystemTrayIcon::ActivationReason)> handler) override;
|
||||
|
||||
private:
|
||||
MacOSStatusIcon m_statusIcon;
|
||||
};
|
||||
|
||||
#endif // MACTRAYICONBACKEND_H
|
||||
@@ -1,55 +0,0 @@
|
||||
#include "mactrayiconbackend.h"
|
||||
|
||||
#include "ui/utils/trayIconCommon.h"
|
||||
|
||||
MacTrayIconBackend::MacTrayIconBackend(QObject *parent)
|
||||
: m_statusIcon(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void MacTrayIconBackend::setMenu(QMenu *menu)
|
||||
{
|
||||
m_statusIcon.setMenu(menu);
|
||||
}
|
||||
|
||||
void MacTrayIconBackend::setToolTip(const QString &tooltip)
|
||||
{
|
||||
m_statusIcon.setToolTip(tooltip);
|
||||
}
|
||||
|
||||
void MacTrayIconBackend::show()
|
||||
{
|
||||
}
|
||||
|
||||
void MacTrayIconBackend::applyVisual(const TrayIconVisual &visual)
|
||||
{
|
||||
if (TrayIconCommon::isColoredState(visual.connectionState)) {
|
||||
// Error icon carries a red badge: render it in color, not as a template.
|
||||
m_statusIcon.setIconFromData(TrayIconCommon::buildColorPng(visual.connectionState, visual.darkTheme),
|
||||
/*asTemplate*/ false);
|
||||
} else {
|
||||
m_statusIcon.setIconFromData(TrayIconCommon::buildTemplatePng(visual.connectionState), /*asTemplate*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
void MacTrayIconBackend::showMessage(const QString &title, const QString &message, const TrayIconVisual &visual, int timerMsec)
|
||||
{
|
||||
Q_UNUSED(visual);
|
||||
Q_UNUSED(timerMsec);
|
||||
m_statusIcon.showMessage(title, message);
|
||||
}
|
||||
|
||||
void MacTrayIconBackend::rebuildMenu()
|
||||
{
|
||||
m_statusIcon.rebuildNativeMenu();
|
||||
}
|
||||
|
||||
void MacTrayIconBackend::setActivatedHandler(std::function<void(QSystemTrayIcon::ActivationReason)> handler)
|
||||
{
|
||||
Q_UNUSED(handler);
|
||||
}
|
||||
|
||||
std::unique_ptr<TrayIconBackend> createTrayIconBackend(QObject *parent)
|
||||
{
|
||||
return std::make_unique<MacTrayIconBackend>(parent);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "mactraytheme.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
void MacTrayTheme::installThemeObserver(const std::function<void()> &onThemeChanged, QObject *parent)
|
||||
{
|
||||
Q_UNUSED(onThemeChanged);
|
||||
Q_UNUSED(parent);
|
||||
// macOS template tray icons follow the menu bar appearance automatically.
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef MACTRAYTHEME_H
|
||||
#define MACTRAYTHEME_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
class QObject;
|
||||
|
||||
namespace MacTrayTheme
|
||||
{
|
||||
|
||||
void installThemeObserver(const std::function<void()> &onThemeChanged, QObject *parent);
|
||||
|
||||
} // namespace MacTrayTheme
|
||||
|
||||
#endif // MACTRAYTHEME_H
|
||||
@@ -14,41 +14,7 @@
|
||||
|
||||
namespace {
|
||||
Logger logger("WindowsUtils");
|
||||
|
||||
constexpr const wchar_t kThemeWatcherClassName[] = L"AmneziaVpnThemeWatcher";
|
||||
|
||||
struct ThemeObserverState
|
||||
{
|
||||
std::function<void()> callback;
|
||||
HWND hwnd = nullptr;
|
||||
};
|
||||
|
||||
ThemeObserverState g_themeObserver;
|
||||
|
||||
bool registryUsesDarkTheme(const QSettings &settings, const QString &lightThemeKey)
|
||||
{
|
||||
if (settings.contains(lightThemeKey)) {
|
||||
return settings.value(lightThemeKey).toInt() != 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK themeWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
Q_UNUSED(wParam);
|
||||
|
||||
if (msg == WM_SETTINGCHANGE && lParam != 0) {
|
||||
const wchar_t *section = reinterpret_cast<const wchar_t *>(lParam);
|
||||
if (wcscmp(section, L"ImmersiveColorSet") == 0 || wcscmp(section, L"WindowsThemeElement") == 0) {
|
||||
if (g_themeObserver.callback) {
|
||||
g_themeObserver.callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
constexpr const int WINDOWS_11_BUILD =
|
||||
22000; // Build Number of the first release win 11 iso
|
||||
@@ -94,51 +60,3 @@ QString WindowsUtils::windowsVersion() {
|
||||
void WindowsUtils::forceCrash() {
|
||||
RaiseException(0x0000DEAD, EXCEPTION_NONCONTINUABLE, 0, NULL);
|
||||
}
|
||||
|
||||
// static
|
||||
bool WindowsUtils::isDarkTheme() {
|
||||
QSettings settings(
|
||||
QStringLiteral("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"),
|
||||
QSettings::NativeFormat);
|
||||
settings.sync();
|
||||
|
||||
if (settings.contains(QStringLiteral("SystemUsesLightTheme"))) {
|
||||
return registryUsesDarkTheme(settings, QStringLiteral("SystemUsesLightTheme"));
|
||||
}
|
||||
|
||||
if (settings.contains(QStringLiteral("AppsUseLightTheme"))) {
|
||||
return registryUsesDarkTheme(settings, QStringLiteral("AppsUseLightTheme"));
|
||||
}
|
||||
|
||||
logger.warning() << "SystemUsesLightTheme registry key is unavailable; assuming dark theme";
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowsUtils::installThemeChangeObserver(std::function<void()> callback)
|
||||
{
|
||||
g_themeObserver.callback = std::move(callback);
|
||||
|
||||
if (g_themeObserver.hwnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
HINSTANCE instance = GetModuleHandleW(nullptr);
|
||||
WNDCLASSW wc = {};
|
||||
wc.lpfnWndProc = themeWndProc;
|
||||
wc.hInstance = instance;
|
||||
wc.lpszClassName = kThemeWatcherClassName;
|
||||
|
||||
WNDCLASSW existing = {};
|
||||
if (!GetClassInfoW(instance, kThemeWatcherClassName, &existing)) {
|
||||
if (!RegisterClassW(&wc)) {
|
||||
WindowsUtils::windowsLog("Failed to register theme watcher window class");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
g_themeObserver.hwnd = CreateWindowExW(0, kThemeWatcherClassName, L"AmneziaVpnThemeWatcher", 0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE, nullptr, instance, nullptr);
|
||||
if (!g_themeObserver.hwnd) {
|
||||
WindowsUtils::windowsLog("Failed to create theme watcher window");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
|
||||
class WindowsUtils final {
|
||||
public:
|
||||
static QString getErrorMessage();
|
||||
@@ -20,9 +18,6 @@ class WindowsUtils final {
|
||||
|
||||
// Force an application crash for testing
|
||||
static void forceCrash();
|
||||
|
||||
static bool isDarkTheme();
|
||||
static void installThemeChangeObserver(std::function<void()> callback);
|
||||
};
|
||||
|
||||
#endif // WINDOWSUTILS_H
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#include "wintrayicon.h"
|
||||
|
||||
#include "ui/utils/trayIconCommon.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
namespace WinTrayIcon
|
||||
{
|
||||
QIcon buildIcon(Vpn::ConnectionState state, bool darkTheme)
|
||||
{
|
||||
return TrayIconCommon::buildIcon(state, darkTheme);
|
||||
}
|
||||
|
||||
void applyTo(QSystemTrayIcon &trayIcon, Vpn::ConnectionState state, bool darkTheme)
|
||||
{
|
||||
trayIcon.setIcon(buildIcon(state, darkTheme));
|
||||
}
|
||||
|
||||
QIcon buildNotifyIcon(bool darkTheme)
|
||||
{
|
||||
return buildIcon(Vpn::ConnectionState::Connected, darkTheme);
|
||||
}
|
||||
|
||||
void configure(QSystemTrayIcon &trayIcon, QMenu *menu, const QString &tooltip)
|
||||
{
|
||||
trayIcon.setContextMenu(menu);
|
||||
trayIcon.setToolTip(tooltip);
|
||||
}
|
||||
|
||||
void show(QSystemTrayIcon &trayIcon)
|
||||
{
|
||||
trayIcon.show();
|
||||
}
|
||||
|
||||
void showMessage(QSystemTrayIcon &trayIcon, const QString &title, const QString &message, bool darkTheme,
|
||||
int timerMsec)
|
||||
{
|
||||
trayIcon.showMessage(title, message, buildNotifyIcon(darkTheme), timerMsec);
|
||||
}
|
||||
} // namespace WinTrayIcon
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef WINTRAYICON_H
|
||||
#define WINTRAYICON_H
|
||||
|
||||
#include "core/protocols/vpnProtocol.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QIcon>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
class QMenu;
|
||||
class QString;
|
||||
|
||||
namespace WinTrayIcon
|
||||
{
|
||||
QIcon buildIcon(Vpn::ConnectionState state, bool darkTheme);
|
||||
void applyTo(QSystemTrayIcon &trayIcon, Vpn::ConnectionState state, bool darkTheme);
|
||||
QIcon buildNotifyIcon(bool darkTheme);
|
||||
|
||||
void configure(QSystemTrayIcon &trayIcon, QMenu *menu, const QString &tooltip);
|
||||
void show(QSystemTrayIcon &trayIcon);
|
||||
void showMessage(QSystemTrayIcon &trayIcon, const QString &title, const QString &message, bool darkTheme,
|
||||
int timerMsec);
|
||||
} // namespace WinTrayIcon
|
||||
|
||||
#endif // WINTRAYICON_H
|
||||
@@ -1,67 +0,0 @@
|
||||
#include "wintrayiconbackend.h"
|
||||
|
||||
#include "platforms/windows/wintrayicon.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
WinTrayIconBackend::WinTrayIconBackend(QObject *parent) : m_trayIcon(parent)
|
||||
{
|
||||
m_reapplyTimerShort.setSingleShot(true);
|
||||
m_reapplyTimerLong.setSingleShot(true);
|
||||
QObject::connect(&m_reapplyTimerShort, &QTimer::timeout, &m_trayIcon, [this]() { reapplyLastVisual(); });
|
||||
QObject::connect(&m_reapplyTimerLong, &QTimer::timeout, &m_trayIcon, [this]() { reapplyLastVisual(); });
|
||||
}
|
||||
|
||||
void WinTrayIconBackend::reapplyLastVisual()
|
||||
{
|
||||
WinTrayIcon::applyTo(m_trayIcon, m_lastVisual.connectionState, m_lastVisual.darkTheme);
|
||||
}
|
||||
|
||||
void WinTrayIconBackend::setMenu(QMenu *menu)
|
||||
{
|
||||
m_trayIcon.setContextMenu(menu);
|
||||
}
|
||||
|
||||
void WinTrayIconBackend::setToolTip(const QString &tooltip)
|
||||
{
|
||||
m_trayIcon.setToolTip(tooltip);
|
||||
}
|
||||
|
||||
void WinTrayIconBackend::show()
|
||||
{
|
||||
WinTrayIcon::show(m_trayIcon);
|
||||
}
|
||||
|
||||
void WinTrayIconBackend::applyVisual(const TrayIconVisual &visual)
|
||||
{
|
||||
m_lastVisual = visual;
|
||||
WinTrayIcon::applyTo(m_trayIcon, visual.connectionState, visual.darkTheme);
|
||||
|
||||
m_reapplyTimerShort.start(250);
|
||||
m_reapplyTimerLong.start(1200);
|
||||
}
|
||||
|
||||
void WinTrayIconBackend::showMessage(const QString &title, const QString &message, const TrayIconVisual &visual,
|
||||
int timerMsec)
|
||||
{
|
||||
WinTrayIcon::showMessage(m_trayIcon, title, message, visual.darkTheme, timerMsec);
|
||||
}
|
||||
|
||||
void WinTrayIconBackend::rebuildMenu()
|
||||
{
|
||||
}
|
||||
|
||||
void WinTrayIconBackend::setActivatedHandler(std::function<void(QSystemTrayIcon::ActivationReason)> handler)
|
||||
{
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(&m_trayIcon, &QSystemTrayIcon::activated, m_trayIcon.parent(),
|
||||
[handler](QSystemTrayIcon::ActivationReason reason) { handler(reason); });
|
||||
}
|
||||
|
||||
std::unique_ptr<TrayIconBackend> createTrayIconBackend(QObject *parent)
|
||||
{
|
||||
return std::make_unique<WinTrayIconBackend>(parent);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#ifndef WINTRAYICONBACKEND_H
|
||||
#define WINTRAYICONBACKEND_H
|
||||
|
||||
#include "ui/utils/trayIconBackend.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QIcon>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QTimer>
|
||||
|
||||
class WinTrayIconBackend final : public TrayIconBackend
|
||||
{
|
||||
public:
|
||||
explicit WinTrayIconBackend(QObject *parent);
|
||||
|
||||
void setMenu(QMenu *menu) override;
|
||||
void setToolTip(const QString &tooltip) override;
|
||||
void show() override;
|
||||
void applyVisual(const TrayIconVisual &visual) override;
|
||||
void showMessage(const QString &title, const QString &message, const TrayIconVisual &visual, int timerMsec) override;
|
||||
void rebuildMenu() override;
|
||||
void setActivatedHandler(std::function<void(QSystemTrayIcon::ActivationReason)> handler) override;
|
||||
|
||||
private:
|
||||
void reapplyLastVisual();
|
||||
|
||||
QSystemTrayIcon m_trayIcon;
|
||||
TrayIconVisual m_lastVisual;
|
||||
QTimer m_reapplyTimerShort;
|
||||
QTimer m_reapplyTimerLong;
|
||||
};
|
||||
|
||||
#endif // WINTRAYICONBACKEND_H
|
||||
@@ -1,32 +0,0 @@
|
||||
#include "wintraytheme.h"
|
||||
|
||||
#include "platforms/windows/windowsutils.h"
|
||||
#include "ui/utils/trayThemeChangeFilter.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGuiApplication>
|
||||
#include <QObject>
|
||||
#include <QStyleHints>
|
||||
#include <QTimer>
|
||||
|
||||
void WinTrayTheme::installThemeObserver(const std::function<void()> &onThemeChanged, QObject *parent)
|
||||
{
|
||||
if (!onThemeChanged || !parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *debounce = new QTimer(parent);
|
||||
debounce->setSingleShot(true);
|
||||
QObject::connect(debounce, &QTimer::timeout, parent, [onThemeChanged]() { onThemeChanged(); });
|
||||
|
||||
const auto schedule = [debounce]() { debounce->start(150); };
|
||||
|
||||
if (QStyleHints *styleHints = QGuiApplication::styleHints()) {
|
||||
QObject::connect(styleHints, &QStyleHints::colorSchemeChanged, parent,
|
||||
[schedule](Qt::ColorScheme) { schedule(); });
|
||||
}
|
||||
|
||||
qApp->installEventFilter(new TrayThemeChangeFilter([schedule]() { schedule(); }, parent));
|
||||
|
||||
WindowsUtils::installThemeChangeObserver([schedule]() { schedule(); });
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef WINTRAYTHEME_H
|
||||
#define WINTRAYTHEME_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
class QObject;
|
||||
|
||||
namespace WinTrayTheme
|
||||
{
|
||||
|
||||
void installThemeObserver(const std::function<void()> &onThemeChanged, QObject *parent);
|
||||
|
||||
} // namespace WinTrayTheme
|
||||
|
||||
#endif // WINTRAYTHEME_H
|
||||
@@ -1,5 +1,6 @@
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
||||
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
|
||||
sudo docker volume ls | grep amnezia | awk '{print $2}' | xargs sudo docker volume rm -f;\
|
||||
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
||||
sudo rm -frd /opt/amnezia
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
sudo docker stop $CONTAINER_NAME;\
|
||||
sudo docker rm -fv $CONTAINER_NAME;\
|
||||
sudo docker rmi $CONTAINER_NAME;\
|
||||
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true
|
||||
sudo docker rmi $CONTAINER_NAME;
|
||||
|
||||
@@ -475,8 +475,7 @@ bool SubscriptionUiController::deactivateExternalDevice(const QString &serverId,
|
||||
void SubscriptionUiController::validateConfig()
|
||||
{
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (!serverId.isEmpty() && m_serversController->isLegacyApiV1Server(serverId)) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
if (serverId.isEmpty()) {
|
||||
emit configValidated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
|
||||
ServersController* serversController,
|
||||
@@ -33,7 +35,7 @@ void ConnectionUiController::openConnection()
|
||||
ErrorCode errorCode = m_connectionController->openConnection(serverId);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
notifyConnectionBlocked(errorCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -130,10 +132,36 @@ void ConnectionUiController::toggleConnection()
|
||||
} else if (isConnected()) {
|
||||
closeConnection();
|
||||
} else {
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ErrorCode errorCode = m_connectionController->isConnectionSupported(serverId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
notifyConnectionBlocked(errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
emit prepareConfig();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionUiController::notifyConnectionBlocked(ErrorCode errorCode)
|
||||
{
|
||||
if (errorCode == ErrorCode::LegacyApiV1NotSupportedError) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
return;
|
||||
}
|
||||
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
bool ConnectionUiController::isConnectionInProgress() const
|
||||
{
|
||||
return m_isConnectionInProgress;
|
||||
@@ -143,3 +171,32 @@ bool ConnectionUiController::isConnected() const
|
||||
{
|
||||
return m_isConnected;
|
||||
}
|
||||
|
||||
bool ConnectionUiController::isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex,
|
||||
const QString &clientId) const
|
||||
{
|
||||
if (clientId.isEmpty() || (!isConnected() && !isConnectionInProgress())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_serversController->getDefaultServerId() != serverId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static_cast<int>(m_serversController->getDefaultContainer(serverId)) != containerIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto adminConfig = m_serversController->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString connectionClientId =
|
||||
adminConfig->containerConfig(static_cast<DockerContainer>(containerIndex)).protocolConfig.clientId();
|
||||
if (connectionClientId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return connectionClientId == clientId || connectionClientId.contains(clientId);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public slots:
|
||||
void openConnection();
|
||||
void closeConnection();
|
||||
|
||||
bool isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex, const QString &clientId) const;
|
||||
|
||||
ErrorCode getLastConnectionError();
|
||||
void onConnectionStateChanged(Vpn::ConnectionState state);
|
||||
|
||||
@@ -48,9 +50,12 @@ signals:
|
||||
void connectButtonClicked();
|
||||
void preparingConfig();
|
||||
void prepareConfig();
|
||||
void unsupportedConnectDrawerRequested();
|
||||
void noInstalledContainers();
|
||||
|
||||
private:
|
||||
Vpn::ConnectionState getCurrentConnectionState();
|
||||
void notifyConnectionBlocked(ErrorCode errorCode);
|
||||
|
||||
ConnectionController* m_connectionController;
|
||||
ServersController* m_serversController;
|
||||
|
||||
@@ -248,8 +248,3 @@ void PageController::onShowErrorMessage(ErrorCode errorCode)
|
||||
|
||||
emit showErrorMessage(fullMessage);
|
||||
}
|
||||
|
||||
void PageController::onErrorMessageClosed()
|
||||
{
|
||||
emit errorMessageClosed();
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public slots:
|
||||
int getDrawerDepth() const;
|
||||
int incrementDrawerDepth();
|
||||
int decrementDrawerDepth();
|
||||
void onErrorMessageClosed();
|
||||
|
||||
bool isEdgeToEdgeEnabled();
|
||||
int getStatusBarHeight();
|
||||
int getNavigationBarHeight();
|
||||
@@ -162,7 +162,7 @@ signals:
|
||||
void showErrorMessage(amnezia::ErrorCode);
|
||||
void showErrorMessage(const QString &errorMessage);
|
||||
void showNotificationMessage(const QString &message);
|
||||
void errorMessageClosed();
|
||||
|
||||
void showBusyIndicator(bool visible);
|
||||
void disableControls(bool disabled);
|
||||
void disableTabBar(bool disabled);
|
||||
|
||||
@@ -75,13 +75,7 @@ InstallUiController::InstallUiController(InstallController *installController,
|
||||
m_connectionController(connectionController)
|
||||
{
|
||||
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
} else {
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
});
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, &InstallUiController::installationErrorOccurred);
|
||||
}
|
||||
|
||||
InstallUiController::~InstallUiController()
|
||||
@@ -217,15 +211,13 @@ void InstallUiController::scanServerForInstalledContainers(const QString &server
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
|
||||
containerConfig.container = container;
|
||||
|
||||
|
||||
switch (protocolType) {
|
||||
case Proto::Awg: {
|
||||
containerConfig.protocolConfig = m_awgConfigModel->getProtocolConfig();
|
||||
@@ -271,6 +263,41 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallUiController::updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateClientConfig(serverId, container, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
m_protocolModel->updateModel(updatedConfig);
|
||||
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
|
||||
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
||||
return;
|
||||
}
|
||||
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
@@ -305,13 +332,13 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|
||||
QFuture<ErrorCode> future =
|
||||
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
|
||||
newConfigCopy]() mutable -> ErrorCode {
|
||||
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
return installController->updateServerConfig(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateContainer(serverId, container, oldContainerConfig, containerConfig);
|
||||
ErrorCode errorCode = m_installController->updateServerConfig(serverId, container, oldContainerConfig, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
|
||||
@@ -64,7 +64,8 @@ public slots:
|
||||
|
||||
void scanServerForInstalledContainers(const QString &serverId);
|
||||
|
||||
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
|
||||
void removeServer(const QString &serverId);
|
||||
void rebootServer(const QString &serverId);
|
||||
@@ -132,7 +133,6 @@ signals:
|
||||
void cachedProfileCleared(const QString &message);
|
||||
void apiConfigRemoved(const QString &message);
|
||||
|
||||
void noInstalledContainers();
|
||||
void configValidated(bool isValid);
|
||||
|
||||
private:
|
||||
@@ -162,6 +162,8 @@ private:
|
||||
QString m_privateKeyPassphrase;
|
||||
|
||||
void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex);
|
||||
|
||||
bool buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig);
|
||||
};
|
||||
|
||||
#endif // INSTALLUICONTROLLER_H
|
||||
|
||||
@@ -156,7 +156,17 @@ void ServersUiController::updateModel()
|
||||
|
||||
m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId);
|
||||
|
||||
updateContainersModel();
|
||||
if (!m_processedServerId.isEmpty()) {
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
} else {
|
||||
updateContainersModel();
|
||||
}
|
||||
}
|
||||
updateDefaultServerContainersModel();
|
||||
|
||||
if (hadServersFromGatewayBefore != hasServersFromGatewayNow) {
|
||||
@@ -350,19 +360,14 @@ void ServersUiController::setProcessedServerId(const QString &serverId)
|
||||
m_processedServerId = normalizedServerId;
|
||||
|
||||
if (newIndex >= 0) {
|
||||
updateContainersModel();
|
||||
|
||||
for (const auto &description : m_orderedServerDescriptions) {
|
||||
if (description.serverId != normalizedServerId) {
|
||||
continue;
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
if (description.isApiV2) {
|
||||
if (description.isCountrySelectionAvailable && !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
emit updateApiServicesModel();
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
updateContainersModel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,6 @@ signals:
|
||||
void processedContainerIndexChanged(int index);
|
||||
void hasServersFromGatewayApiChanged();
|
||||
void updateApiCountryModel();
|
||||
void updateApiServicesModel();
|
||||
|
||||
public:
|
||||
void updateModel();
|
||||
|
||||
@@ -22,12 +22,10 @@
|
||||
|
||||
SettingsUiController::SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_settingsController(settingsController),
|
||||
m_serversController(serversController),
|
||||
m_languageUiController(languageUiController)
|
||||
m_serversController(serversController)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged);
|
||||
@@ -157,13 +155,13 @@ void SettingsUiController::restoreAppConfigFromData(const QByteArray &data)
|
||||
{
|
||||
ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data);
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
emit appLanguageChanged(
|
||||
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageUiController->getCurrentLanguageIndex()));
|
||||
emit appLanguageChanged();
|
||||
|
||||
bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled();
|
||||
emit amneziaDnsToggled(amneziaDnsEnabled);
|
||||
|
||||
emit restoreBackupFinished();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
} else {
|
||||
emit errorOccurred(errorCode);
|
||||
@@ -178,6 +176,7 @@ QString SettingsUiController::getAppVersion()
|
||||
void SettingsUiController::clearSettings()
|
||||
{
|
||||
m_settingsController->clearSettings();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
emit resetLanguageToSystem();
|
||||
|
||||
@@ -206,9 +205,8 @@ bool SettingsUiController::isAutoStartEnabled()
|
||||
void SettingsUiController::toggleAutoStart(bool enable)
|
||||
{
|
||||
m_settingsController->toggleAutoStart(enable);
|
||||
if (!enable) {
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
|
||||
bool SettingsUiController::isStartMinimizedEnabled()
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
@@ -17,7 +15,6 @@ class SettingsUiController : public QObject
|
||||
public:
|
||||
explicit SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
|
||||
@@ -32,6 +29,7 @@ public:
|
||||
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
|
||||
|
||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||
Q_PROPERTY(bool autoStartEnabled READ isAutoStartEnabled NOTIFY autoStartChanged)
|
||||
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||
|
||||
public slots:
|
||||
@@ -122,7 +120,7 @@ signals:
|
||||
|
||||
void loggingDisableByWatcher();
|
||||
|
||||
void appLanguageChanged(const LanguageSettings::AvailableLanguageEnum language);
|
||||
void appLanguageChanged();
|
||||
void resetLanguageToSystem();
|
||||
|
||||
void onNotificationStateChanged();
|
||||
@@ -135,12 +133,12 @@ signals:
|
||||
void activityResumed();
|
||||
|
||||
void isHomeAdLabelVisibleChanged(bool visible);
|
||||
void autoStartChanged();
|
||||
void startMinimizedChanged();
|
||||
|
||||
private:
|
||||
SettingsController* m_settingsController;
|
||||
ServersController* m_serversController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,7 +30,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||
switch (role) {
|
||||
case SubscriptionStatusRole: {
|
||||
if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) {
|
||||
return tr("Active");
|
||||
return QStringLiteral("<p><a style=\"color: #28c840;\">%1</a>").arg(tr("Active"));
|
||||
}
|
||||
|
||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)
|
||||
|
||||
@@ -27,6 +27,7 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
|
||||
auto userData = client.value(configKey::userData).toObject();
|
||||
|
||||
switch (role) {
|
||||
case ClientIdRole: return client.value(configKey::clientId).toString();
|
||||
case ClientNameRole: return userData.value(configKey::clientName).toString();
|
||||
case CreationDateRole: return userData.value(configKey::creationDate).toString();
|
||||
case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString();
|
||||
@@ -62,6 +63,7 @@ void ClientManagementModel::updateClientName(int row, const QString &newName)
|
||||
QHash<int, QByteArray> ClientManagementModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[ClientIdRole] = "clientId";
|
||||
roles[ClientNameRole] = "clientName";
|
||||
roles[CreationDateRole] = "creationDate";
|
||||
roles[LatestHandshakeRole] = "latestHandshake";
|
||||
|
||||
@@ -10,7 +10,8 @@ class ClientManagementModel : public QAbstractListModel
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
ClientNameRole = Qt::UserRole + 1,
|
||||
ClientIdRole = Qt::UserRole + 1,
|
||||
ClientNameRole,
|
||||
CreationDateRole,
|
||||
LatestHandshakeRole,
|
||||
DataReceivedRole,
|
||||
|
||||
@@ -23,6 +23,10 @@ public:
|
||||
Q_INVOKABLE int containerFromString(const QString &container) const {
|
||||
return static_cast<int>(amnezia::ContainerUtils::containerFromString(container));
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool isUnsupportedContainer(int containerIndex) const {
|
||||
return amnezia::ContainerUtils::isUnsupportedContainer(static_cast<amnezia::DockerContainer>(containerIndex));
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CONTAINERPROPS_H
|
||||
|
||||
@@ -67,6 +67,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
|
||||
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
|
||||
case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container);
|
||||
case IsShareableRole: return ContainerUtils::isShareable(container);
|
||||
case IsUnsupportedContainerRole: return ContainerUtils::isUnsupportedContainer(container);
|
||||
case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn;
|
||||
case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other;
|
||||
case IsIpsecRole: return container == DockerContainer::Ipsec;
|
||||
@@ -142,7 +143,8 @@ bool ContainersModel::hasInstalledProtocols()
|
||||
|
||||
bool ContainersModel::isInstallationAllowed(DockerContainer container)
|
||||
{
|
||||
return container != DockerContainer::Awg;
|
||||
return container != DockerContainer::Awg
|
||||
&& !ContainerUtils::isUnsupportedContainer(container);
|
||||
}
|
||||
|
||||
void ContainersModel::openContainerSettings(int containerIndex)
|
||||
@@ -176,6 +178,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
|
||||
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
|
||||
roles[IsSupportedRole] = "isSupported";
|
||||
roles[IsShareableRole] = "isShareable";
|
||||
roles[IsUnsupportedContainerRole] = "isUnsupportedContainer";
|
||||
roles[IsInstallationAllowedRole] = "isInstallationAllowed";
|
||||
roles[InstallPageOrderRole] = "installPageOrder";
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
IsSupportedRole,
|
||||
IsShareableRole,
|
||||
|
||||
IsUnsupportedContainerRole,
|
||||
|
||||
InstallPageOrderRole,
|
||||
|
||||
// Container type check roles
|
||||
|
||||
@@ -56,14 +56,17 @@ ListViewType {
|
||||
return
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, proxyDefaultServerContainersModel.mapToSource(index))
|
||||
} else {
|
||||
ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
|
||||
if (!isInstalled) {
|
||||
ServersUiController.processedContainerIndex = containerIndex
|
||||
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
|
||||
containersDropDown.closeTriggered()
|
||||
return
|
||||
}
|
||||
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -5,7 +5,6 @@ import QtQuick.Layouts
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import PageEnum 1.0
|
||||
import ContainerProps 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
@@ -6,8 +6,36 @@ Menu {
|
||||
|
||||
popupType: Popup.Native
|
||||
|
||||
onAboutToShow: blocker.enabled = true
|
||||
onClosed: blocker.enabled = false
|
||||
property Item inputBlocker: null
|
||||
|
||||
Component {
|
||||
id: inputBlockerComponent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
onAboutToShow: {
|
||||
if (!textObj || !textObj.window) {
|
||||
return
|
||||
}
|
||||
|
||||
const contentItem = textObj.window.contentItem
|
||||
if (!inputBlocker) {
|
||||
inputBlocker = inputBlockerComponent.createObject(contentItem)
|
||||
} else {
|
||||
inputBlocker.parent = contentItem
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (inputBlocker) {
|
||||
inputBlocker.destroy()
|
||||
inputBlocker = null
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("C&ut")
|
||||
@@ -31,11 +59,4 @@ Menu {
|
||||
enabled: textObj.length > 0
|
||||
onTriggered: textObj.selectAll()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: blocker
|
||||
z: 2
|
||||
enabled: false
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ PageType {
|
||||
|
||||
filters: [
|
||||
ValueFilter {
|
||||
roleName: "isCurrentlyProcessed"
|
||||
value: true
|
||||
roleName: "serverId"
|
||||
value: ServersUiController.processedServerId
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -440,8 +440,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
@@ -561,7 +561,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
@@ -434,7 +434,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
|
||||
@@ -128,8 +128,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
}
|
||||
var noButtonFunction = function() {}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
|
||||
@@ -129,7 +129,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
|
||||
@@ -112,7 +112,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -279,7 +279,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -17,6 +17,10 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
enableTimer: false
|
||||
|
||||
property bool portDirty: false
|
||||
|
||||
function formatTransport(value) {
|
||||
if (value === "raw") return "RAW (TCP)"
|
||||
if (value === "xhttp") return "XHTTP"
|
||||
@@ -39,8 +43,8 @@ PageType {
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
onActiveFocusChanged: {
|
||||
if (backButton.enabled && backButton.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
@@ -60,8 +64,6 @@ PageType {
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
property alias focusItemId: textFieldWithHeaderType.textField
|
||||
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
@@ -107,13 +109,32 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
enabled: listView.enabled
|
||||
headerText: qsTr("Port")
|
||||
textField.text: port
|
||||
|
||||
Binding {
|
||||
target: textFieldWithHeaderType.textField
|
||||
property: "text"
|
||||
value: port
|
||||
when: !textFieldWithHeaderType.textField.activeFocus
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
textField.maximumLength: 5
|
||||
textField.validator: IntValidator {
|
||||
bottom: 1; top: 65535
|
||||
}
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus && textField.text === "" && port !== "") {
|
||||
textField.text = port
|
||||
}
|
||||
}
|
||||
textField.onTextChanged: {
|
||||
root.portDirty = (textField.text !== port)
|
||||
}
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== port) port = textField.text
|
||||
if (textField.text !== port) {
|
||||
port = textField.text
|
||||
}
|
||||
root.portDirty = false
|
||||
}
|
||||
checkEmptyText: true
|
||||
}
|
||||
@@ -172,9 +193,8 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
visible: listView.enabled
|
||||
&& (XrayConfigModel.hasUnsavedChanges
|
||||
|| textFieldWithHeaderType.textField.text !== port)
|
||||
enabled: visible && textFieldWithHeaderType.errorText === ""
|
||||
&& (XrayConfigModel.hasUnsavedChanges || root.portDirty)
|
||||
enabled: visible && textFieldWithHeaderType.textField.text !== ""
|
||||
text: qsTr("Save")
|
||||
onClicked: function() {
|
||||
forceActiveFocus()
|
||||
@@ -193,7 +213,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) saveButton.forceActiveFocus()
|
||||
|
||||
@@ -742,7 +742,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -95,7 +95,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -211,7 +211,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -208,7 +208,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -179,7 +179,7 @@ PageType {
|
||||
function mtProxyScheduleUpdate(closePage) {
|
||||
var cp = closePage === undefined ? false : closePage
|
||||
Qt.callLater(function () {
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
|
||||
tempPort = portTextField.textField.text
|
||||
tempUsername = usernameTextField.textField.text
|
||||
tempPassword = passwordTextField.textField.text
|
||||
|
||||
@@ -154,7 +154,7 @@ PageType {
|
||||
function telemtScheduleUpdate(closePage) {
|
||||
var cp = closePage === undefined ? false : closePage
|
||||
Qt.callLater(function () {
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,12 @@ PageType {
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
textFormat: Text.RichText
|
||||
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this URL.")
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
|
||||
@@ -30,6 +30,16 @@ PageType {
|
||||
root.isInAppPurchase = ApiAccountInfoModel.data("isInAppPurchase")
|
||||
}
|
||||
|
||||
function selectConnectionCountry(countryIndex, countryCode, countryName) {
|
||||
if (countryIndex === ApiCountryModel.currentIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
PageController.showBusyIndicator(true)
|
||||
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.updateSubscriptionState()
|
||||
}
|
||||
@@ -83,7 +93,7 @@ PageType {
|
||||
|
||||
model: ApiCountryModel
|
||||
|
||||
currentIndex: 0
|
||||
currentIndex: ApiCountryModel.currentIndex
|
||||
|
||||
ButtonGroup {
|
||||
id: containersRadioButtonGroup
|
||||
@@ -204,15 +214,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
if (index !== ApiCountryModel.currentIndex) {
|
||||
PageController.showBusyIndicator(true)
|
||||
var prevIndex = ApiCountryModel.currentIndex
|
||||
ApiCountryModel.currentIndex = index
|
||||
if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)) {
|
||||
ApiCountryModel.currentIndex = prevIndex
|
||||
}
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
root.selectConnectionCountry(index, countryCode, countryName)
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: {
|
||||
|
||||
@@ -108,9 +108,9 @@ PageType {
|
||||
text: qsTr("Auto start")
|
||||
descriptionText: qsTr("Launch the application every time the device is starts")
|
||||
|
||||
checked: SettingsController.isAutoStartEnabled()
|
||||
checked: SettingsController.autoStartEnabled
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.isAutoStartEnabled()) {
|
||||
if (checked !== SettingsController.autoStartEnabled) {
|
||||
SettingsController.toggleAutoStart(checked)
|
||||
}
|
||||
}
|
||||
@@ -154,10 +154,10 @@ PageType {
|
||||
text: qsTr("Start minimized")
|
||||
descriptionText: qsTr("Launch application minimized (works with autostart option turned on)")
|
||||
|
||||
enabled: SettingsController.isAutoStartEnabled()
|
||||
enabled: SettingsController.autoStartEnabled
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
|
||||
checked: SettingsController.isAutoStartEnabled() && SettingsController.startMinimized
|
||||
checked: SettingsController.autoStartEnabled && SettingsController.startMinimized
|
||||
onToggled: function() {
|
||||
if (checked !== SettingsController.startMinimized) {
|
||||
SettingsController.toggleStartMinimized(checked)
|
||||
@@ -166,7 +166,7 @@ PageType {
|
||||
}
|
||||
|
||||
DividerType {
|
||||
visible: !GC.isMobile()
|
||||
visible: !GC.isMobile() && ServersUiController.hasServersFromGatewayApi
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
|
||||
@@ -36,17 +36,6 @@ PageType {
|
||||
function onRebootServerFinished(finishedMessage) {
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onRemoveAllContainersFinished(finishedMessage) {
|
||||
PageController.closePage() // close deInstalling page
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
function onRemoveContainerFinished(finishedMessage) {
|
||||
PageController.closePage() // close deInstalling page
|
||||
PageController.closePage() // close page with remove button
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -17,7 +17,8 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool isClearCacheVisible: ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
|
||||
property bool isUnsupportedContainer: ContainerProps.isUnsupportedContainer(ServersUiController.processedContainerIndex)
|
||||
property bool isClearCacheVisible: !isUnsupportedContainer && ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
@@ -52,10 +53,11 @@ PageType {
|
||||
Layout.bottomMargin: 32
|
||||
|
||||
headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings")
|
||||
descriptionText: root.isUnsupportedContainer ? qsTr("This protocol is no longer supported.") : ""
|
||||
}
|
||||
}
|
||||
|
||||
model: ProtocolsModel
|
||||
model: root.isUnsupportedContainer ? null : ProtocolsModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: delegateContent
|
||||
|
||||
@@ -29,6 +29,10 @@ PageType {
|
||||
ValueFilter {
|
||||
roleName: "isInstallationAllowed"
|
||||
value: true
|
||||
},
|
||||
ValueFilter {
|
||||
roleName: "isUnsupportedContainer"
|
||||
value: false
|
||||
}
|
||||
]
|
||||
sorters: RoleSorter {
|
||||
|
||||
@@ -382,6 +382,10 @@ PageType {
|
||||
ValueFilter {
|
||||
roleName: "isShareable"
|
||||
value: true
|
||||
},
|
||||
ValueFilter {
|
||||
roleName: "isUnsupportedContainer"
|
||||
value: false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -396,9 +400,19 @@ PageType {
|
||||
target: serverSelector
|
||||
|
||||
function onServerSelectorIndexChanged() {
|
||||
var defaultContainer = proxyContainersModel.mapFromSource(ServersUiController.serverDefaultContainer(ServersUiController.processedServerId))
|
||||
if (!proxyContainersModel.count) {
|
||||
root.shareButtonEnabled = false
|
||||
return
|
||||
}
|
||||
|
||||
var defaultContainer = proxyContainersModel.mapFromSource(
|
||||
ServersUiController.serverDefaultContainer(ServersUiController.processedServerId))
|
||||
if (defaultContainer < 0) {
|
||||
defaultContainer = 0
|
||||
}
|
||||
|
||||
containerSelectorListView.selectedIndex = defaultContainer
|
||||
containerSelectorListView.positionViewAtIndex(selectedIndex, ListView.Beginning)
|
||||
containerSelectorListView.positionViewAtIndex(defaultContainer, ListView.Beginning)
|
||||
containerSelectorListView.triggerCurrentItem()
|
||||
}
|
||||
}
|
||||
@@ -837,11 +851,10 @@ PageType {
|
||||
var noButtonFunction = function() {
|
||||
}
|
||||
|
||||
var isActiveConfigForCurrentClient = ServersUiController.isDefaultServerCurrentlyProcessed()
|
||||
&& ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex
|
||||
|
||||
if ((ConnectionController.isConnectionInProgress || ConnectionController.isConnected)
|
||||
&& isActiveConfigForCurrentClient) {
|
||||
if (ConnectionController.isRevokeBlockedDuringActiveConnection(
|
||||
ServersUiController.processedServerId,
|
||||
ServersUiController.processedContainerIndex,
|
||||
clientId)) {
|
||||
PageController.showNotificationMessage("Unable to revoke current config during active connection")
|
||||
} else {
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
|
||||