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<mtx::events::state::CanonicalAlias>
+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<CanonicalAlias> 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<std::string> joinedRooms();
QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
+ std::optional<mtx::events::state::CanonicalAlias> getRoomAliases(const std::string &roomid);
std::map<QString, bool> 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<std::string> 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 <QApplication>
#include <QCommandLineParser>
+#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDir>
#include <QFile>
@@ -33,6 +34,7 @@
#include <QStandardPaths>
#include <QTranslator>
+#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
|