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 ¤tPassword, 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"