From d84f6a7091c7eac12356bcd45e2ed5609b2c701c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 28 Dec 2020 22:58:27 +0100 Subject: [PATCH 01/35] Link keychain in flatpak statically --- io.github.NhekoReborn.Nheko.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 34b0d7e7..352ca648 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -97,7 +97,8 @@ { "config-opts": [ "-DCMAKE_BUILD_TYPE=Release", - "-DBUILD_TEST_APPLICATION=OFF" + "-DBUILD_TEST_APPLICATION=OFF", + "-DQTKEYCHAIN_STATIC=ON" ], "buildsystem": "cmake-ninja", "name": "QtKeychain", From 63f0d6bf271a8112086dedc4f56c69dec4271bff Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 28 Dec 2020 23:34:42 +0100 Subject: [PATCH 02/35] Fix missing include on Windows --- src/UserSettingsPage.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index af73202e..6744d101 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -23,6 +23,8 @@ #include #include +#include + class Toggle; class QLabel; class QFormLayout; From 6533269611fd35aec72b618c6103e3bd51966362 Mon Sep 17 00:00:00 2001 From: trilene Date: Wed, 30 Dec 2020 16:02:11 -0500 Subject: [PATCH 03/35] Add Fedora build requirements to README --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f24b8d13..a4b1354d 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Most of the features you would expect from a chat application are missing right but we are getting close to a more feature complete client. Specifically there is support for: - E2E encryption. -- VoIP calls (voice & video) +- VoIP calls (voice & video). - User registration. - Creating, joining & leaving rooms. - Sending & receiving invites. @@ -210,6 +210,14 @@ sudo apt install cmake gcc make automake liblmdb-dev \ qt5keychain-dev ``` +##### Fedora + +```bash +sudo dnf install qt5-qtbase-devel qt5-linguist qt5-qtsvg-devel qt5-qtmultimedia-devel \ + qt5-qtquickcontrols2-devel qtkeychain-qt5-devel spdlog-devel openssl-devel \ + libolm-devel cmark-devel lmdb-devel lmdbxx-devel tweeny-devel +``` + ##### Guix ```bash From 4bc25ffb15d1e559417550a64ca8995ce894f8a8 Mon Sep 17 00:00:00 2001 From: d42 Date: Thu, 31 Dec 2020 16:09:42 +0100 Subject: [PATCH 04/35] find sso flow in all of the flows --- src/LoginPage.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index 05741cca..fc918359 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -315,10 +315,14 @@ LoginPage::checkHomeserverVersion() if (err || flows.flows.empty()) emit versionOkCb(LoginMethod::Password); - if (flows.flows[0].type == mtx::user_interactive::auth_types::sso) - emit versionOkCb(LoginMethod::SSO); - else - emit versionOkCb(LoginMethod::Password); + LoginMethod login_method = LoginMethod::Password; + for(const auto &flow : flows.flows) { + if (flow.type == mtx::user_interactive::auth_types::sso) { + login_method = LoginMethod::SSO; + break; + } + } + emit versionOk(login_method); }); }); } From b732ea432b8e759b06e221b6d15f1e8a8b1723a9 Mon Sep 17 00:00:00 2001 From: d42 Date: Thu, 31 Dec 2020 17:57:09 +0100 Subject: [PATCH 05/35] rename login_method -> loginMethod to fit the Nheko coding style --- src/LoginPage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index fc918359..62498738 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -315,14 +315,14 @@ LoginPage::checkHomeserverVersion() if (err || flows.flows.empty()) emit versionOkCb(LoginMethod::Password); - LoginMethod login_method = LoginMethod::Password; + LoginMethod loginMethod = LoginMethod::Password; for(const auto &flow : flows.flows) { if (flow.type == mtx::user_interactive::auth_types::sso) { - login_method = LoginMethod::SSO; + loginMethod = LoginMethod::SSO; break; } } - emit versionOk(login_method); + emit versionOk(loginMethod); }); }); } From a13502b881528a13da644f1441ce7eaa99764938 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 1 Jan 2021 04:14:34 +0100 Subject: [PATCH 06/35] lint --- src/LoginPage.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index 62498738..39d0f9d6 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -316,11 +316,11 @@ LoginPage::checkHomeserverVersion() emit versionOkCb(LoginMethod::Password); LoginMethod loginMethod = LoginMethod::Password; - for(const auto &flow : flows.flows) { - if (flow.type == mtx::user_interactive::auth_types::sso) { - loginMethod = LoginMethod::SSO; - break; - } + for (const auto &flow : flows.flows) { + if (flow.type == mtx::user_interactive::auth_types::sso) { + loginMethod = LoginMethod::SSO; + break; + } } emit versionOk(loginMethod); }); From 5ca043ad87d56cfaa984f67b84c5e98f1900c61e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Jan 2021 15:10:53 +0100 Subject: [PATCH 07/35] Fix user status that got lost at some point --- resources/qml/MessageView.qml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 679c1f50..aa222ac5 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -140,6 +140,15 @@ ListView { } + Label { + color: colors.buttonText + text: TimelineManager.userStatus(modelData.userId) + textFormat: Text.PlainText + elide: Text.ElideRight + width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - avatarSize + font.italic: true + } + } } From 3a41bb9fff45c7e87ee1ce647155a86ce4f227b1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 5 Jan 2021 22:10:40 +0100 Subject: [PATCH 08/35] Fix typing notifications flickering sometimes --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.json | 2 +- src/Cache.cpp | 77 ++++++++++++++++++---------- src/timeline/TimelineViewManager.cpp | 19 ++++--- 4 files changed, 66 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ead1c32b..6ba411ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -356,7 +356,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG ce8bc9c3dd6bba432e716f55136133111b0186e7 + GIT_TAG cad81d1677a4845366b93112f8f2e267ee8c9ae0 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 352ca648..49e92311 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -162,7 +162,7 @@ "name": "mtxclient", "sources": [ { - "commit": "ce8bc9c3dd6bba432e716f55136133111b0186e7", + "commit": "cad81d1677a4845366b93112f8f2e267ee8c9ae0", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" } diff --git a/src/Cache.cpp b/src/Cache.cpp index dac0b23a..04046346 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -124,17 +124,15 @@ Cache::isHiddenEvent(lmdb::txn &txn, EventType::Reaction, EventType::CallCandidates, EventType::Unsupported}; if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, "")) - hiddenEvents = std::move( - std::get< - mtx::events::Event>( - *temp) - .content); + hiddenEvents = + std::move(std::get>(*temp) + .content); if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id)) - hiddenEvents = std::move( - std::get< - mtx::events::Event>( - *temp) - .content); + hiddenEvents = + std::move(std::get>(*temp) + .content); return std::visit( [hiddenEvents](const auto &ev) { @@ -1197,7 +1195,7 @@ void Cache::saveState(const mtx::responses::Sync &res) { using namespace mtx::events; - auto user_id = this->localUserId_.toStdString(); + auto local_user_id = this->localUserId_.toStdString(); auto currentBatchToken = nextBatchToken(); @@ -1252,13 +1250,19 @@ Cache::saveState(const mtx::responses::Sync &res) evt); // for tag events - if (std::holds_alternative>(evt)) { - auto tags_evt = std::get>(evt); - has_new_tags = true; + if (std::holds_alternative>( + evt)) { + auto tags_evt = + std::get>(evt); + has_new_tags = true; for (const auto &tag : tags_evt.content.tags) { updatedInfo.tags.push_back(tag.first); } } + if (auto fr = std::get_if>(&evt)) { + nhlog::db()->debug("Fully read: {}", fr->content.event_id); + } } if (!has_new_tags) { // retrieve the old tags, they haven't changed @@ -1282,7 +1286,20 @@ Cache::saveState(const mtx::responses::Sync &res) lmdb::dbi_put( txn, roomsDb_, lmdb::val(room.first), lmdb::val(json(updatedInfo).dump())); - updateReadReceipt(txn, room.first, room.second.ephemeral.receipts); + for (const auto &e : room.second.ephemeral.events) { + if (auto receiptsEv = std::get_if< + mtx::events::EphemeralEvent>(&e)) { + Receipts receipts; + + for (const auto &[event_id, userReceipts] : + receiptsEv->content.receipts) { + for (const auto &[user_id, receipt] : userReceipts.users) { + receipts[event_id][user_id] = receipt.ts; + } + } + updateReadReceipt(txn, room.first, receipts); + } + } // Clean up non-valid invites. removeInvite(txn, room.first); @@ -1302,19 +1319,27 @@ Cache::saveState(const mtx::responses::Sync &res) std::map readStatus; for (const auto &room : res.rooms.join) { - if (!room.second.ephemeral.receipts.empty()) { - std::vector receipts; - for (const auto &receipt : room.second.ephemeral.receipts) { - for (const auto &receiptUsersTs : receipt.second) { - if (receiptUsersTs.first != user_id) { - receipts.push_back( - QString::fromStdString(receipt.first)); - break; + for (const auto &e : room.second.ephemeral.events) { + if (auto receiptsEv = std::get_if< + mtx::events::EphemeralEvent>(&e)) { + std::vector receipts; + + for (const auto &[event_id, userReceipts] : + receiptsEv->content.receipts) { + for (const auto &[user_id, receipt] : userReceipts.users) { + (void)receipt; + + if (user_id != local_user_id) { + receipts.push_back( + QString::fromStdString(event_id)); + break; + } } } + if (!receipts.empty()) + emit newReadReceipts(QString::fromStdString(room.first), + receipts); } - if (!receipts.empty()) - emit newReadReceipts(QString::fromStdString(room.first), receipts); } readStatus.emplace(QString::fromStdString(room.first), calculateRoomReadStatus(room.first)); @@ -1440,7 +1465,7 @@ Cache::roomsWithTagUpdates(const mtx::responses::Sync &res) for (const auto &room : res.rooms.join) { bool hasUpdates = false; for (const auto &evt : room.second.account_data.events) { - if (std::holds_alternative>(evt)) { + if (std::holds_alternative>(evt)) { hasUpdates = true; } } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 03eb53fc..f31b5ea5 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -297,13 +297,20 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms) &CallManager::syncEvent); if (ChatPage::instance()->userSettings()->typingNotifications()) { - std::vector typing; - typing.reserve(room.ephemeral.typing.size()); - for (const auto &user : room.ephemeral.typing) { - if (user != http::client()->user_id().to_string()) - typing.push_back(QString::fromStdString(user)); + for (const auto &ev : room.ephemeral.events) { + if (auto t = std::get_if< + mtx::events::EphemeralEvent>( + &ev)) { + std::vector typing; + typing.reserve(t->content.user_ids.size()); + for (const auto &user : t->content.user_ids) { + if (user != http::client()->user_id().to_string()) + typing.push_back( + QString::fromStdString(user)); + } + room_model->updateTypingUsers(typing); + } } - room_model->updateTypingUsers(typing); } } From 03d817d2770d2238cc84042742755f81e0a6dd8c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 Jan 2021 09:51:41 +0100 Subject: [PATCH 09/35] Force override macos deps in CI --- .ci/macos/Brewfile | 2 +- .gitlab-ci.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.ci/macos/Brewfile b/.ci/macos/Brewfile index 7e9687c7..e7a62374 100644 --- a/.ci/macos/Brewfile +++ b/.ci/macos/Brewfile @@ -1,12 +1,12 @@ tap "nlohmann/json" +brew "python3" brew "pkg-config" brew "clang-format" brew "cmake" brew "ninja" brew "openssl" brew "qt5" -brew "python3" brew "nlohmann_json" brew "gstreamer" brew "gst-plugins-base" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a6775db..4fa41d37 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,7 +52,8 @@ build-macos: tags: [macos] before_script: - brew update - - brew bundle --file=./.ci/macos/Brewfile + - brew reinstall --force python3 + - brew bundle --file=./.ci/macos/Brewfile --force --cleanup - pip3 install dmgbuild script: - export PATH=/usr/local/opt/qt/bin/:${PATH} From 0c0c69074df71187ec857dbad119fbdefa655501 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 Jan 2021 11:01:41 +0100 Subject: [PATCH 10/35] Fix shadowing in LoginPage.cpp --- src/LoginPage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index 39d0f9d6..dba5ba51 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -315,14 +315,14 @@ LoginPage::checkHomeserverVersion() if (err || flows.flows.empty()) emit versionOkCb(LoginMethod::Password); - LoginMethod loginMethod = LoginMethod::Password; + LoginMethod loginMethod_ = LoginMethod::Password; for (const auto &flow : flows.flows) { if (flow.type == mtx::user_interactive::auth_types::sso) { - loginMethod = LoginMethod::SSO; + loginMethod_ = LoginMethod::SSO; break; } } - emit versionOk(loginMethod); + emit versionOk(loginMethod_); }); }); } From f3b6e994d00d2f63fcc8ad2903da6a67c5d122e5 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 Jan 2021 11:24:35 +0100 Subject: [PATCH 11/35] Remove useless capture --- src/UserSettingsPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 4ca3be49..7c7ef9ab 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -54,7 +54,7 @@ QSharedPointer UserSettings::instance_; UserSettings::UserSettings() { - connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [this]() { + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() { instance_.clear(); }); } From 4ead72fdc1155100630a3181bc66eb12b65c6742 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 Jan 2021 12:47:13 +0100 Subject: [PATCH 12/35] Remove sodium from flatpak --- io.github.NhekoReborn.Nheko.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 49e92311..7aba130b 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -125,16 +125,6 @@ } ] }, - { - "name": "sodium", - "sources": [ - { - "sha256": "6f504490b342a4f8a4c4a02fc9b866cbef8622d5df4e5452b46be121e46636c1", - "type": "archive", - "url": "https://github.com/jedisct1/libsodium/releases/download/1.0.18-RELEASE/libsodium-1.0.18.tar.gz" - } - ] - }, { "build-commands": [ "./bootstrap.sh --with-libraries=thread,system,iostreams --prefix=/app", From 236bf09a0ec67d7ee96a99dedd65b92344c66935 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 6 Jan 2021 16:47:35 +0100 Subject: [PATCH 13/35] Explicitly mention, if call support is enabled at build time fixes #366 --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ba411ca..2d0d6dd6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -446,6 +446,15 @@ endif() include(FindPkgConfig) pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.14 gstreamer-webrtc-1.0>=1.14) +if (TARGET PkgConfig::GSTREAMER) + add_feature_info(voip ON "GStreamer found. Call support is enabled + automatically.") +else() + add_feature_info(voip OFF "GStreamer could not be found on your system. + As a consequence call support has been disabled. If you don't want that, + make sure gstreamer-sdp-1.0>=1.16 gstreamer-webrtc-1.0>=1.16 can be found + via pkgconfig.") +endif() # single instance functionality set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") From 4b4c321397e0681f306eade889e0fab6dbbe94f5 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 7 Jan 2021 10:44:59 +0100 Subject: [PATCH 14/35] Allow inline replies from notifications on linux --- src/ChatPage.cpp | 8 ++++++ src/notifications/Manager.h | 2 ++ src/notifications/ManagerLinux.cpp | 42 +++++++++++++++++++++------- src/timeline/InputBar.h | 2 +- src/timeline/TimelineViewManager.cpp | 12 ++++++++ src/timeline/TimelineViewManager.h | 3 ++ 6 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 37248022..4e87349a 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -282,6 +282,14 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) room_list_->highlightSelectedRoom(roomid); activateWindow(); }); + connect(¬ificationsManager, + &NotificationsManager::sendNotificationReply, + this, + [this](const QString &roomid, const QString &eventid, const QString &body) { + view_manager_->queueReply(roomid, eventid, body); + room_list_->highlightSelectedRoom(roomid); + activateWindow(); + }); setGroupViewState(userSettings_->groupView()); diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h index e6be5953..b5347bd6 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -36,6 +36,7 @@ public: signals: void notificationClicked(const QString roomId, const QString eventId); + void sendNotificationReply(const QString roomId, const QString eventId, const QString body); public slots: void removeNotification(const QString &roomId, const QString &eventId); @@ -58,6 +59,7 @@ private: private slots: void actionInvoked(uint id, QString action); void notificationClosed(uint id, uint reason); + void notificationReplied(uint id, QString reply); }; #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index b9eca1a8..b5e9a6a4 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -28,6 +28,12 @@ NotificationsManager::NotificationsManager(QObject *parent) "NotificationClosed", this, SLOT(notificationClosed(uint, uint))); + QDBusConnection::sessionBus().connect("org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "NotificationReplied", + this, + SLOT(notificationReplied(uint, QString))); } void @@ -56,14 +62,19 @@ NotificationsManager::showNotification(const QString summary, hints["image-data"] = image; hints["sound-name"] = "message-new-instant"; QList argumentList; - argumentList << "nheko"; // app_name - argumentList << (uint)0; // replace_id - argumentList << ""; // app_icon - argumentList << summary; // summary - argumentList << text; // body - argumentList << (QStringList("default") << "reply"); // actions - argumentList << hints; // hints - argumentList << (int)-1; // timeout in ms + argumentList << "nheko"; // app_name + argumentList << (uint)0; // replace_id + argumentList << ""; // app_icon + argumentList << summary; // summary + argumentList << text; // body + // The list of actions has always the action name and then a localized version of that + // action. Currently we just use an empty string for that. + // TODO(Nico): Look into what to actually put there. + argumentList << (QStringList("default") << "" + << "inline-reply" + << ""); // actions + argumentList << hints; // hints + argumentList << (int)-1; // timeout in ms static QDBusInterface notifyApp("org.freedesktop.Notifications", "/org/freedesktop/Notifications", @@ -121,9 +132,20 @@ NotificationsManager::removeNotification(const QString &roomId, const QString &e void NotificationsManager::actionInvoked(uint id, QString action) { - if (action == "default" && notificationIds.contains(id)) { + if (notificationIds.contains(id)) { roomEventId idEntry = notificationIds[id]; - emit notificationClicked(idEntry.roomId, idEntry.eventId); + if (action == "default") { + emit notificationClicked(idEntry.roomId, idEntry.eventId); + } + } +} + +void +NotificationsManager::notificationReplied(uint id, QString reply) +{ + if (notificationIds.contains(id)) { + roomEventId idEntry = notificationIds[id]; + emit sendNotificationReply(idEntry.roomId, idEntry.eventId, reply); } } diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index 27aa4bc3..89ca34fe 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -42,6 +42,7 @@ public slots: void openFileSelection(); bool uploading() const { return uploading_; } void callButton(); + void message(QString body); QObject *completerFor(QString completerName); @@ -54,7 +55,6 @@ signals: void uploadingChanged(bool value); private: - void message(QString body); void emote(QString body); void command(QString name, QString args); void image(const QString &filename, diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index f31b5ea5..f10c2c0d 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -508,6 +508,18 @@ TimelineViewManager::initWithMessages(const std::vector &roomIds) addRoom(roomId); } +void +TimelineViewManager::queueReply(const QString &roomid, + const QString &repliedToEvent, + const QString &replyBody) +{ + auto room = models.find(roomid); + if (room != models.end()) { + room.value()->setReply(repliedToEvent); + room.value()->input()->message(replyBody); + } +} + void TimelineViewManager::queueReactionMessage(const QString &reactedEvent, const QString &reactionKey) { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index f346acf8..1cec0939 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -120,6 +120,9 @@ public slots: } void updateColorPalette(); + void queueReply(const QString &roomid, + const QString &repliedToEvent, + const QString &replyBody); void queueReactionMessage(const QString &reactedEvent, const QString &reactionKey); void queueCallMessage(const QString &roomid, const mtx::events::msg::CallInvite &); void queueCallMessage(const QString &roomid, const mtx::events::msg::CallCandidates &); From 4e6df2edcbaa23a8130d70c5d7d83df1096e5d31 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 7 Jan 2021 11:21:10 +0100 Subject: [PATCH 15/35] Fix notification reply build on other platforms --- src/notifications/ManagerMac.mm | 5 +++++ src/notifications/ManagerWin.cpp | 1 + 2 files changed, 6 insertions(+) diff --git a/src/notifications/ManagerMac.mm b/src/notifications/ManagerMac.mm index f035e5f2..c09e894c 100644 --- a/src/notifications/ManagerMac.mm +++ b/src/notifications/ManagerMac.mm @@ -39,6 +39,11 @@ NotificationsManager::postNotification( //unused void NotificationsManager::actionInvoked(uint, QString) +{ + } + +void +NotificationsManager::notificationReplied(uint, QString) { } diff --git a/src/notifications/ManagerWin.cpp b/src/notifications/ManagerWin.cpp index 5a9cb83e..cc61c645 100644 --- a/src/notifications/ManagerWin.cpp +++ b/src/notifications/ManagerWin.cpp @@ -61,6 +61,7 @@ NotificationsManager::postNotification(const QString &room_id, } void NotificationsManager::actionInvoked(uint, QString) {} +void NotificationsManager::notificationReplied(uint, QString) {} void NotificationsManager::notificationClosed(uint, uint) {} From ac410f46f2900bdab5a5619529a1c91f002c48c4 Mon Sep 17 00:00:00 2001 From: trilene Date: Wed, 9 Dec 2020 20:49:48 -0500 Subject: [PATCH 16/35] Move call-related properties to CallManager --- resources/qml/ActiveCallBar.qml | 37 +++++++++++----------- resources/qml/MessageInput.qml | 8 ++--- resources/qml/TimelineView.qml | 2 +- src/CallManager.cpp | 35 ++++++++++++++------- src/CallManager.h | 21 +++++++++++-- src/timeline/InputBar.cpp | 2 +- src/timeline/TimelineViewManager.cpp | 47 +++------------------------- src/timeline/TimelineViewManager.h | 23 +------------- 8 files changed, 70 insertions(+), 105 deletions(-) diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml index 3059e213..57b0877c 100644 --- a/resources/qml/ActiveCallBar.qml +++ b/resources/qml/ActiveCallBar.qml @@ -6,14 +6,14 @@ import im.nheko 1.0 Rectangle { id: activeCallBar - visible: TimelineManager.callState != WebRTCState.DISCONNECTED + visible: CallManager.isOnCall color: "#2ECC71" implicitHeight: visible ? rowLayout.height + 8 : 0 MouseArea { anchors.fill: parent onClicked: { - if (TimelineManager.onVideoCall) + if (CallManager.isOnVideoCall) stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1; } @@ -30,19 +30,19 @@ Rectangle { Avatar { width: avatarSize height: avatarSize - url: TimelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") - displayName: TimelineManager.callPartyName + url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") + displayName: CallManager.callPartyName } Label { font.pointSize: fontMetrics.font.pointSize * 1.1 - text: " " + TimelineManager.callPartyName + " " + text: " " + CallManager.callPartyName + " " } Image { Layout.preferredWidth: 24 Layout.preferredHeight: 24 - source: TimelineManager.onVideoCall ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" + source: CallManager.isOnVideoCall ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" } Label { @@ -52,11 +52,10 @@ Rectangle { } Item { - state: TimelineManager.callState states: [ State { name: "OFFERSENT" - when: state == WebRTCState.OFFERSENT + when: CallManager.callState == WebRTCState.OFFERSENT PropertyChanges { target: callStateLabel @@ -66,7 +65,7 @@ Rectangle { }, State { name: "CONNECTING" - when: state == WebRTCState.CONNECTING + when: CallManager.callState == WebRTCState.CONNECTING PropertyChanges { target: callStateLabel @@ -76,7 +75,7 @@ Rectangle { }, State { name: "ANSWERSENT" - when: state == WebRTCState.ANSWERSENT + when: CallManager.callState == WebRTCState.ANSWERSENT PropertyChanges { target: callStateLabel @@ -86,7 +85,7 @@ Rectangle { }, State { name: "CONNECTED" - when: state == WebRTCState.CONNECTED + when: CallManager.callState == WebRTCState.CONNECTED PropertyChanges { target: callStateLabel @@ -100,13 +99,13 @@ Rectangle { PropertyChanges { target: stackLayout - currentIndex: TimelineManager.onVideoCall ? 1 : 0 + currentIndex: CallManager.isOnVideoCall ? 1 : 0 } }, State { name: "DISCONNECTED" - when: state == WebRTCState.DISCONNECTED + when: CallManager.callState == WebRTCState.DISCONNECTED PropertyChanges { target: callStateLabel @@ -132,7 +131,7 @@ Rectangle { } interval: 1000 - running: TimelineManager.callState == WebRTCState.CONNECTED + running: CallManager.callState == WebRTCState.CONNECTED repeat: true onTriggered: { var d = new Date(); @@ -149,7 +148,7 @@ Rectangle { } ImageButton { - visible: TimelineManager.onVideoCall + visible: CallManager.isOnVideoCall width: 24 height: 24 buttonTextColor: "#000000" @@ -157,7 +156,7 @@ Rectangle { hoverEnabled: true ToolTip.visible: hovered ToolTip.text: "Toggle camera view" - onClicked: TimelineManager.toggleCameraView() + onClicked: CallManager.toggleCameraView() } Item { @@ -168,11 +167,11 @@ Rectangle { width: 24 height: 24 buttonTextColor: "#000000" - image: TimelineManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.png" : ":/icons/icons/ui/microphone-mute.png" + image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.png" : ":/icons/icons/ui/microphone-mute.png" hoverEnabled: true ToolTip.visible: hovered - ToolTip.text: TimelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") - onClicked: TimelineManager.toggleMicMute() + ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") + onClicked: CallManager.toggleMicMute() } Item { diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index e8ebd5fc..2847d51d 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -17,14 +17,14 @@ Rectangle { spacing: 16 ImageButton { - visible: TimelineManager.callsSupported + visible: CallManager.callsSupported Layout.alignment: Qt.AlignBottom hoverEnabled: true width: 22 height: 22 - image: TimelineManager.isOnCall ? ":/icons/icons/ui/end-call.png" : ":/icons/icons/ui/place-call.png" + image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.png" : ":/icons/icons/ui/place-call.png" ToolTip.visible: hovered - ToolTip.text: TimelineManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call") + ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call") Layout.topMargin: 8 Layout.bottomMargin: 8 Layout.leftMargin: 16 @@ -39,7 +39,7 @@ Rectangle { image: ":/icons/icons/ui/paper-clip-outline.png" Layout.topMargin: 8 Layout.bottomMargin: 8 - Layout.leftMargin: TimelineManager.callsSupported ? 0 : 16 + Layout.leftMargin: CallManager.callsSupported ? 0 : 16 onClicked: TimelineManager.timeline.input.openFileSelection() ToolTip.visible: hovered ToolTip.text: qsTr("Send a file") diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 6e9cd665..c71eb89f 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -210,7 +210,7 @@ Page { } Loader { - source: TimelineManager.onVideoCall ? "VideoCall.qml" : "" + source: CallManager.isOnVideoCall ? "VideoCall.qml" : "" onLoaded: TimelineManager.setVideoCallItem() } diff --git a/src/CallManager.cpp b/src/CallManager.cpp index 89cfeaf9..cb523bc2 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp @@ -13,7 +13,6 @@ #include "MainWindow.h" #include "MatrixClient.h" #include "Utils.h" -#include "WebRTCSession.h" #include "dialogs/AcceptCall.h" #include "mtx/responses/turn_server.hpp" @@ -112,6 +111,7 @@ CallManager::CallManager(QObject *parent) default: break; } + emit newCallState(); }); connect(&player_, @@ -144,7 +144,7 @@ CallManager::CallManager(QObject *parent) void CallManager::sendInvite(const QString &roomid, bool isVideo) { - if (onActiveCall()) + if (isOnCall()) return; auto roomInfo = cache::singleRoomInfo(roomid.toStdString()); @@ -206,12 +206,6 @@ CallManager::hangUp(CallHangUp::Reason reason) } } -bool -CallManager::onActiveCall() const -{ - return session_.state() != webrtc::State::DISCONNECTED; -} - void CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event) { @@ -257,7 +251,7 @@ CallManager::handleEvent(const RoomEvent &callInviteEvent) return; auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id); - if (onActiveCall() || roomInfo.member_count != 2) { + if (isOnCall() || roomInfo.member_count != 2) { emit newMessage(QString::fromStdString(callInviteEvent.room_id), CallHangUp{callInviteEvent.content.call_id, 0, @@ -332,7 +326,7 @@ CallManager::handleEvent(const RoomEvent &callCandidatesEvent) callCandidatesEvent.sender); if (callid_ == callCandidatesEvent.content.call_id) { - if (onActiveCall()) + if (isOnCall()) session_.acceptICECandidates(callCandidatesEvent.content.candidates); else { // CallInvite has been received and we're awaiting localUser to accept or @@ -350,7 +344,7 @@ CallManager::handleEvent(const RoomEvent &callAnswerEvent) callAnswerEvent.content.call_id, callAnswerEvent.sender); - if (!onActiveCall() && callAnswerEvent.sender == utils::localUser().toStdString() && + if (!isOnCall() && callAnswerEvent.sender == utils::localUser().toStdString() && callid_ == callAnswerEvent.content.call_id) { emit ChatPage::instance()->showNotification("Call answered on another device."); stopRingtone(); @@ -358,7 +352,7 @@ CallManager::handleEvent(const RoomEvent &callAnswerEvent) return; } - if (onActiveCall() && callid_ == callAnswerEvent.content.call_id) { + if (isOnCall() && callid_ == callAnswerEvent.content.call_id) { stopRingtone(); if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) { emit ChatPage::instance()->showNotification("Problem setting up call."); @@ -381,6 +375,23 @@ CallManager::handleEvent(const RoomEvent &callHangUpEvent) } } +void +CallManager::toggleMicMute() +{ + session_.toggleMicMute(); + emit micMuteChanged(); +} + +bool +CallManager::callsSupported() const +{ +#ifdef GSTREAMER_AVAILABLE + return true; +#else + return false; +#endif +} + void CallManager::generateCallID() { diff --git a/src/CallManager.h b/src/CallManager.h index 8004e838..d59a6249 100644 --- a/src/CallManager.h +++ b/src/CallManager.h @@ -8,6 +8,7 @@ #include #include +#include "WebRTCSession.h" #include "mtx/events/collections.hpp" #include "mtx/events/voip.hpp" @@ -16,11 +17,17 @@ struct TurnServer; } class QUrl; -class WebRTCSession; class CallManager : public QObject { Q_OBJECT + Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState) + Q_PROPERTY(bool isOnVideoCall READ isOnVideoCall NOTIFY newVideoCallState) + Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState) + Q_PROPERTY(QString callPartyName READ callPartyName NOTIFY newCallParty) + Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newCallParty) + Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged) + Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT) public: CallManager(QObject *); @@ -28,21 +35,29 @@ public: void sendInvite(const QString &roomid, bool isVideo); void hangUp( mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User); - bool onActiveCall() const; + bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; } + bool isOnVideoCall() const { return session_.isVideo(); } + webrtc::State callState() const { return session_.state(); } QString callPartyName() const { return callPartyName_; } QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; } + bool isMicMuted() const { return session_.isMicMuted(); } + bool callsSupported() const; void refreshTurnServer(); public slots: void syncEvent(const mtx::events::collections::TimelineEvents &event); + void toggleMicMute(); + void toggleCameraView() { session_.toggleCameraView(); } signals: void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &); void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &); void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); - void newCallParty(); + void newCallState(); void newVideoCallState(); + void newCallParty(); + void micMuteChanged(); void turnServerRetrieved(const mtx::responses::TurnServer &); private slots: diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 5cbc33e0..2f50a7cc 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -597,7 +597,7 @@ void InputBar::callButton() { auto callManager_ = ChatPage::instance()->callManager(); - if (callManager_->onActiveCall()) { + if (callManager_->isOnCall()) { callManager_->hangUp(); } else { auto current_room_ = room->roomId(); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index f10c2c0d..97af0065 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -136,6 +136,10 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) -> QObject * { return ChatPage::instance()->userSettings().data(); }); + qmlRegisterSingletonType( + "im.nheko", 1, 0, "CallManager", [](QQmlEngine *, QJSEngine *) -> QObject * { + return ChatPage::instance()->callManager(); + }); qRegisterMetaType(); qRegisterMetaType>(); @@ -237,36 +241,6 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par isInitialSync_ = true; emit initialSyncChanged(true); }); - connect(&WebRTCSession::instance(), - &WebRTCSession::stateChanged, - this, - &TimelineViewManager::callStateChanged); - connect( - callManager_, &CallManager::newCallParty, this, &TimelineViewManager::callPartyChanged); - connect(callManager_, - &CallManager::newVideoCallState, - this, - &TimelineViewManager::videoCallChanged); - - connect(&WebRTCSession::instance(), - &WebRTCSession::stateChanged, - this, - &TimelineViewManager::onCallChanged); -} - -bool -TimelineViewManager::isOnCall() const -{ - return callManager_->onActiveCall(); -} -bool -TimelineViewManager::callsSupported() const -{ -#ifdef GSTREAMER_AVAILABLE - return true; -#else - return false; -#endif } void @@ -354,19 +328,6 @@ TimelineViewManager::escapeEmoji(QString str) const return utils::replaceEmoji(str); } -void -TimelineViewManager::toggleMicMute() -{ - WebRTCSession::instance().toggleMicMute(); - emit micMuteChanged(); -} - -void -TimelineViewManager::toggleCameraView() -{ - WebRTCSession::instance().toggleCameraView(); -} - void TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 1cec0939..23a960b8 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -36,13 +36,6 @@ class TimelineViewManager : public QObject bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged) Q_PROPERTY( bool isNarrowView MEMBER isNarrowView_ READ isNarrowView NOTIFY narrowViewChanged) - Q_PROPERTY(webrtc::State callState READ callState NOTIFY callStateChanged) - Q_PROPERTY(bool onVideoCall READ onVideoCall NOTIFY videoCallChanged) - Q_PROPERTY(QString callPartyName READ callPartyName NOTIFY callPartyChanged) - Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY callPartyChanged) - Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged) - Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY onCallChanged) - Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT) public: TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr); @@ -61,14 +54,6 @@ public: Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; } Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } bool isNarrowView() const { return isNarrowView_; } - webrtc::State callState() const { return WebRTCSession::instance().state(); } - bool onVideoCall() const { return WebRTCSession::instance().isVideo(); } - Q_INVOKABLE void setVideoCallItem(); - QString callPartyName() const { return callManager_->callPartyName(); } - QString callPartyAvatarUrl() const { return callManager_->callPartyAvatarUrl(); } - bool isMicMuted() const { return WebRTCSession::instance().isMicMuted(); } - Q_INVOKABLE void toggleMicMute(); - Q_INVOKABLE void toggleCameraView(); Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const; Q_INVOKABLE QColor userColor(QString id, QColor background); Q_INVOKABLE QString escapeEmoji(QString str) const; @@ -98,11 +83,6 @@ signals: void inviteUsers(QStringList users); void showRoomList(); void narrowViewChanged(); - void callStateChanged(webrtc::State); - void videoCallChanged(); - void callPartyChanged(); - void micMuteChanged(); - void onCallChanged(); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); @@ -130,8 +110,7 @@ public slots: void queueCallMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); void updateEncryptedDescriptions(); - bool isOnCall() const; - bool callsSupported() const; + void setVideoCallItem(); void enableBackButton() { From 7124024977ccb237547b88b6a96a50ac6838c354 Mon Sep 17 00:00:00 2001 From: trilene Date: Thu, 17 Dec 2020 11:25:32 -0500 Subject: [PATCH 17/35] Make call invites less intrusive --- CMakeLists.txt | 2 - resources/qml/TimelineView.qml | 9 +- resources/qml/{ => voip}/ActiveCallBar.qml | 16 +-- resources/qml/voip/CallInviteBar.qml | 95 +++++++++++++ resources/qml/{ => voip}/VideoCall.qml | 0 resources/res.qrc | 5 +- src/CallManager.cpp | 100 ++++++++------ src/CallManager.h | 32 +++-- src/WebRTCSession.cpp | 2 + src/WebRTCSession.h | 1 + src/dialogs/AcceptCall.cpp | 152 --------------------- src/dialogs/AcceptCall.h | 39 ------ src/timeline/InputBar.cpp | 4 +- 13 files changed, 204 insertions(+), 253 deletions(-) rename resources/qml/{ => voip}/ActiveCallBar.qml (91%) create mode 100644 resources/qml/voip/CallInviteBar.qml rename resources/qml/{ => voip}/VideoCall.qml (100%) delete mode 100644 src/dialogs/AcceptCall.cpp delete mode 100644 src/dialogs/AcceptCall.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d0d6dd6..c674053f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -245,7 +245,6 @@ configure_file(cmake/nheko.h config/nheko.h) # set(SRC_FILES # Dialogs - src/dialogs/AcceptCall.cpp src/dialogs/CreateRoom.cpp src/dialogs/FallbackAuth.cpp src/dialogs/ImageOverlay.cpp @@ -464,7 +463,6 @@ feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAG qt5_wrap_cpp(MOC_HEADERS # Dialogs - src/dialogs/AcceptCall.h src/dialogs/CreateRoom.h src/dialogs/FallbackAuth.h src/dialogs/ImageOverlay.h diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index c71eb89f..3e134b35 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -1,6 +1,7 @@ import "./delegates" import "./device-verification" import "./emoji" +import "./voip" import QtGraphicalEffects 1.0 import QtQuick 2.9 import QtQuick.Controls 2.3 @@ -210,7 +211,7 @@ Page { } Loader { - source: CallManager.isOnVideoCall ? "VideoCall.qml" : "" + source: CallManager.isOnCall && CallManager.isVideo ? "voip/VideoCall.qml" : "" onLoaded: TimelineManager.setVideoCallItem() } @@ -223,6 +224,12 @@ Page { } + CallInviteBar { + id: callInviteBar + Layout.fillWidth: true + z: 3 + } + ActiveCallBar { Layout.fillWidth: true z: 3 diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml similarity index 91% rename from resources/qml/ActiveCallBar.qml rename to resources/qml/voip/ActiveCallBar.qml index 57b0877c..9efdb325 100644 --- a/resources/qml/ActiveCallBar.qml +++ b/resources/qml/voip/ActiveCallBar.qml @@ -2,18 +2,18 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import im.nheko 1.0 +import "../" Rectangle { - id: activeCallBar visible: CallManager.isOnCall - color: "#2ECC71" + color: callInviteBar.color implicitHeight: visible ? rowLayout.height + 8 : 0 MouseArea { anchors.fill: parent onClicked: { - if (CallManager.isOnVideoCall) + if (CallManager.isVideo) stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1; } @@ -31,18 +31,18 @@ Rectangle { width: avatarSize height: avatarSize url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") - displayName: CallManager.callPartyName + displayName: CallManager.callParty } Label { font.pointSize: fontMetrics.font.pointSize * 1.1 - text: " " + CallManager.callPartyName + " " + text: " " + CallManager.callParty + " " } Image { Layout.preferredWidth: 24 Layout.preferredHeight: 24 - source: CallManager.isOnVideoCall ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" + source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" } Label { @@ -99,7 +99,7 @@ Rectangle { PropertyChanges { target: stackLayout - currentIndex: CallManager.isOnVideoCall ? 1 : 0 + currentIndex: CallManager.isVideo ? 1 : 0 } }, @@ -148,7 +148,7 @@ Rectangle { } ImageButton { - visible: CallManager.isOnVideoCall + visible: CallManager.isVideo width: 24 height: 24 buttonTextColor: "#000000" diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml new file mode 100644 index 00000000..6d4d2ac0 --- /dev/null +++ b/resources/qml/voip/CallInviteBar.qml @@ -0,0 +1,95 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Dialogs 1.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 +import "../" + +Rectangle { + + visible: CallManager.haveCallInvite + color: "#2ECC71" + implicitHeight: visible ? rowLayout.height + 8 : 0 + + MessageDialog { + id: warningDialog + icon: StandardIcon.Warning + } + + RowLayout { + id: rowLayout + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 8 + + Avatar { + width: avatarSize + height: avatarSize + url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") + displayName: CallManager.callParty + } + + Label { + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: " " + CallManager.callParty + " " + } + + Image { + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 + source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" + } + + Label { + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: CallManager.isVideo ? "Video Call" : "Voice Call" + } + + Item { + Layout.fillWidth: true + } + + Button { + icon.source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" + palette: colors + text: qsTr("Accept") + onClicked: { + if (CallManager.mics.length == 0) { + warningDialog.text = "No microphone found."; + warningDialog.open(); + return; + } + else if (!CallManager.mics.includes(Settings.microphone)) { + warningDialog.text = "Unknown microphone: " + Settings.microphone; + warningDialog.open(); + return; + } + if (CallManager.isVideo && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) { + warningDialog.text = "Unknown camera: " + Settings.camera; + warningDialog.open(); + return; + } + CallManager.acceptInvite(); + } + } + + Item { + implicitWidth: 8 + } + + Button { + icon.source: "qrc:/icons/icons/ui/end-call.png" + palette: colors + text: qsTr("Decline") + onClicked: { + CallManager.hangUp(); + } + } + + Item { + implicitWidth: 16 + } + } +} diff --git a/resources/qml/VideoCall.qml b/resources/qml/voip/VideoCall.qml similarity index 100% rename from resources/qml/VideoCall.qml rename to resources/qml/voip/VideoCall.qml diff --git a/resources/res.qrc b/resources/res.qrc index a01907ec..321fe12e 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -123,7 +123,6 @@ qtquickcontrols2.conf qml/TimelineView.qml - qml/ActiveCallBar.qml qml/Avatar.qml qml/Completer.qml qml/EncryptionIndicator.qml @@ -139,7 +138,6 @@ qml/TimelineRow.qml qml/TopBar.qml qml/TypingIndicator.qml - qml/VideoCall.qml qml/emoji/EmojiButton.qml qml/emoji/EmojiPicker.qml qml/UserProfile.qml @@ -159,6 +157,9 @@ qml/device-verification/NewVerificationRequest.qml qml/device-verification/Failed.qml qml/device-verification/Success.qml + qml/voip/ActiveCallBar.qml + qml/voip/CallInviteBar.qml + qml/voip/VideoCall.qml media/ring.ogg diff --git a/src/CallManager.cpp b/src/CallManager.cpp index cb523bc2..9864b203 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp @@ -10,10 +10,9 @@ #include "CallManager.h" #include "ChatPage.h" #include "Logging.h" -#include "MainWindow.h" #include "MatrixClient.h" +#include "UserSettingsPage.h" #include "Utils.h" -#include "dialogs/AcceptCall.h" #include "mtx/responses/turn_server.hpp" @@ -114,6 +113,22 @@ CallManager::CallManager(QObject *parent) emit newCallState(); }); + connect(&session_, &WebRTCSession::devicesChanged, this, [this]() { + if (ChatPage::instance()->userSettings()->microphone().isEmpty()) { + auto mics = session_.getDeviceNames(false, std::string()); + if (!mics.empty()) + ChatPage::instance()->userSettings()->setMicrophone( + QString::fromStdString(mics.front())); + } + if (ChatPage::instance()->userSettings()->camera().isEmpty()) { + auto cameras = session_.getDeviceNames(true, std::string()); + if (!cameras.empty()) + ChatPage::instance()->userSettings()->setCamera( + QString::fromStdString(cameras.front())); + } + emit devicesChanged(); + }); + connect(&player_, &QMediaPlayer::mediaStatusChanged, this, @@ -160,7 +175,8 @@ CallManager::sendInvite(const QString &roomid, bool isVideo) return; } - roomid_ = roomid; + isVideo_ = isVideo; + roomid_ = roomid; session_.setTurnServers(turnURIs_); generateCallID(); nhlog::ui()->debug( @@ -168,16 +184,14 @@ CallManager::sendInvite(const QString &roomid, bool isVideo) std::vector members(cache::getMembers(roomid.toStdString())); const RoomMember &callee = members.front().user_id == utils::localUser() ? members.back() : members.front(); - callPartyName_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name; + callParty_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name; callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); - emit newCallParty(); + emit newInviteState(); playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true); if (!session_.createOffer(isVideo)) { emit ChatPage::instance()->showNotification("Problem setting up call."); endCall(); } - if (isVideo) - emit newVideoCallState(); } namespace { @@ -271,48 +285,41 @@ CallManager::handleEvent(const RoomEvent &callInviteEvent) std::vector members(cache::getMembers(callInviteEvent.room_id)); const RoomMember &caller = members.front().user_id == utils::localUser() ? members.back() : members.front(); - callPartyName_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name; + callParty_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name; callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); - emit newCallParty(); - auto dialog = new dialogs::AcceptCall(caller.user_id, - caller.display_name, - QString::fromStdString(roomInfo.name), - QString::fromStdString(roomInfo.avatar_url), - isVideo, - MainWindow::instance()); - connect(dialog, &dialogs::AcceptCall::accept, this, [this, callInviteEvent, isVideo]() { - MainWindow::instance()->hideOverlay(); - answerInvite(callInviteEvent.content, isVideo); - }); - connect(dialog, &dialogs::AcceptCall::reject, this, [this]() { - MainWindow::instance()->hideOverlay(); - hangUp(); - }); - MainWindow::instance()->showSolidOverlayModal(dialog); + + haveCallInvite_ = true; + isVideo_ = isVideo; + inviteSDP_ = callInviteEvent.content.sdp; + session_.refreshDevices(); + emit newInviteState(); } void -CallManager::answerInvite(const CallInvite &invite, bool isVideo) +CallManager::acceptInvite() { + if (!haveCallInvite_) + return; + stopRingtone(); std::string errorMessage; if (!session_.havePlugins(false, &errorMessage) || - (isVideo && !session_.havePlugins(true, &errorMessage))) { + (isVideo_ && !session_.havePlugins(true, &errorMessage))) { emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); hangUp(); return; } session_.setTurnServers(turnURIs_); - if (!session_.acceptOffer(invite.sdp)) { + if (!session_.acceptOffer(inviteSDP_)) { emit ChatPage::instance()->showNotification("Problem setting up call."); hangUp(); return; } session_.acceptICECandidates(remoteICECandidates_); remoteICECandidates_.clear(); - if (isVideo) - emit newVideoCallState(); + haveCallInvite_ = false; + emit newInviteState(); } void @@ -348,7 +355,8 @@ CallManager::handleEvent(const RoomEvent &callAnswerEvent) callid_ == callAnswerEvent.content.call_id) { emit ChatPage::instance()->showNotification("Call answered on another device."); stopRingtone(); - MainWindow::instance()->hideOverlay(); + haveCallInvite_ = false; + emit newInviteState(); return; } @@ -369,10 +377,8 @@ CallManager::handleEvent(const RoomEvent &callHangUpEvent) callHangUpReasonString(callHangUpEvent.content.reason), callHangUpEvent.sender); - if (callid_ == callHangUpEvent.content.call_id) { - MainWindow::instance()->hideOverlay(); + if (callid_ == callHangUpEvent.content.call_id) endCall(); - } } void @@ -392,6 +398,23 @@ CallManager::callsSupported() const #endif } +QStringList +CallManager::devices(bool isVideo) const +{ + QStringList ret; + const QString &defaultDevice = isVideo ? ChatPage::instance()->userSettings()->camera() + : ChatPage::instance()->userSettings()->microphone(); + std::vector devices = + session_.getDeviceNames(isVideo, defaultDevice.toStdString()); + ret.reserve(devices.size()); + std::transform(devices.cbegin(), + devices.cend(), + std::back_inserter(ret), + [](const auto &d) { return QString::fromStdString(d); }); + + return ret; +} + void CallManager::generateCallID() { @@ -404,9 +427,13 @@ void CallManager::clear() { roomid_.clear(); - callPartyName_.clear(); + callParty_.clear(); callPartyAvatarUrl_.clear(); callid_.clear(); + isVideo_ = false; + haveCallInvite_ = false; + emit newInviteState(); + inviteSDP_.clear(); remoteICECandidates_.clear(); } @@ -414,11 +441,8 @@ void CallManager::endCall() { stopRingtone(); - clear(); - bool isVideo = session_.isVideo(); session_.end(); - if (isVideo) - emit newVideoCallState(); + clear(); } void diff --git a/src/CallManager.h b/src/CallManager.h index d59a6249..e5571c88 100644 --- a/src/CallManager.h +++ b/src/CallManager.h @@ -16,48 +16,56 @@ namespace mtx::responses { struct TurnServer; } +class QStringList; class QUrl; class CallManager : public QObject { Q_OBJECT + Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState) Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState) - Q_PROPERTY(bool isOnVideoCall READ isOnVideoCall NOTIFY newVideoCallState) + Q_PROPERTY(bool isVideo READ isVideo NOTIFY newInviteState) Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState) - Q_PROPERTY(QString callPartyName READ callPartyName NOTIFY newCallParty) - Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newCallParty) + Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState) + Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState) Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged) Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT) + Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged) + Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged) public: CallManager(QObject *); void sendInvite(const QString &roomid, bool isVideo); - void hangUp( - mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User); + bool haveCallInvite() const { return haveCallInvite_; } bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; } - bool isOnVideoCall() const { return session_.isVideo(); } + bool isVideo() const { return isVideo_; } webrtc::State callState() const { return session_.state(); } - QString callPartyName() const { return callPartyName_; } + QString callParty() const { return callParty_; } QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; } bool isMicMuted() const { return session_.isMicMuted(); } bool callsSupported() const; + QStringList mics() const { return devices(false); } + QStringList cameras() const { return devices(true); } void refreshTurnServer(); public slots: void syncEvent(const mtx::events::collections::TimelineEvents &event); void toggleMicMute(); void toggleCameraView() { session_.toggleCameraView(); } + void acceptInvite(); + void hangUp( + mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User); signals: void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &); void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &); void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); + void newInviteState(); void newCallState(); - void newVideoCallState(); - void newCallParty(); void micMuteChanged(); + void devicesChanged(); void turnServerRetrieved(const mtx::responses::TurnServer &); private slots: @@ -66,10 +74,13 @@ private slots: private: WebRTCSession &session_; QString roomid_; - QString callPartyName_; + QString callParty_; QString callPartyAvatarUrl_; std::string callid_; const uint32_t timeoutms_ = 120000; + bool isVideo_ = false; + bool haveCallInvite_ = false; + std::string inviteSDP_; std::vector remoteICECandidates_; std::vector turnURIs_; QTimer turnServerTimer_; @@ -83,6 +94,7 @@ private: void handleEvent(const mtx::events::RoomEvent &); void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo); void generateCallID(); + QStringList devices(bool isVideo) const; void clear(); void endCall(); void playRingtone(const QUrl &ringtone, bool repeat); diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index 0770a439..90a693a4 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp @@ -242,12 +242,14 @@ newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data) GstDevice *device; gst_message_parse_device_added(msg, &device); addDevice(device); + emit WebRTCSession::instance().devicesChanged(); break; } case GST_MESSAGE_DEVICE_REMOVED: { GstDevice *device; gst_message_parse_device_removed(msg, &device); removeDevice(device, false); + emit WebRTCSession::instance().devicesChanged(); break; } case GST_MESSAGE_DEVICE_CHANGED: { diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h index 57002f8f..fe82725f 100644 --- a/src/WebRTCSession.h +++ b/src/WebRTCSession.h @@ -75,6 +75,7 @@ signals: const std::vector &); void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &); void stateChanged(webrtc::State); + void devicesChanged(); private slots: void setState(webrtc::State state) { state_ = state; } diff --git a/src/dialogs/AcceptCall.cpp b/src/dialogs/AcceptCall.cpp deleted file mode 100644 index 3d25ad82..00000000 --- a/src/dialogs/AcceptCall.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include -#include -#include -#include -#include - -#include "ChatPage.h" -#include "Config.h" -#include "UserSettingsPage.h" -#include "Utils.h" -#include "WebRTCSession.h" -#include "dialogs/AcceptCall.h" -#include "ui/Avatar.h" - -namespace dialogs { - -AcceptCall::AcceptCall(const QString &caller, - const QString &displayName, - const QString &roomName, - const QString &avatarUrl, - bool isVideo, - QWidget *parent) - : QWidget(parent) -{ - std::string errorMessage; - WebRTCSession *session = &WebRTCSession::instance(); - if (!session->havePlugins(false, &errorMessage)) { - emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); - emit close(); - return; - } - if (isVideo && !session->havePlugins(true, &errorMessage)) { - emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); - emit close(); - return; - } - - session->refreshDevices(); - microphones_ = session->getDeviceNames( - false, ChatPage::instance()->userSettings()->microphone().toStdString()); - if (microphones_.empty()) { - emit ChatPage::instance()->showNotification( - tr("Incoming call: No microphone found.")); - emit close(); - return; - } - if (isVideo) - cameras_ = session->getDeviceNames( - true, ChatPage::instance()->userSettings()->camera().toStdString()); - - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH); - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(conf::modals::WIDGET_SPACING); - layout->setMargin(conf::modals::WIDGET_MARGIN); - - QFont f; - f.setPointSizeF(f.pointSizeF()); - - QFont labelFont; - labelFont.setWeight(QFont::Medium); - - QLabel *displayNameLabel = nullptr; - if (!displayName.isEmpty() && displayName != caller) { - displayNameLabel = new QLabel(displayName, this); - labelFont.setPointSizeF(f.pointSizeF() * 2); - displayNameLabel->setFont(labelFont); - displayNameLabel->setAlignment(Qt::AlignCenter); - } - - QLabel *callerLabel = new QLabel(caller, this); - labelFont.setPointSizeF(f.pointSizeF() * 1.2); - callerLabel->setFont(labelFont); - callerLabel->setAlignment(Qt::AlignCenter); - - auto avatar = new Avatar(this, QFontMetrics(f).height() * 6); - if (!avatarUrl.isEmpty()) - avatar->setImage(avatarUrl); - else - avatar->setLetter(utils::firstChar(roomName)); - - const int iconSize = 22; - QLabel *callTypeIndicator = new QLabel(this); - callTypeIndicator->setPixmap( - QIcon(isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png") - .pixmap(QSize(iconSize * 2, iconSize * 2))); - - QLabel *callTypeLabel = new QLabel(isVideo ? tr("Video Call") : tr("Voice Call"), this); - labelFont.setPointSizeF(f.pointSizeF() * 1.1); - callTypeLabel->setFont(labelFont); - callTypeLabel->setAlignment(Qt::AlignCenter); - - auto buttonLayout = new QHBoxLayout; - buttonLayout->setSpacing(18); - acceptBtn_ = new QPushButton(tr("Accept"), this); - acceptBtn_->setDefault(true); - acceptBtn_->setIcon( - QIcon(isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png")); - acceptBtn_->setIconSize(QSize(iconSize, iconSize)); - - rejectBtn_ = new QPushButton(tr("Reject"), this); - rejectBtn_->setIcon(QIcon(":/icons/icons/ui/end-call.png")); - rejectBtn_->setIconSize(QSize(iconSize, iconSize)); - buttonLayout->addWidget(acceptBtn_); - buttonLayout->addWidget(rejectBtn_); - - microphoneCombo_ = new QComboBox(this); - for (const auto &m : microphones_) - microphoneCombo_->addItem(QIcon(":/icons/icons/ui/microphone-unmute.png"), - QString::fromStdString(m)); - - if (!cameras_.empty()) { - cameraCombo_ = new QComboBox(this); - for (const auto &c : cameras_) - cameraCombo_->addItem(QIcon(":/icons/icons/ui/video-call.png"), - QString::fromStdString(c)); - } - - if (displayNameLabel) - layout->addWidget(displayNameLabel, 0, Qt::AlignCenter); - layout->addWidget(callerLabel, 0, Qt::AlignCenter); - layout->addWidget(avatar, 0, Qt::AlignCenter); - layout->addWidget(callTypeIndicator, 0, Qt::AlignCenter); - layout->addWidget(callTypeLabel, 0, Qt::AlignCenter); - layout->addLayout(buttonLayout); - layout->addWidget(microphoneCombo_); - if (cameraCombo_) - layout->addWidget(cameraCombo_); - - connect(acceptBtn_, &QPushButton::clicked, this, [this]() { - ChatPage::instance()->userSettings()->setMicrophone( - QString::fromStdString(microphones_[microphoneCombo_->currentIndex()])); - if (cameraCombo_) { - ChatPage::instance()->userSettings()->setCamera( - QString::fromStdString(cameras_[cameraCombo_->currentIndex()])); - } - emit accept(); - emit close(); - }); - connect(rejectBtn_, &QPushButton::clicked, this, [this]() { - emit reject(); - emit close(); - }); -} - -} diff --git a/src/dialogs/AcceptCall.h b/src/dialogs/AcceptCall.h deleted file mode 100644 index 76ca7ae1..00000000 --- a/src/dialogs/AcceptCall.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include - -#include - -class QComboBox; -class QPushButton; -class QString; - -namespace dialogs { - -class AcceptCall : public QWidget -{ - Q_OBJECT - -public: - AcceptCall(const QString &caller, - const QString &displayName, - const QString &roomName, - const QString &avatarUrl, - bool isVideo, - QWidget *parent = nullptr); - -signals: - void accept(); - void reject(); - -private: - QPushButton *acceptBtn_ = nullptr; - QPushButton *rejectBtn_ = nullptr; - QComboBox *microphoneCombo_ = nullptr; - QComboBox *cameraCombo_ = nullptr; - std::vector microphones_; - std::vector cameras_; -}; - -} diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 2f50a7cc..78c8c6a3 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -597,7 +597,9 @@ void InputBar::callButton() { auto callManager_ = ChatPage::instance()->callManager(); - if (callManager_->isOnCall()) { + if (callManager_->haveCallInvite()) { + return; + } else if (callManager_->isOnCall()) { callManager_->hangUp(); } else { auto current_room_ = room->roomId(); From 459c59901e85d5ac4e04b48ba68a046142ea5bf8 Mon Sep 17 00:00:00 2001 From: trilene Date: Thu, 17 Dec 2020 12:45:54 -0500 Subject: [PATCH 18/35] Fix one-way video calls --- resources/qml/voip/ActiveCallBar.qml | 2 +- src/CallManager.h | 2 ++ src/WebRTCSession.cpp | 24 +++++++++++++++++++++++- src/WebRTCSession.h | 1 + 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml index 9efdb325..a1ddd853 100644 --- a/resources/qml/voip/ActiveCallBar.qml +++ b/resources/qml/voip/ActiveCallBar.qml @@ -148,7 +148,7 @@ Rectangle { } ImageButton { - visible: CallManager.isVideo + visible: CallManager.haveLocalVideo width: 24 height: 24 buttonTextColor: "#000000" diff --git a/src/CallManager.h b/src/CallManager.h index e5571c88..ad25fbd1 100644 --- a/src/CallManager.h +++ b/src/CallManager.h @@ -25,6 +25,7 @@ class CallManager : public QObject Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState) Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState) Q_PROPERTY(bool isVideo READ isVideo NOTIFY newInviteState) + Q_PROPERTY(bool haveLocalVideo READ haveLocalVideo NOTIFY newCallState) Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState) Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState) Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState) @@ -40,6 +41,7 @@ public: bool haveCallInvite() const { return haveCallInvite_; } bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; } bool isVideo() const { return isVideo_; } + bool haveLocalVideo() const { return session_.haveLocalVideo(); } webrtc::State callState() const { return session_.state(); } QString callParty() const { return callParty_; } QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; } diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index 90a693a4..a431ab54 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp @@ -555,7 +555,10 @@ getResolution(GstPad *pad) void addCameraView(GstElement *pipe, const std::pair &videoCallSize) { - GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe), "videosrctee"); + GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe), "videosrctee"); + if (!tee) + return; + GstElement *queue = gst_element_factory_make("queue", nullptr); GstElement *videorate = gst_element_factory_make("videorate", nullptr); gst_bin_add_many(GST_BIN(pipe), queue, videorate, nullptr); @@ -1152,6 +1155,19 @@ WebRTCSession::addVideoPipeline(int vp8PayloadType) return true; } +bool +WebRTCSession::haveLocalVideo() const +{ + if (isVideo_ && state_ >= State::INITIATED) { + GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe_), "videosrctee"); + if (tee) { + gst_object_unref(tee); + return true; + } + } + return false; +} + bool WebRTCSession::isMicMuted() const { @@ -1326,6 +1342,12 @@ WebRTCSession::havePlugins(bool, std::string *) return false; } +bool +WebRTCSession::haveLocalVideo() const +{ + return false; +} + bool WebRTCSession::createOffer(bool) { diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h index fe82725f..2f0fb70e 100644 --- a/src/WebRTCSession.h +++ b/src/WebRTCSession.h @@ -43,6 +43,7 @@ public: bool havePlugins(bool isVideo, std::string *errorMessage = nullptr); webrtc::State state() const { return state_; } bool isVideo() const { return isVideo_; } + bool haveLocalVideo() const; bool isOffering() const { return isOffering_; } bool isRemoteVideoRecvOnly() const { return isRemoteVideoRecvOnly_; } From 07ac7b7e85b504953b0a751a3f56b60ce0a6fb37 Mon Sep 17 00:00:00 2001 From: trilene Date: Fri, 18 Dec 2020 12:49:24 -0500 Subject: [PATCH 19/35] Port PlaceCall dialog to Qml --- CMakeLists.txt | 2 - resources/qml/MessageInput.qml | 23 +++++- resources/qml/voip/PlaceCall.qml | 107 +++++++++++++++++++++++++ resources/res.qrc | 1 + src/CallManager.h | 2 +- src/ChatPage.cpp | 1 - src/dialogs/PlaceCall.cpp | 131 ------------------------------- src/dialogs/PlaceCall.h | 44 ----------- src/timeline/InputBar.cpp | 46 ----------- src/timeline/InputBar.h | 1 - 10 files changed, 131 insertions(+), 227 deletions(-) create mode 100644 resources/qml/voip/PlaceCall.qml delete mode 100644 src/dialogs/PlaceCall.cpp delete mode 100644 src/dialogs/PlaceCall.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c674053f..2365ac09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,7 +253,6 @@ set(SRC_FILES src/dialogs/LeaveRoom.cpp src/dialogs/Logout.cpp src/dialogs/MemberList.cpp - src/dialogs/PlaceCall.cpp src/dialogs/PreviewUploadOverlay.cpp src/dialogs/ReCaptcha.cpp src/dialogs/ReadReceipts.cpp @@ -471,7 +470,6 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/LeaveRoom.h src/dialogs/Logout.h src/dialogs/MemberList.h - src/dialogs/PlaceCall.h src/dialogs/PreviewUploadOverlay.h src/dialogs/RawMessage.h src/dialogs/ReCaptcha.h diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 2847d51d..ecacedba 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 import im.nheko 1.0 +import "./voip" Rectangle { color: colors.window @@ -10,6 +11,13 @@ Rectangle { Layout.preferredHeight: textInput.height Layout.minimumHeight: 40 + Component { + id: placeCallDialog + + PlaceCall { + } + } + RowLayout { id: inputBar @@ -28,7 +36,20 @@ Rectangle { Layout.topMargin: 8 Layout.bottomMargin: 8 Layout.leftMargin: 16 - onClicked: TimelineManager.timeline.input.callButton() + onClicked: { + if (TimelineManager.timeline) { + if (CallManager.haveCallInvite) { + return; + } + else if (CallManager.isOnCall) { + CallManager.hangUp(); + } + else { + var dialog = placeCallDialog.createObject(timelineRoot); + dialog.show(); + } + } + } } ImageButton { diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml new file mode 100644 index 00000000..7bf76ca6 --- /dev/null +++ b/resources/qml/voip/PlaceCall.qml @@ -0,0 +1,107 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.3 +import QtQuick.Dialogs 1.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 +import "../" + +ApplicationWindow { + + flags: Qt.Dialog + modality: Qt.ApplicationModal + palette: colors + width: columnLayout.implicitWidth + height: columnLayout.implicitHeight + + MessageDialog { + id: warningDialog + icon: StandardIcon.Warning + } + + ColumnLayout { + + id: columnLayout + spacing: 16 + + RowLayout { + + Layout.topMargin: 16 + Layout.leftMargin: 8 + + Label { + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: "Place a call to " + TimelineManager.timeline.roomName + "?" + } + + Item { + Layout.fillWidth: true + } + } + + RowLayout { + + id: rowLayout + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.bottomMargin: 16 + spacing: 16 + + function validateMic() { + if (CallManager.mics.length == 0) { + warningDialog.text = "No microphone found."; + warningDialog.open(); + return false; + } + else if (!CallManager.mics.includes(Settings.microphone)) { + warningDialog.text = "Unknown microphone: " + Settings.microphone; + warningDialog.open(); + return false; + } + return true; + } + + Avatar { + width: avatarSize + height: avatarSize + url: TimelineManager.timeline.roomAvatarUrl.replace("mxc://", "image://MxcImage/") + displayName: TimelineManager.timeline.roomName + } + + Button { + text: qsTr("Voice") + icon.source: "qrc:/icons/icons/ui/place-call.png" + onClicked: { + if (rowLayout.validateMic()) { + CallManager.sendInvite(TimelineManager.timeline.roomId(), false); + close(); + } + } + } + + Button { + visible: CallManager.cameras.length > 0 + text: qsTr("Video") + icon.source: "qrc:/icons/icons/ui/video-call.png" + onClicked: { + if (rowLayout.validateMic()) { + if (!CallManager.cameras.includes(Settings.camera)) { + warningDialog.text = "Unknown camera: " + Settings.camera; + warningDialog.open(); + return; + } + CallManager.sendInvite(TimelineManager.timeline.roomId(), true); + close(); + } + } + } + + Button { + palette: colors + text: qsTr("Cancel") + onClicked: { + close(); + } + } + } + } +} diff --git a/resources/res.qrc b/resources/res.qrc index 321fe12e..52157df0 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -159,6 +159,7 @@ qml/device-verification/Success.qml qml/voip/ActiveCallBar.qml qml/voip/CallInviteBar.qml + qml/voip/PlaceCall.qml qml/voip/VideoCall.qml diff --git a/src/CallManager.h b/src/CallManager.h index ad25fbd1..75768ee1 100644 --- a/src/CallManager.h +++ b/src/CallManager.h @@ -37,7 +37,6 @@ class CallManager : public QObject public: CallManager(QObject *); - void sendInvite(const QString &roomid, bool isVideo); bool haveCallInvite() const { return haveCallInvite_; } bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; } bool isVideo() const { return isVideo_; } @@ -52,6 +51,7 @@ public: void refreshTurnServer(); public slots: + void sendInvite(const QString &roomid, bool isVideo); void syncEvent(const mtx::events::collections::TimelineEvents &event); void toggleMicMute(); void toggleCameraView() { session_.toggleCameraView(); } diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 4e87349a..238c9362 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -47,7 +47,6 @@ #include "notifications/Manager.h" -#include "dialogs/PlaceCall.h" #include "dialogs/ReadReceipts.h" #include "popups/UserMentions.h" #include "timeline/TimelineViewManager.h" diff --git a/src/dialogs/PlaceCall.cpp b/src/dialogs/PlaceCall.cpp deleted file mode 100644 index 85a398a2..00000000 --- a/src/dialogs/PlaceCall.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include -#include -#include -#include -#include - -#include "ChatPage.h" -#include "Config.h" -#include "UserSettingsPage.h" -#include "Utils.h" -#include "WebRTCSession.h" -#include "dialogs/PlaceCall.h" -#include "ui/Avatar.h" - -namespace dialogs { - -PlaceCall::PlaceCall(const QString &callee, - const QString &displayName, - const QString &roomName, - const QString &avatarUrl, - QSharedPointer settings, - QWidget *parent) - : QWidget(parent) -{ - std::string errorMessage; - WebRTCSession *session = &WebRTCSession::instance(); - if (!session->havePlugins(false, &errorMessage)) { - emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); - emit close(); - return; - } - session->refreshDevices(); - microphones_ = session->getDeviceNames(false, settings->microphone().toStdString()); - if (microphones_.empty()) { - emit ChatPage::instance()->showNotification(tr("No microphone found.")); - emit close(); - return; - } - cameras_ = session->getDeviceNames(true, settings->camera().toStdString()); - - 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); - - auto buttonLayout = new QHBoxLayout; - buttonLayout->setSpacing(15); - buttonLayout->setMargin(0); - - QFont f; - f.setPointSizeF(f.pointSizeF()); - auto avatar = new Avatar(this, QFontMetrics(f).height() * 3); - if (!avatarUrl.isEmpty()) - avatar->setImage(avatarUrl); - else - avatar->setLetter(utils::firstChar(roomName)); - - voiceBtn_ = new QPushButton(tr("Voice"), this); - voiceBtn_->setIcon(QIcon(":/icons/icons/ui/place-call.png")); - voiceBtn_->setIconSize(QSize(iconSize_, iconSize_)); - voiceBtn_->setDefault(true); - - if (!cameras_.empty()) { - videoBtn_ = new QPushButton(tr("Video"), this); - videoBtn_->setIcon(QIcon(":/icons/icons/ui/video-call.png")); - videoBtn_->setIconSize(QSize(iconSize_, iconSize_)); - } - cancelBtn_ = new QPushButton(tr("Cancel"), this); - - buttonLayout->addWidget(avatar); - buttonLayout->addStretch(); - buttonLayout->addWidget(voiceBtn_); - if (videoBtn_) - buttonLayout->addWidget(videoBtn_); - buttonLayout->addWidget(cancelBtn_); - - QString name = displayName.isEmpty() ? callee : displayName; - QLabel *label = new QLabel(tr("Place a call to ") + name + "?", this); - - microphoneCombo_ = new QComboBox(this); - for (const auto &m : microphones_) - microphoneCombo_->addItem(QIcon(":/icons/icons/ui/microphone-unmute.png"), - QString::fromStdString(m)); - - if (videoBtn_) { - cameraCombo_ = new QComboBox(this); - for (const auto &c : cameras_) - cameraCombo_->addItem(QIcon(":/icons/icons/ui/video-call.png"), - QString::fromStdString(c)); - } - - layout->addWidget(label); - layout->addLayout(buttonLayout); - layout->addStretch(); - layout->addWidget(microphoneCombo_); - if (videoBtn_) - layout->addWidget(cameraCombo_); - - connect(voiceBtn_, &QPushButton::clicked, this, [this, settings]() { - settings->setMicrophone( - QString::fromStdString(microphones_[microphoneCombo_->currentIndex()])); - emit voice(); - emit close(); - }); - if (videoBtn_) - connect(videoBtn_, &QPushButton::clicked, this, [this, settings, session]() { - std::string error; - if (!session->havePlugins(true, &error)) { - emit ChatPage::instance()->showNotification( - QString::fromStdString(error)); - emit close(); - return; - } - settings->setMicrophone( - QString::fromStdString(microphones_[microphoneCombo_->currentIndex()])); - settings->setCamera( - QString::fromStdString(cameras_[cameraCombo_->currentIndex()])); - emit video(); - emit close(); - }); - connect(cancelBtn_, &QPushButton::clicked, this, [this]() { - emit cancel(); - emit close(); - }); -} - -} diff --git a/src/dialogs/PlaceCall.h b/src/dialogs/PlaceCall.h deleted file mode 100644 index e042258f..00000000 --- a/src/dialogs/PlaceCall.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -class QComboBox; -class QPushButton; -class QString; -class UserSettings; - -namespace dialogs { - -class PlaceCall : public QWidget -{ - Q_OBJECT - -public: - PlaceCall(const QString &callee, - const QString &displayName, - const QString &roomName, - const QString &avatarUrl, - QSharedPointer settings, - QWidget *parent = nullptr); - -signals: - void voice(); - void video(); - void cancel(); - -private: - const int iconSize_ = 18; - QPushButton *voiceBtn_ = nullptr; - QPushButton *videoBtn_ = nullptr; - QPushButton *cancelBtn_ = nullptr; - QComboBox *microphoneCombo_ = nullptr; - QComboBox *cameraCombo_ = nullptr; - std::vector microphones_; - std::vector cameras_; -}; - -} diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 78c8c6a3..3cddd613 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -13,7 +13,6 @@ #include #include "Cache.h" -#include "CallManager.h" #include "ChatPage.h" #include "CompletionProxyModel.h" #include "Logging.h" @@ -25,7 +24,6 @@ #include "UserSettingsPage.h" #include "UsersModel.h" #include "Utils.h" -#include "dialogs/PlaceCall.h" #include "dialogs/PreviewUploadOverlay.h" #include "emoji/EmojiModel.h" @@ -593,50 +591,6 @@ InputBar::showPreview(const QMimeData &source, QString path, const QStringList & }); } -void -InputBar::callButton() -{ - auto callManager_ = ChatPage::instance()->callManager(); - if (callManager_->haveCallInvite()) { - return; - } else if (callManager_->isOnCall()) { - callManager_->hangUp(); - } else { - auto current_room_ = room->roomId(); - if (auto roomInfo = cache::singleRoomInfo(current_room_.toStdString()); - roomInfo.member_count != 2) { - ChatPage::instance()->showNotification("Calls are limited to 1:1 rooms."); - } else { - std::vector members( - cache::getMembers(current_room_.toStdString())); - const RoomMember &callee = members.front().user_id == utils::localUser() - ? members.back() - : members.front(); - auto dialog = - new dialogs::PlaceCall(callee.user_id, - callee.display_name, - QString::fromStdString(roomInfo.name), - QString::fromStdString(roomInfo.avatar_url), - ChatPage::instance()->userSettings(), - MainWindow::instance()); - connect(dialog, - &dialogs::PlaceCall::voice, - callManager_, - [callManager_, current_room_]() { - callManager_->sendInvite(current_room_, false); - }); - connect(dialog, - &dialogs::PlaceCall::video, - callManager_, - [callManager_, current_room_]() { - callManager_->sendInvite(current_room_, true); - }); - utils::centerWidget(dialog, MainWindow::instance()); - dialog->show(); - } - } -} - void InputBar::startTyping() { diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index 89ca34fe..c729a6fc 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -41,7 +41,6 @@ public slots: void updateState(int selectionStart, int selectionEnd, int cursorPosition, QString text); void openFileSelection(); bool uploading() const { return uploading_; } - void callButton(); void message(QString body); QObject *completerFor(QString completerName); From d315d02ee6ee77576d37eef279cf635fa31ed64a Mon Sep 17 00:00:00 2001 From: trilene Date: Sat, 19 Dec 2020 09:32:20 -0500 Subject: [PATCH 20/35] Use Layout margins --- resources/qml/voip/ActiveCallBar.qml | 4 +++- resources/qml/voip/CallInviteBar.qml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml index a1ddd853..e0853808 100644 --- a/resources/qml/voip/ActiveCallBar.qml +++ b/resources/qml/voip/ActiveCallBar.qml @@ -35,11 +35,13 @@ Rectangle { } Label { + Layout.leftMargin: 8 font.pointSize: fontMetrics.font.pointSize * 1.1 - text: " " + CallManager.callParty + " " + text: CallManager.callParty } Image { + Layout.leftMargin: 4 Layout.preferredWidth: 24 Layout.preferredHeight: 24 source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index 6d4d2ac0..61a3f0ec 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -32,11 +32,13 @@ Rectangle { } Label { + Layout.leftMargin: 8 font.pointSize: fontMetrics.font.pointSize * 1.1 - text: " " + CallManager.callParty + " " + text: CallManager.callParty } Image { + Layout.leftMargin: 4 Layout.preferredWidth: 24 Layout.preferredHeight: 24 source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" From 055c6f724833a76131051d23400e5d7c57245fb2 Mon Sep 17 00:00:00 2001 From: trilene Date: Sat, 19 Dec 2020 10:49:13 -0500 Subject: [PATCH 21/35] Add device combos to PlaceCall dialog --- resources/qml/voip/PlaceCall.qml | 63 ++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index 7bf76ca6..7a839e8d 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -19,8 +19,8 @@ ApplicationWindow { } ColumnLayout { - id: columnLayout + spacing: 16 RowLayout { @@ -30,7 +30,7 @@ ApplicationWindow { Label { font.pointSize: fontMetrics.font.pointSize * 1.1 - text: "Place a call to " + TimelineManager.timeline.roomName + "?" + text: qsTr("Place a call to ") + TimelineManager.timeline.roomName + "?" } Item { @@ -39,11 +39,10 @@ ApplicationWindow { } RowLayout { + id: buttonLayout - id: rowLayout Layout.leftMargin: 8 Layout.rightMargin: 8 - Layout.bottomMargin: 16 spacing: 16 function validateMic() { @@ -52,11 +51,6 @@ ApplicationWindow { warningDialog.open(); return false; } - else if (!CallManager.mics.includes(Settings.microphone)) { - warningDialog.text = "Unknown microphone: " + Settings.microphone; - warningDialog.open(); - return false; - } return true; } @@ -71,7 +65,8 @@ ApplicationWindow { text: qsTr("Voice") icon.source: "qrc:/icons/icons/ui/place-call.png" onClicked: { - if (rowLayout.validateMic()) { + if (buttonLayout.validateMic()) { + Settings.microphone = micCombo.currentText CallManager.sendInvite(TimelineManager.timeline.roomId(), false); close(); } @@ -83,12 +78,9 @@ ApplicationWindow { text: qsTr("Video") icon.source: "qrc:/icons/icons/ui/video-call.png" onClicked: { - if (rowLayout.validateMic()) { - if (!CallManager.cameras.includes(Settings.camera)) { - warningDialog.text = "Unknown camera: " + Settings.camera; - warningDialog.open(); - return; - } + if (buttonLayout.validateMic()) { + Settings.microphone = micCombo.currentText + Settings.camera = cameraCombo.currentText CallManager.sendInvite(TimelineManager.timeline.roomId(), true); close(); } @@ -103,5 +95,44 @@ ApplicationWindow { } } } + + RowLayout { + + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.bottomMargin: cameraCombo.visible ? 0 : 16 + + Image { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + source: "qrc:/icons/icons/ui/microphone-unmute.png" + } + + ComboBox { + id: micCombo + Layout.fillWidth: true + model: CallManager.mics + } + } + + RowLayout { + + visible: CallManager.cameras.length > 0 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.bottomMargin: 16 + + Image { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + source: "qrc:/icons/icons/ui/video-call.png" + } + + ComboBox { + id: cameraCombo + Layout.fillWidth: true + model: CallManager.cameras + } + } } } From 87d2074c8192e5321f76525c55bc4e44dd1bc790 Mon Sep 17 00:00:00 2001 From: trilene Date: Sun, 20 Dec 2020 09:37:22 -0500 Subject: [PATCH 22/35] Add devices dialog to CallInviteBar --- resources/qml/voip/CallDevices.qml | 91 ++++++++++++++++++++++++++++ resources/qml/voip/CallInviteBar.qml | 27 ++++++++- resources/res.qrc | 1 + 3 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 resources/qml/voip/CallDevices.qml diff --git a/resources/qml/voip/CallDevices.qml b/resources/qml/voip/CallDevices.qml new file mode 100644 index 00000000..ee3503ca --- /dev/null +++ b/resources/qml/voip/CallDevices.qml @@ -0,0 +1,91 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.3 +import QtQuick.Dialogs 1.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 +import "../" + +ApplicationWindow { + + flags: Qt.Dialog + modality: Qt.ApplicationModal + palette: colors + width: columnLayout.implicitWidth + height: columnLayout.implicitHeight + + ColumnLayout { + id: columnLayout + + spacing: 16 + + ColumnLayout { + spacing: 8 + + RowLayout { + + Layout.topMargin: 8 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + + Image { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + source: "qrc:/icons/icons/ui/microphone-unmute.png" + } + + ComboBox { + id: micCombo + Layout.fillWidth: true + model: CallManager.mics + } + } + + RowLayout { + + visible: CallManager.cameras.length > 0 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + + Image { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + source: "qrc:/icons/icons/ui/video-call.png" + } + + ComboBox { + id: cameraCombo + Layout.fillWidth: true + model: CallManager.cameras + } + } + } + + RowLayout { + + Layout.rightMargin: 8 + Layout.bottomMargin: 8 + + Item { + implicitWidth: 128 + } + + Button { + text: qsTr("Ok") + onClicked: { + Settings.microphone = micCombo.currentText + if (cameraCombo.visible) { + Settings.camera = cameraCombo.currentText + } + close(); + } + } + + Button { + text: qsTr("Cancel") + onClicked: { + close(); + } + } + } + } +} diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index 61a3f0ec..e22ee645 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -16,6 +16,13 @@ Rectangle { icon: StandardIcon.Warning } + Component { + id: devicesDialog + + CallDevices { + } + } + RowLayout { id: rowLayout @@ -53,6 +60,24 @@ Rectangle { Layout.fillWidth: true } + ImageButton { + width: 24 + height: 24 + buttonTextColor: "#000000" + image: ":/icons/icons/ui/settings.png" + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: "Devices" + onClicked: { + var dialog = devicesDialog.createObject(timelineRoot); + dialog.show(); + } + } + + Item { + implicitWidth: 8 + } + Button { icon.source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" palette: colors @@ -78,7 +103,7 @@ Rectangle { } Item { - implicitWidth: 8 + implicitWidth: 4 } Button { diff --git a/resources/res.qrc b/resources/res.qrc index 52157df0..ca333978 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -158,6 +158,7 @@ qml/device-verification/Failed.qml qml/device-verification/Success.qml qml/voip/ActiveCallBar.qml + qml/voip/CallDevices.qml qml/voip/CallInviteBar.qml qml/voip/PlaceCall.qml qml/voip/VideoCall.qml From 6427687d208bdf2b303d1444d978d7f83fcc2be0 Mon Sep 17 00:00:00 2001 From: trilene Date: Sun, 20 Dec 2020 10:14:55 -0500 Subject: [PATCH 23/35] Add missing translation marks --- resources/qml/voip/ActiveCallBar.qml | 8 ++++---- resources/qml/voip/CallInviteBar.qml | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml index e0853808..1683df6a 100644 --- a/resources/qml/voip/ActiveCallBar.qml +++ b/resources/qml/voip/ActiveCallBar.qml @@ -61,7 +61,7 @@ Rectangle { PropertyChanges { target: callStateLabel - text: "Calling..." + text: qsTr("Calling...") } }, @@ -71,7 +71,7 @@ Rectangle { PropertyChanges { target: callStateLabel - text: "Connecting..." + text: qsTr("Connecting...") } }, @@ -81,7 +81,7 @@ Rectangle { PropertyChanges { target: callStateLabel - text: "Connecting..." + text: qsTr("Connecting...") } }, @@ -157,7 +157,7 @@ Rectangle { image: ":/icons/icons/ui/toggle-camera-view.png" hoverEnabled: true ToolTip.visible: hovered - ToolTip.text: "Toggle camera view" + ToolTip.text: qsTr("Toggle camera view") onClicked: CallManager.toggleCameraView() } diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index e22ee645..43aa93b8 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -53,7 +53,7 @@ Rectangle { Label { font.pointSize: fontMetrics.font.pointSize * 1.1 - text: CallManager.isVideo ? "Video Call" : "Voice Call" + text: CallManager.isVideo ? qsTr("Video Call") : qsTr("Voice Call") } Item { @@ -67,7 +67,7 @@ Rectangle { image: ":/icons/icons/ui/settings.png" hoverEnabled: true ToolTip.visible: hovered - ToolTip.text: "Devices" + ToolTip.text: qsTr("Devices") onClicked: { var dialog = devicesDialog.createObject(timelineRoot); dialog.show(); @@ -84,17 +84,17 @@ Rectangle { text: qsTr("Accept") onClicked: { if (CallManager.mics.length == 0) { - warningDialog.text = "No microphone found."; + warningDialog.text = qsTr("No microphone found."); warningDialog.open(); return; } else if (!CallManager.mics.includes(Settings.microphone)) { - warningDialog.text = "Unknown microphone: " + Settings.microphone; + warningDialog.text = qsTr("Unknown microphone: ") + Settings.microphone; warningDialog.open(); return; } if (CallManager.isVideo && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) { - warningDialog.text = "Unknown camera: " + Settings.camera; + warningDialog.text = qsTr("Unknown camera: ") + Settings.camera; warningDialog.open(); return; } From 13a280df136cf4db28796175cd9dd8546acce79a Mon Sep 17 00:00:00 2001 From: trilene Date: Sun, 20 Dec 2020 10:33:22 -0500 Subject: [PATCH 24/35] Finesse PlaceCall dialog --- resources/qml/voip/PlaceCall.qml | 70 +++++++++++++++++--------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index 7a839e8d..c047e625 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -43,11 +43,10 @@ ApplicationWindow { Layout.leftMargin: 8 Layout.rightMargin: 8 - spacing: 16 function validateMic() { if (CallManager.mics.length == 0) { - warningDialog.text = "No microphone found."; + warningDialog.text = qsTr("No microphone found."); warningDialog.open(); return false; } @@ -61,6 +60,10 @@ ApplicationWindow { displayName: TimelineManager.timeline.roomName } + Item { + implicitWidth: cameraCombo.visible ? 16 : 64 + } + Button { text: qsTr("Voice") icon.source: "qrc:/icons/icons/ui/place-call.png" @@ -88,7 +91,6 @@ ApplicationWindow { } Button { - palette: colors text: qsTr("Cancel") onClicked: { close(); @@ -96,42 +98,46 @@ ApplicationWindow { } } - RowLayout { + ColumnLayout { + spacing: 8 - Layout.leftMargin: 8 - Layout.rightMargin: 8 - Layout.bottomMargin: cameraCombo.visible ? 0 : 16 + RowLayout { - Image { - Layout.preferredWidth: 22 - Layout.preferredHeight: 22 - source: "qrc:/icons/icons/ui/microphone-unmute.png" + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.bottomMargin: cameraCombo.visible ? 0 : 16 + + Image { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + source: "qrc:/icons/icons/ui/microphone-unmute.png" + } + + ComboBox { + id: micCombo + Layout.fillWidth: true + model: CallManager.mics + } } - ComboBox { - id: micCombo - Layout.fillWidth: true - model: CallManager.mics - } - } + RowLayout { - RowLayout { + visible: CallManager.cameras.length > 0 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.bottomMargin: 16 - visible: CallManager.cameras.length > 0 - Layout.leftMargin: 8 - Layout.rightMargin: 8 - Layout.bottomMargin: 16 + Image { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + source: "qrc:/icons/icons/ui/video-call.png" + } - Image { - Layout.preferredWidth: 22 - Layout.preferredHeight: 22 - source: "qrc:/icons/icons/ui/video-call.png" - } - - ComboBox { - id: cameraCombo - Layout.fillWidth: true - model: CallManager.cameras + ComboBox { + id: cameraCombo + Layout.fillWidth: true + model: CallManager.cameras + } } } } From 1c4a86e502a1e57cf359ff8528fadf203c6ce027 Mon Sep 17 00:00:00 2001 From: trilene Date: Sun, 20 Dec 2020 17:12:42 -0500 Subject: [PATCH 25/35] Set Label text color explicitly --- resources/qml/voip/ActiveCallBar.qml | 2 ++ resources/qml/voip/CallInviteBar.qml | 2 ++ resources/qml/voip/PlaceCall.qml | 1 + 3 files changed, 5 insertions(+) diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml index 1683df6a..86fe37f6 100644 --- a/resources/qml/voip/ActiveCallBar.qml +++ b/resources/qml/voip/ActiveCallBar.qml @@ -38,6 +38,7 @@ Rectangle { Layout.leftMargin: 8 font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.callParty + color: colors.windowText } Image { @@ -51,6 +52,7 @@ Rectangle { id: callStateLabel font.pointSize: fontMetrics.font.pointSize * 1.1 + color: colors.windowText } Item { diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index 43aa93b8..3b40d394 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -42,6 +42,7 @@ Rectangle { Layout.leftMargin: 8 font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.callParty + color: windowText } Image { @@ -54,6 +55,7 @@ Rectangle { Label { font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.isVideo ? qsTr("Video Call") : qsTr("Voice Call") + color: windowText } Item { diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index c047e625..99b82046 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -31,6 +31,7 @@ ApplicationWindow { Label { font.pointSize: fontMetrics.font.pointSize * 1.1 text: qsTr("Place a call to ") + TimelineManager.timeline.roomName + "?" + color: windowText } Item { From 4123e6aff1ffa756c17e89b0930a3bcabcbb8104 Mon Sep 17 00:00:00 2001 From: trilene Date: Sun, 20 Dec 2020 17:19:24 -0500 Subject: [PATCH 26/35] Fix previous commit --- resources/qml/voip/CallInviteBar.qml | 4 ++-- resources/qml/voip/PlaceCall.qml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index 3b40d394..93ff1042 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -42,7 +42,7 @@ Rectangle { Layout.leftMargin: 8 font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.callParty - color: windowText + color: colors.windowText } Image { @@ -55,7 +55,7 @@ Rectangle { Label { font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.isVideo ? qsTr("Video Call") : qsTr("Voice Call") - color: windowText + color: colors.windowText } Item { diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index 99b82046..4e29c1ae 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -31,7 +31,7 @@ ApplicationWindow { Label { font.pointSize: fontMetrics.font.pointSize * 1.1 text: qsTr("Place a call to ") + TimelineManager.timeline.roomName + "?" - color: windowText + color: colors.windowText } Item { From 2984d71971fb49e47ff1485ad918e9d520404e4b Mon Sep 17 00:00:00 2001 From: trilene Date: Wed, 30 Dec 2020 15:03:07 -0500 Subject: [PATCH 27/35] Fix Qml control colors --- resources/qml/MessageInput.qml | 5 +- resources/qml/voip/ActiveCallBar.qml | 18 ++----- resources/qml/voip/CallDevices.qml | 77 ++++++++++++++-------------- resources/qml/voip/CallInviteBar.qml | 69 +++++++++++++------------ resources/qml/voip/DeviceError.qml | 31 +++++++++++ resources/qml/voip/PlaceCall.qml | 61 +++++++++++++--------- resources/res.qrc | 1 + src/UserSettingsPage.cpp | 4 +- 8 files changed, 155 insertions(+), 111 deletions(-) create mode 100644 resources/qml/voip/DeviceError.qml diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index ecacedba..71c61697 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -1,9 +1,9 @@ +import "./voip" import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 import im.nheko 1.0 -import "./voip" Rectangle { color: colors.window @@ -26,6 +26,7 @@ Rectangle { ImageButton { visible: CallManager.callsSupported + opacity: CallManager.haveCallInvite ? 0.3 : 1.0 Layout.alignment: Qt.AlignBottom hoverEnabled: true width: 22 @@ -46,7 +47,7 @@ Rectangle { } else { var dialog = placeCallDialog.createObject(timelineRoot); - dialog.show(); + dialog.open(); } } } diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml index 86fe37f6..0e932e13 100644 --- a/resources/qml/voip/ActiveCallBar.qml +++ b/resources/qml/voip/ActiveCallBar.qml @@ -1,8 +1,8 @@ +import "../" import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import im.nheko 1.0 -import "../" Rectangle { @@ -38,7 +38,7 @@ Rectangle { Layout.leftMargin: 8 font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.callParty - color: colors.windowText + color: "#000000" } Image { @@ -52,7 +52,7 @@ Rectangle { id: callStateLabel font.pointSize: fontMetrics.font.pointSize * 1.1 - color: colors.windowText + color: "#000000" } Item { @@ -163,11 +163,9 @@ Rectangle { onClicked: CallManager.toggleCameraView() } - Item { - implicitWidth: 8 - } - ImageButton { + Layout.leftMargin: 8 + Layout.rightMargin: 16 width: 24 height: 24 buttonTextColor: "#000000" @@ -177,11 +175,5 @@ Rectangle { ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") onClicked: CallManager.toggleMicMute() } - - Item { - implicitWidth: 16 - } - } - } diff --git a/resources/qml/voip/CallDevices.qml b/resources/qml/voip/CallDevices.qml index ee3503ca..b07412c7 100644 --- a/resources/qml/voip/CallDevices.qml +++ b/resources/qml/voip/CallDevices.qml @@ -1,31 +1,44 @@ -import QtQuick 2.3 +import QtQuick 2.9 import QtQuick.Controls 2.3 -import QtQuick.Dialogs 1.3 import QtQuick.Layouts 1.2 import im.nheko 1.0 -import "../" -ApplicationWindow { +Popup { - flags: Qt.Dialog - modality: Qt.ApplicationModal - palette: colors - width: columnLayout.implicitWidth - height: columnLayout.implicitHeight + modal: true + anchors.centerIn: parent + background: Rectangle { + color: colors.window + border.color: colors.windowText + } + + // palette: colors + // colorize controls correctly + palette.base: colors.base + palette.brightText: colors.brightText + palette.button: colors.button + palette.buttonText: colors.buttonText + palette.dark: colors.dark + palette.highlight: colors.highlight + palette.highlightedText: colors.highlightedText + palette.light: colors.light + palette.mid: colors.mid + palette.text: colors.text + palette.window: colors.window + palette.windowText: colors.windowText ColumnLayout { - id: columnLayout spacing: 16 ColumnLayout { spacing: 8 - RowLayout { + Layout.topMargin: 8 + Layout.leftMargin: 8 + Layout.rightMargin: 8 - Layout.topMargin: 8 - Layout.leftMargin: 8 - Layout.rightMargin: 8 + RowLayout { Image { Layout.preferredWidth: 22 @@ -42,9 +55,7 @@ ApplicationWindow { RowLayout { - visible: CallManager.cameras.length > 0 - Layout.leftMargin: 8 - Layout.rightMargin: 8 + visible: CallManager.isVideo && CallManager.cameras.length > 0 Image { Layout.preferredWidth: 22 @@ -60,31 +71,21 @@ ApplicationWindow { } } - RowLayout { + DialogButtonBox { - Layout.rightMargin: 8 - Layout.bottomMargin: 8 + Layout.leftMargin: 128 + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - Item { - implicitWidth: 128 + onAccepted: { + Settings.microphone = micCombo.currentText + if (cameraCombo.visible) { + Settings.camera = cameraCombo.currentText + } + close(); } - Button { - text: qsTr("Ok") - onClicked: { - Settings.microphone = micCombo.currentText - if (cameraCombo.visible) { - Settings.camera = cameraCombo.currentText - } - close(); - } - } - - Button { - text: qsTr("Cancel") - onClicked: { - close(); - } + onRejected: { + close(); } } } diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index 93ff1042..8c3a8f6a 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -1,9 +1,8 @@ +import "../" import QtQuick 2.9 import QtQuick.Controls 2.3 -import QtQuick.Dialogs 1.3 import QtQuick.Layouts 1.2 import im.nheko 1.0 -import "../" Rectangle { @@ -11,15 +10,15 @@ Rectangle { color: "#2ECC71" implicitHeight: visible ? rowLayout.height + 8 : 0 - MessageDialog { - id: warningDialog - icon: StandardIcon.Warning + Component { + id: devicesDialog + CallDevices { + } } Component { - id: devicesDialog - - CallDevices { + id: deviceError + DeviceError { } } @@ -42,7 +41,7 @@ Rectangle { Layout.leftMargin: 8 font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.callParty - color: colors.windowText + color: "#000000" } Image { @@ -55,7 +54,7 @@ Rectangle { Label { font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.isVideo ? qsTr("Video Call") : qsTr("Voice Call") - color: colors.windowText + color: "#000000" } Item { @@ -63,8 +62,9 @@ Rectangle { } ImageButton { - width: 24 - height: 24 + Layout.rightMargin: 16 + width: 20 + height: 20 buttonTextColor: "#000000" image: ":/icons/icons/ui/settings.png" hoverEnabled: true @@ -72,53 +72,56 @@ Rectangle { ToolTip.text: qsTr("Devices") onClicked: { var dialog = devicesDialog.createObject(timelineRoot); - dialog.show(); + dialog.open(); } } - Item { - implicitWidth: 8 - } - Button { + Layout.rightMargin: 4 icon.source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" - palette: colors text: qsTr("Accept") + palette.button: colors.button + palette.buttonText: colors.buttonText + onClicked: { if (CallManager.mics.length == 0) { - warningDialog.text = qsTr("No microphone found."); - warningDialog.open(); + var dialog = deviceError.createObject(timelineRoot, { + "errorString": qsTr("No microphone found."), + "iconSource": "qrc:/icons/icons/ui/place-call.png" + }); + dialog.open(); return; } else if (!CallManager.mics.includes(Settings.microphone)) { - warningDialog.text = qsTr("Unknown microphone: ") + Settings.microphone; - warningDialog.open(); + var dialog = deviceError.createObject(timelineRoot, { + "errorString": qsTr("Unknown microphone: ") + Settings.microphone, + "iconSource": "qrc:/icons/icons/ui/place-call.png" + }); + dialog.open(); return; } if (CallManager.isVideo && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) { - warningDialog.text = qsTr("Unknown camera: ") + Settings.camera; - warningDialog.open(); + var dialog = deviceError.createObject(timelineRoot, { + "errorString": qsTr("Unknown camera: ") + Settings.camera, + "iconSource": "qrc:/icons/icons/ui/video-call.png" + }); + dialog.open(); return; } CallManager.acceptInvite(); } } - Item { - implicitWidth: 4 - } - Button { + Layout.rightMargin: 16 icon.source: "qrc:/icons/icons/ui/end-call.png" - palette: colors text: qsTr("Decline") + palette.button: colors.button + palette.buttonText: colors.buttonText + onClicked: { CallManager.hangUp(); } } - - Item { - implicitWidth: 16 - } } } diff --git a/resources/qml/voip/DeviceError.qml b/resources/qml/voip/DeviceError.qml new file mode 100644 index 00000000..c88c7faa --- /dev/null +++ b/resources/qml/voip/DeviceError.qml @@ -0,0 +1,31 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +Popup { + + property string errorString + property var iconSource + + modal: true + anchors.centerIn: parent + background: Rectangle { + color: colors.window + border.color: colors.windowText + } + + RowLayout { + + Image { + Layout.preferredWidth: 16 + Layout.preferredHeight: 16 + source: iconSource + } + + Label { + text: errorString + color: colors.windowText + } + } +} diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index 4e29c1ae..8dc7d781 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -1,23 +1,39 @@ -import QtQuick 2.3 +import "../" +import QtQuick 2.9 import QtQuick.Controls 2.3 -import QtQuick.Dialogs 1.3 import QtQuick.Layouts 1.2 import im.nheko 1.0 -import "../" -ApplicationWindow { +Popup { - flags: Qt.Dialog - modality: Qt.ApplicationModal - palette: colors - width: columnLayout.implicitWidth - height: columnLayout.implicitHeight - - MessageDialog { - id: warningDialog - icon: StandardIcon.Warning + modal: true + anchors.centerIn: parent + background: Rectangle { + color: colors.window + border.color: colors.windowText } + Component { + id: deviceError + DeviceError { + } + } + + // palette: colors + // colorize controls correctly + palette.base: colors.base + palette.brightText: colors.brightText + palette.button: colors.button + palette.buttonText: colors.buttonText + palette.dark: colors.dark + palette.highlight: colors.highlight + palette.highlightedText: colors.highlightedText + palette.light: colors.light + palette.mid: colors.mid + palette.text: colors.text + palette.window: colors.window + palette.windowText: colors.windowText + ColumnLayout { id: columnLayout @@ -25,11 +41,10 @@ ApplicationWindow { RowLayout { - Layout.topMargin: 16 + Layout.topMargin: 8 Layout.leftMargin: 8 Label { - font.pointSize: fontMetrics.font.pointSize * 1.1 text: qsTr("Place a call to ") + TimelineManager.timeline.roomName + "?" color: colors.windowText } @@ -47,24 +62,24 @@ ApplicationWindow { function validateMic() { if (CallManager.mics.length == 0) { - warningDialog.text = qsTr("No microphone found."); - warningDialog.open(); + var dialog = deviceError.createObject(timelineRoot, { + "errorString": qsTr("No microphone found."), + "iconSource": "qrc:/icons/icons/ui/place-call.png" + }); + dialog.open(); return false; } return true; } Avatar { + Layout.rightMargin: cameraCombo.visible ? 16 : 64 width: avatarSize height: avatarSize url: TimelineManager.timeline.roomAvatarUrl.replace("mxc://", "image://MxcImage/") displayName: TimelineManager.timeline.roomName } - Item { - implicitWidth: cameraCombo.visible ? 16 : 64 - } - Button { text: qsTr("Voice") icon.source: "qrc:/icons/icons/ui/place-call.png" @@ -106,7 +121,7 @@ ApplicationWindow { Layout.leftMargin: 8 Layout.rightMargin: 8 - Layout.bottomMargin: cameraCombo.visible ? 0 : 16 + Layout.bottomMargin: cameraCombo.visible ? 0 : 8 Image { Layout.preferredWidth: 22 @@ -126,7 +141,7 @@ ApplicationWindow { visible: CallManager.cameras.length > 0 Layout.leftMargin: 8 Layout.rightMargin: 8 - Layout.bottomMargin: 16 + Layout.bottomMargin: 8 Image { Layout.preferredWidth: 22 diff --git a/resources/res.qrc b/resources/res.qrc index ca333978..bceeb298 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -160,6 +160,7 @@ qml/voip/ActiveCallBar.qml qml/voip/CallDevices.qml qml/voip/CallInviteBar.qml + qml/voip/DeviceError.qml qml/voip/PlaceCall.qml qml/voip/VideoCall.qml diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 7c7ef9ab..f133c87d 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -464,7 +464,7 @@ UserSettings::applyTheme() stylefile.setFileName(":/styles/styles/nheko.qss"); QPalette lightActive( /*windowText*/ QColor("#333"), - /*button*/ QColor("#333"), + /*button*/ QColor("white"), /*light*/ QColor(0xef, 0xef, 0xef), /*dark*/ QColor(110, 110, 110), /*mid*/ QColor(220, 220, 220), @@ -477,7 +477,7 @@ UserSettings::applyTheme() lightActive.setColor(QPalette::ToolTipBase, lightActive.base().color()); lightActive.setColor(QPalette::ToolTipText, lightActive.text().color()); lightActive.setColor(QPalette::Link, QColor("#0077b5")); - lightActive.setColor(QPalette::ButtonText, QColor("#495057")); + lightActive.setColor(QPalette::ButtonText, QColor("#333")); QApplication::setPalette(lightActive); } else if (this->theme() == "dark") { stylefile.setFileName(":/styles/styles/nheko-dark.qss"); From 9bbade37dec0ba98be0e9b20f7a6f45cff59a9b0 Mon Sep 17 00:00:00 2001 From: trilene Date: Fri, 1 Jan 2021 08:46:08 -0500 Subject: [PATCH 28/35] Fix call answered on another device --- src/CallManager.cpp | 13 ++++++++----- src/timeline/TimelineModel.cpp | 7 ++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/CallManager.cpp b/src/CallManager.cpp index 9864b203..f725d49f 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp @@ -351,12 +351,15 @@ CallManager::handleEvent(const RoomEvent &callAnswerEvent) callAnswerEvent.content.call_id, callAnswerEvent.sender); - if (!isOnCall() && callAnswerEvent.sender == utils::localUser().toStdString() && + if (callAnswerEvent.sender == utils::localUser().toStdString() && callid_ == callAnswerEvent.content.call_id) { - emit ChatPage::instance()->showNotification("Call answered on another device."); - stopRingtone(); - haveCallInvite_ = false; - emit newInviteState(); + if (!isOnCall()) { + emit ChatPage::instance()->showNotification( + "Call answered on another device."); + stopRingtone(); + haveCallInvite_ = false; + emit newInviteState(); + } return; } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index adef886d..2b5b5794 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -613,8 +613,13 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline) std::visit( [this](auto &event) { event.room_id = room_id_.toStdString(); - if (event.sender != http::client()->user_id().to_string()) + if constexpr (std::is_same_v, + RoomEvent>) emit newCallEvent(event); + else { + if (event.sender != http::client()->user_id().to_string()) + emit newCallEvent(event); + } }, e); else if (std::holds_alternative>(e)) From 2bd8a386e29e5abdf65164d6dd2fc3213de1f878 Mon Sep 17 00:00:00 2001 From: trilene Date: Wed, 6 Jan 2021 17:15:43 -0500 Subject: [PATCH 29/35] Color and icon button spacing fixes --- resources/qml/voip/CallDevices.qml | 19 +++---------------- resources/qml/voip/CallInviteBar.qml | 16 +++++++--------- resources/qml/voip/DeviceError.qml | 4 ++-- resources/qml/voip/PlaceCall.qml | 26 ++++++-------------------- 4 files changed, 18 insertions(+), 47 deletions(-) diff --git a/resources/qml/voip/CallDevices.qml b/resources/qml/voip/CallDevices.qml index b07412c7..f0847b14 100644 --- a/resources/qml/voip/CallDevices.qml +++ b/resources/qml/voip/CallDevices.qml @@ -12,20 +12,7 @@ Popup { border.color: colors.windowText } - // palette: colors - // colorize controls correctly - palette.base: colors.base - palette.brightText: colors.brightText - palette.button: colors.button - palette.buttonText: colors.buttonText - palette.dark: colors.dark - palette.highlight: colors.highlight - palette.highlightedText: colors.highlightedText - palette.light: colors.light - palette.mid: colors.mid - palette.text: colors.text - palette.window: colors.window - palette.windowText: colors.windowText + palette: colors ColumnLayout { @@ -43,7 +30,7 @@ Popup { Image { Layout.preferredWidth: 22 Layout.preferredHeight: 22 - source: "qrc:/icons/icons/ui/microphone-unmute.png" + source: "image://colorimage/:/icons/icons/ui/microphone-unmute.png?" + colors.windowText } ComboBox { @@ -60,7 +47,7 @@ Popup { Image { Layout.preferredWidth: 22 Layout.preferredHeight: 22 - source: "qrc:/icons/icons/ui/video-call.png" + source: "image://colorimage/:/icons/icons/ui/video-call.png?" + colors.windowText } ComboBox { diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index 8c3a8f6a..58b89ed3 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -79,15 +79,14 @@ Rectangle { Button { Layout.rightMargin: 4 icon.source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" - text: qsTr("Accept") - palette.button: colors.button - palette.buttonText: colors.buttonText + text: qsTr(" Accept ") + palette: colors onClicked: { if (CallManager.mics.length == 0) { var dialog = deviceError.createObject(timelineRoot, { "errorString": qsTr("No microphone found."), - "iconSource": "qrc:/icons/icons/ui/place-call.png" + "image": ":/icons/icons/ui/place-call.png" }); dialog.open(); return; @@ -95,7 +94,7 @@ Rectangle { else if (!CallManager.mics.includes(Settings.microphone)) { var dialog = deviceError.createObject(timelineRoot, { "errorString": qsTr("Unknown microphone: ") + Settings.microphone, - "iconSource": "qrc:/icons/icons/ui/place-call.png" + "image": ":/icons/icons/ui/place-call.png" }); dialog.open(); return; @@ -103,7 +102,7 @@ Rectangle { if (CallManager.isVideo && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) { var dialog = deviceError.createObject(timelineRoot, { "errorString": qsTr("Unknown camera: ") + Settings.camera, - "iconSource": "qrc:/icons/icons/ui/video-call.png" + "image": ":/icons/icons/ui/video-call.png" }); dialog.open(); return; @@ -115,9 +114,8 @@ Rectangle { Button { Layout.rightMargin: 16 icon.source: "qrc:/icons/icons/ui/end-call.png" - text: qsTr("Decline") - palette.button: colors.button - palette.buttonText: colors.buttonText + text: qsTr(" Decline ") + palette: colors onClicked: { CallManager.hangUp(); diff --git a/resources/qml/voip/DeviceError.qml b/resources/qml/voip/DeviceError.qml index c88c7faa..a6411b95 100644 --- a/resources/qml/voip/DeviceError.qml +++ b/resources/qml/voip/DeviceError.qml @@ -6,7 +6,7 @@ import im.nheko 1.0 Popup { property string errorString - property var iconSource + property var image modal: true anchors.centerIn: parent @@ -20,7 +20,7 @@ Popup { Image { Layout.preferredWidth: 16 Layout.preferredHeight: 16 - source: iconSource + source: "image://colorimage/" + image + "?" + colors.windowText } Label { diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index 8dc7d781..95383d95 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -8,6 +8,7 @@ Popup { modal: true anchors.centerIn: parent + palette: colors background: Rectangle { color: colors.window border.color: colors.windowText @@ -19,21 +20,6 @@ Popup { } } - // palette: colors - // colorize controls correctly - palette.base: colors.base - palette.brightText: colors.brightText - palette.button: colors.button - palette.buttonText: colors.buttonText - palette.dark: colors.dark - palette.highlight: colors.highlight - palette.highlightedText: colors.highlightedText - palette.light: colors.light - palette.mid: colors.mid - palette.text: colors.text - palette.window: colors.window - palette.windowText: colors.windowText - ColumnLayout { id: columnLayout @@ -64,7 +50,7 @@ Popup { if (CallManager.mics.length == 0) { var dialog = deviceError.createObject(timelineRoot, { "errorString": qsTr("No microphone found."), - "iconSource": "qrc:/icons/icons/ui/place-call.png" + "image": ":/icons/icons/ui/place-call.png" }); dialog.open(); return false; @@ -81,7 +67,7 @@ Popup { } Button { - text: qsTr("Voice") + text: qsTr(" Voice ") icon.source: "qrc:/icons/icons/ui/place-call.png" onClicked: { if (buttonLayout.validateMic()) { @@ -94,7 +80,7 @@ Popup { Button { visible: CallManager.cameras.length > 0 - text: qsTr("Video") + text: qsTr(" Video ") icon.source: "qrc:/icons/icons/ui/video-call.png" onClicked: { if (buttonLayout.validateMic()) { @@ -126,7 +112,7 @@ Popup { Image { Layout.preferredWidth: 22 Layout.preferredHeight: 22 - source: "qrc:/icons/icons/ui/microphone-unmute.png" + source: "image://colorimage/:/icons/icons/ui/microphone-unmute.png?" + colors.windowText } ComboBox { @@ -146,7 +132,7 @@ Popup { Image { Layout.preferredWidth: 22 Layout.preferredHeight: 22 - source: "qrc:/icons/icons/ui/video-call.png" + source: "image://colorimage/:/icons/icons/ui/video-call.png?" + colors.windowText } ComboBox { From cf8a47503f8f74a04bf0bb95b95ca189a6c6a19c Mon Sep 17 00:00:00 2001 From: trilene Date: Thu, 7 Jan 2021 09:48:25 -0500 Subject: [PATCH 30/35] Fix device discovery under GStreamer 1.16 --- resources/qml/MessageInput.qml | 1 + resources/qml/voip/CallInviteBar.qml | 1 + src/CallManager.h | 1 + src/WebRTCSession.cpp | 1 + 4 files changed, 4 insertions(+) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 71c61697..00edb7e5 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -46,6 +46,7 @@ Rectangle { CallManager.hangUp(); } else { + CallManager.refreshDevices(); var dialog = placeCallDialog.createObject(timelineRoot); dialog.open(); } diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index 58b89ed3..5021949a 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -71,6 +71,7 @@ Rectangle { ToolTip.visible: hovered ToolTip.text: qsTr("Devices") onClicked: { + CallManager.refreshDevices(); var dialog = devicesDialog.createObject(timelineRoot); dialog.open(); } diff --git a/src/CallManager.h b/src/CallManager.h index 75768ee1..7d388efd 100644 --- a/src/CallManager.h +++ b/src/CallManager.h @@ -53,6 +53,7 @@ public: public slots: void sendInvite(const QString &roomid, bool isVideo); void syncEvent(const mtx::events::collections::TimelineEvents &event); + void refreshDevices() { session_.refreshDevices(); } void toggleMicMute(); void toggleCameraView() { session_.toggleCameraView(); } void acceptInvite(); diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index a431ab54..094a2906 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp @@ -1292,6 +1292,7 @@ WebRTCSession::refreshDevices() addDevice(GST_DEVICE_CAST(l->data)); g_list_free(devices); } + emit devicesChanged(); #endif } From e9519689f1d373190834368f2d1a8faf5f256142 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 7 Jan 2021 16:13:33 +0100 Subject: [PATCH 31/35] Bump gstreamer requirement --- CMakeLists.txt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2365ac09..ec9c8c1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -443,15 +443,11 @@ else() endif() include(FindPkgConfig) -pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.14 gstreamer-webrtc-1.0>=1.14) +pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.16 gstreamer-webrtc-1.0>=1.16) if (TARGET PkgConfig::GSTREAMER) - add_feature_info(voip ON "GStreamer found. Call support is enabled - automatically.") + add_feature_info(voip ON "GStreamer found. Call support is enabled automatically.") else() - add_feature_info(voip OFF "GStreamer could not be found on your system. - As a consequence call support has been disabled. If you don't want that, - make sure gstreamer-sdp-1.0>=1.16 gstreamer-webrtc-1.0>=1.16 can be found - via pkgconfig.") + add_feature_info(voip OFF "GStreamer could not be found on your system. As a consequence call support has been disabled. If you don't want that, make sure gstreamer-sdp-1.0>=1.16 gstreamer-webrtc-1.0>=1.16 can be found via pkgconfig.") endif() # single instance functionality From cc9de7f3b0d0459a773a82a79a6c38bbf4ea6ea7 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 8 Jan 2021 12:48:39 +0100 Subject: [PATCH 32/35] Fix some nulls in relations --- src/Olm.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index 07fc49f6..fe789560 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -579,13 +579,12 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, mtx::common::RelatesTo r_relation; // relations shouldn't be encrypted... - if (body["content"].contains("m.relates_to") && - body["content"]["m.relates_to"].contains("m.in_reply_to")) { - relation = body["content"]["m.relates_to"]; - body["content"].erase("m.relates_to"); - } else if (body["content"]["m.relates_to"].contains("event_id")) { - r_relation = body["content"]["m.relates_to"]; - body["content"].erase("m.relates_to"); + if (body["content"].contains("m.relates_to")) { + if (body["content"]["m.relates_to"].contains("m.in_reply_to")) { + relation = body["content"]["m.relates_to"]; + } else if (body["content"]["m.relates_to"].contains("event_id")) { + r_relation = body["content"]["m.relates_to"]; + } } auto payload = olm::client()->encrypt_group_message(session.get(), body.dump()); From 3572c111f3f1730e6e8b0fb38a125d69aa1f9116 Mon Sep 17 00:00:00 2001 From: trilene Date: Sun, 10 Jan 2021 12:23:58 -0500 Subject: [PATCH 33/35] Fix call invite declined on another device --- src/timeline/TimelineModel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 2b5b5794..852f584d 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -614,7 +614,9 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline) [this](auto &event) { event.room_id = room_id_.toStdString(); if constexpr (std::is_same_v, - RoomEvent>) + RoomEvent> || + std::is_same_v, + RoomEvent>) emit newCallEvent(event); else { if (event.sender != http::client()->user_id().to_string()) From 39f9b7d90adbdbc9eb6186d93bb6bfd0564c152c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 10 Jan 2021 18:36:06 +0100 Subject: [PATCH 34/35] Handle matrix scheme Link opening only works on Linux for now. See https://github.com/matrix-org/matrix-doc/pull/2312 --- resources/nheko.desktop | 1 + src/Cache.cpp | 28 ++++++++ src/Cache_p.h | 1 + src/ChatPage.cpp | 140 ++++++++++++++++++++++++++++++++++++++++ src/ChatPage.h | 4 ++ src/main.cpp | 52 +++++++++++---- src/ui/UserProfile.cpp | 7 +- 7 files changed, 216 insertions(+), 17 deletions(-) diff --git a/resources/nheko.desktop b/resources/nheko.desktop index 16e04926..4404e460 100644 --- a/resources/nheko.desktop +++ b/resources/nheko.desktop @@ -8,3 +8,4 @@ Type=Application Categories=Network;InstantMessaging;Qt; StartupWMClass=nheko Terminal=false +MimeType=x-scheme-handler/matrix; diff --git a/src/Cache.cpp b/src/Cache.cpp index 04046346..17b55144 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -2221,6 +2221,34 @@ Cache::getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb) return QString("1"); } +std::optional +Cache::getRoomAliases(const std::string &roomid) +{ + using namespace mtx::events; + using namespace mtx::events::state; + + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + auto statesdb = getStatesDb(txn, roomid); + + lmdb::val event; + bool res = lmdb::dbi_get( + txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomCanonicalAlias)), event); + + if (res) { + try { + StateEvent msg = + json::parse(std::string_view(event.data(), event.size())); + + return msg.content; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", + e.what()); + } + } + + return std::nullopt; +} + QString Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { diff --git a/src/Cache_p.h b/src/Cache_p.h index 059c1461..e2ce1668 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -81,6 +81,7 @@ public: std::vector joinedRooms(); QMap roomInfo(bool withInvites = true); + std::optional getRoomAliases(const std::string &roomid); std::map invites(); //! Calculate & return the name of the room. diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 238c9362..33c993ae 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -918,6 +918,8 @@ ChatPage::joinRoom(const QString &room) } catch (const lmdb::error &e) { emit showNotification(tr("Failed to remove invite: %1").arg(e.what())); } + + room_list_->highlightSelectedRoom(QString::fromStdString(room_id)); }); } @@ -1268,3 +1270,141 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio cache::storeSecret(secretName, decrypted); } } + +void +ChatPage::startChat(QString userid) +{ + auto joined_rooms = cache::joinedRooms(); + auto room_infos = cache::getRoomInfo(joined_rooms); + + for (std::string room_id : joined_rooms) { + if (room_infos[QString::fromStdString(room_id)].member_count == 2) { + auto room_members = cache::roomMembers(room_id); + if (std::find(room_members.begin(), + room_members.end(), + (userid).toStdString()) != room_members.end()) { + room_list_->highlightSelectedRoom(QString::fromStdString(room_id)); + return; + } + } + } + + mtx::requests::CreateRoom req; + req.preset = mtx::requests::Preset::PrivateChat; + req.visibility = mtx::requests::Visibility::Private; + if (utils::localUser() != userid) + req.invite = {userid.toStdString()}; + emit ChatPage::instance()->createRoom(req); +} + +static QString +mxidFromSegments(QStringRef sigil, QStringRef mxid) +{ + if (mxid.isEmpty()) + return ""; + + auto mxid_ = QUrl::fromPercentEncoding(mxid.toUtf8()); + + if (sigil == "user") { + return "@" + mxid_; + } else if (sigil == "roomid") { + return "!" + mxid_; + } else if (sigil == "room") { + return "#" + mxid_; + } else if (sigil == "group") { + return "+" + mxid_; + } else { + return ""; + } +} + +void +ChatPage::handleMatrixUri(const QByteArray &uri) +{ + nhlog::ui()->info("Received uri! {}", uri.toStdString()); + QUrl uri_{QString::fromUtf8(uri)}; + + if (uri_.scheme() != "matrix") + return; + + auto tempPath = uri_.path(QUrl::ComponentFormattingOption::FullyEncoded); + if (tempPath.startsWith('/')) + tempPath.remove(0, 1); + auto segments = tempPath.splitRef('/'); + + if (segments.size() != 2 && segments.size() != 4) + return; + + auto sigil1 = segments[0]; + auto mxid1 = mxidFromSegments(sigil1, segments[1]); + if (mxid1.isEmpty()) + return; + + QString mxid2; + if (segments.size() == 4 && segments[2] == "event") { + if (segments[3].isEmpty()) + return; + else + mxid2 = "$" + QUrl::fromPercentEncoding(segments[3].toUtf8()); + } + + std::vector vias; + QString action; + + for (QString item : uri_.query(QUrl::ComponentFormattingOption::FullyEncoded).split('&')) { + nhlog::ui()->info("item: {}", item.toStdString()); + + if (item.startsWith("action=")) { + action = item.remove("action="); + } else if (item.startsWith("via=")) { + vias.push_back( + QUrl::fromPercentEncoding(item.remove("via=").toUtf8()).toStdString()); + } + } + + if (sigil1 == "user") { + if (action.isEmpty()) { + view_manager_->activeTimeline()->openUserProfile(mxid1); + } else if (action == "chat") { + this->startChat(mxid1); + } + } else if (sigil1 == "roomid") { + auto joined_rooms = cache::joinedRooms(); + auto targetRoomId = mxid1.toStdString(); + + for (auto roomid : joined_rooms) { + if (roomid == targetRoomId) { + room_list_->highlightSelectedRoom(mxid1); + break; + } + } + + if (action == "join") { + joinRoom(mxid1); + } + } else if (sigil1 == "room") { + auto joined_rooms = cache::joinedRooms(); + auto targetRoomAlias = mxid1.toStdString(); + + for (auto roomid : joined_rooms) { + auto aliases = cache::client()->getRoomAliases(roomid); + if (aliases) { + if (aliases->alias == targetRoomAlias) { + room_list_->highlightSelectedRoom( + QString::fromStdString(roomid)); + break; + } + } + } + + if (action == "join") { + joinRoom(mxid1); + } + } +} + +void +ChatPage::handleMatrixUri(const QUrl &uri) +{ + handleMatrixUri(uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8()); +} diff --git a/src/ChatPage.h b/src/ChatPage.h index 45a4ff63..004bb3e8 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -110,6 +110,10 @@ public: mtx::presence::PresenceState currentPresence() const; public slots: + void handleMatrixUri(const QByteArray &uri); + void handleMatrixUri(const QUrl &uri); + + void startChat(QString userid); void leaveRoom(const QString &room_id); void createRoom(const mtx::requests::CreateRoom &req); void joinRoom(const QString &room); diff --git a/src/main.cpp b/src/main.cpp index a60c66c4..7a417ae2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include +#include "ChatPage.h" #include "Config.h" #include "Logging.h" #include "MainWindow.h" @@ -128,34 +130,43 @@ main(int argc, char *argv[]) // This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name // parsed before the SingleApplication userdata is set. QString userdata{""}; + QString matrixUri; for (int i = 0; i < argc; ++i) { - if (QString{argv[i]}.startsWith("--profile=")) { - QString q{argv[i]}; - q.remove("--profile="); - userdata = q; - } else if (QString{argv[i]}.startsWith("--p=")) { - QString q{argv[i]}; - q.remove("-p="); - userdata = q; - } else if (QString{argv[i]} == "--profile" || QString{argv[i]} == "-p") { + QString arg{argv[i]}; + if (arg.startsWith("--profile=")) { + arg.remove("--profile="); + userdata = arg; + } else if (arg.startsWith("--p=")) { + arg.remove("-p="); + userdata = arg; + } else if (arg == "--profile" || arg == "-p") { if (i < argc - 1) // if i is less than argc - 1, we still have a parameter // left to process as the name { ++i; // the next arg is the name, so increment userdata = QString{argv[i]}; } + } else if (arg.startsWith("matrix:")) { + matrixUri = arg; } } SingleApplication app(argc, argv, - false, + true, SingleApplication::Mode::User | SingleApplication::Mode::ExcludeAppPath | - SingleApplication::Mode::ExcludeAppVersion, + SingleApplication::Mode::ExcludeAppVersion | + SingleApplication::Mode::SecondaryNotification, 100, userdata); + if (app.isSecondary()) { + // open uri in main instance + app.sendMessage(matrixUri.toUtf8()); + return 0; + } + QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); @@ -245,6 +256,25 @@ main(int argc, char *argv[]) w.activateWindow(); }); + QObject::connect( + &app, + &SingleApplication::receivedMessage, + ChatPage::instance(), + [&](quint32, QByteArray message) { ChatPage::instance()->handleMatrixUri(message); }); + + QMetaObject::Connection uriConnection; + if (app.isPrimary() && !matrixUri.isEmpty()) { + uriConnection = QObject::connect(ChatPage::instance(), + &ChatPage::contentLoaded, + ChatPage::instance(), + [&uriConnection, matrixUri]() { + ChatPage::instance()->handleMatrixUri( + matrixUri.toUtf8()); + QObject::disconnect(uriConnection); + }); + } + QDesktopServices::setUrlHandler("matrix", ChatPage::instance(), "handleMatrixUri"); + #if defined(Q_OS_MAC) // Temporary solution for the emoji picker until // nheko has a proper menu bar with more functionality. diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 974aa5cc..6ef82123 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -202,12 +202,7 @@ UserProfile::kickUser() void UserProfile::startChat() { - mtx::requests::CreateRoom req; - req.preset = mtx::requests::Preset::PrivateChat; - req.visibility = mtx::requests::Visibility::Private; - if (utils::localUser() != this->userid_) - req.invite = {this->userid_.toStdString()}; - emit ChatPage::instance()->createRoom(req); + ChatPage::instance()->startChat(this->userid_); } void From c1d5df44d96d006d04b48c08e4e8ab853bfa1af1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 10 Jan 2021 23:45:10 +0100 Subject: [PATCH 35/35] Fix branch name in PRs --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4fa41d37..f03dcbbe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -96,8 +96,8 @@ build-flatpak-amd64: - export VERSION=$(git describe) - mkdir -p build-flatpak - cd build-flatpak - - flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME} --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.json - - flatpak build-bundle repo nheko-amd64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME} + - flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.json + - flatpak build-bundle repo nheko-amd64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_} after_script: - bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-amd64.flatpak cache: @@ -123,8 +123,8 @@ build-flatpak-arm64: - export VERSION=$(git describe) - mkdir -p build-flatpak - cd build-flatpak - - flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME} --subject="Build of Nheko ${VERSION} `date` for arm64" app ../io.github.NhekoReborn.Nheko.json - - flatpak build-bundle repo nheko-arm64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME} + - flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date` for arm64" app ../io.github.NhekoReborn.Nheko.json + - flatpak build-bundle repo nheko-arm64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_} after_script: - bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-arm64.flatpak cache: