Skip to content

Commit 78f6628

Browse files
committed
Handle matrix scheme
Link opening only works on Linux for now.
1 parent cc9de7f commit 78f6628

7 files changed

Lines changed: 216 additions & 17 deletions

File tree

resources/nheko.desktop

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Type=Application
88
Categories=Network;InstantMessaging;Qt;
99
StartupWMClass=nheko
1010
Terminal=false
11+
MimeType=x-scheme-handler/matrix;

src/Cache.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,6 +2221,34 @@ Cache::getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb)
22212221
return QString("1");
22222222
}
22232223

2224+
std::optional<mtx::events::state::CanonicalAlias>
2225+
Cache::getRoomAliases(const std::string &roomid)
2226+
{
2227+
using namespace mtx::events;
2228+
using namespace mtx::events::state;
2229+
2230+
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
2231+
auto statesdb = getStatesDb(txn, roomid);
2232+
2233+
lmdb::val event;
2234+
bool res = lmdb::dbi_get(
2235+
txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomCanonicalAlias)), event);
2236+
2237+
if (res) {
2238+
try {
2239+
StateEvent<CanonicalAlias> msg =
2240+
json::parse(std::string_view(event.data(), event.size()));
2241+
2242+
return msg.content;
2243+
} catch (const json::exception &e) {
2244+
nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}",
2245+
e.what());
2246+
}
2247+
}
2248+
2249+
return std::nullopt;
2250+
}
2251+
22242252
QString
22252253
Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
22262254
{

src/Cache_p.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class Cache : public QObject
8181
std::vector<std::string> joinedRooms();
8282

8383
QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
84+
std::optional<mtx::events::state::CanonicalAlias> getRoomAliases(const std::string &roomid);
8485
std::map<QString, bool> invites();
8586

8687
//! Calculate & return the name of the room.

src/ChatPage.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,8 @@ ChatPage::joinRoom(const QString &room)
918918
} catch (const lmdb::error &e) {
919919
emit showNotification(tr("Failed to remove invite: %1").arg(e.what()));
920920
}
921+
922+
room_list_->highlightSelectedRoom(QString::fromStdString(room_id));
921923
});
922924
}
923925

