From a5e9cea22f21561ad118907933457a7ac1362aec Mon Sep 17 00:00:00 2001 From: pokamest Date: Tue, 20 Apr 2021 02:09:47 +0300 Subject: [PATCH] Release 1.6 WIP --- client/client.pro | 5 +- client/configurators/openvpn_configurator.cpp | 2 +- client/core/defs.h | 37 +- client/core/scripts_registry.cpp | 2 +- client/core/server_defs.cpp | 2 +- client/core/server_defs.h | 6 +- client/core/servercontroller.cpp | 16 +- client/defines.h | 2 +- client/images/check.png | Bin 0 -> 13735 bytes client/images/plus.png | Bin 0 -> 215 bytes client/images/settings.png | Bin 871 -> 846 bytes client/images/settings_grey.png | Bin 0 -> 871 bytes client/images/uncheck.png | Bin 0 -> 10362 bytes client/protocols/openvpnovercloakprotocol.cpp | 2 +- client/protocols/openvpnprotocol.cpp | 8 +- client/protocols/shadowsocksvpnprotocol.cpp | 2 +- client/protocols/shadowsocksvpnprotocol.h | 2 +- client/resources.qrc | 4 + client/settings.cpp | 111 +- client/settings.h | 72 +- client/ui/Controls/SlidingStackedWidget.cpp | 41 +- client/ui/Controls/SlidingStackedWidget.h | 6 + client/ui/mainwindow.cpp | 202 ++- client/ui/mainwindow.h | 14 +- client/ui/mainwindow.ui | 1413 ++++++++++++++++- client/ui/server_widget.cpp | 30 + client/ui/server_widget.h | 22 + client/ui/server_widget.ui | 167 ++ client/vpnconnection.cpp | 14 +- 29 files changed, 2046 insertions(+), 136 deletions(-) create mode 100644 client/images/check.png create mode 100644 client/images/plus.png create mode 100644 client/images/settings_grey.png create mode 100644 client/images/uncheck.png create mode 100644 client/ui/server_widget.cpp create mode 100644 client/ui/server_widget.h create mode 100644 client/ui/server_widget.ui diff --git a/client/client.pro b/client/client.pro index e56f69fa5..1c027a043 100644 --- a/client/client.pro +++ b/client/client.pro @@ -31,6 +31,7 @@ HEADERS += \ ui/Controls/SlidingStackedWidget.h \ ui/mainwindow.h \ ui/qautostart.h \ + ui/server_widget.h \ utils.h \ vpnconnection.h \ protocols/vpnprotocol.h \ @@ -52,12 +53,14 @@ SOURCES += \ ui/Controls/SlidingStackedWidget.cpp \ ui/mainwindow.cpp \ ui/qautostart.cpp \ + ui/server_widget.cpp \ utils.cpp \ vpnconnection.cpp \ protocols/vpnprotocol.cpp \ protocols/openvpnprotocol.cpp \ -FORMS += ui/mainwindow.ui +FORMS += ui/mainwindow.ui \ + ui/server_widget.ui RESOURCES += \ resources.qrc diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index b9e0c0d23..89cf2c6d0 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -215,7 +215,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia if (proto == Protocol::OpenVpn) config.replace("$PROTO", "udp"); - else if (proto == Protocol::ShadowSocks) { + else if (proto == Protocol::ShadowSocksOverOpenVpn) { config.replace("$PROTO", "tcp"); config.replace("$LOCAL_PROXY_PORT", QString::number(amnezia::protocols::shadowsocks::ssContainerPort())); } diff --git a/client/core/defs.h b/client/core/defs.h index d90947b40..9b6f85b8a 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -1,25 +1,48 @@ #ifndef DEFS_H #define DEFS_H +#include #include namespace amnezia { +Q_NAMESPACE enum class Protocol { Any, OpenVpn, - ShadowSocks, + ShadowSocksOverOpenVpn, OpenVpnOverCloak, WireGuard }; +Q_ENUM_NS(Protocol) + +inline Protocol protoFromString(QString proto){ + auto&& metaEnum = QMetaEnum::fromType(); + return static_cast(metaEnum.keyToValue(proto.toStdString().c_str())); +} + +inline QString protoToString(Protocol proto){ + return QVariant::fromValue(proto).toString(); +} + enum class DockerContainer { None, OpenVpn, - ShadowSocks, + ShadowSocksOverOpenVpn, OpenVpnOverCloak, WireGuard }; +Q_ENUM_NS(DockerContainer) + +inline DockerContainer containerFromString(QString container){ + auto&& metaEnum = QMetaEnum::fromType(); + return static_cast(metaEnum.keyToValue(container.toStdString().c_str())); +} + +inline QString containerToString(DockerContainer container){ + return QVariant::fromValue(container).toString(); +} static DockerContainer containerForProto(Protocol proto) { @@ -28,7 +51,7 @@ static DockerContainer containerForProto(Protocol proto) switch (proto) { case Protocol::OpenVpn: return DockerContainer::OpenVpn; case Protocol::OpenVpnOverCloak: return DockerContainer::OpenVpnOverCloak; - case Protocol::ShadowSocks: return DockerContainer::ShadowSocks; + case Protocol::ShadowSocksOverOpenVpn: return DockerContainer::ShadowSocksOverOpenVpn; case Protocol::WireGuard: return DockerContainer::WireGuard; case Protocol::Any: return DockerContainer::None; } @@ -91,10 +114,10 @@ enum ErrorCode namespace config { // config keys -static QString key_openvpn_config_data() { return "openvpn_config_data"; } -static QString key_openvpn_config_path() { return "openvpn_config_path"; } -static QString key_shadowsocks_config_data() { return "shadowsocks_config_data"; } -static QString key_cloak_config_data() { return "cloak_config_data"; } +const char key_openvpn_config_data[] = "openvpn_config_data"; +const char key_openvpn_config_path[] = "openvpn_config_path"; +const char key_shadowsocks_config_data[] = "shadowsocks_config_data"; +const char key_cloak_config_data[] = "cloak_config_data"; } diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 5496ecb81..dc01a44ee 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -9,7 +9,7 @@ QString amnezia::scriptFolder(amnezia::Protocol proto) switch (proto) { case Protocol::OpenVpn: return QLatin1String("openvpn"); case Protocol::OpenVpnOverCloak: return QLatin1String("openvpn_cloak"); - case Protocol::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); + case Protocol::ShadowSocksOverOpenVpn: return QLatin1String("openvpn_shadowsocks"); case Protocol::WireGuard: return QLatin1String("wireguard"); default: return ""; } diff --git a/client/core/server_defs.cpp b/client/core/server_defs.cpp index eea22a5a6..a2f64133a 100644 --- a/client/core/server_defs.cpp +++ b/client/core/server_defs.cpp @@ -5,7 +5,7 @@ QString amnezia::server::getContainerName(amnezia::DockerContainer container) switch (container) { case(DockerContainer::OpenVpn): return "amnezia-openvpn"; case(DockerContainer::OpenVpnOverCloak): return "amnezia-openvpn-cloak"; - case(DockerContainer::ShadowSocks): return "amnezia-shadowsocks"; + case(DockerContainer::ShadowSocksOverOpenVpn): return "amnezia-shadowsocks"; default: return ""; } } diff --git a/client/core/server_defs.h b/client/core/server_defs.h index 2475771bd..99865a02f 100644 --- a/client/core/server_defs.h +++ b/client/core/server_defs.h @@ -9,9 +9,9 @@ namespace server { QString getContainerName(amnezia::DockerContainer container); QString getDockerfileFolder(amnezia::DockerContainer container); -static QString vpnDefaultSubnetIp() { return "10.8.0.0"; } -static QString vpnDefaultSubnetMask() { return "255.255.255.0"; } -static QString vpnDefaultSubnetMaskVal() { return "24"; } +const char vpnDefaultSubnetIp[] = "10.8.0.0"; +const char vpnDefaultSubnetMask[] = "255.255.255.0"; +const char vpnDefaultSubnetMaskVal[] = "24"; } } diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index befff7b8b..ce4f8dd96 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -392,15 +392,15 @@ ErrorCode ServerController::removeServer(const ServerCredentials &credentials, P if (e) { return e; } - return removeServer(credentials, Protocol::ShadowSocks); + return removeServer(credentials, Protocol::ShadowSocksOverOpenVpn); } else if (proto == Protocol::OpenVpn) { scriptFileName = ":/server_scripts/remove_container.sh"; container = DockerContainer::OpenVpn; } - else if (proto == Protocol::ShadowSocks) { + else if (proto == Protocol::ShadowSocksOverOpenVpn) { scriptFileName = ":/server_scripts/remove_container.sh"; - container = DockerContainer::ShadowSocks; + container = DockerContainer::ShadowSocksOverOpenVpn; } else return ErrorCode::NotImplementedError; @@ -427,7 +427,7 @@ ErrorCode ServerController::setupServer(const ServerCredentials &credentials, Pr return ErrorCode::NoError; //return setupOpenVpnServer(credentials); } - else if (proto == Protocol::ShadowSocks) { + else if (proto == Protocol::ShadowSocksOverOpenVpn) { return setupShadowSocksServer(credentials); } else if (proto == Protocol::Any) { @@ -586,9 +586,9 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential { Vars vars; - vars.append(qMakePair("$VPN_SUBNET_IP", amnezia::server::vpnDefaultSubnetIp())); - vars.append(qMakePair("$VPN_SUBNET_MASK_VAL", amnezia::server::vpnDefaultSubnetMaskVal())); - vars.append(qMakePair("$VPN_SUBNET_MASK", amnezia::server::vpnDefaultSubnetMask())); + vars.append(qMakePair("$VPN_SUBNET_IP", amnezia::server::vpnDefaultSubnetIp)); + vars.append(qMakePair("$VPN_SUBNET_MASK_VAL", amnezia::server::vpnDefaultSubnetMaskVal)); + vars.append(qMakePair("$VPN_SUBNET_MASK", amnezia::server::vpnDefaultSubnetMask)); vars.append(qMakePair("$CONTAINER_NAME", amnezia::server::getContainerName(container))); vars.append(qMakePair("$DOCKERFILE_FOLDER", "/opt/amnezia/" + amnezia::server::getContainerName(container))); @@ -610,7 +610,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append(qMakePair("$SERVER_PORT", amnezia::protocols::cloak::ckDefaultPort())); vars.append(qMakePair("$FAKE_WEB_SITE_ADDRESS", amnezia::protocols::cloak::ckDefaultRedirSite())); } - else if (container == DockerContainer::ShadowSocks) { + else if (container == DockerContainer::ShadowSocksOverOpenVpn) { vars.append(qMakePair("$SERVER_PORT", "6789")); } diff --git a/client/defines.h b/client/defines.h index 4f244d905..e106f0033 100644 --- a/client/defines.h +++ b/client/defines.h @@ -4,6 +4,6 @@ #define APPLICATION_NAME "AmneziaVPN" #define SERVICE_NAME "AmneziaVPN-service" #define ORGANIZATION_NAME "AmneziaVPN.ORG" -#define APP_VERSION "1.0.0.0" +#define APP_VERSION "1.6.0.0" #endif // DEFINES_H diff --git a/client/images/check.png b/client/images/check.png new file mode 100644 index 0000000000000000000000000000000000000000..558bed5d07e9b048e815ec560d4d02d8d1bf1c0a GIT binary patch literal 13735 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 literal 0 HcmV?d00001 diff --git a/client/images/plus.png b/client/images/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..40a238e06e511eacb37e38d4429dfc9389d42f41 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJd7dtgArY-_r&#kfCaU7?Vdp1CnC?ONp>Cyw^;oT_F>TqxEq`PqvJ P=u!qxS3j3^P6BJZal>nd;fRRuMIxz+=#?l-f4*)3IG5N1e3=kS&Rr-1t0l-YC1Y(V_ zggP-Q1G;KkM-?r3eb+3bROw8h_v*?T?_AT8ZjmE6$y($LP9d$C zTMB^A!G9;v+qUM_(DMia0F5U+ub9LA7eBsm)s*cpWOkC~-u)ig8gd*R(Iq8202%-` zLM0q(N@gF^QdjpF^kNixF%EZV0szW+ACr4nUK34fZ4(4R5E^^ZOw8V!dlp(<*MoH1 zT5hPv0x0H|m0_;{0QObb6Y_zus5w{(l~C0@B!4fv{l4%ts$5CD{kkPQ*_PjY|7>z4 z;cQQ_f@)hwL8ye68wjWYXxX|xH4UR9p%`l2iCIQH5PQ9*9C|df;w71Z`o*)ljsZT9 z`cshiQzlR&2!g=1!WKBc!~sR{a}&~Ty)4RHf2@y2$3UiU5cJDFSc= zh@$x1Y$8}T0<#fF87xPjDA@Nzk30w$XuE1^M%~lDH$6SFRDVWVEEa?F`P}2$w(X$V zZ1%J0Sy?tMm&>6xuIY3-`JGP3d!(C7Gsfd_F_+6#F``zh6^XG|%Zg*E$hPq5>8Xp^ z}X`JLZTj`z>!^S|5!nG3jng5_u%$h6;X zw@3Qh)(c=T^Vw@VHcayoV?hu$0a;?NrsSg}wjRBs?c1`eiQ*v!alX)GU+An+;hRv^V(>PPd z($Uq0?>g3>;#fOaiSu_>8ShT)fX3COp37ojg2)1`2;NZhanL2q6&RaH#5y2#I2=ag zi%tylY&P>OUDM#dz`(|J-E9h$_l-681x;eQgG+B zvr1r7+e(L3(x=K8FY59>@SB&f#K7d{mcQ!+nI3)K3%*n-b^_TwvQ-9>zXx9+V1$AB b!rh~PvuO(QDWDC&00000NkvXXu0mjfgPfCH diff --git a/client/images/settings_grey.png b/client/images/settings_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..60127b5cd52f31da16a02e43e7787a28fb59e20d GIT binary patch literal 871 zcmV-t1DO1YP)UiU5cJDFSc=h@$x1Y$8}T0<#fF87xPjDA@Nzk30w$ zXuE1^M%~lDH$6SFR7P4X7K8Ko+~eA|?V#Cg_Os|&SvD<~%b_-|>2x~zoleJlq?=1K z#^Z4@m&;W#qE@RFiLqD9iestBw(#lcsf*d;$8dO$qn6$Wo(2;5l5ABfafUl46DIWRgz zWWG2Wh$_l-681x z;T;mVgT%33NWJ$YLRbAKvGao(m5Pk4s=0{#3Rd_L5z07jFWD-^(ptBqswq)l@_okT`QzHN4L2jcRSRzTy7 zsG+M!_j$?%mOlHJG{~yFAy+->=1rEfU{l*lhgH(2$`~)|@;>mJm#@UYjaq| xeclVcR4H}>**&sV29m!AUm#$Ff%(GSqkpq$3i2tS4Zr{Z002ovPDHLkV1h%~lS}{r literal 0 HcmV?d00001 diff --git a/client/images/uncheck.png b/client/images/uncheck.png new file mode 100644 index 0000000000000000000000000000000000000000..233843c5b0c24a17ed8265e026c4201226bd16b8 GIT binary patch literal 10362 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 N_%kP8oN&7L{{Rs~*;W7m literal 0 HcmV?d00001 diff --git a/client/protocols/openvpnovercloakprotocol.cpp b/client/protocols/openvpnovercloakprotocol.cpp index 314706d6c..3338b0ac3 100644 --- a/client/protocols/openvpnovercloakprotocol.cpp +++ b/client/protocols/openvpnovercloakprotocol.cpp @@ -101,5 +101,5 @@ QString OpenVpnOverCloakProtocol::cloakExecPath() void OpenVpnOverCloakProtocol::readCloakConfiguration(const QJsonObject &configuration) { - m_cloakConfig = configuration.value(config::key_cloak_config_data()).toObject(); + m_cloakConfig = configuration.value(config::key_cloak_config_data).toObject(); } diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index c3fddebe9..ca6731702 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -68,16 +68,16 @@ void OpenVpnProtocol::killOpenVpnProcess() void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration) { - if (configuration.contains(config::key_openvpn_config_data())) { + if (configuration.contains(config::key_openvpn_config_data)) { m_configFile.open(); - m_configFile.write(configuration.value(config::key_openvpn_config_data()).toString().toUtf8()); + m_configFile.write(configuration.value(config::key_openvpn_config_data).toString().toUtf8()); m_configFile.close(); m_configFileName = m_configFile.fileName(); qDebug().noquote() << QString("Set config data") << m_configFileName; } - else if (configuration.contains(config::key_openvpn_config_path())) { - m_configFileName = configuration.value(config::key_openvpn_config_path()).toString(); + else if (configuration.contains(config::key_openvpn_config_path)) { + m_configFileName = configuration.value(config::key_openvpn_config_path).toString(); QFileInfo file(m_configFileName); if (file.fileName().isEmpty()) { diff --git a/client/protocols/shadowsocksvpnprotocol.cpp b/client/protocols/shadowsocksvpnprotocol.cpp index 83f660097..74e0ec4d5 100644 --- a/client/protocols/shadowsocksvpnprotocol.cpp +++ b/client/protocols/shadowsocksvpnprotocol.cpp @@ -110,5 +110,5 @@ QJsonObject ShadowSocksVpnProtocol::genShadowSocksConfig(const ServerCredentials void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration) { - m_shadowSocksConfig = configuration.value(config::key_shadowsocks_config_data()).toObject(); + m_shadowSocksConfig = configuration.value(config::key_shadowsocks_config_data).toObject(); } diff --git a/client/protocols/shadowsocksvpnprotocol.h b/client/protocols/shadowsocksvpnprotocol.h index 86ad40b3b..a7545a58f 100644 --- a/client/protocols/shadowsocksvpnprotocol.h +++ b/client/protocols/shadowsocksvpnprotocol.h @@ -13,7 +13,7 @@ public: ErrorCode start() override; void stop() override; - static QJsonObject genShadowSocksConfig(const ServerCredentials &credentials, Protocol proto = Protocol::ShadowSocks); + static QJsonObject genShadowSocksConfig(const ServerCredentials &credentials, Protocol proto = Protocol::ShadowSocksOverOpenVpn); protected: void readShadowSocksConfiguration(const QJsonObject &configuration); diff --git a/client/resources.qrc b/client/resources.qrc index 18045ed47..6e87c0449 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -48,5 +48,9 @@ server_scripts/install_docker.sh server_scripts/build_container.sh server_scripts/prepare_host.sh + images/check.png + images/uncheck.png + images/settings_grey.png + images/plus.png diff --git a/client/settings.cpp b/client/settings.cpp index a7e12278b..27a9c9281 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -1,33 +1,116 @@ - #include "defines.h" #include "settings.h" +#include + Settings::Settings(QObject* parent) : QObject(parent), m_settings (ORGANIZATION_NAME, APPLICATION_NAME, this) { + // Import old settings + if (serversCount() == 0) { + QString user = m_settings.value("Server/userName").toString(); + QString password = m_settings.value("Server/password").toString(); + QString serverName = m_settings.value("Server/serverName").toString(); + int port = m_settings.value("Server/serverPort").toInt(); + + if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()){ + QJsonObject server; + server.insert(userNameString, user); + server.insert(passwordString, password); + server.insert(hostNameString, serverName); + server.insert(portString, port); + server.insert(descriptionString, tr("Server #1")); + + addServer(server); + } + } +} + +int Settings::serversCount() const +{ + return serversArray().size(); +} + +QJsonObject Settings::server(int index) const +{ + const QJsonArray &servers = serversArray(); + if (index >= servers.size()) return QJsonObject(); + + return servers.at(index).toObject(); +} + +void Settings::addServer(const QJsonObject &server) +{ + QJsonArray servers = serversArray(); + servers.append(server); + setServersArray(servers); +} + +void Settings::removeServer(int index) +{ + QJsonArray servers = serversArray(); + if (index >= servers.size()) return; + + servers.removeAt(index); + setServersArray(servers); +} + +bool Settings::editServer(int index, const QJsonObject &server) +{ + QJsonArray servers = serversArray(); + if (index >= servers.size()) return false; + + servers.replace(index, server); + setServersArray(servers); + return true; +} + +void Settings::setDefaultContainer(int serverIndex, DockerContainer container) +{ + QJsonObject s = server(serverIndex); + s.insert(defaultContainerString, containerToString(container)); + editServer(serverIndex, s); +} + +DockerContainer Settings::defaultContainer(int serverIndex) const +{ + return containerFromString(defaultContainerName(serverIndex)); +} + +QString Settings::defaultContainerName(int serverIndex) const +{ + QString name = server(serverIndex).value(defaultContainerString).toString(); + if (name.isEmpty()) { + return containerToString(DockerContainer::OpenVpnOverCloak); + } + else return name; } bool Settings::haveAuthData() const { - return (!serverName().isEmpty() && !userName().isEmpty() && !password().isEmpty()); + ServerCredentials cred = defaultServerCredentials(); + + return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.password.isEmpty()); } -void Settings::setServerCredentials(const ServerCredentials &credentials) -{ - setServerName(credentials.hostName); - setServerPort(credentials.port); - setUserName(credentials.userName); - setPassword(credentials.password); -} +//void Settings::setServerCredentials(const ServerCredentials &credentials) +//{ +// setServerName(credentials.hostName); +// setServerPort(credentials.port); +// setUserName(credentials.userName); +// setPassword(credentials.password); +//} -ServerCredentials Settings::serverCredentials() +ServerCredentials Settings::defaultServerCredentials() const { + const QJsonObject &s = defaultServer(); + ServerCredentials credentials; - credentials.hostName = serverName(); - credentials.userName = userName(); - credentials.password = password(); - credentials.port = serverPort(); + credentials.hostName = s.value(hostNameString).toString(); + credentials.userName = s.value(userNameString).toString(); + credentials.password = s.value(passwordString).toString(); + credentials.port = s.value(portString).toInt(); return credentials; } diff --git a/client/settings.h b/client/settings.h index a6dc1b66d..4fe5b1835 100644 --- a/client/settings.h +++ b/client/settings.h @@ -5,6 +5,10 @@ #include #include +#include +#include +#include + #include "core/defs.h" using namespace amnezia; @@ -18,22 +22,43 @@ class Settings : public QObject public: explicit Settings(QObject* parent = nullptr); - QString userName() const { return m_settings.value("Server/userName", QString()).toString(); } - void setUserName(const QString& login) { m_settings.setValue("Server/userName", login); } - QString password() const { return m_settings.value("Server/password", QString()).toString(); } - void setPassword(const QString& password) { m_settings.setValue("Server/password", password); } +// QString userName() const { return m_settings.value("Server/userName", QString()).toString(); } +// void setUserName(const QString& login) { m_settings.setValue("Server/userName", login); } - QString serverName() const { return m_settings.value("Server/serverName", QString()).toString(); } - void setServerName(const QString& serverName) { m_settings.setValue("Server/serverName", serverName); } +// QString password() const { return m_settings.value("Server/password", QString()).toString(); } +// void setPassword(const QString& password) { m_settings.setValue("Server/password", password); } - int serverPort() const { return m_settings.value("Server/serverPort", 22).toInt(); } - void setServerPort(int serverPort = 22) { m_settings.setValue("Server/serverPort", serverPort); } +// QString serverName() const { return m_settings.value("Server/serverName", QString()).toString(); } +// void setServerName(const QString& serverName) { m_settings.setValue("Server/serverName", serverName); } + +// int serverPort() const { return m_settings.value("Server/serverPort", 22).toInt(); } +// void setServerPort(int serverPort = 22) { m_settings.setValue("Server/serverPort", serverPort); } + + ServerCredentials defaultServerCredentials() const; + //void setServerCredentials(const ServerCredentials &credentials); + + QJsonArray serversArray() const {return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); } + void setServersArray(const QJsonArray &servers) { m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); } + + // Servers section + int serversCount() const; + QJsonObject server(int index) const; + void addServer(const QJsonObject &server); + void removeServer(int index); + bool editServer(int index, const QJsonObject &server); + + int defaultServerIndex() const { return m_settings.value("Servers/defaultServerIndex", 0).toInt(); } + void setDefaultServer(int index) { m_settings.setValue("Servers/defaultServerIndex", index); } + QJsonObject defaultServer() const { return server(defaultServerIndex()); } + + void setDefaultContainer(int serverIndex, DockerContainer container ); + DockerContainer defaultContainer(int serverIndex) const; + QString defaultContainerName(int serverIndex) const; - ServerCredentials serverCredentials(); - void setServerCredentials(const ServerCredentials &credentials); bool haveAuthData() const; + // App settings section bool isAutoConnect() const { return m_settings.value("Conf/autoConnect", QString()).toBool(); } void setAutoConnect(bool enabled) { m_settings.setValue("Conf/autoConnect", enabled); } @@ -48,8 +73,8 @@ public: QStringList customIps() { return m_settings.value("Conf/customIps").toStringList(); } void setCustomIps(const QStringList &customIps) { m_settings.setValue("Conf/customIps", customIps); } - QString primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1()).toString(); } - QString secondaryDns() const { return m_settings.value("Conf/secondaryDns", cloudFlareNs2()).toString(); } + QString primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); } + QString secondaryDns() const { return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); } //QString primaryDns() const { return m_primaryDns; } void setPrimaryDns(const QString &primaryDns) { m_settings.setValue("Conf/primaryDns", primaryDns); } @@ -57,14 +82,29 @@ public: //QString secondaryDns() const { return m_secondaryDns; } void setSecondaryDns(const QString &secondaryDns) { m_settings.setValue("Conf/secondaryDns", secondaryDns); } - QString cloudFlareNs1() const { return "1.1.1.1"; } - QString cloudFlareNs2() const { return "1.0.0.1"; } + static constexpr char cloudFlareNs1[] = "1.1.1.1"; + static constexpr char cloudFlareNs2[] = "1.0.0.1"; + + static constexpr char openNicNs5[] = "94.103.153.176"; + static constexpr char openNicNs13[] = "144.76.103.143"; + + +public: + // Json strings + static constexpr char hostNameString[] = "hostName"; + static constexpr char userNameString[] = "userName"; + static constexpr char passwordString[] = "password"; + static constexpr char portString[] = "port"; + static constexpr char descriptionString[] = "description"; + + static constexpr char defaultContainerString[] = "defaultContainer"; - QString openNicNs5() const { return "94.103.153.176"; } - QString openNicNs13() const { return "144.76.103.143"; } private: QSettings m_settings; + + + }; #endif // SETTINGS_H diff --git a/client/ui/Controls/SlidingStackedWidget.cpp b/client/ui/Controls/SlidingStackedWidget.cpp index 104de5228..ee370fe99 100644 --- a/client/ui/Controls/SlidingStackedWidget.cpp +++ b/client/ui/Controls/SlidingStackedWidget.cpp @@ -1,5 +1,7 @@ #include "SlidingStackedWidget.h" +#include + SlidingStackedWidget::SlidingStackedWidget(QWidget *parent) : QStackedWidget(parent) { @@ -28,6 +30,14 @@ SlidingStackedWidget::SlidingStackedWidget(QWidget *parent) m_wrap = false; m_pnow = QPoint(0,0); m_active = false; + + animnow = new QPropertyAnimation(); + animnext = new QPropertyAnimation(); + + animgroup = new QParallelAnimationGroup; + + animgroup->addAnimation(animnow); + animgroup->addAnimation(animnext); } SlidingStackedWidget::~SlidingStackedWidget() { @@ -90,6 +100,21 @@ void SlidingStackedWidget::slideInWidget(QWidget *widget, SlidingStackedWidget:: #endif } +bool SlidingStackedWidget::isAnimationRunning() +{ + return animgroup->state() == QAnimationGroup::Running; +} + +void SlidingStackedWidget::waitForAnimation() +{ + if (!isAnimationRunning()) return; + + qDebug() << "Wait for stacked widget animation"; + QEventLoop l; + connect(animgroup, &QParallelAnimationGroup::finished, &l, &QEventLoop::quit); + l.exec(); +} + void SlidingStackedWidget::slideInWgtImpl(QWidget * newwidget, enum t_direction direction) { if (m_active) { return; @@ -149,22 +174,28 @@ void SlidingStackedWidget::slideInWgtImpl(QWidget * newwidget, enum t_direction widget(next)->raise(); // animate both, the now and next widget to the side, using animation framework - QPropertyAnimation *animnow = new QPropertyAnimation(widget(now), "pos"); + //QPropertyAnimation *animnow = new QPropertyAnimation(widget(now), "pos"); + animnow->setTargetObject(widget(now)); + animnow->setPropertyName("pos"); + animnow->setDuration(m_speed); animnow->setEasingCurve(m_animationtype); animnow->setStartValue(QPoint(pnow.x(), pnow.y())); animnow->setEndValue(QPoint(offsetx + pnow.x(), offsety + pnow.y())); - QPropertyAnimation *animnext = new QPropertyAnimation(widget(next), "pos"); + //QPropertyAnimation *animnext = new QPropertyAnimation(widget(next), "pos"); + animnext->setTargetObject(widget(next)); + animnext->setPropertyName("pos"); + animnext->setDuration(m_speed); animnext->setEasingCurve(m_animationtype); animnext->setStartValue(QPoint(-offsetx + pnext.x(), offsety + pnext.y())); animnext->setEndValue(QPoint(pnext.x(), pnext.y())); - QParallelAnimationGroup *animgroup = new QParallelAnimationGroup; +// QParallelAnimationGroup *animgroup = new QParallelAnimationGroup; - animgroup->addAnimation(animnow); - animgroup->addAnimation(animnext); +// animgroup->addAnimation(animnow); +// animgroup->addAnimation(animnext); QObject::connect(animgroup, SIGNAL(finished()),this,SLOT(animationDoneSlot())); m_next = next; diff --git a/client/ui/Controls/SlidingStackedWidget.h b/client/ui/Controls/SlidingStackedWidget.h index 979e8f6ab..f07983061 100644 --- a/client/ui/Controls/SlidingStackedWidget.h +++ b/client/ui/Controls/SlidingStackedWidget.h @@ -47,6 +47,8 @@ public slots: void slideInIdx(int idx, enum t_direction direction = AUTOMATIC); void slideInWidget(QWidget *widget, enum t_direction direction = AUTOMATIC); + bool isAnimationRunning(); + void waitForAnimation(); signals: // this is used for internal purposes in the class engine void animationFinished(void); @@ -71,6 +73,10 @@ protected: bool m_active; QList blockedPageList; + + QPropertyAnimation *animnow; + QPropertyAnimation *animnext; + QParallelAnimationGroup *animgroup; }; #endif // SLIDINGSTACKEDWIDGET_H diff --git a/client/ui/mainwindow.cpp b/client/ui/mainwindow.cpp index 43637108b..23d6350f5 100644 --- a/client/ui/mainwindow.cpp +++ b/client/ui/mainwindow.cpp @@ -25,6 +25,8 @@ #include "ui_mainwindow.h" #include "utils.h" #include "vpnconnection.h" +#include "ui/server_widget.h" +#include "ui_server_widget.h" #ifdef Q_OS_MAC #include "ui/macos_util.h" @@ -43,8 +45,10 @@ MainWindow::MainWindow(QWidget *parent) : setupTray(); setupUiConnections(); + setupProtocolsPage(); ui->label_error_text->clear(); + installEventFilter(this); ui->widget_tittlebar->installEventFilter(this); ui->stackedWidget_main->setSpeed(200); @@ -67,11 +71,10 @@ MainWindow::MainWindow(QWidget *parent) : } // Post initialization + goToPage(Page::Start, true, false); if (m_settings.haveAuthData()) { goToPage(Page::Vpn, true, false); - } else { - goToPage(Page::Start, true, false); } connect(ui->lineEdit_sites_add_custom, &QLineEdit::returnPressed, [&](){ @@ -137,6 +140,7 @@ MainWindow::~MainWindow() void MainWindow::goToPage(Page page, bool reset, bool slide) { + qDebug() << "goToPage" << page; if (reset) { if (page == Page::NewServer) { ui->label_new_server_wait_info->hide(); @@ -148,13 +152,13 @@ void MainWindow::goToPage(Page page, bool reset, bool slide) if (page == Page::ServerSettings) { ui->label_server_settings_wait_info->hide(); ui->label_server_settings_wait_info->clear(); - ui->label_server_settings_server->setText(QString("%1@%2:%3") - .arg(m_settings.userName()) - .arg(m_settings.serverName()) - .arg(m_settings.serverPort())); +// ui->label_server_settings_server->setText(QString("%1@%2:%3") +// .arg(m_settings.userName()) +// .arg(m_settings.serverName()) +// .arg(m_settings.serverPort())); } if (page == Page::ShareConnection) { - QJsonObject ssConfig = ShadowSocksVpnProtocol::genShadowSocksConfig(m_settings.serverCredentials()); + QJsonObject ssConfig = ShadowSocksVpnProtocol::genShadowSocksConfig(m_settings.defaultServerCredentials()); QString ssString = QString("%1:%2@%3:%4") .arg(ssConfig.value("method").toString()) @@ -171,14 +175,29 @@ void MainWindow::goToPage(Page page, bool reset, bool slide) ui->label_share_ss_method->setText(ssConfig.value("method").toString()); ui->label_share_ss_password->setText(ssConfig.value("password").toString()); } + if (page == Page::ServerSettings) { + updateSettings(); + } + if (page == Page::Start) { + ui->pushButton_back_from_start->setVisible(!pagesStack.isEmpty()); + } ui->pushButton_new_server_connect_key->setChecked(false); } if (slide) - ui->stackedWidget_main->slideInWidget(getPageWidget(page)); + ui->stackedWidget_main->slideInWidget(getPageWidget(page), SlidingStackedWidget::RIGHT2LEFT); else ui->stackedWidget_main->setCurrentWidget(getPageWidget(page)); + + pagesStack.push(page); +} + +void MainWindow::closePage() +{ + Page prev = pagesStack.pop(); + qDebug() << "closePage" << prev << "Set page" << pagesStack.top(); + ui->stackedWidget_main->slideInWidget(getPageWidget(pagesStack.top()), SlidingStackedWidget::LEFT2RIGHT); } QWidget *MainWindow::getPageWidget(MainWindow::Page page) @@ -191,8 +210,13 @@ QWidget *MainWindow::getPageWidget(MainWindow::Page page) case(Page::AppSettings): return ui->page_app_settings; case(Page::NetworkSettings): return ui->page_network_settings; case(Page::ServerSettings): return ui->page_server_settings; + case(Page::ServerVpnProtocols): return ui->page_server_protocols; + case(Page::ServersList): return ui->page_servers; case(Page::ShareConnection): return ui->page_share_connection; case(Page::Sites): return ui->page_sites; + case(Page::OpenVpnSettings): return ui->page_proto_openvpn; + case(Page::ShadowSocksSettings): return ui->page_proto_shadowsocks; + case(Page::CloakSettings): return ui->page_proto_cloak; } return nullptr; } @@ -224,6 +248,14 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) return false; } } + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape && ! ui->stackedWidget_main->isAnimationRunning() ) { + if (currentPage() != Page::Vpn && currentPage() != Page::Start) { + closePage(); + } + } + } return QMainWindow::eventFilter(obj, event); } @@ -236,6 +268,8 @@ void MainWindow::keyPressEvent(QKeyEvent *event) QMessageBox::warning(this, APPLICATION_NAME, tr("Cannot open logs folder!")); } break; + case Qt::Key_Q: + qApp->quit(); default: ; } @@ -326,7 +360,7 @@ void MainWindow::onPushButtonNewServerConnectWithNewData(bool) ui->label_new_server_wait_info); if (ok) { - m_settings.setServerCredentials(serverCredentials); + //m_settings.setServerCredentials(serverCredentials); goToPage(Page::Vpn); qApp->processEvents(); @@ -358,7 +392,7 @@ void MainWindow::onPushButtonNewServerConnectWithExistingCode(bool) return; } - m_settings.setServerCredentials(credentials); + //m_settings.setServerCredentials(credentials); goToPage(Page::Vpn); } @@ -425,7 +459,7 @@ bool MainWindow::installServer(ServerCredentials credentials, void MainWindow::onPushButtonReinstallServer(bool) { onDisconnect(); - installServer(m_settings.serverCredentials(), + installServer(m_settings.defaultServerCredentials(), ui->page_server_settings, ui->progressBar_server_settings_reinstall, ui->pushButton_server_settings_reinstall, @@ -436,7 +470,7 @@ void MainWindow::onPushButtonClearServer(bool) { onDisconnect(); - ErrorCode e = ServerController::removeServer(m_settings.serverCredentials(), Protocol::Any); + ErrorCode e = ServerController::removeServer(m_settings.defaultServerCredentials(), Protocol::Any); if (e) { QMessageBox::warning(this, APPLICATION_NAME, tr("Error occurred while configuring server.") + "\n" + @@ -455,10 +489,10 @@ void MainWindow::onPushButtonForgetServer(bool) { onDisconnect(); - m_settings.setUserName(""); - m_settings.setPassword(""); - m_settings.setServerName(""); - m_settings.setServerPort(); +// m_settings.setUserName(""); +// m_settings.setPassword(""); +// m_settings.setServerName(""); +// m_settings.setServerPort(); goToPage(Page::Start); } @@ -580,6 +614,8 @@ void MainWindow::setTrayIcon(const QString &iconPath) MainWindow::Page MainWindow::currentPage() { + ui->stackedWidget_main->waitForAnimation(); + QWidget *currentPage = ui->stackedWidget_main->currentWidget(); QMetaEnum e = QMetaEnum::fromType(); @@ -616,6 +652,8 @@ void MainWindow::setupUiConnections() connect(ui->pushButton_app_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::AppSettings); }); connect(ui->pushButton_network_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::NetworkSettings); }); connect(ui->pushButton_server_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::ServerSettings); }); + connect(ui->pushButton_server_settings_protocols, &QPushButton::clicked, this, [this](){ goToPage(Page::ServerVpnProtocols); }); + connect(ui->pushButton_servers_list, &QPushButton::clicked, this, [this](){ goToPage(Page::ServersList); }); connect(ui->pushButton_share_connection, &QPushButton::clicked, this, [this](){ goToPage(Page::ShareConnection); updateShareCode(); @@ -631,13 +669,35 @@ void MainWindow::setupUiConnections() }); - connect(ui->pushButton_back_from_sites, &QPushButton::clicked, this, [this](){ goToPage(Page::Vpn); }); - connect(ui->pushButton_back_from_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::Vpn); }); - connect(ui->pushButton_back_from_new_server, &QPushButton::clicked, this, [this](){ goToPage(Page::Start); }); - connect(ui->pushButton_back_from_app_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::GeneralSettings); }); - connect(ui->pushButton_back_from_network_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::GeneralSettings); }); - connect(ui->pushButton_back_from_server_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::GeneralSettings); }); - connect(ui->pushButton_back_from_share, &QPushButton::clicked, this, [this](){ goToPage(Page::GeneralSettings); }); +// connect(ui->pushButton_back_from_sites, &QPushButton::clicked, this, [this](){ goToPage(Page::Vpn); }); +// connect(ui->pushButton_back_from_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::Vpn); }); +// connect(ui->pushButton_back_from_new_server, &QPushButton::clicked, this, [this](){ goToPage(Page::Start); }); +// connect(ui->pushButton_back_from_app_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::GeneralSettings); }); +// connect(ui->pushButton_back_from_network_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::GeneralSettings); }); +// connect(ui->pushButton_back_from_server_settings, &QPushButton::clicked, this, [this](){ goToPage(Page::GeneralSettings); }); +// connect(ui->pushButton_back_from_servers, &QPushButton::clicked, this, [this](){ goToPage(Page::GeneralSettings); }); +// connect(ui->pushButton_back_from_share, &QPushButton::clicked, this, [this](){ goToPage(Page::GeneralSettings); }); +// connect(ui->pushButton_back_from_server_vpn_protocols, &QPushButton::clicked, this, [this](){ goToPage(Page::ServerSettings); }); + +// connect(ui->pushButton_back_from_server_vpn_protocols, &QPushButton::clicked, this, [this](){ goToPage(Page::ServerSettings); }); +// connect(ui->pushButton_back_from_server_vpn_protocols, &QPushButton::clicked, this, [this](){ goToPage(Page::ServerSettings); }); +// connect(ui->pushButton_back_from_server_vpn_protocols, &QPushButton::clicked, this, [this](){ goToPage(Page::ServerSettings); }); + + connect(ui->pushButton_back_from_sites, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_settings, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_start, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_new_server, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_app_settings, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_network_settings, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_server_settings, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_servers, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_share, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_server_vpn_protocols, &QPushButton::clicked, this, [this](){ closePage(); }); + + connect(ui->pushButton_back_from_openvpn_settings, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_cloak_settings, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_back_from_shadowsocks_settings, &QPushButton::clicked, this, [this](){ closePage(); }); + connect(ui->pushButton_sites_add_custom, &QPushButton::clicked, this, [this](){ onPushButtonAddCustomSitesClicked(); }); @@ -659,11 +719,11 @@ void MainWindow::setupUiConnections() }); connect(ui->pushButton_network_settings_resetdns1, &QPushButton::clicked, this, [this](){ - m_settings.setPrimaryDns(m_settings.cloudFlareNs1()); + m_settings.setPrimaryDns(m_settings.cloudFlareNs1); updateSettings(); }); connect(ui->pushButton_network_settings_resetdns2, &QPushButton::clicked, this, [this](){ - m_settings.setSecondaryDns(m_settings.cloudFlareNs2()); + m_settings.setSecondaryDns(m_settings.cloudFlareNs2); updateSettings(); }); @@ -689,6 +749,36 @@ void MainWindow::setupUiConnections() QDesktopServices::openUrl(QUrl("https://github.com/amnezia-vpn/desktop-client/releases")); }); + connect(ui->pushButton_servers_add_new, &QPushButton::clicked, this, [this](){ goToPage(Page::Start); }); +} + +void MainWindow::setupProtocolsPage() +{ + connect(ui->pushButton_proto_openvpn_cont_openvpn_config, &QPushButton::clicked, this, [this](){ + goToPage(Page::OpenVpnSettings); + }); + connect(ui->pushButton_proto_ss_openvpn_cont_openvpn_config, &QPushButton::clicked, this, [this](){ + goToPage(Page::OpenVpnSettings); + }); + connect(ui->pushButton_proto_cloak_openvpn_cont_openvpn_config, &QPushButton::clicked, this, [this](){ + goToPage(Page::OpenVpnSettings); + }); + + connect(ui->pushButton_proto_cloak_openvpn_cont_default, &QPushButton::clicked, this, [this](){ + m_settings.setDefaultContainer(selectedServerIndex, DockerContainer::OpenVpnOverCloak); + updateSettings(); + }); + + connect(ui->pushButton_proto_ss_openvpn_cont_default, &QPushButton::clicked, this, [this](){ + m_settings.setDefaultContainer(selectedServerIndex, DockerContainer::ShadowSocksOverOpenVpn); + updateSettings(); + }); + + connect(ui->pushButton_proto_openvpn_cont_default, &QPushButton::clicked, this, [this](){ + m_settings.setDefaultContainer(selectedServerIndex, DockerContainer::OpenVpn); + updateSettings(); + }); + } void MainWindow::setTrayState(VpnProtocol::ConnectionState state) @@ -753,7 +843,7 @@ void MainWindow::onConnect() qApp->processEvents(); // TODO: Call connectToVpn with restricted server account - ServerCredentials credentials = m_settings.serverCredentials(); + ServerCredentials credentials = m_settings.defaultServerCredentials(); ErrorCode errorCode = m_vpnConnection->connectToVpn(credentials); if (errorCode) { @@ -864,18 +954,36 @@ void MainWindow::updateSettings() for(const QString &site : m_settings.customSites()) { makeSitesListItem(ui->listWidget_sites, site); } + + ui->listWidget_servers->clear(); + const QJsonArray &servers = m_settings.serversArray(); + int defaultServer = m_settings.defaultServerIndex(); + + for(int i = 0; i < servers.size(); i++) { + makeServersListItem(ui->listWidget_servers, servers.at(i).toObject(), i == defaultServer, i); + } + + QJsonObject selectedServer = m_settings.server(selectedServerIndex); + QString selectedContainerName = m_settings.defaultContainerName(selectedServerIndex); + + ui->label_server_settings_current_vpn_protocol->setText(tr("Protocol: ") + selectedContainerName); + + qDebug() << "DefaultContainer(selectedServerIndex)" << selectedServerIndex << m_settings.defaultContainer(selectedServerIndex); + ui->pushButton_proto_cloak_openvpn_cont_default->setChecked(m_settings.defaultContainer(selectedServerIndex) == DockerContainer::OpenVpnOverCloak); + ui->pushButton_proto_ss_openvpn_cont_default->setChecked(m_settings.defaultContainer(selectedServerIndex) == DockerContainer::ShadowSocksOverOpenVpn); + ui->pushButton_proto_openvpn_cont_default->setChecked(m_settings.defaultContainer(selectedServerIndex) == DockerContainer::OpenVpn); } void MainWindow::updateShareCode() { - QJsonObject o; - o.insert("h", m_settings.serverName()); - o.insert("p", m_settings.serverPort()); - o.insert("u", m_settings.userName()); - o.insert("w", m_settings.password()); +// QJsonObject o; +// o.insert("h", m_settings.serverName()); +// o.insert("p", m_settings.serverPort()); +// o.insert("u", m_settings.userName()); +// o.insert("w", m_settings.password()); - QByteArray ba = QJsonDocument(o).toJson().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - ui->textEdit_sharing_code->setText(QString("vpn://%1").arg(QString(ba))); +// QByteArray ba = QJsonDocument(o).toJson().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); +// ui->textEdit_sharing_code->setText(QString("vpn://%1").arg(QString(ba))); //qDebug() << "Share code" << QJsonDocument(o).toJson(); } @@ -909,6 +1017,34 @@ void MainWindow::makeSitesListItem(QListWidget *listWidget, const QString &addre widget->setStyleSheet(styleSheet()); } +void MainWindow::makeServersListItem(QListWidget *listWidget, const QJsonObject &server, bool isDefault, int index) +{ + QSize size(310, 70); + ServerWidget* widget = new ServerWidget(server, isDefault); + widget->resize(size); + + connect(widget->ui->pushButton_default, &QPushButton::clicked, this, [this, index](){ + m_settings.setDefaultServer(index); + updateSettings(); + }); + + connect(widget->ui->pushButton_share, &QPushButton::clicked, this, [this, index](){ + goToPage(Page::ShareConnection); + // update share page + }); + + connect(widget->ui->pushButton_settings, &QPushButton::clicked, this, [this, index](){ + selectedServerIndex = index; + goToPage(Page::ServerSettings); + }); + + QListWidgetItem* item = new QListWidgetItem(listWidget); + item->setSizeHint(size); + listWidget->setItemWidget(item, widget); + + widget->setStyleSheet(styleSheet()); +} + void MainWindow::updateQRCodeImage(const QString &text, QLabel *label) { int levelIndex = 1; diff --git a/client/ui/mainwindow.h b/client/ui/mainwindow.h index f8d67ccc6..45c56a51d 100644 --- a/client/ui/mainwindow.h +++ b/client/ui/mainwindow.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -39,7 +40,9 @@ public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); - enum Page {Start, NewServer, Vpn, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ShareConnection, Sites}; + enum Page {Start, NewServer, Vpn, GeneralSettings, AppSettings, NetworkSettings, + ServerSettings, ServerVpnProtocols, ServersList, ShareConnection, Sites, + OpenVpnSettings, ShadowSocksSettings, CloakSettings}; Q_ENUM(Page) private slots: @@ -69,6 +72,8 @@ private slots: private: void goToPage(Page page, bool reset = true, bool slide = true); + void closePage(); + QWidget *getPageWidget(Page page); Page currentPage(); @@ -78,10 +83,13 @@ private: void setTrayIcon(const QString &iconPath); void setupUiConnections(); + void setupProtocolsPage(); + void updateSettings(); void updateShareCode(); void makeSitesListItem(QListWidget* listWidget, const QString &address); + void makeServersListItem(QListWidget* listWidget, const QJsonObject &server, bool isDefault, int index); void updateQRCodeImage(const QString &text, QLabel *label); private: @@ -111,6 +119,10 @@ private: const QString ConnectedTrayIconName = "active.png"; const QString DisconnectedTrayIconName = "default.png"; const QString ErrorTrayIconName = "error.png"; + + + QStack pagesStack; + int selectedServerIndex = -1; // server index to use when proto settings page opened }; #endif // MAINWINDOW_H diff --git a/client/ui/mainwindow.ui b/client/ui/mainwindow.ui index ebe00ae3b..1764970b8 100644 --- a/client/ui/mainwindow.ui +++ b/client/ui/mainwindow.ui @@ -163,7 +163,26 @@ QScrollBar::down-arrow:vertical { /* arrow to scroll down with */ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background-color: black; -} +} + +/* +QGroupBox { + border: 1px solid lightgray; + border-radius: 2px; + margin-top: 1ex; +} + +QGroupBox::title { + font-size: 16px; + font-style: normal; + font-weight: normal; + color: #181922; + + subcontrol-origin: margin; + subcontrol-position: top left; + padding: 0 3px; +} +*/ @@ -263,14 +282,14 @@ QPushButton:hover { - 5 + 1 10 - 40 + 50 361 71 @@ -430,6 +449,38 @@ border-radius: 4px; Set up your own server + + + + 10 + 10 + 26 + 20 + + + + PointingHandCursor + + + QPushButton { + image: url(:/images/arrow_right.png); + image-position: left; + text-align: left; + /*font: 17pt "Ancient";*/ + + padding: 1px; + image: url(:/images/arrow_left.png); +} +QPushButton:hover { + padding: 0px; +} + + + + + + + @@ -1028,7 +1079,7 @@ background: #282932; PointingHandCursor - image: url(:/images/settings.png); + image: url(:/images/settings_grey.png); background: transparent @@ -1198,34 +1249,31 @@ color: #181922; - -QListView { + QListView { outline: 0; background: transparent; border: none; - gridline-color: darkgray; } QListView::item { - padding-left: 5px; + padding-left: 5px; border: none; -color: #181922; - + color: #181922; } QListView::item:disabled { - padding-left: 5px; + padding-left: 5px; border: none; -color: #181922; + color: #181922; } QListView::item:selected { border: none; -background: rgba(167, 167, 167, 0.1); -color: #181922; + background: rgba(167, 167, 167, 0.1); + color: #181922; } @@ -1772,6 +1820,218 @@ background-repeat: no-repeat; Network settings + + + + 30 + 300 + 330 + 30 + + + + PointingHandCursor + + + Reinstall server, clear server + + + font-family: Lato; +font-style: normal; +font-weight: bold; +font-size: 20px; +line-height: 25px; +Text-align:left; +padding-left: 30px; + + +/* black */ +color: #100A44; + +background-image: url(:/images/server_settings.png); +background-repeat: no-repeat; + background-position: left center; + + + Servers + + + + + + 10 + 340 + 360 + 10 + + + + image: url(:/images/line.png); + + + + + + + + + + + + + + 20 + 90 + 340 + 501 + + + + QWidget { + margin: 0px; + padding: 0px; +} + +QPushButton:hover { + image: url(:/images/close.png); + image-position: right center; +} + +QListView { + outline: 0; + background: transparent; + border: none; + gridline-color: darkgray; + show-decoration-selected: 1; +} + +QListView::item +{ + padding-left: 5px; + color: #181922; + border: none; + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #FAFBFE, stop: 1 #ECEEFF); +} + +QListView::item:disabled +{ + padding-left: 5px; + border: none; + color: #181922; +} + +QListView::item:selected { + border: none; + background: rgba(167, 167, 167, 0.1); + color: #181922; +} + +QListView::item:selected:!active { + background: transparent; + border: none; +} + +QListView::item:selected:active { + background: transparent; + border: none; +} + +QListView::item:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #FAFBFE, stop: 1 #DCDEDF); +} + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + + + + 10 + 10 + 26 + 20 + + + + PointingHandCursor + + + QPushButton { + image: url(:/images/arrow_right.png); + image-position: left; + text-align: left; + /*font: 17pt "Ancient";*/ + + padding: 1px; + image: url(:/images/arrow_left.png); +} +QPushButton:hover { + padding: 0px; +} + + + + + + + + + + + 120 + 40 + 111 + 40 + + + + font-family: Lato; +font-style: normal; +font-weight: bold; +font-size: 20px; +line-height: 25px; +color: #100A44; + + + + Servers + + + Qt::AlignCenter + + + + + + 230 + 49 + 24 + 24 + + + + PointingHandCursor + + + QPushButton { + image: url(:/images/plus.png); + padding:1px; +} +QPushButton:hover { + padding:0px; +} + + + + + + @@ -1926,8 +2186,8 @@ background: #211966; - 20 - 20 + 10 + 10 26 20 @@ -2139,7 +2399,7 @@ padding:0px; 40 - 490 + 530 301 41 @@ -2155,7 +2415,7 @@ padding:0px; 40 - 150 + 350 300 40 @@ -2221,7 +2481,7 @@ QPushButton:hover { 20 - 40 + 30 340 40 @@ -2236,7 +2496,7 @@ color: #100A44; - Your configured server + Server settings Qt::AlignCenter @@ -2249,7 +2509,7 @@ color: #100A44; 40 - 150 + 350 300 40 @@ -2309,7 +2569,7 @@ border-radius: 4px 0px 0px 4px; 40 - 210 + 410 300 40 @@ -2343,7 +2603,7 @@ background: #211966; 40 - 270 + 470 300 40 @@ -2377,9 +2637,9 @@ background: #211966; 20 - 80 + 120 341 - 41 + 31 @@ -2387,8 +2647,7 @@ background: #211966; font-family: Lato; font-style: normal; font-weight: normal; -font-size: 24px; -color: #333333; +font-size: 20px; } @@ -2401,8 +2660,94 @@ color: #333333; Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + 70 + 80 + 251 + 31 + + + + QLineEdit { +border: none; +outline: none; +border-bottom: 1px solid lightgrey; +font-size: 18px; +font-weight: bold; +} + + + + Qt::AlignCenter + + + false + + + + + + 40 + 220 + 300 + 40 + + + + PointingHandCursor + + + QPushButton { +color:rgb(212, 212, 212); +border-radius: 4px; + +font-family: Lato; +font-style: normal; +font-weight: normal; +font-size: 16px; +line-height: 21px; + +background: #100A44; +border-radius: 4px; +} +QPushButton:hover { +background: #211966; +} + + + VPN protocols + + + + + + 20 + 150 + 341 + 31 + + + + QLabel { +font-family: Lato; +font-style: normal; +font-weight: normal; +font-size: 20px; +} + + + VPN Protocol: + + + Qt::AlignCenter + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + label_server_settings_wait_info - pushButton_back_from_server_settings label_16 progressBar_server_settings_reinstall label_17 @@ -2410,6 +2755,663 @@ color: #333333; pushButton_server_settings_clear pushButton_server_settings_forget label_server_settings_server + lineEdit_server_settings_description + pushButton_server_settings_protocols + pushButton_back_from_server_settings + label_server_settings_current_vpn_protocol + + + + + + + + + 20 + 440 + 340 + 121 + + + + QWidget { + margin: 0px; + padding: 0px; +} + +QPushButton:hover { + image: url(:/images/close.png); + image-position: right center; +} + +QListView { + outline: 0; + background: transparent; + border: none; + gridline-color: darkgray; + show-decoration-selected: 1; +} + +QListView::item +{ + padding-left: 5px; + color: #181922; + border: none; + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #FAFBFE, stop: 1 #ECEEFF); +} + +QListView::item:disabled +{ + padding-left: 5px; + border: none; + color: #181922; +} + +QListView::item:selected { + border: none; + background: rgba(167, 167, 167, 0.1); + color: #181922; +} + +QListView::item:selected:!active { + background: transparent; + border: none; +} + +QListView::item:selected:active { + background: transparent; + border: none; +} + +QListView::item:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #FAFBFE, stop: 1 #DCDEDF); +} + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + + + + 20 + 30 + 340 + 40 + + + + font-family: Lato; +font-style: normal; +font-weight: bold; +font-size: 20px; +line-height: 25px; +color: #100A44; + + + + Protocols + + + Qt::AlignCenter + + + true + + + + + + 10 + 10 + 26 + 20 + + + + PointingHandCursor + + + QPushButton { + image: url(:/images/arrow_right.png); + image-position: left; + text-align: left; + /*font: 17pt "Ancient";*/ + + padding: 1px; + image: url(:/images/arrow_left.png); +} +QPushButton:hover { + padding: 0px; +} + + + + + + + + + + + 0 + 70 + 381 + 511 + + + + QWidget { +background: transparent; +} +QPushButton { + text-align: left; + background-repeat:no-repeat; + background-position:left top; + + background-image: url(:/images/settings.png); + padding-left: 30px; + min-height: 24px; +} +QFrame { +border: 1px solid lightgrey; +border-radius: 2px; +} +QFrame#scrollArea_server_protocols { +border: none; +} +QLabel { +border: none; +} + + + true + + + + + 0 + 0 + 381 + 511 + + + + + 19 + + + + + + 0 + 100 + + + + + + + + + + + + Cloak container + + + + + + + + 36 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + background: transparent; +padding: 0px; +image: url(:/images/connect_button_connected.png); +margin: 0px; + + + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + background: transparent; +image: url(:/images/share.png); +padding: 0px; +margin: 0px; + + + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + QPushButton { + background: transparent; + image: url(:/images/check.png); + padding: 0px; + margin: 0px; +} +QPushButton:checked { + image: url(:/images/check.png); +} +QPushButton:!checked { + image: url(:/images/uncheck.png); +} + + + + + + + + true + + + false + + + + + + + + + + + + PointingHandCursor + + + OpenVPN settings + + + + + + + PointingHandCursor + + + ShadowSocks settings + + + + + + + PointingHandCursor + + + Cloak settings + + + + + + + + + + + + + + 0 + 100 + + + + + + + + + ShadowSocks container + + + + + + + + 36 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + background: transparent; +padding: 0px; +image: url(:/images/connect_button_disconnected.png); +margin: 0px; + + + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + background: transparent; +image: url(:/images/share.png); +padding: 0px; +margin: 0px; + + + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + QPushButton { + background: transparent; + image: url(:/images/check.png); + padding: 0px; + margin: 0px; +} +QPushButton:checked { + image: url(:/images/check.png); +} +QPushButton:!checked { + image: url(:/images/uncheck.png); +} + + + + + + + true + + + false + + + + + + + + + + + + PointingHandCursor + + + OpenVPN settings + + + + + + + PointingHandCursor + + + ShadowSocks settings + + + + + + + + + + + + + + 0 + 100 + + + + + + + + + + + + OpenVPN container + + + + + + + + 36 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + background: transparent; +padding: 0px; +image: url(:/images/connect_button_connected.png); +margin: 0px; + + + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + background: transparent; +image: url(:/images/share.png); +padding: 0px; +margin: 0px; + + + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + PointingHandCursor + + + QPushButton { + background: transparent; + image: url(:/images/check.png); + padding: 0px; + margin: 0px; +} +QPushButton:checked { + image: url(:/images/check.png); +} +QPushButton:!checked { + image: url(:/images/uncheck.png); +} + + + + + + + true + + + false + + + + + + + + + + + + PointingHandCursor + + + OpenVPN settings + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + @@ -2659,8 +3661,8 @@ background: #282932; 0 0 - 100 - 30 + 360 + 500 @@ -2880,6 +3882,357 @@ color: #15CDCB; + + + + + 20 + 30 + 340 + 40 + + + + font-family: Lato; +font-style: normal; +font-weight: bold; +font-size: 20px; +line-height: 25px; +color: #100A44; + + + + OpenVPN Settings + + + Qt::AlignCenter + + + true + + + + + + 10 + 10 + 26 + 20 + + + + PointingHandCursor + + + QPushButton { + image: url(:/images/arrow_right.png); + image-position: left; + text-align: left; + /*font: 17pt "Ancient";*/ + + padding: 1px; + image: url(:/images/arrow_left.png); +} +QPushButton:hover { + padding: 0px; +} + + + + + + + + + + + 30 + 95 + 321 + 31 + + + + + + + + + + + + true + + + + 30 + 70 + 291 + 21 + + + + VPN Addresses Subnet + + + true + + + + + + 30 + 150 + 321 + 91 + + + + QGroupBox{ + border: 1px solid lightgray; + border-radius: 2px; + margin-top: 7ex; +} + +QGroupBox::title { + font-size: 16px; + font-style: normal; + font-weight: normal; + color: #181922; + + subcontrol-origin: margin; + subcontrol-position: top left; +} + + + Network protocol + + + + + 10 + 30 + 171 + 19 + + + + UDP + + + + + + 10 + 60 + 171 + 19 + + + + TCP + + + + + + + 30 + 290 + 151 + 31 + + + + + + true + + + + 30 + 260 + 151 + 21 + + + + Cipher + + + true + + + + + + 200 + 290 + 151 + 31 + + + + + + true + + + + 200 + 260 + 151 + 21 + + + + Hash + + + true + + + + + + 30 + 350 + 321 + 21 + + + + Block DNS requests outside of VPN + + + false + + + + + + + + 20 + 30 + 340 + 40 + + + + font-family: Lato; +font-style: normal; +font-weight: bold; +font-size: 20px; +line-height: 25px; +color: #100A44; + + + + OpenVPN Settings + + + Qt::AlignCenter + + + true + + + + + + 10 + 10 + 26 + 20 + + + + PointingHandCursor + + + QPushButton { + image: url(:/images/arrow_right.png); + image-position: left; + text-align: left; + /*font: 17pt "Ancient";*/ + + padding: 1px; + image: url(:/images/arrow_left.png); +} +QPushButton:hover { + padding: 0px; +} + + + + + + + + + + + + + 20 + 30 + 340 + 40 + + + + font-family: Lato; +font-style: normal; +font-weight: bold; +font-size: 20px; +line-height: 25px; +color: #100A44; + + + + OpenVPN Settings + + + Qt::AlignCenter + + + true + + + + + + 10 + 10 + 26 + 20 + + + + PointingHandCursor + + + QPushButton { + image: url(:/images/arrow_right.png); + image-position: left; + text-align: left; + /*font: 17pt "Ancient";*/ + + padding: 1px; + image: url(:/images/arrow_left.png); +} +QPushButton:hover { + padding: 0px; +} + + + + + + + + diff --git a/client/ui/server_widget.cpp b/client/ui/server_widget.cpp new file mode 100644 index 000000000..4c2f445ff --- /dev/null +++ b/client/ui/server_widget.cpp @@ -0,0 +1,30 @@ +#include "server_widget.h" +#include "ui_server_widget.h" + +#include "settings.h" + +ServerWidget::ServerWidget(const QJsonObject &server, bool isDefault, QWidget *parent) : + QWidget(parent), + ui(new Ui::ServerWidget) +{ + ui->setupUi(this); + QString desc = server.value(Settings::descriptionString).toString(); + QString address = server.value(Settings::hostNameString).toString(); + + ui->label_address->setText(address); + + if (desc.isEmpty()) { + ui->label_description->setText(address); + } + else { + ui->label_description->setText(desc); + } + + ui->pushButton_default->setChecked(isDefault); + ui->pushButton_default->setDisabled(isDefault); +} + +ServerWidget::~ServerWidget() +{ + delete ui; +} diff --git a/client/ui/server_widget.h b/client/ui/server_widget.h new file mode 100644 index 000000000..dd529979c --- /dev/null +++ b/client/ui/server_widget.h @@ -0,0 +1,22 @@ +#ifndef SERVER_WIDGET_H +#define SERVER_WIDGET_H + +#include +#include + +namespace Ui { +class ServerWidget; +} + +class ServerWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ServerWidget(const QJsonObject &server, bool isDefault, QWidget *parent = nullptr); + ~ServerWidget(); + Ui::ServerWidget *ui; + +}; + +#endif // SERVER_WIDGET_H diff --git a/client/ui/server_widget.ui b/client/ui/server_widget.ui new file mode 100644 index 000000000..69779f9ab --- /dev/null +++ b/client/ui/server_widget.ui @@ -0,0 +1,167 @@ + + + ServerWidget + + + + 0 + 0 + 325 + 70 + + + + Form + + + + + + + + 10 + 10 + 181 + 21 + + + + QLabel { + font-size: 16px; + + font-style: normal; + font-weight: bold; + color: #181922; +} + + + Description + + + + + + 20 + 40 + 141 + 16 + + + + Address + + + + + + 300 + 25 + 24 + 24 + + + + PointingHandCursor + + + Set as default + + + QPushButton:checked { + image: url(:/images/check.png); +} +QPushButton:!checked { + image: url(:/images/uncheck.png); +} + + + + + + true + + + + + + 260 + 25 + 24 + 24 + + + + PointingHandCursor + + + Share connection + + + image: url(:/images/share.png); + + + + + + true + + + + + + 212 + 25 + 32 + 24 + + + + PointingHandCursor + + + Connection + + + QPushButton:checked { + image: url(:/images/connect_button_connected.png); +} +QPushButton:!checked { + image: url(:/images/connect_button_disconnected.png); +} + + + + + + true + + + + + + 174 + 25 + 24 + 24 + + + + PointingHandCursor + + + Server settings + + + image: url(:/images/settings.png); + + + + + + true + + + + + + diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 155e9b6b4..bd847d3c2 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -83,9 +83,9 @@ ErrorCode VpnConnection::lastError() const ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credentials, Protocol protocol) { ErrorCode errorCode = ErrorCode::NoError; - if (protocol == Protocol::OpenVpn || protocol == Protocol::ShadowSocks || protocol == Protocol::OpenVpnOverCloak) { + if (protocol == Protocol::OpenVpn || protocol == Protocol::ShadowSocksOverOpenVpn || protocol == Protocol::OpenVpnOverCloak) { QString openVpnConfigData = OpenVpnConfigurator::genOpenVpnConfig(credentials, protocol, &errorCode); - m_vpnConfiguration.insert(config::key_openvpn_config_data(), openVpnConfigData); + m_vpnConfiguration.insert(config::key_openvpn_config_data, openVpnConfigData); if (errorCode) { return errorCode; } @@ -101,14 +101,14 @@ ErrorCode VpnConnection::createVpnConfiguration(const ServerCredentials &credent } } - if (protocol == Protocol::ShadowSocks) { + if (protocol == Protocol::ShadowSocksOverOpenVpn) { QJsonObject ssConfigData = ShadowSocksVpnProtocol::genShadowSocksConfig(credentials); - m_vpnConfiguration.insert(config::key_shadowsocks_config_data(), ssConfigData); + m_vpnConfiguration.insert(config::key_shadowsocks_config_data, ssConfigData); } if (protocol == Protocol::OpenVpnOverCloak) { QJsonObject cloakConfigData = CloakConfigurator::genCloakConfig(credentials, Protocol::OpenVpnOverCloak, &errorCode); - m_vpnConfiguration.insert(config::key_cloak_config_data(), cloakConfigData); + m_vpnConfiguration.insert(config::key_cloak_config_data, cloakConfigData); } //qDebug().noquote() << "VPN config" << QJsonDocument(m_vpnConfiguration).toJson(); @@ -152,8 +152,8 @@ ErrorCode VpnConnection::connectToVpn(const ServerCredentials &credentials, Prot return e; } } - else if (protocol == Protocol::ShadowSocks) { - ErrorCode e = createVpnConfiguration(credentials, Protocol::ShadowSocks); + else if (protocol == Protocol::ShadowSocksOverOpenVpn) { + ErrorCode e = createVpnConfiguration(credentials, Protocol::ShadowSocksOverOpenVpn); if (e) { emit connectionStateChanged(VpnProtocol::ConnectionState::Error); return e;