Handle matrix scheme

Link opening only works on Linux for now.

See https://github.com/matrix-org/matrix-doc/pull/2312
This commit is contained in:
Nicolas Werner 2021-01-10 18:36:06 +01:00
parent cc9de7f3b0
commit 39f9b7d90a
7 changed files with 216 additions and 17 deletions

View file

@ -8,3 +8,4 @@ Type=Application
Categories=Network;InstantMessaging;Qt; Categories=Network;InstantMessaging;Qt;
StartupWMClass=nheko StartupWMClass=nheko
Terminal=false Terminal=false
MimeType=x-scheme-handler/matrix;

View file

@ -2221,6 +2221,34 @@ Cache::getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb)
return QString("1"); 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 QString
Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
{ {

View file

@ -81,6 +81,7 @@ public:
std::vector<std::string> joinedRooms(); std::vector<std::string> joinedRooms();
QMap<QString, RoomInfo> roomInfo(bool withInvites = true); QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
std::optional<mtx::events::state::CanonicalAlias> getRoomAliases(const std::string &roomid);
std::map<QString, bool> invites(); std::map<QString, bool> invites();
//! Calculate & return the name of the room. //! Calculate & return the name of the room.

View file

@ -918,6 +918,8 @@ ChatPage::joinRoom(const QString &room)
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
emit showNotification(tr("Failed to remove invite: %1").arg(e.what())); 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); 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());
}

View file

@ -110,6 +110,10 @@ public:
mtx::presence::PresenceState currentPresence() const; mtx::presence::PresenceState currentPresence() const;
public slots: public slots:
void handleMatrixUri(const QByteArray &uri);
void handleMatrixUri(const QUrl &uri);
void startChat(QString userid);
void leaveRoom(const QString &room_id); void leaveRoom(const QString &room_id);
void createRoom(const mtx::requests::CreateRoom &req); void createRoom(const mtx::requests::CreateRoom &req);
void joinRoom(const QString &room); void joinRoom(const QString &room);

View file

@ -19,6 +19,7 @@
#include <QApplication> #include <QApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QDesktopServices>
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
@ -33,6 +34,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <QTranslator> #include <QTranslator>
#include "ChatPage.h"
#include "Config.h" #include "Config.h"
#include "Logging.h" #include "Logging.h"
#include "MainWindow.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 // This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
// parsed before the SingleApplication userdata is set. // parsed before the SingleApplication userdata is set.
QString userdata{""}; QString userdata{""};
QString matrixUri;
for (int i = 0; i < argc; ++i) { for (int i = 0; i < argc; ++i) {
if (QString{argv[i]}.startsWith("--profile=")) { QString arg{argv[i]};
QString q{argv[i]}; if (arg.startsWith("--profile=")) {
q.remove("--profile="); arg.remove("--profile=");
userdata = q; userdata = arg;
} else if (QString{argv[i]}.startsWith("--p=")) { } else if (arg.startsWith("--p=")) {
QString q{argv[i]}; arg.remove("-p=");
q.remove("-p="); userdata = arg;
userdata = q; } else if (arg == "--profile" || arg == "-p") {
} else if (QString{argv[i]} == "--profile" || QString{argv[i]} == "-p") {
if (i < argc - 1) // if i is less than argc - 1, we still have a parameter if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
// left to process as the name // left to process as the name
{ {
++i; // the next arg is the name, so increment ++i; // the next arg is the name, so increment
userdata = QString{argv[i]}; userdata = QString{argv[i]};
} }
} else if (arg.startsWith("matrix:")) {
matrixUri = arg;
} }
} }
SingleApplication app(argc, SingleApplication app(argc,
argv, argv,
false, true,
SingleApplication::Mode::User | SingleApplication::Mode::User |
SingleApplication::Mode::ExcludeAppPath | SingleApplication::Mode::ExcludeAppPath |
SingleApplication::Mode::ExcludeAppVersion, SingleApplication::Mode::ExcludeAppVersion |
SingleApplication::Mode::SecondaryNotification,
100, 100,
userdata); userdata);
if (app.isSecondary()) {
// open uri in main instance
app.sendMessage(matrixUri.toUtf8());
return 0;
}
QCommandLineParser parser; QCommandLineParser parser;
parser.addHelpOption(); parser.addHelpOption();
parser.addVersionOption(); parser.addVersionOption();
@ -245,6 +256,25 @@ main(int argc, char *argv[])
w.activateWindow(); 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) #if defined(Q_OS_MAC)
// Temporary solution for the emoji picker until // Temporary solution for the emoji picker until
// nheko has a proper menu bar with more functionality. // nheko has a proper menu bar with more functionality.

View file

@ -202,12 +202,7 @@ UserProfile::kickUser()
void void
UserProfile::startChat() UserProfile::startChat()
{ {
mtx::requests::CreateRoom req; ChatPage::instance()->startChat(this->userid_);
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);
} }
void void