summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2020-09-24 21:36:43 +0200
committerNicolas Werner <nicolas.werner@hotmail.de>2020-09-24 21:36:43 +0200
commit4802c340091a5ce18cf444792eed45f461cd6f6b (patch)
tree6c3ff6843df6bc719e97d246a7a1cc70494b7dd5 /src
parentSimplify outbound session setup (diff)
parentTranslated using Weblate (French) (diff)
downloadnheko-4802c340091a5ce18cf444792eed45f461cd6f6b.tar.xz
Merge remote-tracking branch 'origin/master' into cross-signing
Diffstat (limited to 'src')
-rw-r--r--src/AvatarProvider.cpp11
-rw-r--r--src/Cache.cpp121
-rw-r--r--src/Cache_p.h14
-rw-r--r--src/ChatPage.cpp132
-rw-r--r--src/ChatPage.h6
-rw-r--r--src/MainWindow.cpp16
-rw-r--r--src/MainWindow.h6
-rw-r--r--src/MxcImageProvider.cpp19
-rw-r--r--src/TextInputWidget.cpp3
-rw-r--r--src/TopRoomBar.cpp229
-rw-r--r--src/TopRoomBar.h90
-rw-r--r--src/UserSettingsPage.cpp5
-rw-r--r--src/UserSettingsPage.h1
-rw-r--r--src/WebRTCSession.cpp164
-rw-r--r--src/WebRTCSession.h12
-rw-r--r--src/timeline/EventStore.cpp2
-rw-r--r--src/timeline/TimelineModel.cpp82
-rw-r--r--src/timeline/TimelineModel.h14
-rw-r--r--src/timeline/TimelineViewManager.cpp41
-rw-r--r--src/timeline/TimelineViewManager.h33
20 files changed, 459 insertions, 542 deletions
diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp

