From 3a41bb9fff45c7e87ee1ce647155a86ce4f227b1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 5 Jan 2021 22:10:40 +0100 Subject: Fix typing notifications flickering sometimes --- src/Cache.cpp | 77 +++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 26 deletions(-) (limited to 'src/Cache.cpp') diff --git a/src/Cache.cpp b/src/Cache.cpp index dac0b23a..04046346 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -124,17 +124,15 @@ Cache::isHiddenEvent(lmdb::txn &txn, EventType::Reaction, EventType::CallCandidates, EventType::Unsupported}; if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, "")) - hiddenEvents = std::move( - std::get< - mtx::events::Event>( - *temp) - .content); + hiddenEvents = + std::move(std::get>(*temp) + .content); if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id)) - hiddenEvents = std::move( - std::get< - mtx::events::Event>( - *temp) - .content); + hiddenEvents = + std::move(std::get>(*temp) + .content); return std::visit( [hiddenEvents](const auto &ev) { @@ -1197,7 +1195,7 @@ void Cache::saveState(const mtx::responses::Sync &res) { using namespace mtx::events; - auto user_id = this->localUserId_.toStdString(); + auto local_user_id = this->localUserId_.toStdString(); auto currentBatchToken = nextBatchToken(); @@ -1252,13 +1250,19 @@ Cache::saveState(const mtx::responses::Sync &res) evt); // for tag events - if (std::holds_alternative>(evt)) { - auto tags_evt = std::get>(evt); - has_new_tags = true; + if (std::holds_alternative>( + evt)) { + auto tags_evt = + std::get>(evt); + has_new_tags = true; for (const auto &tag : tags_evt.content.tags) { updatedInfo.tags.push_back(tag.first); } } + if (auto fr = std::get_if>(&evt)) { + nhlog::db()->debug("Fully read: {}", fr->content.event_id); + } } if (!has_new_tags) { // retrieve the old tags, they haven't changed @@ -1282,7 +1286,20 @@ Cache::saveState(const mtx::responses::Sync &res) lmdb::dbi_put( txn, roomsDb_, lmdb::val(room.first), lmdb::val(json(updatedInfo).dump())); - updateReadReceipt(txn, room.first, room.second.ephemeral.receipts); + for (const auto &e : room.second.ephemeral.events) { + if (auto receiptsEv = std::get_if< + mtx::events::EphemeralEvent>(&e)) { + Receipts receipts; + + for (const auto &[event_id, userReceipts] : + receiptsEv->content.receipts) { + for (const auto &[user_id, receipt] : userReceipts.users) { + receipts[event_id][user_id] = receipt.ts; + } + } + updateReadReceipt(txn, room.first, receipts); + } + } // Clean up non-valid invites. removeInvite(txn, room.first); @@ -1302,19 +1319,27 @@ Cache::saveState(const mtx::responses::Sync &res) std::map readStatus; for (const auto &room : res.rooms.join) { - if (!room.second.ephemeral.receipts.empty()) { - std::vector receipts; - for (const auto &receipt : room.second.ephemeral.receipts) { - for (const auto &receiptUsersTs : receipt.second) { - if (receiptUsersTs.first != user_id) { - receipts.push_back( - QString::fromStdString(receipt.first)); - break; + for (const auto &e : room.second.ephemeral.events) { + if (auto receiptsEv = std::get_if< + mtx::events::EphemeralEvent>(&e)) { + std::vector receipts; + + for (const auto &[event_id, userReceipts] : + receiptsEv->content.receipts) { + for (const auto &[user_id, receipt] : userReceipts.users) { + (void)receipt; + + if (user_id != local_user_id) { + receipts.push_back( + QString::fromStdString(event_id)); + break; + } } } + if (!receipts.empty()) + emit newReadReceipts(QString::fromStdString(room.first), + receipts); } - if (!receipts.empty()) - emit newReadReceipts(QString::fromStdString(room.first), receipts); } readStatus.emplace(QString::fromStdString(room.first), calculateRoomReadStatus(room.first)); @@ -1440,7 +1465,7 @@ Cache::roomsWithTagUpdates(const mtx::responses::Sync &res) for (const auto &room : res.rooms.join) { bool hasUpdates = false; for (const auto &evt : room.second.account_data.events) { - if (std::holds_alternative>(evt)) { + if (std::holds_alternative>(evt)) { hasUpdates = true; } } -- cgit 1.5.1 From 39f9b7d90adbdbc9eb6186d93bb6bfd0564c152c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 10 Jan 2021 18:36:06 +0100 Subject: Handle matrix scheme Link opening only works on Linux for now. See https://github.com/matrix-org/matrix-doc/pull/2312 --- resources/nheko.desktop | 1 + src/Cache.cpp | 28 ++++++++++ src/Cache_p.h | 1 + src/ChatPage.cpp | 140 ++++++++++++++++++++++++++++++++++++++++++++++++ src/ChatPage.h | 4 ++ src/main.cpp | 52 ++++++++++++++---- src/ui/UserProfile.cpp | 7 +-- 7 files changed, 216 insertions(+), 17 deletions(-) (limited to 'src/Cache.cpp') diff --git a/resources/nheko.desktop b/resources/nheko.desktop index 16e04926..4404e460 100644 --- a/resources/nheko.desktop +++ b/resources/nheko.desktop @@ -8,3 +8,4 @@ Type=Application Categories=Network;InstantMessaging;Qt; StartupWMClass=nheko Terminal=false +MimeType=x-scheme-handler/matrix; diff --git a/src/Cache.cpp b/src/Cache.cpp index 04046346..17b55144 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -2221,6 +2221,34 @@ Cache::getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb) return QString("1"); } +std::optional +Cache::getRoomAliases(const std::string &roomid) +{ + using namespace mtx::events; + using namespace mtx::events::state; + + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + auto statesdb = getStatesDb(txn, roomid); + + lmdb::val event; + bool res = lmdb::dbi_get( + txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomCanonicalAlias)), event); + + if (res) { + try { + StateEvent msg = + json::parse(std::string_view(event.data(), event.size())); + + return msg.content; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", + e.what()); + } + } + + return std::nullopt; +} + QString Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { diff --git a/src/Cache_p.h b/src/Cache_p.h index 059c1461..e2ce1668 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -81,6 +81,7 @@ public: std::vector joinedRooms(); QMap roomInfo(bool withInvites = true); + std::optional getRoomAliases(const std::string &roomid); std::map invites(); //! Calculate & return the name of the room. diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 238c9362..33c993ae 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -918,6 +918,8 @@ ChatPage::joinRoom(const QString &room) } catch (const lmdb::error &e) { emit showNotification(tr("Failed to remove invite: %1").arg(e.what())); } + + room_list_->highlightSelectedRoom(QString::fromStdString(room_id)); }); } @@ -1268,3 +1270,141 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio cache::storeSecret(secretName, decrypted); } } + +void +ChatPage::startChat(QString userid) +{ + auto joined_rooms = cache::joinedRooms(); + auto room_infos = cache::getRoomInfo(joined_rooms); + + for (std::string room_id : joined_rooms) { + if (room_infos[QString::fromStdString(room_id)].member_count == 2) { + auto room_members = cache::roomMembers(room_id); + if (std::find(room_members.begin(), + room_members.end(), + (userid).toStdString()) != room_members.end()) { + room_list_->highlightSelectedRoom(QString::fromStdString(room_id)); + return; + } + } + } + + mtx::requests::CreateRoom req; + req.preset = mtx::requests::Preset::PrivateChat; + req.visibility = mtx::requests::Visibility::Private; + if (utils::localUser() != userid) + req.invite = {userid.toStdString()}; + emit ChatPage::instance()->createRoom(req); +} + +static QString +mxidFromSegments(QStringRef sigil, QStringRef mxid) +{ + if (mxid.isEmpty()) + return ""; + + auto mxid_ = QUrl::fromPercentEncoding(mxid.toUtf8()); + + if (sigil == "user") { + return "@" + mxid_; + } else if (sigil == "roomid") { + return "!" + mxid_; + } else if (sigil == "room") { + return "#" + mxid_; + } else if (sigil == "group") { + return "+" + mxid_; + } else { + return ""; + } +} + +void +ChatPage::handleMatrixUri(const QByteArray &uri) +{ + nhlog::ui()->info("Received uri! {}", uri.toStdString()); + QUrl uri_{QString::fromUtf8(uri)}; + + if (uri_.scheme() != "matrix") + return; + + auto tempPath = uri_.path(QUrl::ComponentFormattingOption::FullyEncoded); + if (tempPath.startsWith('/')) + tempPath.remove(0, 1); + auto segments = tempPath.splitRef('/'); + + if (segments.size() != 2 && segments.size() != 4) + return; + + auto sigil1 = segments[0]; + auto mxid1 = mxidFromSegments(sigil1, segments[1]); + if (mxid1.isEmpty()) + return; + + QString mxid2; + if (segments.size() == 4 && segments[2] == "event") { + if (segments[3].isEmpty()) + return; + else + mxid2 = "$" + QUrl::fromPercentEncoding(segments[3].toUtf8()); + } + + std::vector vias; + QString action; + + for (QString item : uri_.query(QUrl::ComponentFormattingOption::FullyEncoded).split('&')) { + nhlog::ui()->info("item: {}", item.toStdString()); + + if (item.startsWith("action=")) { + action = item.remove("action="); + } else if (item.startsWith("via=")) { + vias.push_back( + QUrl::fromPercentEncoding(item.remove("via=").toUtf8()).toStdString()); + } + } + + if (sigil1 == "user") { + if (action.isEmpty()) { + view_manager_->activeTimeline()->openUserProfile(mxid1); + } else if (action == "chat") { + this->startChat(mxid1); + } + } else if (sigil1 == "roomid") { + auto joined_rooms = cache::joinedRooms(); + auto targetRoomId = mxid1.toStdString(); + + for (auto roomid : joined_rooms) { + if (roomid == targetRoomId) { + room_list_->highlightSelectedRoom(mxid1); + break; + } + } + + if (action == "join") { + joinRoom(mxid1); + } + } else if (sigil1 == "room") { + auto joined_rooms = cache::joinedRooms(); + auto targetRoomAlias = mxid1.toStdString(); + + for (auto roomid : joined_rooms) { + auto aliases = cache::client()->getRoomAliases(roomid); + if (aliases) { + if (aliases->alias == targetRoomAlias) { + room_list_->highlightSelectedRoom( + QString::fromStdString(roomid)); + break; + } + } + } + + if (action == "join") { + joinRoom(mxid1); + } + } +} + +void +ChatPage::handleMatrixUri(const QUrl &uri) +{ + handleMatrixUri(uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8()); +} diff --git a/src/ChatPage.h b/src/ChatPage.h index 45a4ff63..004bb3e8 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -110,6 +110,10 @@ public: mtx::presence::PresenceState currentPresence() const; public slots: + void handleMatrixUri(const QByteArray &uri); + void handleMatrixUri(const QUrl &uri); + + void startChat(QString userid); void leaveRoom(const QString &room_id); void createRoom(const mtx::requests::CreateRoom &req); void joinRoom(const QString &room); diff --git a/src/main.cpp b/src/main.cpp index a60c66c4..7a417ae2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include +#include "ChatPage.h" #include "Config.h" #include "Logging.h" #include "MainWindow.h" @@ -128,34 +130,43 @@ main(int argc, char *argv[]) // This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name // parsed before the SingleApplication userdata is set. QString userdata{""}; + QString matrixUri; for (int i = 0; i < argc; ++i) { - if (QString{argv[i]}.startsWith("--profile=")) { - QString q{argv[i]}; - q.remove("--profile="); - userdata = q; - } else if (QString{argv[i]}.startsWith("--p=")) { - QString q{argv[i]}; - q.remove("-p="); - userdata = q; - } else if (QString{argv[i]} == "--profile" || QString{argv[i]} == "-p") { + QString arg{argv[i]}; + if (arg.startsWith("--profile=")) { + arg.remove("--profile="); + userdata = arg; + } else if (arg.startsWith("--p=")) { + arg.remove("-p="); + userdata = arg; + } else if (arg == "--profile" || arg == "-p") { if (i < argc - 1) // if i is less than argc - 1, we still have a parameter // left to process as the name { ++i; // the next arg is the name, so increment userdata = QString{argv[i]}; } + } else if (arg.startsWith("matrix:")) { + matrixUri = arg; } } SingleApplication app(argc, argv, - false, + true, SingleApplication::Mode::User | SingleApplication::Mode::ExcludeAppPath | - SingleApplication::Mode::ExcludeAppVersion, + SingleApplication::Mode::ExcludeAppVersion | + SingleApplication::Mode::SecondaryNotification, 100, userdata); + if (app.isSecondary()) { + // open uri in main instance + app.sendMessage(matrixUri.toUtf8()); + return 0; + } + QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); @@ -245,6 +256,25 @@ main(int argc, char *argv[]) w.activateWindow(); }); + QObject::connect( + &app, + &SingleApplication::receivedMessage, + ChatPage::instance(), + [&](quint32, QByteArray message) { ChatPage::instance()->handleMatrixUri(message); }); + + QMetaObject::Connection uriConnection; + if (app.isPrimary() && !matrixUri.isEmpty()) { + uriConnection = QObject::connect(ChatPage::instance(), + &ChatPage::contentLoaded, + ChatPage::instance(), + [&uriConnection, matrixUri]() { + ChatPage::instance()->handleMatrixUri( + matrixUri.toUtf8()); + QObject::disconnect(uriConnection); + }); + } + QDesktopServices::setUrlHandler("matrix", ChatPage::instance(), "handleMatrixUri"); + #if defined(Q_OS_MAC) // Temporary solution for the emoji picker until // nheko has a proper menu bar with more functionality. diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 974aa5cc..6ef82123 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -202,12 +202,7 @@ UserProfile::kickUser() void UserProfile::startChat() { - mtx::requests::CreateRoom req; - req.preset = mtx::requests::Preset::PrivateChat; - req.visibility = mtx::requests::Visibility::Private; - if (utils::localUser() != this->userid_) - req.invite = {this->userid_.toStdString()}; - emit ChatPage::instance()->createRoom(req); + ChatPage::instance()->startChat(this->userid_); } void -- cgit 1.5.1