refactor: refactor the application to the mvvm architecture (#2009)

* refactor: move business logic from servers model

* refactor: move containersModel initialization

* refactor: added protocol ui controller and removed settings class from protocols model

* refactor: moved cli management to separate controller

* refactor: moved app split to separate controller

* refactor: moved site split to separate controller

* refactor: moved allowed dns to separate controller

* refactor: moved language logic to separate ui controller

* refactor: removed Settings from devices model

* refactor: moved configs and services api logit to separate core controller

* refactor: added a layer with a repository between the storage and controllers

* refactor: use child parent system instead of smart pointers for controllers and models initialization

* refactor: moved install functions from server controller to install controller

* refactor: install controller refactoring

* chore: renamed exportController to exportUiController

* refactor: separate export controller

* refactor: removed VpnConfigurationsController

* chore: renamed ServerController to SshSession

* refactor: replaced ServerController to SshSession

* chore: moved qml controllers to separate folder

* chore: include fixes

* chore: moved utils from core root to core/utils

* chore: include fixes

* chore: rename core/utils files to camelCase foramt

* chore: include fixes

* chore: moved some utils to api and selfhosted folders

* chore: include fixes

* chore: remove unused file

* chore: moved serialization folder to core/utils

* chore: include fixes

* chore: moved some files from client root to core/utils

* chore: include fixes

* chore: moved ui utils to ui/utils folder

* chore: include fixes

* chore: move utils from root to ui/utils

* chore: include fixes

* chore: moved configurators to core/configurators

* chore: include fixes

* refactor: moved iap logic from ui controller to core

* refactor: moved remaining core logic from ApiConfigsController to SubscriptionController

* chore: rename apiNewsController to apiNewsUiController

* refactor: moved core logic from news ui controller to core

* chore: renamed apiConfigsController to subscriptionUiController

* chore: include fixes

* refactor: merge ApiSettingsController with SubscriptionUiController

* chore: moved ui selfhosted controllers to separate folder

* chore: include fixes

* chore: rename connectionController to connectiomUiController

* refactor: moved core logic from connectionUiController

* chore: rename settingsController to settingsUiController

* refactor: move core logic from settingsUiController

* refactor: moved core controller signal/slot connections to separate class

* fix: newsController fixes after refactoring

* chore: rename model to camelCase

* chore: include fixes

* chore: remove unused code

* chore: move selfhosted core to separate folder

* chore: include fixes

* chore: rename importController to importUiController

* refactor: move core logic from importUiController

* chore: minor fixes

* chore: remove prem v1 migration

* refactor: remove openvpn over cloak and openvpn over shadowsocks

* refactor: removed protocolsForContainer function

* refactor: add core models

* refactor: replace json with c++ structs for server config

* refactor: move getDnsPair to ServerConfigUtils

* feat: add admin selfhosted config export test

* feat: add multi import test

* refactor: use coreController for tests

* feat: add few simple tests

* chore: qrepos in all core controllers

* feat: add test for settings

* refactor: remove repo dependency from configurators

* chore: moved protocols to core folder

* chore: include fixes

* refactor: moved containersDefs, defs, apiDefs, protocolsDefs to different places

* chore: include fixes

* chore: build fixes

* chore: build fixes

* refactor: remove q repo and interface repo

* feat: add test for ui servers model and controller

* chore: renamed to camelCase

* chore: include fixes

* refactor: moved core logic from sites ui controller

* fix: fixed api config processing

* fix: fixed processed server index processing

* refactor: protocol models now use c++ structs instead of json configs

* refactor: servers model now use c++ struct instead of json config

* fix: fixed default server index processing

* fix: fix logs init

* fix: fix secure settings load keys

* chore: build fixes

* fix: fixed clear settings

* fix: fixed restore backup

* fix: sshSession usage

* fix: fixed export functions signatures

* fix: return missing part from buildContainerWorker

* fix: fixed server description on page home

* refactor: add container config helpers functions

* refactor: c++ structs instead of json

* chore: add dns protocol config struct

* refactor: move config utils functions to config structs

* feat: add test for selfhosted server setup

* refactor: separate resources.qrc

* fix: fixed server rename

* chore: return nameOverriddenByUser

* fix: build fixes

* fix: fixed models init

* refactor: cleanup models usage

* fix: fixed models init

* chore: cleanup connections and functions signatures

* chore: cleanup updateModel calls

* feat: added cache to servers repo

* chore: cleanup unused functions

* chore: ssxray processing

* chore: remove transportProtoWithDefault and portWithDefault functions

* chore: removed proto types any and l2tp

* refactor: moved some constants

* fix: fixed native configs export

* refactor: remove json from processConfigWith functions

* fix: fixed processed server index usage

* fix: qml warning fixes

* chore: merge fixes

* chore: update tests

* fix: fixed xray config processing

* fix: fixed split tunneling processing

* chore: rename sites controllers and model

* chore: rename fixes

* chore: minor fixes

* chore: remove ability to load backup from "file with connection settings" button

* fix: fixed api device revoke

* fix: remove full model update when renaming a user

* fix: fixed premium/free server rename

* fix: fixed selfhosted new server install

* fix: fixed updateContainer function

* fix: fixed revoke for external premium configs

* feat: add native configs qr processing

* chore: codestyle fixes

* fix: fixed admin config create

* chore: again remove ability to load backup from "file with connection settings" button

* chore: minor fixes

* fix: fixed variables initialization

* fix: fixed qml imports

* fix: minor fixes

* fix: fix vpnConnection function calls

* feat: add buckup error handling

* fix: fixed admin config revok

* fix: fixed selfhosted awg installation

* fix: ad visability

* feat: add empty check for primary dns

* chore: minor fixes
This commit is contained in:
vkamn
2026-04-30 14:53:03 +08:00
committed by GitHub
parent 2edd7de413
commit 847bb6923b
469 changed files with 25992 additions and 17154 deletions
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <QMetaEnum>
namespace utils
{
template<typename Enum>
QString enumToString(Enum value)
{
auto metaEnum = QMetaEnum::fromType<Enum>();
return metaEnum.valueToKey(static_cast<int>(value));
}
template<typename Enum>
Enum enumFromString(const QString &str, Enum defaultValue = {})
{
auto metaEnum = QMetaEnum::fromType<Enum>();
bool isOk;
auto value = metaEnum.keyToValue(str.toLatin1(), &isOk);
if (isOk) {
return static_cast<Enum>(value);
}
return defaultValue;
}
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef OSXUTIL_H
#define OSXUTIL_H
#ifndef Q_OS_IOS
#include <QDialog>
#include <QWidget>
void setDockIconVisible(bool visible);
void fixWidget(QWidget *widget);
#endif
#endif
+87
View File
@@ -0,0 +1,87 @@
#include "macosUtil.h"
#include <QMainWindow>
#include <QProcess>
#include <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
// void setDockIconVisible(bool visible)
//{
// QProcess process;
// process.start(
// "osascript",
// { "-e tell application \"System Events\" to get properties of (get application process \"AmneziaVPN\")" });
// process.waitForFinished(3000);
// const auto output = QString::fromLocal8Bit(process.readAllStandardOutput());
// qDebug() << output;
// if (visible) {
// [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
// } else {
// [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
// }
//}
void setDockIconVisible(bool visible)
{
ProcessSerialNumber psn = { 0, kCurrentProcess };
if (visible) {
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
} else {
TransformProcessType(&psn, kProcessTransformToBackgroundApplication);
}
}
// this Objective-c class is used to override the action of system close button and zoom button
// https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results
@interface ButtonPasser : NSObject {
}
@property (readwrite) QMainWindow *window;
+ (void)closeButtonAction:(id)sender;
- (void)zoomButtonAction:(id)sender;
@end
@implementation ButtonPasser {
}
+ (void)closeButtonAction:(id)sender
{
Q_UNUSED(sender);
ProcessSerialNumber pn;
GetFrontProcess(&pn);
ShowHideProcess(&pn, false);
}
- (void)zoomButtonAction:(id)sender
{
Q_UNUSED(sender);
if (0 == self.window)
return;
if (self.window->isMaximized())
self.window->showNormal();
else
self.window->showMaximized();
}
@end
void fixWidget(QWidget *widget)
{
NSView *view = (NSView *)widget->winId();
if (0 == view)
return;
NSWindow *window = view.window;
if (0 == window)
return;
// override the action of close button
// https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results
// https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Target-Action/Target-Action.html
// NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton];
// [closeButton setTarget:[ButtonPasser class]];
// [closeButton setAction:@selector(closeButtonAction:)];
[[window standardWindowButton:NSWindowZoomButton] setHidden:YES];
[[window standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef NENOTIFICATIONHANDLER_H
#define NENOTIFICATIONHANDLER_H
#include "notificationHandler.h"
#include <QMenu>
#include <QAction>
class MacOSStatusIcon;
class NEStatusBarNotificationHandler : public NotificationHandler {
Q_OBJECT
public:
explicit NEStatusBarNotificationHandler(QObject* parent);
~NEStatusBarNotificationHandler() override;
void setConnectionState(Vpn::ConnectionState state) override;
void onTranslationsUpdated() override;
protected:
void notify(Message type, const QString& title,
const QString& message, int timerMsec) override;
private:
void buildMenu();
QMenu m_menu;
MacOSStatusIcon* m_statusIcon;
QAction* m_actionShow;
QAction* m_actionConnect;
QAction* m_actionDisconnect;
QAction* m_actionVisitWebsite;
QAction* m_actionQuit;
};
#endif // NENOTIFICATIONHANDLER_H
+113
View File
@@ -0,0 +1,113 @@
/* 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 <QDebug>
#include "notificationHandler.h"
#if defined(Q_OS_IOS)
# include "platforms/ios/iosnotificationhandler.h"
#else
# include "systemTrayNotificationHandler.h"
#endif
// static
NotificationHandler* NotificationHandler::create(QObject* parent) {
#if defined(Q_OS_IOS)
return new IOSNotificationHandler(parent);
#else
return new SystemTrayNotificationHandler(parent);
#endif
}
namespace {
NotificationHandler* s_instance = nullptr;
} // namespace
// static
NotificationHandler* NotificationHandler::instance() {
Q_ASSERT(s_instance);
return s_instance;
}
NotificationHandler::NotificationHandler(QObject* parent) : QObject(parent) {
Q_ASSERT(!s_instance);
s_instance = this;
}
NotificationHandler::~NotificationHandler() {
Q_ASSERT(s_instance == this);
s_instance = nullptr;
}
void NotificationHandler::setConnectionState(Vpn::ConnectionState state)
{
if (state != Vpn::ConnectionState::Connected && state != Vpn::ConnectionState::Disconnected) {
return;
}
QString title;
QString message;
switch (state) {
case Vpn::ConnectionState::Connected:
m_connected = true;
title = tr("AmneziaVPN");
message = tr("VPN Connected");
break;
case Vpn::ConnectionState::Disconnected:
if (m_connected) {
m_connected = false;
title = tr("AmneziaVPN");
message = tr("VPN Disconnected");
}
break;
default:
break;
}
Q_ASSERT(title.isEmpty() == message.isEmpty());
if (!title.isEmpty()) {
notifyInternal(VpnState, title, message, 2000);
}
}
void NotificationHandler::onTranslationsUpdated()
{
}
void NotificationHandler::unsecuredNetworkNotification(const QString& networkName) {
qDebug() << "Unsecured network notification shown";
QString title = tr("AmneziaVPN notification");
QString message = tr("Unsecured network detected: ") + networkName;
notifyInternal(UnsecuredNetwork, title, message, 2000);
}
void NotificationHandler::notifyInternal(Message type, const QString& title,
const QString& message,
int timerMsec) {
m_lastMessage = type;
emit notificationShown(title, message);
notify(type, title, message, timerMsec);
}
void NotificationHandler::messageClickHandle() {
qDebug() << "Message clicked";
if (m_lastMessage == VpnState) {
qCritical() << "Random message clicked received";
return;
}
emit notificationClicked(m_lastMessage);
m_lastMessage = VpnState;
}
+65
View File
@@ -0,0 +1,65 @@
/* 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 NOTIFICATIONHANDLER_H
#define NOTIFICATIONHANDLER_H
#include <QObject>
#include "core/protocols/vpnProtocol.h"
class QMenu;
class NotificationHandler : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(NotificationHandler)
public:
enum Message {
VpnState,
UnsecuredNetwork
};
static NotificationHandler* create(QObject* parent);
static NotificationHandler* instance();
virtual ~NotificationHandler();
void unsecuredNetworkNotification(const QString& networkName);
void messageClickHandle();
public slots:
virtual void setConnectionState(Vpn::ConnectionState state);
virtual void onTranslationsUpdated();
signals:
void notificationShown(const QString& title, const QString& message);
void notificationClicked(Message message);
void raiseRequested();
void connectRequested();
void disconnectRequested();
protected:
explicit NotificationHandler(QObject* parent);
virtual void notify(Message type, const QString& title,
const QString& message, int timerMsec) = 0;
private:
virtual void notifyInternal(Message type, const QString& title,
const QString& message, int timerMsec);
protected:
Message m_lastMessage = VpnState;
private:
// We want to show a 'disconnected' notification only if we were actually
// connected.
bool m_connected = false;
};
#endif // NOTIFICATIONHANDLER_H
+44
View File
@@ -0,0 +1,44 @@
#ifndef PAGES_H
#define PAGES_H
#include <QObject>
#include <QQmlEngine>
class PageType : public QObject
{
Q_GADGET
public:
enum Type {
Basic,
Proto,
ShareProto,
ClientInfo
};
Q_ENUM(Type)
};
namespace PageEnumNS
{
Q_NAMESPACE
enum class Page { Start = 0, NewServer, NewServerProtocols, Vpn,
Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress,
GeneralSettings, AppSettings, NetworkSettings, ServerSettings,
ServerContainers, ServersList, ShareConnection, Sites,
ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig,
AdvancedServerSettings, ClientManagement, ClientInfo};
Q_ENUM_NS(Page)
static void declareQmlPageEnum() {
qmlRegisterUncreatableMetaObject(
PageEnumNS::staticMetaObject,
"PageEnum",
1, 0,
"PageEnum",
"Error: only enums"
);
}
} // PAGES_H
#endif
+159
View File
@@ -0,0 +1,159 @@
// The MIT License (MIT)
//
// Copyright (C) 2016 Mostafa Sedaghat Joo (mostafa.sedaghat@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "qAutoStart.h"
#include <QCoreApplication>
#include <QTextStream>
#include <QFileInfo>
#include <QSettings>
#include <QProcess>
#include <QString>
#include <QFile>
#include <QDir>
#if defined (Q_OS_WIN)
#define REG_KEY "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
bool Autostart::isAutostart() {
QSettings settings(REG_KEY, QSettings::NativeFormat);
if (settings.value(appName()).isNull()) {
return false;
}
return true;
}
void Autostart::setAutostart(bool autostart) {
QSettings settings(REG_KEY, QSettings::NativeFormat);
if (autostart) {
settings.setValue(appName() , appPath().replace('/','\\'));
} else {
settings.remove(appName());
}
}
QString Autostart::appPath() {
return QCoreApplication::applicationFilePath() + " --autostart";
}
#elif defined Q_OS_MACX
bool Autostart::isAutostart() {
QProcess process;
process.start("osascript", {
"-e tell application \"System Events\" to get the path of every login item"
});
process.waitForFinished(3000);
const auto output = QString::fromLocal8Bit(process.readAllStandardOutput());
return output.contains(appPath());
}
void Autostart::setAutostart(bool autostart) {
// Remove any existing login entry for this app first, in case there was one
// from a previous installation, that may be under a different launch path.
{
QProcess::execute("osascript", {
"-e tell application \"System Events\" to delete every login item whose name is \"" + appName() + "\""
});
}
// Now install the login item, if needed.
if ( autostart )
{
QProcess::execute("osascript", {
"-e tell application \"System Events\" to make login item at end with properties {path:\"" + appPath() + "\", hidden:true, name: \"" + appName() + "\"}"
});
}
}
QString Autostart::appPath() {
QDir appDir = QDir(QCoreApplication::applicationDirPath());
appDir.cdUp();
appDir.cdUp();
QString absolutePath = appDir.absolutePath();
return absolutePath;
}
#elif defined (Q_OS_LINUX)
bool Autostart::isAutostart() {
QFileInfo check_file(QDir::homePath() + "/.config/autostart/" + appName() +".desktop");
if (check_file.exists() && check_file.isFile()) {
return true;
}
return false;
}
void Autostart::setAutostart(bool autostart) {
QString path = QDir::homePath() + "/.config/autostart/";
QString name = appName() +".desktop";
QFile file(path+name);
file.remove();
if(autostart) {
QDir dir(path);
if(!dir.exists()) {
dir.mkpath(path);
}
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
stream << "[Desktop Entry]" << Qt::endl;
stream << "Exec=AmneziaVPN" << Qt::endl;
stream << "Type=Application" << Qt::endl;
stream << "Name=AmneziaVPN" << Qt::endl;
stream << "Comment=Client of your self-hosted VPN" << Qt::endl;
stream << "Icon=/usr/share/pixmaps/AmneziaVPN.png" << Qt::endl;
stream << "Categories=Network;Qt;Security;" << Qt::endl;
stream << "Terminal=false" << Qt::endl;
}
}
}
QString Autostart::appPath() {
return QCoreApplication::applicationFilePath() + " --autostart";
}
#else
bool Autostart::isAutostart() {
return false;
}
void Autostart::setAutostart(bool autostart) {
Q_UNUSED(autostart);
}
QString Autostart::appPath() {
return QString();
}
#endif
QString Autostart::appName() {
return QCoreApplication::applicationName();
}
+39
View File
@@ -0,0 +1,39 @@
// The MIT License (MIT)
//
// Copyright (C) 2016 Mostafa Sedaghat Joo (mostafa.sedaghat@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef AUTOSTART_H
#define AUTOSTART_H
#include <QString>
class Autostart
{
public:
static bool isAutostart();
static void setAutostart(bool autostart);
protected:
static QString appPath();
static QString appName();
};
#endif // AUTOSTART_H
+128
View File
@@ -0,0 +1,128 @@
#include "qmlUtils.h"
#include <QPointF>
#include <QQuickItem>
#include <QQuickWindow>
namespace FocusControl
{
QPointF getItemCenterPointOnScene(QQuickItem *item)
{
const auto x0 = item->x() + (item->width() / 2);
const auto y0 = item->y() + (item->height() / 2);
return item->parentItem()->mapToScene(QPointF { x0, y0 });
}
bool isEnabled(QObject *obj)
{
const auto item = qobject_cast<QQuickItem *>(obj);
return item && item->isEnabled();
}
bool isVisible(QObject *item)
{
const auto res = item->property("visible").toBool();
return res;
}
bool isFocusable(QObject *item)
{
const auto res = item->property("isFocusable").toBool();
return res;
}
bool isListView(QObject *item)
{
return item->inherits("QQuickListView");
}
bool isOnTheScene(QObject *object)
{
QQuickItem *item = qobject_cast<QQuickItem *>(object);
if (!item) {
qWarning() << "Couldn't recognize object as item";
return false;
}
if (!item->isVisible()) {
return false;
}
QRectF itemRect = item->mapRectToScene(item->childrenRect());
QQuickWindow *window = item->window();
if (!window) {
qWarning() << "Couldn't get the window on the Scene check";
return false;
}
const auto contentItem = window->contentItem();
if (!contentItem) {
qWarning() << "Couldn't get the content item on the Scene check";
return false;
}
QRectF windowRect = contentItem->childrenRect();
const auto res = (windowRect.contains(itemRect) || isListView(item));
return res;
}
bool isMore(QObject *item1, QObject *item2)
{
return !isLess(item1, item2);
}
bool isLess(QObject *item1, QObject *item2)
{
const auto p1 = getItemCenterPointOnScene(qobject_cast<QQuickItem *>(item1));
const auto p2 = getItemCenterPointOnScene(qobject_cast<QQuickItem *>(item2));
return (p1.y() == p2.y()) ? (p1.x() < p2.x()) : (p1.y() < p2.y());
}
QList<QObject *> getSubChain(QObject *object)
{
QList<QObject *> res;
if (!object) {
return res;
}
const auto children = object->children();
for (const auto child : children) {
if (child && isFocusable(child) && isOnTheScene(child) && isEnabled(child)) {
res.append(child);
} else {
res.append(getSubChain(child));
}
}
return res;
}
QList<QObject *> getItemsChain(QObject *object)
{
QList<QObject *> res;
if (!object) {
return res;
}
const auto children = object->children();
for (const auto child : children) {
if (child && isFocusable(child) && isEnabled(child) && isVisible(child)) {
res.append(child);
} else {
res.append(getItemsChain(child));
}
}
return res;
}
void printItems(const QList<QObject *> &items, QObject *current_item)
{
for (const auto &item : items) {
QQuickItem *i = qobject_cast<QQuickItem *>(item);
QPointF coords { getItemCenterPointOnScene(i) };
QString prefix = current_item == i ? "==>" : " ";
qDebug() << prefix << " Item: " << i << " with coords: " << coords;
}
}
} // namespace FocusControl
+30
View File
@@ -0,0 +1,30 @@
#ifndef FOCUSCONTROL_H
#define FOCUSCONTROL_H
#include <QList>
#include <QObject>
namespace FocusControl
{
bool isEnabled(QObject *item);
bool isVisible(QObject *item);
bool isFocusable(QObject *item);
bool isListView(QObject *item);
bool isOnTheScene(QObject *object);
bool isMore(QObject *item1, QObject *item2);
bool isLess(QObject *item1, QObject *item2);
/*!
* \brief Make focus chain of elements which are on the scene
*/
QList<QObject *> getSubChain(QObject *object);
/*!
* \brief Make focus chain of elements which could be not on the scene
*/
QList<QObject *> getItemsChain(QObject *object);
void printItems(const QList<QObject *> &items, QObject *current_item);
} // namespace FocusControl
#endif // FOCUSCONTROL_H
@@ -0,0 +1,173 @@
/* 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 <QDebug>
#include "systemTrayNotificationHandler.h"
#ifdef Q_OS_MAC
# include "platforms/macos/macosutils.h"
#endif
#include <QApplication>
#include <QDesktopServices>
#include <QIcon>
#include <QWindow>
#include "version.h"
SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) :
NotificationHandler(parent),
m_systemTrayIcon(parent)
{
m_systemTrayIcon.show();
connect(&m_systemTrayIcon, &QSystemTrayIcon::activated, this, &SystemTrayNotificationHandler::onTrayActivated);
m_trayActionShow = m_menu.addAction(QIcon(":/images/tray/application.png"), tr("Show") + " " + APPLICATION_NAME, this, [this](){
emit raiseRequested();
});
m_menu.addSeparator();
m_trayActionConnect = m_menu.addAction(tr("Connect"), this, [this](){ emit connectRequested(); });
m_trayActionDisconnect = m_menu.addAction(tr("Disconnect"), this, [this](){ emit disconnectRequested(); });
m_menu.addSeparator();
m_trayActionVisitWebSite = m_menu.addAction(QIcon(":/images/tray/link.png"), 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,
this,
[&](){ qApp->quit(); });
m_systemTrayIcon.setContextMenu(&m_menu);
setTrayState(Vpn::ConnectionState::Disconnected);
}
SystemTrayNotificationHandler::~SystemTrayNotificationHandler() {
}
void SystemTrayNotificationHandler::setConnectionState(Vpn::ConnectionState state)
{
setTrayState(state);
NotificationHandler::setConnectionState(state);
}
void SystemTrayNotificationHandler::onTranslationsUpdated()
{
m_trayActionShow->setText(tr("Show") + " " + APPLICATION_NAME);
m_trayActionConnect->setText(tr("Connect"));
m_trayActionDisconnect->setText(tr("Disconnect"));
m_trayActionVisitWebSite->setText(tr("Visit Website"));
m_trayActionQuit->setText(tr("Quit")+ " " + APPLICATION_NAME);
}
void SystemTrayNotificationHandler::updateWebsiteUrl(const QString &newWebsiteUrl) {
qDebug() << "Updated website URL:" << newWebsiteUrl;
websiteUrl = newWebsiteUrl;
}
void SystemTrayNotificationHandler::setTrayIcon(const QString &iconPath)
{
QIcon trayIconMask(QPixmap(iconPath).scaled(128,128));
#ifndef Q_OS_MAC
trayIconMask.setIsMask(true);
#endif
m_systemTrayIcon.setIcon(trayIconMask);
}
void SystemTrayNotificationHandler::onTrayActivated(QSystemTrayIcon::ActivationReason reason)
{
#ifndef Q_OS_MAC
if(reason == QSystemTrayIcon::DoubleClick || reason == QSystemTrayIcon::Trigger) {
emit raiseRequested();
}
#endif
}
void SystemTrayNotificationHandler::setTrayState(Vpn::ConnectionState state)
{
QString resourcesPath = ":/images/tray/%1";
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;
case Vpn::ConnectionState::Unknown:
default:
m_trayActionConnect->setEnabled(false);
m_trayActionDisconnect->setEnabled(true);
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
}
//#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
}
void SystemTrayNotificationHandler::notify(NotificationHandler::Message type,
const QString& title,
const QString& message,
int timerMsec) {
Q_UNUSED(type);
QIcon icon(ConnectedTrayIconName);
m_systemTrayIcon.showMessage(title, message, icon, 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
// }
}
@@ -0,0 +1,57 @@
/* 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 SYSTEMTRAYNOTIFICATIONHANDLER_H
#define SYSTEMTRAYNOTIFICATIONHANDLER_H
#include "notificationHandler.h"
#include <QMenu>
#include <QSystemTrayIcon>
class SystemTrayNotificationHandler : public NotificationHandler {
Q_OBJECT
public:
explicit SystemTrayNotificationHandler(QObject* parent);
~SystemTrayNotificationHandler();
void setConnectionState(Vpn::ConnectionState state) override;
void onTranslationsUpdated() override;
public slots:
void updateWebsiteUrl(const QString &newWebsiteUrl);
protected:
virtual 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);
private:
QMenu m_menu;
QSystemTrayIcon m_systemTrayIcon;
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_separator = nullptr;
const QString ConnectedTrayIconName = "active.png";
const QString DisconnectedTrayIconName = "default.png";
const QString ErrorTrayIconName = "error.png";
QString websiteUrl = "https://amnezia.org";
};
#endif // SYSTEMTRAYNOTIFICATIONHANDLER_H