@@ -1268,3 +1270,141 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio
12681270
cache::storeSecret(secretName, decrypted);
12691271
}
12701272
}
1273+
1274+
void
1275+
ChatPage::startChat(QString userid)
1276+
{
1277+
auto joined_rooms = cache::joinedRooms();
1278+
auto room_infos = cache::getRoomInfo(joined_rooms);
1279+
1280+
for (std::string room_id : joined_rooms) {
1281+
if (room_infos[QString::fromStdString(room_id)].member_count == 2) {
1282+
auto room_members = cache::roomMembers(room_id);
1283+
if (std::find(room_members.begin(),
1284+
room_members.end(),
1285+
(userid).toStdString()) != room_members.end()) {
1286+
room_list_->highlightSelectedRoom(QString::fromStdString(room_id));
1287+
return;
1288+
}
1289+
}
1290+
}
1291+
1292+
mtx::requests::CreateRoom req;
1293+
req.preset = mtx::requests::Preset::PrivateChat;
1294+
req.visibility = mtx::requests::Visibility::Private;
1295+
if (utils::localUser() != userid)
1296+
req.invite = {userid.toStdString()};
1297+
emit ChatPage::instance()->createRoom(req);
1298+
}
1299+
1300+
static QString
1301+
mxidFromSegments(QStringRef sigil, QStringRef mxid)
1302+
{
1303+
if (mxid.isEmpty())
1304+
return "";
1305+
1306+
auto mxid_ = QUrl::fromPercentEncoding(mxid.toUtf8());
1307+
1308+
if (sigil == "user") {
1309+
return "@" + mxid_;
1310+
} else if (sigil == "roomid") {
1311+
return "!" + mxid_;
1312+
} else if (sigil == "room") {
1313+
return "#" + mxid_;
1314+
} else if (sigil == "group") {
1315+
return "+" + mxid_;
1316+
} else {
1317+
return "";
1318+
}
1319+
}
1320+
1321+
void
1322+
ChatPage::handleMatrixUri(const QByteArray &uri)
1323+
{
1324+
nhlog::ui()->info("Received uri! {}", uri.toStdString());
1325+
QUrl uri_{QString::fromUtf8(uri)};
1326+
1327+
if (uri_.scheme() != "matrix")
1328+
return;
1329+
1330+
auto tempPath = uri_.path(QUrl::ComponentFormattingOption::FullyEncoded);
1331+
if (tempPath.startsWith('/'))
1332+
tempPath.remove(0, 1);
1333+
auto segments = tempPath.splitRef('/');
1334+
1335+
if (segments.size() != 2 && segments.size() != 4)
1336+
return;
1337+
1338+
auto sigil1 = segments[0];
1339+
auto mxid1 = mxidFromSegments(sigil1, segments[1]);
1340+
if (mxid1.isEmpty())
1341+
return;
1342+
1343+
QString mxid2;
1344+
if (segments.size() == 4 && segments[2] == "event") {
1345+
if (segments[3].isEmpty())
1346+
return;
1347+
else
1348+
mxid2 = "$" + QUrl::fromPercentEncoding(segments[3].toUtf8());
1349+
}
1350+
1351+
std::vector<std::string> vias;
1352+
QString action;
1353+
1354+
for (QString item : uri_.query(QUrl::ComponentFormattingOption::FullyEncoded).split('&')) {
1355+
nhlog::ui()->info("item: {}", item.toStdString());
1356+
1357+
if (item.startsWith("action=")) {
1358+
action = item.remove("action=");
1359+
} else if (item.startsWith("via=")) {
1360+
vias.push_back(
1361+
QUrl::fromPercentEncoding(item.remove("via=").toUtf8()).toStdString());
1362+
}
1363+
}
1364+
1365+
if (sigil1 == "user") {
1366+
if (action.isEmpty()) {
1367+
view_manager_->activeTimeline()->openUserProfile(mxid1);
1368+
} else if (action == "chat") {
1369+
this->startChat(mxid1);
1370+
}
1371+
} else if (sigil1 == "roomid") {
1372+
auto joined_rooms = cache::joinedRooms();
1373+
auto targetRoomId = mxid1.toStdString();
1374+
1375+
for (auto roomid : joined_rooms) {
1376+
if (roomid == targetRoomId) {
1377+
room_list_->highlightSelectedRoom(mxid1);
1378+
break;
1379+
}
1380+
}
1381+
1382+
if (action == "join") {
1383+
joinRoom(mxid1);
1384+
}
1385+
} else if (sigil1 == "room") {
1386+
auto joined_rooms = cache::joinedRooms();
1387+
auto targetRoomAlias = mxid1.toStdString();
1388+
1389+
for (auto roomid : joined_rooms) {
1390+
auto aliases = cache::client()->getRoomAliases(roomid);
1391+
if (aliases) {
1392+
if (aliases->alias == targetRoomAlias) {
1393+
room_list_->highlightSelectedRoom(
1394+
QString::fromStdString(roomid));
1395+
break;
1396+
}
1397+
}
1398+
}
1399+
1400+
if (action == "join") {
1401+
joinRoom(mxid1);
1402+
}
1403+
}
1404+
}
1405+
1406+
void
1407+
ChatPage::handleMatrixUri(const QUrl &uri)
1408+
{
1409+
handleMatrixUri(uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8());
1410+
}

src/ChatPage.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ class ChatPage : public QWidget
110110
mtx::presence::PresenceState currentPresence() const;
111111

112112
public slots:
113+
void handleMatrixUri(const QByteArray &uri);
114+
void handleMatrixUri(const QUrl &uri);
115+
116+
void startChat(QString userid);
113117
void leaveRoom(const QString &room_id);
114118
void createRoom(const mtx::requests::CreateRoom &req);
115119
void joinRoom(const QString &room);

