summary refs log tree commit diff
path: root/src/timeline
diff options
context:
space:
mode:
authortrilene <trilene@runbox.com>2020-07-10 19:19:48 -0400
committertrilene <trilene@runbox.com>2020-07-10 19:19:48 -0400
commit7a206441c86cd2aa84cbbbc6be803f03b2f355ab (patch)
tree1fe734ab983daa8998eb23432bd560d7dabf7866 /src/timeline
parentFix m.relates_to being sent as 'null' when not set in encrypted messages. (diff)
downloadnheko-7a206441c86cd2aa84cbbbc6be803f03b2f355ab.tar.xz
Support voice calls
Diffstat (limited to 'src/timeline')
-rw-r--r--src/timeline/TimelineModel.cpp205
-rw-r--r--src/timeline/TimelineModel.h21
-rw-r--r--src/timeline/TimelineViewManager.cpp60
-rw-r--r--src/timeline/TimelineViewManager.h13
4 files changed, 227 insertions, 72 deletions
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;