Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,28 @@ else()
qt_finalize_target(${PROJECT})
endif()

if(IS_REGISTER_VPN_URL)
if(APPLE AND NOT IOS AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister
-f
-R
$<TARGET_BUNDLE_DIR:${PROJECT}>
COMMENT "lsregister: register $<TARGET_BUNDLE_DIR:${PROJECT}> with Launch Services"
)
elseif(WIN32)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DEXE_PATH=$<TARGET_FILE:${PROJECT}>
-P "${CMAKE_SOURCE_DIR}/cmake/register_vpn_url_win.cmake"
COMMENT "HKCU: register vpn\\shell\\open\\command -> $<TARGET_FILE:${PROJECT}>"
)
endif()
endif()

install(TARGETS ${PROJECT}
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT AmneziaVPN
Expand Down
207 changes: 184 additions & 23 deletions client/amneziaApplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#include <QEvent>
#include <QDir>
#include <QSettings>
#include <QFileOpenEvent>
#include <QUrl>
#include <QCoreApplication>
#include <QtQuick/QQuickWindow>
#include <QWindow>

Expand All @@ -29,6 +32,17 @@

bool AmneziaApplication::m_forceQuit = false;

#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
namespace {
bool g_secondaryInstanceForDeepLink = false;
}

void AmneziaApplication::markSecondaryInstanceForDeepLink()
{
g_secondaryInstanceForDeepLink = true;
}
#endif

AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
Expand Down Expand Up @@ -61,25 +75,54 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
AmneziaApplication::~AmneziaApplication()
{
#ifdef AMNEZIA_DESKTOP
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);

QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
if (m_vpnConnection) {
m_vpnConnection->disconnectSlots();
m_vpnConnection->disconnectFromVpn();
}
#endif

m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit();
if (m_engine) {
delete m_engine;
}
}

if (!m_vpnConnectionThread.wait(3000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait(500);
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
namespace {
QString vpnUrlFromArguments(const QStringList &args)
{
for (const QString &arg : args) {
const QString t = arg.trimmed();
if (t.startsWith(QLatin1String("vpn://"), Qt::CaseInsensitive)) {
return t;
}
}
return {};
}
} // namespace
#endif

if (m_engine) {
delete m_engine;
#if defined(Q_OS_WIN) && !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
namespace {
void registerWindowsVpnUrlSchemeIfNeeded()
{
QSettings flag(ORGANIZATION_NAME, APPLICATION_NAME);
if (flag.value(QStringLiteral("protocolHandler/vpnRegistered")).toBool()) {
return;
}

const QString exe = QDir::toNativeSeparators(QCoreApplication::applicationFilePath());

QSettings vpnKey(QStringLiteral("HKEY_CURRENT_USER\\Software\\Classes\\vpn"), QSettings::NativeFormat);
vpnKey.setValue(QStringLiteral("."), QStringLiteral("URL:AmneziaVPN"));
vpnKey.setValue(QStringLiteral("URL Protocol"), QString());

QSettings cmdKey(QStringLiteral("HKEY_CURRENT_USER\\Software\\Classes\\vpn\\shell\\open\\command"), QSettings::NativeFormat);
cmdKey.setValue(QStringLiteral("."), QStringLiteral("\"%1\" \"%2\"").arg(exe, QStringLiteral("%1")));

flag.setValue(QStringLiteral("protocolHandler/vpnRegistered"), true);
}
} // namespace
#endif

#ifdef Q_OS_ANDROID
namespace {
Expand Down Expand Up @@ -133,9 +176,6 @@ void AmneziaApplication::init()
#endif

m_vpnConnection.reset(new VpnConnection(nullptr, nullptr));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();

m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));

m_engine->addImportPath("qrc:/ui/qml/Modules/");
Expand Down Expand Up @@ -190,6 +230,23 @@ void AmneziaApplication::init()
});
}
}

#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
# ifdef Q_OS_WIN
registerWindowsVpnUrlSchemeIfNeeded();
# endif
if (!m_parser.isSet(m_optImport)) {
const QString vpnArg = vpnUrlFromArguments(QCoreApplication::arguments());
if (!vpnArg.isEmpty()) {
m_pendingVpnDeepLink.clear();
QTimer::singleShot(0, this, [this, vpnArg]() { deliverVpnDeepLink(vpnArg); });
}
}
if (!m_pendingVpnDeepLink.isEmpty()) {
const QString pending = std::move(m_pendingVpnDeepLink);
QTimer::singleShot(0, this, [this, pending]() { deliverVpnDeepLink(pending); });
}
#endif
}

void AmneziaApplication::registerTypes()
Expand Down Expand Up @@ -250,20 +307,124 @@ bool AmneziaApplication::parseCommands()
}

#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance");
void AmneziaApplication::startLocalServer()
{
const QString serverName(QStringLiteral("AmneziaVPNInstance"));
QLocalServer::removeServer(serverName);

QLocalServer *server = new QLocalServer(this);
server->listen(serverName);
if (!server->listen(serverName)) {
qWarning() << "QLocalServer::listen failed:" << server->errorString();
}

QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
if (server) {
QLocalSocket *clientConnection = server->nextPendingConnection();
clientConnection->deleteLater();
QObject::connect(server, &QLocalServer::newConnection, this, [this, server]() {
QLocalSocket *sock = server->nextPendingConnection();
if (!sock) {
return;
}
emit m_coreController->pageController()->raiseMainWindow(); //TODO
});

QString vpnPayload;
if (sock->waitForReadyRead(3000)) {
const QByteArray buf = sock->readAll();
static const QByteArray prefix = QByteArrayLiteral("VPN\n");
if (buf.startsWith(prefix)) {
vpnPayload = QString::fromUtf8(buf.mid(prefix.size())).trimmed();
}
}
sock->deleteLater();

if (!vpnPayload.isEmpty()) {
QTimer::singleShot(0, this, [this, vpnPayload]() { deliverVpnDeepLink(vpnPayload); });
}

QTimer::singleShot(0, this, [this]() {
if (m_coreController && m_coreController->pageController()) {
emit m_coreController->pageController()->raiseMainWindow();
}
});
}, Qt::QueuedConnection);
}
#endif

