File indexing completed on 2024-10-06 12:53:59

0001 // SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
0002 // SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
0003 // SPDX-License-Identifier: GPL-3.0-only
0004 
0005 #include "controller.h"
0006 
0007 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0008 #include <qt5keychain/keychain.h>
0009 #else
0010 #include <qt6keychain/keychain.h>
0011 #endif
0012 
0013 #include <KConfig>
0014 #include <KConfigGroup>
0015 #include <KLocalizedString>
0016 #include <KWindowConfig>
0017 #ifdef HAVE_WINDOWSYSTEM
0018 #include <KWindowEffects>
0019 #endif
0020 
0021 #include <QFile>
0022 #include <QFileInfo>
0023 #include <QGuiApplication>
0024 #include <QImageReader>
0025 #include <QNetworkProxy>
0026 #include <QQuickTextDocument>
0027 #include <QQuickWindow>
0028 #include <QStandardPaths>
0029 #include <QStringBuilder>
0030 #include <QTimer>
0031 
0032 #include <signal.h>
0033 
0034 #include <Quotient/accountregistry.h>
0035 #include <Quotient/connection.h>
0036 #include <Quotient/csapi/content-repo.h>
0037 #include <Quotient/csapi/logout.h>
0038 #include <Quotient/csapi/profile.h>
0039 #include <Quotient/jobs/downloadfilejob.h>
0040 #include <Quotient/qt_connection_util.h>
0041 #include <Quotient/csapi/notifications.h>
0042 #include <Quotient/eventstats.h>
0043 
0044 #include "neochatconfig.h"
0045 #include "neochatroom.h"
0046 #include "neochatuser.h"
0047 #include "notificationsmanager.h"
0048 #include "roommanager.h"
0049 #include "windowcontroller.h"
0050 
0051 #if defined(Q_OS_WIN) || defined(Q_OS_MAC)
0052 #include "trayicon.h"
0053 #elif !defined(Q_OS_ANDROID)
0054 #include "trayicon_sni.h"
0055 #endif
0056 
0057 using namespace Quotient;
0058 
0059 Controller::Controller(QObject *parent)
0060     : QObject(parent)
0061 {
0062     Connection::setRoomType<NeoChatRoom>();
0063     Connection::setUserType<NeoChatUser>();
0064 
0065     setApplicationProxy();
0066 
0067 #ifndef Q_OS_ANDROID
0068     setQuitOnLastWindowClosed();
0069     connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
0070 #endif
0071 
0072     QTimer::singleShot(0, this, [this] {
0073         invokeLogin();
0074     });
0075 
0076     QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [] {
0077         NeoChatConfig::self()->save();
0078     });
0079 
0080 #ifndef Q_OS_WINDOWS
0081     // Setup Unix signal handlers
0082     const auto unixExitHandler = [](int /*sig*/) -> void {
0083         QCoreApplication::quit();
0084     };
0085 
0086     const int quitSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP};
0087 
0088     sigset_t blockingMask;
0089     sigemptyset(&blockingMask);
0090     for (const auto sig : quitSignals) {
0091         sigaddset(&blockingMask, sig);
0092     }
0093 
0094     struct sigaction sa;
0095     sa.sa_handler = unixExitHandler;
0096     sa.sa_mask = blockingMask;
0097     sa.sa_flags = 0;
0098 
0099     for (auto sig : quitSignals) {
0100         sigaction(sig, &sa, nullptr);
0101     }
0102 #endif
0103 
0104     connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, &Controller::activeConnectionIndexChanged);
0105 
0106     static int oldAccountCount = 0;
0107     connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
0108         if (m_accountRegistry.size() > oldAccountCount) {
0109             auto connection = m_accountRegistry.accounts()[m_accountRegistry.size() - 1];
0110             connect(connection, &Connection::syncDone, this, [connection]() {
0111                 NotificationsManager::instance().handleNotifications(connection);
0112             });
0113         }
0114         oldAccountCount = m_accountRegistry.size();
0115     });
0116 
0117     QTimer::singleShot(0, this, [this] {
0118         m_pushRuleModel = new PushRuleModel;
0119     });
0120 }
0121 
0122 Controller &Controller::instance()
0123 {
0124     static Controller _instance;
0125     return _instance;
0126 }
0127 
0128 void Controller::showWindow()
0129 {
0130     WindowController::instance().showAndRaiseWindow(QString());
0131 }
0132 
0133 void Controller::logout(Connection *conn, bool serverSideLogout)
0134 {
0135     if (!conn) {
0136         qCritical() << "Attempt to logout null connection";
0137         return;
0138     }
0139 
0140     SettingsGroup("Accounts").remove(conn->userId());
0141 
0142     QKeychain::DeletePasswordJob job(qAppName());
0143     job.setAutoDelete(true);
0144     job.setKey(conn->userId());
0145     QEventLoop loop;
0146     QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
0147     job.start();
0148     loop.exec();
0149 
0150     if (m_accountRegistry.count() > 1) {
0151         // Only set the connection if the the account being logged out is currently active
0152         if (conn == activeConnection()) {
0153             setActiveConnection(m_accountRegistry.accounts()[0]);
0154         }
0155     } else {
0156         setActiveConnection(nullptr);
0157     }
0158     if (!serverSideLogout) {
0159         return;
0160     }
0161     conn->logout();
0162 }
0163 
0164 void Controller::addConnection(Connection *c)
0165 {
0166     Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
0167 
0168     m_accountRegistry.add(c);
0169 
0170     c->setLazyLoading(true);
0171 
0172     connect(c, &Connection::syncDone, this, [this, c] {
0173         Q_EMIT syncDone();
0174 
0175         c->sync(30000);
0176         c->saveState();
0177     });
0178     connect(c, &Connection::loggedOut, this, [this, c] {
0179         dropConnection(c);
0180     });
0181 
0182     connect(c, &Connection::requestFailed, this, [this](BaseJob *job) {
0183         if (job->error() == BaseJob::UserConsentRequired) {
0184             Q_EMIT userConsentRequired(job->errorUrl());
0185         }
0186     });
0187 
0188     c->sync();
0189 
0190     Q_EMIT connectionAdded(c);
0191     Q_EMIT accountCountChanged();
0192 }
0193 
0194 void Controller::dropConnection(Connection *c)
0195 {
0196     Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
0197 
0198     Q_EMIT connectionDropped(c);
0199     Q_EMIT accountCountChanged();
0200 }
0201 
0202 void Controller::invokeLogin()
0203 {
0204     const auto accounts = SettingsGroup("Accounts").childGroups();
0205     QString id = NeoChatConfig::self()->activeConnection();
0206     for (const auto &accountId : accounts) {
0207         AccountSettings account{accountId};
0208         if (id.isEmpty()) {
0209             // handle case where the account config is empty
0210             id = accountId;
0211         }
0212         if (!account.homeserver().isEmpty()) {
0213             auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
0214             connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, id, this, accessTokenLoadingJob](QKeychain::Job *) {
0215                 AccountSettings account{accountId};
0216                 QString accessToken;
0217                 if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
0218                     accessToken = accessTokenLoadingJob->binaryData();
0219                 } else {
0220                     return;
0221                 }
0222 
0223                 auto connection = new Connection(account.homeserver());
0224                 connect(connection, &Connection::connected, this, [this, connection, id] {
0225                     connection->loadState();
0226                     addConnection(connection);
0227                     if (connection->userId() == id) {
0228                         setActiveConnection(connection);
0229                         connectSingleShot(connection, &Connection::syncDone, this, &Controller::initiated);
0230                     }
0231                 });
0232                 connect(connection, &Connection::loginError, this, [this, connection](const QString &error, const QString &) {
0233                     if (error == "Unrecognised access token") {
0234                         Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
0235                         logout(connection, false);
0236                     } else if (error == "Connection closed") {
0237                         Q_EMIT errorOccured(i18n("Login Failed: %1", error));
0238                         // Failed due to network connection issue. This might happen when the homeserver is
0239                         // temporary down, or the user trying to re-launch NeoChat in a network that cannot
0240                         // connect to the homeserver. In this case, we don't want to do logout().
0241                     } else {
0242                         Q_EMIT errorOccured(i18n("Login Failed: %1", error));
0243                         logout(connection, true);
0244                     }
0245                     Q_EMIT initiated();
0246                 });
0247                 connect(connection, &Connection::networkError, this, [this](const QString &error, const QString &, int, int) {
0248                     Q_EMIT errorOccured(i18n("Network Error: %1", error));
0249                 });
0250                 connection->assumeIdentity(account.userId(), accessToken);
0251             });
0252         }
0253     }
0254     if (accounts.isEmpty()) {
0255         Q_EMIT initiated();
0256     }
0257 }
0258 
0259 QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
0260 {
0261     qDebug() << "Reading access token from the keychain for" << account.userId();
0262     auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
0263     job->setKey(account.userId());
0264 
0265     // Handling of errors
0266     connect(job, &QKeychain::Job::finished, this, [this, job]() {
0267         if (job->error() == QKeychain::Error::NoError) {
0268             return;
0269         }
0270 
0271         switch (job->error()) {
0272         case QKeychain::EntryNotFound:
0273             Q_EMIT globalErrorOccured(i18n("Access token wasn't found"), i18n("Maybe it was deleted?"));
0274             break;
0275         case QKeychain::AccessDeniedByUser:
0276         case QKeychain::AccessDenied:
0277             Q_EMIT globalErrorOccured(i18n("Access to keychain was denied."), i18n("Please allow NeoChat to read the access token"));
0278             break;
0279         case QKeychain::NoBackendAvailable:
0280             Q_EMIT globalErrorOccured(i18n("No keychain available."), i18n("Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
0281             break;
0282         case QKeychain::OtherError:
0283             Q_EMIT globalErrorOccured(i18n("Unable to read access token"), job->errorString());
0284             break;
0285         default:
0286             break;
0287         }
0288     });
0289     job->start();
0290 
0291     return job;
0292 }
0293 
0294 bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
0295 {
0296     qDebug() << "Save the access token to the keychain for " << account.userId();
0297     QKeychain::WritePasswordJob job(qAppName());
0298     job.setAutoDelete(false);
0299     job.setKey(account.userId());
0300     job.setBinaryData(accessToken);
0301     QEventLoop loop;
0302     QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
0303     job.start();
0304     loop.exec();
0305 
0306     if (job.error()) {
0307         qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString());
0308         return false;
0309     }
0310     return true;
0311 }
0312 
0313 void Controller::changeAvatar(Connection *conn, const QUrl &localFile)
0314 {
0315     auto job = conn->uploadFile(localFile.toLocalFile());
0316     connect(job, &BaseJob::success, this, [conn, job] {
0317         conn->callApi<SetAvatarUrlJob>(conn->userId(), job->contentUri());
0318     });
0319 }
0320 
0321 void Controller::markAllMessagesAsRead(Connection *conn)
0322 {
0323     const auto rooms = conn->allRooms();
0324     for (auto room : rooms) {
0325         room->markAllMessagesAsRead();
0326     }
0327 }
0328 
0329 bool Controller::supportSystemTray() const
0330 {
0331 #ifdef Q_OS_ANDROID
0332     return false;
0333 #else
0334     QString de = getenv("XDG_CURRENT_DESKTOP");
0335     return de != QStringLiteral("GNOME") && de != QStringLiteral("Pantheon");
0336 #endif
0337 }
0338 
0339 void Controller::changePassword(Connection *connection, const QString &currentPassword, const QString &newPassword)
0340 {
0341     NeochatChangePasswordJob *job = connection->callApi<NeochatChangePasswordJob>(newPassword, false);
0342     connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword, connection] {
0343         if (job->error() == 103) {
0344             QJsonObject replyData = job->jsonData();
0345             QJsonObject authData;
0346             authData["session"] = replyData["session"];
0347             authData["password"] = currentPassword;
0348             authData["type"] = "m.login.password";
0349             authData["user"] = connection->user()->id();
0350             QJsonObject identifier = {{"type", "m.id.user"}, {"user", connection->user()->id()}};
0351             authData["identifier"] = identifier;
0352             NeochatChangePasswordJob *innerJob = connection->callApi<NeochatChangePasswordJob>(newPassword, false, authData);
0353             connect(innerJob, &BaseJob::success, this, [this]() {
0354                 Q_EMIT passwordStatus(PasswordStatus::Success);
0355             });
0356             connect(innerJob, &BaseJob::failure, this, [innerJob, this]() {
0357                 if (innerJob->jsonData()["errcode"] == "M_FORBIDDEN") {
0358                     Q_EMIT passwordStatus(PasswordStatus::Wrong);
0359                 } else {
0360                     Q_EMIT passwordStatus(PasswordStatus::Other);
0361                 }
0362             });
0363         }
0364     });
0365 }
0366 
0367 bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource)
0368 {
0369     User *localUser = connection->user();
0370     QString decoded = avatarSource.path();
0371     if (decoded.isEmpty()) {
0372         connection->callApi<SetAvatarUrlJob>(localUser->id(), avatarSource);
0373         return true;
0374     }
0375     if (QImageReader(decoded).read().isNull()) {
0376         return false;
0377     } else {
0378         return localUser->setAvatar(decoded);
0379     }
0380 }
0381 
0382 NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
0383     : BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), "/_matrix/client/r0/account/password")
0384 {
0385     QJsonObject _data;
0386     addParam<>(_data, QStringLiteral("new_password"), newPassword);
0387     addParam<IfNotEmpty>(_data, QStringLiteral("logout_devices"), logoutDevices);
0388     addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth);
0389     setRequestData(_data);
0390 }
0391 
0392 int Controller::accountCount() const
0393 {
0394     return m_accountRegistry.count();
0395 }
0396 
0397 void Controller::setQuitOnLastWindowClosed()
0398 {
0399 #ifndef Q_OS_ANDROID
0400     if (NeoChatConfig::self()->systemTray()) {
0401         m_trayIcon = new TrayIcon(this);
0402         m_trayIcon->show();
0403         connect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
0404     } else {
0405         if (m_trayIcon) {
0406             disconnect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
0407             delete m_trayIcon;
0408             m_trayIcon = nullptr;
0409         }
0410     }
0411     QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
0412 #else
0413     return;
0414 #endif
0415 }
0416 
0417 Connection *Controller::activeConnection() const
0418 {
0419     if (m_connection.isNull()) {
0420         return nullptr;
0421     }
0422     return m_connection;
0423 }
0424 
0425 void Controller::setActiveConnection(Connection *connection)
0426 {
0427     if (connection == m_connection) {
0428         return;
0429     }
0430     if (m_connection != nullptr) {
0431         disconnect(m_connection, &Connection::syncError, this, nullptr);
0432         disconnect(m_connection, &Connection::accountDataChanged, this, nullptr);
0433     }
0434     m_connection = connection;
0435     if (connection != nullptr) {
0436         NeoChatConfig::self()->setActiveConnection(connection->userId());
0437         connect(connection, &Connection::networkError, this, [this]() {
0438             if (!m_isOnline) {
0439                 return;
0440             }
0441             m_isOnline = false;
0442             Q_EMIT isOnlineChanged(false);
0443         });
0444         connect(connection, &Connection::syncDone, this, [this] {
0445             if (m_isOnline) {
0446                 return;
0447             }
0448             m_isOnline = true;
0449             Q_EMIT isOnlineChanged(true);
0450         });
0451         connect(connection, &Connection::requestFailed, this, [](BaseJob *job) {
0452             if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"].toString() == "M_TOO_LARGE"_ls) {
0453                 RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
0454             }
0455         });
0456         connect(connection, &Connection::accountDataChanged, this, [this](const QString &type) {
0457             if (type == QLatin1String("org.kde.neochat.account_label")) {
0458                 Q_EMIT activeAccountLabelChanged();
0459             }
0460         });
0461     } else {
0462         NeoChatConfig::self()->setActiveConnection(QString());
0463     }
0464     NeoChatConfig::self()->save();
0465     Q_EMIT activeConnectionChanged();
0466     Q_EMIT activeConnectionIndexChanged();
0467     Q_EMIT activeAccountLabelChanged();
0468 }
0469 
0470 PushRuleModel *Controller::pushRuleModel() const
0471 {
0472     return m_pushRuleModel;
0473 }
0474 
0475 void Controller::saveWindowGeometry()
0476 {
0477     WindowController::instance().saveGeometry();
0478 }
0479 
0480 NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
0481     : Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId).toLatin1())
0482 {
0483     QJsonObject _data;
0484     addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth);
0485     setRequestData(std::move(_data));
0486 }
0487 
0488 void Controller::createRoom(const QString &name, const QString &topic)
0489 {
0490     auto createRoomJob = m_connection->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
0491     connect(createRoomJob, &CreateRoomJob::failure, this, [this, createRoomJob] {
0492         Q_EMIT errorOccured(i18n("Room creation failed: %1", createRoomJob->errorString()));
0493     });
0494     connectSingleShot(this, &Controller::roomAdded, &RoomManager::instance(), &RoomManager::enterRoom, Qt::QueuedConnection);
0495 }
0496 
0497 void Controller::createSpace(const QString &name, const QString &topic)
0498 {
0499     auto createRoomJob = m_connection->createRoom(Connection::UnpublishRoom,
0500                                                   {},
0501                                                   name,
0502                                                   topic,
0503                                                   QStringList(),
0504                                                   {},
0505                                                   {},
0506                                                   false,
0507                                                   {},
0508                                                   {},
0509                                                   QJsonObject{
0510                                                       {"type"_ls, "m.space"_ls},
0511                                                   });
0512     connect(createRoomJob, &CreateRoomJob::failure, this, [this, createRoomJob] {
0513         Q_EMIT errorOccured(i18n("Space creation failed: %1", createRoomJob->errorString()));
0514     });
0515     connectSingleShot(this, &Controller::roomAdded, &RoomManager::instance(), &RoomManager::enterRoom, Qt::QueuedConnection);
0516 }
0517 
0518 bool Controller::isOnline() const
0519 {
0520     return m_isOnline;
0521 }
0522 
0523 // TODO: Remove in favor of RoomManager::joinRoom
0524 void Controller::joinRoom(const QString &alias)
0525 {
0526     if (!alias.contains(":")) {
0527         Q_EMIT errorOccured(i18n("The room id you are trying to join is not valid"));
0528         return;
0529     }
0530 
0531     const auto knownServer = alias.mid(alias.indexOf(":") + 1);
0532     RoomManager::instance().joinRoom(m_connection, alias, QStringList{knownServer});
0533 }
0534 
0535 void Controller::openOrCreateDirectChat(NeoChatUser *user)
0536 {
0537     const auto existing = activeConnection()->directChats();
0538 
0539     if (existing.contains(user)) {
0540         const auto &room = static_cast<NeoChatRoom *>(activeConnection()->room(existing.value(user)));
0541         if (room) {
0542             RoomManager::instance().enterRoom(room);
0543             return;
0544         }
0545     }
0546     activeConnection()->requestDirectChat(user);
0547 }
0548 
0549 QString Controller::formatByteSize(double size, int precision) const
0550 {
0551     return QLocale().formattedDataSize(size, precision);
0552 }
0553 
0554 QString Controller::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const
0555 {
0556     return KFormat().formatDuration(msecs, options);
0557 }
0558 
0559 void Controller::setBlur(QQuickItem *item, bool blur)
0560 {
0561 #ifdef HAVE_WINDOWSYSTEM
0562     auto setWindows = [item, blur]() {
0563         auto reg = QRect(QPoint(0, 0), item->window()->size());
0564         KWindowEffects::enableBackgroundContrast(item->window(), blur, 1, 1, 1, reg);
0565         KWindowEffects::enableBlurBehind(item->window(), blur, reg);
0566     };
0567 
0568     disconnect(item->window(), &QQuickWindow::heightChanged, this, nullptr);
0569     disconnect(item->window(), &QQuickWindow::widthChanged, this, nullptr);
0570     connect(item->window(), &QQuickWindow::heightChanged, this, setWindows);
0571     connect(item->window(), &QQuickWindow::widthChanged, this, setWindows);
0572     setWindows();
0573 #endif
0574 }
0575 
0576 bool Controller::hasWindowSystem() const
0577 {
0578 #ifdef HAVE_WINDOWSYSTEM
0579     return true;
0580 #else
0581     return false;
0582 #endif
0583 }
0584 
0585 bool Controller::encryptionSupported() const
0586 {
0587     return Quotient::encryptionSupported();
0588 }
0589 
0590 void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
0591 {
0592     // HACK: Workaround bug QTBUG 93281
0593     connect(textDocument->textDocument(), SIGNAL(imagesLoaded()), item, SLOT(updateWholeDocument()));
0594 }
0595 
0596 void Controller::setApplicationProxy()
0597 {
0598     NeoChatConfig *cfg = NeoChatConfig::self();
0599     QNetworkProxy proxy;
0600 
0601     // type match to ProxyType from neochatconfig.kcfg
0602     switch (cfg->proxyType()) {
0603     case 1: // HTTP
0604         proxy.setType(QNetworkProxy::HttpProxy);
0605         proxy.setHostName(cfg->proxyHost());
0606         proxy.setPort(cfg->proxyPort());
0607         proxy.setUser(cfg->proxyUser());
0608         proxy.setPassword(cfg->proxyPassword());
0609         break;
0610     case 2: // SOCKS 5
0611         proxy.setType(QNetworkProxy::Socks5Proxy);
0612         proxy.setHostName(cfg->proxyHost());
0613         proxy.setPort(cfg->proxyPort());
0614         proxy.setUser(cfg->proxyUser());
0615         proxy.setPassword(cfg->proxyPassword());
0616         break;
0617     case 0: // System Default
0618     default:
0619         // do nothing
0620         break;
0621     }
0622 
0623     QNetworkProxy::setApplicationProxy(proxy);
0624 }
0625 
0626 int Controller::activeConnectionIndex() const
0627 {
0628     auto result = std::find_if(m_accountRegistry.accounts().begin(), m_accountRegistry.accounts().end(), [this](const auto &it) {
0629         return it == m_connection;
0630     });
0631     return result - m_accountRegistry.accounts().begin();
0632 }
0633 
0634 int Controller::quotientMinorVersion() const
0635 {
0636     // TODO libQuotient 0.7: Replace with version function from libQuotient
0637     return 7;
0638 }
0639 
0640 bool Controller::isFlatpak() const
0641 {
0642 #ifdef NEOCHAT_FLATPAK
0643     return true;
0644 #else
0645     return false;
0646 #endif
0647 }
0648 
0649 void Controller::setActiveAccountLabel(const QString &label)
0650 {
0651     if (!m_connection) {
0652         return;
0653     }
0654     QJsonObject json{
0655         {"account_label", label},
0656     };
0657     m_connection->setAccountData("org.kde.neochat.account_label", json);
0658 }
0659 
0660 QString Controller::activeAccountLabel() const
0661 {
0662     if (!m_connection) {
0663         return {};
0664     }
0665     return m_connection->accountDataJson("org.kde.neochat.account_label")["account_label"].toString();
0666 }
0667 
0668 QVariantList Controller::getSupportedRoomVersions(Quotient::Connection *connection)
0669 {
0670     auto roomVersions = connection->availableRoomVersions();
0671 
0672     QVariantList supportedRoomVersions;
0673     for (const Quotient::Connection::SupportedRoomVersion &v : roomVersions) {
0674         QVariantMap roomVersionMap;
0675         roomVersionMap.insert("id", v.id);
0676         roomVersionMap.insert("status", v.status);
0677         roomVersionMap.insert("isStable", v.isStable());
0678         supportedRoomVersions.append(roomVersionMap);
0679     }
0680 
0681     return supportedRoomVersions;
0682 }
0683 
0684 AccountRegistry &Controller::accounts()
0685 {
0686     return m_accountRegistry;
0687 }
0688 
0689 #include "moc_controller.cpp"