/* * Copyright (C) 2020-2022 Savoir-faire Linux Inc. * Author: Edric Ladent Milaret * Author: Anthony Léonard * Author: Olivier Soldano * Author: Andreas Traczyk * Author: Isa Nanic * Author: Mingrui Zhang * Author: Sébastien Blin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "calladapter.h" #include "systemtray.h" #include "utils.h" #include "qmlregister.h" #include #include #include #include CallAdapter::CallAdapter(SystemTray* systemTray, LRCInstance* instance, QObject* parent) : QmlAdapterBase(instance, parent) , systemTray_(systemTray) { participantsModel_.reset(new CallParticipantsModel(lrcInstance_, this)); QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, participantsModel_.get(), "CallParticipantsModel"); participantsModelFiltered_.reset( new GenericParticipantsFilterModel(lrcInstance_, participantsModel_.get())); QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, participantsModelFiltered_.get(), "GenericParticipantsFilterModel"); activeParticipantsModel_.reset( new ActiveParticipantsFilterModel(lrcInstance_, participantsModel_.get())); QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, activeParticipantsModel_.get(), "ActiveParticipantsFilterModel"); overlayModel_.reset(new CallOverlayModel(lrcInstance_, this)); QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, overlayModel_.get(), "CallOverlayModel"); accountId_ = lrcInstance_->get_currentAccountId(); if (!accountId_.isEmpty()) connectCallModel(accountId_); connect(&lrcInstance_->behaviorController(), &BehaviorController::showIncomingCallView, this, &CallAdapter::onShowIncomingCallView); connect(&lrcInstance_->behaviorController(), &BehaviorController::showCallView, this, &CallAdapter::onShowCallView); connect(lrcInstance_, &LRCInstance::currentAccountIdChanged, this, &CallAdapter::onAccountChanged); #ifdef Q_OS_LINUX // notification responses (gnu/linux currently) connect(systemTray_, &SystemTray::answerCallActivated, [this](const QString& accountId, const QString& convUid) { acceptACall(accountId, convUid); Q_EMIT lrcInstance_->notificationClicked(); lrcInstance_->selectConversation(convUid, accountId); updateCall(convUid, accountId); Q_EMIT lrcInstance_->conversationUpdated(convUid, accountId); }); connect(systemTray_, &SystemTray::declineCallActivated, [this](const QString& accountId, const QString& convUid) { hangUpACall(accountId, convUid); }); #endif connect(&lrcInstance_->behaviorController(), &BehaviorController::callStatusChanged, this, QOverload::of(&CallAdapter::onCallStatusChanged)); connect(lrcInstance_, &LRCInstance::selectedConvUidChanged, this, &CallAdapter::saveConferenceSubcalls); } void CallAdapter::onAccountChanged() { accountId_ = lrcInstance_->get_currentAccountId(); connectCallModel(accountId_); } void CallAdapter::onCallStatusChanged(const QString& accountId, const QString& callId) { set_hasCall(lrcInstance_->hasActiveCall()); #ifdef Q_OS_LINUX auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId); auto& callModel = accInfo.callModel; const auto call = callModel->getCall(callId); const auto& convInfo = lrcInstance_->getConversationFromCallId(callId, accountId); if (convInfo.uid.isEmpty()) return; // handle notifications if (call.status == lrc::api::call::Status::IN_PROGRESS) { // Call answered and in progress; close the notification systemTray_->hideNotification(QString("%1;%2").arg(accountId).arg(convInfo.uid)); } else if (call.status == lrc::api::call::Status::ENDED) { // Call ended; close the notification if (systemTray_->hideNotification(QString("%1;%2").arg(accountId).arg(convInfo.uid)) && call.startTime.time_since_epoch().count() == 0) { // This was a missed call; show a missed call notification auto convAvatar = Utils::conversationAvatar(lrcInstance_, convInfo.uid, QSize(50, 50), accountId); auto& accInfo = lrcInstance_->getAccountInfo(accountId); auto from = accInfo.conversationModel->title(convInfo.uid); auto notifId = QString("%1;%2").arg(accountId).arg(convInfo.uid); systemTray_->showNotification(notifId, tr("Missed call"), tr("Missed call with %1").arg(from), NotificationType::CHAT, Utils::QImageToByteArray(convAvatar)); } } #else Q_UNUSED(accountId) Q_UNUSED(callId) #endif } void CallAdapter::onParticipantAdded(const QString& callId, int index) { auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_); auto& callModel = accInfo.callModel; try { if (lrcInstance_->get_selectedConvUid().isEmpty()) return; const auto& currentConvInfo = accInfo.conversationModel.get()->getConversationForUid( lrcInstance_->get_selectedConvUid()); if (callId != currentConvInfo->get().callId && callId != currentConvInfo->get().confId) { qDebug() << "trying to update not current conf"; return; } auto infos = getConferencesInfos(); if (index < infos.size()) participantsModel_->addParticipant(index, infos[index]); } catch (...) { } } void CallAdapter::onParticipantRemoved(const QString& callId, int index) { auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_); auto& callModel = accInfo.callModel; try { if (lrcInstance_->get_selectedConvUid().isEmpty()) return; const auto& currentConvInfo = accInfo.conversationModel.get()->getConversationForUid( lrcInstance_->get_selectedConvUid()); if (callId != currentConvInfo->get().callId && callId != currentConvInfo->get().confId) { qDebug() << "trying to update not current conf"; return; } participantsModel_->removeParticipant(index); } catch (...) { } } void CallAdapter::onParticipantUpdated(const QString& callId, int index) { auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_); auto& callModel = accInfo.callModel; try { if (lrcInstance_->get_selectedConvUid().isEmpty()) return; const auto& currentConvInfo = accInfo.conversationModel.get()->getConversationForUid( lrcInstance_->get_selectedConvUid()); if (callId != currentConvInfo->get().callId && callId != currentConvInfo->get().confId) { qDebug() << "trying to update not current conf"; return; } auto infos = getConferencesInfos(); participantsModel_->updateParticipant(index, infos[index]); } catch (...) { } } void CallAdapter::onCallStatusChanged(const QString& callId, int code) { Q_UNUSED(code) auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_); auto& callModel = accInfo.callModel; try { const auto call = callModel->getCall(callId); /* * Change status label text. */ const auto& convInfo = lrcInstance_->getConversationFromCallId(callId); if (!convInfo.uid.isEmpty()) { Q_EMIT callStatusChanged(static_cast(call.status), accountId_, convInfo.uid); updateCallOverlay(convInfo); } switch (call.status) { case lrc::api::call::Status::INVALID: case lrc::api::call::Status::INACTIVE: case lrc::api::call::Status::ENDED: case lrc::api::call::Status::PEER_BUSY: case lrc::api::call::Status::TIMEOUT: case lrc::api::call::Status::TERMINATING: { const auto& convInfo = lrcInstance_->getConversationFromCallId(callId); if (convInfo.uid.isEmpty()) { return; } const auto& currentConvId = lrcInstance_->get_selectedConvUid(); const auto& currentConvInfo = lrcInstance_->getConversationFromConvUid(currentConvId); // was it a conference and now is a dialog? if (currentConvInfo.confId.isEmpty() && currentConfSubcalls_.size() == 2) { auto it = std::find_if(currentConfSubcalls_.cbegin(), currentConfSubcalls_.cend(), [&callId](const QString& cid) { return cid != callId; }); if (it != currentConfSubcalls_.cend()) { // select the conversation using the other callId auto otherCall = lrcInstance_->getCurrentCallModel()->getCall(*it); if (otherCall.status == lrc::api::call::Status::IN_PROGRESS) { const auto& otherConv = lrcInstance_->getConversationFromCallId(*it); if (!otherConv.uid.isEmpty() && otherConv.uid != convInfo.uid) { lrcInstance_->selectConversation(otherConv.uid); Q_EMIT lrcInstance_->conversationUpdated(otherConv.uid, accountId_); updateCall(otherConv.uid); } } // then clear the list currentConfSubcalls_.clear(); return; } } else { // okay, still a conference, so just update the subcall list and this call saveConferenceSubcalls(); Q_EMIT lrcInstance_->conversationUpdated(currentConvInfo.uid, accountId_); updateCall(currentConvInfo.uid); return; } Q_EMIT lrcInstance_->conversationUpdated(convInfo.uid, accountId_); updateCall(currentConvInfo.uid); preventScreenSaver(false); break; } case lrc::api::call::Status::CONNECTED: case lrc::api::call::Status::IN_PROGRESS: { const auto& convInfo = lrcInstance_->getConversationFromCallId(callId, accountId_); if (!convInfo.uid.isEmpty() && convInfo.uid == lrcInstance_->get_selectedConvUid()) { accInfo.conversationModel->selectConversation(convInfo.uid); } saveConferenceSubcalls(); updateCall(convInfo.uid, accountId_); preventScreenSaver(true); break; } case lrc::api::call::Status::PAUSED: updateCall(); break; default: break; } } catch (...) { } } void CallAdapter::onCallInfosChanged(const QString& accountId, const QString& callId) { auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId); auto& callModel = accInfo.callModel; try { const auto call = callModel->getCall(callId); /* * Change status label text. */ const auto& convInfo = lrcInstance_->getConversationFromCallId(callId); if (!convInfo.uid.isEmpty()) { Q_EMIT callInfosChanged(call.isAudioOnly, accountId, convInfo.uid); participantsModel_->setConferenceLayout(static_cast(call.layout), callId); updateCallOverlay(convInfo); } } catch (...) { } } void CallAdapter::onRemoteRecordingChanged(const QString& callId, const QSet& peerRec, bool state) { Q_UNUSED(peerRec) Q_UNUSED(state) const auto currentCallId = lrcInstance_->getCallIdForConversationUid(lrcInstance_->get_selectedConvUid(), accountId_); if (callId == currentCallId) updateRecordingPeers(); } void CallAdapter::onCallAddedToConference(const QString& callId, const QString& confId) { Q_UNUSED(callId) Q_UNUSED(confId) saveConferenceSubcalls(); } void CallAdapter::placeAudioOnlyCall() { const auto convUid = lrcInstance_->get_selectedConvUid(); if (!convUid.isEmpty()) { lrcInstance_->getCurrentConversationModel()->placeAudioOnlyCall(convUid); } } void CallAdapter::placeCall() { const auto convUid = lrcInstance_->get_selectedConvUid(); if (!convUid.isEmpty()) { lrcInstance_->getCurrentConversationModel()->placeCall(convUid); } } void CallAdapter::hangUpACall(const QString& accountId, const QString& convUid) { const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid, accountId); if (!convInfo.uid.isEmpty()) { lrcInstance_->getAccountInfo(accountId).callModel->hangUp(convInfo.callId); } } void CallAdapter::setCallMedia(const QString& accountId, const QString& convUid, bool video) { const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid, accountId); if (convInfo.uid.isEmpty()) return; try { lrcInstance_->getAccountInfo(accountId).callModel->updateCallMediaList(convInfo.callId, video); } catch (...) { } } void CallAdapter::acceptACall(const QString& accountId, const QString& convUid) { const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid, accountId); if (convInfo.uid.isEmpty()) return; lrcInstance_->getAccountInfo(accountId).callModel->accept(convInfo.callId); auto& accInfo = lrcInstance_->getAccountInfo(convInfo.accountId); accInfo.callModel->setCurrentCall(convInfo.callId); if (convInfo.isRequest) { lrcInstance_->makeConversationPermanent(convInfo.uid, accountId); } } void CallAdapter::onShowIncomingCallView(const QString& accountId, const QString& convUid) { const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid, accountId); if (convInfo.uid.isEmpty()) { qWarning() << Q_FUNC_INFO << "No conversation for id: " << convUid; return; } const auto& accInfo = lrcInstance_->getAccountInfo(accountId); if (!accInfo.callModel->hasCall(convInfo.callId)) { qWarning() << Q_FUNC_INFO << "No call for id: " << convInfo.callId; return; } auto call = accInfo.callModel->getCall(convInfo.callId); // this will update various UI elements that portray the call state Q_EMIT callStatusChanged(static_cast(call.status), accountId, convInfo.uid); auto callBelongsToSelectedAccount = accountId == lrcInstance_->get_currentAccountId(); auto accountProperties = lrcInstance_->accountModel().getAccountConfig(accountId); // do nothing but update the status UI for incoming calls on RendezVous accounts if (accountProperties.isRendezVous && !call.isOutgoing) { qInfo() << Q_FUNC_INFO << "The call's associated account is a RendezVous point"; return; } auto currentConvId = lrcInstance_->get_selectedConvUid(); auto isCallSelected = currentConvId == convInfo.uid; // pop a notification when: // - the window is not focused OR the call is for another account // - the call is incoming AND the call's target account is // not a RendezVous point // - the call has just transitioned to the INCOMING_RINGING state if ((QApplication::focusObject() == nullptr || !callBelongsToSelectedAccount) && !call.isOutgoing && !accountProperties.isRendezVous && call.status == call::Status::INCOMING_RINGING) { // if the window is not focused but the call belongs to the selected account // then select the conversation immediately to show the call view if (callBelongsToSelectedAccount) { if (isCallSelected) { Q_EMIT lrcInstance_->conversationUpdated(convInfo.uid, accountId); } else { lrcInstance_->selectConversation(convInfo.uid); } } showNotification(accountId, convInfo.uid); return; } // this slot has been triggered as a result of either selecting a conversation // with an active call, placing a call, or an incoming call for the current // or any other conversation if (isCallSelected) { // current conversation, only update Q_EMIT lrcInstance_->conversationUpdated(convInfo.uid, accountId); return; } // pop a notification if the current conversation has an in-progress call const auto& currentConvInfo = lrcInstance_->getConversationFromConvUid(currentConvId); auto currentConvHasCall = accInfo.callModel->hasCall(currentConvInfo.callId); if (currentConvHasCall) { auto currentCall = accInfo.callModel->getCall(currentConvInfo.callId); if ((currentCall.status == call::Status::CONNECTED || currentCall.status == call::Status::IN_PROGRESS) && !accountProperties.autoAnswer) { showNotification(accountId, convInfo.uid); return; } } // finally, in this case, the conversation isn't selected yet // and there are no other special conditions, so just select the conversation lrcInstance_->selectConversation(convInfo.uid, accountId); } void CallAdapter::onShowCallView(const QString& accountId, const QString& convUid) { Q_EMIT lrcInstance_->conversationUpdated(convUid, accountId); // This will show the call } void CallAdapter::updateCall(const QString& convUid, const QString& accountId, bool forceCallOnly) { if (convUid != lrcInstance_->get_selectedConvUid()) return; accountId_ = accountId.isEmpty() ? accountId_ : accountId; const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid); if (convInfo.uid.isEmpty()) { return; } auto call = lrcInstance_->getCallInfoForConversation(convInfo, forceCallOnly); if (!call) { return; } if (convInfo.uid == lrcInstance_->get_selectedConvUid()) { auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_); if (accInfo.profileInfo.type != lrc::api::profile::Type::SIP) { accInfo.callModel->setCurrentCall(call->id); } } updateCallOverlay(convInfo); updateRecordingPeers(true); participantsModel_->setParticipants(call->id, getConferencesInfos()); participantsModel_->setConferenceLayout(static_cast(call->layout), call->id); } void CallAdapter::fillParticipantData(QJsonObject& participant) const { participant[lrc::api::ParticipantsInfosStrings::BESTNAME] = participant[lrc::api::ParticipantsInfosStrings::URI]; auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_); participant[lrc::api::ParticipantsInfosStrings::ISLOCAL] = false; if (participant[lrc::api::ParticipantsInfosStrings::BESTNAME] == accInfo.profileInfo.uri) { participant[lrc::api::ParticipantsInfosStrings::BESTNAME] = tr("me"); participant[lrc::api::ParticipantsInfosStrings::ISLOCAL] = true; } else { try { participant[lrc::api::ParticipantsInfosStrings::BESTNAME] = lrcInstance_->getCurrentAccountInfo().contactModel->bestNameForContact( participant[lrc::api::ParticipantsInfosStrings::URI].toString()); } catch (...) { } } } QVariantList CallAdapter::getConferencesInfos() const { QVariantList map; const auto& convInfo = lrcInstance_->getConversationFromConvUid( lrcInstance_->get_selectedConvUid()); if (convInfo.uid.isEmpty()) return map; auto callId = convInfo.confId.isEmpty() ? convInfo.callId : convInfo.confId; if (!callId.isEmpty()) { try { auto& participantsModel = lrcInstance_->accountModel() .getAccountInfo(accountId_) .callModel.get() ->getParticipantsInfos(callId); int index = 0; for (int index = 0; index < participantsModel.getParticipants().size(); index++) { auto participant = participantsModel.toQJsonObject(index); fillParticipantData(participant); map.push_back(QVariant(participant)); } return map; } catch (...) { } } return map; } void CallAdapter::showNotification(const QString& accountId, const QString& convUid) { auto& accInfo = lrcInstance_->getAccountInfo(accountId); auto title = accInfo.conversationModel->title(convUid); #ifdef Q_OS_LINUX auto convAvatar = Utils::conversationAvatar(lrcInstance_, convUid, QSize(50, 50), accountId); auto notifId = QString("%1;%2").arg(accountId).arg(convUid); systemTray_->showNotification(notifId, tr("Incoming call"), tr("%1 is calling you").arg(title), NotificationType::CALL, Utils::QImageToByteArray(convAvatar)); #else auto onClicked = [this, accountId, convUid]() { Q_EMIT lrcInstance_->notificationClicked(); const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid, accountId); if (convUid.isEmpty()) return; lrcInstance_->selectConversation(convUid, accountId); }; systemTray_->showNotification(tr("is calling you"), title, onClicked); #endif } void CallAdapter::connectCallModel(const QString& accountId) { auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId); connect(accInfo.callModel.get(), &NewCallModel::participantAdded, this, &CallAdapter::onParticipantAdded, Qt::UniqueConnection); connect(accInfo.callModel.get(), &NewCallModel::participantRemoved, this, &CallAdapter::onParticipantRemoved, Qt::UniqueConnection); connect(accInfo.callModel.get(), &NewCallModel::participantUpdated, this, &CallAdapter::onParticipantUpdated, Qt::UniqueConnection); connect(accInfo.callModel.get(), &NewCallModel::callStatusChanged, this, QOverload::of(&CallAdapter::onCallStatusChanged), Qt::UniqueConnection); connect(accInfo.callModel.get(), &NewCallModel::remoteRecordingChanged, this, &CallAdapter::onRemoteRecordingChanged, Qt::UniqueConnection); connect(accInfo.callModel.get(), &NewCallModel::callAddedToConference, this, &CallAdapter::onCallAddedToConference, Qt::UniqueConnection); connect(accInfo.callModel.get(), &NewCallModel::callInfosChanged, this, QOverload::of(&CallAdapter::onCallInfosChanged)); } void CallAdapter::updateRecordingPeers(bool eraseLabelOnEmpty) { const auto& convInfo = lrcInstance_->getConversationFromConvUid( lrcInstance_->get_selectedConvUid()); auto* call = lrcInstance_->getCallInfoForConversation(convInfo); if (!call) { return; } const auto& accInfo = lrcInstance_->getCurrentAccountInfo(); QStringList peers {}; for (const auto& uri : call->peerRec) { auto bestName = accInfo.contactModel->bestNameForContact(uri); if (!bestName.isEmpty()) { peers.append(bestName); } } if (!peers.isEmpty()) Q_EMIT remoteRecordingChanged(peers, true); else if (eraseLabelOnEmpty) Q_EMIT eraseRemoteRecording(); else Q_EMIT remoteRecordingChanged(peers, false); } void CallAdapter::sipInputPanelPlayDTMF(const QString& key) { auto callId = lrcInstance_->getCallIdForConversationUid(lrcInstance_->get_selectedConvUid(), accountId_); if (callId.isEmpty() || !lrcInstance_->getCurrentCallModel()->hasCall(callId)) { return; } lrcInstance_->getCurrentCallModel()->playDTMF(callId, key); } /* * For Call Overlay */ void CallAdapter::updateCallOverlay(const lrc::api::conversation::Info& convInfo) { auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_); auto* callModel = accInfo.callModel.get(); auto* call = lrcInstance_->getCallInfoForConversation(convInfo); if (!call) { return; } bool isPaused = call->status == lrc::api::call::Status::PAUSED; bool isAudioOnly = call->isAudioOnly && !isPaused; bool isAudioMuted = call->audioMuted && (call->status != lrc::api::call::Status::PAUSED); bool isVideoMuted = call->isAudioOnly || (call->videoMuted && !isPaused); bool isGrid = call->layout == lrc::api::call::Layout::GRID; QString previewId {}; if (!isAudioOnly && !isVideoMuted && call->status == lrc::api::call::Status::IN_PROGRESS) { for (const auto& media : call->mediaList) { if (media["MEDIA_TYPE"] == "MEDIA_TYPE_VIDEO") { if (media["ENABLED"] == "true" && media["MUTED"] == "false") { previewId = media["SOURCE"]; break; } } } } Q_EMIT updateOverlay(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, accInfo.profileInfo.type == lrc::api::profile::Type::SIP, isGrid, previewId); } void CallAdapter::saveConferenceSubcalls() { const auto& currentConvId = lrcInstance_->get_selectedConvUid(); const auto& convInfo = lrcInstance_->getConversationFromConvUid(currentConvId); if (!convInfo.confId.isEmpty()) { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); currentConfSubcalls_ = callModel->getConferenceSubcalls(convInfo.confId); } } void CallAdapter::hangUpCall(const QString& callId) { lrcInstance_->getCurrentCallModel()->hangUp(callId); } void CallAdapter::maximizeParticipant(const QString& uri) { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); const auto& convInfo = lrcInstance_->getConversationFromConvUid(lrcInstance_->get_selectedConvUid(), accountId_); auto confId = convInfo.confId; if (confId.isEmpty()) confId = convInfo.callId; try { const auto call = callModel->getCall(confId); auto participants = getConferencesInfos(); for (auto part : participants) { auto participant = part.toJsonObject(); if (participant[lrc::api::ParticipantsInfosStrings::URI].toString() == uri) { auto active = participant[lrc::api::ParticipantsInfosStrings::ACTIVE].toBool(); // Else, continue. if (!active) { callModel->setActiveParticipant(confId, uri); callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL); } else if (call.layout == lrc::api::call::Layout::ONE_WITH_SMALL) { callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE); } return; } } } catch (...) { } } void CallAdapter::minimizeParticipant(const QString& uri) { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); const auto& convInfo = lrcInstance_->getConversationFromConvUid(lrcInstance_->get_selectedConvUid(), accountId_); auto confId = convInfo.confId; if (confId.isEmpty()) confId = convInfo.callId; try { const auto call = callModel->getCall(confId); auto participants = getConferencesInfos(); for (auto& part : participants) { auto participant = part.toJsonObject(); if (participant[lrc::api::ParticipantsInfosStrings::URI].toString() == uri) { auto active = participant[lrc::api::ParticipantsInfosStrings::ACTIVE].toBool(); if (active) { if (call.layout == lrc::api::call::Layout::ONE) { callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL); } else { callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID); } } return; } } } catch (...) { } } void CallAdapter::showGridConferenceLayout() { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); const auto& convInfo = lrcInstance_->getConversationFromConvUid(lrcInstance_->get_selectedConvUid(), accountId_); auto confId = convInfo.confId; if (confId.isEmpty()) confId = convInfo.callId; callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID); } void CallAdapter::hangUpThisCall() { const auto& convInfo = lrcInstance_->getConversationFromConvUid(lrcInstance_->get_selectedConvUid(), accountId_); if (!convInfo.uid.isEmpty()) { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); if (!convInfo.confId.isEmpty() && callModel->hasCall(convInfo.confId)) { callModel->hangUp(convInfo.confId); } else if (callModel->hasCall(convInfo.callId)) { callModel->hangUp(convInfo.callId); } } } bool CallAdapter::isRecordingThisCall() { const auto& convInfo = lrcInstance_->getConversationFromConvUid(lrcInstance_->get_selectedConvUid(), accountId_); auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_); return accInfo.callModel->isRecording(convInfo.confId) || accInfo.callModel->isRecording(convInfo.callId); } bool CallAdapter::isCurrentHost() const { const auto& convInfo = lrcInstance_->getConversationFromConvUid(lrcInstance_->get_selectedConvUid(), accountId_); if (!convInfo.uid.isEmpty()) { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); try { auto confId = convInfo.confId; if (confId.isEmpty()) confId = convInfo.callId; if (callModel->getParticipantsInfos(confId).getParticipants().size() == 0) { return true; } else { return !convInfo.confId.isEmpty() && callModel->hasCall(convInfo.confId); } } catch (...) { } } return true; } bool CallAdapter::participantIsHost(const QString& uri) const { const auto& convInfo = lrcInstance_->getConversationFromConvUid( lrcInstance_->get_selectedConvUid()); if (!convInfo.uid.isEmpty()) { auto& accInfo = lrcInstance_->getAccountInfo(accountId_); auto* callModel = accInfo.callModel.get(); try { if (isCurrentHost()) { return uri == accInfo.profileInfo.uri; } else { auto call = callModel->getCall(convInfo.callId); auto peer = call.peerUri.remove("jami:").remove("ring:"); return (uri == peer); } } catch (...) { } } return true; } bool CallAdapter::isModerator(const QString& uri) const { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); const auto& convInfo = lrcInstance_->getConversationFromConvUid( lrcInstance_->get_selectedConvUid()); auto confId = convInfo.confId; if (confId.isEmpty()) confId = convInfo.callId; try { return callModel->isModerator(confId, uri); } catch (...) { } return false; } bool CallAdapter::isHandRaised(const QString& uri) const { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); const auto& convInfo = lrcInstance_->getConversationFromConvUid( lrcInstance_->get_selectedConvUid()); auto confId = convInfo.confId; if (confId.isEmpty()) confId = convInfo.callId; return callModel->isHandRaised(confId, uri); } void CallAdapter::setHandRaised(const QString& uri, bool state) { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); const auto& convInfo = lrcInstance_->getConversationFromConvUid( lrcInstance_->get_selectedConvUid()); auto confId = convInfo.confId; if (confId.isEmpty()) confId = convInfo.callId; try { callModel->setHandRaised(accountId_, confId, uri, state); } catch (...) { } } void CallAdapter::setModerator(const QString& uri, const bool state) { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); const auto& convInfo = lrcInstance_->getConversationFromConvUid( lrcInstance_->get_selectedConvUid()); auto confId = convInfo.confId; if (confId.isEmpty()) confId = convInfo.callId; try { callModel->setModerator(confId, uri, state); } catch (...) { } } void CallAdapter::muteParticipant(const QString& uri, const bool state) { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); const auto& convInfo = lrcInstance_->getConversationFromConvUid( lrcInstance_->get_selectedConvUid()); auto confId = convInfo.confId; if (confId.isEmpty()) confId = convInfo.callId; try { const auto call = callModel->getCall(confId); callModel->muteParticipant(confId, uri, state); } catch (...) { } } CallAdapter::MuteStates CallAdapter::getMuteState(const QString& uri) const { const auto& convInfo = lrcInstance_->getConversationFromConvUid( lrcInstance_->get_selectedConvUid()); auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); auto confId = convInfo.confId.isEmpty() ? convInfo.callId : convInfo.confId; try { auto& participantsModel = callModel->getParticipantsInfos(confId); if (participantsModel.getParticipants().size() == 0) { return MuteStates::UNMUTED; } else { for (const auto& participant : participantsModel.getParticipants()) { if (participant.uri == uri) { if (participant.audioLocalMuted) { if (participant.audioModeratorMuted) { return MuteStates::BOTH_MUTED; } else { return MuteStates::LOCAL_MUTED; } } else if (participant.audioModeratorMuted) { return MuteStates::MODERATOR_MUTED; } return MuteStates::UNMUTED; } } } return MuteStates::UNMUTED; } catch (...) { } return MuteStates::UNMUTED; } void CallAdapter::hangupParticipant(const QString& uri) { auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); const auto& convInfo = lrcInstance_->getConversationFromConvUid( lrcInstance_->get_selectedConvUid()); auto confId = convInfo.confId; if (confId.isEmpty()) confId = convInfo.callId; try { const auto call = callModel->getCall(confId); callModel->hangupParticipant(confId, uri); } catch (...) { } } void CallAdapter::holdThisCallToggle() { const auto callId = lrcInstance_->getCallIdForConversationUid(lrcInstance_->get_selectedConvUid(), accountId_); if (callId.isEmpty() || !lrcInstance_->getCurrentCallModel()->hasCall(callId)) { return; } auto* callModel = lrcInstance_->getCurrentCallModel(); if (callModel->hasCall(callId)) { callModel->togglePause(callId); } Q_EMIT showOnHoldLabel(true); } void CallAdapter::muteThisCallToggle(bool mute) { const auto callId = lrcInstance_->getCallIdForConversationUid(lrcInstance_->get_selectedConvUid(), accountId_); if (callId.isEmpty() || !lrcInstance_->getCurrentCallModel()->hasCall(callId)) { return; } auto* callModel = lrcInstance_->getCurrentCallModel(); if (callModel->hasCall(callId)) { callModel->requestMediaChange(callId, "audio_0", lrcInstance_->avModel().getCurrentVideoCaptureDevice(), lrc::api::NewCallModel::MediaRequestType::CAMERA, mute); } } void CallAdapter::recordThisCallToggle() { const auto callId = lrcInstance_->getCallIdForConversationUid(lrcInstance_->get_selectedConvUid(), accountId_); if (callId.isEmpty() || !lrcInstance_->getCurrentCallModel()->hasCall(callId)) { return; } auto* callModel = lrcInstance_->getCurrentCallModel(); if (callModel->hasCall(callId)) { callModel->toggleAudioRecord(callId); } } void CallAdapter::videoPauseThisCallToggle(bool mute) { const auto callId = lrcInstance_->getCallIdForConversationUid(lrcInstance_->get_selectedConvUid(), accountId_); if (callId.isEmpty() || !lrcInstance_->getCurrentCallModel()->hasCall(callId)) { return; } auto* callModel = lrcInstance_->getCurrentCallModel(); if (callModel->hasCall(callId)) { callModel->requestMediaChange(callId, "video_0", lrcInstance_->avModel().getCurrentVideoCaptureDevice(), lrc::api::NewCallModel::MediaRequestType::CAMERA, mute); // media label should come from qml // also thi function can me emrged with "muteThisCallToggle" } } QString CallAdapter::getCallDurationTime(const QString& accountId, const QString& convUid) { const auto callId = lrcInstance_->getCallIdForConversationUid(convUid, accountId); if (callId.isEmpty() || !lrcInstance_->getCurrentCallModel()->hasCall(callId)) { return QString(); } const auto callInfo = lrcInstance_->getCurrentCallModel()->getCall(callId); if (callInfo.status == lrc::api::call::Status::IN_PROGRESS || callInfo.status == lrc::api::call::Status::PAUSED) { return lrcInstance_->getCurrentCallModel()->getFormattedCallDuration(callId); } return QString(); } void CallAdapter::preventScreenSaver(bool state) { if (state) { if (!screenSaver.isInhibited()) screenSaver.inhibit(); } else if (screenSaver.isInhibited()) { screenSaver.uninhibit(); } };