Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/nheko.desktop
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Type=Application
Categories=Network;InstantMessaging;Qt;
StartupWMClass=nheko
Terminal=false
MimeType=x-scheme-handler/matrix;
28 changes: 28 additions & 0 deletions src/Cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
1 change: 1 addition & 0 deletions src/Cache_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class Cache : public QObject
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.
Expand Down
140 changes: 140 additions & 0 deletions src/ChatPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});
}

Expand Down Expand Up @@ -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());
}
4 changes: 4 additions & 0 deletions src/ChatPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ class ChatPage : public QWidget
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);
Expand Down
52 changes: 41 additions & 11 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include <QApplication>
#include <QCommandLineParser>
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDir>
#include <QFile>
Expand All @@ -33,6 +34,7 @@
#include <QStandardPaths>
#include <QTranslator>

#include "ChatPage.h"
#include "Config.h"
#include "Logging.h"
#include "MainWindow.h"
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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.
Expand Down
7 changes: 1 addition & 6 deletions src/ui/UserProfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down