/* * Copyright (c) 2010-2020 Belledonne Communications SARL. * * This file is part of linphone-desktop * (see https://www.linphone.org). * * 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 #include #include #include "app/App.hpp" #include "components/call/CallModel.hpp" #include "components/conference/ConferenceAddModel.hpp" #include "components/conference/ConferenceHelperModel.hpp" #include "components/conference/ConferenceModel.hpp" #include "components/conferenceInfo/ConferenceInfoModel.hpp" #include "components/core/CoreHandlers.hpp" #include "components/core/CoreManager.hpp" #include "components/participant/ParticipantModel.hpp" #include "components/settings/SettingsModel.hpp" #include "components/timeline/TimelineListModel.hpp" #include "components/timeline/TimelineModel.hpp" #include "utils/Utils.hpp" #include "CallsListModel.hpp" #include "utils/hacks/ChatRoomInitializer.hpp" // ============================================================================= using namespace std; namespace { // Delay before removing call in ms. constexpr int DelayBeforeRemoveCall = 3000; } static inline int findCallIndex (QList> &list, const shared_ptr &call) { auto it = find_if(list.begin(), list.end(), [call](QSharedPointer callModel) { return call == callModel.objectCast()->getCall(); }); Q_ASSERT(it != list.end()); return int(distance(list.begin(), it)); } static inline int findCallIndex (QList> &list, const CallModel &callModel) { return ::findCallIndex(list, callModel.getCall()); } // ----------------------------------------------------------------------------- CallsListModel::CallsListModel (QObject *parent) : ProxyListModel(parent) { mCoreHandlers = CoreManager::getInstance()->getHandlers(); QObject::connect( mCoreHandlers.get(), &CoreHandlers::callStateChanged, this, &CallsListModel::handleCallStateChanged ); } CallModel *CallsListModel::findCallModelFromPeerAddress (const QString &peerAddress) const { std::shared_ptr address = Utils::interpretUrl(peerAddress); auto it = find_if(mList.begin(), mList.end(), [address](QSharedPointer callModel) { return callModel.objectCast()->getRemoteAddress()->weakEqual(address); }); return it != mList.end() ? it->objectCast().get() : nullptr; } // ----------------------------------------------------------------------------- void CallsListModel::askForTransfer (CallModel *callModel) { emit callTransferAsked(callModel); } void CallsListModel::askForAttendedTransfer (CallModel *callModel) { emit callAttendedTransferAsked(callModel); } // ----------------------------------------------------------------------------- void CallsListModel::launchAudioCall (const QString &sipAddress, const QString& prepareTransfertAddress, const QHash &headers) const { CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = true; shared_ptr core = CoreManager::getInstance()->getCore(); shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); if (!address) return; shared_ptr params = core->createCallParams(nullptr); params->enableVideo(false); QHashIterator iterator(headers); while (iterator.hasNext()) { iterator.next(); params->addCustomHeader(Utils::appStringToCoreString(iterator.key()), Utils::appStringToCoreString(iterator.value())); } params->setAccount(core->getDefaultAccount()); CallModel::setRecordFile(params, Utils::coreStringToAppString(address->getUsername())); shared_ptr currentAccount = core->getDefaultAccount(); if(currentAccount){ if(!CoreManager::getInstance()->getSettingsModel()->getWaitRegistrationForCall() || currentAccount->getState() == linphone::RegistrationState::Ok) CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress); else{ QObject * context = new QObject(); QObject::connect(CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::registrationStateChanged,context, [address,core,params,currentAccount,prepareTransfertAddress, context](const std::shared_ptr &account, linphone::RegistrationState state) mutable { if(context && account==currentAccount && state==linphone::RegistrationState::Ok){ CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress); context->deleteLater(); context = nullptr; } }); } }else CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress); } void CallsListModel::launchSecureAudioCall (const QString &sipAddress, LinphoneEnums::MediaEncryption encryption, const QHash &headers, const QString& prepareTransfertAddress) const { CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = true; shared_ptr core = CoreManager::getInstance()->getCore(); shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); if (!address) return; shared_ptr params = core->createCallParams(nullptr); params->enableVideo(false); QHashIterator iterator(headers); while (iterator.hasNext()) { iterator.next(); params->addCustomHeader(Utils::appStringToCoreString(iterator.key()), Utils::appStringToCoreString(iterator.value())); } params->setAccount(core->getDefaultAccount()); CallModel::setRecordFile(params, Utils::coreStringToAppString(address->getUsername())); shared_ptr currentAccount = core->getDefaultAccount(); params->setMediaEncryption(LinphoneEnums::toLinphone(encryption)); if(currentAccount){ if(!CoreManager::getInstance()->getSettingsModel()->getWaitRegistrationForCall() || currentAccount->getState() == linphone::RegistrationState::Ok) CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress); else{ QObject * context = new QObject(); QObject::connect(CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::registrationStateChanged,context, [address,core,params,currentAccount,prepareTransfertAddress, context](const std::shared_ptr &account, linphone::RegistrationState state) mutable { if(context && account==currentAccount && state==linphone::RegistrationState::Ok){ CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress); context->deleteLater(); context = nullptr; } }); } }else CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress); } void CallsListModel::launchVideoCall (const QString &sipAddress, const QString& prepareTransfertAddress, const bool& autoSelectAfterCreation, QVariantMap options) const { CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = autoSelectAfterCreation; shared_ptr core = CoreManager::getInstance()->getCore(); if (!core->videoSupported()) { qWarning() << QStringLiteral("Unable to launch video call. (Video not supported.) Launching audio call..."); launchAudioCall(sipAddress, prepareTransfertAddress, {}); return; } shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); if (!address) return; shared_ptr params = core->createCallParams(nullptr); bool enableVideo = options.contains("video") ? options["video"].toBool() : true; params->enableVideo(enableVideo); if( enableVideo ){ params->setVideoDirection(linphone::MediaDirection::SendRecv); params->setConferenceVideoLayout(linphone::ConferenceLayout::Grid); } params->enableMic(options.contains("micro") ? options["micro"].toBool() : true); params->enableAudio(options.contains("audio") ? options["audio"].toBool() : true); // ??? params->setAccount(core->getDefaultAccount()); CallModel::setRecordFile(params, Utils::coreStringToAppString(address->getUsername())); CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress); } ChatRoomModel* CallsListModel::launchSecureChat (const QString &sipAddress) const { CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = true; shared_ptr core = CoreManager::getInstance()->getCore(); shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(sipAddress)); if (!address) return nullptr; std::shared_ptr params = core->createDefaultChatRoomParams(); std::list > participants; std::shared_ptr localAddress; participants.push_back(address); params->enableEncryption(true); params->setSubject("Dummy Subject"); params->enableEncryption(true); std::shared_ptr chatRoom = core->createChatRoom(params, localAddress, participants); if( chatRoom != nullptr){ auto timelineList = CoreManager::getInstance()->getTimelineListModel(); timelineList->update(); auto timeline = timelineList->getTimeline(chatRoom, false); if(!timeline){ timeline = timelineList->getTimeline(chatRoom, true); timelineList->add(timeline); } return timeline->getChatRoomModel(); } return nullptr; } QVariantMap CallsListModel::launchChat(const QString &sipAddress, const int& securityLevel) const{ QVariantList participants; participants << sipAddress; return createChatRoom("", securityLevel, participants, true); } ChatRoomModel* CallsListModel::createChat (const QString &participantAddress) const{ CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = true; shared_ptr core = CoreManager::getInstance()->getCore(); shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(participantAddress)); if (!address) return nullptr; std::shared_ptr params = core->createDefaultChatRoomParams(); std::list > participants; std::shared_ptr localAddress; participants.push_back(address); params->setBackend(linphone::ChatRoomBackend::Basic); std::shared_ptr chatRoom = core->createChatRoom(params, localAddress, participants); if( chatRoom != nullptr){ auto timelineList = CoreManager::getInstance()->getTimelineListModel(); auto timeline = timelineList->getTimeline(chatRoom, true); return timeline->getChatRoomModel(); } return nullptr; } ChatRoomModel* CallsListModel::createChat (const CallModel * model) const{ if(model){ return model->getChatRoomModel(); } return nullptr; } bool CallsListModel::createSecureChat (const QString& subject, const QString &participantAddress) const{ CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = true; shared_ptr core = CoreManager::getInstance()->getCore(); shared_ptr address = core->interpretUrl(Utils::appStringToCoreString(participantAddress)); if (!address) return false; std::shared_ptr params = core->createDefaultChatRoomParams(); std::list > participants; std::shared_ptr localAddress; participants.push_back(address); params->enableEncryption(true); params->setSubject(Utils::appStringToCoreString(subject)); params->enableEncryption(true); params->enableGroup(true); std::shared_ptr chatRoom = core->createChatRoom(params, localAddress, participants); return chatRoom != nullptr; } // Created, timeline that can be used QVariantMap CallsListModel::createChatRoom(const QString& subject, const int& securityLevel, const QVariantList& participants, const bool& selectAfterCreation) const{ return createChatRoom(subject, securityLevel, nullptr, participants, selectAfterCreation); } QVariantMap CallsListModel::createChatRoom(const QString& subject, const int& securityLevel, std::shared_ptr localAddress, const QVariantList& participants, const bool& selectAfterCreation) const{ CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = selectAfterCreation; QVariantMap result; shared_ptr core = CoreManager::getInstance()->getCore(); std::shared_ptr chatRoom; QList< std::shared_ptr> admins; QSharedPointer timeline; auto timelineList = CoreManager::getInstance()->getTimelineListModel(); QString localAddressStr = (localAddress ? Utils::coreStringToAppString(localAddress->asStringUriOnly()) : "local"); qInfo() << "ChatRoom creation of " << subject << " at " << securityLevel << " security, from " << localAddressStr << " and with " << participants; std::shared_ptr params = core->createDefaultChatRoomParams(); std::list > chatRoomParticipants; for(auto p : participants){ ParticipantModel* participant = p.value(); std::shared_ptr address; if(participant) { address = Utils::interpretUrl(participant->getSipAddress()); if(participant->getAdminStatus()) admins << address; }else{ QString participant = p.toString(); if( participant != "") address = Utils::interpretUrl(participant); } if( address) chatRoomParticipants.push_back( address ); } params->enableEncryption(securityLevel>0); if( securityLevel>0){ params->enableEncryption(true); }else params->setBackend(linphone::ChatRoomBackend::Basic); params->enableGroup( subject!="" ); if(chatRoomParticipants.size() > 0) { if(!params->groupEnabled()) {// Chat room is one-one : check if it is already exist with empty or dummy subject chatRoom = core->searchChatRoom(params, localAddress , nullptr , chatRoomParticipants); params->setSubject(subject != ""?Utils::appStringToCoreString(subject):"Dummy Subject"); if(!chatRoom) chatRoom = core->searchChatRoom(params, localAddress , nullptr , chatRoomParticipants); }else params->setSubject(subject != ""?Utils::appStringToCoreString(subject):"Dummy Subject"); if( !chatRoom) { chatRoom = core->createChatRoom(params, localAddress, chatRoomParticipants); if(chatRoom != nullptr && admins.size() > 0) ChatRoomInitializer::setAdminsAsync(params->getSubject(), params->getBackend(), params->groupEnabled(), admins ); timeline = timelineList->getTimeline(chatRoom, false); }else{ if(admins.size() > 0){ ChatRoomInitializer::setAdminsSync(chatRoom, admins); } timeline = timelineList->getTimeline(chatRoom, true); } if(timeline){ CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = false; result["chatRoomModel"] = QVariant::fromValue(timeline->getChatRoomModel()); if(selectAfterCreation) {// The timeline here will not receive the first creation event. Set Selected if needed QTimer::singleShot(200, [timeline](){// Delay process in order to let GUI time for Timeline building/linking before doing actions timeline->setSelected(true); }); } } } if( !chatRoom) qWarning() << "Chat room cannot be created"; result["created"] = (chatRoom != nullptr); return result; } QVariantMap CallsListModel::createConference(ConferenceInfoModel * conferenceInfo, const int& securityLevel, const int& inviteMode, const bool& selectAfterCreation) { QVariantMap result; CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = selectAfterCreation; shared_ptr core = CoreManager::getInstance()->getCore(); std::shared_ptr conference; QList< std::shared_ptr> admins; auto timelineList = CoreManager::getInstance()->getTimelineListModel(); qInfo() << "Conference creation of " << conferenceInfo->getSubject() << " at " << securityLevel << " security";// and with " << conferenceInfo->getConferenceInfo()->getParticipants().size(); auto conferenceScheduler = core->createConferenceScheduler(); conferenceScheduler->setInfo(conferenceInfo->getConferenceInfo()); return result; } void CallsListModel::prepareConferenceCall(ConferenceInfoModel * model){ auto app = App::getInstance(); app->smartShowWindow(app->getCallsWindow()); emit callConferenceAsked(model); } // ----------------------------------------------------------------------------- int CallsListModel::getRunningCallsNumber () const { return CoreManager::getInstance()->getCore()->getCallsNb(); } void CallsListModel::terminateAllCalls () const { CoreManager::getInstance()->getCore()->terminateAllCalls(); } void CallsListModel::terminateCall (const QString& sipAddress) const{ auto coreManager = CoreManager::getInstance(); shared_ptr address = coreManager->getCore()->interpretUrl(Utils::appStringToCoreString(sipAddress)); if (!address) qWarning() << "Cannot terminate Call. The address cannot be parsed : " << sipAddress; else{ std::shared_ptr call = coreManager->getCore()->getCallByRemoteAddress2(address); if( call){ coreManager->lockVideoRender(); call->terminate(); coreManager->unlockVideoRender(); }else{ qWarning() << "Cannot terminate call as it doesn't exist : " << sipAddress; } } } std::list> CallsListModel::getCallHistory(const QString& peerAddress, const QString& localAddress){ std::shared_ptr cleanedPeerAddress = Utils::interpretUrl(Utils::cleanSipAddress(peerAddress)); std::shared_ptr cleanedLocalAddress = Utils::interpretUrl(Utils::cleanSipAddress(localAddress)); return CoreManager::getInstance()->getCore()->getCallHistory(cleanedPeerAddress, cleanedLocalAddress); } // ----------------------------------------------------------------------------- static void joinConference (const shared_ptr &call) { if (call->getToHeader("method") != "join-conference") return; shared_ptr core = CoreManager::getInstance()->getCore(); if (!core->getConference()) { qWarning() << QStringLiteral("Not in a conference. => Responding to `join-conference` as a simple call..."); return; } shared_ptr conference = core->getConference(); const QString conferenceId = Utils::coreStringToAppString(call->getToHeader("conference-id")); if (conference->getId() != Utils::appStringToCoreString(conferenceId)) { qWarning() << QStringLiteral("Trying to join conference with an invalid conference id: `%1`. Responding as a simple call...") .arg(conferenceId); return; } qInfo() << QStringLiteral("Join conference: `%1`.").arg(conferenceId); ConferenceHelperModel helperModel; ConferenceHelperModel::ConferenceAddModel *addModel = helperModel.getConferenceAddModel(); CallModel *callModel = &call->getData("call-model"); callModel->accept(); addModel->addToConference(call->getRemoteAddress()); addModel->update(); } void CallsListModel::handleCallStateChanged (const shared_ptr &call, linphone::Call::State state) { switch (state) { case linphone::Call::State::IncomingReceived: addCall(call); joinConference(call); break; case linphone::Call::State::OutgoingInit: addCall(call); break; case linphone::Call::State::End: case linphone::Call::State::Error:{ if(call->dataExists("call-model")) { CallModel * model = &call->getData("call-model"); model->callEnded(); } removeCall(call); } break; case linphone::Call::State::StreamsRunning: { int index = findCallIndex(mList, call); emit callRunning(index, &call->getData("call-model")); } break; default: break; } } // ----------------------------------------------------------------------------- void CallsListModel::addCall (const shared_ptr &call) { if (call->getDir() == linphone::Call::Dir::Outgoing) { QQuickWindow *callsWindow = App::getInstance()->getCallsWindow(); if (callsWindow) { if (CoreManager::getInstance()->getSettingsModel()->getKeepCallsWindowInBackground()) { if (!callsWindow->isVisible()) callsWindow->showMinimized(); } else App::smartShowWindow(callsWindow); } } QSharedPointer callModel = QSharedPointer::create(call); qInfo() << QStringLiteral("Add call:") << callModel->getFullLocalAddress() << callModel->getFullPeerAddress(); App::getInstance()->getEngine()->setObjectOwnership(callModel.get(), QQmlEngine::CppOwnership); add(callModel); emit layoutChanged(); } void CallsListModel::addDummyCall () { QQuickWindow *callsWindow = App::getInstance()->getCallsWindow(); if (callsWindow) { App::smartShowWindow(callsWindow); } QSharedPointer callModel = QSharedPointer::create(nullptr); qInfo() << QStringLiteral("Add call:") << callModel->getFullLocalAddress() << callModel->getFullPeerAddress(); App::getInstance()->getEngine()->setObjectOwnership(callModel.get(), QQmlEngine::CppOwnership); // This connection is (only) useful for `CallsListProxyModel`. QObject::connect(callModel.get(), &CallModel::isInConferenceChanged, this, [this, callModel](bool) { int id = findCallIndex(mList, *callModel); emit dataChanged(index(id, 0), index(id, 0)); }); add(callModel); emit layoutChanged(); } void CallsListModel::removeCall (const shared_ptr &call) { CallModel *callModel; try { callModel = &call->getData("call-model"); } catch (const out_of_range &) { // The call model not exists because the linphone call state // `CallStateIncomingReceived`/`CallStateOutgoingInit` was not notified. qWarning() << QStringLiteral("Unable to find call:") << call.get(); return; } QTimer::singleShot(DelayBeforeRemoveCall, this, [this, callModel] { removeCallCb(callModel); }); } void CallsListModel::removeCallCb (CallModel *callModel) { remove(callModel); }