From da27670cbe474cf7e4f487b11d58c63e7cefb976 Mon Sep 17 00:00:00 2001 From: trilene Date: Tue, 22 Sep 2020 12:07:36 -0400 Subject: [PATCH 1/9] Port ActiveCallBar to Qml --- CMakeLists.txt | 2 - resources/qml/ImageButton.qml | 5 +- resources/qml/TimelineView.qml | 140 +++++++++++++++++++++++ src/ActiveCallBar.cpp | 160 --------------------------- src/ActiveCallBar.h | 40 ------- src/CallManager.cpp | 22 +--- src/CallManager.h | 4 - src/ChatPage.cpp | 8 -- src/ChatPage.h | 2 - src/MainWindow.cpp | 4 +- src/TextInputWidget.cpp | 8 +- src/TextInputWidget.h | 2 +- src/WebRTCSession.cpp | 43 ++++--- src/WebRTCSession.h | 41 ++++--- src/timeline/TimelineViewManager.cpp | 2 + src/timeline/TimelineViewManager.h | 6 + 16 files changed, 212 insertions(+), 277 deletions(-) delete mode 100644 src/ActiveCallBar.cpp delete mode 100644 src/ActiveCallBar.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fe686ddf..f5628e2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -279,7 +279,6 @@ set(SRC_FILES src/ui/Theme.cpp src/ui/ThemeManager.cpp - src/ActiveCallBar.cpp src/AvatarProvider.cpp src/BlurhashProvider.cpp src/Cache.cpp @@ -491,7 +490,6 @@ qt5_wrap_cpp(MOC_HEADERS src/notifications/Manager.h - src/ActiveCallBar.h src/AvatarProvider.h src/BlurhashProvider.h src/Cache_p.h diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml index dd67d597..0a33d376 100644 --- a/resources/qml/ImageButton.qml +++ b/resources/qml/ImageButton.qml @@ -2,7 +2,8 @@ import QtQuick 2.3 import QtQuick.Controls 2.3 AbstractButton { - property string image: undefined + property string image + property string src width: 16 height: 16 id: button @@ -11,7 +12,7 @@ AbstractButton { id: buttonImg // Workaround, can't get icon.source working for now... anchors.fill: parent - source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText) + source: src ? src : ("image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText)) } MouseArea diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index de818cd9..4ea15f7b 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -497,6 +497,146 @@ Page { } } } + + Rectangle { + id: activeCallBar + visible: timelineManager.callState != WebRTCState.DISCONNECTED + + Layout.fillWidth: true + implicitHeight: topLayout.height + 16 + color: "#2ECC71" + z: 3 + + GridLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 8 + anchors.verticalCenter: parent.verticalCenter + + Avatar { + Layout.column: 1 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + + width: avatarSize + height: avatarSize + + url: chat.model ? chat.model.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : "" + displayName: chat.model ? chat.model.roomName : qsTr("No room selected") + } + + Label { + Layout.column: 2 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: chat.model ? " " + chat.model.roomName + " " : "" + } + + Image { + Layout.column: 3 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: 23 + Layout.preferredHeight: 23 + source: "qrc:/icons/icons/ui/place-call.png" + } + + Connections { + target: timelineManager + function onCallStateChanged(state) { + switch (state) { + case WebRTCState.INITIATING: + callStateLabel.text = "Initiating call..." + break; + case WebRTCState.INITIATED: + callStateLabel.text = "Call initiated..." + break; + case WebRTCState.OFFERSENT: + callStateLabel.text = "Calling..." + break; + case WebRTCState.CONNECTING: + callStateLabel.text = "Connecting..." + break; + case WebRTCState.CONNECTED: + callStateLabel.text = "00:00" + var d = new Date() + callTimer.startTime = Math.floor(d.getTime() / 1000) + break; + } + } + } + + Label { + id: callStateLabel + Layout.column: 4 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + font.pointSize: fontMetrics.font.pointSize * 1.1 + } + + Timer { + id: callTimer + property int startTime + interval: 1000 + running: timelineManager.callState == WebRTCState.CONNECTED + repeat: true + onTriggered: { + var d = new Date() + let seconds = Math.floor(d.getTime() / 1000 - startTime) + let s = Math.floor(seconds % 60) + let m = Math.floor(seconds / 60) % 60 + let h = Math.floor(seconds / 3600) + callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s) + } + + function pad(n) { + return (n < 10) ? ("0" + n) : n + } + } + + Item { + Layout.column: 5 + Layout.fillWidth: true + } + + ImageButton { + Layout.column: 6 + Layout.row: 0 + Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter + + width: 22 + height: 22 + src: "qrc:/icons/icons/ui/microphone-mute.png" + + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Mute Mic") + + onClicked: { + if (timelineManager.toggleMuteAudioSource()) { + src = "qrc:/icons/icons/ui/microphone-unmute.png" + ToolTip.text = qsTr("Unmute Mic") + } + else { + src = "qrc:/icons/icons/ui/microphone-mute.png" + ToolTip.text = qsTr("Mute Mic") + } + } + } + + Item { + Layout.column: 7 + implicitWidth: 16 + } + } + } } } } diff --git a/src/ActiveCallBar.cpp b/src/ActiveCallBar.cpp deleted file mode 100644 index c0d2c13a..00000000 --- a/src/ActiveCallBar.cpp +++ /dev/null @@ -1,160 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -#include "ActiveCallBar.h" -#include "ChatPage.h" -#include "Utils.h" -#include "WebRTCSession.h" -#include "ui/Avatar.h" -#include "ui/FlatButton.h" - -ActiveCallBar::ActiveCallBar(QWidget *parent) - : QWidget(parent) -{ - setAutoFillBackground(true); - auto p = palette(); - p.setColor(backgroundRole(), QColor(46, 204, 113)); - setPalette(p); - - QFont f; - f.setPointSizeF(f.pointSizeF()); - - const int fontHeight = QFontMetrics(f).height(); - const int widgetMargin = fontHeight / 3; - const int contentHeight = fontHeight * 3; - - setFixedHeight(contentHeight + widgetMargin); - - layout_ = new QHBoxLayout(this); - layout_->setSpacing(widgetMargin); - layout_->setContentsMargins(2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin); - - QFont labelFont; - labelFont.setPointSizeF(labelFont.pointSizeF() * 1.1); - labelFont.setWeight(QFont::Medium); - - avatar_ = new Avatar(this, QFontMetrics(f).height() * 2.5); - - callPartyLabel_ = new QLabel(this); - callPartyLabel_->setFont(labelFont); - - stateLabel_ = new QLabel(this); - stateLabel_->setFont(labelFont); - - durationLabel_ = new QLabel(this); - durationLabel_->setFont(labelFont); - durationLabel_->hide(); - - muteBtn_ = new FlatButton(this); - setMuteIcon(false); - muteBtn_->setFixedSize(buttonSize_, buttonSize_); - muteBtn_->setCornerRadius(buttonSize_ / 2); - connect(muteBtn_, &FlatButton::clicked, this, [this]() { - if (WebRTCSession::instance().toggleMuteAudioSrc(muted_)) - setMuteIcon(muted_); - }); - - layout_->addWidget(avatar_, 0, Qt::AlignLeft); - layout_->addWidget(callPartyLabel_, 0, Qt::AlignLeft); - layout_->addWidget(stateLabel_, 0, Qt::AlignLeft); - layout_->addWidget(durationLabel_, 0, Qt::AlignLeft); - layout_->addStretch(); - layout_->addWidget(muteBtn_, 0, Qt::AlignCenter); - layout_->addSpacing(18); - - timer_ = new QTimer(this); - connect(timer_, &QTimer::timeout, this, [this]() { - auto seconds = QDateTime::currentSecsSinceEpoch() - callStartTime_; - int s = seconds % 60; - int m = (seconds / 60) % 60; - int h = seconds / 3600; - char buf[12]; - if (h) - snprintf(buf, sizeof(buf), "%.2d:%.2d:%.2d", h, m, s); - else - snprintf(buf, sizeof(buf), "%.2d:%.2d", m, s); - durationLabel_->setText(buf); - }); - - connect( - &WebRTCSession::instance(), &WebRTCSession::stateChanged, this, &ActiveCallBar::update); -} - -void -ActiveCallBar::setMuteIcon(bool muted) -{ - QIcon icon; - if (muted) { - muteBtn_->setToolTip("Unmute Mic"); - icon.addFile(":/icons/icons/ui/microphone-unmute.png"); - } else { - muteBtn_->setToolTip("Mute Mic"); - icon.addFile(":/icons/icons/ui/microphone-mute.png"); - } - muteBtn_->setIcon(icon); - muteBtn_->setIconSize(QSize(buttonSize_, buttonSize_)); -} - -void -ActiveCallBar::setCallParty(const QString &userid, - const QString &displayName, - const QString &roomName, - const QString &avatarUrl) -{ - callPartyLabel_->setText(" " + (displayName.isEmpty() ? userid : displayName) + " "); - - if (!avatarUrl.isEmpty()) - avatar_->setImage(avatarUrl); - else - avatar_->setLetter(utils::firstChar(roomName)); -} - -void -ActiveCallBar::update(WebRTCSession::State state) -{ - switch (state) { - case WebRTCSession::State::INITIATING: - show(); - stateLabel_->setText("Initiating call..."); - break; - case WebRTCSession::State::INITIATED: - show(); - stateLabel_->setText("Call initiated..."); - break; - case WebRTCSession::State::OFFERSENT: - show(); - stateLabel_->setText("Calling..."); - break; - case WebRTCSession::State::CONNECTING: - show(); - stateLabel_->setText("Connecting..."); - break; - case WebRTCSession::State::CONNECTED: - show(); - callStartTime_ = QDateTime::currentSecsSinceEpoch(); - timer_->start(1000); - stateLabel_->setPixmap( - QIcon(":/icons/icons/ui/place-call.png").pixmap(QSize(buttonSize_, buttonSize_))); - durationLabel_->setText("00:00"); - durationLabel_->show(); - break; - case WebRTCSession::State::ICEFAILED: - case WebRTCSession::State::DISCONNECTED: - hide(); - timer_->stop(); - callPartyLabel_->setText(QString()); - stateLabel_->setText(QString()); - durationLabel_->setText(QString()); - durationLabel_->hide(); - setMuteIcon(false); - break; - default: - break; - } -} diff --git a/src/ActiveCallBar.h b/src/ActiveCallBar.h deleted file mode 100644 index 1e940227..00000000 --- a/src/ActiveCallBar.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include - -#include "WebRTCSession.h" - -class QHBoxLayout; -class QLabel; -class QTimer; -class Avatar; -class FlatButton; - -class ActiveCallBar : public QWidget -{ - Q_OBJECT - -public: - ActiveCallBar(QWidget *parent = nullptr); - -public slots: - void update(WebRTCSession::State); - void setCallParty(const QString &userid, - const QString &displayName, - const QString &roomName, - const QString &avatarUrl); - -private: - QHBoxLayout *layout_ = nullptr; - Avatar *avatar_ = nullptr; - QLabel *callPartyLabel_ = nullptr; - QLabel *stateLabel_ = nullptr; - QLabel *durationLabel_ = nullptr; - FlatButton *muteBtn_ = nullptr; - int buttonSize_ = 22; - bool muted_ = false; - qint64 callStartTime_ = 0; - QTimer *timer_ = nullptr; - - void setMuteIcon(bool muted); -}; diff --git a/src/CallManager.cpp b/src/CallManager.cpp index 7a8d2ca7..2c0f9d5a 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp @@ -52,7 +52,7 @@ CallManager::CallManager(QSharedPointer userSettings) emit newMessage(roomid_, CallInvite{callid_, sdp, 0, timeoutms_}); emit newMessage(roomid_, CallCandidates{callid_, candidates, 0}); QTimer::singleShot(timeoutms_, this, [this]() { - if (session_.state() == WebRTCSession::State::OFFERSENT) { + if (session_.state() == webrtc::State::OFFERSENT) { hangUp(CallHangUp::Reason::InviteTimeOut); emit ChatPage::instance()->showNotification( "The remote side failed to pick up."); @@ -99,13 +99,13 @@ CallManager::CallManager(QSharedPointer userSettings) turnServerTimer_.setInterval(ttl * 1000 * 0.9); }); - connect(&session_, &WebRTCSession::stateChanged, this, [this](WebRTCSession::State state) { + connect(&session_, &WebRTCSession::stateChanged, this, [this](webrtc::State state) { switch (state) { - case WebRTCSession::State::DISCONNECTED: + case webrtc::State::DISCONNECTED: playRingtone("qrc:/media/media/callend.ogg", false); clear(); break; - case WebRTCSession::State::ICEFAILED: { + case webrtc::State::ICEFAILED: { QString error("Call connection failed."); if (turnURIs_.empty()) error += " Your homeserver has no configured TURN server."; @@ -152,13 +152,6 @@ CallManager::sendInvite(const QString &roomid) generateCallID(); nhlog::ui()->debug("WebRTC: call id: {} - creating invite", callid_); - std::vector members(cache::getMembers(roomid.toStdString())); - const RoomMember &callee = - members.front().user_id == utils::localUser() ? members.back() : members.front(); - emit newCallParty(callee.user_id, - callee.display_name, - QString::fromStdString(roomInfo.name), - QString::fromStdString(roomInfo.avatar_url)); playRingtone("qrc:/media/media/ringback.ogg", true); if (!session_.createOffer()) { emit ChatPage::instance()->showNotification("Problem setting up call."); @@ -195,7 +188,7 @@ CallManager::hangUp(CallHangUp::Reason reason) bool CallManager::onActiveCall() { - return session_.state() != WebRTCSession::State::DISCONNECTED; + return session_.state() != webrtc::State::DISCONNECTED; } void @@ -259,11 +252,6 @@ 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(); - emit newCallParty(caller.user_id, - caller.display_name, - QString::fromStdString(roomInfo.name), - QString::fromStdString(roomInfo.avatar_url)); - auto dialog = new dialogs::AcceptCall(caller.user_id, caller.display_name, QString::fromStdString(roomInfo.name), diff --git a/src/CallManager.h b/src/CallManager.h index 3a406438..1de8d2ae 100644 --- a/src/CallManager.h +++ b/src/CallManager.h @@ -41,10 +41,6 @@ signals: void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); void turnServerRetrieved(const mtx::responses::TurnServer &); - void newCallParty(const QString &userid, - const QString &displayName, - const QString &roomName, - const QString &avatarUrl); private slots: void retrieveTurnServer(); diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 6008846a..59ae7ef2 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -22,7 +22,6 @@ #include #include -#include "ActiveCallBar.h" #include "AvatarProvider.h" #include "Cache.h" #include "Cache_p.h" @@ -40,7 +39,6 @@ #include "UserInfoWidget.h" #include "UserSettingsPage.h" #include "Utils.h" -#include "WebRTCSession.h" #include "ui/OverlayModal.h" #include "ui/Theme.h" @@ -129,12 +127,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) contentLayout_->addWidget(view_manager_->getWidget()); - activeCallBar_ = new ActiveCallBar(this); - contentLayout_->addWidget(activeCallBar_); - activeCallBar_->hide(); - connect( - &callManager_, &CallManager::newCallParty, activeCallBar_, &ActiveCallBar::setCallParty); - // Splitter splitter->addWidget(sideBar_); splitter->addWidget(content_); diff --git a/src/ChatPage.h b/src/ChatPage.h index a139b5fd..6b3916a2 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -41,7 +41,6 @@ #include "notifications/Manager.h" #include "popups/UserMentions.h" -class ActiveCallBar; class OverlayModal; class QuickSwitcher; class RoomList; @@ -235,7 +234,6 @@ private: SideBarActions *sidebarActions_; TextInputWidget *text_input_; - ActiveCallBar *activeCallBar_; QTimer connectivityTimer_; std::atomic_bool isConnected_; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 29abed86..330ba2a3 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -288,7 +288,7 @@ MainWindow::showChatPage() void MainWindow::closeEvent(QCloseEvent *event) { - if (WebRTCSession::instance().state() != WebRTCSession::State::DISCONNECTED) { + if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") != QMessageBox::Yes) { event->ignore(); @@ -440,7 +440,7 @@ MainWindow::openLogoutDialog() { auto dialog = new dialogs::Logout(this); connect(dialog, &dialogs::Logout::loggingOut, this, [this]() { - if (WebRTCSession::instance().state() != WebRTCSession::State::DISCONNECTED) { + if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { if (QMessageBox::question( this, "nheko", "A call is in progress. Log out?") != QMessageBox::Yes) { diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index 4a25c4cf..a7d9797e 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -560,7 +560,7 @@ TextInputWidget::TextInputWidget(QWidget *parent) #ifdef GSTREAMER_AVAILABLE callBtn_ = new FlatButton(this); - changeCallButtonState(WebRTCSession::State::DISCONNECTED); + changeCallButtonState(webrtc::State::DISCONNECTED); connect(&WebRTCSession::instance(), &WebRTCSession::stateChanged, this, @@ -776,11 +776,11 @@ TextInputWidget::paintEvent(QPaintEvent *) } void -TextInputWidget::changeCallButtonState(WebRTCSession::State state) +TextInputWidget::changeCallButtonState(webrtc::State state) { QIcon icon; - if (state == WebRTCSession::State::ICEFAILED || - state == WebRTCSession::State::DISCONNECTED) { + if (state == webrtc::State::ICEFAILED || + state == webrtc::State::DISCONNECTED) { callBtn_->setToolTip(tr("Place a call")); icon.addFile(":/icons/icons/ui/place-call.png"); } else { diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h index 3aa05c39..f4ca980e 100644 --- a/src/TextInputWidget.h +++ b/src/TextInputWidget.h @@ -164,7 +164,7 @@ public slots: void openFileSelection(); void hideUploadSpinner(); void focusLineEdit() { input_->setFocus(); } - void changeCallButtonState(WebRTCSession::State); + void changeCallButtonState(webrtc::State); private slots: void addSelectedEmoji(const QString &emoji); diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index d8497833..8f877c41 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp @@ -1,4 +1,5 @@ #include +#include #include "Logging.h" #include "WebRTCSession.h" @@ -14,12 +15,22 @@ extern "C" } #endif -Q_DECLARE_METATYPE(WebRTCSession::State) +Q_DECLARE_METATYPE(webrtc::State) + +using webrtc::State; WebRTCSession::WebRTCSession() : QObject() -{ - qRegisterMetaType(); +{ + qRegisterMetaType(); + qmlRegisterUncreatableMetaObject( + webrtc::staticMetaObject, + "im.nheko", + 1, + 0, + "WebRTCState", + "Can't instantiate enum"); + connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState); init(); } @@ -247,11 +258,11 @@ iceGatheringStateChanged(GstElement *webrtc, if (isoffering_) { emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_); emit WebRTCSession::instance().stateChanged( - WebRTCSession::State::OFFERSENT); + State::OFFERSENT); } else { emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_); emit WebRTCSession::instance().stateChanged( - WebRTCSession::State::ANSWERSENT); + State::ANSWERSENT); } } } @@ -264,10 +275,10 @@ onICEGatheringCompletion(gpointer timerid) *(guint *)(timerid) = 0; if (isoffering_) { emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_); - emit WebRTCSession::instance().stateChanged(WebRTCSession::State::OFFERSENT); + emit WebRTCSession::instance().stateChanged(State::OFFERSENT); } else { emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_); - emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ANSWERSENT); + emit WebRTCSession::instance().stateChanged(State::ANSWERSENT); } return FALSE; } @@ -285,7 +296,7 @@ addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED, localcandidates_.push_back({"audio", (uint16_t)mlineIndex, candidate}); return; #else - if (WebRTCSession::instance().state() >= WebRTCSession::State::OFFERSENT) { + if (WebRTCSession::instance().state() >= State::OFFERSENT) { emit WebRTCSession::instance().newICECandidate( {"audio", (uint16_t)mlineIndex, candidate}); return; @@ -314,11 +325,11 @@ iceConnectionStateChanged(GstElement *webrtc, switch (newState) { case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING: nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking"); - emit WebRTCSession::instance().stateChanged(WebRTCSession::State::CONNECTING); + emit WebRTCSession::instance().stateChanged(State::CONNECTING); break; case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED: nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed"); - emit WebRTCSession::instance().stateChanged(WebRTCSession::State::ICEFAILED); + emit WebRTCSession::instance().stateChanged(State::ICEFAILED); break; default: break; @@ -356,7 +367,7 @@ linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe nhlog::ui()->error("WebRTC: unable to link new pad"); else { emit WebRTCSession::instance().stateChanged( - WebRTCSession::State::CONNECTED); + State::CONNECTED); } gst_object_unref(queuepad); } @@ -633,21 +644,17 @@ WebRTCSession::createPipeline(int opusPayloadType) } bool -WebRTCSession::toggleMuteAudioSrc(bool &isMuted) +WebRTCSession::toggleMuteAudioSource() { if (state_ < State::INITIATED) return false; GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel"); - if (!srclevel) - return false; - gboolean muted; g_object_get(srclevel, "mute", &muted, nullptr); g_object_set(srclevel, "mute", !muted, nullptr); gst_object_unref(srclevel); - isMuted = !muted; - return true; + return !muted; } void @@ -778,7 +785,7 @@ WebRTCSession::createPipeline(int) } bool -WebRTCSession::toggleMuteAudioSrc(bool &) +WebRTCSession::toggleMuteAudioSource() { return false; } diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h index 653ec2cf..9593def9 100644 --- a/src/WebRTCSession.h +++ b/src/WebRTCSession.h @@ -9,23 +9,30 @@ typedef struct _GstElement GstElement; +namespace webrtc { +Q_NAMESPACE + +enum class State +{ + DISCONNECTED, + ICEFAILED, + INITIATING, + INITIATED, + OFFERSENT, + ANSWERSENT, + CONNECTING, + CONNECTED + +}; +Q_ENUM_NS(State) + +} + class WebRTCSession : public QObject { Q_OBJECT public: - enum class State - { - DISCONNECTED, - ICEFAILED, - INITIATING, - INITIATED, - OFFERSENT, - ANSWERSENT, - CONNECTING, - CONNECTED - }; - static WebRTCSession &instance() { static WebRTCSession instance; @@ -33,14 +40,14 @@ public: } bool init(std::string *errorMessage = nullptr); - State state() const { return state_; } + webrtc::State state() const { return state_; } bool createOffer(); bool acceptOffer(const std::string &sdp); bool acceptAnswer(const std::string &sdp); void acceptICECandidates(const std::vector &); - bool toggleMuteAudioSrc(bool &isMuted); + bool toggleMuteAudioSource(); void end(); void setStunServer(const std::string &stunServer) { stunServer_ = stunServer; } @@ -55,16 +62,16 @@ signals: void answerCreated(const std::string &sdp, const std::vector &); void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &); - void stateChanged(WebRTCSession::State); // explicit qualifier necessary for Qt + void stateChanged(webrtc::State); private slots: - void setState(State state) { state_ = state; } + void setState(webrtc::State state) { state_ = state; } private: WebRTCSession(); bool initialised_ = false; - State state_ = State::DISCONNECTED; + webrtc::State state_ = webrtc::State::DISCONNECTED; GstElement *pipe_ = nullptr; GstElement *webrtc_ = nullptr; unsigned int busWatchId_ = 0; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index abb807b3..3a308be8 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -141,6 +141,8 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin isInitialSync_ = true; emit initialSyncChanged(true); }); + connect( + &WebRTCSession::instance(), &WebRTCSession::stateChanged, this, &TimelineViewManager::callStateChanged); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 1a98f64d..fdf1603d 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -13,6 +13,7 @@ #include "Logging.h" #include "TimelineModel.h" #include "Utils.h" +#include "WebRTCSession.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" @@ -33,6 +34,8 @@ 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) public: TimelineViewManager(QSharedPointer userSettings, @@ -48,6 +51,8 @@ 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(); } + Q_INVOKABLE bool toggleMuteAudioSource() { return WebRTCSession::instance().toggleMuteAudioSource(); } Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const; Q_INVOKABLE QColor userColor(QString id, QColor background); Q_INVOKABLE QString escapeEmoji(QString str) const; @@ -72,6 +77,7 @@ signals: void inviteUsers(QStringList users); void showRoomList(); void narrowViewChanged(); + void callStateChanged(webrtc::State); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); From 44cfc8d22afd5b7522a13f52af67839de6a37dcc Mon Sep 17 00:00:00 2001 From: trilene Date: Tue, 22 Sep 2020 12:14:15 -0400 Subject: [PATCH 2/9] clang-format --- src/TextInputWidget.cpp | 3 +-- src/WebRTCSession.cpp | 20 ++++++-------------- src/timeline/TimelineViewManager.cpp | 6 ++++-- src/timeline/TimelineViewManager.h | 8 +++++--- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp index a7d9797e..f0d23e21 100644 --- a/src/TextInputWidget.cpp +++ b/src/TextInputWidget.cpp @@ -779,8 +779,7 @@ void TextInputWidget::changeCallButtonState(webrtc::State state) { QIcon icon; - if (state == webrtc::State::ICEFAILED || - state == webrtc::State::DISCONNECTED) { + if (state == webrtc::State::ICEFAILED || state == webrtc::State::DISCONNECTED) { callBtn_->setToolTip(tr("Place a call")); icon.addFile(":/icons/icons/ui/place-call.png"); } else { diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index 8f877c41..b4eaadab 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include "Logging.h" #include "WebRTCSession.h" @@ -21,15 +21,10 @@ using webrtc::State; WebRTCSession::WebRTCSession() : QObject() -{ +{ qRegisterMetaType(); qmlRegisterUncreatableMetaObject( - webrtc::staticMetaObject, - "im.nheko", - 1, - 0, - "WebRTCState", - "Can't instantiate enum"); + webrtc::staticMetaObject, "im.nheko", 1, 0, "WebRTCState", "Can't instantiate enum"); connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState); init(); @@ -257,12 +252,10 @@ iceGatheringStateChanged(GstElement *webrtc, nhlog::ui()->debug("WebRTC: GstWebRTCICEGatheringState -> Complete"); if (isoffering_) { emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_); - emit WebRTCSession::instance().stateChanged( - State::OFFERSENT); + emit WebRTCSession::instance().stateChanged(State::OFFERSENT); } else { emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_); - emit WebRTCSession::instance().stateChanged( - State::ANSWERSENT); + emit WebRTCSession::instance().stateChanged(State::ANSWERSENT); } } } @@ -366,8 +359,7 @@ linkNewPad(GstElement *decodebin G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad))) nhlog::ui()->error("WebRTC: unable to link new pad"); else { - emit WebRTCSession::instance().stateChanged( - State::CONNECTED); + emit WebRTCSession::instance().stateChanged(State::CONNECTED); } gst_object_unref(queuepad); } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 3a308be8..09ab8cf8 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -141,8 +141,10 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin isInitialSync_ = true; emit initialSyncChanged(true); }); - connect( - &WebRTCSession::instance(), &WebRTCSession::stateChanged, this, &TimelineViewManager::callStateChanged); + connect(&WebRTCSession::instance(), + &WebRTCSession::stateChanged, + this, + &TimelineViewManager::callStateChanged); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index fdf1603d..243927ef 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -34,8 +34,7 @@ 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(webrtc::State callState READ callState NOTIFY callStateChanged) public: TimelineViewManager(QSharedPointer userSettings, @@ -52,7 +51,10 @@ public: Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } bool isNarrowView() const { return isNarrowView_; } webrtc::State callState() const { return WebRTCSession::instance().state(); } - Q_INVOKABLE bool toggleMuteAudioSource() { return WebRTCSession::instance().toggleMuteAudioSource(); } + Q_INVOKABLE bool toggleMuteAudioSource() + { + return WebRTCSession::instance().toggleMuteAudioSource(); + } Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const; Q_INVOKABLE QColor userColor(QString id, QColor background); Q_INVOKABLE QString escapeEmoji(QString str) const; From 3f73853e4b71b67ab6c8ced2dd4eb1e7f473025e Mon Sep 17 00:00:00 2001 From: trilene Date: Fri, 25 Sep 2020 10:26:36 -0400 Subject: [PATCH 3/9] Move ActiveCallBar Qml to separate file --- resources/qml/ActiveCallBar.qml | 110 +++++++++++++++++++++ resources/qml/Avatar.qml | 2 +- resources/qml/TimelineView.qml | 137 +-------------------------- resources/res.qrc | 1 + src/CallManager.cpp | 13 ++- src/CallManager.h | 7 +- src/WebRTCSession.cpp | 15 ++- src/WebRTCSession.h | 3 +- src/timeline/TimelineViewManager.cpp | 10 +- src/timeline/TimelineViewManager.h | 15 ++- 10 files changed, 166 insertions(+), 147 deletions(-) create mode 100644 resources/qml/ActiveCallBar.qml diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml new file mode 100644 index 00000000..8a63725e --- /dev/null +++ b/resources/qml/ActiveCallBar.qml @@ -0,0 +1,110 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 + +import im.nheko 1.0 + +Rectangle { + id: activeCallBar + visible: timelineManager.callState != WebRTCState.DISCONNECTED + color: "#2ECC71" + implicitHeight: rowLayout.height + 8 + + RowLayout { + id: rowLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 8 + + Avatar { + width: avatarSize + height: avatarSize + + url: timelineManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") + displayName: timelineManager.callPartyName + } + + Label { + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: " " + timelineManager.callPartyName + " " + } + + Image { + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 + source: "qrc:/icons/icons/ui/place-call.png" + } + + Label { + id: callStateLabel + font.pointSize: fontMetrics.font.pointSize * 1.1 + } + + Connections { + target: timelineManager + function onCallStateChanged(state) { + switch (state) { + case WebRTCState.INITIATING: + callStateLabel.text = qsTr("Initiating...") + break; + case WebRTCState.OFFERSENT: + callStateLabel.text = qsTr("Calling...") + break; + case WebRTCState.CONNECTING: + callStateLabel.text = qsTr("Connecting...") + break; + case WebRTCState.CONNECTED: + callStateLabel.text = "00:00" + var d = new Date() + callTimer.startTime = Math.floor(d.getTime() / 1000) + break; + case WebRTCState.DISCONNECTED: + callStateLabel.text = "" + } + } + } + + Timer { + id: callTimer + property int startTime + interval: 1000 + running: timelineManager.callState == WebRTCState.CONNECTED + repeat: true + onTriggered: { + var d = new Date() + let seconds = Math.floor(d.getTime() / 1000 - startTime) + let s = Math.floor(seconds % 60) + let m = Math.floor(seconds / 60) % 60 + let h = Math.floor(seconds / 3600) + callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s) + } + + function pad(n) { + return (n < 10) ? ("0" + n) : n + } + } + + Item { + Layout.fillWidth: true + } + + ImageButton { + width: 24 + height: 24 + src: timelineManager.isMicMuted ? + "qrc:/icons/icons/ui/microphone-unmute.png" : + "qrc:/icons/icons/ui/microphone-mute.png" + + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: timelineManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") + + onClicked: timelineManager.toggleMicMute() + } + + Item { + implicitWidth: 16 + } + } +} diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index a3943806..c030c843 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -14,7 +14,7 @@ Rectangle { Label { anchors.fill: parent - text: timelineManager.escapeEmoji(String.fromCodePoint(displayName.codePointAt(0))) + text: timelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "") textFormat: Text.RichText font.pixelSize: avatar.height/2 verticalAlignment: Text.AlignVCenter diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 4ea15f7b..07c5e1a4 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -498,144 +498,9 @@ Page { } } - Rectangle { - id: activeCallBar - visible: timelineManager.callState != WebRTCState.DISCONNECTED - + ActiveCallBar { Layout.fillWidth: true - implicitHeight: topLayout.height + 16 - color: "#2ECC71" z: 3 - - GridLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: 8 - anchors.verticalCenter: parent.verticalCenter - - Avatar { - Layout.column: 1 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - - width: avatarSize - height: avatarSize - - url: chat.model ? chat.model.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : "" - displayName: chat.model ? chat.model.roomName : qsTr("No room selected") - } - - Label { - Layout.column: 2 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - - font.pointSize: fontMetrics.font.pointSize * 1.1 - text: chat.model ? " " + chat.model.roomName + " " : "" - } - - Image { - Layout.column: 3 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - Layout.preferredWidth: 23 - Layout.preferredHeight: 23 - source: "qrc:/icons/icons/ui/place-call.png" - } - - Connections { - target: timelineManager - function onCallStateChanged(state) { - switch (state) { - case WebRTCState.INITIATING: - callStateLabel.text = "Initiating call..." - break; - case WebRTCState.INITIATED: - callStateLabel.text = "Call initiated..." - break; - case WebRTCState.OFFERSENT: - callStateLabel.text = "Calling..." - break; - case WebRTCState.CONNECTING: - callStateLabel.text = "Connecting..." - break; - case WebRTCState.CONNECTED: - callStateLabel.text = "00:00" - var d = new Date() - callTimer.startTime = Math.floor(d.getTime() / 1000) - break; - } - } - } - - Label { - id: callStateLabel - Layout.column: 4 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - font.pointSize: fontMetrics.font.pointSize * 1.1 - } - - Timer { - id: callTimer - property int startTime - interval: 1000 - running: timelineManager.callState == WebRTCState.CONNECTED - repeat: true - onTriggered: { - var d = new Date() - let seconds = Math.floor(d.getTime() / 1000 - startTime) - let s = Math.floor(seconds % 60) - let m = Math.floor(seconds / 60) % 60 - let h = Math.floor(seconds / 3600) - callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s) - } - - function pad(n) { - return (n < 10) ? ("0" + n) : n - } - } - - Item { - Layout.column: 5 - Layout.fillWidth: true - } - - ImageButton { - Layout.column: 6 - Layout.row: 0 - Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - - width: 22 - height: 22 - src: "qrc:/icons/icons/ui/microphone-mute.png" - - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Mute Mic") - - onClicked: { - if (timelineManager.toggleMuteAudioSource()) { - src = "qrc:/icons/icons/ui/microphone-unmute.png" - ToolTip.text = qsTr("Unmute Mic") - } - else { - src = "qrc:/icons/icons/ui/microphone-mute.png" - ToolTip.text = qsTr("Mute Mic") - } - } - } - - Item { - Layout.column: 7 - implicitWidth: 16 - } - } } } } diff --git a/resources/res.qrc b/resources/res.qrc index b245f48f..63a40431 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -121,6 +121,7 @@ qtquickcontrols2.conf qml/TimelineView.qml + qml/ActiveCallBar.qml qml/Avatar.qml qml/ImageButton.qml qml/MatrixText.qml diff --git a/src/CallManager.cpp b/src/CallManager.cpp index 2c0f9d5a..b1d1a75a 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp @@ -152,6 +152,12 @@ CallManager::sendInvite(const QString &roomid) generateCallID(); nhlog::ui()->debug("WebRTC: call id: {} - creating invite", callid_); + 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; + callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); + emit newCallParty(); playRingtone("qrc:/media/media/ringback.ogg", true); if (!session_.createOffer()) { emit ChatPage::instance()->showNotification("Problem setting up call."); @@ -186,7 +192,7 @@ CallManager::hangUp(CallHangUp::Reason reason) } bool -CallManager::onActiveCall() +CallManager::onActiveCall() const { return session_.state() != webrtc::State::DISCONNECTED; } @@ -252,6 +258,9 @@ 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; + callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); + emit newCallParty(); auto dialog = new dialogs::AcceptCall(caller.user_id, caller.display_name, QString::fromStdString(roomInfo.name), @@ -364,6 +373,8 @@ void CallManager::clear() { roomid_.clear(); + callPartyName_.clear(); + callPartyAvatarUrl_.clear(); callid_.clear(); remoteICECandidates_.clear(); } diff --git a/src/CallManager.h b/src/CallManager.h index 1de8d2ae..640230a4 100644 --- a/src/CallManager.h +++ b/src/CallManager.h @@ -29,7 +29,9 @@ public: void sendInvite(const QString &roomid); void hangUp( mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User); - bool onActiveCall(); + bool onActiveCall() const; + QString callPartyName() const { return callPartyName_; } + QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; } void refreshTurnServer(); public slots: @@ -40,6 +42,7 @@ signals: 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 turnServerRetrieved(const mtx::responses::TurnServer &); private slots: @@ -48,6 +51,8 @@ private slots: private: WebRTCSession &session_; QString roomid_; + QString callPartyName_; + QString callPartyAvatarUrl_; std::string callid_; const uint32_t timeoutms_ = 120000; std::vector remoteICECandidates_; diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index b4eaadab..14f17030 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp @@ -636,7 +636,20 @@ WebRTCSession::createPipeline(int opusPayloadType) } bool -WebRTCSession::toggleMuteAudioSource() +WebRTCSession::isMicMuted() const +{ + if (state_ < State::INITIATED) + return false; + + GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel"); + gboolean muted; + g_object_get(srclevel, "mute", &muted, nullptr); + gst_object_unref(srclevel); + return muted; +} + +bool +WebRTCSession::toggleMicMute() { if (state_ < State::INITIATED) return false; diff --git a/src/WebRTCSession.h b/src/WebRTCSession.h index 9593def9..83cabf5c 100644 --- a/src/WebRTCSession.h +++ b/src/WebRTCSession.h @@ -47,7 +47,8 @@ public: bool acceptAnswer(const std::string &sdp); void acceptICECandidates(const std::vector &); - bool toggleMuteAudioSource(); + bool isMicMuted() const; + bool toggleMicMute(); void end(); void setStunServer(const std::string &stunServer) { stunServer_ = stunServer; } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 09ab8cf8..2b453e56 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -7,7 +7,6 @@ #include #include "BlurhashProvider.h" -#include "CallManager.h" #include "ChatPage.h" #include "ColorImageProvider.h" #include "DelegateChooser.h" @@ -145,6 +144,8 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin &WebRTCSession::stateChanged, this, &TimelineViewManager::callStateChanged); + connect( + callManager_, &CallManager::newCallParty, this, &TimelineViewManager::callPartyChanged); } void @@ -216,6 +217,13 @@ TimelineViewManager::escapeEmoji(QString str) const return utils::replaceEmoji(str); } +void +TimelineViewManager::toggleMicMute() +{ + WebRTCSession::instance().toggleMicMute(); + emit micMuteChanged(); +} + void TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 243927ef..9ff9adac 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -10,6 +10,7 @@ #include #include "Cache.h" +#include "CallManager.h" #include "Logging.h" #include "TimelineModel.h" #include "Utils.h" @@ -19,7 +20,6 @@ class MxcImageProvider; class BlurhashProvider; -class CallManager; class ColorImageProvider; class UserSettings; class ChatPage; @@ -35,6 +35,9 @@ class TimelineViewManager : public QObject Q_PROPERTY( bool isNarrowView MEMBER isNarrowView_ READ isNarrowView NOTIFY narrowViewChanged) Q_PROPERTY(webrtc::State callState READ callState NOTIFY callStateChanged) + Q_PROPERTY(QString callPartyName READ callPartyName NOTIFY callPartyChanged) + Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY callPartyChanged) + Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged) public: TimelineViewManager(QSharedPointer userSettings, @@ -51,10 +54,10 @@ public: Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } bool isNarrowView() const { return isNarrowView_; } webrtc::State callState() const { return WebRTCSession::instance().state(); } - Q_INVOKABLE bool toggleMuteAudioSource() - { - return WebRTCSession::instance().toggleMuteAudioSource(); - } + 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 openImageOverlay(QString mxcUrl, QString eventId) const; Q_INVOKABLE QColor userColor(QString id, QColor background); Q_INVOKABLE QString escapeEmoji(QString str) const; @@ -80,6 +83,8 @@ signals: void showRoomList(); void narrowViewChanged(); void callStateChanged(webrtc::State); + void callPartyChanged(); + void micMuteChanged(); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); From aab6cb88a58d3c070233298e199fd7653cf909df Mon Sep 17 00:00:00 2001 From: trilene Date: Fri, 25 Sep 2020 11:10:45 -0400 Subject: [PATCH 4/9] Fix build for those without GStreamer --- src/WebRTCSession.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index 14f17030..9c8c032d 100644 --- a/src/WebRTCSession.cpp +++ b/src/WebRTCSession.cpp @@ -790,7 +790,13 @@ WebRTCSession::createPipeline(int) } bool -WebRTCSession::toggleMuteAudioSource() +WebRTCSession::isMicMuted() const +{ + return false; +} + +bool +WebRTCSession::toggleMicMute() { return false; } From e57199412aa34ad95e124cad6f8703add92063db Mon Sep 17 00:00:00 2001 From: trilene Date: Fri, 25 Sep 2020 12:09:22 -0400 Subject: [PATCH 5/9] Allow button colors override --- resources/qml/ActiveCallBar.qml | 7 ++++--- resources/qml/ImageButton.qml | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/resources/qml/ActiveCallBar.qml b/resources/qml/ActiveCallBar.qml index 8a63725e..8d837c29 100644 --- a/resources/qml/ActiveCallBar.qml +++ b/resources/qml/ActiveCallBar.qml @@ -92,9 +92,10 @@ Rectangle { ImageButton { width: 24 height: 24 - src: timelineManager.isMicMuted ? - "qrc:/icons/icons/ui/microphone-unmute.png" : - "qrc:/icons/icons/ui/microphone-mute.png" + buttonTextColor: "#000000" + image: timelineManager.isMicMuted ? + ":/icons/icons/ui/microphone-unmute.png" : + ":/icons/icons/ui/microphone-mute.png" hoverEnabled: true ToolTip.visible: hovered diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml index 0a33d376..54399ae7 100644 --- a/resources/qml/ImageButton.qml +++ b/resources/qml/ImageButton.qml @@ -2,8 +2,9 @@ import QtQuick 2.3 import QtQuick.Controls 2.3 AbstractButton { - property string image - property string src + property string image: undefined + property color highlightColor: colors.highlight + property color buttonTextColor: colors.buttonText width: 16 height: 16 id: button @@ -12,7 +13,7 @@ AbstractButton { id: buttonImg // Workaround, can't get icon.source working for now... anchors.fill: parent - source: src ? src : ("image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText)) + source: "image://colorimage/" + image + "?" + (button.hovered ? highlightColor : buttonTextColor) } MouseArea From 5bfe0cd178afa7a7b41ecf3a6fca512efa4bdfbe Mon Sep 17 00:00:00 2001 From: trilene Date: Sat, 26 Sep 2020 12:07:03 -0400 Subject: [PATCH 6/9] Remove duplicate control from settings page --- src/UserSettingsPage.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index f1542ec5..7d81e663 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -511,7 +511,6 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge callsLabel->setFixedHeight(callsLabel->minimumHeight() + LayoutTopMargin); callsLabel->setAlignment(Qt::AlignBottom); callsLabel->setFont(font); - useStunServer_ = new Toggle{this}; auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this}; encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin); From 1a97859930120f39c35cd8f9d5683bb1b08d1809 Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 30 Sep 2020 07:02:24 -0400 Subject: [PATCH 7/9] Added translation using Weblate (Portuguese (Portugal)) Co-authored-by: Tnpod --- resources/langs/nheko_pt_PT.ts | 1993 ++++++++++++++++++++++++++++++++ 1 file changed, 1993 insertions(+) create mode 100644 resources/langs/nheko_pt_PT.ts diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts new file mode 100644 index 00000000..66e07bb4 --- /dev/null +++ b/resources/langs/nheko_pt_PT.ts @@ -0,0 +1,1993 @@ + + + + + Cache + + + You joined this room. + + + + + ChatPage + + + Failed to invite user: %1 + + + + + + Invited user: %1 + + + + + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. + + + + + Room %1 created. + + + + + Confirm invite + + + + + Do you really want to invite %1 (%2)? + + + + + Failed to invite %1 to %2: %3 + + + + + Confirm kick + + + + + Do you really want to kick %1 (%2)? + + + + + Failed to kick %1 to %2: %3 + + + + + Kicked user: %1 + + + + + Confirm ban + + + + + Do you really want to ban %1 (%2)? + + + + + Failed to ban %1 in %2: %3 + + + + + Banned user: %1 + + + + + Confirm unban + + + + + Do you really want to unban %1 (%2)? + + + + + Failed to unban %1 in %2: %3 + + + + + Unbanned user: %1 + + + + + Failed to upload media. Please try again. + + + + + Cache migration failed! + + + + + Incompatible cache version + + + + + The cache on your disk is newer than this version of Nheko supports. Please update or clear your cache. + + + + + Failed to restore OLM account. Please login again. + + + + + Failed to restore save data. Please login again. + + + + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + + + + + + Please try to login again: %1 + + + + + Failed to join room: %1 + + + + + You joined the room + + + + + Failed to remove invite: %1 + + + + + Room creation failed: %1 + + + + + Failed to leave room: %1 + + + + + CommunitiesListItem + + + All rooms + + + + + Favourite rooms + + + + + Low priority rooms + + + + + Server Notices + Tag translation for m.server_notice + + + + + + (tag) + + + + + (community) + + + + + EditModal + + + Apply + + + + + Cancel + + + + + Name + + + + + Topic + + + + + EmojiPicker + + + + Search + + + + + People + + + + + Nature + + + + + Food + + + + + Activity + + + + + Travel + + + + + Objects + + + + + Symbols + + + + + Flags + + + + + EncryptionIndicator + + + Encrypted + + + + + This message is not encrypted! + + + + + EventStore + + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted. + + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed as %1. + + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. + + + + + -- Replay attack! This message index was reused! -- + + + + + -- Message by unverified device! -- + + + + + InviteeItem + + + Remove + + + + + LoginPage + + + Matrix ID + + + + + e.g @joe:matrix.org + + + + + Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :. +You can also put your homeserver address there, if your server doesn't support .well-known lookup. +Example: @user:server.my +If Nheko fails to discover your homeserver, it will show you a field to enter the server manually. + + + + + Password + + + + + Device name + + + + + A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used. + + + + + The address that can be used to contact you homeservers client API. +Example: https://server.my:8787 + + + + + + LOGIN + + + + + Autodiscovery failed. Received malformed response. + + + + + Autodiscovery failed. Unknown error when requesting .well-known. + + + + + The required endpoints were not found. Possibly not a Matrix server. + + + + + Received malformed response. Make sure the homeserver domain is valid. + + + + + An unknown error occured. Make sure the homeserver domain is valid. + + + + + SSO LOGIN + + + + + Empty password + + + + + SSO login failed + + + + + MemberList + + + Room members + + + + + OK + + + + + MessageDelegate + + + + redacted + + + + + Encryption enabled + + + + + room name changed to: %1 + + + + + removed room name + + + + + topic changed to: %1 + + + + + removed topic + + + + + %1 created and configured room: %2 + + + + + %1 placed a voice call. + + + + + %1 placed a video call. + + + + + %1 placed a call. + + + + + Negotiating call... + + + + + %1 answered the call. + + + + + %1 ended the call. + + + + + Placeholder + + + unimplemented event: + + + + + QuickSwitcher + + + Search for a room... + + + + + RegisterPage + + + Username + + + + + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. + + + + + Password + + + + + Please choose a secure password. The exact requirements for password strength may depend on your server. + + + + + Password confirmation + + + + + Homeserver + + + + + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. + + + + + REGISTER + + + + + No supported registration flows! + + + + + Invalid username + + + + + Password is not long enough (min 8 chars) + + + + + Passwords don't match + + + + + Invalid server name + + + + + RoomInfo + + + no version stored + + + + + RoomInfoListItem + + + Leave room + + + + + Tag room as: + + + + + Favourite + Standard matrix tag for favourites + + + + + Low Priority + Standard matrix tag for low priority rooms + + + + + Server Notice + Standard matrix tag for server notices + + + + + Adds or removes the specified tag. + WhatsThis hint for tag menu actions + + + + + New tag... + Add a new tag to the room + + + + + New Tag + Tag name prompt title + + + + + Tag: + Tag name prompt + + + + + Accept + + + + + Decline + + + + + SideBarActions + + + User settings + + + + + Create new room + + + + + Join a room + + + + + Start a new chat + + + + + Room directory + + + + + StatusIndicator + + + Failed + + + + + Sent + + + + + Received + + + + + Read + + + + + TextInputWidget + + + Send a file + + + + + + Write a message... + + + + + Send a message + + + + + Emoji + + + + + Select a file + + + + + All Files (*) + + + + + Place a call + + + + + Hang up + + + + + Connection lost. Nheko is trying to re-connect... + + + + + TimelineModel + + + Message redaction failed: %1 + + + + + + + + Failed to encrypt event, sending aborted! + + + + + Save image + + + + + Save video + + + + + Save audio + + + + + Save file + + + + + %1 and %2 are typing. + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + + + + + + + %1 opened the room to the public. + + + + + %1 made this room require and invitation to join. + + + + + %1 made the room open to guests. + + + + + %1 has closed the room to guest access. + + + + + %1 made the room history world readable. Events may be now read by non-joined people. + + + + + %1 set the room history visible to members from this point on. + + + + + %1 set the room history visible to members since they were invited. + + + + + %1 set the room history visible to members since they joined the room. + + + + + %1 has changed the room's permissions. + + + + + %1 was invited. + + + + + %1 changed their display name and avatar. + + + + + %1 changed their display name. + + + + + %1 changed their avatar. + + + + + %1 changed some profile info. + + + + + %1 joined. + + + + + %1 rejected their invite. + + + + + Revoked the invite to %1. + + + + + %1 left the room. + + + + + Kicked %1. + + + + + Unbanned %1. + + + + + %1 was banned. + + + + + %1 redacted their knock. + + + + + You joined this room. + + + + + Rejected the knock from %1. + + + + + %1 left after having already left! + This is a leave event after the user already left and shouldn't happen apart from state resets + + + + + Reason: %1 + + + + + %1 knocked. + + + + + TimelineRow + + + React + + + + + Reply + + + + + Options + + + + + TimelineView + + + React + + + + + Reply + + + + + Read receipts + + + + + Mark as read + + + + + View raw message + + + + + View decrypted raw message + + + + + Redact message + + + + + Save as + + + + + No room open + + + + + Back to room list + + + + + + No room selected + + + + + Room options + + + + + Invite users + + + + + Members + + + + + Leave room + + + + + Settings + + + + + Close + + + + + TrayIcon + + + Show + + + + + Quit + + + + + UserInfoWidget + + + Logout + + + + + Set custom status message + + + + + Custom status message + + + + + Status: + + + + + Set presence automatically + + + + + Online + + + + + Unavailable + + + + + Offline + + + + + UserSettingsPage + + + Minimize to tray + + + + + Start in tray + + + + + Group's sidebar + + + + + Circular Avatars + + + + + CALLS + + + + + Keep the application running in the background after closing the client window. + + + + + Start the application in the background without showing the client window. + + + + + Change the appearance of user avatars in chats. +OFF - square, ON - Circle. + + + + + Show a column containing groups and tags next to the room list. + + + + + Decrypt messages in sidebar + + + + + Decrypt the messages shown in the sidebar. +Only affects messages in encrypted chats. + + + + + Show buttons in timeline + + + + + Show buttons to quickly reply, react or access additional options next to each message. + + + + + Limit width of timeline + + + + + Set the max width of messages in the timeline (in pixels). This can help readability on wide screen, when Nheko is maximised + + + + + Typing notifications + + + + + Show who is typing in a room. +This will also enable or disable sending typing notifications to others. + + + + + Sort rooms by unreads + + + + + Display rooms with new messages first. +If this is off, the list of rooms will only be sorted by the timestamp of the last message in a room. +If this is on, rooms which have active notifications (the small circle with a number in it) will be sorted on top. Rooms, that you have muted, will still be sorted by timestamp, since you don't seem to consider them as important as the other rooms. + + + + + Read receipts + + + + + Show if your message was read. +Status is displayed next to timestamps. + + + + + Send messages as Markdown + + + + + Allow using markdown in messages. +When disabled, all messages are sent as a plain text. + + + + + Desktop notifications + + + + + Notify about received message when the client is not currently focused. + + + + + Alert on notification + + + + + Show an alert when a message is received. +This usually causes the application icon in the task bar to animate in some fashion. + + + + + Highlight message on hover + + + + + Change the background color of messages when you hover over them. + + + + + Large Emoji in timeline + + + + + Make font size larger if messages with only a few emojis are displayed. + + + + + Scale factor + + + + + Change the scale factor of the whole user interface. + + + + + Font size + + + + + Font Family + + + + + Theme + + + + + Allow fallback call assist server + + + + + Will use turn.matrix.org as assist when your home server does not offer one. + + + + + Device ID + + + + + Device Fingerprint + + + + + Session Keys + + + + + IMPORT + + + + + EXPORT + + + + + ENCRYPTION + + + + + GENERAL + + + + + INTERFACE + + + + + Emoji Font Family + + + + + Open Sessions File + + + + + + + + + + + + + + Error + + + + + + File Password + + + + + Enter the passphrase to decrypt the file: + + + + + + The password cannot be empty + + + + + Enter passphrase to encrypt your session keys: + + + + + File to save the exported session keys + + + + + WelcomePage + + + Welcome to nheko! The desktop client for the Matrix protocol. + + + + + Enjoy your stay! + + + + + REGISTER + + + + + LOGIN + + + + + descriptiveTime + + + Yesterday + + + + + dialogs::AcceptCall + + + Accept + + + + + Reject + + + + + dialogs::CreateRoom + + + Create room + + + + + Cancel + + + + + Name + + + + + Topic + + + + + Alias + + + + + Room Visibility + + + + + Room Preset + + + + + Direct Chat + + + + + dialogs::FallbackAuth + + + Open Fallback in Browser + + + + + Cancel + + + + + Confirm + + + + + Open the fallback, follow the steps and confirm after completing them. + + + + + dialogs::InviteUsers + + + Cancel + + + + + User ID to invite + + + + + dialogs::JoinRoom + + + Join + + + + + Cancel + + + + + Room ID or alias + + + + + dialogs::LeaveRoom + + + Cancel + + + + + Are you sure you want to leave? + + + + + dialogs::Logout + + + Cancel + + + + + Logout. Are you sure? + + + + + dialogs::PlaceCall + + + Voice + + + + + Cancel + + + + + dialogs::PreviewUploadOverlay + + + Upload + + + + + Cancel + + + + + Media type: %1 +Media size: %2 + + + + + + dialogs::ReCaptcha + + + Cancel + + + + + Confirm + + + + + Solve the reCAPTCHA and press the confirm button + + + + + dialogs::ReadReceipts + + + Read receipts + + + + + Close + + + + + dialogs::ReceiptItem + + + Today %1 + + + + + Yesterday %1 + + + + + dialogs::RoomSettings + + + Settings + + + + + Info + + + + + Internal ID + + + + + Room Version + + + + + Notifications + + + + + Muted + + + + + Mentions only + + + + + All messages + + + + + Room access + + + + + Anyone and guests + + + + + Anyone + + + + + Invited users + + + + + Encryption + + + + + End-to-End Encryption + + + + + Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards. + + + + + Respond to key requests + + + + + Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed. + + + + + %n member(s) + + + + + + + + Failed to enable encryption: %1 + + + + + Select an avatar + + + + + All Files (*) + + + + + The selected file is not an image + + + + + Error while reading file: %1 + + + + + + Failed to upload image: %s + + + + + dialogs::UserProfile + + + Ban the user from the room + + + + + Ignore messages from this user + + + + + Kick the user from the room + + + + + Start a conversation + + + + + Confirm DM + + + + + Do you really want to invite %1 (%2) to a direct chat? + + + + + Devices + + + + + emoji::Panel + + + Smileys & People + + + + + Animals & Nature + + + + + Food & Drink + + + + + Activity + + + + + Travel & Places + + + + + Objects + + + + + Symbols + + + + + Flags + + + + + message-description sent: + + + You sent an audio clip + + + + + %1 sent an audio clip + + + + + You sent an image + + + + + %1 sent an image + + + + + You sent a file + + + + + %1 sent a file + + + + + You sent a video + + + + + %1 sent a video + + + + + You sent a sticker + + + + + %1 sent a sticker + + + + + You sent a notification + + + + + %1 sent a notification + + + + + You: %1 + + + + + %1: %2 + + + + + You sent an encrypted message + + + + + %1 sent an encrypted message + + + + + You placed a call + + + + + %1 placed a call + + + + + You answered a call + + + + + %1 answered a call + + + + + You ended a call + + + + + %1 ended a call + + + + + popups::UserMentions + + + This Room + + + + + All Rooms + + + + + utils + + + Unknown Message Type + + + + From 4caa206483c3d81df1b1545a2915d2bfc5bb3fac Mon Sep 17 00:00:00 2001 From: trilene Date: Thu, 1 Oct 2020 08:21:51 -0400 Subject: [PATCH 8/9] Bump mtxclient --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f5628e2a..938fbc8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -339,7 +339,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG ac680e971d437eb2135ef994dcb58c0bbb5bdf61 + GIT_TAG 21a55ba65d0712a441fbef2af2ede66771430247 ) FetchContent_MakeAvailable(MatrixClient) else() From 28e9a7ad402fad5fdede9cf22dceb98bb456b582 Mon Sep 17 00:00:00 2001 From: trilene Date: Thu, 1 Oct 2020 08:47:01 -0400 Subject: [PATCH 9/9] Bump mtxclient --- io.github.NhekoReborn.Nheko.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 8ff82636..842017f7 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -146,7 +146,7 @@ "name": "mtxclient", "sources": [ { - "commit": "ac680e971d437eb2135ef994dcb58c0bbb5bdf61", + "commit": "21a55ba65d0712a441fbef2af2ede66771430247", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" }