#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::deliverVpnDeepLink(const QString &payload)
{
if (!m_coreController) {
return;
}
const QString trimmed = payload.trimmed();
if (trimmed.isEmpty()) {
return;
}
m_coreController->openVpnKeyImportPreview(trimmed);
}

QString vpnPayloadFromFileOpenUrl(const QUrl &url)
{
const QString decoded = url.toString(QUrl::PrettyDecoded);
const int idx = decoded.indexOf(QLatin1String("vpn://"), 0, Qt::CaseInsensitive);
if (idx >= 0) {
qDebug() << "vpn://" << decoded;
return decoded.mid(idx).trimmed();
}
if (url.scheme().compare(QLatin1String("vpn"), Qt::CaseInsensitive) == 0) {
qDebug() << "vpn://" << decoded;
return decoded.trimmed();
}
return {};
}

#if !defined(MACOS_NE)
bool forwardVpnPayloadToPrimaryInstance(const QString &payload)
{
if (payload.trimmed().isEmpty()) {
return false;
}
QLocalSocket socket;
socket.connectToServer(QStringLiteral("AmneziaVPNInstance"));
if (!socket.waitForConnected(800)) {
return false;
}
const QByteArray msg = QByteArrayLiteral("VPN\n") + payload.toUtf8() + '\n';
socket.write(msg);
socket.waitForBytesWritten(3000);
socket.flush();
return true;
}
#endif

bool AmneziaApplication::event(QEvent *event)
{
if (event->type() == QEvent::FileOpen) {
auto *foe = static_cast<QFileOpenEvent *>(event);
const QUrl &url = foe->url();
qDebug() << "url:" << url;
const QString payload = vpnPayloadFromFileOpenUrl(url);
qDebug() << "payload" << payload;
if (!payload.isEmpty()) {
if (m_coreController) {
QTimer::singleShot(0, this, [this, payload]() { deliverVpnDeepLink(payload); });
return true;
}
#if !defined(MACOS_NE)
// True secondary process (main skipped init): forward to the primary instance.
if (g_secondaryInstanceForDeepLink) {
if (forwardVpnPayloadToPrimaryInstance(payload)) {
qInfo().noquote() << "Forwarded vpn deep link to primary instance, bytes:" << payload.size();
QTimer::singleShot(0, qApp, &QCoreApplication::quit);
return true;
}
qWarning() << "vpn FileOpen: secondary instance could not reach primary (socket)";
return true;
}
#endif
// Cold start: FileOpen can arrive while init() is still running (CoreController not ready yet).
// Do not forward to our own local server — queue and flush at end of init().
m_pendingVpnDeepLink = payload;
return true;
}
}
return AMNEZIA_BASE_CLASS::event(event);
}
#endif

Expand Down
14 changes: 12 additions & 2 deletions client/amneziaApplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
#include <QNetworkAccessManager>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include <QClipboard>
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include <QEvent>
#endif

#include "core/controllers/coreController.h"
#include "secureQSettings.h"
Expand Down Expand Up @@ -41,6 +43,7 @@ class AmneziaApplication : public AMNEZIA_BASE_CLASS

#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void startLocalServer();
static void markSecondaryInstanceForDeepLink();
#endif

QQmlApplicationEngine *qmlEngine() const;
Expand All @@ -67,12 +70,19 @@ public slots:
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;

#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void deliverVpnDeepLink(const QString &payload);
QString m_pendingVpnDeepLink;
#endif

QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;

QNetworkAccessManager *m_nam;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
bool event(QEvent *event) override;
#endif
};

#endif // AMNEZIA_APPLICATION_H
5 changes: 4 additions & 1 deletion client/android/src/org/amnezia/vpn/AmneziaActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,10 @@ class AmneziaActivity : QtActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent: $intent")
intent?.let(::processIntent)
intent?.let {
setIntent(it)
processIntent(it)
}
}

private fun processIntent(intent: Intent) {
Expand Down
1 change: 1 addition & 0 deletions client/android/src/org/amnezia/vpn/ImportConfigActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class ImportConfigActivity : ComponentActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent: $intent")
setIntent(intent)
intent.let(::readConfig)
}

Expand Down
2 changes: 2 additions & 0 deletions client/cmake/ios.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaOpenUrlImport.h
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)

Expand All @@ -47,6 +48,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaOpenUrlImport.mm
)


Expand Down
6 changes: 5 additions & 1 deletion client/cmake/macos.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ set(LIBS ${LIBS}

set_target_properties(${PROJECT} PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in"
MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_OSX_APP_IDENTIFIER}"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
MACOSX_BUNDLE_COPYRIGHT ""
MACOSX_BUNDLE_ICON_FILE "app.icns"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
)
Expand All @@ -35,7 +40,6 @@ set(SOURCES ${SOURCES}


set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${ICON_FILE})

Expand Down
Loading
Loading