src/main.cpp

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include <QApplication>
2121
#include <QCommandLineParser>
22+
#include <QDesktopServices>
2223
#include <QDesktopWidget>
2324
#include <QDir>
2425
#include <QFile>
@@ -33,6 +34,7 @@
3334
#include <QStandardPaths>
3435
#include <QTranslator>
3536

37+
#include "ChatPage.h"
3638
#include "Config.h"
3739
#include "Logging.h"
3840
#include "MainWindow.h"
@@ -128,34 +130,43 @@ main(int argc, char *argv[])
128130
// This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
129131
// parsed before the SingleApplication userdata is set.
130132
QString userdata{""};
133+
QString matrixUri;
131134
for (int i = 0; i < argc; ++i) {
132-
if (QString{argv[i]}.startsWith("--profile=")) {
133-
QString q{argv[i]};
134-
q.remove("--profile=");
135-
userdata = q;
136-
} else if (QString{argv[i]}.startsWith("--p=")) {
137-
QString q{argv[i]};
138-
q.remove("-p=");
139-
userdata = q;
140-
} else if (QString{argv[i]} == "--profile" || QString{argv[i]} == "-p") {
135+
QString arg{argv[i]};
136+
if (arg.startsWith("--profile=")) {
137+
arg.remove("--profile=");
138+
userdata = arg;
139+
} else if (arg.startsWith("--p=")) {
140+
arg.remove("-p=");
141+
userdata = arg;
142+
} else if (arg == "--profile" || arg == "-p") {
141143
if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
142144
// left to process as the name
143145
{
144146
++i; // the next arg is the name, so increment
145147
userdata = QString{argv[i]};
146148
}
149+
} else if (arg.startsWith("matrix:")) {
150+
matrixUri = arg;
147151
}
148152
}
149153

150154
SingleApplication app(argc,
151155
argv,
152-
false,
156+
true,
153157
SingleApplication::Mode::User |
154158
SingleApplication::Mode::ExcludeAppPath |
155-
SingleApplication::Mode::ExcludeAppVersion,
159+
SingleApplication::Mode::ExcludeAppVersion |
160+
SingleApplication::Mode::SecondaryNotification,
156161
100,
157162
userdata);
158163

164+
if (app.isSecondary()) {
165+
// open uri in main instance
166+
app.sendMessage(matrixUri.toUtf8());
167+
return 0;
168+
}
169+
159170
QCommandLineParser parser;
160171
parser.addHelpOption();
161172
parser.addVersionOption();
@@ -245,6 +256,25 @@ main(int argc, char *argv[])
245256
w.activateWindow();
246257
});
247258

259+
QObject::connect(
260+
&app,
261+
&SingleApplication::receivedMessage,
262+
ChatPage::instance(),
263+
[&](quint32, QByteArray message) { ChatPage::instance()->handleMatrixUri(message); });
264+
265+
QMetaObject::Connection uriConnection;
266+
if (app.isPrimary() && !matrixUri.isEmpty()) {
267+
uriConnection = QObject::connect(ChatPage::instance(),
268+
&ChatPage::contentLoaded,
269+
ChatPage::instance(),
270+
[&uriConnection, matrixUri]() {
271+
ChatPage::instance()->handleMatrixUri(
272+
matrixUri.toUtf8());
273+
QObject::disconnect(uriConnection);
274+
});
275+
}
276+
QDesktopServices::setUrlHandler("matrix", ChatPage::instance(), "handleMatrixUri");
277+
248278
#if defined(Q_OS_MAC)
249279
// Temporary solution for the emoji picker until
250280
// nheko has a proper menu bar with more functionality.

src/ui/UserProfile.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,7 @@ UserProfile::kickUser()
202202
void
203203
UserProfile::startChat()
204204
{
205-
mtx::requests::CreateRoom req;
206-
req.preset = mtx::requests::Preset::PrivateChat;
207-
req.visibility = mtx::requests::Visibility::Private;
208-
if (utils::localUser() != this->userid_)
209-
req.invite = {this->userid_.toStdString()};
210-
emit ChatPage::instance()->createRoom(req);
205+
ChatPage::instance()->startChat(this->userid_);
211206
}
212207

213208
void

0 commit comments

Comments
 (0)