diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 16e4f207..cdbd36c5 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -121,6 +121,21 @@ struct RoomEventType
{
return qml_mtx_events::EventType::Redacted;
}
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::CallInvite> &)
+ {
+ return qml_mtx_events::EventType::CallInvite;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::CallAnswer> &)
+ {
+ return qml_mtx_events::EventType::CallAnswer;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::CallHangUp> &)
+ {
+ return qml_mtx_events::EventType::CallHangUp;
+ }
// ::EventType::Type operator()(const Event<mtx::events::msg::Location> &e) { return
// ::EventType::LocationMessage; }
};
@@ -538,7 +553,7 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
if (timeline.events.empty())
return;
- std::vector<QString> ids = internalAddEvents(timeline.events);
+ std::vector<QString> ids = internalAddEvents(timeline.events, true);
if (!ids.empty()) {
beginInsertRows(QModelIndex(), 0, static_cast<int>(ids.size() - 1));
@@ -572,6 +587,23 @@ isMessage(const mtx::events::EncryptedEvent<T> &)
return true;
}
+auto
+isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &)
+{
+ return true;
+}
+
+auto
+isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &)
+{
+ return true;
+}
+auto
+isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &)
+{
+ return true;
+}
+
// Workaround. We also want to see a room at the top, if we just joined it
auto
isYourJoin(const mtx::events::StateEvent<mtx::events::state::Member> &e)
@@ -623,7 +655,8 @@ TimelineModel::updateLastMessage()
std::vector<QString>
TimelineModel::internalAddEvents(
- const std::vector<mtx::events::collections::TimelineEvents> &timeline)
+ const std::vector<mtx::events::collections::TimelineEvents> &timeline,
+ bool emitCallEvents)
{
std::vector<QString> ids;
for (auto e : timeline) {
@@ -717,6 +750,46 @@ TimelineModel::internalAddEvents(
if (encInfo)
emit newEncryptedImage(encInfo.value());
+
+ if (emitCallEvents) {
+ // event room_id is not set, apparently due to spec bug
+ if (auto callInvite = std::get_if<
+ mtx::events::RoomEvent<mtx::events::msg::CallInvite>>(&e_)) {
+ callInvite->room_id = room_id_.toStdString();
+ emit newCallEvent(e_);
+ } else if (std::holds_alternative<mtx::events::RoomEvent<
+ mtx::events::msg::CallCandidates>>(e_) ||
+ std::holds_alternative<
+ mtx::events::RoomEvent<mtx::events::msg::CallAnswer>>( e_) ||
+ std::holds_alternative<
+ mtx::events::RoomEvent<mtx::events::msg::CallHangUp>>( e_)) {
+ emit newCallEvent(e_);
+ }
+ }
+ }
+
+ if (std::holds_alternative<
+ mtx::events::RoomEvent<mtx::events::msg::CallCandidates>>(e)) {
+ // don't display CallCandidate events to user
+ events.insert(id, e);
+ if (emitCallEvents)
+ emit newCallEvent(e);
+ continue;
+ }
+
+ if (emitCallEvents) {
+ // event room_id is not set, apparently due to spec bug
+ if (auto callInvite =
+ std::get_if<mtx::events::RoomEvent<mtx::events::msg::CallInvite>>(
+ &e)) {
+ callInvite->room_id = room_id_.toStdString();
+ emit newCallEvent(e);
+ } else if (std::holds_alternative<
+ mtx::events::RoomEvent<mtx::events::msg::CallAnswer>>(e) ||
+ std::holds_alternative<
+ mtx::events::RoomEvent<mtx::events::msg::CallHangUp>>(e)) {
+ emit newCallEvent(e);
+ }
}
this->events.insert(id, e);
@@ -774,7 +847,7 @@ TimelineModel::readEvent(const std::string &id)
void
TimelineModel::addBackwardsEvents(const mtx::responses::Messages &msgs)
{
- std::vector<QString> ids = internalAddEvents(msgs.chunk);
+ std::vector<QString> ids = internalAddEvents(msgs.chunk, false);
if (!ids.empty()) {
beginInsertRows(QModelIndex(),
@@ -1064,14 +1137,17 @@ TimelineModel::markEventsAsRead(const std::vector<QString> &event_ids)
}
void
-TimelineModel::sendEncryptedMessage(const std::string &txn_id, nlohmann::json content)
+TimelineModel::sendEncryptedMessageEvent(const std::string &txn_id,
+ nlohmann::json content,
+ mtx::events::EventType eventType)
{
const auto room_id = room_id_.toStdString();
using namespace mtx::events;
using namespace mtx::identifiers;
- json doc = {{"type", "m.room.message"}, {"content", content}, {"room_id", room_id}};
+ json doc = {
+ {"type", mtx::events::to_string(eventType)}, {"content", content}, {"room_id", room_id}};
try {
// Check if we have already an outbound megolm session then we can use.
@@ -1375,45 +1451,56 @@ struct SendMessageVisitor
, model_(model)
{}
+ template<typename T, mtx::events::EventType Event>
+ void sendRoomEvent(const mtx::events::RoomEvent<T> &msg)
+ {
+ if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
+ auto encInfo = mtx::accessors::file(msg);
+ if (encInfo)
+ emit model_->newEncryptedImage(encInfo.value());
+
+ model_->sendEncryptedMessageEvent(
+ txn_id_qstr_.toStdString(), nlohmann::json(msg.content), Event);
+ } else {
+ sendUnencryptedRoomEvent<T, Event>(msg);
+ }
+ }
+
+ template<typename T, mtx::events::EventType Event>
+ void sendUnencryptedRoomEvent(const mtx::events::RoomEvent<T> &msg)
+ {
+ QString txn_id_qstr = txn_id_qstr_;
+ TimelineModel *model = model_;
+ http::client()->send_room_message<T, Event>(
+ model->room_id_.toStdString(),
+ txn_id_qstr.toStdString(),
+ msg.content,
+ [txn_id_qstr, model](const mtx::responses::EventId &res,
+ mtx::http::RequestErr err) {
+ if (err) {
+ const int status_code = static_cast<int>(err->status_code);
+ nhlog::net()->warn("[{}] failed to send message: {} {}",
+ txn_id_qstr.toStdString(),
+ err->matrix_error.error,
+ status_code);
+ emit model->messageFailed(txn_id_qstr);
+ }
+ emit model->messageSent(txn_id_qstr,
+ QString::fromStdString(res.event_id.to_string()));
+ });
+ }
+
// Do-nothing operator for all unhandled events
template<typename T>
void operator()(const mtx::events::Event<T> &)
{}
+
// Operator for m.room.message events that contain a msgtype in their content
template<typename T,
std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0>
void operator()(const mtx::events::RoomEvent<T> &msg)
-
{
- if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
- auto encInfo = mtx::accessors::file(msg);
- if (encInfo)
- emit model_->newEncryptedImage(encInfo.value());
-
- model_->sendEncryptedMessage(txn_id_qstr_.toStdString(),
- nlohmann::json(msg.content));
- } else {
- QString txn_id_qstr = txn_id_qstr_;
- TimelineModel *model = model_;
- http::client()->send_room_message<T, mtx::events::EventType::RoomMessage>(
- model->room_id_.toStdString(),
- txn_id_qstr.toStdString(),
- msg.content,
- [txn_id_qstr, model](const mtx::responses::EventId &res,
- mtx::http::RequestErr err) {
- if (err) {
- const int status_code =
- static_cast<int>(err->status_code);
- nhlog::net()->warn("[{}] failed to send message: {} {}",
- txn_id_qstr.toStdString(),
- err->matrix_error.error,
- status_code);
- emit model->messageFailed(txn_id_qstr);
- }
- emit model->messageSent(
- txn_id_qstr, QString::fromStdString(res.event_id.to_string()));
- });
- }
+ sendRoomEvent<T, mtx::events::EventType::RoomMessage>(msg);
}
// Special operator for reactions, which are a type of m.room.message, but need to be
@@ -1422,28 +1509,33 @@ struct SendMessageVisitor
// cannot handle it correctly. See the MSC for more details:
// https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption
void operator()(const mtx::events::RoomEvent<mtx::events::msg::Reaction> &msg)
+ {
+ sendUnencryptedRoomEvent<mtx::events::msg::Reaction,
+ mtx::events::EventType::Reaction>(msg);
+ }
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &event)
{
- QString txn_id_qstr = txn_id_qstr_;
- TimelineModel *model = model_;
- http::client()
- ->send_room_message<mtx::events::msg::Reaction, mtx::events::EventType::Reaction>(
- model->room_id_.toStdString(),
- txn_id_qstr.toStdString(),
- msg.content,
- [txn_id_qstr, model](const mtx::responses::EventId &res,
- mtx::http::RequestErr err) {
- if (err) {
- const int status_code = static_cast<int>(err->status_code);
- nhlog::net()->warn("[{}] failed to send message: {} {}",
- txn_id_qstr.toStdString(),
- err->matrix_error.error,
- status_code);
- emit model->messageFailed(txn_id_qstr);
- }
- emit model->messageSent(
- txn_id_qstr, QString::fromStdString(res.event_id.to_string()));
- });
+ sendRoomEvent<mtx::events::msg::CallInvite, mtx::events::EventType::CallInvite>(
+ event);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &event)
+ {
+ sendRoomEvent<mtx::events::msg::CallCandidates,
+ mtx::events::EventType::CallCandidates>(event);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &event)
+ {
+ sendRoomEvent<mtx::events::msg::CallAnswer, mtx::events::EventType::CallAnswer>(
+ event);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &event)
+ {
+ sendRoomEvent<mtx::events::msg::CallHangUp, mtx::events::EventType::CallHangUp>(
+ event);
}
QString txn_id_qstr_;
@@ -1467,14 +1559,13 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
{
std::visit(
[](auto &msg) {
- msg.type = mtx::events::EventType::RoomMessage;
msg.event_id = http::client()->generate_txn_id();
msg.sender = http::client()->user_id().to_string();
msg.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
},
event);
- internalAddEvents({event});
+ internalAddEvents({event}, false);
QString txn_id_qstr = QString::fromStdString(mtx::accessors::event_id(event));
pending.push_back(txn_id_qstr);
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index a3b92f83..ed7036c7 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -36,6 +36,12 @@ enum EventType
Aliases,
/// m.room.avatar
Avatar,
+ /// m.call.invite
+ CallInvite,
+ /// m.call.answer
+ CallAnswer,
+ /// m.call.hangup
+ CallHangUp,
/// m.room.canonical_alias
CanonicalAlias,
/// m.room.create
@@ -200,7 +206,7 @@ public:
void updateLastMessage();
void addEvents(const mtx::responses::Timeline &events);
template<class T>
- void sendMessage(const T &msg);
+ void sendMessageEvent(const T &content, mtx::events::EventType eventType);
RelatedInfo relatedInfo(QString id);
public slots:
@@ -255,13 +261,17 @@ signals:
void typingUsersChanged(std::vector<QString> users);
void replyChanged(QString reply);
void paginationInProgressChanged(const bool);
+ void newCallEvent(const mtx::events::collections::TimelineEvents &event);
private:
DecryptionResult decryptEvent(
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e) const;
std::vector<QString> internalAddEvents(
- const std::vector<mtx::events::collections::TimelineEvents> &timeline);
- void sendEncryptedMessage(const std::string &txn_id, nlohmann::json content);
+ const std::vector<mtx::events::collections::TimelineEvents> &timeline,
+ bool emitCallEvents);
+ void sendEncryptedMessageEvent(const std::string &txn_id,
+ nlohmann::json content,
+ mtx::events::EventType);
void handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
const std::map<std::string, std::string> &room_key,
const std::map<std::string, DevicePublicKeys> &pks,
@@ -296,9 +306,10 @@ private:
template<class T>
void
-TimelineModel::sendMessage(const T &msg)
+TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType)
{
mtx::events::RoomEvent<T> msgCopy = {};
- msgCopy.content = msg;
+ msgCopy.content = content;
+ msgCopy.type = eventType;
emit newMessageToSend(msgCopy);
}
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index a36cd0bf..b652b78e 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -3,8 +3,10 @@
#include <QMetaType>
#include <QPalette>
#include <QQmlContext>
+#include <QString>
#include "BlurhashProvider.h"
+#include "CallManager.h"
#include "ChatPage.h"
#include "ColorImageProvider.h"
#include "DelegateChooser.h"
@@ -71,10 +73,13 @@ TimelineViewManager::userStatus(QString id) const
return QString::fromStdString(cache::statusMessage(id.toStdString()));
}
-TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent)
+TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettings,
+ CallManager *callManager,
+ QWidget *parent)
: imgProvider(new MxcImageProvider())
, colorImgProvider(new ColorImageProvider())
, blurhashProvider(new BlurhashProvider())
+ , callManager_(callManager)
, settings(userSettings)
{
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
@@ -139,7 +144,17 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
// addRoom will only add the room, if it doesn't exist
addRoom(QString::fromStdString(room_id));
const auto &room_model = models.value(QString::fromStdString(room_id));
+ if (!isInitialSync_)
+ connect(room_model.data(),
+ &TimelineModel::newCallEvent,
+ callManager_,
+ &CallManager::syncEvent);
room_model->addEvents(room.timeline);
+ if (!isInitialSync_)
+ disconnect(room_model.data(),
+ &TimelineModel::newCallEvent,
+ callManager_,
+ &CallManager::syncEvent);
if (ChatPage::instance()->userSettings()->typingNotifications()) {
std::vector<QString> typing;
@@ -285,7 +300,7 @@ TimelineViewManager::queueTextMessage(const QString &msg)
timeline_->resetReply();
}
- timeline_->sendMessage(text);
+ timeline_->sendMessageEvent(text, mtx::events::EventType::RoomMessage);
}
void
@@ -307,7 +322,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
}
if (timeline_)
- timeline_->sendMessage(emote);
+ timeline_->sendMessageEvent(emote, mtx::events::EventType::RoomMessage);
}
void
@@ -337,7 +352,7 @@ TimelineViewManager::queueReactionMessage(const QString &roomId,
reaction.relates_to.key = reactionKey.toStdString();
auto model = models.value(roomId);
- model->sendMessage(reaction);
+ model->sendMessageEvent(reaction, mtx::events::EventType::RoomMessage);
}
void
@@ -366,7 +381,7 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
model->resetReply();
}
- model->sendMessage(image);
+ model->sendMessageEvent(image, mtx::events::EventType::RoomMessage);
}
void
@@ -391,7 +406,7 @@ TimelineViewManager::queueFileMessage(
model->resetReply();
}
- model->sendMessage(file);
+ model->sendMessageEvent(file, mtx::events::EventType::RoomMessage);
}
void
@@ -415,7 +430,7 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
model->resetReply();
}
- model->sendMessage(audio);
+ model->sendMessageEvent(audio, mtx::events::EventType::RoomMessage);
}
void
@@ -439,5 +454,34 @@ TimelineViewManager::queueVideoMessage(const QString &roomid,
model->resetReply();
}
- model->sendMessage(video);
+ model->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
+}
+
+void
+TimelineViewManager::queueCallMessage(const QString &roomid,
+ const mtx::events::msg::CallInvite &callInvite)
+{
+ models.value(roomid)->sendMessageEvent(callInvite, mtx::events::EventType::CallInvite);
+}
+
+void
+TimelineViewManager::queueCallMessage(const QString &roomid,
+ const mtx::events::msg::CallCandidates &callCandidates)
+{
+ models.value(roomid)->sendMessageEvent(callCandidates,
+ mtx::events::EventType::CallCandidates);
+}
+
+void
+TimelineViewManager::queueCallMessage(const QString &roomid,
+ const mtx::events::msg::CallAnswer &callAnswer)
+{
+ models.value(roomid)->sendMessageEvent(callAnswer, mtx::events::EventType::CallAnswer);
+}
+
+void
+TimelineViewManager::queueCallMessage(const QString &roomid,
+ const mtx::events::msg::CallHangUp &callHangUp)
+{
+ models.value(roomid)->sendMessageEvent(callHangUp, mtx::events::EventType::CallHangUp);
}
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index ed095058..5224cd56 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -18,6 +18,7 @@
class MxcImageProvider;
class BlurhashProvider;
+class CallManager;
class ColorImageProvider;
class UserSettings;
@@ -31,7 +32,9 @@ class TimelineViewManager : public QObject
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
public:
- TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
+ TimelineViewManager(QSharedPointer<UserSettings> userSettings,
+ CallManager *callManager,
+ QWidget *parent = nullptr);
QWidget *getWidget() const { return container; }
void sync(const mtx::responses::Rooms &rooms);
@@ -96,6 +99,11 @@ public slots:
const QString &url,
const QString &mime,
uint64_t dsize);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
+
void updateEncryptedDescriptions();
private:
@@ -111,7 +119,8 @@ private:
BlurhashProvider *blurhashProvider;
QHash<QString, QSharedPointer<TimelineModel>> models;
- TimelineModel *timeline_ = nullptr;
+ TimelineModel *timeline_ = nullptr;
+ CallManager *callManager_ = nullptr;
bool isInitialSync_ = true;
|