index 603bb71a..b1751c33 100644 --- a/src/AvatarProvider.cpp +++ b/src/AvatarProvider.cpp
@@ -34,10 +34,12 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca { const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size); - if (avatarUrl.isEmpty()) + QPixmap pixmap; + if (avatarUrl.isEmpty()) { + callback(pixmap); return; + } - QPixmap pixmap; if (avatar_cache.find(cacheKey, &pixmap)) { callback(pixmap); return; @@ -75,11 +77,10 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca opts.mxc_url, mtx::errors::to_string(err->matrix_error.errcode), err->matrix_error.error); - return; + } else { + cache::saveImage(cacheKey.toStdString(), res); } - cache::saveImage(cacheKey.toStdString(), res); - emit proxy->avatarDownloaded(QByteArray(res.data(), res.size())); }); } diff --git a/src/Cache.cpp b/src/Cache.cpp
index 07d01819..667506c5 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp
@@ -96,8 +96,10 @@ namespace { std::unique_ptr<Cache> instance_ = nullptr; } -static bool -isHiddenEvent(mtx::events::collections::TimelineEvents e, const std::string &room_id) +bool +Cache::isHiddenEvent(lmdb::txn &txn, + mtx::events::collections::TimelineEvents e, + const std::string &room_id) { using namespace mtx::events; if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) { @@ -111,13 +113,27 @@ isHiddenEvent(mtx::events::collections::TimelineEvents e, const std::string &roo e = result.event.value(); } - static constexpr std::initializer_list<EventType> hiddenEvents = { + mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEvents; + hiddenEvents.hidden_event_types = { EventType::Reaction, EventType::CallCandidates, EventType::Unsupported}; + if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, "")) + hiddenEvents = std::move( + std::get< + mtx::events::Event<mtx::events::account_data::nheko_extensions::HiddenEvents>>( + *temp) + .content); + if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id)) + hiddenEvents = std::move( + std::get< + mtx::events::Event<mtx::events::account_data::nheko_extensions::HiddenEvents>>( + *temp) + .content); + return std::visit( - [](const auto &ev) { - return std::any_of(hiddenEvents.begin(), - hiddenEvents.end(), + [hiddenEvents](const auto &ev) { + return std::any_of(hiddenEvents.hidden_event_types.begin(), + hiddenEvents.hidden_event_types.end(), [ev](EventType type) { return type == ev.type; }); }, e); @@ -646,6 +662,7 @@ Cache::removeRoom(lmdb::txn &txn, const std::string &roomid) { lmdb::dbi_del(txn, roomsDb_, lmdb::val(roomid), nullptr); lmdb::dbi_drop(txn, getStatesDb(txn, roomid), true); + lmdb::dbi_drop(txn, getAccountDataDb(txn, roomid), true); lmdb::dbi_drop(txn, getMembersDb(txn, roomid), true); } @@ -1004,6 +1021,19 @@ Cache::saveState(const mtx::responses::Sync &res) setNextBatchToken(txn, res.next_batch); + if (!res.account_data.events.empty()) { + auto accountDataDb = getAccountDataDb(txn, ""); + for (const auto &ev : res.account_data.events) + std::visit( + [&txn, &accountDataDb](const auto &event) { + lmdb::dbi_put(txn, + accountDataDb, + lmdb::val(to_string(event.type)), + lmdb::val(json(event).dump())); + }, + ev); + } + // Save joined rooms for (const auto &room : res.rooms.join) { auto statesdb = getStatesDb(txn, room.first); @@ -1023,30 +1053,43 @@ Cache::saveState(const mtx::responses::Sync &res) updatedInfo.version = getRoomVersion(txn, statesdb).toStdString(); // Process the account_data associated with this room - bool has_new_tags = false; - for (const auto &evt : room.second.account_data.events) { - // for now only fetch tag events - if (std::holds_alternative<Event<account_data::Tags>>(evt)) { - auto tags_evt = std::get<Event<account_data::Tags>>(evt); - has_new_tags = true; - for (const auto &tag : tags_evt.content.tags) { - updatedInfo.tags.push_back(tag.first); + if (!room.second.account_data.events.empty()) { + auto accountDataDb = getAccountDataDb(txn, room.first); + + bool has_new_tags = false; + for (const auto &evt : room.second.account_data.events) { + std::visit( + [&txn, &accountDataDb](const auto &event) { + lmdb::dbi_put(txn, + accountDataDb, + lmdb::val(to_string(event.type)), + lmdb::val(json(event).dump())); + }, + evt); + + // for tag events + if (std::holds_alternative<Event<account_data::Tags>>(evt)) { + auto tags_evt = std::get<Event<account_data::Tags>>(evt); + has_new_tags = true; + for (const auto &tag : tags_evt.content.tags) { + updatedInfo.tags.push_back(tag.first); + } } } - } - if (!has_new_tags) { - // retrieve the old tags, they haven't changed - lmdb::val data; - if (lmdb::dbi_get(txn, roomsDb_, lmdb::val(room.first), data)) { - try { - RoomInfo tmp = - json::parse(std::string_view(data.data(), data.size())); - updatedInfo.tags = tmp.tags; - } catch (const json::exception &e) { - nhlog::db()->warn( - "failed to parse room info: room_id ({}), {}", - room.first, - std::string(data.data(), data.size())); + if (!has_new_tags) { + // retrieve the old tags, they haven't changed + lmdb::val data; + if (lmdb::dbi_get(txn, roomsDb_, lmdb::val(room.first), data)) { + try { + RoomInfo tmp = json::parse( + std::string_view(data.data(), data.size())); + updatedInfo.tags = tmp.tags; + } catch (const json::exception &e) { + nhlog::db()->warn( + "failed to parse room info: room_id ({}), {}", + room.first, + std::string(data.data(), data.size())); + } } } } @@ -2463,7 +2506,7 @@ Cache::saveTimelineMessages(lmdb::txn &txn, lmdb::dbi_put(txn, evToOrderDb, event_id, lmdb::val(&index, sizeof(index))); // TODO(Nico): Allow blacklisting more event types in UI - if (!isHiddenEvent(e, room_id)) { + if (!isHiddenEvent(txn, e, room_id)) { ++msgIndex; lmdb::cursor_put(msgCursor.handle(), lmdb::val(&msgIndex, sizeof(msgIndex)), @@ -2546,7 +2589,7 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message lmdb::dbi_put(txn, evToOrderDb, event_id, lmdb::val(&index, sizeof(index))); // TODO(Nico): Allow blacklisting more event types in UI - if (!isHiddenEvent(e, room_id)) { + if (!isHiddenEvent(txn, e, room_id)) { --msgIndex; lmdb::dbi_put( txn, order2msgDb, lmdb::val(&msgIndex, sizeof(msgIndex)), event_id); @@ -2864,6 +2907,24 @@ Cache::deleteOldData() noexcept } } +std::optional<mtx::events::collections::RoomAccountDataEvents> +Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id) +{ + try { + auto db = getAccountDataDb(txn, room_id); + + lmdb::val data; + if (lmdb::dbi_get(txn, db, lmdb::val(to_string(type)), data)) { + mtx::responses::utils::RoomAccountDataEvents events; + mtx::responses::utils::parse_room_account_data_events( + std::string_view(data.data(), data.size()), events); + return events.front(); + } + } catch (...) { + } + return std::nullopt; +} + bool Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes, const std::string &room_id, diff --git a/src/Cache_p.h b/src/Cache_p.h
index 7d7b70e6..ce6414ab 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h
@@ -301,6 +301,14 @@ private: const std::string &room_id, const mtx::responses::Timeline &res); + //! retrieve a specific event from account data + //! pass empty room_id for global account data + std::optional<mtx::events::collections::RoomAccountDataEvents> + getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id); + bool isHiddenEvent(lmdb::txn &txn, + mtx::events::collections::TimelineEvents e, + const std::string &room_id); + //! Remove a room from the cache. // void removeLeftRoom(lmdb::txn &txn, const std::string &room_id); template<class T> @@ -510,6 +518,12 @@ private: return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE); } + lmdb::dbi getAccountDataDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open( + txn, std::string(room_id + "/account_data").c_str(), MDB_CREATE); + } + lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id) { return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE); diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 704543b5..c6978a59 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp
@@ -38,7 +38,6 @@ #include "SideBarActions.h" #include "Splitter.h" #include "TextInputWidget.h" -#include "TopRoomBar.h" #include "UserInfoWidget.h" #include "UserSettingsPage.h" #include "Utils.h" @@ -127,10 +126,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) contentLayout_->setSpacing(0); contentLayout_->setMargin(0); - top_bar_ = new TopRoomBar(this); view_manager_ = new TimelineViewManager(userSettings_, &callManager_, this); - contentLayout_->addWidget(top_bar_); contentLayout_->addWidget(view_manager_->getWidget()); activeCallBar_ = new ActiveCallBar(this); @@ -182,30 +179,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) room_list_->previousRoom(); }); - connect(top_bar_, &TopRoomBar::mentionsClicked, this, [this](const QPoint &mentionsPos) { - if (user_mentions_popup_->isVisible()) { - user_mentions_popup_->hide(); - } else { - showNotificationsDialog(mentionsPos); - http::client()->notifications( - 1000, - "", - "highlight", - [this, mentionsPos](const mtx::responses::Notifications &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to retrieve notifications: {} ({})", - err->matrix_error.error, - static_cast<int>(err->status_code)); - return; - } - - emit highlightedNotifsRetrieved(std::move(res), mentionsPos); - }); - } - }); - connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL); connect(&connectivityTimer_, &QTimer::timeout, this, [=]() { if (http::client()->access_token().empty()) { @@ -227,8 +200,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) connect(this, &ChatPage::loggedOut, this, &ChatPage::logout); - connect(top_bar_, &TopRoomBar::showRoomList, splitter, &Splitter::showFullRoomList); - connect(top_bar_, &TopRoomBar::inviteUsers, this, [this](QStringList users) { + connect( + view_manager_, &TimelineViewManager::showRoomList, splitter, &Splitter::showFullRoomList); + connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList users) { const auto room_id = current_room_.toStdString(); for (int ii = 0; ii < users.size(); ++ii) { @@ -252,8 +226,10 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) } }); + connect(room_list_, &RoomList::roomChanged, this, [this](QString room_id) { + this->current_room_ = room_id; + }); connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::stopTyping); - connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo); connect(room_list_, &RoomList::roomChanged, splitter, &Splitter::showChatView); connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit); connect( @@ -488,8 +464,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) } }); - connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar); - connect( this, &ChatPage::updateGroupsInfo, communitiesList_, &CommunitiesList::setCommunities); @@ -589,11 +563,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) }); connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync); connect(this, &ChatPage::syncTags, communitiesList_, &CommunitiesList::syncTags); - connect( - this, &ChatPage::syncTopBar, this, [this](const std::map<QString, RoomInfo> &updates) { - if (updates.find(currentRoom()) != updates.end()) - changeTopRoomInfo(currentRoom()); - }); // Callbacks to update the user info (top left corner of the page). connect(this, &ChatPage::setUserAvatar, user_info_widget_, &UserInfoWidget::setAvatar); @@ -658,7 +627,6 @@ void ChatPage::resetUI() { room_list_->clear(); - top_bar_->reset(); user_info_widget_->reset(); view_manager_->clearAll(); @@ -788,46 +756,6 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) } void -ChatPage::updateTopBarAvatar(const QString &roomid, const QString &img) -{ - if (current_room_ != roomid) - return; - - top_bar_->updateRoomAvatar(img); -} - -void -ChatPage::changeTopRoomInfo(const QString &room_id) -{ - if (room_id.isEmpty()) { - nhlog::ui()->warn("cannot switch to empty room_id"); - return; - } - - try { - auto room_info = cache::getRoomInfo({room_id.toStdString()}); - - if (room_info.find(room_id) == room_info.end()) - return; - - const auto name = QString::fromStdString(room_info[room_id].name); - const auto avatar_url = QString::fromStdString(room_info[room_id].avatar_url); - - top_bar_->updateRoomName(name); - top_bar_->updateRoomTopic(QString::fromStdString(room_info[room_id].topic)); - - top_bar_->updateRoomAvatarFromName(name); - if (!avatar_url.isEmpty()) - top_bar_->updateRoomAvatar(avatar_url); - - } catch (const lmdb::error &e) { - nhlog::ui()->error("failed to change top bar room info: {}", e.what()); - } - - current_room_ = room_id; -} - -void ChatPage::showUnreadMessageNotification(int count) { emit unreadMessages(count); @@ -968,14 +896,22 @@ ChatPage::sendNotifications(const mtx::responses::Notifications &res) } if (userSettings_->hasDesktopNotifications()) { - notificationsManager.postNotification( - room_id, - QString::fromStdString(event_id), - QString::fromStdString( - cache::singleRoomInfo(item.room_id).name), - cache::displayName(room_id, user_id), - utils::event_body(item.event), - cache::getRoomAvatar(room_id)); + auto info = cache::singleRoomInfo(item.room_id); + + AvatarProvider::resolve( + QString::fromStdString(info.avatar_url), + 96, + this, + [this, room_id, event_id, item, user_id, info]( + QPixmap image) { + notificationsManager.postNotification( + room_id, + QString::fromStdString(event_id), + QString::fromStdString(info.name), + cache::displayName(room_id, user_id), + utils::event_body(item.event), + image.toImage()); + }); } } } catch (const lmdb::error &e) { @@ -1071,7 +1007,6 @@ ChatPage::handleSyncResponse(mtx::responses::Sync res) auto updates = cache::roomUpdates(res); - emit syncTopBar(updates); emit syncRoomlist(updates); emit syncUI(res.rooms); @@ -1482,9 +1417,12 @@ ChatPage::getProfileInfo() void ChatPage::hideSideBars() { - communitiesList_->hide(); - sideBar_->hide(); - top_bar_->enableBackButton(); + // Don't hide side bar, if we are currently only showing the side bar! + if (view_manager_->getWidget()->isVisible()) { + communitiesList_->hide(); + sideBar_->hide(); + } + view_manager_->enableBackButton(); } void @@ -1494,23 +1432,19 @@ ChatPage::showSideBars() communitiesList_->show(); sideBar_->show(); - top_bar_->disableBackButton(); + view_manager_->disableBackButton(); + content_->show(); } uint64_t ChatPage::timelineWidth() { - int sidebarWidth = sideBar_->size().width(); - sidebarWidth += communitiesList_->size().width(); + int sidebarWidth = sideBar_->minimumSize().width(); + sidebarWidth += communitiesList_->minimumSize().width(); + nhlog::ui()->info("timelineWidth: {}", size().width() - sidebarWidth); return size().width() - sidebarWidth; } -bool -ChatPage::isSideBarExpanded() -{ - const auto sz = splitter::calculateSidebarSizes(QFont{}); - return sideBar_->size().width() > sz.normal; -} void ChatPage::initiateLogout() diff --git a/src/ChatPage.h b/src/ChatPage.h
index de4cb4ca..9d8abb24 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h
@@ -50,7 +50,6 @@ class SideBarActions; class Splitter; class TextInputWidget; class TimelineViewManager; -class TopRoomBar; class UserInfoWidget; class UserSettings; class NotificationsManager; @@ -85,7 +84,6 @@ public: //! Calculate the width of the message timeline. uint64_t timelineWidth(); - bool isSideBarExpanded(); //! Hide the room & group list (if it was visible). void hideSideBars(); //! Show the room/group list (if it was visible). @@ -156,7 +154,6 @@ signals: void syncUI(const mtx::responses::Rooms &rooms); void syncRoomlist(const std::map<QString, RoomInfo> &updates); void syncTags(const std::map<QString, RoomInfo> &updates); - void syncTopBar(const std::map<QString, RoomInfo> &updates); void dropToLoginPageCb(const QString &msg); void notifyMessage(const QString &roomid, @@ -191,8 +188,6 @@ signals: private slots: void showUnreadMessageNotification(int count); - void updateTopBarAvatar(const QString &roomid, const QString &img); - void changeTopRoomInfo(const QString &room_id); void logout(); void removeRoom(const QString &room_id); void dropToLoginPage(const QString &msg); @@ -263,7 +258,6 @@ private: TimelineViewManager *view_manager_; SideBarActions *sidebarActions_; - TopRoomBar *top_bar_; TextInputWidget *text_input_; ActiveCallBar *activeCallBar_; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 59557bff..63722372 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp
@@ -200,7 +200,8 @@ MainWindow::adjustSideBars() const uint64_t timelineWidth = chat_page_->timelineWidth(); const uint64_t minAvailableWidth = sz.collapsePoint + sz.groups; - if (timelineWidth < minAvailableWidth && !chat_page_->isSideBarExpanded()) { + nhlog::ui()->info("timelineWidth: {}, min {}", timelineWidth, minAvailableWidth); + if (timelineWidth < minAvailableWidth) { chat_page_->hideSideBars(); } else { chat_page_->showSideBars(); @@ -330,9 +331,7 @@ MainWindow::hasActiveUser() void MainWindow::openRoomSettings(const QString &room_id) { - const auto roomToSearch = room_id.isEmpty() ? chat_page_->currentRoom() : ""; - - auto dialog = new dialogs::RoomSettings(roomToSearch, this); + auto dialog = new dialogs::RoomSettings(room_id, this); showDialog(dialog); } @@ -340,8 +339,7 @@ MainWindow::openRoomSettings(const QString &room_id) void MainWindow::openMemberListDialog(const QString &room_id) { - const auto roomToSearch = room_id.isEmpty() ? chat_page_->currentRoom() : ""; - auto dialog = new dialogs::MemberList(roomToSearch, this); + auto dialog = new dialogs::MemberList(room_id, this); showDialog(dialog); } @@ -349,11 +347,9 @@ MainWindow::openMemberListDialog(const QString &room_id) void MainWindow::openLeaveRoomDialog(const QString &room_id) { - auto roomToLeave = room_id.isEmpty() ? chat_page_->currentRoom() : room_id; - auto dialog = new dialogs::LeaveRoom(this); - connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, roomToLeave]() { - chat_page_->leaveRoom(roomToLeave); + connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() { + chat_page_->leaveRoom(room_id); }); showDialog(dialog); diff --git a/src/MainWindow.h b/src/MainWindow.h
index 2fc2d00f..e66f299f 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h
@@ -67,14 +67,14 @@ public: static MainWindow *instance() { return instance_; }; void saveCurrentWindowSize(); - void openLeaveRoomDialog(const QString &room_id = ""); + void openLeaveRoomDialog(const QString &room_id); void openInviteUsersDialog(std::function<void(const QStringList &invitees)> callback); void openCreateRoomDialog( std::function<void(const mtx::requests::CreateRoom &request)> callback); void openJoinRoomDialog(std::function<void(const QString &room_id)> callback); void openLogoutDialog(); - void openRoomSettings(const QString &room_id = ""); - void openMemberListDialog(const QString &room_id = ""); + void openRoomSettings(const QString &room_id); + void openMemberListDialog(const QString &room_id); void openReadReceiptsDialog(const QString &event_id); void hideOverlay(); diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp
index a197e4aa..b59fdff8 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp
@@ -17,13 +17,16 @@ MxcImageResponse::run() auto data = cache::image(fileName); if (!data.isNull()) { m_image = utils::readImage(&data); - m_image = m_image.scaled( - m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - m_image.setText("mxc url", "mxc://" + m_id); if (!m_image.isNull()) { - emit finished(); - return; + m_image = m_image.scaled( + m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + m_image.setText("mxc url", "mxc://" + m_id); + + if (!m_image.isNull()) { + emit finished(); + return; + } } } @@ -34,7 +37,7 @@ MxcImageResponse::run() opts.method = "crop"; http::client()->get_thumbnail( opts, [this, fileName](const std::string &res, mtx::http::RequestErr err) { - if (err) { + if (err || res.empty()) { nhlog::net()->error("Failed to download image {}", m_id.toStdString()); m_error = "Failed download"; @@ -46,6 +49,10 @@ MxcImageResponse::run() auto data = QByteArray(res.data(), res.size()); cache::saveImage(fileName, data); m_image = utils::readImage(&data); + if (!m_image.isNull()) { + m_image = m_image.scaled( + m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } m_image.setText("mxc url", "mxc://" + m_id); emit finished(); diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index 6d57a5f1..4a25c4cf 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp
@@ -246,7 +246,8 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event) } case Qt::Key_Colon: { QTextEdit::keyPressEvent(event); - trigger_pos_ = textCursor().position() - 1; + trigger_pos_ = textCursor().position() - 1; + emoji_completion_model_->setFilterRegExp(""); emoji_popup_open_ = true; break; } diff --git a/src/TopRoomBar.cpp b/src/TopRoomBar.cpp deleted file mode 100644
index a45a751e..00000000 --- a/src/TopRoomBar.cpp +++ /dev/null
@@ -1,229 +0,0 @@ -/* - * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <QAction> -#include <QIcon> -#include <QLabel> -#include <QPaintEvent> -#include <QPainter> -#include <QPen> -#include <QPoint> -#include <QStyle> -#include <QStyleOption> -#include <QVBoxLayout> - -#include "Config.h" -#include "MainWindow.h" -#include "TopRoomBar.h" -#include "Utils.h" -#include "ui/Avatar.h" -#include "ui/FlatButton.h" -#include "ui/Menu.h" -#include "ui/OverlayModal.h" -#include "ui/TextLabel.h" - -TopRoomBar::TopRoomBar(QWidget *parent) - : QWidget(parent) - , buttonSize_{32} -{ - QFont f; - f.setPointSizeF(f.pointSizeF()); - - const int fontHeight = QFontMetrics(f).height(); - const int widgetMargin = fontHeight / 3; - const int contentHeight = fontHeight * 3; - - setFixedHeight(contentHeight + widgetMargin); - - topLayout_ = new QHBoxLayout(this); - topLayout_->setSpacing(widgetMargin); - topLayout_->setContentsMargins( - 2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin); - - avatar_ = new Avatar(this, fontHeight * 2); - avatar_->setLetter(""); - - textLayout_ = new QVBoxLayout(); - textLayout_->setSpacing(0); - textLayout_->setMargin(0); - - QFont roomFont; - roomFont.setPointSizeF(roomFont.pointSizeF() * 1.1); - roomFont.setWeight(QFont::Medium); - - nameLabel_ = new QLabel(this); - nameLabel_->setFont(roomFont); - nameLabel_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - - QFont descriptionFont; - - topicLabel_ = new TextLabel(this); - topicLabel_->setLineWrapMode(QTextEdit::NoWrap); - topicLabel_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - topicLabel_->setFont(descriptionFont); - topicLabel_->setTextInteractionFlags(Qt::TextBrowserInteraction); - topicLabel_->setOpenExternalLinks(true); - topicLabel_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - - textLayout_->addWidget(nameLabel_); - textLayout_->addWidget(topicLabel_); - - settingsBtn_ = new FlatButton(this); - settingsBtn_->setToolTip(tr("Room options")); - settingsBtn_->setFixedSize(buttonSize_, buttonSize_); - settingsBtn_->setCornerRadius(buttonSize_ / 2); - - mentionsBtn_ = new FlatButton(this); - mentionsBtn_->setToolTip(tr("Mentions")); - mentionsBtn_->setFixedSize(buttonSize_, buttonSize_); - mentionsBtn_->setCornerRadius(buttonSize_ / 2); - - QIcon settings_icon; - settings_icon.addFile(":/icons/icons/ui/vertical-ellipsis.png"); - settingsBtn_->setIcon(settings_icon); - settingsBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2)); - - QIcon mentions_icon; - mentions_icon.addFile(":/icons/icons/ui/at-solid.svg"); - mentionsBtn_->setIcon(mentions_icon); - mentionsBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2)); - - backBtn_ = new FlatButton(this); - backBtn_->setFixedSize(buttonSize_, buttonSize_); - backBtn_->setCornerRadius(buttonSize_ / 2); - - QIcon backIcon; - backIcon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); - backBtn_->setIcon(backIcon); - backBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2)); - backBtn_->hide(); - - connect(backBtn_, &QPushButton::clicked, this, &TopRoomBar::showRoomList); - - topLayout_->addWidget(avatar_); - topLayout_->addWidget(backBtn_); - topLayout_->addLayout(textLayout_, 1); - topLayout_->addWidget(mentionsBtn_, 0, Qt::AlignRight); - topLayout_->addWidget(settingsBtn_, 0, Qt::AlignRight); - - menu_ = new Menu(this); - - inviteUsers_ = new QAction(tr("Invite users"), this); - connect(inviteUsers_, &QAction::triggered, this, [this]() { - MainWindow::instance()->openInviteUsersDialog( - [this](const QStringList &invitees) { emit inviteUsers(invitees); }); - }); - - roomMembers_ = new QAction(tr("Members"), this); - connect(roomMembers_, &QAction::triggered, this, []() { - MainWindow::instance()->openMemberListDialog(); - }); - - leaveRoom_ = new QAction(tr("Leave room"), this); - connect(leaveRoom_, &QAction::triggered, this, []() { - MainWindow::instance()->openLeaveRoomDialog(); - }); - - roomSettings_ = new QAction(tr("Settings"), this); - connect(roomSettings_, &QAction::triggered, this, []() { - MainWindow::instance()->openRoomSettings(); - }); - - menu_->addAction(inviteUsers_); - menu_->addAction(roomMembers_); - menu_->addAction(leaveRoom_); - menu_->addAction(roomSettings_); - - connect(settingsBtn_, &QPushButton::clicked, this, [this]() { - auto pos = mapToGlobal(settingsBtn_->pos()); - menu_->popup( - QPoint(pos.x() + buttonSize_ - menu_->sizeHint().width(), pos.y() + buttonSize_)); - }); - - connect(mentionsBtn_, &QPushButton::clicked, this, [this]() { - auto pos = mapToGlobal(mentionsBtn_->pos()); - emit mentionsClicked(pos); - }); -} - -void -TopRoomBar::enableBackButton() -{ - avatar_->hide(); - backBtn_->show(); -} - -void -TopRoomBar::disableBackButton() -{ - avatar_->show(); - backBtn_->hide(); -} - -void -TopRoomBar::updateRoomAvatarFromName(const QString &name) -{ - avatar_->setLetter(utils::firstChar(name)); - update(); -} - -void -TopRoomBar::reset() -{ - nameLabel_->setText(""); - topicLabel_->setText(""); - avatar_->setLetter(""); -} - -void -TopRoomBar::updateRoomAvatar(const QString &avatar_image) -{ - avatar_->setImage(avatar_image); - update(); -} - -void -TopRoomBar::updateRoomName(const QString &name) -{ - nameLabel_->setText(name); - update(); -} - -void -TopRoomBar::updateRoomTopic(QString topic) -{ - topic.replace(conf::strings::url_regex, conf::strings::url_html); - topicLabel_->clearLinks(); - topicLabel_->setHtml(topic); - update(); -} - -void -TopRoomBar::mousePressEvent(QMouseEvent *) -{ - if (roomSettings_ != nullptr) - roomSettings_->trigger(); -} - -void -TopRoomBar::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} diff --git a/src/TopRoomBar.h b/src/TopRoomBar.h deleted file mode 100644
index 0c33c1e0..00000000 --- a/src/TopRoomBar.h +++ /dev/null
@@ -1,90 +0,0 @@ -/* - * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#pragma once - -#include <QColor> -#include <QStringList> -#include <QWidget> - -class Avatar; -class FlatButton; -class Menu; -class TextLabel; -class OverlayModal; - -class QLabel; -class QHBoxLayout; -class QVBoxLayout; - -class TopRoomBar : public QWidget -{ - Q_OBJECT - - Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor) - -public: - TopRoomBar(QWidget *parent = nullptr); - - void updateRoomAvatar(const QString &avatar_image); - void updateRoomName(const QString &name); - void updateRoomTopic(QString topic); - void updateRoomAvatarFromName(const QString &name); - - void reset(); - - QColor borderColor() const { return borderColor_; } - void setBorderColor(QColor &color) { borderColor_ = color; } - -public slots: - //! Add a "back-arrow" button that can switch to roomlist only view. - void enableBackButton(); - //! Replace the "back-arrow" button with the avatar of the room. - void disableBackButton(); - -signals: - void inviteUsers(QStringList users); - void showRoomList(); - void mentionsClicked(const QPoint &pos); - -protected: - void mousePressEvent(QMouseEvent *) override; - void paintEvent(QPaintEvent *) override; - -private: - QHBoxLayout *topLayout_ = nullptr; - QVBoxLayout *textLayout_ = nullptr; - - QLabel *nameLabel_ = nullptr; - TextLabel *topicLabel_ = nullptr; - - Menu *menu_; - QAction *leaveRoom_ = nullptr; - QAction *roomMembers_ = nullptr; - QAction *roomSettings_ = nullptr; - QAction *inviteUsers_ = nullptr; - - FlatButton *settingsBtn_; - FlatButton *mentionsBtn_; - FlatButton *backBtn_; - - Avatar *avatar_; - - int buttonSize_; - - QColor borderColor_; -}; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index ab5658a4..f1542ec5 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp
@@ -513,9 +513,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge callsLabel->setFont(font); useStunServer_ = new Toggle{this}; - defaultAudioSourceValue_ = new QLabel(this); - defaultAudioSourceValue_->setFont(font); - auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this}; encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin); encryptionLabel_->setAlignment(Qt::AlignBottom); @@ -652,7 +649,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge boxWrap(tr("Allow fallback call assist server"), useStunServer_, tr("Will use turn.matrix.org as assist when your home server does not offer one.")); - boxWrap(tr("Default audio source device"), defaultAudioSourceValue_); formLayout_->addRow(encryptionLabel_); formLayout_->addRow(new HorizontalLine{this}); @@ -813,7 +809,6 @@ UserSettingsPage::showEvent(QShowEvent *) deviceIdValue_->setText(QString::fromStdString(http::client()->device_id())); timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth()); useStunServer_->setState(!settings_->useStunServer()); - defaultAudioSourceValue_->setText(settings_->defaultAudioSource()); deviceFingerprintValue_->setText( utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519)); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 52ff9466..e947bfae 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h
@@ -250,7 +250,6 @@ private: Toggle *decryptSidebar_; QLabel *deviceFingerprintValue_; QLabel *deviceIdValue_; - QLabel *defaultAudioSourceValue_; QComboBox *themeCombo_; QComboBox *scaleFactorCombo_; diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp
index 64172e61..30a27b60 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp
@@ -21,6 +21,7 @@ WebRTCSession::WebRTCSession() { qRegisterMetaType<WebRTCSession::State>(); connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState); + init(); } bool @@ -78,7 +79,11 @@ WebRTCSession::init(std::string *errorMessage) gst_object_unref(plugin); } - if (!initialised_) { + if (initialised_) { +#if GST_CHECK_VERSION(1, 18, 0) + startDeviceMonitor(); +#endif + } else { nhlog::ui()->error(strError); if (errorMessage) *errorMessage = strError; @@ -95,12 +100,65 @@ namespace { bool isoffering_; std::string localsdp_; std::vector<mtx::events::msg::CallCandidates::Candidate> localcandidates_; +std::vector<std::pair<std::string, GstDevice *>> audioSources_; + +void +addDevice(GstDevice *device) +{ + if (device) { + gchar *name = gst_device_get_display_name(device); + nhlog::ui()->debug("WebRTC: device added: {}", name); + audioSources_.push_back({name, device}); + g_free(name); + } +} + +#if GST_CHECK_VERSION(1, 18, 0) +void +removeDevice(GstDevice *device, bool changed) +{ + if (device) { + if (auto it = std::find_if(audioSources_.begin(), + audioSources_.end(), + [device](const auto &s) { return s.second == device; }); + it != audioSources_.end()) { + nhlog::ui()->debug(std::string("WebRTC: device ") + + (changed ? "changed: " : "removed: ") + "{}", + it->first); + gst_object_unref(device); + audioSources_.erase(it); + } + } +} +#endif gboolean newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data) { WebRTCSession *session = static_cast<WebRTCSession *>(user_data); switch (GST_MESSAGE_TYPE(msg)) { +#if GST_CHECK_VERSION(1, 18, 0) + case GST_MESSAGE_DEVICE_ADDED: { + GstDevice *device; + gst_message_parse_device_added(msg, &device); + addDevice(device); + break; + } + case GST_MESSAGE_DEVICE_REMOVED: { + GstDevice *device; + gst_message_parse_device_removed(msg, &device); + removeDevice(device, false); + break; + } + case GST_MESSAGE_DEVICE_CHANGED: { + GstDevice *device; + GstDevice *oldDevice; + gst_message_parse_device_changed(msg, &device, &oldDevice); + removeDevice(oldDevice, true); + addDevice(device); + break; + } +#endif case GST_MESSAGE_EOS: nhlog::ui()->error("WebRTC: end of stream"); session->end(); @@ -176,7 +234,7 @@ createAnswer(GstPromise *promise, gpointer webrtc) g_signal_emit_by_name(webrtc, "create-answer", nullptr, promise); } -#if GST_CHECK_VERSION(1, 17, 0) +#if GST_CHECK_VERSION(1, 18, 0) void iceGatheringStateChanged(GstElement *webrtc, GParamSpec *pspec G_GNUC_UNUSED, @@ -223,7 +281,7 @@ addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED, { nhlog::ui()->debug("WebRTC: local candidate: (m-line:{}):{}", mlineIndex, candidate); -#if GST_CHECK_VERSION(1, 17, 0) +#if GST_CHECK_VERSION(1, 18, 0) localcandidates_.push_back({"audio", (uint16_t)mlineIndex, candidate}); return; #else @@ -233,8 +291,10 @@ addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED, return; } + localcandidates_.push_back({"audio", (uint16_t)mlineIndex, candidate}); + // GStreamer v1.16: webrtcbin's notify::ice-gathering-state triggers - // GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE too early. Fixed in v1.17. + // GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE too early. Fixed in v1.18. // Use a 100ms timeout in the meantime static guint timerid = 0; if (timerid) @@ -423,8 +483,12 @@ WebRTCSession::acceptICECandidates( for (const auto &c : candidates) { nhlog::ui()->debug( "WebRTC: remote candidate: (m-line:{}):{}", c.sdpMLineIndex, c.candidate); - g_signal_emit_by_name( - webrtc_, "add-ice-candidate", c.sdpMLineIndex, c.candidate.c_str()); + if (!c.candidate.empty()) { + g_signal_emit_by_name(webrtc_, + "add-ice-candidate", + c.sdpMLineIndex, + c.candidate.c_str()); + } } } } @@ -471,7 +535,7 @@ WebRTCSession::startPipeline(int opusPayloadType) gst_element_set_state(pipe_, GST_STATE_READY); g_signal_connect(webrtc_, "pad-added", G_CALLBACK(addDecodeBin), pipe_); -#if GST_CHECK_VERSION(1, 17, 0) +#if GST_CHECK_VERSION(1, 18, 0) // capture ICE gathering completion g_signal_connect( webrtc_, "notify::ice-gathering-state", G_CALLBACK(iceGatheringStateChanged), nullptr); @@ -488,7 +552,7 @@ WebRTCSession::startPipeline(int opusPayloadType) } GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_)); - gst_bus_add_watch(bus, newBusMessage, this); + busWatchId_ = gst_bus_add_watch(bus, newBusMessage, this); gst_object_unref(bus); emit stateChanged(State::INITIATED); return true; @@ -497,19 +561,18 @@ WebRTCSession::startPipeline(int opusPayloadType) bool WebRTCSession::createPipeline(int opusPayloadType) { - int nSources = audioSources_ ? g_list_length(audioSources_) : 0; - if (nSources == 0) { + if (audioSources_.empty()) { nhlog::ui()->error("WebRTC: no audio sources"); return false; } - if (audioSourceIndex_ < 0 || audioSourceIndex_ >= nSources) { + if (audioSourceIndex_ < 0 || (size_t)audioSourceIndex_ >= audioSources_.size()) { nhlog::ui()->error("WebRTC: invalid audio source index"); return false; } - GstElement *source = gst_device_create_element( - GST_DEVICE_CAST(g_list_nth_data(audioSources_, audioSourceIndex_)), nullptr); + GstElement *source = + gst_device_create_element(audioSources_[audioSourceIndex_].second, nullptr); GstElement *volume = gst_element_factory_make("volume", "srclevel"); GstElement *convert = gst_element_factory_make("audioconvert", nullptr); GstElement *resample = gst_element_factory_make("audioresample", nullptr); @@ -594,12 +657,40 @@ WebRTCSession::end() gst_element_set_state(pipe_, GST_STATE_NULL); gst_object_unref(pipe_); pipe_ = nullptr; + g_source_remove(busWatchId_); + busWatchId_ = 0; } webrtc_ = nullptr; if (state_ != State::DISCONNECTED) emit stateChanged(State::DISCONNECTED); } +#if GST_CHECK_VERSION(1, 18, 0) +void +WebRTCSession::startDeviceMonitor() +{ + if (!initialised_) + return; + + static GstDeviceMonitor *monitor = nullptr; + if (!monitor) { + monitor = gst_device_monitor_new(); + GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw"); + gst_device_monitor_add_filter(monitor, "Audio/Source", caps); + gst_caps_unref(caps); + + GstBus *bus = gst_device_monitor_get_bus(monitor); + gst_bus_add_watch(bus, newBusMessage, nullptr); + gst_object_unref(bus); + if (!gst_device_monitor_start(monitor)) { + nhlog::ui()->error("WebRTC: failed to start device monitor"); + return; + } + } +} + +#else + void WebRTCSession::refreshDevices() { @@ -613,31 +704,42 @@ WebRTCSession::refreshDevices() gst_device_monitor_add_filter(monitor, "Audio/Source", caps); gst_caps_unref(caps); } - g_list_free_full(audioSources_, g_object_unref); - audioSources_ = gst_device_monitor_get_devices(monitor); + + std::for_each(audioSources_.begin(), audioSources_.end(), [](const auto &s) { + gst_object_unref(s.second); + }); + audioSources_.clear(); + GList *devices = gst_device_monitor_get_devices(monitor); + if (devices) { + audioSources_.reserve(g_list_length(devices)); + for (GList *l = devices; l != nullptr; l = l->next) + addDevice(GST_DEVICE_CAST(l->data)); + g_list_free(devices); + } } +#endif std::vector<std::string> WebRTCSession::getAudioSourceNames(const std::string &defaultDevice) { - if (!initialised_) - return {}; - +#if !GST_CHECK_VERSION(1, 18, 0) refreshDevices(); +#endif + // move default device to top of the list + if (auto it = std::find_if(audioSources_.begin(), + audioSources_.end(), + [&](const auto &s) { return s.first == defaultDevice; }); + it != audioSources_.end()) + std::swap(audioSources_.front(), *it); + std::vector<std::string> ret; - ret.reserve(g_list_length(audioSources_)); - for (GList *l = audioSources_; l != nullptr; l = l->next) { - gchar *name = gst_device_get_display_name(GST_DEVICE_CAST(l->data)); - ret.emplace_back(name); - g_free(name); - if (ret.back() == defaultDevice) { - // move default device to top of the list - std::swap(audioSources_->data, l->data); - std::swap(ret.front(), ret.back()); - } - } + ret.reserve(audioSources_.size()); + std::for_each(audioSources_.cbegin(), audioSources_.cend(), [&](const auto &s) { + ret.push_back(s.first); + }); return ret; } + #else bool @@ -688,6 +790,10 @@ void WebRTCSession::refreshDevices() {} +void +WebRTCSession::startDeviceMonitor() +{} + std::vector<std::string> WebRTCSession::getAudioSourceNames(const std::string &) { diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h
index 56d76fa8..653ec2cf 100644 --- a/src/WebRTCSession.h +++ b/src/WebRTCSession.h
@@ -7,7 +7,6 @@ #include "mtx/events/voip.hpp" -typedef struct _GList GList; typedef struct _GstElement GstElement; class WebRTCSession : public QObject @@ -64,18 +63,19 @@ private slots: private: WebRTCSession(); - bool initialised_ = false; - State state_ = State::DISCONNECTED; - GstElement *pipe_ = nullptr; - GstElement *webrtc_ = nullptr; + bool initialised_ = false; + State state_ = State::DISCONNECTED; + GstElement *pipe_ = nullptr; + GstElement *webrtc_ = nullptr; + unsigned int busWatchId_ = 0; std::string stunServer_; std::vector<std::string> turnServers_; - GList *audioSources_ = nullptr; int audioSourceIndex_ = -1; bool startPipeline(int opusPayloadType); bool createPipeline(int opusPayloadType); void refreshDevices(); + void startDeviceMonitor(); public: WebRTCSession(WebRTCSession const &) = delete; diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index af1f7b23..298e0d18 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp
@@ -573,7 +573,7 @@ EventStore::decryptEvent(const IdIndex &idx, room_id_, index.sender_key); dummy.content.body = - tr("-- Reply attack! This message index was reused! --").toStdString(); + tr("-- Replay attack! This message index was reused! --").toStdString(); break; case olm::DecryptionErrorCode::UnknownFingerprint: // TODO: don't fail, just show in UI. diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index ddd238b9..af26a543 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp
@@ -566,6 +566,25 @@ TimelineModel::fetchMore(const QModelIndex &) } void +TimelineModel::syncState(const mtx::responses::State &s) +{ + using namespace mtx::events; + + for (const auto &e : s.events) { + if (std::holds_alternative<StateEvent<state::Avatar>>(e)) + emit roomAvatarUrlChanged(); + else if (std::holds_alternative<StateEvent<state::Name>>(e)) + emit roomNameChanged(); + else if (std::holds_alternative<StateEvent<state::Topic>>(e)) + emit roomTopicChanged(); + else if (std::holds_alternative<StateEvent<state::Member>>(e)) { + emit roomAvatarUrlChanged(); + emit roomNameChanged(); + } + } +} + +void TimelineModel::addEvents(const mtx::responses::Timeline &timeline) { if (timeline.events.empty()) @@ -574,6 +593,7 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline) events.handleSync(timeline); using namespace mtx::events; + for (auto e : timeline.events) { if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) { MegolmSessionIndex index; @@ -597,6 +617,16 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline) emit newCallEvent(event); }, e); + else if (std::holds_alternative<StateEvent<state::Avatar>>(e)) + emit roomAvatarUrlChanged(); + else if (std::holds_alternative<StateEvent<state::Name>>(e)) + emit roomNameChanged(); + else if (std::holds_alternative<StateEvent<state::Topic>>(e)) + emit roomTopicChanged(); + else if (std::holds_alternative<StateEvent<state::Member>>(e)) { + emit roomAvatarUrlChanged(); + emit roomNameChanged(); + } } updateLastMessage(); } @@ -737,12 +767,6 @@ TimelineModel::formatDateSeparator(QDate date) const return date.toString(fmt); } -QString -TimelineModel::escapeEmoji(QString str) const -{ - return utils::replaceEmoji(str); -} - void TimelineModel::viewRawMessage(QString id) const { @@ -1440,7 +1464,7 @@ TimelineModel::formatTypingUsers(const std::vector<QString> &users, QColor bg) QStringList uidWithoutLast; auto formatUser = [this, bg](const QString &user_id) -> QString { - auto uncoloredUsername = escapeEmoji(displayName(user_id)); + auto uncoloredUsername = utils::replaceEmoji(displayName(user_id)); QString prefix = QString("<font color=\"%1\">").arg(manager_->userColor(user_id, bg).name()); @@ -1490,7 +1514,7 @@ TimelineModel::formatJoinRuleEvent(QString id) return ""; QString user = QString::fromStdString(event->sender); - QString name = escapeEmoji(displayName(user)); + QString name = utils::replaceEmoji(displayName(user)); switch (event->content.join_rule) { case mtx::events::state::JoinRule::Public: @@ -1515,7 +1539,7 @@ TimelineModel::formatGuestAccessEvent(QString id) return ""; QString user = QString::fromStdString(event->sender); - QString name = escapeEmoji(displayName(user)); + QString name = utils::replaceEmoji(displayName(user)); switch (event->content.guest_access) { case mtx::events::state::AccessState::CanJoin: @@ -1540,7 +1564,7 @@ TimelineModel::formatHistoryVisibilityEvent(QString id) return ""; QString user = QString::fromStdString(event->sender); - QString name = escapeEmoji(displayName(user)); + QString name = utils::replaceEmoji(displayName(user)); switch (event->content.history_visibility) { case mtx::events::state::Visibility::WorldReadable: @@ -1573,7 +1597,7 @@ TimelineModel::formatPowerLevelEvent(QString id) return ""; QString user = QString::fromStdString(event->sender); - QString name = escapeEmoji(displayName(user)); + QString name = utils::replaceEmoji(displayName(user)); // TODO: power levels rendering is actually a bit complex. work on this later. return tr("%1 has changed the room's permissions.").arg(name); @@ -1602,7 +1626,7 @@ TimelineModel::formatMemberEvent(QString id) } QString user = QString::fromStdString(event->state_key); - QString name = escapeEmoji(displayName(user)); + QString name = utils::replaceEmoji(displayName(user)); QString rendered; // see table https://matrix.org/docs/spec/client_server/latest#m-room-member @@ -1675,3 +1699,37 @@ TimelineModel::formatMemberEvent(QString id) return rendered; } + +QString +TimelineModel::roomName() const +{ + auto info = cache::getRoomInfo({room_id_.toStdString()}); + + if (!info.count(room_id_)) + return ""; + else + return QString::fromStdString(info[room_id_].name); +} + +QString +TimelineModel::roomAvatarUrl() const +{ + auto info = cache::getRoomInfo({room_id_.toStdString()}); + + if (!info.count(room_id_)) + return ""; + else + return QString::fromStdString(info[room_id_].avatar_url); +} + +QString +TimelineModel::roomTopic() const +{ + auto info = cache::getRoomInfo({room_id_.toStdString()}); + + if (!info.count(room_id_)) + return ""; + else + return utils::replaceEmoji(utils::linkifyMessage( + utils::escapeBlacklistedHtml(QString::fromStdString(info[room_id_].topic)))); +} diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 61d00df9..3234a20c 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h
@@ -146,6 +146,9 @@ class TimelineModel : public QAbstractListModel Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply) Q_PROPERTY( bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged) + Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) + Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged) + Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged) public: explicit TimelineModel(TimelineViewManager *manager, @@ -203,7 +206,6 @@ public: Q_INVOKABLE QString formatGuestAccessEvent(QString id); Q_INVOKABLE QString formatPowerLevelEvent(QString id); - Q_INVOKABLE QString escapeEmoji(QString str) const; Q_INVOKABLE void viewRawMessage(QString id) const; Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; Q_INVOKABLE void openUserProfile(QString userid); @@ -226,6 +228,7 @@ public: void updateLastMessage(); void addEvents(const mtx::responses::Timeline &events); + void syncState(const mtx::responses::State &state); template<class T> void sendMessageEvent(const T &content, mtx::events::EventType eventType); RelatedInfo relatedInfo(QString id); @@ -262,6 +265,11 @@ public slots: void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; } void clearTimeline() { events.clearTimeline(); } + QString roomName() const; + QString roomTopic() const; + QString roomAvatarUrl() const; + QString roomId() const { return room_id_; } + private slots: void addPendingMessage(mtx::events::collections::TimelineEvents event); @@ -282,6 +290,10 @@ signals: void addPendingMessageToStore(mtx::events::collections::TimelineEvents event); void updateFlowEventId(std::string event_id); + void roomNameChanged(); + void roomTopicChanged(); + void roomAvatarUrlChanged(); + private: template<typename T> void sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 97c119bf..03dd4773 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp
@@ -13,6 +13,7 @@ #include "ColorImageProvider.h" #include "DelegateChooser.h" #include "Logging.h" +#include "MainWindow.h" #include "MatrixClient.h" #include "MxcImageProvider.h" #include "UserSettingsPage.h" @@ -102,7 +103,7 @@ TimelineViewManager::userStatus(QString id) const TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettings, CallManager *callManager, - QWidget *parent) + ChatPage *parent) : imgProvider(new MxcImageProvider()) , colorImgProvider(new ColorImageProvider()) , blurhashProvider(new BlurhashProvider()) @@ -189,11 +190,8 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin view->engine()->addImageProvider("blurhash", blurhashProvider); view->setSource(QUrl("qrc:///qml/TimelineView.qml")); - connect(dynamic_cast<ChatPage *>(parent), - &ChatPage::themeChanged, - this, - &TimelineViewManager::updateColorPalette); - connect(dynamic_cast<ChatPage *>(parent), + connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette); + connect(parent, &ChatPage::decryptSidebarChanged, this, &TimelineViewManager::updateEncryptedDescriptions); @@ -295,7 +293,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin } } }); - connect(dynamic_cast<ChatPage *>(parent), &ChatPage::loggedOut, this, [this]() { + connect(parent, &ChatPage::loggedOut, this, [this]() { isInitialSync_ = true; emit initialSyncChanged(true); }); @@ -313,6 +311,7 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms) &TimelineModel::newCallEvent, callManager_, &CallManager::syncEvent); + room_model->syncState(room.state); room_model->addEvents(room.timeline); if (!isInitialSync_) disconnect(room_model.data(), @@ -363,6 +362,12 @@ TimelineViewManager::setHistoryView(const QString &room_id) } } +QString +TimelineViewManager::escapeEmoji(QString str) const +{ + return utils::replaceEmoji(str); +} + void TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const { @@ -402,6 +407,28 @@ TimelineViewManager::openLink(QString link) const } void +TimelineViewManager::openInviteUsersDialog() +{ + MainWindow::instance()->openInviteUsersDialog( + [this](const QStringList &invitees) { emit inviteUsers(invitees); }); +} +void +TimelineViewManager::openMemberListDialog() const +{ + MainWindow::instance()->openMemberListDialog(timeline_->roomId()); +} +void +TimelineViewManager::openLeaveRoomDialog() const +{ + MainWindow::instance()->openLeaveRoomDialog(timeline_->roomId()); +} +void +TimelineViewManager::openRoomSettings() const +{ + MainWindow::instance()->openRoomSettings(timeline_->roomId()); +} + +void TimelineViewManager::updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids) { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 19406872..4779d3cd 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h
@@ -22,6 +22,7 @@ class BlurhashProvider; class CallManager; class ColorImageProvider; class UserSettings; +class ChatPage; class DeviceVerificationList : public QObject { @@ -45,11 +46,13 @@ class TimelineViewManager : public QObject TimelineModel *timeline MEMBER timeline_ READ activeTimeline NOTIFY activeTimelineChanged) Q_PROPERTY( bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged) + Q_PROPERTY( + bool isNarrowView MEMBER isNarrowView_ READ isNarrowView NOTIFY narrowViewChanged) public: TimelineViewManager(QSharedPointer<UserSettings> userSettings, CallManager *callManager, - QWidget *parent = nullptr); + ChatPage *parent = nullptr); QWidget *getWidget() const { return container; } void sync(const mtx::responses::Rooms &rooms); @@ -59,14 +62,21 @@ public: Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; } Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } + bool isNarrowView() const { return isNarrowView_; } Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const; Q_INVOKABLE QColor userColor(QString id, QColor background); + Q_INVOKABLE QString escapeEmoji(QString str) const; Q_INVOKABLE QString userPresence(QString id) const; Q_INVOKABLE QString userStatus(QString id) const; Q_INVOKABLE void openLink(QString link) const; + Q_INVOKABLE void openInviteUsersDialog(); + Q_INVOKABLE void openMemberListDialog() const; + Q_INVOKABLE void openLeaveRoomDialog() const; + Q_INVOKABLE void openRoomSettings() const; + signals: void clearRoomMessageCount(QString roomid); void updateRoomsLastMessage(QString roomid, const DescInfo &info); @@ -79,6 +89,9 @@ signals: QString userId, QString deviceId, bool isRequest = false); + void inviteUsers(QStringList users); + void showRoomList(); + void narrowViewChanged(); public slots: void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids); @@ -128,6 +141,23 @@ public slots: timeline_->clearTimeline(); } + void enableBackButton() + { + if (isNarrowView_) + return; + isNarrowView_ = true; + emit narrowViewChanged(); + } + void disableBackButton() + { + if (!isNarrowView_) + return; + isNarrowView_ = false; + emit narrowViewChanged(); + } + + void backToRooms() { emit showRoomList(); } + private: #ifdef USE_QUICK_VIEW QQuickView *view; @@ -145,6 +175,7 @@ private: CallManager *callManager_ = nullptr; bool isInitialSync_ = true; + bool isNarrowView_ = false; QSharedPointer<UserSettings> settings; QHash<QString, QColor> userColors;