Merge master and fix conflicts
This commit is contained in:
commit
3f567a8da7
87 changed files with 5667 additions and 2989 deletions
206
src/Cache.cpp
206
src/Cache.cpp
|
|
@ -78,6 +78,8 @@ constexpr auto ENCRYPTED_ROOMS_DB("encrypted_rooms");
|
|||
constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions");
|
||||
//! MegolmSessionIndex -> pickled OlmOutboundGroupSession
|
||||
constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions");
|
||||
//! MegolmSessionIndex -> session data about which devices have access to this
|
||||
constexpr auto MEGOLM_SESSIONS_DATA_DB("megolm_sessions_data_db");
|
||||
|
||||
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
|
||||
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
|
||||
|
|
@ -284,6 +286,7 @@ Cache::setup()
|
|||
// Session management
|
||||
inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
|
||||
outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
|
||||
megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE);
|
||||
|
||||
txn.commit();
|
||||
|
||||
|
|
@ -387,9 +390,14 @@ Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
|
|||
index.session_id = s.session_id;
|
||||
index.sender_key = s.sender_key;
|
||||
|
||||
GroupSessionData data{};
|
||||
data.forwarding_curve25519_key_chain = s.forwarding_curve25519_key_chain;
|
||||
if (s.sender_claimed_keys.count("ed25519"))
|
||||
data.sender_claimed_ed25519_key = s.sender_claimed_keys.at("ed25519");
|
||||
|
||||
auto exported_session = mtx::crypto::import_session(s.session_key);
|
||||
|
||||
saveInboundMegolmSession(index, std::move(exported_session));
|
||||
saveInboundMegolmSession(index, std::move(exported_session), data);
|
||||
ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id);
|
||||
}
|
||||
}
|
||||
|
|
@ -400,7 +408,8 @@ Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
|
|||
|
||||
void
|
||||
Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||
mtx::crypto::InboundGroupSessionPtr session)
|
||||
mtx::crypto::InboundGroupSessionPtr session,
|
||||
const GroupSessionData &data)
|
||||
{
|
||||
using namespace mtx::crypto;
|
||||
const auto key = json(index).dump();
|
||||
|
|
@ -420,6 +429,7 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
|
|||
}
|
||||
|
||||
inboundMegolmSessionDb_.put(txn, key, pickled);
|
||||
megolmSessionDataDb_.put(txn, key, json(data).dump());
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
|
|
@ -464,7 +474,7 @@ Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index)
|
|||
|
||||
void
|
||||
Cache::updateOutboundMegolmSession(const std::string &room_id,
|
||||
const OutboundGroupSessionData &data_,
|
||||
const GroupSessionData &data_,
|
||||
mtx::crypto::OutboundGroupSessionPtr &ptr)
|
||||
{
|
||||
using namespace mtx::crypto;
|
||||
|
|
@ -472,18 +482,20 @@ Cache::updateOutboundMegolmSession(const std::string &room_id,
|
|||
if (!outboundMegolmSessionExists(room_id))
|
||||
return;
|
||||
|
||||
OutboundGroupSessionData data = data_;
|
||||
data.message_index = olm_outbound_group_session_message_index(ptr.get());
|
||||
data.session_id = mtx::crypto::session_id(ptr.get());
|
||||
data.session_key = mtx::crypto::session_key(ptr.get());
|
||||
GroupSessionData data = data_;
|
||||
data.message_index = olm_outbound_group_session_message_index(ptr.get());
|
||||
MegolmSessionIndex index;
|
||||
index.room_id = room_id;
|
||||
index.sender_key = olm::client()->identity_keys().ed25519;
|
||||
index.session_id = mtx::crypto::session_id(ptr.get());
|
||||
|
||||
// Save the updated pickled data for the session.
|
||||
json j;
|
||||
j["data"] = data;
|
||||
j["session"] = pickle<OutboundSessionObject>(ptr.get(), SECRET);
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
outboundMegolmSessionDb_.put(txn, room_id, j.dump());
|
||||
megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump());
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
|
|
@ -498,24 +510,32 @@ Cache::dropOutboundMegolmSession(const std::string &room_id)
|
|||
{
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
outboundMegolmSessionDb_.del(txn, room_id);
|
||||
// don't delete session data, so that we can still share the session.
|
||||
txn.commit();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Cache::saveOutboundMegolmSession(const std::string &room_id,
|
||||
const OutboundGroupSessionData &data,
|
||||
const GroupSessionData &data_,
|
||||
mtx::crypto::OutboundGroupSessionPtr &session)
|
||||
{
|
||||
using namespace mtx::crypto;
|
||||
const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET);
|
||||
|
||||
GroupSessionData data = data_;
|
||||
data.message_index = olm_outbound_group_session_message_index(session.get());
|
||||
MegolmSessionIndex index;
|
||||
index.room_id = room_id;
|
||||
index.sender_key = olm::client()->identity_keys().ed25519;
|
||||
index.session_id = mtx::crypto::session_id(session.get());
|
||||
|
||||
json j;
|
||||
j["data"] = data;
|
||||
j["session"] = pickled;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
outboundMegolmSessionDb_.put(txn, room_id, j.dump());
|
||||
megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump());
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
|
|
@ -544,8 +564,17 @@ Cache::getOutboundMegolmSession(const std::string &room_id)
|
|||
auto obj = json::parse(value);
|
||||
|
||||
OutboundGroupSessionDataRef ref{};
|
||||
ref.data = obj.at("data").get<OutboundGroupSessionData>();
|
||||
ref.session = unpickle<OutboundSessionObject>(obj.at("session"), SECRET);
|
||||
|
||||
MegolmSessionIndex index;
|
||||
index.room_id = room_id;
|
||||
index.sender_key = olm::client()->identity_keys().ed25519;
|
||||
index.session_id = mtx::crypto::session_id(ref.session.get());
|
||||
|
||||
if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) {
|
||||
ref.data = nlohmann::json::parse(value).get<GroupSessionData>();
|
||||
}
|
||||
|
||||
return ref;
|
||||
} catch (std::exception &e) {
|
||||
nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
|
||||
|
|
@ -553,6 +582,25 @@ Cache::getOutboundMegolmSession(const std::string &room_id)
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<GroupSessionData>
|
||||
Cache::getMegolmSessionData(const MegolmSessionIndex &index)
|
||||
{
|
||||
try {
|
||||
using namespace mtx::crypto;
|
||||
|
||||
auto txn = ro_txn(env_);
|
||||
|
||||
std::string_view value;
|
||||
if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) {
|
||||
return nlohmann::json::parse(value).get<GroupSessionData>();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
} catch (std::exception &e) {
|
||||
nhlog::db()->error("Failed to retrieve Megolm Session Data: {}", e.what());
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
//
|
||||
// OLM sessions.
|
||||
//
|
||||
|
|
@ -829,6 +877,7 @@ Cache::deleteData()
|
|||
|
||||
lmdb::dbi_close(env_, inboundMegolmSessionDb_);
|
||||
lmdb::dbi_close(env_, outboundMegolmSessionDb_);
|
||||
lmdb::dbi_close(env_, megolmSessionDataDb_);
|
||||
|
||||
env_.close();
|
||||
|
||||
|
|
@ -3333,6 +3382,86 @@ Cache::getChildRoomIds(const std::string &room_id)
|
|||
return roomids;
|
||||
}
|
||||
|
||||
std::vector<ImagePackInfo>
|
||||
Cache::getImagePacks(const std::string &room_id, std::optional<bool> stickers)
|
||||
{
|
||||
auto txn = ro_txn(env_);
|
||||
std::vector<ImagePackInfo> infos;
|
||||
|
||||
auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack,
|
||||
const std::string &source_room,
|
||||
const std::string &state_key) {
|
||||
if (!pack.pack || !stickers.has_value() ||
|
||||
(stickers.value() ? pack.pack->is_sticker() : pack.pack->is_emoji())) {
|
||||
ImagePackInfo info;
|
||||
info.source_room = source_room;
|
||||
info.state_key = state_key;
|
||||
info.pack.pack = pack.pack;
|
||||
|
||||
for (const auto &img : pack.images) {
|
||||
if (img.second.overrides_usage() &&
|
||||
(stickers ? !img.second.is_sticker() : !img.second.is_emoji()))
|
||||
continue;
|
||||
|
||||
info.pack.images.insert(img);
|
||||
}
|
||||
|
||||
if (!info.pack.images.empty())
|
||||
infos.push_back(std::move(info));
|
||||
}
|
||||
};
|
||||
|
||||
// packs from account data
|
||||
if (auto accountpack =
|
||||
getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) {
|
||||
auto tmp =
|
||||
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePack>>(
|
||||
&*accountpack);
|
||||
if (tmp)
|
||||
addPack(tmp->content, "", "");
|
||||
}
|
||||
|
||||
// packs from rooms, that were enabled globally
|
||||
if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) {
|
||||
auto tmp =
|
||||
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
|
||||
&*roomPacks);
|
||||
if (tmp) {
|
||||
for (const auto &[room_id2, state_to_d] : tmp->content.rooms) {
|
||||
// don't add stickers from this room twice
|
||||
if (room_id2 == room_id)
|
||||
continue;
|
||||
|
||||
for (const auto &[state_id, d] : state_to_d) {
|
||||
(void)d;
|
||||
if (auto pack =
|
||||
getStateEvent<mtx::events::msc2545::ImagePack>(
|
||||
txn, room_id2, state_id))
|
||||
addPack(pack->content, room_id2, state_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// packs from current room
|
||||
if (auto pack = getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id)) {
|
||||
addPack(pack->content, room_id, "");
|
||||
}
|
||||
for (const auto &pack :
|
||||
getStateEventsWithType<mtx::events::msc2545::ImagePack>(txn, room_id)) {
|
||||
addPack(pack.content, room_id, pack.state_key);
|
||||
}
|
||||
|
||||
return infos;
|
||||
}
|
||||
|
||||
std::optional<mtx::events::collections::RoomAccountDataEvents>
|
||||
Cache::getAccountData(mtx::events::EventType type, const std::string &room_id)
|
||||
{
|
||||
auto txn = ro_txn(env_);
|
||||
return getAccountData(txn, type, room_id);
|
||||
}
|
||||
|
||||
std::optional<mtx::events::collections::RoomAccountDataEvents>
|
||||
Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id)
|
||||
{
|
||||
|
|
@ -3525,6 +3654,7 @@ to_json(json &j, const UserKeyCache &info)
|
|||
{
|
||||
j["device_keys"] = info.device_keys;
|
||||
j["seen_device_keys"] = info.seen_device_keys;
|
||||
j["seen_device_ids"] = info.seen_device_ids;
|
||||
j["master_keys"] = info.master_keys;
|
||||
j["master_key_changed"] = info.master_key_changed;
|
||||
j["user_signing_keys"] = info.user_signing_keys;
|
||||
|
|
@ -3538,6 +3668,7 @@ from_json(const json &j, UserKeyCache &info)
|
|||
{
|
||||
info.device_keys = j.value("device_keys", std::map<std::string, mtx::crypto::DeviceKeys>{});
|
||||
info.seen_device_keys = j.value("seen_device_keys", std::set<std::string>{});
|
||||
info.seen_device_ids = j.value("seen_device_ids", std::set<std::string>{});
|
||||
info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{});
|
||||
info.master_key_changed = j.value("master_key_changed", false);
|
||||
info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{});
|
||||
|
|
@ -3634,6 +3765,15 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
|
|||
keyReused = true;
|
||||
break;
|
||||
}
|
||||
if (updateToWrite.seen_device_ids.count(
|
||||
device_id)) {
|
||||
nhlog::crypto()->warn(
|
||||
"device_id '{}' reused by ({})",
|
||||
device_id,
|
||||
user);
|
||||
keyReused = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keyReused && !oldDeviceKeys.count(device_id))
|
||||
|
|
@ -3644,6 +3784,7 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
|
|||
(void)key_id;
|
||||
updateToWrite.seen_device_keys.insert(key);
|
||||
}
|
||||
updateToWrite.seen_device_ids.insert(device_id);
|
||||
}
|
||||
}
|
||||
db.put(txn, user, json(updateToWrite).dump());
|
||||
|
|
@ -4077,17 +4218,15 @@ from_json(const json &j, MemberInfo &info)
|
|||
}
|
||||
|
||||
void
|
||||
to_json(nlohmann::json &obj, const DeviceAndMasterKeys &msg)
|
||||
to_json(nlohmann::json &obj, const DeviceKeysToMsgIndex &msg)
|
||||
{
|
||||
obj["devices"] = msg.devices;
|
||||
obj["master_keys"] = msg.master_keys;
|
||||
obj["deviceids"] = msg.deviceids;
|
||||
}
|
||||
|
||||
void
|
||||
from_json(const nlohmann::json &obj, DeviceAndMasterKeys &msg)
|
||||
from_json(const nlohmann::json &obj, DeviceKeysToMsgIndex &msg)
|
||||
{
|
||||
msg.devices = obj.at("devices").get<decltype(msg.devices)>();
|
||||
msg.master_keys = obj.at("master_keys").get<decltype(msg.master_keys)>();
|
||||
msg.deviceids = obj.at("deviceids").get<decltype(msg.deviceids)>();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -4099,30 +4238,31 @@ to_json(nlohmann::json &obj, const SharedWithUsers &msg)
|
|||
void
|
||||
from_json(const nlohmann::json &obj, SharedWithUsers &msg)
|
||||
{
|
||||
msg.keys = obj.at("keys").get<std::map<std::string, DeviceAndMasterKeys>>();
|
||||
msg.keys = obj.at("keys").get<std::map<std::string, DeviceKeysToMsgIndex>>();
|
||||
}
|
||||
|
||||
void
|
||||
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg)
|
||||
to_json(nlohmann::json &obj, const GroupSessionData &msg)
|
||||
{
|
||||
obj["session_id"] = msg.session_id;
|
||||
obj["session_key"] = msg.session_key;
|
||||
obj["message_index"] = msg.message_index;
|
||||
obj["ts"] = msg.timestamp;
|
||||
|
||||
obj["initially"] = msg.initially;
|
||||
obj["sender_claimed_ed25519_key"] = msg.sender_claimed_ed25519_key;
|
||||
obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain;
|
||||
|
||||
obj["currently"] = msg.currently;
|
||||
}
|
||||
|
||||
void
|
||||
from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg)
|
||||
from_json(const nlohmann::json &obj, GroupSessionData &msg)
|
||||
{
|
||||
msg.session_id = obj.at("session_id");
|
||||
msg.session_key = obj.at("session_key");
|
||||
msg.message_index = obj.at("message_index");
|
||||
msg.timestamp = obj.value("ts", 0ULL);
|
||||
|
||||
msg.initially = obj.value("initially", SharedWithUsers{});
|
||||
msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", "");
|
||||
msg.forwarding_curve25519_key_chain =
|
||||
obj.value("forwarding_curve25519_key_chain", std::vector<std::string>{});
|
||||
|
||||
msg.currently = obj.value("currently", SharedWithUsers{});
|
||||
}
|
||||
|
||||
|
|
@ -4522,7 +4662,7 @@ isRoomMember(const std::string &user_id, const std::string &room_id)
|
|||
//
|
||||
void
|
||||
saveOutboundMegolmSession(const std::string &room_id,
|
||||
const OutboundGroupSessionData &data,
|
||||
const GroupSessionData &data,
|
||||
mtx::crypto::OutboundGroupSessionPtr &session)
|
||||
{
|
||||
instance_->saveOutboundMegolmSession(room_id, data, session);
|
||||
|
|
@ -4539,7 +4679,7 @@ outboundMegolmSessionExists(const std::string &room_id) noexcept
|
|||
}
|
||||
void
|
||||
updateOutboundMegolmSession(const std::string &room_id,
|
||||
const OutboundGroupSessionData &data,
|
||||
const GroupSessionData &data,
|
||||
mtx::crypto::OutboundGroupSessionPtr &session)
|
||||
{
|
||||
instance_->updateOutboundMegolmSession(room_id, data, session);
|
||||
|
|
@ -4566,9 +4706,10 @@ exportSessionKeys()
|
|||
//
|
||||
void
|
||||
saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||
mtx::crypto::InboundGroupSessionPtr session)
|
||||
mtx::crypto::InboundGroupSessionPtr session,
|
||||
const GroupSessionData &data)
|
||||
{
|
||||
instance_->saveInboundMegolmSession(index, std::move(session));
|
||||
instance_->saveInboundMegolmSession(index, std::move(session), data);
|
||||
}
|
||||
mtx::crypto::InboundGroupSessionPtr
|
||||
getInboundMegolmSession(const MegolmSessionIndex &index)
|
||||
|
|
@ -4580,6 +4721,11 @@ inboundMegolmSessionExists(const MegolmSessionIndex &index)
|
|||
{
|
||||
return instance_->inboundMegolmSessionExists(index);
|
||||
}
|
||||
std::optional<GroupSessionData>
|
||||
getMegolmSessionData(const MegolmSessionIndex &index)
|
||||
{
|
||||
return instance_->getMegolmSessionData(index);
|
||||
}
|
||||
|
||||
//
|
||||
// Olm Sessions
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ isRoomMember(const std::string &user_id, const std::string &room_id);
|
|||
//
|
||||
void
|
||||
saveOutboundMegolmSession(const std::string &room_id,
|
||||
const OutboundGroupSessionData &data,
|
||||
const GroupSessionData &data,
|
||||
mtx::crypto::OutboundGroupSessionPtr &session);
|
||||
OutboundGroupSessionDataRef
|
||||
getOutboundMegolmSession(const std::string &room_id);
|
||||
|
|
@ -208,7 +208,7 @@ bool
|
|||
outboundMegolmSessionExists(const std::string &room_id) noexcept;
|
||||
void
|
||||
updateOutboundMegolmSession(const std::string &room_id,
|
||||
const OutboundGroupSessionData &data,
|
||||
const GroupSessionData &data,
|
||||
mtx::crypto::OutboundGroupSessionPtr &session);
|
||||
void
|
||||
dropOutboundMegolmSession(const std::string &room_id);
|
||||
|
|
@ -223,11 +223,14 @@ exportSessionKeys();
|
|||
//
|
||||
void
|
||||
saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||
mtx::crypto::InboundGroupSessionPtr session);
|
||||
mtx::crypto::InboundGroupSessionPtr session,
|
||||
const GroupSessionData &data);
|
||||
mtx::crypto::InboundGroupSessionPtr
|
||||
getInboundMegolmSession(const MegolmSessionIndex &index);
|
||||
bool
|
||||
inboundMegolmSessionExists(const MegolmSessionIndex &index);
|
||||
std::optional<GroupSessionData>
|
||||
getMegolmSessionData(const MegolmSessionIndex &index);
|
||||
|
||||
//
|
||||
// Olm Sessions
|
||||
|
|
|
|||
|
|
@ -27,40 +27,43 @@ enum Trust
|
|||
Q_ENUM_NS(Trust)
|
||||
}
|
||||
|
||||
struct DeviceAndMasterKeys
|
||||
struct DeviceKeysToMsgIndex
|
||||
{
|
||||
// map from device id or master key id to message_index
|
||||
std::map<std::string, uint64_t> devices, master_keys;
|
||||
// map from device key to message_index
|
||||
// Using the device id is safe because we check for reuse on device list updates
|
||||
// Using the device id makes our logic much easier to read.
|
||||
std::map<std::string, uint64_t> deviceids;
|
||||
};
|
||||
|
||||
struct SharedWithUsers
|
||||
{
|
||||
// userid to keys
|
||||
std::map<std::string, DeviceAndMasterKeys> keys;
|
||||
std::map<std::string, DeviceKeysToMsgIndex> keys;
|
||||
};
|
||||
|
||||
// Extra information associated with an outbound megolm session.
|
||||
struct OutboundGroupSessionData
|
||||
struct GroupSessionData
|
||||
{
|
||||
std::string session_id;
|
||||
std::string session_key;
|
||||
uint64_t message_index = 0;
|
||||
uint64_t timestamp = 0;
|
||||
|
||||
std::string sender_claimed_ed25519_key;
|
||||
std::vector<std::string> forwarding_curve25519_key_chain;
|
||||
|
||||
// who has access to this session.
|
||||
// Rotate, when a user leaves the room and share, when a user gets added.
|
||||
SharedWithUsers initially, currently;
|
||||
SharedWithUsers currently;
|
||||
};
|
||||
|
||||
void
|
||||
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg);
|
||||
to_json(nlohmann::json &obj, const GroupSessionData &msg);
|
||||
void
|
||||
from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg);
|
||||
from_json(const nlohmann::json &obj, GroupSessionData &msg);
|
||||
|
||||
struct OutboundGroupSessionDataRef
|
||||
{
|
||||
mtx::crypto::OutboundGroupSessionPtr session;
|
||||
OutboundGroupSessionData data;
|
||||
GroupSessionData data;
|
||||
};
|
||||
|
||||
struct DevicePublicKeys
|
||||
|
|
@ -134,6 +137,8 @@ struct UserKeyCache
|
|||
bool master_key_changed = false;
|
||||
//! Device keys that were already used at least once
|
||||
std::set<std::string> seen_device_keys;
|
||||
//! Device ids that were already used at least once
|
||||
std::set<std::string> seen_device_ids;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <string>
|
||||
|
||||
#include <mtx/events/join_rules.hpp>
|
||||
#include <mtx/events/mscs/image_packs.hpp>
|
||||
|
||||
namespace cache {
|
||||
enum class CacheVersion : int
|
||||
|
|
@ -109,3 +110,10 @@ struct RoomSearchResult
|
|||
std::string room_id;
|
||||
RoomInfo info;
|
||||
};
|
||||
|
||||
struct ImagePackInfo
|
||||
{
|
||||
mtx::events::msc2545::ImagePack pack;
|
||||
std::string source_room;
|
||||
std::string state_key;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -97,6 +97,12 @@ public:
|
|||
return getStateEvent<T>(txn, room_id, state_key);
|
||||
}
|
||||
|
||||
//! retrieve a specific event from account data
|
||||
//! pass empty room_id for global account data
|
||||
std::optional<mtx::events::collections::RoomAccountDataEvents> getAccountData(
|
||||
mtx::events::EventType type,
|
||||
const std::string &room_id = "");
|
||||
|
||||
//! Retrieve member info from a room.
|
||||
std::vector<RoomMember> getMembers(const std::string &room_id,
|
||||
std::size_t startIndex = 0,
|
||||
|
|
@ -225,6 +231,9 @@ public:
|
|||
std::vector<std::string> getParentRoomIds(const std::string &room_id);
|
||||
std::vector<std::string> getChildRoomIds(const std::string &room_id);
|
||||
|
||||
std::vector<ImagePackInfo> getImagePacks(const std::string &room_id,
|
||||
std::optional<bool> stickers);
|
||||
|
||||
//! Mark a room that uses e2e encryption.
|
||||
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
|
||||
bool isRoomEncrypted(const std::string &room_id);
|
||||
|
|
@ -238,12 +247,12 @@ public:
|
|||
// Outbound Megolm Sessions
|
||||
//
|
||||
void saveOutboundMegolmSession(const std::string &room_id,
|
||||
const OutboundGroupSessionData &data,
|
||||
const GroupSessionData &data,
|
||||
mtx::crypto::OutboundGroupSessionPtr &session);
|
||||
OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
|
||||
bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
|
||||
void updateOutboundMegolmSession(const std::string &room_id,
|
||||
const OutboundGroupSessionData &data,
|
||||
const GroupSessionData &data,
|
||||
mtx::crypto::OutboundGroupSessionPtr &session);
|
||||
void dropOutboundMegolmSession(const std::string &room_id);
|
||||
|
||||
|
|
@ -254,10 +263,12 @@ public:
|
|||
// Inbound Megolm Sessions
|
||||
//
|
||||
void saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||
mtx::crypto::InboundGroupSessionPtr session);
|
||||
mtx::crypto::InboundGroupSessionPtr session,
|
||||
const GroupSessionData &data);
|
||||
mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(
|
||||
const MegolmSessionIndex &index);
|
||||
bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
|
||||
std::optional<GroupSessionData> getMegolmSessionData(const MegolmSessionIndex &index);
|
||||
|
||||
//
|
||||
// Olm Sessions
|
||||
|
|
@ -676,6 +687,7 @@ private:
|
|||
|
||||
lmdb::dbi inboundMegolmSessionDb_;
|
||||
lmdb::dbi outboundMegolmSessionDb_;
|
||||
lmdb::dbi megolmSessionDataDb_;
|
||||
|
||||
QString localUserId_;
|
||||
QString cacheDirectory_;
|
||||
|
|
|
|||
|
|
@ -116,29 +116,31 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
|
||||
connect(this, &ChatPage::loggedOut, this, &ChatPage::logout);
|
||||
|
||||
connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList users) {
|
||||
const auto room_id = currentRoom().toStdString();
|
||||
connect(
|
||||
view_manager_,
|
||||
&TimelineViewManager::inviteUsers,
|
||||
this,
|
||||
[this](QString roomId, QStringList users) {
|
||||
for (int ii = 0; ii < users.size(); ++ii) {
|
||||
QTimer::singleShot(ii * 500, this, [this, roomId, ii, users]() {
|
||||
const auto user = users.at(ii);
|
||||
|
||||
for (int ii = 0; ii < users.size(); ++ii) {
|
||||
QTimer::singleShot(ii * 500, this, [this, room_id, ii, users]() {
|
||||
const auto user = users.at(ii);
|
||||
http::client()->invite_user(
|
||||
roomId.toStdString(),
|
||||
user.toStdString(),
|
||||
[this, user](const mtx::responses::RoomInvite &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit showNotification(
|
||||
tr("Failed to invite user: %1").arg(user));
|
||||
return;
|
||||
}
|
||||
|
||||
http::client()->invite_user(
|
||||
room_id,
|
||||
user.toStdString(),
|
||||
[this, user](const mtx::responses::RoomInvite &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit showNotification(
|
||||
tr("Failed to invite user: %1").arg(user));
|
||||
return;
|
||||
}
|
||||
|
||||
emit showNotification(tr("Invited user: %1").arg(user));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
emit showNotification(tr("Invited user: %1").arg(user));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
|
||||
connect(this, &ChatPage::newRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection);
|
||||
|
|
@ -927,27 +929,33 @@ ChatPage::currentPresence() const
|
|||
void
|
||||
ChatPage::ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts)
|
||||
{
|
||||
for (const auto &entry : counts) {
|
||||
if (entry.second < MAX_ONETIME_KEYS) {
|
||||
const int nkeys = MAX_ONETIME_KEYS - entry.second;
|
||||
uint16_t count = 0;
|
||||
if (auto c = counts.find(mtx::crypto::SIGNED_CURVE25519); c != counts.end())
|
||||
count = c->second;
|
||||
|
||||
nhlog::crypto()->info("uploading {} {} keys", nkeys, entry.first);
|
||||
olm::client()->generate_one_time_keys(nkeys);
|
||||
if (count < MAX_ONETIME_KEYS) {
|
||||
const int nkeys = MAX_ONETIME_KEYS - count;
|
||||
|
||||
http::client()->upload_keys(
|
||||
olm::client()->create_upload_keys_request(),
|
||||
[](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::crypto()->warn(
|
||||
"failed to update one-time keys: {} {}",
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
nhlog::crypto()->info(
|
||||
"uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519);
|
||||
olm::client()->generate_one_time_keys(nkeys);
|
||||
|
||||
http::client()->upload_keys(
|
||||
olm::client()->create_upload_keys_request(),
|
||||
[](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::crypto()->warn("failed to update one-time keys: {} {} {}",
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code),
|
||||
static_cast<int>(err->error_code));
|
||||
|
||||
if (err->status_code < 400 || err->status_code >= 500)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
olm::mark_keys_as_published();
|
||||
});
|
||||
}
|
||||
// mark as published anyway, otherwise we may end up in a loop.
|
||||
olm::mark_keys_as_published();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
77
src/CombinedImagePackModel.cpp
Normal file
77
src/CombinedImagePackModel.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "CombinedImagePackModel.h"
|
||||
|
||||
#include "Cache_p.h"
|
||||
#include "CompletionModelRoles.h"
|
||||
|
||||
CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId,
|
||||
bool stickers,
|
||||
QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, room_id(roomId)
|
||||
{
|
||||
auto packs = cache::client()->getImagePacks(room_id, stickers);
|
||||
|
||||
for (const auto &pack : packs) {
|
||||
QString packname =
|
||||
pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : "";
|
||||
|
||||
for (const auto &img : pack.pack.images) {
|
||||
ImageDesc i{};
|
||||
i.shortcode = QString::fromStdString(img.first);
|
||||
i.packname = packname;
|
||||
i.image = img.second;
|
||||
images.push_back(std::move(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CombinedImagePackModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return (int)images.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
CombinedImagePackModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{CompletionModel::CompletionRole, "completionRole"},
|
||||
{CompletionModel::SearchRole, "searchRole"},
|
||||
{CompletionModel::SearchRole2, "searchRole2"},
|
||||
{Roles::Url, "url"},
|
||||
{Roles::ShortCode, "shortcode"},
|
||||
{Roles::Body, "body"},
|
||||
{Roles::PackName, "packname"},
|
||||
{Roles::OriginalRow, "originalRow"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
CombinedImagePackModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case CompletionModel::CompletionRole:
|
||||
return QString::fromStdString(images[index.row()].image.url);
|
||||
case Roles::Url:
|
||||
return QString::fromStdString(images[index.row()].image.url);
|
||||
case CompletionModel::SearchRole:
|
||||
case Roles::ShortCode:
|
||||
return images[index.row()].shortcode;
|
||||
case CompletionModel::SearchRole2:
|
||||
case Roles::Body:
|
||||
return QString::fromStdString(images[index.row()].image.body);
|
||||
case Roles::PackName:
|
||||
return images[index.row()].packname;
|
||||
case Roles::OriginalRow:
|
||||
return index.row();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
48
src/CombinedImagePackModel.h
Normal file
48
src/CombinedImagePackModel.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <mtx/events/mscs/image_packs.hpp>
|
||||
|
||||
class CombinedImagePackModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Url = Qt::UserRole,
|
||||
ShortCode,
|
||||
Body,
|
||||
PackName,
|
||||
OriginalRow,
|
||||
};
|
||||
|
||||
CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
mtx::events::msc2545::PackImage imageAt(int row)
|
||||
{
|
||||
if (row < 0 || static_cast<size_t>(row) >= images.size())
|
||||
return {};
|
||||
return images.at(static_cast<size_t>(row)).image;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string room_id;
|
||||
|
||||
struct ImageDesc
|
||||
{
|
||||
QString shortcode;
|
||||
QString packname;
|
||||
|
||||
mtx::events::msc2545::PackImage image;
|
||||
};
|
||||
|
||||
std::vector<ImageDesc> images;
|
||||
};
|
||||
76
src/ImagePackListModel.cpp
Normal file
76
src/ImagePackListModel.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "ImagePackListModel.h"
|
||||
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "Cache_p.h"
|
||||
#include "SingleImagePackModel.h"
|
||||
|
||||
ImagePackListModel::ImagePackListModel(const std::string &roomId, QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, room_id(roomId)
|
||||
{
|
||||
auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt);
|
||||
|
||||
for (const auto &pack : packs_) {
|
||||
packs.push_back(
|
||||
QSharedPointer<SingleImagePackModel>(new SingleImagePackModel(pack)));
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ImagePackListModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return (int)packs.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
ImagePackListModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{Roles::DisplayName, "displayName"},
|
||||
{Roles::AvatarUrl, "avatarUrl"},
|
||||
{Roles::FromAccountData, "fromAccountData"},
|
||||
{Roles::FromCurrentRoom, "fromCurrentRoom"},
|
||||
{Roles::StateKey, "statekey"},
|
||||
{Roles::RoomId, "roomid"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
ImagePackListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
const auto &pack = packs.at(index.row());
|
||||
switch (role) {
|
||||
case Roles::DisplayName:
|
||||
return pack->packname();
|
||||
case Roles::AvatarUrl:
|
||||
return pack->avatarUrl();
|
||||
case Roles::FromAccountData:
|
||||
return pack->roomid().isEmpty();
|
||||
case Roles::FromCurrentRoom:
|
||||
return pack->roomid().toStdString() == this->room_id;
|
||||
case Roles::StateKey:
|
||||
return pack->statekey();
|
||||
case Roles::RoomId:
|
||||
return pack->roomid();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
SingleImagePackModel *
|
||||
ImagePackListModel::packAt(int row)
|
||||
{
|
||||
if (row < 0 || static_cast<size_t>(row) >= packs.size())
|
||||
return {};
|
||||
auto e = packs.at(row).get();
|
||||
QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership);
|
||||
return e;
|
||||
}
|
||||
37
src/ImagePackListModel.h
Normal file
37
src/ImagePackListModel.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class SingleImagePackModel;
|
||||
class ImagePackListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
DisplayName = Qt::UserRole,
|
||||
AvatarUrl,
|
||||
FromAccountData,
|
||||
FromCurrentRoom,
|
||||
StateKey,
|
||||
RoomId,
|
||||
};
|
||||
|
||||
ImagePackListModel(const std::string &roomId, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
Q_INVOKABLE SingleImagePackModel *packAt(int row);
|
||||
|
||||
private:
|
||||
std::string room_id;
|
||||
|
||||
std::vector<QSharedPointer<SingleImagePackModel>> packs;
|
||||
};
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "InviteeItem.h"
|
||||
|
||||
constexpr int SidePadding = 10;
|
||||
|
||||
InviteeItem::InviteeItem(mtx::identifiers::User user, QWidget *parent)
|
||||
: QWidget{parent}
|
||||
, user_{QString::fromStdString(user.to_string())}
|
||||
{
|
||||
auto topLayout_ = new QHBoxLayout(this);
|
||||
topLayout_->setSpacing(0);
|
||||
topLayout_->setContentsMargins(SidePadding, 0, 3 * SidePadding, 0);
|
||||
|
||||
name_ = new QLabel(user_, this);
|
||||
removeUserBtn_ = new QPushButton(tr("Remove"), this);
|
||||
|
||||
topLayout_->addWidget(name_);
|
||||
topLayout_->addWidget(removeUserBtn_, 0, Qt::AlignRight);
|
||||
|
||||
connect(removeUserBtn_, &QPushButton::clicked, this, &InviteeItem::removeItem);
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <mtx/identifiers.hpp>
|
||||
|
||||
class QPushButton;
|
||||
class QLabel;
|
||||
|
||||
class InviteeItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InviteeItem(mtx::identifiers::User user, QWidget *parent = nullptr);
|
||||
|
||||
QString userID() { return user_; }
|
||||
|
||||
signals:
|
||||
void removeItem();
|
||||
|
||||
private:
|
||||
QString user_;
|
||||
|
||||
QLabel *name_;
|
||||
QPushButton *removeUserBtn_;
|
||||
};
|
||||
84
src/InviteesModel.cpp
Normal file
84
src/InviteesModel.cpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "InviteesModel.h"
|
||||
|
||||
#include "Cache.h"
|
||||
#include "Logging.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "mtx/responses/profile.hpp"
|
||||
|
||||
InviteesModel::InviteesModel(QObject *parent)
|
||||
: QAbstractListModel{parent}
|
||||
{}
|
||||
|
||||
void
|
||||
InviteesModel::addUser(QString mxid)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
|
||||
|
||||
auto invitee = new Invitee{mxid, this};
|
||||
auto indexOfInvitee = invitees_.count();
|
||||
connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() {
|
||||
emit dataChanged(index(indexOfInvitee), index(indexOfInvitee));
|
||||
});
|
||||
|
||||
invitees_.push_back(invitee);
|
||||
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
InviteesModel::roleNames() const
|
||||
{
|
||||
return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}};
|
||||
}
|
||||
|
||||
QVariant
|
||||
InviteesModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0)
|
||||
return {};
|
||||
|
||||
switch (role) {
|
||||
case Mxid:
|
||||
return invitees_[index.row()]->mxid_;
|
||||
case DisplayName:
|
||||
return invitees_[index.row()]->displayName_;
|
||||
case AvatarUrl:
|
||||
return invitees_[index.row()]->avatarUrl_;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QStringList
|
||||
InviteesModel::mxids()
|
||||
{
|
||||
QStringList mxidList;
|
||||
for (int i = 0; i < invitees_.length(); ++i)
|
||||
mxidList.push_back(invitees_[i]->mxid_);
|
||||
return mxidList;
|
||||
}
|
||||
|
||||
Invitee::Invitee(const QString &mxid, QObject *parent)
|
||||
: QObject{parent}
|
||||
, mxid_{mxid}
|
||||
{
|
||||
http::client()->get_profile(
|
||||
mxid_.toStdString(),
|
||||
[this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve profile info");
|
||||
emit userInfoLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
displayName_ = QString::fromStdString(res.display_name);
|
||||
avatarUrl_ = QString::fromStdString(res.avatar_url);
|
||||
|
||||
emit userInfoLoaded();
|
||||
});
|
||||
}
|
||||
63
src/InviteesModel.h
Normal file
63
src/InviteesModel.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef INVITEESMODEL_H
|
||||
#define INVITEESMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QVector>
|
||||
|
||||
class Invitee : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Invitee(const QString &mxid, QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void userInfoLoaded();
|
||||
|
||||
private:
|
||||
const QString mxid_;
|
||||
QString displayName_;
|
||||
QString avatarUrl_;
|
||||
|
||||
friend class InviteesModel;
|
||||
};
|
||||
|
||||
class InviteesModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Mxid,
|
||||
DisplayName,
|
||||
AvatarUrl,
|
||||
};
|
||||
|
||||
InviteesModel(QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void addUser(QString mxid);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex & = QModelIndex()) const override
|
||||
{
|
||||
return (int)invitees_.size();
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QStringList mxids();
|
||||
|
||||
signals:
|
||||
void accept();
|
||||
void countChanged();
|
||||
|
||||
private:
|
||||
QVector<Invitee *> invitees_;
|
||||
};
|
||||
|
||||
#endif // INVITEESMODEL_H
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
#include "LoginPage.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "MemberList.h"
|
||||
#include "RegisterPage.h"
|
||||
#include "TrayIcon.h"
|
||||
#include "UserSettingsPage.h"
|
||||
|
|
@ -32,11 +33,9 @@
|
|||
#include "ui/SnackBar.h"
|
||||
|
||||
#include "dialogs/CreateRoom.h"
|
||||
#include "dialogs/InviteUsers.h"
|
||||
#include "dialogs/JoinRoom.h"
|
||||
#include "dialogs/LeaveRoom.h"
|
||||
#include "dialogs/Logout.h"
|
||||
#include "dialogs/MemberList.h"
|
||||
#include "dialogs/ReadReceipts.h"
|
||||
|
||||
MainWindow *MainWindow::instance_ = nullptr;
|
||||
|
|
@ -310,14 +309,6 @@ MainWindow::hasActiveUser()
|
|||
settings.contains(prefix + "auth/user_id");
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::openMemberListDialog(const QString &room_id)
|
||||
{
|
||||
auto dialog = new dialogs::MemberList(room_id, this);
|
||||
|
||||
showDialog(dialog);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::openLeaveRoomDialog(const QString &room_id)
|
||||
{
|
||||
|
|
@ -341,18 +332,6 @@ MainWindow::showOverlayProgressBar()
|
|||
showSolidOverlayModal(spinner_);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::openInviteUsersDialog(std::function<void(const QStringList &invitees)> callback)
|
||||
{
|
||||
auto dialog = new dialogs::InviteUsers(this);
|
||||
connect(dialog, &dialogs::InviteUsers::sendInvites, this, [callback](QStringList invitees) {
|
||||
if (!invitees.isEmpty())
|
||||
callback(invitees);
|
||||
});
|
||||
|
||||
showDialog(dialog);
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::openJoinRoomDialog(std::function<void(const QString &room_id)> callback)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ public:
|
|||
std::function<void(const mtx::requests::CreateRoom &request)> callback);
|
||||
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
|
||||
void openLogoutDialog();
|
||||
void openMemberListDialog(const QString &room_id);
|
||||
void openReadReceiptsDialog(const QString &event_id);
|
||||
|
||||
void hideOverlay();
|
||||
|
|
|
|||
111
src/MemberList.cpp
Normal file
111
src/MemberList.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QAbstractSlider>
|
||||
#include <QLabel>
|
||||
#include <QListWidgetItem>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
#include <QShortcut>
|
||||
#include <QStyleOption>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "MemberList.h"
|
||||
|
||||
#include "Cache.h"
|
||||
#include "ChatPage.h"
|
||||
#include "Config.h"
|
||||
#include "Logging.h"
|
||||
#include "Utils.h"
|
||||
#include "timeline/TimelineViewManager.h"
|
||||
#include "ui/Avatar.h"
|
||||
|
||||
MemberList::MemberList(const QString &room_id, QObject *parent)
|
||||
: QAbstractListModel{parent}
|
||||
, room_id_{room_id}
|
||||
{
|
||||
try {
|
||||
info_ = cache::singleRoomInfo(room_id_.toStdString());
|
||||
} catch (const lmdb::error &) {
|
||||
nhlog::db()->warn("failed to retrieve room info from cache: {}",
|
||||
room_id_.toStdString());
|
||||
}
|
||||
|
||||
try {
|
||||
auto members = cache::getMembers(room_id_.toStdString());
|
||||
addUsers(members);
|
||||
numUsersLoaded_ = members.size();
|
||||
} catch (const lmdb::error &e) {
|
||||
nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MemberList::addUsers(const std::vector<RoomMember> &members)
|
||||
{
|
||||
beginInsertRows(
|
||||
QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1);
|
||||
|
||||
for (const auto &member : members)
|
||||
m_memberList.push_back(
|
||||
{member,
|
||||
ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl(
|
||||
member.user_id)});
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
MemberList::roleNames() const
|
||||
{
|
||||
return {
|
||||
{Mxid, "mxid"},
|
||||
{DisplayName, "displayName"},
|
||||
{AvatarUrl, "avatarUrl"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
MemberList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0)
|
||||
return {};
|
||||
|
||||
switch (role) {
|
||||
case Mxid:
|
||||
return m_memberList[index.row()].first.user_id;
|
||||
case DisplayName:
|
||||
return m_memberList[index.row()].first.display_name;
|
||||
case AvatarUrl:
|
||||
return m_memberList[index.row()].second;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MemberList::canFetchMore(const QModelIndex &) const
|
||||
{
|
||||
const size_t numMembers = rowCount();
|
||||
if (numMembers > 1 && numMembers < info_.member_count)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
MemberList::fetchMore(const QModelIndex &)
|
||||
{
|
||||
loadingMoreMembers_ = true;
|
||||
emit loadingMoreMembersChanged();
|
||||
|
||||
auto members = cache::getMembers(room_id_.toStdString(), rowCount());
|
||||
addUsers(members);
|
||||
numUsersLoaded_ += members.size();
|
||||
emit numUsersLoadedChanged();
|
||||
|
||||
loadingMoreMembers_ = false;
|
||||
emit loadingMoreMembersChanged();
|
||||
}
|
||||
66
src/MemberList.h
Normal file
66
src/MemberList.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CacheStructs.h"
|
||||
#include <QAbstractListModel>
|
||||
|
||||
class MemberList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
|
||||
Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged)
|
||||
Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
|
||||
Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged)
|
||||
Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged)
|
||||
Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged)
|
||||
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Mxid,
|
||||
DisplayName,
|
||||
AvatarUrl,
|
||||
};
|
||||
MemberList(const QString &room_id, QObject *parent = nullptr);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return static_cast<int>(m_memberList.size());
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
QString roomName() const { return QString::fromStdString(info_.name); }
|
||||
int memberCount() const { return info_.member_count; }
|
||||
QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); }
|
||||
QString roomId() const { return room_id_; }
|
||||
int numUsersLoaded() const { return numUsersLoaded_; }
|
||||
bool loadingMoreMembers() const { return loadingMoreMembers_; }
|
||||
|
||||
signals:
|
||||
void roomNameChanged();
|
||||
void memberCountChanged();
|
||||
void avatarUrlChanged();
|
||||
void roomIdChanged();
|
||||
void numUsersLoadedChanged();
|
||||
void loadingMoreMembersChanged();
|
||||
|
||||
public slots:
|
||||
void addUsers(const std::vector<RoomMember> &users);
|
||||
|
||||
protected:
|
||||
bool canFetchMore(const QModelIndex &) const override;
|
||||
void fetchMore(const QModelIndex &) override;
|
||||
|
||||
private:
|
||||
QVector<QPair<RoomMember, QString>> m_memberList;
|
||||
QString room_id_;
|
||||
RoomInfo info_;
|
||||
int numUsersLoaded_{0};
|
||||
bool loadingMoreMembers_{false};
|
||||
};
|
||||
|
|
@ -196,7 +196,6 @@ MxcImageProvider::download(const QString &id,
|
|||
image.setText("original filename",
|
||||
QString::fromStdString(originalFilename));
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
image.save(fileInfo.absoluteFilePath());
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
});
|
||||
} catch (std::exception &e) {
|
||||
|
|
|
|||
216
src/Olm.cpp
216
src/Olm.cpp
|
|
@ -123,7 +123,17 @@ handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEven
|
|||
if (msg_type == to_string(mtx::events::EventType::RoomEncrypted)) {
|
||||
try {
|
||||
olm::OlmMessage olm_msg = j_msg;
|
||||
handle_olm_message(std::move(olm_msg));
|
||||
cache::client()->query_keys(
|
||||
olm_msg.sender,
|
||||
[olm_msg](const UserKeyCache &userKeys, mtx::http::RequestErr e) {
|
||||
if (e) {
|
||||
nhlog::crypto()->error(
|
||||
"Failed to query user keys, dropping olm "
|
||||
"message");
|
||||
return;
|
||||
}
|
||||
handle_olm_message(std::move(olm_msg), userKeys);
|
||||
});
|
||||
} catch (const nlohmann::json::exception &e) {
|
||||
nhlog::crypto()->warn(
|
||||
"parsing error for olm message: {} {}", e.what(), j_msg.dump(2));
|
||||
|
|
@ -197,13 +207,15 @@ handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEven
|
|||
}
|
||||
|
||||
void
|
||||
handle_olm_message(const OlmMessage &msg)
|
||||
handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKeys)
|
||||
{
|
||||
nhlog::crypto()->info("sender : {}", msg.sender);
|
||||
nhlog::crypto()->info("sender_key: {}", msg.sender_key);
|
||||
|
||||
const auto my_key = olm::client()->identity_keys().curve25519;
|
||||
|
||||
bool failed_decryption = false;
|
||||
|
||||
for (const auto &cipher : msg.ciphertext) {
|
||||
// We skip messages not meant for the current device.
|
||||
if (cipher.first != my_key) {
|
||||
|
|
@ -224,6 +236,7 @@ handle_olm_message(const OlmMessage &msg)
|
|||
msg.sender, msg.sender_key, cipher.second);
|
||||
} else {
|
||||
nhlog::crypto()->error("Undecryptable olm message!");
|
||||
failed_decryption = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -231,6 +244,57 @@ handle_olm_message(const OlmMessage &msg)
|
|||
if (!payload.is_null()) {
|
||||
mtx::events::collections::DeviceEvents device_event;
|
||||
|
||||
// Other properties are included in order to prevent an attacker from
|
||||
// publishing someone else's curve25519 keys as their own and subsequently
|
||||
// claiming to have sent messages which they didn't. sender must correspond
|
||||
// to the user who sent the event, recipient to the local user, and
|
||||
// recipient_keys to the local ed25519 key.
|
||||
std::string receiver_ed25519 = payload["recipient_keys"]["ed25519"];
|
||||
if (receiver_ed25519.empty() ||
|
||||
receiver_ed25519 != olm::client()->identity_keys().ed25519) {
|
||||
nhlog::crypto()->warn(
|
||||
"Decrypted event doesn't include our ed25519: {}",
|
||||
payload.dump());
|
||||
return;
|
||||
}
|
||||
std::string receiver = payload["recipient"];
|
||||
if (receiver.empty() || receiver != http::client()->user_id().to_string()) {
|
||||
nhlog::crypto()->warn(
|
||||
"Decrypted event doesn't include our user_id: {}",
|
||||
payload.dump());
|
||||
return;
|
||||
}
|
||||
|
||||
// Clients must confirm that the sender_key and the ed25519 field value
|
||||
// under the keys property match the keys returned by /keys/query for the
|
||||
// given user, and must also verify the signature of the payload. Without
|
||||
// this check, a client cannot be sure that the sender device owns the
|
||||
// private part of the ed25519 key it claims to have in the Olm payload.
|
||||
// This is crucial when the ed25519 key corresponds to a verified device.
|
||||
std::string sender_ed25519 = payload["keys"]["ed25519"];
|
||||
if (sender_ed25519.empty()) {
|
||||
nhlog::crypto()->warn(
|
||||
"Decrypted event doesn't include sender ed25519: {}",
|
||||
payload.dump());
|
||||
return;
|
||||
}
|
||||
|
||||
bool from_their_device = false;
|
||||
for (auto [device_id, key] : otherUserDeviceKeys.device_keys) {
|
||||
if (key.keys.at("curve25519:" + device_id) == msg.sender_key) {
|
||||
if (key.keys.at("ed25519:" + device_id) == sender_ed25519) {
|
||||
from_their_device = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!from_their_device) {
|
||||
nhlog::crypto()->warn("Decrypted event isn't sent from a device "
|
||||
"listed by that user! {}",
|
||||
payload.dump());
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::string msg_type = payload["type"];
|
||||
json event_array = json::array();
|
||||
|
|
@ -242,7 +306,7 @@ handle_olm_message(const OlmMessage &msg)
|
|||
if (temp_events.empty()) {
|
||||
nhlog::crypto()->warn("Decrypted unknown event: {}",
|
||||
payload.dump());
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
device_event = temp_events.at(0);
|
||||
}
|
||||
|
|
@ -276,17 +340,20 @@ handle_olm_message(const OlmMessage &msg)
|
|||
ChatPage::instance()->receivedDeviceVerificationDone(e8->content);
|
||||
} else if (auto roomKey =
|
||||
std::get_if<DeviceEvent<msg::RoomKey>>(&device_event)) {
|
||||
create_inbound_megolm_session(*roomKey, msg.sender_key);
|
||||
create_inbound_megolm_session(
|
||||
*roomKey, msg.sender_key, sender_ed25519);
|
||||
} else if (auto forwardedRoomKey =
|
||||
std::get_if<DeviceEvent<msg::ForwardedRoomKey>>(
|
||||
&device_event)) {
|
||||
forwardedRoomKey->content.forwarding_curve25519_key_chain.push_back(
|
||||
msg.sender_key);
|
||||
import_inbound_megolm_session(*forwardedRoomKey);
|
||||
} else if (auto e =
|
||||
std::get_if<DeviceEvent<msg::SecretSend>>(&device_event)) {
|
||||
auto local_user = http::client()->user_id();
|
||||
|
||||
if (msg.sender != local_user.to_string())
|
||||
continue;
|
||||
return;
|
||||
|
||||
auto secret_name =
|
||||
request_id_to_secret_name.find(e->content.request_id);
|
||||
|
|
@ -306,7 +373,7 @@ handle_olm_message(const OlmMessage &msg)
|
|||
cache::verificationStatus(local_user.to_string());
|
||||
|
||||
if (!verificationStatus)
|
||||
continue;
|
||||
return;
|
||||
|
||||
auto deviceKeys = cache::userKeys(local_user.to_string());
|
||||
std::string sender_device_id;
|
||||
|
|
@ -344,7 +411,6 @@ handle_olm_message(const OlmMessage &msg)
|
|||
"for secrect "
|
||||
"'{}'",
|
||||
name);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -360,27 +426,28 @@ handle_olm_message(const OlmMessage &msg)
|
|||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
failed_decryption = true;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
auto otherUserDeviceKeys = cache::userKeys(msg.sender);
|
||||
if (failed_decryption) {
|
||||
try {
|
||||
std::map<std::string, std::vector<std::string>> targets;
|
||||
for (auto [device_id, key] : otherUserDeviceKeys.device_keys) {
|
||||
if (key.keys.at("curve25519:" + device_id) == msg.sender_key)
|
||||
targets[msg.sender].push_back(device_id);
|
||||
}
|
||||
|
||||
if (!otherUserDeviceKeys)
|
||||
return;
|
||||
|
||||
std::map<std::string, std::vector<std::string>> targets;
|
||||
for (auto [device_id, key] : otherUserDeviceKeys->device_keys) {
|
||||
if (key.keys.at("curve25519:" + device_id) == msg.sender_key)
|
||||
targets[msg.sender].push_back(device_id);
|
||||
send_encrypted_to_device_messages(
|
||||
targets, mtx::events::DeviceEvent<mtx::events::msg::Dummy>{}, true);
|
||||
nhlog::crypto()->info("Recovering from broken olm channel with {}:{}",
|
||||
msg.sender,
|
||||
msg.sender_key);
|
||||
} catch (std::exception &e) {
|
||||
nhlog::crypto()->error("Failed to recover from broken olm sessions: {}",
|
||||
e.what());
|
||||
}
|
||||
|
||||
send_encrypted_to_device_messages(
|
||||
targets, mtx::events::DeviceEvent<mtx::events::msg::Dummy>{}, true);
|
||||
nhlog::crypto()->info(
|
||||
"Recovering from broken olm channel with {}:{}", msg.sender, msg.sender_key);
|
||||
} catch (std::exception &e) {
|
||||
nhlog::crypto()->error("Failed to recover from broken olm sessions: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -450,7 +517,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
|||
|
||||
std::map<std::string, std::vector<std::string>> sendSessionTo;
|
||||
mtx::crypto::OutboundGroupSessionPtr session = nullptr;
|
||||
OutboundGroupSessionData group_session_data;
|
||||
GroupSessionData group_session_data;
|
||||
|
||||
if (cache::outboundMegolmSessionExists(room_id)) {
|
||||
auto res = cache::getOutboundMegolmSession(room_id);
|
||||
|
|
@ -519,7 +586,8 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
|||
} else {
|
||||
// compare devices
|
||||
bool device_removed = false;
|
||||
for (const auto &dev : session_member_it->second.devices) {
|
||||
for (const auto &dev :
|
||||
session_member_it->second.deviceids) {
|
||||
if (!member_it->second ||
|
||||
!member_it->second->device_keys.count(
|
||||
dev.first)) {
|
||||
|
|
@ -541,7 +609,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
|||
if (member_it->second)
|
||||
for (const auto &dev :
|
||||
member_it->second->device_keys)
|
||||
if (!session_member_it->second.devices
|
||||
if (!session_member_it->second.deviceids
|
||||
.count(dev.first) &&
|
||||
(member_it->first != own_user_id ||
|
||||
dev.first != device_id))
|
||||
|
|
@ -571,32 +639,28 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
|||
const auto session_key = mtx::crypto::session_key(session.get());
|
||||
|
||||
// Saving the new megolm session.
|
||||
OutboundGroupSessionData session_data{};
|
||||
session_data.session_id = mtx::crypto::session_id(session.get());
|
||||
session_data.session_key = mtx::crypto::session_key(session.get());
|
||||
session_data.message_index = 0;
|
||||
session_data.timestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
GroupSessionData session_data{};
|
||||
session_data.message_index = 0;
|
||||
session_data.timestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
session_data.sender_claimed_ed25519_key = olm::client()->identity_keys().ed25519;
|
||||
|
||||
sendSessionTo.clear();
|
||||
|
||||
for (const auto &[user, devices] : members) {
|
||||
sendSessionTo[user] = {};
|
||||
session_data.initially.keys[user] = {};
|
||||
session_data.currently.keys[user] = {};
|
||||
if (devices) {
|
||||
for (const auto &[device_id_, key] : devices->device_keys) {
|
||||
(void)key;
|
||||
if (device_id != device_id_ || user != own_user_id) {
|
||||
sendSessionTo[user].push_back(device_id_);
|
||||
session_data.initially.keys[user]
|
||||
.devices[device_id_] = 0;
|
||||
session_data.currently.keys[user]
|
||||
.deviceids[device_id_] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache::saveOutboundMegolmSession(room_id, session_data, session);
|
||||
group_session_data = std::move(session_data);
|
||||
|
||||
{
|
||||
MegolmSessionIndex index;
|
||||
index.room_id = room_id;
|
||||
|
|
@ -604,8 +668,12 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
|||
index.sender_key = olm::client()->identity_keys().curve25519;
|
||||
auto megolm_session =
|
||||
olm::client()->init_inbound_group_session(session_key);
|
||||
cache::saveInboundMegolmSession(index, std::move(megolm_session));
|
||||
cache::saveInboundMegolmSession(
|
||||
index, std::move(megolm_session), session_data);
|
||||
}
|
||||
|
||||
cache::saveOutboundMegolmSession(room_id, session_data, session);
|
||||
group_session_data = std::move(session_data);
|
||||
}
|
||||
|
||||
mtx::events::DeviceEvent<mtx::events::msg::RoomKey> megolm_payload{};
|
||||
|
|
@ -641,8 +709,8 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
|||
group_session_data.currently.keys[user] = {};
|
||||
|
||||
for (const auto &device_id_ : devices) {
|
||||
if (!group_session_data.currently.keys[user].devices.count(device_id_))
|
||||
group_session_data.currently.keys[user].devices[device_id_] =
|
||||
if (!group_session_data.currently.keys[user].deviceids.count(device_id_))
|
||||
group_session_data.currently.keys[user].deviceids[device_id_] =
|
||||
group_session_data.message_index;
|
||||
}
|
||||
}
|
||||
|
|
@ -704,7 +772,8 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCip
|
|||
|
||||
void
|
||||
create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::RoomKey> &roomKey,
|
||||
const std::string &sender_key)
|
||||
const std::string &sender_key,
|
||||
const std::string &sender_ed25519)
|
||||
{
|
||||
MegolmSessionIndex index;
|
||||
index.room_id = roomKey.content.room_id;
|
||||
|
|
@ -712,9 +781,13 @@ create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::R
|
|||
index.sender_key = sender_key;
|
||||
|
||||
try {
|
||||
GroupSessionData data{};
|
||||
data.forwarding_curve25519_key_chain = {sender_key};
|
||||
data.sender_claimed_ed25519_key = sender_ed25519;
|
||||
|
||||
auto megolm_session =
|
||||
olm::client()->init_inbound_group_session(roomKey.content.session_key);
|
||||
cache::saveInboundMegolmSession(index, std::move(megolm_session));
|
||||
cache::saveInboundMegolmSession(index, std::move(megolm_session), data);
|
||||
} catch (const lmdb::error &e) {
|
||||
nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
|
||||
return;
|
||||
|
|
@ -741,7 +814,13 @@ import_inbound_megolm_session(
|
|||
try {
|
||||
auto megolm_session =
|
||||
olm::client()->import_inbound_group_session(roomKey.content.session_key);
|
||||
cache::saveInboundMegolmSession(index, std::move(megolm_session));
|
||||
|
||||
GroupSessionData data{};
|
||||
data.forwarding_curve25519_key_chain =
|
||||
roomKey.content.forwarding_curve25519_key_chain;
|
||||
data.sender_claimed_ed25519_key = roomKey.content.sender_claimed_ed25519_key;
|
||||
|
||||
cache::saveInboundMegolmSession(index, std::move(megolm_session), data);
|
||||
} catch (const lmdb::error &e) {
|
||||
nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
|
||||
return;
|
||||
|
|
@ -817,21 +896,16 @@ handle_key_request_message(const mtx::events::DeviceEvent<mtx::events::msg::KeyR
|
|||
return;
|
||||
}
|
||||
|
||||
// Check if we were the sender of the session being requested.
|
||||
if (req.content.sender_key != olm::client()->identity_keys().curve25519) {
|
||||
nhlog::crypto()->debug("ignoring key request {} because we were not the sender: "
|
||||
"\nrequested({}) ours({})",
|
||||
req.content.request_id,
|
||||
req.content.sender_key,
|
||||
olm::client()->identity_keys().curve25519);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have the keys for the requested session.
|
||||
auto outboundSession = cache::getOutboundMegolmSession(req.content.room_id);
|
||||
if (!outboundSession.session) {
|
||||
nhlog::crypto()->warn("requested session not found in room: {}",
|
||||
req.content.room_id);
|
||||
// Check if we were the sender of the session being requested (unless it is actually us
|
||||
// requesting the session).
|
||||
if (req.sender != http::client()->user_id().to_string() &&
|
||||
req.content.sender_key != olm::client()->identity_keys().curve25519) {
|
||||
nhlog::crypto()->debug(
|
||||
"ignoring key request {} because we did not create the requested session: "
|
||||
"\nrequested({}) ours({})",
|
||||
req.content.request_id,
|
||||
req.content.sender_key,
|
||||
olm::client()->identity_keys().curve25519);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -839,7 +913,15 @@ handle_key_request_message(const mtx::events::DeviceEvent<mtx::events::msg::KeyR
|
|||
MegolmSessionIndex index{};
|
||||
index.room_id = req.content.room_id;
|
||||
index.session_id = req.content.session_id;
|
||||
index.sender_key = olm::client()->identity_keys().curve25519;
|
||||
index.sender_key = req.content.sender_key;
|
||||
|
||||
// Check if we have the keys for the requested session.
|
||||
auto sessionData = cache::getMegolmSessionData(index);
|
||||
if (!sessionData) {
|
||||
nhlog::crypto()->warn("requested session not found in room: {}",
|
||||
req.content.room_id);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto session = cache::getInboundMegolmSession(index);
|
||||
if (!session) {
|
||||
|
|
@ -873,12 +955,12 @@ handle_key_request_message(const mtx::events::DeviceEvent<mtx::events::msg::KeyR
|
|||
|
||||
bool shouldSeeKeys = false;
|
||||
uint64_t minimumIndex = -1;
|
||||
if (outboundSession.data.currently.keys.count(req.sender)) {
|
||||
if (outboundSession.data.currently.keys.at(req.sender)
|
||||
.devices.count(req.content.requesting_device_id)) {
|
||||
if (sessionData->currently.keys.count(req.sender)) {
|
||||
if (sessionData->currently.keys.at(req.sender)
|
||||
.deviceids.count(req.content.requesting_device_id)) {
|
||||
shouldSeeKeys = true;
|
||||
minimumIndex = outboundSession.data.currently.keys.at(req.sender)
|
||||
.devices.at(req.content.requesting_device_id);
|
||||
minimumIndex = sessionData->currently.keys.at(req.sender)
|
||||
.deviceids.at(req.content.requesting_device_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -906,8 +988,9 @@ handle_key_request_message(const mtx::events::DeviceEvent<mtx::events::msg::KeyR
|
|||
forward_key.sender_key = index.sender_key;
|
||||
|
||||
// TODO(Nico): Figure out if this is correct
|
||||
forward_key.sender_claimed_ed25519_key = olm::client()->identity_keys().ed25519;
|
||||
forward_key.forwarding_curve25519_key_chain = {};
|
||||
forward_key.sender_claimed_ed25519_key = sessionData->sender_claimed_ed25519_key;
|
||||
forward_key.forwarding_curve25519_key_chain =
|
||||
sessionData->forwarding_curve25519_key_chain;
|
||||
|
||||
send_megolm_key_to_device(
|
||||
req.sender, req.content.requesting_device_id, forward_key);
|
||||
|
|
@ -928,6 +1011,7 @@ send_megolm_key_to_device(const std::string &user_id,
|
|||
std::map<std::string, std::vector<std::string>> targets;
|
||||
targets[user_id] = {device_id};
|
||||
send_encrypted_to_device_messages(targets, room_key);
|
||||
nhlog::crypto()->debug("Forwarded key to {}:{}", user_id, device_id);
|
||||
}
|
||||
|
||||
DecryptionResult
|
||||
|
|
|
|||
|
|
@ -59,12 +59,13 @@ try_olm_decryption(const std::string &sender_key,
|
|||
const mtx::events::msg::OlmCipherContent &content);
|
||||
|
||||
void
|
||||
handle_olm_message(const OlmMessage &msg);
|
||||
handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKeys);
|
||||
|
||||
//! Establish a new inbound megolm session with the decrypted payload from olm.
|
||||
void
|
||||
create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::RoomKey> &roomKey,
|
||||
const std::string &sender_key);
|
||||
const std::string &sender_key,
|
||||
const std::string &sender_ed25519);
|
||||
void
|
||||
import_inbound_megolm_session(
|
||||
const mtx::events::DeviceEvent<mtx::events::msg::ForwardedRoomKey> &roomKey);
|
||||
|
|
|
|||
100
src/SingleImagePackModel.cpp
Normal file
100
src/SingleImagePackModel.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "SingleImagePackModel.h"
|
||||
|
||||
#include "Cache_p.h"
|
||||
#include "MatrixClient.h"
|
||||
|
||||
SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, roomid_(std::move(pack_.source_room))
|
||||
, statekey_(std::move(pack_.state_key))
|
||||
, pack(std::move(pack_.pack))
|
||||
{
|
||||
if (!pack.pack)
|
||||
pack.pack = mtx::events::msc2545::ImagePack::PackDescription{};
|
||||
|
||||
for (const auto &e : pack.images)
|
||||
shortcodes.push_back(e.first);
|
||||
}
|
||||
|
||||
int
|
||||
SingleImagePackModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return (int)shortcodes.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
SingleImagePackModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{Roles::Url, "url"},
|
||||
{Roles::ShortCode, "shortCode"},
|
||||
{Roles::Body, "body"},
|
||||
{Roles::IsEmote, "isEmote"},
|
||||
{Roles::IsSticker, "isSticker"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant
|
||||
SingleImagePackModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
const auto &img = pack.images.at(shortcodes.at(index.row()));
|
||||
switch (role) {
|
||||
case Url:
|
||||
return QString::fromStdString(img.url);
|
||||
case ShortCode:
|
||||
return QString::fromStdString(shortcodes.at(index.row()));
|
||||
case Body:
|
||||
return QString::fromStdString(img.body);
|
||||
case IsEmote:
|
||||
return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji();
|
||||
case IsSticker:
|
||||
return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool
|
||||
SingleImagePackModel::isGloballyEnabled() const
|
||||
{
|
||||
if (auto roomPacks =
|
||||
cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) {
|
||||
if (auto tmp = std::get_if<
|
||||
mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
|
||||
&*roomPacks)) {
|
||||
if (tmp->content.rooms.count(roomid_) &&
|
||||
tmp->content.rooms.at(roomid_).count(statekey_))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void
|
||||
SingleImagePackModel::setGloballyEnabled(bool enabled)
|
||||
{
|
||||
mtx::events::msc2545::ImagePackRooms content{};
|
||||
if (auto roomPacks =
|
||||
cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) {
|
||||
if (auto tmp = std::get_if<
|
||||
mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
|
||||
&*roomPacks)) {
|
||||
content = tmp->content;
|
||||
}
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
content.rooms[roomid_][statekey_] = {};
|
||||
else
|
||||
content.rooms[roomid_].erase(statekey_);
|
||||
|
||||
http::client()->put_account_data(content, [](mtx::http::RequestErr) {
|
||||
// emit this->globallyEnabledChanged();
|
||||
});
|
||||
}
|
||||
61
src/SingleImagePackModel.h
Normal file
61
src/SingleImagePackModel.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <mtx/events/mscs/image_packs.hpp>
|
||||
|
||||
#include "CacheStructs.h"
|
||||
|
||||
class SingleImagePackModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString roomid READ roomid CONSTANT)
|
||||
Q_PROPERTY(QString statekey READ statekey CONSTANT)
|
||||
Q_PROPERTY(QString attribution READ statekey CONSTANT)
|
||||
Q_PROPERTY(QString packname READ packname CONSTANT)
|
||||
Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT)
|
||||
Q_PROPERTY(bool isStickerPack READ isStickerPack CONSTANT)
|
||||
Q_PROPERTY(bool isEmotePack READ isEmotePack CONSTANT)
|
||||
Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY
|
||||
globallyEnabledChanged)
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
Url = Qt::UserRole,
|
||||
ShortCode,
|
||||
Body,
|
||||
IsEmote,
|
||||
IsSticker,
|
||||
};
|
||||
|
||||
SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
QString roomid() const { return QString::fromStdString(roomid_); }
|
||||
QString statekey() const { return QString::fromStdString(statekey_); }
|
||||
QString packname() const { return QString::fromStdString(pack.pack->display_name); }
|
||||
QString attribution() const { return QString::fromStdString(pack.pack->attribution); }
|
||||
QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); }
|
||||
bool isStickerPack() const { return pack.pack->is_sticker(); }
|
||||
bool isEmotePack() const { return pack.pack->is_emoji(); }
|
||||
|
||||
bool isGloballyEnabled() const;
|
||||
void setGloballyEnabled(bool enabled);
|
||||
|
||||
signals:
|
||||
void globallyEnabledChanged();
|
||||
|
||||
private:
|
||||
std::string roomid_;
|
||||
std::string statekey_;
|
||||
|
||||
mtx::events::msc2545::ImagePack pack;
|
||||
std::vector<std::string> shortcodes;
|
||||
};
|
||||
|
|
@ -91,7 +91,7 @@ UserSettings::load(std::optional<QString> profile)
|
|||
privacyScreen_ = settings.value("user/privacy_screen", false).toBool();
|
||||
privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt();
|
||||
shareKeysWithTrustedUsers_ =
|
||||
settings.value("user/share_keys_with_trusted_users", true).toBool();
|
||||
settings.value("user/automatically_share_keys_with_trusted_users", false).toBool();
|
||||
mobileMode_ = settings.value("user/mobile_mode", false).toBool();
|
||||
emojiFont_ = settings.value("user/emoji_font_family", "default").toString();
|
||||
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
|
||||
|
|
@ -610,7 +610,8 @@ UserSettings::save()
|
|||
settings.setValue("decrypt_sidebar", decryptSidebar_);
|
||||
settings.setValue("privacy_screen", privacyScreen_);
|
||||
settings.setValue("privacy_screen_timeout", privacyScreenTimeout_);
|
||||
settings.setValue("share_keys_with_trusted_users", shareKeysWithTrustedUsers_);
|
||||
settings.setValue("automatically_share_keys_with_trusted_users",
|
||||
shareKeysWithTrustedUsers_);
|
||||
settings.setValue("mobile_mode", mobileMode_);
|
||||
settings.setValue("font_size", baseFontSize_);
|
||||
settings.setValue("typing_notifications", typingNotifications_);
|
||||
|
|
@ -1398,7 +1399,7 @@ UserSettingsPage::exportSessionKeys()
|
|||
QString suffix("-----END MEGOLM SESSION DATA-----");
|
||||
QString newline("\n");
|
||||
QTextStream out(&file);
|
||||
out << prefix << newline << b64 << newline << suffix;
|
||||
out << prefix << newline << b64 << newline << suffix << newline;
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
QMessageBox::warning(this, tr("Error"), e.what());
|
||||
|
|
|
|||
|
|
@ -540,7 +540,7 @@ utils::markdownToHtml(const QString &text, bool rainbowify)
|
|||
// Use colors as described here:
|
||||
// https://shark.comfsm.fm/~dleeling/cis/hsl_rainbow.html
|
||||
auto color =
|
||||
QColor::fromHslF((charIdx - 1.0) / textLen * (5. / 6.), 1.0, 0.5);
|
||||
QColor::fromHslF((charIdx - 1.0) / textLen * (5. / 6.), 0.9, 0.5);
|
||||
// format color for HTML
|
||||
auto colorString = color.name(QColor::NameFormat::HexRgb);
|
||||
// create HTML element for current char
|
||||
|
|
|
|||
|
|
@ -1,158 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QDebug>
|
||||
#include <QIcon>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QListWidgetItem>
|
||||
#include <QPushButton>
|
||||
#include <QStyleOption>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "dialogs/InviteUsers.h"
|
||||
|
||||
#include "Config.h"
|
||||
#include "InviteeItem.h"
|
||||
#include "ui/TextField.h"
|
||||
|
||||
#include <mtx/identifiers.hpp>
|
||||
|
||||
using namespace dialogs;
|
||||
|
||||
InviteUsers::InviteUsers(QWidget *parent)
|
||||
: QFrame(parent)
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
setMinimumWidth(conf::window::minModalWidth);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(0);
|
||||
buttonLayout->setMargin(0);
|
||||
|
||||
confirmBtn_ = new QPushButton("Invite", this);
|
||||
confirmBtn_->setDefault(true);
|
||||
cancelBtn_ = new QPushButton(tr("Cancel"), this);
|
||||
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->setSpacing(15);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
|
||||
inviteeInput_ = new TextField(this);
|
||||
inviteeInput_->setLabel(tr("User ID to invite"));
|
||||
|
||||
inviteeList_ = new QListWidget;
|
||||
inviteeList_->setFrameStyle(QFrame::NoFrame);
|
||||
inviteeList_->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
inviteeList_->setAttribute(Qt::WA_MacShowFocusRect, 0);
|
||||
inviteeList_->setSpacing(5);
|
||||
|
||||
errorLabel_ = new QLabel(this);
|
||||
errorLabel_->setAlignment(Qt::AlignCenter);
|
||||
|
||||
layout->addWidget(inviteeInput_);
|
||||
layout->addWidget(errorLabel_);
|
||||
layout->addWidget(inviteeList_);
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(inviteeInput_, &TextField::returnPressed, this, &InviteUsers::addUser);
|
||||
connect(confirmBtn_, &QPushButton::clicked, [this]() {
|
||||
if (!inviteeInput_->text().trimmed().isEmpty()) {
|
||||
addUser();
|
||||
}
|
||||
|
||||
emit sendInvites(invitedUsers());
|
||||
|
||||
inviteeInput_->clear();
|
||||
inviteeList_->clear();
|
||||
errorLabel_->hide();
|
||||
|
||||
emit close();
|
||||
});
|
||||
|
||||
connect(cancelBtn_, &QPushButton::clicked, [this]() {
|
||||
inviteeInput_->clear();
|
||||
inviteeList_->clear();
|
||||
errorLabel_->hide();
|
||||
|
||||
emit close();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
InviteUsers::addUser()
|
||||
{
|
||||
auto user_id = inviteeInput_->text();
|
||||
|
||||
try {
|
||||
namespace ids = mtx::identifiers;
|
||||
auto user = ids::parse<ids::User>(user_id.toStdString());
|
||||
|
||||
auto item = new QListWidgetItem(inviteeList_);
|
||||
auto invitee = new InviteeItem(user, this);
|
||||
|
||||
item->setSizeHint(invitee->minimumSizeHint());
|
||||
item->setFlags(Qt::NoItemFlags);
|
||||
item->setTextAlignment(Qt::AlignCenter);
|
||||
|
||||
inviteeList_->setItemWidget(item, invitee);
|
||||
|
||||
connect(invitee, &InviteeItem::removeItem, this, [this, item]() {
|
||||
emit removeInvitee(item);
|
||||
});
|
||||
|
||||
errorLabel_->hide();
|
||||
inviteeInput_->clear();
|
||||
} catch (std::exception &e) {
|
||||
errorLabel_->setText(e.what());
|
||||
errorLabel_->show();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
InviteUsers::removeInvitee(QListWidgetItem *item)
|
||||
{
|
||||
int row = inviteeList_->row(item);
|
||||
auto widget = inviteeList_->takeItem(row);
|
||||
|
||||
inviteeList_->removeItemWidget(widget);
|
||||
}
|
||||
|
||||
QStringList
|
||||
InviteUsers::invitedUsers() const
|
||||
{
|
||||
QStringList users;
|
||||
|
||||
for (int ii = 0; ii < inviteeList_->count(); ++ii) {
|
||||
auto item = inviteeList_->item(ii);
|
||||
auto widget = inviteeList_->itemWidget(item);
|
||||
auto invitee = qobject_cast<InviteeItem *>(widget);
|
||||
|
||||
if (invitee)
|
||||
users << invitee->userID();
|
||||
else
|
||||
qDebug() << "Cast InviteeItem failed";
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
void
|
||||
InviteUsers::showEvent(QShowEvent *event)
|
||||
{
|
||||
inviteeInput_->setFocus();
|
||||
|
||||
QFrame::showEvent(event);
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QStringList>
|
||||
|
||||
class QPushButton;
|
||||
class QLabel;
|
||||
class TextField;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
|
||||
namespace dialogs {
|
||||
|
||||
class InviteUsers : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InviteUsers(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
signals:
|
||||
void sendInvites(QStringList invitees);
|
||||
|
||||
private slots:
|
||||
void removeInvitee(QListWidgetItem *item);
|
||||
|
||||
private:
|
||||
void addUser();
|
||||
QStringList invitedUsers() const;
|
||||
|
||||
QPushButton *confirmBtn_;
|
||||
QPushButton *cancelBtn_;
|
||||
|
||||
TextField *inviteeInput_;
|
||||
QLabel *errorLabel_;
|
||||
|
||||
QListWidget *inviteeList_;
|
||||
};
|
||||
} // dialogs
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QAbstractSlider>
|
||||
#include <QLabel>
|
||||
#include <QListWidgetItem>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
#include <QShortcut>
|
||||
#include <QStyleOption>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "dialogs/MemberList.h"
|
||||
|
||||
#include "Cache.h"
|
||||
#include "ChatPage.h"
|
||||
#include "Config.h"
|
||||
#include "Logging.h"
|
||||
#include "Utils.h"
|
||||
#include "ui/Avatar.h"
|
||||
|
||||
using namespace dialogs;
|
||||
|
||||
MemberItem::MemberItem(const RoomMember &member, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
topLayout_ = new QHBoxLayout(this);
|
||||
topLayout_->setMargin(0);
|
||||
|
||||
textLayout_ = new QVBoxLayout;
|
||||
textLayout_->setMargin(0);
|
||||
textLayout_->setSpacing(0);
|
||||
|
||||
avatar_ = new Avatar(this, 44);
|
||||
avatar_->setLetter(utils::firstChar(member.display_name));
|
||||
|
||||
avatar_->setImage(ChatPage::instance()->currentRoom(), member.user_id);
|
||||
|
||||
QFont nameFont;
|
||||
nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1);
|
||||
|
||||
userId_ = new QLabel(member.user_id, this);
|
||||
userName_ = new QLabel(member.display_name, this);
|
||||
userName_->setFont(nameFont);
|
||||
|
||||
textLayout_->addWidget(userName_);
|
||||
textLayout_->addWidget(userId_);
|
||||
|
||||
topLayout_->addWidget(avatar_);
|
||||
topLayout_->addLayout(textLayout_, 1);
|
||||
}
|
||||
|
||||
void
|
||||
MemberItem::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
||||
|
||||
MemberList::MemberList(const QString &room_id, QWidget *parent)
|
||||
: QFrame(parent)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(conf::modals::WIDGET_SPACING);
|
||||
layout->setMargin(conf::modals::WIDGET_MARGIN);
|
||||
|
||||
list_ = new QListWidget;
|
||||
list_->setFrameStyle(QFrame::NoFrame);
|
||||
list_->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
list_->setSpacing(5);
|
||||
|
||||
QFont largeFont;
|
||||
largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
|
||||
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
setMinimumHeight(list_->sizeHint().height() * 2);
|
||||
setMinimumWidth(std::max(list_->sizeHint().width() + 4 * conf::modals::WIDGET_MARGIN,
|
||||
QFontMetrics(largeFont).averageCharWidth() * 30 -
|
||||
2 * conf::modals::WIDGET_MARGIN));
|
||||
|
||||
QFont font;
|
||||
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
|
||||
|
||||
topLabel_ = new QLabel(tr("Room members"), this);
|
||||
topLabel_->setAlignment(Qt::AlignCenter);
|
||||
topLabel_->setFont(font);
|
||||
|
||||
auto okBtn = new QPushButton(tr("OK"), this);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(15);
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(okBtn);
|
||||
|
||||
layout->addWidget(topLabel_);
|
||||
layout->addWidget(list_);
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
list_->clear();
|
||||
|
||||
connect(list_->verticalScrollBar(), &QAbstractSlider::valueChanged, this, [this](int pos) {
|
||||
if (pos != list_->verticalScrollBar()->maximum())
|
||||
return;
|
||||
|
||||
const size_t numMembers = list_->count() - 1;
|
||||
|
||||
if (numMembers > 0)
|
||||
addUsers(cache::getMembers(room_id_.toStdString(), numMembers));
|
||||
});
|
||||
|
||||
try {
|
||||
addUsers(cache::getMembers(room_id_.toStdString()));
|
||||
} catch (const lmdb::error &e) {
|
||||
nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what());
|
||||
}
|
||||
|
||||
auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this);
|
||||
connect(closeShortcut, &QShortcut::activated, this, &MemberList::close);
|
||||
connect(okBtn, &QPushButton::clicked, this, &MemberList::close);
|
||||
}
|
||||
|
||||
void
|
||||
MemberList::addUsers(const std::vector<RoomMember> &members)
|
||||
{
|
||||
for (const auto &member : members) {
|
||||
auto user = new MemberItem(member, this);
|
||||
auto item = new QListWidgetItem;
|
||||
|
||||
item->setSizeHint(user->minimumSizeHint());
|
||||
item->setFlags(Qt::NoItemFlags);
|
||||
item->setTextAlignment(Qt::AlignCenter);
|
||||
|
||||
list_->insertItem(list_->count() - 1, item);
|
||||
list_->setItemWidget(item, user);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QListWidget>
|
||||
|
||||
class Avatar;
|
||||
class QPushButton;
|
||||
class QHBoxLayout;
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
|
||||
struct RoomMember;
|
||||
|
||||
template<class T>
|
||||
class QSharedPointer;
|
||||
|
||||
namespace dialogs {
|
||||
|
||||
class MemberItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MemberItem(const RoomMember &member, QWidget *parent);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
private:
|
||||
QHBoxLayout *topLayout_;
|
||||
QVBoxLayout *textLayout_;
|
||||
|
||||
Avatar *avatar_;
|
||||
|
||||
QLabel *userName_;
|
||||
QLabel *userId_;
|
||||
};
|
||||
|
||||
class MemberList : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MemberList(const QString &room_id, QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void addUsers(const std::vector<RoomMember> &users);
|
||||
|
||||
private:
|
||||
QString room_id_;
|
||||
QLabel *topLabel_;
|
||||
QListWidget *list_;
|
||||
};
|
||||
} // dialogs
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "Cache.h"
|
||||
#include "ChatPage.h"
|
||||
#include "CombinedImagePackModel.h"
|
||||
#include "CompletionProxyModel.h"
|
||||
#include "Config.h"
|
||||
#include "Logging.h"
|
||||
|
|
@ -501,6 +502,31 @@ InputBar::video(const QString &filename,
|
|||
room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
|
||||
}
|
||||
|
||||
void
|
||||
InputBar::sticker(CombinedImagePackModel *model, int row)
|
||||
{
|
||||
if (!model || row < 0)
|
||||
return;
|
||||
|
||||
auto img = model->imageAt(row);
|
||||
|
||||
mtx::events::msg::StickerImage sticker{};
|
||||
sticker.info = img.info.value_or(mtx::common::ImageInfo{});
|
||||
sticker.url = img.url;
|
||||
sticker.body = img.body;
|
||||
|
||||
if (!room->reply().isEmpty()) {
|
||||
sticker.relations.relations.push_back(
|
||||
{mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
|
||||
}
|
||||
if (!room->edit().isEmpty()) {
|
||||
sticker.relations.relations.push_back(
|
||||
{mtx::common::RelationType::Replace, room->edit().toStdString()});
|
||||
}
|
||||
|
||||
room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
|
||||
}
|
||||
|
||||
void
|
||||
InputBar::command(QString command, QString args)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <mtx/responses/messages.hpp>
|
||||
|
||||
class TimelineModel;
|
||||
class CombinedImagePackModel;
|
||||
class QMimeData;
|
||||
class QDropEvent;
|
||||
class QStringList;
|
||||
|
|
@ -57,6 +58,7 @@ public slots:
|
|||
MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
|
||||
bool rainbowify = false);
|
||||
void reaction(const QString &reactedEvent, const QString &reactionKey);
|
||||
void sticker(CombinedImagePackModel *model, int row);
|
||||
|
||||
private slots:
|
||||
void startTyping();
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
#include "MatrixClient.h"
|
||||
#include "TimelineModel.h"
|
||||
|
||||
Permissions::Permissions(TimelineModel *parent)
|
||||
Permissions::Permissions(QString roomId, QObject *parent)
|
||||
: QObject(parent)
|
||||
, room(parent)
|
||||
, roomId_(roomId)
|
||||
{
|
||||
invalidate();
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ void
|
|||
Permissions::invalidate()
|
||||
{
|
||||
pl = cache::client()
|
||||
->getStateEvent<mtx::events::state::PowerLevels>(room->roomId().toStdString())
|
||||
->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString())
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
|
||||
.content;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class Permissions : public QObject
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Permissions(TimelineModel *parent);
|
||||
Permissions(QString roomId, QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE bool canInvite();
|
||||
Q_INVOKABLE bool canBan();
|
||||
|
|
@ -28,6 +28,6 @@ public:
|
|||
void invalidate();
|
||||
|
||||
private:
|
||||
TimelineModel *room;
|
||||
QString roomId_;
|
||||
mtx::events::state::PowerLevels pl;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "Logging.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "MemberList.h"
|
||||
#include "MxcImageProvider.h"
|
||||
#include "Olm.h"
|
||||
#include "TimelineViewManager.h"
|
||||
|
|
@ -317,6 +318,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
|
|||
, events(room_id.toStdString(), this)
|
||||
, room_id_(room_id)
|
||||
, manager_(manager)
|
||||
, permissions_{room_id}
|
||||
{
|
||||
lastMessage_.timestamp = 0;
|
||||
|
||||
|
|
@ -325,6 +327,10 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
|
|||
this->isSpace_ = create->content.type == mtx::events::state::room_type::space;
|
||||
this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
|
||||
|
||||
// this connection will simplify adding the plainRoomNameChanged() signal everywhere that it
|
||||
// needs to be
|
||||
connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged);
|
||||
|
||||
connect(
|
||||
this,
|
||||
&TimelineModel::redactionFailed,
|
||||
|
|
@ -344,6 +350,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
|
|||
&EventStore::dataChanged,
|
||||
this,
|
||||
[this](int from, int to) {
|
||||
relatedEventCacheBuster++;
|
||||
nhlog::ui()->debug(
|
||||
"data changed {} to {}", events.size() - to - 1, events.size() - from - 1);
|
||||
emit dataChanged(index(events.size() - to - 1, 0),
|
||||
|
|
@ -443,6 +450,7 @@ TimelineModel::roleNames() const
|
|||
{RoomTopic, "roomTopic"},
|
||||
{CallType, "callType"},
|
||||
{Dump, "dump"},
|
||||
{RelatedEventCacheBuster, "relatedEventCacheBuster"},
|
||||
};
|
||||
}
|
||||
int
|
||||
|
|
@ -676,6 +684,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
|
|||
|
||||
return QVariant(m);
|
||||
}
|
||||
case RelatedEventCacheBuster:
|
||||
return relatedEventCacheBuster;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
|
@ -710,6 +720,14 @@ TimelineModel::data(const QModelIndex &index, int role) const
|
|||
return data(*event, role);
|
||||
}
|
||||
|
||||
QVariant
|
||||
TimelineModel::dataById(QString id, int role, QString relatedTo)
|
||||
{
|
||||
if (auto event = events.get(id.toStdString(), relatedTo.toStdString()))
|
||||
return data(*event, role);
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool
|
||||
TimelineModel::canFetchMore(const QModelIndex &) const
|
||||
{
|
||||
|
|
@ -1048,14 +1066,6 @@ TimelineModel::openUserProfile(QString userid)
|
|||
emit manager_->openProfile(userProfile);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineModel::openRoomSettings()
|
||||
{
|
||||
RoomSettings *settings = new RoomSettings(roomId(), this);
|
||||
connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged);
|
||||
openRoomSettingsDialog(settings);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineModel::replyAction(QString id)
|
||||
{
|
||||
|
|
@ -1292,6 +1302,14 @@ struct SendMessageVisitor
|
|||
sendRoomEvent<mtx::events::msg::KeyVerificationCancel,
|
||||
mtx::events::EventType::KeyVerificationCancel>(msg);
|
||||
}
|
||||
void operator()(mtx::events::Sticker msg)
|
||||
{
|
||||
msg.type = mtx::events::EventType::Sticker;
|
||||
if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
|
||||
model_->sendEncryptedMessage(msg, mtx::events::EventType::Sticker);
|
||||
} else
|
||||
emit model_->addPendingMessageToStore(msg);
|
||||
}
|
||||
|
||||
TimelineModel *model_;
|
||||
};
|
||||
|
|
@ -1301,6 +1319,7 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
|
|||
{
|
||||
std::visit(
|
||||
[](auto &msg) {
|
||||
// gets overwritten for reactions and stickers in SendMessageVisitor
|
||||
msg.type = mtx::events::EventType::RoomMessage;
|
||||
msg.event_id = "m" + http::client()->generate_txn_id();
|
||||
msg.sender = http::client()->user_id().to_string();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
#include "CacheStructs.h"
|
||||
#include "EventStore.h"
|
||||
#include "InputBar.h"
|
||||
#include "InviteesModel.h"
|
||||
#include "MemberList.h"
|
||||
#include "Permissions.h"
|
||||
#include "ui/RoomSettings.h"
|
||||
#include "ui/UserProfile.h"
|
||||
|
|
@ -158,7 +160,9 @@ class TimelineModel : public QAbstractListModel
|
|||
Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit)
|
||||
Q_PROPERTY(
|
||||
bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged)
|
||||
Q_PROPERTY(QString roomId READ roomId CONSTANT)
|
||||
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
|
||||
Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY plainRoomNameChanged)
|
||||
Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged)
|
||||
Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
|
||||
Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged)
|
||||
|
|
@ -208,6 +212,7 @@ public:
|
|||
RoomTopic,
|
||||
CallType,
|
||||
Dump,
|
||||
RelatedEventCacheBuster,
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
|
|
@ -215,10 +220,7 @@ public:
|
|||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
|
||||
Q_INVOKABLE QVariant dataById(QString id, int role)
|
||||
{
|
||||
return data(index(idToIndex(id)), role);
|
||||
}
|
||||
Q_INVOKABLE QVariant dataById(QString id, int role, QString relatedTo);
|
||||
|
||||
bool canFetchMore(const QModelIndex &) const override;
|
||||
void fetchMore(const QModelIndex &) override;
|
||||
|
|
@ -237,7 +239,6 @@ public:
|
|||
Q_INVOKABLE void forwardMessage(QString eventId, QString roomId);
|
||||
Q_INVOKABLE void viewDecryptedRawMessage(QString id) const;
|
||||
Q_INVOKABLE void openUserProfile(QString userid);
|
||||
Q_INVOKABLE void openRoomSettings();
|
||||
Q_INVOKABLE void editAction(QString id);
|
||||
Q_INVOKABLE void replyAction(QString id);
|
||||
Q_INVOKABLE void readReceiptsAction(QString id) const;
|
||||
|
|
@ -354,14 +355,13 @@ signals:
|
|||
void lastMessageChanged();
|
||||
void notificationsChanged();
|
||||
|
||||
void openRoomSettingsDialog(RoomSettings *settings);
|
||||
|
||||
void newMessageToSend(mtx::events::collections::TimelineEvents event);
|
||||
void addPendingMessageToStore(mtx::events::collections::TimelineEvents event);
|
||||
void updateFlowEventId(std::string event_id);
|
||||
|
||||
void encryptionChanged();
|
||||
void roomNameChanged();
|
||||
void plainRoomNameChanged();
|
||||
void roomTopicChanged();
|
||||
void roomAvatarUrlChanged();
|
||||
void roomMemberCountChanged();
|
||||
|
|
@ -391,7 +391,7 @@ private:
|
|||
TimelineViewManager *manager_;
|
||||
|
||||
InputBar input_{this};
|
||||
Permissions permissions_{this};
|
||||
Permissions permissions_;
|
||||
|
||||
QTimer showEventTimer{this};
|
||||
QString eventIdToShow;
|
||||
|
|
@ -403,6 +403,8 @@ private:
|
|||
|
||||
int notification_count = 0, highlight_count = 0;
|
||||
|
||||
unsigned int relatedEventCacheBuster = 0;
|
||||
|
||||
bool decryptDescription = true;
|
||||
bool m_paginationInProgress = false;
|
||||
bool isSpace_ = false;
|
||||
|
|
@ -413,10 +415,17 @@ template<class T>
|
|||
void
|
||||
TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType)
|
||||
{
|
||||
mtx::events::RoomEvent<T> msgCopy = {};
|
||||
msgCopy.content = content;
|
||||
msgCopy.type = eventType;
|
||||
emit newMessageToSend(msgCopy);
|
||||
if constexpr (std::is_same_v<T, mtx::events::msg::StickerImage>) {
|
||||
mtx::events::Sticker msgCopy = {};
|
||||
msgCopy.content = content;
|
||||
msgCopy.type = eventType;
|
||||
emit newMessageToSend(msgCopy);
|
||||
} else {
|
||||
mtx::events::RoomEvent<T> msgCopy = {};
|
||||
msgCopy.content = content;
|
||||
msgCopy.type = eventType;
|
||||
emit newMessageToSend(msgCopy);
|
||||
}
|
||||
resetReply();
|
||||
resetEdit();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,19 @@
|
|||
#include "ChatPage.h"
|
||||
#include "Clipboard.h"
|
||||
#include "ColorImageProvider.h"
|
||||
#include "CombinedImagePackModel.h"
|
||||
#include "CompletionProxyModel.h"
|
||||
#include "DelegateChooser.h"
|
||||
#include "DeviceVerificationFlow.h"
|
||||
#include "EventAccessors.h"
|
||||
#include "ImagePackListModel.h"
|
||||
#include "InviteesModel.h"
|
||||
#include "Logging.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "MxcImageProvider.h"
|
||||
#include "RoomsModel.h"
|
||||
#include "SingleImagePackModel.h"
|
||||
#include "UserSettingsPage.h"
|
||||
#include "UsersModel.h"
|
||||
#include "dialogs/ImageOverlay.h"
|
||||
|
|
@ -144,6 +148,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
|||
qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
|
||||
qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
|
||||
qRegisterMetaType<CombinedImagePackModel *>();
|
||||
|
||||
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
|
||||
"im.nheko",
|
||||
|
|
@ -172,6 +177,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
|||
0,
|
||||
"UserProfileModel",
|
||||
"UserProfile needs to be instantiated on the C++ side");
|
||||
qmlRegisterUncreatableType<MemberList>(
|
||||
"im.nheko", 1, 0, "MemberList", "MemberList needs to be instantiated on the C++ side");
|
||||
qmlRegisterUncreatableType<RoomSettings>(
|
||||
"im.nheko",
|
||||
1,
|
||||
|
|
@ -180,6 +187,24 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
|||
"Room Settings needs to be instantiated on the C++ side");
|
||||
qmlRegisterUncreatableType<TimelineModel>(
|
||||
"im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side");
|
||||
qmlRegisterUncreatableType<ImagePackListModel>(
|
||||
"im.nheko",
|
||||
1,
|
||||
0,
|
||||
"ImagePackListModel",
|
||||
"ImagePackListModel needs to be instantiated on the C++ side");
|
||||
qmlRegisterUncreatableType<SingleImagePackModel>(
|
||||
"im.nheko",
|
||||
1,
|
||||
0,
|
||||
"SingleImagePackModel",
|
||||
"SingleImagePackModel needs to be instantiated on the C++ side");
|
||||
qmlRegisterUncreatableType<InviteesModel>(
|
||||
"im.nheko",
|
||||
1,
|
||||
0,
|
||||
"InviteesModel",
|
||||
"InviteesModel needs to be instantiated on the C++ side");
|
||||
|
||||
static auto self = this;
|
||||
qmlRegisterSingletonType<MainWindow>(
|
||||
|
|
@ -340,6 +365,41 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
|||
&TimelineViewManager::openImageOverlayInternal);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openRoomMembers(QString room_id)
|
||||
{
|
||||
MemberList *memberList = new MemberList(room_id, this);
|
||||
emit openRoomMembersDialog(memberList);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openRoomSettings(QString room_id)
|
||||
{
|
||||
RoomSettings *settings = new RoomSettings(room_id, this);
|
||||
connect(rooms_->getRoomById(room_id).data(),
|
||||
&TimelineModel::roomAvatarUrlChanged,
|
||||
settings,
|
||||
&RoomSettings::avatarChanged);
|
||||
emit openRoomSettingsDialog(settings);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openInviteUsers(QString roomId)
|
||||
{
|
||||
InviteesModel *model = new InviteesModel{this};
|
||||
connect(model, &InviteesModel::accept, this, [this, model, roomId]() {
|
||||
emit inviteUsers(roomId, model->mxids());
|
||||
});
|
||||
emit openInviteUsersDialog(model);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openGlobalUserProfile(QString userId)
|
||||
{
|
||||
UserProfile *profile = new UserProfile{QString{}, userId, this};
|
||||
emit openProfile(profile);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::setVideoCallItem()
|
||||
{
|
||||
|
|
@ -397,6 +457,12 @@ TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId)
|
|||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openImagePackSettings(QString roomid)
|
||||
{
|
||||
emit showImagePackSettings(new ImagePackListModel(roomid.toStdString(), this));
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img)
|
||||
{
|
||||
|
|
@ -419,17 +485,6 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img)
|
|||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openInviteUsersDialog()
|
||||
{
|
||||
MainWindow::instance()->openInviteUsersDialog(
|
||||
[this](const QStringList &invitees) { emit inviteUsers(invitees); });
|
||||
}
|
||||
void
|
||||
TimelineViewManager::openMemberListDialog(QString roomid) const
|
||||
{
|
||||
MainWindow::instance()->openMemberListDialog(roomid);
|
||||
}
|
||||
void
|
||||
TimelineViewManager::openLeaveRoomDialog(QString roomid) const
|
||||
{
|
||||
|
|
@ -593,6 +648,11 @@ TimelineViewManager::completerFor(QString completerName, QString roomId)
|
|||
auto proxy = new CompletionProxyModel(roomModel);
|
||||
roomModel->setParent(proxy);
|
||||
return proxy;
|
||||
} else if (completerName == "stickers") {
|
||||
auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), true);
|
||||
auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4);
|
||||
stickerModel->setParent(proxy);
|
||||
return proxy;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ class ColorImageProvider;
|
|||
class UserSettings;
|
||||
class ChatPage;
|
||||
class DeviceVerificationFlow;
|
||||
class ImagePackListModel;
|
||||
|
||||
class TimelineViewManager : public QObject
|
||||
{
|
||||
|
|
@ -57,6 +58,7 @@ public:
|
|||
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
|
||||
bool isWindowFocused() const { return isWindowFocused_; }
|
||||
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId);
|
||||
Q_INVOKABLE void openImagePackSettings(QString roomid);
|
||||
Q_INVOKABLE QColor userColor(QString id, QColor background);
|
||||
Q_INVOKABLE QString escapeEmoji(QString str) const;
|
||||
Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); }
|
||||
|
|
@ -64,9 +66,12 @@ public:
|
|||
Q_INVOKABLE QString userPresence(QString id) const;
|
||||
Q_INVOKABLE QString userStatus(QString id) const;
|
||||
|
||||
Q_INVOKABLE void openRoomMembers(QString room_id);
|
||||
Q_INVOKABLE void openRoomSettings(QString room_id);
|
||||
Q_INVOKABLE void openInviteUsers(QString roomId);
|
||||
Q_INVOKABLE void openGlobalUserProfile(QString userId);
|
||||
|
||||
Q_INVOKABLE void focusMessageInput();
|
||||
Q_INVOKABLE void openInviteUsersDialog();
|
||||
Q_INVOKABLE void openMemberListDialog(QString roomid) const;
|
||||
Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const;
|
||||
Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow);
|
||||
|
||||
|
|
@ -81,11 +86,17 @@ signals:
|
|||
void replyingEventChanged(QString replyingEvent);
|
||||
void replyClosed();
|
||||
void newDeviceVerificationRequest(DeviceVerificationFlow *flow);
|
||||
void inviteUsers(QStringList users);
|
||||
void inviteUsers(QString roomId, QStringList users);
|
||||
void showRoomList();
|
||||
void narrowViewChanged();
|
||||
void focusChanged();
|
||||
void focusInput();
|
||||
void openImageOverlayInternalCb(QString eventId, QImage img);
|
||||
void openRoomMembersDialog(MemberList *members);
|
||||
void openRoomSettingsDialog(RoomSettings *settings);
|
||||
void openInviteUsersDialog(InviteesModel *invitees);
|
||||
void openProfile(UserProfile *profile);
|
||||
void showImagePackSettings(ImagePackListModel *packlist);
|
||||
|
||||
public slots:
|
||||
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||
|
|
|
|||
|
|
@ -134,4 +134,4 @@ private:
|
|||
RoomInfo info_;
|
||||
int notifications_ = 0;
|
||||
int accessRules_ = 0;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue