summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2021-01-10 18:36:06 +0100
committerNicolas Werner <nicolas.werner@hotmail.de>2021-01-10 22:41:56 +0100
commit39f9b7d90adbdbc9eb6186d93bb6bfd0564c152c (patch)
tree600a18ee72fe9e1e2a508ff8c11673ae5a6250ed /src
parentFix some nulls in relations (diff)
downloadnheko-39f9b7d90adbdbc9eb6186d93bb6bfd0564c152c.tar.xz
Handle matrix scheme
Link opening only works on Linux for now.

See https://github.com/matrix-org/matrix-doc/pull/2312
Diffstat (limited to 'src')
-rw-r--r--src/Cache.cpp28
-rw-r--r--src/Cache_p.h1
-rw-r--r--src/ChatPage.cpp140
-rw-r--r--src/ChatPage.h4
-rw-r--r--src/main.cpp52
-rw-r--r--src/ui/UserProfile.cpp7
6 files changed, 215 insertions, 17 deletions
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