Compare commits

..

20 Commits

Author SHA1 Message Date
dranik 6f34f3509b add new icons error & fix logic 2026-06-05 16:13:06 +03:00
dranik b21d77a911 fixed update icon windows 2026-06-05 13:35:11 +03:00
dranik a4edb7279d fixed update icon linux 2026-06-05 12:12:12 +03:00
dranik e61b1dfa11 update icons 2026-06-05 09:00:14 +03:00
dranik 0db1e52468 fixed update icon windows & ref code 2026-05-30 14:50:18 +03:00
dranik 596a422475 reset file macos 2026-05-30 14:10:20 +03:00
dranik 345fbf99de fix update linux 2026-05-30 12:55:27 +03:00
dranik 58d6c362f3 ref code sort files 2026-05-30 10:53:44 +03:00
dranik 9937add4eb add linux file 2026-05-29 23:17:50 +03:00
dranik 7eecc3667f format code 2026-05-29 22:57:54 +03:00
dranik 446e7b6a8e fix build macos 2026-05-29 22:52:34 +03:00
dranik 10d5bdbc60 reset file 2026-05-29 22:48:16 +03:00
dranik bb38388140 reset file 2026-05-29 22:45:03 +03:00
dranik 47834a10a6 format code 2026-05-29 22:43:02 +03:00
dranik 61afebcedb format code 2026-05-29 22:40:24 +03:00
dranik 9fa03e5387 fixed update icon linux ubuntu 24 2026-05-29 22:22:53 +03:00
dranik 9857a5d90a remove old include 2026-05-29 18:47:09 +03:00
dranik 2c37305cf0 fixed update icon & green indicator - windows 2026-05-29 18:30:23 +03:00
dranik 6fad9f56e4 fixed icon conn/disc macos 2026-05-29 17:01:08 +03:00
dranik a40bd0d580 add new icon 2026-05-29 15:58:07 +03:00
67 changed files with 1394 additions and 962 deletions
+65 -10
View File
@@ -260,16 +260,6 @@ if(WIN32)
)
endif()
if(APPLE AND NOT IOS AND NOT MACOS_NE)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolMacos.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolMacos.mm
)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
@@ -277,6 +267,10 @@ 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
@@ -288,20 +282,81 @@ 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()
@@ -425,7 +425,12 @@ void CoreSignalHandlers::initNotificationHandler()
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_coreController->m_notificationHandler);
connect(m_coreController, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
#endif
connect(m_coreController->m_connectionUiController, &ConnectionUiController::connectionErrorOccurred, trayHandler,
&SystemTrayNotificationHandler::setConnectionError);
connect(m_coreController->m_pageController, &PageController::errorMessageClosed, trayHandler,
&SystemTrayNotificationHandler::clearConnectionError);
#endif
}
void CoreSignalHandlers::initUpdateFoundHandler()
@@ -1,64 +0,0 @@
#ifndef IKEV2_VPN_PROTOCOL_MACOS_H
#define IKEV2_VPN_PROTOCOL_MACOS_H
#include <QJsonObject>
#include <QObject>
#include <QString>
#include <QTimer>
#include "vpnProtocol.h"
#if defined(__OBJC__)
#include <NetworkExtension/NetworkExtension.h>
#endif
class Ikev2ProtocolMacos : public VpnProtocol
{
Q_OBJECT
public:
explicit Ikev2ProtocolMacos(const QJsonObject &configuration, QObject *parent = nullptr);
~Ikev2ProtocolMacos() override;
ErrorCode start() override;
void stop() override;
static QString tunnelName() { return "AmneziaVPN IKEv2"; }
private:
void readIkev2Configuration(const QJsonObject &configuration);
bool storeClientIdentity();
void handleStatusChange(int rawStatus);
void startTunnelNow();
void reportError(ErrorCode code);
void startHandshakeTimeoutTimer();
void stopHandshakeTimeoutTimer();
void removeStatusObserver();
private:
QJsonObject m_config;
QString m_hostName;
QString m_clientId;
QString m_clientCertBase64;
QString m_clientCertPassword;
QTimer *m_handshakeTimeoutTimer { nullptr };
bool m_handshakeTimedOut { false };
bool m_startWhenDisconnected { false };
bool m_tunnelStarted { false };
int m_startRetries { 0 };
void *m_statusObserver { nullptr };
int m_lastVpnStatus { 0 };
static constexpr int HANDSHAKE_TIMEOUT_SEC = 20;
static constexpr int MAX_START_RETRIES = 5;
};
#endif // IKEV2_VPN_PROTOCOL_MACOS_H
@@ -1,666 +0,0 @@
#include "ikev2VpnProtocolMacos.h"
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QPointer>
#include <QTemporaryFile>
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/ipcClient.h"
#include "ipc.h"
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#import <Foundation/Foundation.h>
#import <NetworkExtension/NetworkExtension.h>
#import <Security/Security.h>
namespace {
const char *kRepackedP12Password = "amnezia";
const char *kVpnSystemKeychainPath = "/Library/Keychains/System.keychain";
const char *vpnStatusName(int status)
{
switch (status) {
case NEVPNStatusInvalid: return "Invalid";
case NEVPNStatusDisconnected: return "Disconnected";
case NEVPNStatusConnecting: return "Connecting";
case NEVPNStatusConnected: return "Connected";
case NEVPNStatusReasserting: return "Reasserting";
case NEVPNStatusDisconnecting: return "Disconnecting";
default: return "Unknown";
}
}
bool prepareIdentity(const QByteArray &source, const QString &friendlyName, QByteArray &repackedP12, QByteArray &caCertDer)
{
const unsigned char *cursor = reinterpret_cast<const unsigned char *>(source.constData());
PKCS12 *sourceP12 = d2i_PKCS12(nullptr, &cursor, source.size());
if (!sourceP12) {
qCritical() << "[IKEv2-mac] failed to parse client p12 container";
return false;
}
EVP_PKEY *privateKey = nullptr;
X509 *certificate = nullptr;
STACK_OF(X509) *caChain = nullptr;
int parsed = PKCS12_parse(sourceP12, "", &privateKey, &certificate, &caChain);
PKCS12_free(sourceP12);
if (!parsed || !privateKey || !certificate) {
qCritical() << "[IKEv2-mac] failed to extract key/certificate from client p12";
if (privateKey) EVP_PKEY_free(privateKey);
if (certificate) X509_free(certificate);
if (caChain) sk_X509_pop_free(caChain, X509_free);
return false;
}
const int caCount = caChain ? sk_X509_num(caChain) : 0;
qInfo() << "[IKEv2-mac] CA certificates bundled in client p12:" << caCount;
if (caCount > 0) {
X509 *caCert = sk_X509_value(caChain, caCount - 1);
unsigned char *caEncoded = nullptr;
int caLength = i2d_X509(caCert, &caEncoded);
if (caLength > 0 && caEncoded) {
caCertDer = QByteArray(reinterpret_cast<const char *>(caEncoded), caLength);
OPENSSL_free(caEncoded);
}
}
PKCS12 *repacked = PKCS12_create(kRepackedP12Password,
friendlyName.toUtf8().constData(),
privateKey,
certificate,
caChain,
NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
PKCS12_DEFAULT_ITER,
PKCS12_DEFAULT_ITER,
0);
EVP_PKEY_free(privateKey);
X509_free(certificate);
if (caChain) sk_X509_pop_free(caChain, X509_free);
if (!repacked) {
qCritical() << "[IKEv2-mac] failed to repackage client p12";
return false;
}
unsigned char *encoded = nullptr;
int encodedLength = i2d_PKCS12(repacked, &encoded);
PKCS12_free(repacked);
if (encodedLength <= 0 || !encoded) {
qCritical() << "[IKEv2-mac] failed to serialize repackaged client p12";
return false;
}
repackedP12 = QByteArray(reinterpret_cast<const char *>(encoded), encodedLength);
OPENSSL_free(encoded);
return true;
}
void removeIdentityFromLoginKeychain(const QString &label)
{
NSDictionary *query = @{
(__bridge id)kSecClass : (__bridge id)kSecClassIdentity,
(__bridge id)kSecAttrLabel : label.toNSString(),
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll,
};
SecItemDelete((__bridge CFDictionaryRef)query);
}
bool importIdentityToLoginKeychain(const QByteArray &p12, const QString &label)
{
SecKeychainRef loginKeychain = NULL;
if (SecKeychainCopyDefault(&loginKeychain) != errSecSuccess || loginKeychain == NULL) {
qCritical() << "[IKEv2-mac] cannot open the login keychain";
return false;
}
CFMutableArrayRef trustedApps = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
SecTrustedApplicationRef selfApp = NULL;
if (SecTrustedApplicationCreateFromPath(NULL, &selfApp) == errSecSuccess) {
CFArrayAppendValue(trustedApps, selfApp);
CFRelease(selfApp);
}
SecTrustedApplicationRef neAgent = NULL;
if (SecTrustedApplicationCreateFromPath("/usr/libexec/neagent", &neAgent) == errSecSuccess) {
CFArrayAppendValue(trustedApps, neAgent);
CFRelease(neAgent);
}
SecAccessRef access = NULL;
OSStatus accessStatus = SecAccessCreate((__bridge CFStringRef)label.toNSString(), trustedApps, &access);
CFRelease(trustedApps);
if (accessStatus != errSecSuccess || access == NULL) {
qCritical() << "[IKEv2-mac] SecAccessCreate failed, status" << (int)accessStatus;
CFRelease(loginKeychain);
return false;
}
NSData *p12Data = [NSData dataWithBytes:p12.constData() length:p12.size()];
NSDictionary *options = @{
(__bridge id)kSecImportExportPassphrase : [NSString stringWithUTF8String:kRepackedP12Password],
(__bridge id)kSecImportExportKeychain : (__bridge id)loginKeychain,
(__bridge id)kSecImportExportAccess : (__bridge id)access,
};
CFArrayRef items = NULL;
OSStatus importStatus = SecPKCS12Import((__bridge CFDataRef)p12Data, (__bridge CFDictionaryRef)options, &items);
if (items) {
CFRelease(items);
}
CFRelease(access);
CFRelease(loginKeychain);
if (importStatus != errSecSuccess) {
qCritical() << "[IKEv2-mac] SecPKCS12Import into login keychain failed, status" << (int)importStatus;
return false;
}
return true;
}
NSData *copyIdentityPersistentRef(const QString &label)
{
SecKeychainRef loginKeychain = NULL;
SecKeychainCopyDefault(&loginKeychain);
NSMutableDictionary *query = [@{
(__bridge id)kSecClass : (__bridge id)kSecClassIdentity,
(__bridge id)kSecAttrLabel : label.toNSString(),
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne,
(__bridge id)kSecReturnPersistentRef : @YES,
} mutableCopy];
if (loginKeychain) {
query[(__bridge id)kSecMatchSearchList] = @[ (__bridge id)loginKeychain ];
}
CFTypeRef persistentRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &persistentRef);
if (loginKeychain) {
CFRelease(loginKeychain);
}
if (status != errSecSuccess || persistentRef == NULL) {
qDebug() << "[IKEv2-mac] client identity not present in login keychain, status" << (int)status;
return nil;
}
return (NSData *)CFAutorelease(persistentRef);
}
void runPrivilegedSecurity(const QString &label, const QStringList &arguments)
{
auto process = IpcClient::CreatePrivilegedProcess();
if (!process) {
qCritical() << "[IKEv2-mac] privileged service is unavailable for" << label;
return;
}
process->setProgram(PermittedProcess::Security);
process->setArguments(arguments);
process->start();
bool started = false;
{
auto reply = process->waitForStarted(5000);
reply.waitForFinished();
started = reply.returnValue();
}
bool finished = false;
{
auto reply = process->waitForFinished(15000);
reply.waitForFinished();
finished = reply.returnValue();
}
QByteArray out;
QByteArray err;
{
auto reply = process->readAllStandardOutput();
reply.waitForFinished();
out = reply.returnValue();
}
{
auto reply = process->readAllStandardError();
reply.waitForFinished();
err = reply.returnValue();
}
qInfo() << "[IKEv2-mac]" << label << "started" << started << "finished" << finished
<< "| out:" << out.trimmed() << "| err:" << err.trimmed();
}
void disableVpnConfiguration()
{
NEVPNManager *manager = [NEVPNManager sharedManager];
if (!manager.enabled) {
return;
}
manager.enabled = NO;
[manager saveToPreferencesWithCompletionHandler:^(NSError *error) {
if (error) {
qWarning() << "[IKEv2-mac] failed to disable VPN configuration:"
<< QString::fromNSString(error.localizedDescription);
} else {
qInfo() << "[IKEv2-mac] VPN configuration disabled";
}
}];
}
} // namespace
Ikev2ProtocolMacos::Ikev2ProtocolMacos(const QJsonObject &configuration, QObject *parent)
: VpnProtocol(configuration, parent)
{
readIkev2Configuration(configuration);
}
Ikev2ProtocolMacos::~Ikev2ProtocolMacos()
{
qInfo() << "[IKEv2-mac] ~Ikev2ProtocolMacos()";
if (m_handshakeTimeoutTimer) {
m_handshakeTimeoutTimer->stop();
m_handshakeTimeoutTimer = nullptr;
}
removeStatusObserver();
}
void Ikev2ProtocolMacos::readIkev2Configuration(const QJsonObject &configuration)
{
m_config = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Ikev2)).toObject();
m_hostName = m_config.value(configKey::hostName).toString();
m_clientId = m_config.value(configKey::userName).toString();
m_clientCertBase64 = m_config.value(configKey::cert).toString();
m_clientCertPassword = m_config.value(configKey::password).toString();
}
bool Ikev2ProtocolMacos::storeClientIdentity()
{
if (copyIdentityPersistentRef(m_clientId) != nil) {
qInfo() << "[IKEv2-mac] client identity already in login keychain, reusing it";
return true;
}
qInfo() << "[IKEv2-mac] installing client identity into login keychain";
QByteArray sourceP12 = QByteArray::fromBase64(m_clientCertBase64.toUtf8());
QByteArray repackedP12;
QByteArray caCertDer;
if (!prepareIdentity(sourceP12, m_clientId, repackedP12, caCertDer)) {
return false;
}
removeIdentityFromLoginKeychain(m_clientId);
if (!importIdentityToLoginKeychain(repackedP12, m_clientId)) {
return false;
}
if (!caCertDer.isEmpty()) {
QTemporaryFile caFile(QDir::tempPath() + "/amnezia-ikev2-ca-XXXXXX.cer");
caFile.setAutoRemove(false);
if (caFile.open()) {
const QString caPath = caFile.fileName();
caFile.write(caCertDer);
caFile.close();
runPrivilegedSecurity("CA trust", { "add-trusted-cert", "-d", "-r", "trustRoot",
"-k", QString::fromLatin1(kVpnSystemKeychainPath), caPath });
QFile::remove(caPath);
}
} else {
qWarning() << "[IKEv2-mac] no CA certificate in client p12; server validation may fail";
}
return true;
}
void Ikev2ProtocolMacos::reportError(ErrorCode code)
{
QMetaObject::invokeMethod(this, [this, code]() { setLastError(code); }, Qt::QueuedConnection);
}
void Ikev2ProtocolMacos::removeStatusObserver()
{
if (m_statusObserver) {
NEVPNManager *manager = [NEVPNManager sharedManager];
[[NSNotificationCenter defaultCenter] removeObserver:(id)m_statusObserver
name:NEVPNStatusDidChangeNotification
object:manager.connection];
m_statusObserver = nullptr;
}
}
ErrorCode Ikev2ProtocolMacos::start()
{
qInfo() << "[IKEv2-mac] start() requested, host =" << m_hostName << ", clientId =" << m_clientId;
if (m_hostName.isEmpty() || m_clientCertBase64.isEmpty()) {
qCritical() << "[IKEv2-mac] missing server address or client certificate";
setLastError(ErrorCode::IKEv2ConfigError);
return ErrorCode::IKEv2ConfigError;
}
if (!storeClientIdentity()) {
setLastError(ErrorCode::IKEv2ConfigError);
return ErrorCode::IKEv2ConfigError;
}
m_handshakeTimedOut = false;
m_lastVpnStatus = NEVPNStatusInvalid;
m_startRetries = 0;
m_tunnelStarted = false;
setConnectionState(Vpn::ConnectionState::Connecting);
NSString *nsServerAddress = m_hostName.toNSString();
NSString *nsLocalIdentifier = m_clientId.toNSString();
QString clientId = m_clientId;
QPointer<Ikev2ProtocolMacos> self = this;
dispatch_async(dispatch_get_main_queue(), ^{
if (!self) return;
NEVPNManager *manager = [NEVPNManager sharedManager];
[manager loadFromPreferencesWithCompletionHandler:^(NSError *loadError) {
if (!self) return;
if (loadError) {
qCritical() << "[IKEv2-mac] loading VPN preferences failed:"
<< QString::fromNSString(loadError.localizedDescription);
self->reportError(ErrorCode::IKEv2LoadError);
return;
}
NSData *identityReference = copyIdentityPersistentRef(clientId);
if (identityReference == nil) {
self->reportError(ErrorCode::IKEv2ConfigError);
return;
}
NEVPNProtocolIKEv2 *protocol = [[NEVPNProtocolIKEv2 alloc] init];
protocol.serverAddress = nsServerAddress;
protocol.remoteIdentifier = nsServerAddress;
protocol.localIdentifier = nsLocalIdentifier;
protocol.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate;
protocol.certificateType = NEVPNIKEv2CertificateTypeRSA;
protocol.identityReference = identityReference;
protocol.useExtendedAuthentication = NO;
protocol.enablePFS = NO;
protocol.disconnectOnSleep = NO;
protocol.deadPeerDetectionRate = NEVPNIKEv2DeadPeerDetectionRateMedium;
protocol.IKESecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithmAES256;
protocol.IKESecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithmSHA256;
protocol.IKESecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup14;
protocol.IKESecurityAssociationParameters.lifetimeMinutes = 1410;
protocol.childSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithmAES128GCM;
protocol.childSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup14;
protocol.childSecurityAssociationParameters.lifetimeMinutes = 1410;
[manager setProtocolConfiguration:protocol];
[manager setLocalizedDescription:Ikev2ProtocolMacos::tunnelName().toNSString()];
[manager setEnabled:YES];
[manager setOnDemandEnabled:NO];
[manager saveToPreferencesWithCompletionHandler:^(NSError *firstSaveError) {
if (!self) return;
if (firstSaveError) {
qCritical() << "[IKEv2-mac] saving VPN preferences failed:"
<< QString::fromNSString(firstSaveError.localizedDescription);
self->reportError(ErrorCode::IKEv2SaveError);
return;
}
[manager loadFromPreferencesWithCompletionHandler:^(NSError *reloadError) {
if (!self) return;
if (reloadError) {
qCritical() << "[IKEv2-mac] reloading VPN preferences failed:"
<< QString::fromNSString(reloadError.localizedDescription);
self->reportError(ErrorCode::IKEv2LoadError);
return;
}
[manager saveToPreferencesWithCompletionHandler:^(NSError *resaveError) {
if (!self) return;
if (resaveError) {
qCritical() << "[IKEv2-mac] re-saving VPN preferences failed:"
<< QString::fromNSString(resaveError.localizedDescription);
self->reportError(ErrorCode::IKEv2SaveError);
return;
}
self->removeStatusObserver();
self->m_statusObserver = (void *)[[NSNotificationCenter defaultCenter]
addObserverForName:NEVPNStatusDidChangeNotification
object:manager.connection
queue:nil
usingBlock:^(NSNotification *notification) {
if (!self) return;
NEVPNConnection *connection = notification.object;
int rawStatus = (int)connection.status;
QMetaObject::invokeMethod(
self, [self, rawStatus]() { if (self) self->handleStatusChange(rawStatus); },
Qt::QueuedConnection);
}];
NEVPNStatus current = manager.connection.status;
if (current == NEVPNStatusDisconnected || current == NEVPNStatusInvalid) {
qInfo() << "[IKEv2-mac] preferences saved, connection is"
<< vpnStatusName(current) << "- starting tunnel now";
self->m_startWhenDisconnected = false;
self->startTunnelNow();
} else {
qInfo() << "[IKEv2-mac] preferences saved, connection is"
<< vpnStatusName(current) << "- waiting for it to disconnect first";
self->m_startWhenDisconnected = true;
[manager.connection stopVPNTunnel];
}
}];
}];
}];
}];
});
return ErrorCode::NoError;
}
void Ikev2ProtocolMacos::stop()
{
qInfo() << "[IKEv2-mac] stop() requested";
stopHandshakeTimeoutTimer();
m_startWhenDisconnected = false;
NEVPNManager *manager = [NEVPNManager sharedManager];
NEVPNStatus status = manager.connection.status;
qInfo() << "[IKEv2-mac] stop(): current NEVPNStatus =" << (int)status;
removeStatusObserver();
if (status != NEVPNStatusDisconnected && status != NEVPNStatusInvalid) {
[manager.connection stopVPNTunnel];
qInfo() << "[IKEv2-mac] stop(): stopVPNTunnel issued";
}
disableVpnConfiguration();
setConnectionState(Vpn::ConnectionState::Disconnected);
}
void Ikev2ProtocolMacos::startTunnelNow()
{
qInfo() << "[IKEv2-mac] startVPNTunnel (attempt" << (m_startRetries + 1) << ")";
NEVPNManager *manager = [NEVPNManager sharedManager];
NSError *startError = nil;
[manager.connection startVPNTunnelAndReturnError:&startError];
if (!startError) {
m_startRetries = 0;
m_tunnelStarted = true;
QMetaObject::invokeMethod(this, [this]() { startHandshakeTimeoutTimer(); }, Qt::QueuedConnection);
return;
}
if (m_startRetries < MAX_START_RETRIES) {
m_startRetries++;
qWarning() << "[IKEv2-mac] startVPNTunnel failed, will retry (" << m_startRetries << "):"
<< QString::fromNSString(startError.localizedDescription);
QPointer<Ikev2ProtocolMacos> self = this;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!self) return;
[[NEVPNManager sharedManager] loadFromPreferencesWithCompletionHandler:^(NSError *reloadError) {
if (!self) return;
self->startTunnelNow();
}];
});
return;
}
qCritical() << "[IKEv2-mac] starting the tunnel failed:"
<< QString::fromNSString(startError.localizedDescription);
disableVpnConfiguration();
reportError(ErrorCode::IKEv2ConnectError);
}
void Ikev2ProtocolMacos::handleStatusChange(int rawStatus)
{
NEVPNStatus vpnStatus = static_cast<NEVPNStatus>(rawStatus);
const Vpn::ConnectionState currentState = connectionState();
qInfo() << "[IKEv2-mac] status ->" << vpnStatusName(rawStatus)
<< "| uiState:" << textConnectionState()
<< "| lastStatus:" << vpnStatusName(m_lastVpnStatus)
<< "| waitingToStart:" << m_startWhenDisconnected;
if (m_startWhenDisconnected) {
if (vpnStatus == NEVPNStatusDisconnecting) {
return;
}
if (vpnStatus == NEVPNStatusDisconnected) {
m_startWhenDisconnected = false;
m_lastVpnStatus = NEVPNStatusDisconnected;
QPointer<Ikev2ProtocolMacos> self = this;
dispatch_async(dispatch_get_main_queue(), ^{
if (self) self->startTunnelNow();
});
return;
}
}
switch (vpnStatus) {
case NEVPNStatusConnecting:
setConnectionState(Vpn::ConnectionState::Connecting);
break;
case NEVPNStatusConnected:
stopHandshakeTimeoutTimer();
m_lastVpnStatus = vpnStatus;
m_tunnelStarted = true;
qInfo() << "[IKEv2-mac] tunnel established";
setConnectionState(Vpn::ConnectionState::Connected);
break;
case NEVPNStatusReasserting:
m_lastVpnStatus = vpnStatus;
setConnectionState(Vpn::ConnectionState::Reconnecting);
break;
case NEVPNStatusDisconnecting:
if (!m_tunnelStarted) {
return;
}
stopHandshakeTimeoutTimer();
setConnectionState(Vpn::ConnectionState::Disconnecting);
break;
case NEVPNStatusDisconnected: {
if (!m_tunnelStarted) {
m_lastVpnStatus = vpnStatus;
return;
}
stopHandshakeTimeoutTimer();
removeStatusObserver();
if (m_handshakeTimedOut) {
qCritical() << "[IKEv2-mac] connection failed: handshake timed out";
setLastError(ErrorCode::IKEv2TimeoutError);
} else if (m_lastVpnStatus == NEVPNStatusInvalid && currentState == Vpn::ConnectionState::Connecting) {
qCritical() << "[IKEv2-mac] connection failed: server rejected the configuration";
setLastError(ErrorCode::IKEv2ConfigError);
} else if (m_lastVpnStatus == NEVPNStatusReasserting
&& (currentState == Vpn::ConnectionState::Connecting
|| currentState == Vpn::ConnectionState::Connected)) {
qWarning() << "[IKEv2-mac] connection lost (network unavailable)";
setConnectionState(Vpn::ConnectionState::Disconnected);
} else if (m_lastVpnStatus == NEVPNStatusConnected
&& (currentState == Vpn::ConnectionState::Connecting
|| currentState == Vpn::ConnectionState::Connected)) {
qWarning() << "[IKEv2-mac] tunnel turned off outside the app (system settings)";
setConnectionState(Vpn::ConnectionState::Disconnected);
} else {
setConnectionState(Vpn::ConnectionState::Disconnected);
}
m_lastVpnStatus = vpnStatus;
disableVpnConfiguration();
break;
}
case NEVPNStatusInvalid:
if (!m_tunnelStarted) {
m_lastVpnStatus = vpnStatus;
return;
}
stopHandshakeTimeoutTimer();
removeStatusObserver();
qCritical() << "[IKEv2-mac] VPN profile became invalid";
m_lastVpnStatus = vpnStatus;
setLastError(ErrorCode::IKEv2ConfigError);
disableVpnConfiguration();
break;
default:
break;
}
}
void Ikev2ProtocolMacos::startHandshakeTimeoutTimer()
{
stopHandshakeTimeoutTimer();
m_handshakeTimeoutTimer = new QTimer(this);
m_handshakeTimeoutTimer->setSingleShot(true);
m_handshakeTimeoutTimer->setInterval(HANDSHAKE_TIMEOUT_SEC * 1000);
connect(m_handshakeTimeoutTimer, &QTimer::timeout, this, [this]() {
if (connectionState() == Vpn::ConnectionState::Connecting) {
m_handshakeTimedOut = true;
dispatch_async(dispatch_get_main_queue(), ^{
[[NEVPNManager sharedManager].connection stopVPNTunnel];
});
}
});
m_handshakeTimeoutTimer->start();
}
void Ikev2ProtocolMacos::stopHandshakeTimeoutTimer()
{
if (m_handshakeTimeoutTimer) {
m_handshakeTimeoutTimer->stop();
m_handshakeTimeoutTimer->deleteLater();
m_handshakeTimeoutTimer = nullptr;
}
}
-6
View File
@@ -14,10 +14,6 @@
#include "ikev2VpnProtocolWindows.h"
#endif
#if defined(Q_OS_MACX) && !defined(MACOS_NE)
#include "ikev2VpnProtocolMacos.h"
#endif
VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent)
: QObject(parent),
m_connectionState(Vpn::ConnectionState::Unknown),
@@ -115,8 +111,6 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
switch (container) {
#if defined(Q_OS_WINDOWS)
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
case DockerContainer::Ipsec: return new Ikev2ProtocolMacos(configuration);
#endif
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
@@ -265,7 +265,7 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
#elif defined(Q_OS_MAC)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Ipsec: return true;
case DockerContainer::Ipsec: return false;
default: return true;
}
-5
View File
@@ -66,11 +66,6 @@ namespace amnezia
OpenVpnUnknownError = 701,
OpenVpnTapAdapterError = 702,
AddressPoolError = 703,
IKEv2ConfigError = 710,
IKEv2LoadError = 711,
IKEv2SaveError = 712,
IKEv2ConnectError = 713,
IKEv2TimeoutError = 714,
// 3rd party utils errors
OpenSslFailed = 800,
-5
View File
@@ -64,11 +64,6 @@ QString errorString(ErrorCode code) {
case (ErrorCode::OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break;
case (ErrorCode::OpenVpnTapAdapterError): errorMessage = QObject::tr("Can't setup OpenVPN TAP network adapter"); break;
case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
case (ErrorCode::IKEv2ConfigError): errorMessage = QObject::tr("Can't connect: invalid IKEv2 configuration"); break;
case (ErrorCode::IKEv2LoadError): errorMessage = QObject::tr("Can't connect: failed to load IKEv2 VPN preferences"); break;
case (ErrorCode::IKEv2SaveError): errorMessage = QObject::tr("Can't connect: failed to save IKEv2 VPN preferences"); break;
case (ErrorCode::IKEv2ConnectError): errorMessage = QObject::tr("Can't connect: failed to start IKEv2 connection"); break;
case (ErrorCode::IKEv2TimeoutError): errorMessage = QObject::tr("Can't connect: IKEv2 connection timeout"); break;
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
-5
View File
@@ -290,11 +290,6 @@ QString Utils::tun2socksPath()
return Utils::executable("tun2socks", true);
}
QString Utils::securityPath()
{
return "/usr/bin/security";
}
#ifdef Q_OS_WIN
// Inspired from http://stackoverflow.com/a/15281070/1529139
// and http://stackoverflow.com/q/40059902/1529139
-1
View File
@@ -34,7 +34,6 @@ public:
static QString wireguardExecPath();
static QString certUtilPath();
static QString tun2socksPath();
static QString securityPath();
static void logException(const std::exception &e);
static void logException(const std::exception_ptr &eptr = std::current_exception());
+6 -3
View File
@@ -65,9 +65,12 @@
<file>controls/text-cursor.svg</file>
<file>controls/trash.svg</file>
<file>controls/x-circle.svg</file>
<file>tray/active.png</file>
<file>tray/default.png</file>
<file>tray/error.png</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>controls/monitor.svg</file>
</qresource>
</RCC>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

+8
View File
@@ -15,6 +15,10 @@
#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()
{
@@ -46,6 +50,10 @@ 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();
@@ -0,0 +1,81 @@
#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);
}
@@ -0,0 +1,33 @@
#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
+24
View File
@@ -0,0 +1,24 @@
#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);
}
+15
View File
@@ -0,0 +1,15 @@
#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
+128
View File
@@ -0,0 +1,128 @@
/* 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"
+19
View File
@@ -0,0 +1,19 @@
/* 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
+10 -2
View File
@@ -5,8 +5,12 @@
#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
@@ -18,10 +22,14 @@ class MacOSStatusIcon final : public QObject {
public:
void setIcon(const QString& iconUrl);
void setIndicatorColor(const QColor& indicatorColor);
void setMenu(NSMenu* statusBarMenu);
void setIconFromData(const QByteArray& imageData, bool asTemplate = true);
void setMenu(QMenu* menu);
void rebuildNativeMenu();
void setToolTip(const QString& tooltip);
void showMessage(const QString& title, const QString& message);
private:
QPointer<QMenu> m_qtMenu;
};
#endif // MACOSSTATUSICON_H
+95 -57
View File
@@ -10,22 +10,38 @@
#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 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 its effective
* appearance.
* 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.
*/
@interface MacOSStatusIconDelegate : NSObject
@property(assign) NSStatusItem* statusItem;
@property(assign) NSView* statusIndicator;
@property(retain) NSMenu* nativeMenu;
@property(retain) NSMutableArray* menuActionTargets;
- (void)setIcon:(NSData*)imageData;
- (void)setIndicator;
- (void)setIndicatorColor:(NSColor*)color;
- (void)setMenu:(NSMenu*)statusBarMenu;
- (void)setIcon:(NSData*)imageData asTemplate:(BOOL)asTemplate;
- (void)setToolTip:(NSString*)tooltip;
- (void)rebuildMenuFromQMenu:(QMenu*)menu;
@end
@implementation MacOSStatusIconDelegate
@@ -36,58 +52,38 @@
*/
- (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 iconPath The data for the icon image.
* @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).
*/
- (void)setIcon:(NSData*)imageData {
- (void)setIcon:(NSData*)imageData asTemplate:(BOOL)asTemplate {
NSImage* image = [[NSImage alloc] initWithData:imageData];
[image setTemplate:true];
[image setTemplate:asTemplate];
[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.
*
@@ -105,6 +101,44 @@
- (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 {
@@ -138,27 +172,31 @@ void MacOSStatusIcon::setIcon(const QString& iconPath) {
QResource imageResource = QResource(iconPath);
Q_ASSERT(imageResource.isValid());
[m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData()];
[m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData() asTemplate:true];
}
void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) {
logger.debug() << "Set indicator color";
void MacOSStatusIcon::setIconFromData(const QByteArray& imageData, bool asTemplate) {
logger.debug() << "Set icon from rendered data";
if (!indicatorColor.isValid()) {
[m_statusBarIcon setIndicatorColor:[NSColor clearColor]];
if (imageData.isEmpty()) {
return;
}
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];
NSData* data = [NSData dataWithBytes:imageData.constData() length:imageData.size()];
[m_statusBarIcon setIcon:data asTemplate:asTemplate];
}
void MacOSStatusIcon::setMenu(NSMenu* statusBarMenu) {
logger.debug() << "Set menu";
[m_statusBarIcon setMenu:statusBarMenu];
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::setToolTip(const QString& tooltip) {
@@ -174,7 +212,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:^(BOOL granted, NSError* _Nullable error) {
completionHandler:^(__unused 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);
+5
View File
@@ -8,6 +8,8 @@
#include <QObject>
#include <QString>
#include <functional>
class MacOSUtils final {
public:
static NSString* appId();
@@ -23,6 +25,9 @@ class MacOSUtils final {
static void showDockIcon();
static void patchNSStatusBarSetImageForBigSur();
static bool isDarkTheme();
static void installInterfaceThemeObserver(std::function<void()> callback);
};
#endif // MACOSUTILS_H
+24
View File
@@ -137,6 +137,30 @@ 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:));
@@ -0,0 +1,25 @@
#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
@@ -0,0 +1,55 @@
#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);
}
+10
View File
@@ -0,0 +1,10 @@
#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.
}
+15
View File
@@ -0,0 +1,15 @@
#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
+83 -1
View File
@@ -14,7 +14,41 @@
namespace {
Logger logger("WindowsUtils");
} // namespace
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
constexpr const int WINDOWS_11_BUILD =
22000; // Build Number of the first release win 11 iso
@@ -60,3 +94,51 @@ 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");
}
}
+5
View File
@@ -7,6 +7,8 @@
#include <QString>
#include <functional>
class WindowsUtils final {
public:
static QString getErrorMessage();
@@ -18,6 +20,9 @@ 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
+40
View File
@@ -0,0 +1,40 @@
#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
+25
View File
@@ -0,0 +1,25 @@
#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
@@ -0,0 +1,67 @@
#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);
}
@@ -0,0 +1,33 @@
#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
+32
View File
@@ -0,0 +1,32 @@
#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(); });
}
+15
View File
@@ -0,0 +1,15 @@
#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,6 +1,5 @@
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
@@ -248,3 +248,8 @@ void PageController::onShowErrorMessage(ErrorCode errorCode)
emit showErrorMessage(fullMessage);
}
void PageController::onErrorMessageClosed()
{
emit errorMessageClosed();
}
+2 -2
View File
@@ -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);
@@ -112,7 +112,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedIndex, 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.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {
@@ -17,10 +17,6 @@ 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"
@@ -43,8 +39,8 @@ PageType {
anchors.right: parent.right
anchors.topMargin: 20 + PageController.safeAreaTopMargin
onActiveFocusChanged: {
if (backButton.enabled && backButton.activeFocus) {
onFocusChanged: {
if (this.activeFocus) {
listView.positionViewAtBeginning()
}
}
@@ -64,6 +60,8 @@ PageType {
delegate: ColumnLayout {
width: listView.width
property alias focusItemId: textFieldWithHeaderType.textField
spacing: 0
Text {
@@ -109,32 +107,13 @@ PageType {
Layout.rightMargin: 16
enabled: listView.enabled
headerText: qsTr("Port")
Binding {
target: textFieldWithHeaderType.textField
property: "text"
value: port
when: !textFieldWithHeaderType.textField.activeFocus
restoreMode: Binding.RestoreNone
}
textField.text: port
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
}
root.portDirty = false
if (textField.text !== port) port = textField.text
}
checkEmptyText: true
}
@@ -193,8 +172,9 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: listView.enabled
&& (XrayConfigModel.hasUnsavedChanges || root.portDirty)
enabled: visible && textFieldWithHeaderType.textField.text !== ""
&& (XrayConfigModel.hasUnsavedChanges
|| textFieldWithHeaderType.textField.text !== port)
enabled: visible && textFieldWithHeaderType.errorText === ""
text: qsTr("Save")
onClicked: function() {
forceActiveFocus()
@@ -742,7 +742,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedIndex, 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.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedIndex, 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.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedIndex, 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.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {
+7
View File
@@ -203,6 +203,13 @@ Window {
PopupType {
id: popupErrorMessage
}
Connections {
target: popupErrorMessage
function onClosed() {
PageController.onErrorMessageClosed()
}
}
}
Item {
+28
View File
@@ -0,0 +1,28 @@
#include "platformTheme.h"
#include <QGuiApplication>
#include <QStyleHints>
#if defined(Q_OS_MAC)
# include "platforms/macos/macosutils.h"
#elif defined(Q_OS_WIN)
# include "platforms/windows/windowsutils.h"
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
# include "platforms/linux/linuxutils.h"
#endif
bool platformIsDarkTheme()
{
#if defined(Q_OS_MAC)
return MacOSUtils::isDarkTheme();
#elif defined(Q_OS_WIN)
return WindowsUtils::isDarkTheme();
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
return LinuxUtils::isDarkTheme();
#else
if (QStyleHints *styleHints = QGuiApplication::styleHints()) {
return styleHints->colorScheme() == Qt::ColorScheme::Dark;
}
return false;
#endif
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef PLATFORMTHEME_H
#define PLATFORMTHEME_H
bool platformIsDarkTheme();
#endif // PLATFORMTHEME_H
+26
View File
@@ -0,0 +1,26 @@
#include "platformTrayTheme.h"
#include <QtGlobal>
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
# include "platforms/macos/mactraytheme.h"
#elif defined(Q_OS_WIN) || (defined(Q_OS_MAC) && defined(MACOS_NE))
# include "platforms/windows/wintraytheme.h"
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
# include "platforms/linux/linuxtraytheme.h"
#endif
void installTrayThemeObserver(const std::function<void()> &onThemeChanged, QObject *parent)
{
if (!onThemeChanged || !parent) {
return;
}
#if defined(Q_OS_MAC) && !defined(MACOS_NE)
MacTrayTheme::installThemeObserver(onThemeChanged, parent);
#elif defined(Q_OS_WIN) || (defined(Q_OS_MAC) && defined(MACOS_NE))
WinTrayTheme::installThemeObserver(onThemeChanged, parent);
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
LinuxTrayTheme::installThemeObserver(onThemeChanged, parent);
#endif
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef PLATFORMTRAYTHEME_H
#define PLATFORMTRAYTHEME_H
#include <functional>
class QObject;
void installTrayThemeObserver(const std::function<void()> &onThemeChanged, QObject *parent);
#endif // PLATFORMTRAYTHEME_H
@@ -5,27 +5,26 @@
#include <QDebug>
#include "systemTrayNotificationHandler.h"
#ifdef Q_OS_MAC
# include "platforms/macos/macosutils.h"
#endif
#include "platformTheme.h"
#include "platformTrayTheme.h"
#include "trayIconBackend.h"
#include <QApplication>
#include <QDesktopServices>
#include <QIcon>
#include <QWindow>
#include "version.h"
SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) :
NotificationHandler(parent),
m_systemTrayIcon(parent)
NotificationHandler(parent)
{
m_systemTrayIcon.show();
connect(&m_systemTrayIcon, &QSystemTrayIcon::activated, this, &SystemTrayNotificationHandler::onTrayActivated);
m_trayIcon = createTrayIconBackend(this);
m_trayIcon->setMenu(&m_menu);
m_trayIcon->setToolTip(APPLICATION_NAME);
m_trayIcon->setActivatedHandler([this](QSystemTrayIcon::ActivationReason reason) {
onTrayActivated(reason);
});
m_trayActionShow = m_menu.addAction(QIcon(":/images/tray/application.png"), tr("Show") + " " + APPLICATION_NAME, this, [this](){
m_trayActionShow = m_menu.addAction(tr("Show") + " " + APPLICATION_NAME, this, [this](){
emit raiseRequested();
});
m_menu.addSeparator();
@@ -34,22 +33,22 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) :
m_menu.addSeparator();
m_trayActionVisitWebSite = m_menu.addAction(QIcon(":/images/tray/link.png"), tr("Visit Website"), [&](){
m_trayActionVisitWebSite = m_menu.addAction(tr("Visit Website"), [&](){
QDesktopServices::openUrl(QUrl(websiteUrl));
});
// Quit action: disconnect VPN first on macOS NE, else quit directly
m_trayActionQuit = m_menu.addAction(QIcon(":/images/tray/cancel.png"),
tr("Quit") + " " + APPLICATION_NAME,
m_trayActionQuit = m_menu.addAction(tr("Quit") + " " + APPLICATION_NAME,
this,
[&](){ qApp->quit(); });
m_systemTrayIcon.setContextMenu(&m_menu);
installTrayThemeObserver([this]() { refreshTheme(); }, this);
m_isDarkTheme = platformIsDarkTheme();
setTrayState(Vpn::ConnectionState::Disconnected);
m_trayIcon->show();
}
SystemTrayNotificationHandler::~SystemTrayNotificationHandler() {
}
SystemTrayNotificationHandler::~SystemTrayNotificationHandler() = default;
void SystemTrayNotificationHandler::setConnectionState(Vpn::ConnectionState state)
{
@@ -63,21 +62,65 @@ void SystemTrayNotificationHandler::onTranslationsUpdated()
m_trayActionConnect->setText(tr("Connect"));
m_trayActionDisconnect->setText(tr("Disconnect"));
m_trayActionVisitWebSite->setText(tr("Visit Website"));
m_trayActionQuit->setText(tr("Quit")+ " " + APPLICATION_NAME);
m_trayActionQuit->setText(tr("Quit") + " " + APPLICATION_NAME);
if (m_trayIcon) {
m_trayIcon->rebuildMenu();
}
}
void SystemTrayNotificationHandler::updateWebsiteUrl(const QString &newWebsiteUrl) {
void SystemTrayNotificationHandler::updateWebsiteUrl(const QString &newWebsiteUrl)
{
qDebug() << "Updated website URL:" << newWebsiteUrl;
websiteUrl = newWebsiteUrl;
}
void SystemTrayNotificationHandler::setTrayIcon(const QString &iconPath)
void SystemTrayNotificationHandler::refreshTheme()
{
QIcon trayIconMask(QPixmap(iconPath).scaled(128,128));
#ifndef Q_OS_MAC
trayIconMask.setIsMask(true);
const bool isDarkTheme = platformIsDarkTheme();
const bool themeChanged = (isDarkTheme != m_isDarkTheme);
m_isDarkTheme = isDarkTheme;
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
updateTrayIcon();
#else
if (themeChanged) {
updateTrayIcon();
}
#endif
m_systemTrayIcon.setIcon(trayIconMask);
}
TrayIconVisual SystemTrayNotificationHandler::currentTrayVisual() const
{
TrayIconVisual visual;
visual.connectionState = m_errorLatched ? Vpn::ConnectionState::Error : m_trayState;
visual.darkTheme = m_isDarkTheme;
return visual;
}
void SystemTrayNotificationHandler::setConnectionError()
{
m_errorLatched = true;
updateTrayIcon();
}
void SystemTrayNotificationHandler::clearConnectionError()
{
if (!m_errorLatched) {
return;
}
m_errorLatched = false;
updateTrayIcon();
}
void SystemTrayNotificationHandler::updateTrayIcon()
{
if (!m_trayIcon) {
return;
}
m_trayIcon->applyVisual(currentTrayVisual());
}
void SystemTrayNotificationHandler::onTrayActivated(QSystemTrayIcon::ActivationReason reason)
@@ -91,41 +134,48 @@ void SystemTrayNotificationHandler::onTrayActivated(QSystemTrayIcon::ActivationR
void SystemTrayNotificationHandler::setTrayState(Vpn::ConnectionState state)
{
QString resourcesPath = ":/images/tray/%1";
if (state == Vpn::ConnectionState::Error || state == Vpn::ConnectionState::Unknown) {
// Latch the error icon. Both Error and Unknown surface the error message
// in the UI. The connection is torn down to Disconnected right after, so
// treat the real state as Disconnected and let the latch keep the error
// icon visible until the error is acknowledged.
m_errorLatched = true;
state = Vpn::ConnectionState::Disconnected;
} else if (state != Vpn::ConnectionState::Disconnected) {
// A new (re)connecting/connected lifecycle clears a previous error.
// Plain Disconnected leaves the latch untouched so the auto-Disconnected
// that immediately follows an error does not drop the error icon.
m_errorLatched = false;
}
m_trayState = state;
switch (state) {
case Vpn::ConnectionState::Disconnected:
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
m_trayActionConnect->setEnabled(true);
m_trayActionDisconnect->setEnabled(false);
break;
case Vpn::ConnectionState::Preparing:
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
m_trayActionConnect->setEnabled(false);
m_trayActionDisconnect->setEnabled(true);
break;
case Vpn::ConnectionState::Connecting:
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
m_trayActionConnect->setEnabled(false);
m_trayActionDisconnect->setEnabled(true);
break;
case Vpn::ConnectionState::Connected:
setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName));
m_trayActionConnect->setEnabled(false);
m_trayActionDisconnect->setEnabled(true);
break;
case Vpn::ConnectionState::Disconnecting:
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
m_trayActionConnect->setEnabled(false);
m_trayActionDisconnect->setEnabled(true);
break;
case Vpn::ConnectionState::Reconnecting:
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
m_trayActionConnect->setEnabled(false);
m_trayActionDisconnect->setEnabled(true);
break;
case Vpn::ConnectionState::Error:
setTrayIcon(QString(resourcesPath).arg(ErrorTrayIconName));
m_trayActionConnect->setEnabled(true);
m_trayActionDisconnect->setEnabled(false);
break;
@@ -133,41 +183,26 @@ void SystemTrayNotificationHandler::setTrayState(Vpn::ConnectionState state)
default:
m_trayActionConnect->setEnabled(false);
m_trayActionDisconnect->setEnabled(true);
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
break;
}
//#ifdef Q_OS_MAC
// // Get theme from current user (note, this app can be launched as root application and in this case this theme can be different from theme of real current user )
// bool darkTaskBar = MacOSFunctions::instance().isMenuBarUseDarkTheme();
// darkTaskBar = forceUseBrightIcons ? true : darkTaskBar;
// resourcesPath = ":/images_mac/tray_icon/%1";
// useIconName = useIconName.replace(".png", darkTaskBar ? "@2x.png" : " dark@2x.png");
//#endif
}
updateTrayIcon();
if (m_trayIcon) {
m_trayIcon->rebuildMenu();
}
}
void SystemTrayNotificationHandler::notify(NotificationHandler::Message type,
const QString& title,
const QString& message,
int timerMsec) {
Q_UNUSED(type);
int timerMsec)
{
Q_UNUSED(type);
QIcon icon(ConnectedTrayIconName);
m_systemTrayIcon.showMessage(title, message, icon, timerMsec);
if (!m_trayIcon) {
return;
}
m_trayIcon->showMessage(title, message, currentTrayVisual(), timerMsec);
}
void SystemTrayNotificationHandler::showHideWindow() {
// QmlEngineHolder* engine = QmlEngineHolder::instance();
// if (engine->window()->isVisible()) {
// engine->hideWindow();
//#ifdef MVPN_MACOS
// MacOSUtils::hideDockIcon();
//#endif
// } else {
// engine->showWindow();
//#ifdef MVPN_MACOS
// MacOSUtils::showDockIcon();
//#endif
// }
}
+18 -14
View File
@@ -6,16 +6,18 @@
#define SYSTEMTRAYNOTIFICATIONHANDLER_H
#include "notificationHandler.h"
#include "trayIconBackend.h"
#include <QMenu>
#include <QSystemTrayIcon>
#include <memory>
class SystemTrayNotificationHandler : public NotificationHandler {
Q_OBJECT
public:
explicit SystemTrayNotificationHandler(QObject* parent);
~SystemTrayNotificationHandler();
~SystemTrayNotificationHandler() override;
void setConnectionState(Vpn::ConnectionState state) override;
@@ -23,35 +25,37 @@ public:
public slots:
void updateWebsiteUrl(const QString &newWebsiteUrl);
void setConnectionError();
void clearConnectionError();
protected:
virtual void notify(Message type, const QString& title,
const QString& message, int timerMsec) override;
void notify(Message type, const QString& title,
const QString& message, int timerMsec) override;
private:
void showHideWindow();
void setTrayState(Vpn::ConnectionState state);
void onTrayActivated(QSystemTrayIcon::ActivationReason reason);
void setTrayIcon(const QString &iconPath);
void refreshTheme();
void updateTrayIcon();
TrayIconVisual currentTrayVisual() const;
private:
QMenu m_menu;
QSystemTrayIcon m_systemTrayIcon;
std::unique_ptr<TrayIconBackend> m_trayIcon;
QAction* m_trayActionShow = nullptr;
QAction* m_trayActionConnect = nullptr;
QAction* m_trayActionDisconnect = nullptr;
QAction* m_trayActionVisitWebSite = nullptr;
QAction* m_trayActionQuit = nullptr;
QAction* m_statusLabel = nullptr;
QAction* m_statusLabel = nullptr;
QAction* m_separator = nullptr;
const QString ConnectedTrayIconName = "active.png";
const QString DisconnectedTrayIconName = "default.png";
const QString ErrorTrayIconName = "error.png";
QString websiteUrl = "https://amnezia.org";
Vpn::ConnectionState m_trayState = Vpn::ConnectionState::Unknown;
bool m_isDarkTheme = false;
bool m_errorLatched = false;
QString websiteUrl = "https://amnezia.org";
};
#endif // SYSTEMTRAYNOTIFICATIONHANDLER_H
+35
View File
@@ -0,0 +1,35 @@
#ifndef TRAYICONBACKEND_H
#define TRAYICONBACKEND_H
#include <functional>
#include <memory>
#include <QMenu>
#include <QString>
#include <QSystemTrayIcon>
#include "core/protocols/vpnProtocol.h"
class QObject;
struct TrayIconVisual {
Vpn::ConnectionState connectionState = Vpn::ConnectionState::Unknown;
bool darkTheme = false;
};
class TrayIconBackend {
public:
virtual ~TrayIconBackend() = default;
virtual void setMenu(QMenu *menu) = 0;
virtual void setToolTip(const QString &tooltip) = 0;
virtual void show() = 0;
virtual void applyVisual(const TrayIconVisual &visual) = 0;
virtual void showMessage(const QString &title, const QString &message, const TrayIconVisual &visual, int timerMsec) = 0;
virtual void rebuildMenu() = 0;
virtual void setActivatedHandler(std::function<void(QSystemTrayIcon::ActivationReason)> handler) = 0;
};
std::unique_ptr<TrayIconBackend> createTrayIconBackend(QObject *parent);
#endif // TRAYICONBACKEND_H
+80
View File
@@ -0,0 +1,80 @@
#include "trayIconCommon.h"
#include <QBuffer>
#include <QDebug>
#include <QPainter>
#include <QSvgRenderer>
namespace TrayIconCommon
{
QString resourcePathForState(Vpn::ConnectionState state, bool darkTheme)
{
switch (state) {
case Vpn::ConnectionState::Error:
return QString::fromLatin1(darkTheme ? kIconErrorWhite : kIconErrorBlack);
case Vpn::ConnectionState::Connected:
return QString::fromLatin1(darkTheme ? kIconOnWhite : kIconOnBlack);
case Vpn::ConnectionState::Disconnected:
case Vpn::ConnectionState::Preparing:
case Vpn::ConnectionState::Connecting:
case Vpn::ConnectionState::Disconnecting:
case Vpn::ConnectionState::Reconnecting:
case Vpn::ConnectionState::Unknown:
default:
return QString::fromLatin1(darkTheme ? kIconOffLight : kIconOffBlack);
}
}
bool isColoredState(Vpn::ConnectionState state)
{
return state == Vpn::ConnectionState::Error;
}
QPixmap renderIcon(const QString &resourcePath, int size)
{
QSvgRenderer renderer(resourcePath);
QPixmap pixmap(size, size);
pixmap.fill(Qt::transparent);
if (!renderer.isValid()) {
qWarning() << "Failed to load tray icon:" << resourcePath;
return pixmap;
}
QPainter painter(&pixmap);
renderer.render(&painter, QRectF(0, 0, size, size));
return pixmap;
}
QPixmap buildPixmap(int size, Vpn::ConnectionState state, bool darkTheme)
{
return renderIcon(resourcePathForState(state, darkTheme), size);
}
QIcon buildIcon(Vpn::ConnectionState state, bool darkTheme)
{
QIcon icon;
icon.addPixmap(buildPixmap(kDefaultTrayIconSize, state, darkTheme));
return icon;
}
QByteArray pixmapToPng(const QPixmap &pixmap)
{
QByteArray bytes;
QBuffer buffer(&bytes);
buffer.open(QIODevice::WriteOnly);
pixmap.save(&buffer, "PNG");
return bytes;
}
QByteArray buildTemplatePng(Vpn::ConnectionState state)
{
return pixmapToPng(renderIcon(resourcePathForState(state, /*darkTheme*/ true), kDefaultTrayIconSize));
}
QByteArray buildColorPng(Vpn::ConnectionState state, bool darkTheme)
{
return pixmapToPng(renderIcon(resourcePathForState(state, darkTheme), kDefaultTrayIconSize));
}
} // namespace TrayIconCommon
+35
View File
@@ -0,0 +1,35 @@
#ifndef TRAYICONCOMMON_H
#define TRAYICONCOMMON_H
#include <QByteArray>
#include <QIcon>
#include <QPixmap>
#include <QString>
#include "core/protocols/vpnProtocol.h"
namespace TrayIconCommon
{
constexpr int kDefaultTrayIconSize = 128;
constexpr char kIconOffBlack[] = ":/images/tray/off-black.svg";
constexpr char kIconOffLight[] = ":/images/tray/off-light.svg";
constexpr char kIconOnBlack[] = ":/images/tray/on-black.svg";
constexpr char kIconOnWhite[] = ":/images/tray/on-white.svg";
constexpr char kIconErrorBlack[] = ":/images/tray/error-black.svg";
constexpr char kIconErrorWhite[] = ":/images/tray/error-white.svg";
QString resourcePathForState(Vpn::ConnectionState state, bool darkTheme);
bool isColoredState(Vpn::ConnectionState state);
QPixmap renderIcon(const QString &resourcePath, int size);
QPixmap buildPixmap(int size, Vpn::ConnectionState state, bool darkTheme);
QIcon buildIcon(Vpn::ConnectionState state, bool darkTheme);
QByteArray buildTemplatePng(Vpn::ConnectionState state);
QByteArray buildColorPng(Vpn::ConnectionState state, bool darkTheme);
} // namespace TrayIconCommon
#endif // TRAYICONCOMMON_H
+20
View File
@@ -0,0 +1,20 @@
#include "trayThemeChangeFilter.h"
#include <QEvent>
TrayThemeChangeFilter::TrayThemeChangeFilter(std::function<void()> onThemeChanged, QObject *parent)
: QObject(parent)
, m_onThemeChanged(std::move(onThemeChanged))
{
}
bool TrayThemeChangeFilter::eventFilter(QObject *watched, QEvent *event)
{
Q_UNUSED(watched);
if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::ThemeChange) {
if (m_onThemeChanged) {
m_onThemeChanged();
}
}
return QObject::eventFilter(watched, event);
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef TRAYTHEMECHANGEFILTER_H
#define TRAYTHEMECHANGEFILTER_H
#include <functional>
#include <QObject>
class TrayThemeChangeFilter final : public QObject {
Q_OBJECT
public:
explicit TrayThemeChangeFilter(std::function<void()> onThemeChanged, QObject *parent = nullptr);
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
std::function<void()> m_onThemeChanged;
};
#endif // TRAYTHEMECHANGEFILTER_H
+1 -4
View File
@@ -15,8 +15,7 @@ enum PermittedProcess {
OpenVPN,
Wireguard,
Tun2Socks,
CertUtil,
Security
CertUtil
};
inline QString permittedProcessPath(PermittedProcess pid)
@@ -30,8 +29,6 @@ inline QString permittedProcessPath(PermittedProcess pid)
return Utils::certUtilPath();
case PermittedProcess::Tun2Socks:
return Utils::tun2socksPath();
case PermittedProcess::Security:
return Utils::securityPath();
default:
return "";
}
+1 -16
View File
@@ -5,7 +5,6 @@ from conan.errors import ConanInvalidConfiguration
from conan.tools.scm import Git
from conan.internal.model.pkg_type import PackageType
from conan.tools.files import chdir
from conan.tools.apple import XCRun
import os
import shutil
@@ -50,10 +49,7 @@ class OpenVPNAdapter(ConanFile):
def build(self):
with chdir(self, self.source_folder):
xcrun = XCRun(self)
xcodebuild = xcrun.find("xcodebuild")
self.run(f"{xcodebuild}"
self.run("xcrun xcodebuild"
" -project OpenVPNAdapter.xcodeproj"
" -scheme OpenVPNAdapter"
" -configuration Release"
@@ -61,20 +57,10 @@ class OpenVPNAdapter(ConanFile):
f" -sdk {self._sdk}"
f' "CONFIGURATION_BUILD_DIR={self.build_folder}"'
f' "BUILT_PRODUCTS_DIR={self.build_folder}"'
" MACH_O_TYPE=staticlib"
" BUILD_LIBRARY_FOR_DISTRIBUTION=YES"
" CODE_SIGNING_ALLOWED=NO"
)
openvpnadapter = os.path.join(self.build_folder, "OpenVPNAdapter.framework", "OpenVPNAdapter")
self.run(f"{xcrun.libtool} -static -o"
f" {openvpnadapter}"
f" {openvpnadapter}"
f' {os.path.join(self.build_folder, "OpenVPNClient.framework", "OpenVPNClient")}'
f' {os.path.join(self.build_folder, "LZ4.framework", "LZ4")}'
f' {os.path.join(self.build_folder, "mbedTLS.framework", "mbedTLS")}'
)
def package(self):
shutil.copytree(os.path.join(self.build_folder, "OpenVPNAdapter.framework"),
os.path.join(self.package_folder, "OpenVPNAdapter.framework"))
@@ -84,4 +70,3 @@ class OpenVPNAdapter(ConanFile):
self.cpp_info.type = PackageType.STATIC
self.cpp_info.package_framework = True
self.cpp_info.location = os.path.join(self.package_folder, "OpenVPNAdapter.framework")
self.cpp_info.frameworks = ["SystemConfiguration"]