diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0ca8e0752..1817c4047 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,7 +25,7 @@ jobs: host: 'linux' target: 'desktop' arch: 'gcc_64' - modules: 'qtremoteobjects qt5compat qtshadertools' + modules: 'qtremoteobjects qt5compat qtshadertools qtcharts' dir: ${{ runner.temp }} setup-python: 'true' tools: 'tools_ifw' @@ -93,7 +93,7 @@ jobs: host: 'windows' target: 'desktop' arch: 'win64_msvc2019_64' - modules: 'qtremoteobjects qt5compat qtshadertools' + modules: 'qtremoteobjects qt5compat qtshadertools qtcharts' dir: ${{ runner.temp }} setup-python: 'true' tools: 'tools_ifw' @@ -150,7 +150,7 @@ jobs: version: ${{ env.QT_VERSION }} host: 'mac' target: 'desktop' - modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia' + modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia qtcharts' arch: 'clang_64' dir: ${{ runner.temp }} set-env: 'true' @@ -162,7 +162,7 @@ jobs: version: ${{ env.QT_VERSION }} host: 'mac' target: 'ios' - modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia' + modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia qtcharts' dir: ${{ runner.temp }} setup-python: 'true' set-env: 'true' @@ -242,7 +242,7 @@ jobs: host: 'mac' target: 'desktop' arch: 'clang_64' - modules: 'qtremoteobjects qt5compat qtshadertools' + modules: 'qtremoteobjects qt5compat qtshadertools qtcharts' dir: ${{ runner.temp }} setup-python: 'true' set-env: 'true' @@ -292,7 +292,7 @@ jobs: env: ANDROID_BUILD_PLATFORM: android-34 QT_VERSION: 6.6.2 - QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' + QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools qtcharts' steps: - name: 'Install desktop Qt' diff --git a/README.md b/README.md index cfb311292..995d8a0d7 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Check deploy folder for build scripts. - Qt 5 Compatibility Module - Qt Shader Tools - Additional Libraries: + - Qt Charts - Qt Image Formats - Qt Multimedia - Qt Remote Objects diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 19351aeff..4fab95ef6 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -12,7 +12,7 @@ set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen") set(PACKAGES Core Gui Network Xml RemoteObjects Quick Svg QuickControls2 - Core5Compat Concurrent LinguistTools + Core5Compat Concurrent LinguistTools Charts ) execute_process( @@ -38,7 +38,7 @@ set(LIBS ${LIBS} Qt6::Core Qt6::Gui Qt6::Network Qt6::Xml Qt6::RemoteObjects Qt6::Quick Qt6::Svg Qt6::QuickControls2 - Qt6::Core5Compat Qt6::Concurrent + Qt6::Core5Compat Qt6::Concurrent Qt6::Charts ) if(IOS) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 910ae2146..300097953 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -195,8 +195,8 @@ void AmneziaApplication::init() // /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46 // So we catch all the copies to the clipboard and clear them from "text/html" #ifdef Q_OS_ANDROID - connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() { - auto clipboard = QGuiApplication::clipboard(); + connect(QApplication::clipboard(), &QClipboard::dataChanged, []() { + auto clipboard = QApplication::clipboard(); if (clipboard->mimeData()->hasHtml()) { clipboard->setText(clipboard->text()); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index d260cd47f..81a103b92 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -5,11 +5,7 @@ #include #include #include -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #include -#else - #include -#endif +#include #include "settings.h" #include "vpnconnection.h" @@ -47,7 +43,7 @@ #define amnApp (static_cast(QCoreApplication::instance())) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #define AMNEZIA_BASE_CLASS QGuiApplication + #define AMNEZIA_BASE_CLASS QApplication #else #define AMNEZIA_BASE_CLASS SingleApplication #define QAPPLICATION_CLASS QApplication diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index c9063f221..1e60e5f90 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -72,7 +72,7 @@ class AmneziaActivity : QtActivity() { object : Handler(Looper.getMainLooper()) { override fun handleMessage(msg: Message) { val event = msg.extractIpcMessage() - Log.d(TAG, "Handle event: $event") + if (event != ServiceEvent.STATISTICS_UPDATE) Log.d(TAG, "Handle event: $event") when (event) { ServiceEvent.STATUS_CHANGED -> { msg.data?.getStatus()?.let { (state) -> diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 3e237e9cc..c2e6a4d4e 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -528,7 +528,6 @@ bool Daemon::switchServer(const InterfaceConfig& config) { QJsonObject Daemon::getStatus() { Q_ASSERT(wgutils() != nullptr); QJsonObject json; - logger.debug() << "Status request"; if (!wgutils()->interfaceExists() || m_connections.isEmpty()) { json.insert("connected", QJsonValue(false)); diff --git a/client/daemon/daemonlocalserverconnection.cpp b/client/daemon/daemonlocalserverconnection.cpp index 1a49b7e5d..ed44d482c 100644 --- a/client/daemon/daemonlocalserverconnection.cpp +++ b/client/daemon/daemonlocalserverconnection.cpp @@ -46,8 +46,6 @@ DaemonLocalServerConnection::~DaemonLocalServerConnection() { } void DaemonLocalServerConnection::readData() { - logger.debug() << "Read Data"; - Q_ASSERT(m_socket); while (true) { @@ -90,8 +88,6 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { } QString type = typeValue.toString(); - logger.debug() << "Command received:" << type; - if (type == "activate") { InterfaceConfig config; if (!Daemon::parseConfig(obj, config)) { diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 0502facc5..494cb1b82 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -273,8 +273,6 @@ void LocalSocketController::deactivate() { } void LocalSocketController::checkStatus() { - logger.debug() << "Check status"; - if (m_daemonState == eReady || m_daemonState == eInitializing) { Q_ASSERT(m_socket); @@ -324,7 +322,6 @@ void LocalSocketController::cleanupBackendLogs() { } void LocalSocketController::readData() { - logger.debug() << "Reading"; Q_ASSERT(m_socket); Q_ASSERT(m_daemonState == eInitializing || m_daemonState == eReady); @@ -366,8 +363,6 @@ void LocalSocketController::parseCommand(const QByteArray& command) { } QString type = typeValue.toString(); - logger.debug() << "Parse command:" << type; - if (m_daemonState == eInitializing && type == "status") { m_daemonState = eReady; @@ -393,6 +388,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) { } emit initialized(true, connected.toBool(), datetime); + checkStatus(); return; } diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index 61b2e261d..a574c0546 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -20,9 +20,20 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject * }); connect(m_impl.get(), &ControllerImpl::disconnected, this, [this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }); + + connect(m_impl.get(), &ControllerImpl::statusUpdated, this, + &WireguardProtocol::statusUpdated); + m_impl->initialize(nullptr, nullptr); } +void WireguardProtocol::statusUpdated(const QString& serverIpv4Gateway, const QString& deviceIpv4Address, + uint64_t txBytes, uint64_t rxBytes) { + setBytesChanged(rxBytes, txBytes); + QThread::msleep(1000); + m_impl->checkStatus(); +} + WireguardProtocol::~WireguardProtocol() { WireguardProtocol::stop(); diff --git a/client/protocols/wireguardprotocol.h b/client/protocols/wireguardprotocol.h index 6d1a05187..e0e98c801 100644 --- a/client/protocols/wireguardprotocol.h +++ b/client/protocols/wireguardprotocol.h @@ -21,7 +21,8 @@ class WireguardProtocol : public VpnProtocol ErrorCode start() override; void stop() override; - + void statusUpdated(const QString& serverIpv4Gateway, const QString& deviceIpv4Address, + uint64_t txBytes, uint64_t rxBytes); ErrorCode startMzImpl(); ErrorCode stopMzImpl(); diff --git a/client/resources.qrc b/client/resources.qrc index 49fd66d35..941ff68bb 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -225,6 +225,7 @@ ui/qml/Pages2/PageShareFullAccess.qml images/controls/close.svg images/controls/search.svg + ui/qml/Controls2/GraphViewType.qml server_scripts/xray/configure_container.sh server_scripts/xray/Dockerfile server_scripts/xray/run_container.sh diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index a72ff06b8..ab5f2af2d 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -7,10 +7,10 @@ #endif #include -#include "utilities.h" #include "core/controllers/apiController.h" #include "core/controllers/vpnConfigurationController.h" #include "core/errorstrings.h" +#include "utilities.h" #include "version.h" ConnectionController::ConnectionController(const QSharedPointer &serversModel, @@ -29,14 +29,30 @@ ConnectionController::ConnectionController(const QSharedPointer &s connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); + connect(m_vpnConnection.get(), &VpnConnection::bytesChanged, this, [this](quint64 rx, quint64 tx) { + m_rxBytes = rx; + m_txBytes = tx; + }); + connect(&m_tick, &QTimer::timeout, this, [this]() { + quint64 time = QDateTime::currentSecsSinceEpoch(); + if (m_times.length() > viewSize) { + m_times.removeFirst(); + m_rxView.removeFirst(); + m_txView.removeFirst(); + } + m_times.append(time); + m_rxView.append(m_rxBytes); + m_txView.append(m_txBytes); + emit bytesChanged(); + }); + m_state = Vpn::ConnectionState::Disconnected; } void ConnectionController::openConnection() { #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) - { + if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) { emit connectionErrorOccurred(errorString(ErrorCode::AmneziaServiceNotRunning)); return; } @@ -124,6 +140,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) m_isConnectionInProgress = false; m_isConnected = true; m_connectionStateText = tr("Connected"); + m_tick.start(1000); break; } case Vpn::ConnectionState::Connecting: { @@ -143,6 +160,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) case Vpn::ConnectionState::Disconnecting: { m_isConnectionInProgress = true; m_connectionStateText = tr("Disconnecting..."); + m_tick.stop(); break; } case Vpn::ConnectionState::Preparing: { @@ -192,6 +210,30 @@ QString ConnectionController::connectionStateText() const return m_connectionStateText; } +quint64 ConnectionController::rxBytes() const +{ + return m_rxBytes; +} + +quint64 ConnectionController::txBytes() const +{ + return m_txBytes; +} + +QVector ConnectionController::getRxView() const +{ + return m_rxView; +} + +QVector ConnectionController::getTxView() const +{ + return m_txView; +} + +QVector ConnectionController::getTimes() const +{ + return m_times; +} void ConnectionController::toggleConnection() { if (m_state == Vpn::ConnectionState::Preparing) { diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 7c6dd9694..ef2b4bf7e 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -15,6 +15,8 @@ class ConnectionController : public QObject Q_PROPERTY(bool isConnected READ isConnected NOTIFY connectionStateChanged) Q_PROPERTY(bool isConnectionInProgress READ isConnectionInProgress NOTIFY connectionStateChanged) Q_PROPERTY(QString connectionStateText READ connectionStateText NOTIFY connectionStateChanged) + Q_PROPERTY(quint64 rxBytes READ rxBytes NOTIFY bytesChanged) + Q_PROPERTY(quint64 txBytes READ txBytes NOTIFY bytesChanged) explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const QSharedPointer &clientManagementModel, @@ -26,6 +28,12 @@ class ConnectionController : public QObject bool isConnected() const; bool isConnectionInProgress() const; QString connectionStateText() const; + quint64 rxBytes() const; + quint64 txBytes() const; + + Q_INVOKABLE QVector getRxView() const; + Q_INVOKABLE QVector getTxView() const; + Q_INVOKABLE QVector getTimes() const; public slots: void toggleConnection(); @@ -50,6 +58,7 @@ public slots: void connectionErrorOccurred(const QString &errorMessage); void reconnectWithUpdatedContainer(const QString &message); + void bytesChanged(); void noInstalledContainers(); @@ -71,8 +80,17 @@ public slots: bool m_isConnected = false; bool m_isConnectionInProgress = false; QString m_connectionStateText = tr("Connect"); + quint64 m_rxBytes = 0; + quint64 m_txBytes = 0; + QVector m_rxView{}; + QVector m_txView{}; + QVector m_times{}; + + QTimer m_tick{}; Vpn::ConnectionState m_state; + + const static quint8 viewSize{60}; }; #endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index ae5230900..9cd249d29 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -24,6 +24,20 @@ SystemController::SystemController(const std::shared_ptr &settings, QO { } +void SystemController::setAppHasFocus(bool appHasFocus) +{ + if (m_appHasFocus != appHasFocus) + { + m_appHasFocus = appHasFocus; + emit appHasFocusChanged(); + } +} + +bool SystemController::appHasFocus() const +{ + return m_appHasFocus; +} + void SystemController::saveFile(QString fileName, const QString &data) { #if defined Q_OS_ANDROID diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h index 274df2349..3a071ef8b 100644 --- a/client/ui/controllers/systemController.h +++ b/client/ui/controllers/systemController.h @@ -8,9 +8,13 @@ class SystemController : public QObject { Q_OBJECT + Q_PROPERTY(bool appHasFocus READ appHasFocus WRITE setAppHasFocus NOTIFY appHasFocusChanged) public: explicit SystemController(const std::shared_ptr &setting, QObject *parent = nullptr); + void setAppHasFocus(bool isActive); + bool appHasFocus() const; + static void saveFile(QString fileName, const QString &data); public slots: @@ -21,11 +25,13 @@ public slots: signals: void fileDialogClosed(const bool isAccepted); + void appHasFocusChanged(); private: std::shared_ptr m_settings; QObject *m_qmlRoot; + bool m_appHasFocus{false}; }; #endif // SYSTEMCONTROLLER_H diff --git a/client/ui/qml/Controls2/GraphViewType.qml b/client/ui/qml/Controls2/GraphViewType.qml new file mode 100644 index 000000000..5d2e49924 --- /dev/null +++ b/client/ui/qml/Controls2/GraphViewType.qml @@ -0,0 +1,125 @@ +import QtQuick +import QtCharts + +ChartView { + id: chartView + legend.visible: false + animationOptions: ChartView.AllAnimations + animationDuration: 2000.0 + + backgroundColor: "#1C1D21" + plotAreaColor: "#1C1D21" + margins.top: 0 + margins.bottom: 0 + margins.left: 0 + margins.right: 0 + antialiasing: true + enabled: false + + property bool shouldUpdate: SystemController.appHasFocus + + function getUTCSeconds() { + return new Date().setMilliseconds(0) / 1000 + } + + function addValues(rx, tx) { + let currentTime = getUTCSeconds() + + xAxis.min = currentTime - 60 + xAxis.max = currentTime + + if (rx > yAxis.max) yAxis.max = rx * 1.1 + if (tx > yAxis.max) yAxis.max = tx * 1.1 + + rxLine.append(currentTime, rx) + txLine.append(currentTime, tx) + } + + function printAll() { + var rxValues = ConnectionController.getRxView() + var txValues = ConnectionController.getTxView() + var times = ConnectionController.getTimes() + + let currentTime = getUTCSeconds() + xAxis.min = currentTime - 60 + xAxis.max = currentTime + + + rxLine.clear() + txLine.clear() + + if (times.length === 0) return + + xAxis.min = times[0] + xAxis.max = times[times.length - 1] + + for (let i = 0; i < times.length; i++) + { + if (rxValues[i] > yAxis.max) yAxis.max = rxValues[i] + if (txValues[i] > yAxis.max) yAxis.max = txValues[i] + + rxLine.append(times[i], rxValues[i]) + txLine.append(times[i], txValues[i]) + } + } + + Component.onCompleted: { + printAll() + } + + Connections { + target: ConnectionController + function onBytesChanged() { + if (shouldUpdate) { + addValues(ConnectionController.rxBytes, ConnectionController.txBytes) + } + } + } + + Connections { + target: SystemController + function onAppHasFocusChanged() { + if (shouldUpdate) { printAll() } + } + } + + ValueAxis { + id: yAxis + min: -100 + max: 1000 + visible: false + labelsVisible: false + gridLineColor: "transparent" + } + + ValueAxis { + id: xAxis + visible: false + labelsVisible: false + gridLineColor: "transparent" + } + + SplineSeries { + id: rxLine + name: "Received Bytes" + //width: 2 + axisX: xAxis + axisY: yAxis + capStyle: Qt.RoundCap + useOpenGL: true + color: "#70553c" + XYPoint { x: getUTCSeconds(); y: 0 } + } + + SplineSeries { + id: txLine + name: "Transmitted Bytes" + //width: 2 + axisX: xAxis + axisY: yAxis + capStyle: Qt.RoundCap + useOpenGL: true + color: "#737274" + XYPoint { x: getUTCSeconds(); y: 0 } + } +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 21098cb23..1aad2fff6 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -261,10 +261,15 @@ PageType { LabelTextType { id: collapsedServerMenuDescription - Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 89 : 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter text: drawer.isCollapsed ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded } + + GraphViewType { + Layout.minimumHeight: 50 + Layout.bottomMargin: drawer.isCollapsed ? 24 : ServersModel.isDefaultServerFromApi ? 69 : 24 + Layout.fillWidth: true + } } Connections { diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 7e31bb09d..3fbc73167 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -28,6 +28,10 @@ Window { PageController.closeWindow() } + onActiveChanged: { + SystemController.appHasFocus = active + } + title: "AmneziaVPN" StackViewType {