diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d0d6dd6..2365ac09 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 @@ -254,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 @@ -464,7 +462,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 @@ -473,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 e8ebd5fc..00edb7e5 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -1,3 +1,4 @@ +import "./voip" import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 @@ -10,6 +11,13 @@ Rectangle { Layout.preferredHeight: textInput.height Layout.minimumHeight: 40 + Component { + id: placeCallDialog + + PlaceCall { + } + } + RowLayout { id: inputBar @@ -17,18 +25,33 @@ Rectangle { spacing: 16 ImageButton { - visible: TimelineManager.callsSupported + visible: CallManager.callsSupported + opacity: CallManager.haveCallInvite ? 0.3 : 1.0 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 - onClicked: TimelineManager.timeline.input.callButton() + onClicked: { + if (TimelineManager.timeline) { + if (CallManager.haveCallInvite) { + return; + } + else if (CallManager.isOnCall) { + CallManager.hangUp(); + } + else { + CallManager.refreshDevices(); + var dialog = placeCallDialog.createObject(timelineRoot); + dialog.open(); + } + } + } } ImageButton { @@ -39,7 +62,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..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: TimelineManager.onVideoCall ? "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 68% rename from resources/qml/ActiveCallBar.qml rename to resources/qml/voip/ActiveCallBar.qml index 3059e213..0e932e13 100644 --- a/resources/qml/ActiveCallBar.qml +++ b/resources/qml/voip/ActiveCallBar.qml @@ -1,19 +1,19 @@ +import "../" 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" + visible: CallManager.isOnCall + color: callInviteBar.color implicitHeight: visible ? rowLayout.height + 8 : 0 MouseArea { anchors.fill: parent onClicked: { - if (TimelineManager.onVideoCall) + if (CallManager.isVideo) stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1; } @@ -30,63 +30,66 @@ 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.callParty } Label { + Layout.leftMargin: 8 font.pointSize: fontMetrics.font.pointSize * 1.1 - text: " " + TimelineManager.callPartyName + " " + text: CallManager.callParty + color: "#000000" } Image { + Layout.leftMargin: 4 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.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" } Label { id: callStateLabel font.pointSize: fontMetrics.font.pointSize * 1.1 + color: "#000000" } Item { - state: TimelineManager.callState states: [ State { name: "OFFERSENT" - when: state == WebRTCState.OFFERSENT + when: CallManager.callState == WebRTCState.OFFERSENT PropertyChanges { target: callStateLabel - text: "Calling..." + text: qsTr("Calling...") } }, State { name: "CONNECTING" - when: state == WebRTCState.CONNECTING + when: CallManager.callState == WebRTCState.CONNECTING PropertyChanges { target: callStateLabel - text: "Connecting..." + text: qsTr("Connecting...") } }, State { name: "ANSWERSENT" - when: state == WebRTCState.ANSWERSENT + when: CallManager.callState == WebRTCState.ANSWERSENT PropertyChanges { target: callStateLabel - text: "Connecting..." + text: qsTr("Connecting...") } }, State { name: "CONNECTED" - when: state == WebRTCState.CONNECTED + when: CallManager.callState == WebRTCState.CONNECTED PropertyChanges { target: callStateLabel @@ -100,13 +103,13 @@ Rectangle { PropertyChanges { target: stackLayout - currentIndex: TimelineManager.onVideoCall ? 1 : 0 + currentIndex: CallManager.isVideo ? 1 : 0 } }, State { name: "DISCONNECTED" - when: state == WebRTCState.DISCONNECTED + when: CallManager.callState == WebRTCState.DISCONNECTED PropertyChanges { target: callStateLabel @@ -132,7 +135,7 @@ Rectangle { } interval: 1000 - running: TimelineManager.callState == WebRTCState.CONNECTED + running: CallManager.callState == WebRTCState.CONNECTED repeat: true onTriggered: { var d = new Date(); @@ -149,36 +152,28 @@ Rectangle { } ImageButton { - visible: TimelineManager.onVideoCall + visible: CallManager.haveLocalVideo width: 24 height: 24 buttonTextColor: "#000000" image: ":/icons/icons/ui/toggle-camera-view.png" hoverEnabled: true ToolTip.visible: hovered - ToolTip.text: "Toggle camera view" - onClicked: TimelineManager.toggleCameraView() - } - - Item { - implicitWidth: 8 + ToolTip.text: qsTr("Toggle camera view") + onClicked: CallManager.toggleCameraView() } ImageButton { + Layout.leftMargin: 8 + Layout.rightMargin: 16 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 { - implicitWidth: 16 - } - } - } diff --git a/resources/qml/voip/CallDevices.qml b/resources/qml/voip/CallDevices.qml new file mode 100644 index 00000000..f0847b14 --- /dev/null +++ b/resources/qml/voip/CallDevices.qml @@ -0,0 +1,79 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +Popup { + + modal: true + anchors.centerIn: parent + background: Rectangle { + color: colors.window + border.color: colors.windowText + } + + palette: colors + + ColumnLayout { + + spacing: 16 + + ColumnLayout { + spacing: 8 + + Layout.topMargin: 8 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + + RowLayout { + + Image { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + source: "image://colorimage/:/icons/icons/ui/microphone-unmute.png?" + colors.windowText + } + + ComboBox { + id: micCombo + Layout.fillWidth: true + model: CallManager.mics + } + } + + RowLayout { + + visible: CallManager.isVideo && CallManager.cameras.length > 0 + + Image { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + source: "image://colorimage/:/icons/icons/ui/video-call.png?" + colors.windowText + } + + ComboBox { + id: cameraCombo + Layout.fillWidth: true + model: CallManager.cameras + } + } + } + + DialogButtonBox { + + Layout.leftMargin: 128 + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: { + Settings.microphone = micCombo.currentText + if (cameraCombo.visible) { + Settings.camera = cameraCombo.currentText + } + close(); + } + + onRejected: { + close(); + } + } + } +} diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml new file mode 100644 index 00000000..5021949a --- /dev/null +++ b/resources/qml/voip/CallInviteBar.qml @@ -0,0 +1,126 @@ +import "../" +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +Rectangle { + + visible: CallManager.haveCallInvite + color: "#2ECC71" + implicitHeight: visible ? rowLayout.height + 8 : 0 + + Component { + id: devicesDialog + CallDevices { + } + } + + Component { + id: deviceError + DeviceError { + } + } + + 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 { + Layout.leftMargin: 8 + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: CallManager.callParty + color: "#000000" + } + + 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" + } + + Label { + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: CallManager.isVideo ? qsTr("Video Call") : qsTr("Voice Call") + color: "#000000" + } + + Item { + Layout.fillWidth: true + } + + ImageButton { + Layout.rightMargin: 16 + width: 20 + height: 20 + buttonTextColor: "#000000" + image: ":/icons/icons/ui/settings.png" + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Devices") + onClicked: { + CallManager.refreshDevices(); + var dialog = devicesDialog.createObject(timelineRoot); + dialog.open(); + } + } + + 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: colors + + onClicked: { + if (CallManager.mics.length == 0) { + var dialog = deviceError.createObject(timelineRoot, { + "errorString": qsTr("No microphone found."), + "image": ":/icons/icons/ui/place-call.png" + }); + dialog.open(); + return; + } + else if (!CallManager.mics.includes(Settings.microphone)) { + var dialog = deviceError.createObject(timelineRoot, { + "errorString": qsTr("Unknown microphone: ") + Settings.microphone, + "image": ":/icons/icons/ui/place-call.png" + }); + dialog.open(); + return; + } + if (CallManager.isVideo && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) { + var dialog = deviceError.createObject(timelineRoot, { + "errorString": qsTr("Unknown camera: ") + Settings.camera, + "image": ":/icons/icons/ui/video-call.png" + }); + dialog.open(); + return; + } + CallManager.acceptInvite(); + } + } + + Button { + Layout.rightMargin: 16 + icon.source: "qrc:/icons/icons/ui/end-call.png" + text: qsTr(" Decline ") + palette: colors + + onClicked: { + CallManager.hangUp(); + } + } + } +} diff --git a/resources/qml/voip/DeviceError.qml b/resources/qml/voip/DeviceError.qml new file mode 100644 index 00000000..a6411b95 --- /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 image + + modal: true + anchors.centerIn: parent + background: Rectangle { + color: colors.window + border.color: colors.windowText + } + + RowLayout { + + Image { + Layout.preferredWidth: 16 + Layout.preferredHeight: 16 + source: "image://colorimage/" + image + "?" + colors.windowText + } + + Label { + text: errorString + color: colors.windowText + } + } +} diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml new file mode 100644 index 00000000..95383d95 --- /dev/null +++ b/resources/qml/voip/PlaceCall.qml @@ -0,0 +1,146 @@ +import "../" +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +Popup { + + modal: true + anchors.centerIn: parent + palette: colors + background: Rectangle { + color: colors.window + border.color: colors.windowText + } + + Component { + id: deviceError + DeviceError { + } + } + + ColumnLayout { + id: columnLayout + + spacing: 16 + + RowLayout { + + Layout.topMargin: 8 + Layout.leftMargin: 8 + + Label { + text: qsTr("Place a call to ") + TimelineManager.timeline.roomName + "?" + color: colors.windowText + } + + Item { + Layout.fillWidth: true + } + } + + RowLayout { + id: buttonLayout + + Layout.leftMargin: 8 + Layout.rightMargin: 8 + + function validateMic() { + if (CallManager.mics.length == 0) { + var dialog = deviceError.createObject(timelineRoot, { + "errorString": qsTr("No microphone found."), + "image": ":/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 + } + + Button { + text: qsTr(" Voice ") + icon.source: "qrc:/icons/icons/ui/place-call.png" + onClicked: { + if (buttonLayout.validateMic()) { + Settings.microphone = micCombo.currentText + 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 (buttonLayout.validateMic()) { + Settings.microphone = micCombo.currentText + Settings.camera = cameraCombo.currentText + CallManager.sendInvite(TimelineManager.timeline.roomId(), true); + close(); + } + } + } + + Button { + text: qsTr("Cancel") + onClicked: { + close(); + } + } + } + + ColumnLayout { + spacing: 8 + + RowLayout { + + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.bottomMargin: cameraCombo.visible ? 0 : 8 + + Image { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + source: "image://colorimage/:/icons/icons/ui/microphone-unmute.png?" + colors.windowText + } + + ComboBox { + id: micCombo + Layout.fillWidth: true + model: CallManager.mics + } + } + + RowLayout { + + visible: CallManager.cameras.length > 0 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.bottomMargin: 8 + + Image { + Layout.preferredWidth: 22 + Layout.preferredHeight: 22 + source: "image://colorimage/:/icons/icons/ui/video-call.png?" + colors.windowText + } + + ComboBox { + id: cameraCombo + Layout.fillWidth: true + model: CallManager.cameras + } + } + } + } +} 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..bceeb298 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,12 @@ qml/device-verification/NewVerificationRequest.qml qml/device-verification/Failed.qml qml/device-verification/Success.qml + qml/voip/ActiveCallBar.qml + qml/voip/CallDevices.qml + qml/voip/CallInviteBar.qml + qml/voip/DeviceError.qml + qml/voip/PlaceCall.qml + qml/voip/VideoCall.qml media/ring.ogg diff --git a/src/CallManager.cpp b/src/CallManager.cpp index 89cfeaf9..f725d49f 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp @@ -10,11 +10,9 @@ #include "CallManager.h" #include "ChatPage.h" #include "Logging.h" -#include "MainWindow.h" #include "MatrixClient.h" +#include "UserSettingsPage.h" #include "Utils.h" -#include "WebRTCSession.h" -#include "dialogs/AcceptCall.h" #include "mtx/responses/turn_server.hpp" @@ -112,6 +110,23 @@ CallManager::CallManager(QObject *parent) default: break; } + 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_, @@ -144,7 +159,7 @@ CallManager::CallManager(QObject *parent) void CallManager::sendInvite(const QString &roomid, bool isVideo) { - if (onActiveCall()) + if (isOnCall()) return; auto roomInfo = cache::singleRoomInfo(roomid.toStdString()); @@ -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 { @@ -206,12 +220,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 +265,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, @@ -277,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 @@ -332,7 +333,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,15 +351,19 @@ CallManager::handleEvent(const RoomEvent &callAnswerEvent) callAnswerEvent.content.call_id, callAnswerEvent.sender); - if (!onActiveCall() && 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(); - MainWindow::instance()->hideOverlay(); + if (!isOnCall()) { + emit ChatPage::instance()->showNotification( + "Call answered on another device."); + stopRingtone(); + haveCallInvite_ = false; + emit newInviteState(); + } 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."); @@ -375,10 +380,42 @@ 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 +CallManager::toggleMicMute() +{ + session_.toggleMicMute(); + emit micMuteChanged(); +} + +bool +CallManager::callsSupported() const +{ +#ifdef GSTREAMER_AVAILABLE + return true; +#else + return false; +#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 @@ -393,9 +430,13 @@ void CallManager::clear() { roomid_.clear(); - callPartyName_.clear(); + callParty_.clear(); callPartyAvatarUrl_.clear(); callid_.clear(); + isVideo_ = false; + haveCallInvite_ = false; + emit newInviteState(); + inviteSDP_.clear(); remoteICECandidates_.clear(); } @@ -403,11 +444,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 8004e838..7d388efd 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" @@ -15,34 +16,59 @@ namespace mtx::responses { struct TurnServer; } +class QStringList; class QUrl; -class WebRTCSession; 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 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) + 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 onActiveCall() const; - QString callPartyName() const { return callPartyName_; } + 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_; } + 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 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(); + 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 newCallParty(); - void newVideoCallState(); + void newInviteState(); + void newCallState(); + void micMuteChanged(); + void devicesChanged(); void turnServerRetrieved(const mtx::responses::TurnServer &); private slots: @@ -51,10 +77,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_; @@ -68,6 +97,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/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/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"); diff --git a/src/WebRTCSession.cpp b/src/WebRTCSession.cpp index 0770a439..094a2906 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: { @@ -553,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); @@ -1150,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 { @@ -1274,6 +1292,7 @@ WebRTCSession::refreshDevices() addDevice(GST_DEVICE_CAST(l->data)); g_list_free(devices); } + emit devicesChanged(); #endif } @@ -1324,6 +1343,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 57002f8f..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_; } @@ -75,6 +76,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/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 5cbc33e0..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,48 +591,6 @@ InputBar::showPreview(const QMimeData &source, QString path, const QStringList & }); } -void -InputBar::callButton() -{ - auto callManager_ = ChatPage::instance()->callManager(); - if (callManager_->onActiveCall()) { - 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); 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)) 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() {