#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "amnezia_application.h" #include "configurators/cloak_configurator.h" #include "configurators/vpn_configurator.h" #include "configurators/openvpn_configurator.h" #include "configurators/shadowsocks_configurator.h" #include "configurators/ssh_configurator.h" #include "core/servercontroller.h" #include "core/server_defs.h" #include "core/errorstrings.h" #include "containers/containers_defs.h" #include "ui/qautostart.h" #include "logger.h" #include "version.h" #include "uilogic.h" #include "utilities.h" #include "vpnconnection.h" #include #if defined Q_OS_MAC || defined Q_OS_LINUX #include "ui/macos_util.h" #endif #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" #endif #include "platforms/ios/MobileUtils.h" #include "pages_logic/AppSettingsLogic.h" #include "pages_logic/GeneralSettingsLogic.h" #include "pages_logic/NetworkSettingsLogic.h" #include "pages_logic/NewServerProtocolsLogic.h" #include "pages_logic/QrDecoderLogic.h" #include "pages_logic/ServerConfiguringProgressLogic.h" #include "pages_logic/ServerListLogic.h" #include "pages_logic/ServerSettingsLogic.h" #include "pages_logic/ServerContainersLogic.h" #include "pages_logic/ShareConnectionLogic.h" #include "pages_logic/SitesLogic.h" #include "pages_logic/StartPageLogic.h" #include "pages_logic/ViewConfigLogic.h" #include "pages_logic/VpnLogic.h" #include "pages_logic/WizardLogic.h" #include "pages_logic/AdvancedServerSettingsLogic.h" #include "pages_logic/ClientManagementLogic.h" #include "pages_logic/ClientInfoLogic.h" #include "pages_logic/protocols/CloakLogic.h" #include "pages_logic/protocols/OpenVpnLogic.h" #include "pages_logic/protocols/ShadowSocksLogic.h" #include "pages_logic/protocols/OtherProtocolsLogic.h" #include "pages_logic/protocols/WireGuardLogic.h" using namespace amnezia; using namespace PageEnumNS; UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent) : QObject(parent), m_settings(settings), m_configurator(configurator) { m_containersModel = new ContainersModel(settings, this); m_protocolsModel = new ProtocolsModel(settings, this); m_clientManagementModel = new ClientManagementModel(this); m_vpnConnection = new VpnConnection(settings, configurator); m_vpnConnection->moveToThread(&m_vpnConnectionThread); m_vpnConnectionThread.start(); m_protocolLogicMap.insert(Proto::OpenVpn, new OpenVpnLogic(this)); m_protocolLogicMap.insert(Proto::ShadowSocks, new ShadowSocksLogic(this)); m_protocolLogicMap.insert(Proto::Cloak, new CloakLogic(this)); m_protocolLogicMap.insert(Proto::WireGuard, new WireGuardLogic(this)); m_protocolLogicMap.insert(Proto::Dns, new OtherProtocolsLogic(this)); m_protocolLogicMap.insert(Proto::Sftp, new OtherProtocolsLogic(this)); m_protocolLogicMap.insert(Proto::TorWebSite, new OtherProtocolsLogic(this)); } UiLogic::~UiLogic() { emit hide(); #ifdef AMNEZIA_DESKTOP if (m_vpnConnection->connectionState() != VpnProtocol::VpnConnectionState::Disconnected) { m_vpnConnection->disconnectFromVpn(); for (int i = 0; i < 50; i++) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); QThread::msleep(100); if (m_vpnConnection->isDisconnected()) { break; } } } #endif m_vpnConnection->deleteLater(); m_vpnConnectionThread.quit(); m_vpnConnectionThread.wait(3000); qDebug() << "Application closed"; } void UiLogic::initializeUiLogic() { #ifdef Q_OS_ANDROID connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) { if (connected) { pageLogic()->onConnectionStateChanged(VpnProtocol::Connected); m_vpnConnection->restoreConnection(); } }); if (!AndroidController::instance()->initialize(pageLogic())) { qCritical() << QString("Init failed") ; emit VpnProtocol::Error; return; } #endif m_notificationHandler = NotificationHandler::create(qmlRoot()); connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, &NotificationHandler::setConnectionState); connect(m_notificationHandler, &NotificationHandler::raiseRequested, this, &UiLogic::raise); connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic(), &VpnLogic::onConnect); connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), &VpnLogic::onDisconnect); if (m_settings->serversCount() > 0) { if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); emit goToPage(Page::Vpn, true, false); } else { emit goToPage(Page::Start, true, false); } m_selectedServerIndex = m_settings->defaultServerIndex(); qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME).arg(APP_VERSION); qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName()).arg(QSysInfo::currentCpuArchitecture()); } void UiLogic::showOnStartup() { if (! m_settings->isStartMinimized()) { emit show(); } else { #ifdef Q_OS_WIN emit hide(); #elif defined Q_OS_MACX // TODO: fix: setDockIconVisible(false); #endif } } void UiLogic::onUpdateAllPages() { for (auto logic : m_logicMap) { if (dynamic_cast(logic) || dynamic_cast(logic)) { continue; } logic->onUpdatePage(); } } void UiLogic::keyPressEvent(Qt::Key key) { switch (key) { case Qt::Key_AsciiTilde: case Qt::Key_QuoteLeft: emit toggleLogPanel(); break; case Qt::Key_L: Logger::openLogsFolder(); break; case Qt::Key_K: Logger::openServiceLogsFolder(); break; #ifdef QT_DEBUG case Qt::Key_Q: qApp->quit(); break; case Qt::Key_H: m_selectedServerIndex = m_settings->defaultServerIndex(); m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); //updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); emit goToPage(Page::ShareConnection); break; #endif case Qt::Key_C: qDebug().noquote() << "Def server" << m_settings->defaultServerIndex() << m_settings->defaultContainerName(m_settings->defaultServerIndex()); qDebug().noquote() << QJsonDocument(m_settings->defaultServer()).toJson(); break; case Qt::Key_A: emit goToPage(Page::Start); break; case Qt::Key_S: m_selectedServerIndex = m_settings->defaultServerIndex(); emit goToPage(Page::ServerSettings); break; case Qt::Key_P: onGotoCurrentProtocolsPage(); break; case Qt::Key_T: m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex())); break; case Qt::Key_Escape: if (currentPage() == Page::Vpn) break; if (currentPage() == Page::ServerConfiguringProgress) break; case Qt::Key_Back: // if (currentPage() == Page::Start && pagesStack.size() < 2) break; // if (currentPage() == Page::Sites && // ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { // ui->tableView_sites->clearSelection(); // break; // } emit closePage(); //} default: ; } } void UiLogic::onCloseWindow() { #ifdef Q_OS_ANDROID qApp->quit(); #else if (m_settings->serversCount() == 0) { qApp->quit(); } else { emit hide(); } #endif } QString UiLogic::containerName(int container) { return ContainerProps::containerHumanNames().value(static_cast(container)); } QString UiLogic::containerDesc(int container) { return ContainerProps::containerDescriptions().value(static_cast(container)); } void UiLogic::onGotoCurrentProtocolsPage() { m_selectedServerIndex = m_settings->defaultServerIndex(); m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); emit goToPage(Page::ServerContainers); } void UiLogic::installServer(QPair &container) { emit goToPage(Page::ServerConfiguringProgress); QEventLoop loop; QTimer::singleShot(500, &loop, SLOT(quit())); loop.exec(); qApp->processEvents(); ServerConfiguringProgressLogic::PageFunc pageFunc; pageFunc.setEnabledFunc = [this] (bool enabled) -> void { pageLogic()->set_pageEnabled(enabled); }; ServerConfiguringProgressLogic::ButtonFunc noButton; ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { pageLogic()->set_labelWaitInfoText(text); }; waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { pageLogic()->set_labelWaitInfoVisible(visible); }; ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { pageLogic()->set_progressBarVisible(visible); }; progressBarFunc.setValueFunc = [this] (int value) -> void { pageLogic()->set_progressBarValue(value); }; progressBarFunc.getValueFunc = [this] (void) -> int { return pageLogic()->progressBarValue(); }; progressBarFunc.getMaximumFunc = [this] (void) -> int { return pageLogic()->progressBarMaximum(); }; progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { pageLogic()->set_progressBarTextVisible(visible); }; progressBarFunc.setTextFunc = [this] (const QString& text) -> void { pageLogic()->set_progressBarText(text); }; ServerConfiguringProgressLogic::LabelFunc busyInfoFunc; busyInfoFunc.setTextFunc = [this] (const QString& text) -> void { pageLogic()->set_labelServerBusyText(text); }; busyInfoFunc.setVisibleFunc = [this] (bool visible) -> void { pageLogic()->set_labelServerBusyVisible(visible); }; ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { pageLogic()->set_pushButtonCancelVisible(visible); }; bool isServerCreated = false; ErrorCode errorCode = addAlreadyInstalledContainersGui(isServerCreated); if (errorCode == ErrorCode::NoError) { if (!isContainerAlreadyAddedToGui(container.first)) { progressBarFunc.setTextFunc(QString("Installing %1").arg(ContainerProps::containerToString(container.first))); auto installAction = [&] () { ServerController serverController(m_settings); return serverController.setupContainer(m_installCredentials, container.first, container.second); }; errorCode = pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, noButton, waitInfoFunc, busyInfoFunc, cancelButtonFunc); if (errorCode == ErrorCode::NoError) { if (!isServerCreated) { QJsonObject server; server.insert(config_key::hostName, m_installCredentials.hostName); server.insert(config_key::userName, m_installCredentials.userName); server.insert(config_key::password, m_installCredentials.password); server.insert(config_key::port, m_installCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); server.insert(config_key::containers, QJsonArray{container.second}); server.insert(config_key::defaultContainer, ContainerProps::containerToString(container.first)); m_settings->addServer(server); m_settings->setDefaultServer(m_settings->serversCount() - 1); } else { m_settings->setContainerConfig(m_settings->serversCount() - 1, container.first, container.second); m_settings->setDefaultContainer(m_settings->serversCount() - 1, container.first); } onUpdateAllPages(); emit setStartPage(Page::Vpn); qApp->processEvents(); return; } } else { onUpdateAllPages(); emit showWarningMessage("Attention! The container you are trying to install is already installed on the server. " "All installed containers have been added to the application "); emit setStartPage(Page::Vpn); return; } } emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + tr("Error message: ") + errorString(errorCode) + "\n" + tr("See logs for details.")); emit closePage(); } PageProtocolLogicBase *UiLogic::protocolLogic(Proto p) { PageProtocolLogicBase *logic = m_protocolLogicMap.value(p); if (logic) return logic; else { qCritical() << "UiLogic::protocolLogic Warning: logic missing for" << p; return new PageProtocolLogicBase(this); } } QObject *UiLogic::qmlRoot() const { return m_qmlRoot; } void UiLogic::setQmlRoot(QObject *newQmlRoot) { m_qmlRoot = newQmlRoot; } NotificationHandler *UiLogic::notificationHandler() const { return m_notificationHandler; } void UiLogic::setQmlContextProperty(PageLogicBase *logic) { amnApp->qmlEngine()->rootContext()->setContextProperty(logic->metaObject()->className(), logic); } PageEnumNS::Page UiLogic::currentPage() { return static_cast(currentPageValue()); } void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QString ext, const QString& data) { #ifdef Q_OS_IOS shareTempFile(suggestedName, ext, data); return; #endif ext.replace("*", ""); QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl fileName; #ifdef AMNEZIA_DESKTOP fileName = QFileDialog::getSaveFileUrl(nullptr, desc, QUrl::fromLocalFile(docDir + "/" + suggestedName), "*" + ext); if (fileName.isEmpty()) return; if (!fileName.toString().endsWith(ext)) fileName = QUrl(fileName.toString() + ext); #elif defined Q_OS_ANDROID qDebug() << "UiLogic::shareConfig" << data; AndroidController::instance()->shareConfig(data, suggestedName); return; #endif if (fileName.isEmpty()) return; #ifdef AMNEZIA_DESKTOP QFile save(fileName.toLocalFile()); #else QFile save(QQmlFile::urlToLocalFileOrQrc(fileName)); #endif save.open(QIODevice::WriteOnly); save.write(data.toUtf8()); save.close(); QFileInfo fi(fileName.toLocalFile()); QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &data) { ext.replace("*", ""); QString fileName = QFileDialog::getSaveFileName(nullptr, desc, QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*" + ext); if (fileName.isEmpty()) return; if (!fileName.endsWith(ext)) fileName.append(ext); QFile save(fileName); save.open(QIODevice::WriteOnly); save.write(QByteArray::fromBase64(data.toUtf8())); save.close(); QFileInfo fi(fileName); QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } void UiLogic::copyToClipboard(const QString &text) { #ifdef Q_OS_ANDROID AndroidController::instance()->copyTextToClipboard(text); #else qApp->clipboard()->setText(text); #endif } void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString& data) { ext.replace("*", ""); QString fileName = QDir::tempPath() + "/" + suggestedName; if (fileName.isEmpty()) return; if (!fileName.endsWith(ext)) fileName.append(ext); QFile::remove(fileName); QFile save(fileName); save.open(QIODevice::WriteOnly); save.write(data.toUtf8()); save.close(); QStringList filesToSend; filesToSend.append(fileName); MobileUtils::shareText(filesToSend); } QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) { QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); #ifdef Q_OS_ANDROID // patch for files containing spaces etc const QString sep {"raw%3A%2F"}; if (fileName.startsWith("content://") && fileName.contains(sep)) { QString contentUrl = fileName.split(sep).at(0); QString rawUrl = fileName.split(sep).at(1); rawUrl.replace(" ", "%20"); fileName = contentUrl + sep + rawUrl; } #endif return fileName; } void UiLogic::registerPagesLogic() { amnApp->qmlEngine()->rootContext()->setContextProperty("UiLogic", this); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); registerPageLogic(); } ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) { isServerCreated = false; ServerCredentials installCredentials = m_installCredentials; bool createNewServer = true; int serverIndex; for (int i = 0; i < m_settings->serversCount(); i++) { const ServerCredentials credentials = m_settings->serverCredentials(i); if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { createNewServer = false; isServerCreated = true; installCredentials = credentials; serverIndex = i; break; } } QMap installedContainers; ServerController serverController(m_settings); ErrorCode errorCode = serverController.getAlreadyInstalledContainers(installCredentials, installedContainers); if (errorCode != ErrorCode::NoError) { return errorCode; } if (!installedContainers.empty()) { QJsonObject server; QJsonArray containerConfigs; if (createNewServer) { server.insert(config_key::hostName, installCredentials.hostName); server.insert(config_key::userName, installCredentials.userName); server.insert(config_key::password, installCredentials.password); server.insert(config_key::port, installCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); } for (auto container = installedContainers.begin(); container != installedContainers.end(); container++) { if (isContainerAlreadyAddedToGui(container.key())) { continue; } if (createNewServer) { containerConfigs.append(container.value()); server.insert(config_key::containers, containerConfigs); } else { m_settings->setContainerConfig(serverIndex, container.key(), container.value()); m_settings->setDefaultContainer(serverIndex, installedContainers.firstKey()); } } if (createNewServer) { server.insert(config_key::defaultContainer, ContainerProps::containerToString(installedContainers.firstKey())); m_settings->addServer(server); m_settings->setDefaultServer(m_settings->serversCount() - 1); isServerCreated = true; } } return ErrorCode::NoError; } bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) { for (int i = 0; i < m_settings->serversCount(); i++) { const ServerCredentials credentials = m_settings->serverCredentials(i); if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { const QJsonObject containerConfig = m_settings->containerConfig(i, container); if (!containerConfig.isEmpty()) { return true; } } } return false; }