From cd82964b231ebe4097239bff6ee13913a62e98e2 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Tue, 21 Nov 2023 18:02:26 +0100 Subject: [PATCH] Replace postAsync functions by SafeConnection in order to make more robust thread connections and guarantee to have always caller/callee while processing signals. Introduce ObjectCore, ObjectGui, ObjectModel. Simulate addAccount signals on adding account (in waiting for SDK callback). --- Linphone/core/App.cpp | 22 ++- Linphone/core/App.hpp | 1 + Linphone/core/CMakeLists.txt | 6 +- Linphone/core/account/Account.cpp | 82 ----------- Linphone/core/account/AccountCore.cpp | 126 +++++++++++++++++ .../account/{Account.hpp => AccountCore.hpp} | 29 ++-- Linphone/core/account/AccountGui.cpp | 40 ++++++ Linphone/core/account/AccountGui.hpp | 41 ++++++ Linphone/core/account/AccountList.cpp | 89 +++++++++--- Linphone/core/account/AccountList.hpp | 13 ++ Linphone/core/account/AccountProxy.cpp | 22 ++- Linphone/core/account/AccountProxy.hpp | 8 ++ Linphone/core/call/Call.cpp | 81 ----------- Linphone/core/call/CallCore.cpp | 132 ++++++++++++++++++ Linphone/core/call/{Call.hpp => CallCore.hpp} | 73 ++++++---- Linphone/core/call/CallGui.cpp | 40 ++++++ Linphone/core/call/CallGui.hpp | 41 ++++++ Linphone/core/notifier/Notifier.cpp | 17 ++- Linphone/core/notifier/Notifier.hpp | 2 +- Linphone/model/CMakeLists.txt | 1 + Linphone/model/account/AccountManager.cpp | 1 + Linphone/model/account/AccountModel.cpp | 20 ++- Linphone/model/account/AccountModel.hpp | 7 +- Linphone/model/call/CallModel.cpp | 9 ++ Linphone/model/call/CallModel.hpp | 8 ++ Linphone/model/core/CoreModel.cpp | 4 + Linphone/model/core/CoreModel.hpp | 3 + Linphone/model/object/SafeObject.cpp | 46 ++++++ Linphone/model/object/SafeObject.hpp | 49 +++++++ Linphone/model/object/VariantObject.cpp | 40 ++---- Linphone/model/object/VariantObject.hpp | 34 +++-- Linphone/model/tool/ToolModel.cpp | 8 +- Linphone/model/tool/ToolModel.hpp | 8 +- Linphone/tool/CMakeLists.txt | 2 + Linphone/tool/Utils.cpp | 34 +++-- Linphone/tool/Utils.hpp | 1 + Linphone/tool/thread/SafeConnection.cpp | 29 ++++ Linphone/tool/thread/SafeConnection.hpp | 117 ++++++++++++++++ Linphone/tool/thread/SafeSharedPointer.hpp | 78 +++++++++++ Linphone/view/App/Main.qml | 7 - Linphone/view/CMakeLists.txt | 1 + .../Notification/NotificationReceivedCall.qml | 6 +- Linphone/view/Prototype/CallPrototype.qml | 86 ++++++++++-- Linphone/view/Prototype/ItemPrototype.qml | 54 +++++++ 44 files changed, 1202 insertions(+), 316 deletions(-) delete mode 100644 Linphone/core/account/Account.cpp create mode 100644 Linphone/core/account/AccountCore.cpp rename Linphone/core/account/{Account.hpp => AccountCore.hpp} (69%) create mode 100644 Linphone/core/account/AccountGui.cpp create mode 100644 Linphone/core/account/AccountGui.hpp delete mode 100644 Linphone/core/call/Call.cpp create mode 100644 Linphone/core/call/CallCore.cpp rename Linphone/core/call/{Call.hpp => CallCore.hpp} (58%) create mode 100644 Linphone/core/call/CallGui.cpp create mode 100644 Linphone/core/call/CallGui.hpp create mode 100644 Linphone/model/object/SafeObject.cpp create mode 100644 Linphone/model/object/SafeObject.hpp create mode 100644 Linphone/tool/thread/SafeConnection.cpp create mode 100644 Linphone/tool/thread/SafeConnection.hpp create mode 100644 Linphone/tool/thread/SafeSharedPointer.hpp create mode 100644 Linphone/view/Prototype/ItemPrototype.qml diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index 7f5f36b19..43db88310 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -30,8 +30,10 @@ #include #include -#include "core/account/Account.hpp" +#include "core/account/AccountCore.hpp" #include "core/account/AccountProxy.hpp" +#include "core/call/CallCore.hpp" +#include "core/call/CallGui.hpp" #include "core/logger/QtLogger.hpp" #include "core/login/LoginPage.hpp" #include "core/notifier/Notifier.hpp" @@ -128,8 +130,10 @@ void App::initCppInterfaces() { qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "PhoneNumber", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountProxy"); - qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "Account", QLatin1String("Uncreatable")); - qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "Call", QLatin1String("Uncreatable")); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountGui"); + qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "AccountCore", QLatin1String("Uncreatable")); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallGui"); + qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable")); LinphoneEnums::registerMetaTypes(); } @@ -167,3 +171,15 @@ void App::createCommandParser() { {"qt-logs-only", tr("commandLineOptionQtLogsOnly")}, }); } + +bool App::notify(QObject *receiver, QEvent *event) { + bool done = true; + try { + done = QApplication::notify(receiver, event); + } catch (const std::exception &ex) { + qCritical() << "[App] Exception has been catch in notify"; + } catch (...) { + qCritical() << "[App] Generic exeption has been catch in notify"; + } + return done; +} diff --git a/Linphone/core/App.hpp b/Linphone/core/App.hpp index 0e0ced4e0..0951e6bbb 100644 --- a/Linphone/core/App.hpp +++ b/Linphone/core/App.hpp @@ -74,6 +74,7 @@ public: void onLoggerInitialized(); QQmlApplicationEngine *mEngine = nullptr; + bool notify(QObject *receiver, QEvent *event); private: void createCommandParser(); diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index c1bd2d134..bd8138e25 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -1,9 +1,11 @@ list(APPEND _LINPHONEAPP_SOURCES - core/account/Account.cpp + core/account/AccountCore.cpp + core/account/AccountGui.cpp core/account/AccountList.cpp core/account/AccountProxy.cpp core/App.cpp - core/call/Call.cpp + core/call/CallCore.cpp + core/call/CallGui.cpp core/logger/QtLogger.cpp core/login/LoginPage.cpp core/notifier/Notifier.cpp diff --git a/Linphone/core/account/Account.cpp b/Linphone/core/account/Account.cpp deleted file mode 100644 index 0bbadc797..000000000 --- a/Linphone/core/account/Account.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2010-2024 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 "Account.hpp" -#include "tool/Utils.hpp" - -DEFINE_ABSTRACT_OBJECT(Account) - -Account::Account(const std::shared_ptr &account) : QObject(nullptr) { - // Should be call from model Thread - mustBeInLinphoneThread(getClassName()); - auto address = account->getContactAddress(); - mContactAddress = address ? Utils::coreStringToAppString(account->getContactAddress()->asString()) : ""; - auto params = account->getParams(); - auto identityAddress = params->getIdentityAddress(); - mIdentityAddress = identityAddress ? Utils::coreStringToAppString(identityAddress->asString()) : ""; - mPictureUri = Utils::coreStringToAppString(params->getPictureUri()); - mRegistrationState = LinphoneEnums::fromLinphone(account->getState()); - - mAccountModel = Utils::makeQObject_ptr(account); // OK - connect(mAccountModel.get(), &AccountModel::registrationStateChanged, this, &Account::onRegistrationStateChanged); - connect(this, &Account::requestSetPictureUri, mAccountModel.get(), &AccountModel::setPictureUri, - Qt::QueuedConnection); - connect(mAccountModel.get(), &AccountModel::pictureUriChanged, this, &Account::onPictureUriChanged, - Qt::QueuedConnection); -} - -Account::~Account() { - mustBeInMainThread("~" + getClassName()); - emit mAccountModel->removeListener(); -} - -QString Account::getContactAddress() const { - return mContactAddress; -} - -QString Account::getIdentityAddress() const { - return mIdentityAddress; -} - -QString Account::getPictureUri() const { - return mPictureUri; -} - -LinphoneEnums::RegistrationState Account::getRegistrationState() const { - return mRegistrationState; -} - -void Account::onRegistrationStateChanged(const std::shared_ptr &account, - linphone::RegistrationState state, - const std::string &message) { - mRegistrationState = LinphoneEnums::fromLinphone(state); - emit registrationStateChanged(Utils::coreStringToAppString(message)); -} - -void Account::setPictureUri(const QString &uri) { - if (uri != mPictureUri) { - emit requestSetPictureUri(Utils::appStringToCoreString(uri)); - } -} - -void Account::onPictureUriChanged(std::string uri) { - mPictureUri = Utils::coreStringToAppString(uri); - emit pictureUriChanged(); -} diff --git a/Linphone/core/account/AccountCore.cpp b/Linphone/core/account/AccountCore.cpp new file mode 100644 index 000000000..f27d92543 --- /dev/null +++ b/Linphone/core/account/AccountCore.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2010-2024 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 "AccountCore.hpp" +#include "core/App.hpp" +#include "model/core/CoreModel.hpp" +#include "tool/Utils.hpp" +#include "tool/thread/SafeConnection.hpp" + +DEFINE_ABSTRACT_OBJECT(AccountCore) + +QSharedPointer AccountCore::create(const std::shared_ptr &account) { + auto model = QSharedPointer(new AccountCore(account), &QObject::deleteLater); + model->moveToThread(App::getInstance()->thread()); + model->setSelf(model); + return model; +} + +AccountCore::AccountCore(const std::shared_ptr &account) : QObject(nullptr) { + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + // Should be call from model Thread + mustBeInLinphoneThread(getClassName()); + // Init data + auto address = account->getContactAddress(); + mContactAddress = address ? Utils::coreStringToAppString(account->getContactAddress()->asString()) : ""; + auto params = account->getParams(); + auto identityAddress = params->getIdentityAddress(); + mIdentityAddress = identityAddress ? Utils::coreStringToAppString(identityAddress->asString()) : ""; + mPictureUri = Utils::coreStringToAppString(params->getPictureUri()); + mRegistrationState = LinphoneEnums::fromLinphone(account->getState()); + mIsDefaultAccount = CoreModel::getInstance()->getCore()->getDefaultAccount() == account; + + // Add listener + mAccountModel = Utils::makeQObject_ptr(account); // OK + mAccountModel->setSelf(mAccountModel); +} + +AccountCore::~AccountCore() { + mustBeInMainThread("~" + getClassName()); + emit mAccountModel->removeListener(); +} + +void AccountCore::setSelf(QSharedPointer me) { + mAccountModelConnection = QSharedPointer( + new SafeConnection(me.objectCast(), std::dynamic_pointer_cast(mAccountModel))); + mAccountModelConnection->makeConnect(mAccountModel.get(), &AccountModel::registrationStateChanged, + [this](const std::shared_ptr &account, + linphone::RegistrationState state, const std::string &message) { + mAccountModelConnection->invokeToCore([this, account, state, message]() { + this->onRegistrationStateChanged(account, state, message); + }); + }); + // From Model + mAccountModelConnection->makeConnect( + mAccountModel.get(), &AccountModel::defaultAccountChanged, [this](bool isDefault) { + mAccountModelConnection->invokeToCore([this, isDefault]() { this->onDefaultAccountChanged(isDefault); }); + }); + + mAccountModelConnection->makeConnect(mAccountModel.get(), &AccountModel::pictureUriChanged, [this](QString uri) { + mAccountModelConnection->invokeToCore([this, uri]() { this->onPictureUriChanged(uri); }); + }); + + // From GUI + mAccountModelConnection->makeConnect(this, &AccountCore::lSetPictureUri, [this](QString uri) { + mAccountModelConnection->invokeToModel([this, uri]() { mAccountModel->setPictureUri(uri); }); + }); + mAccountModelConnection->makeConnect(this, &AccountCore::lSetDefaultAccount, [this]() { + mAccountModelConnection->invokeToModel([this]() { mAccountModel->setDefault(); }); + }); +} + +QString AccountCore::getContactAddress() const { + return mContactAddress; +} + +QString AccountCore::getIdentityAddress() const { + return mIdentityAddress; +} + +QString AccountCore::getPictureUri() const { + return mPictureUri; +} + +LinphoneEnums::RegistrationState AccountCore::getRegistrationState() const { + return mRegistrationState; +} + +bool AccountCore::getIsDefaultAccount() const { + return mIsDefaultAccount; +} + +void AccountCore::onRegistrationStateChanged(const std::shared_ptr &account, + linphone::RegistrationState state, + const std::string &message) { + mRegistrationState = LinphoneEnums::fromLinphone(state); + emit registrationStateChanged(Utils::coreStringToAppString(message)); +} + +void AccountCore::onDefaultAccountChanged(bool isDefault) { + if (mIsDefaultAccount != isDefault) { + mIsDefaultAccount = isDefault; + emit defaultAccountChanged(mIsDefaultAccount); + } +} + +void AccountCore::onPictureUriChanged(QString uri) { + mPictureUri = uri; + emit pictureUriChanged(); +} diff --git a/Linphone/core/account/Account.hpp b/Linphone/core/account/AccountCore.hpp similarity index 69% rename from Linphone/core/account/Account.hpp rename to Linphone/core/account/AccountCore.hpp index d02ffc3ab..fa1de1184 100644 --- a/Linphone/core/account/Account.hpp +++ b/Linphone/core/account/AccountCore.hpp @@ -18,54 +18,63 @@ * along with this program. If not, see . */ -#ifndef ACCOUNT_H_ -#define ACCOUNT_H_ +#ifndef ACCOUNT_CORE_H_ +#define ACCOUNT_CORE_H_ #include "model/account/AccountModel.hpp" #include "tool/LinphoneEnums.hpp" +#include "tool/thread/SafeConnection.hpp" #include #include #include -class Account : public QObject, public AbstractObject { +class AccountCore : public QObject, public AbstractObject { Q_OBJECT Q_PROPERTY(QString contactAddress READ getContactAddress CONSTANT) Q_PROPERTY(QString identityAddress READ getIdentityAddress CONSTANT) - Q_PROPERTY(QString pictureUri READ getPictureUri WRITE setPictureUri NOTIFY pictureUriChanged) + Q_PROPERTY(QString pictureUri READ getPictureUri WRITE lSetPictureUri NOTIFY pictureUriChanged) Q_PROPERTY( LinphoneEnums::RegistrationState registrationState READ getRegistrationState NOTIFY registrationStateChanged) + Q_PROPERTY(bool isDefaultAccount READ getIsDefaultAccount NOTIFY defaultAccountChanged) public: + static QSharedPointer create(const std::shared_ptr &account); // Should be call from model Thread. Will be automatically in App thread after initialization - Account(const std::shared_ptr &account); - ~Account(); + AccountCore(const std::shared_ptr &account); + ~AccountCore(); + void setSelf(QSharedPointer me); QString getContactAddress() const; QString getIdentityAddress() const; QString getPictureUri() const; LinphoneEnums::RegistrationState getRegistrationState() const; + bool getIsDefaultAccount() const; - void setPictureUri(const QString &uri); - - void onPictureUriChanged(std::string uri); + void onPictureUriChanged(QString uri); void onRegistrationStateChanged(const std::shared_ptr &account, linphone::RegistrationState state, const std::string &message); + void onDefaultAccountChanged(bool isDefault); + signals: void pictureUriChanged(); void registrationStateChanged(const QString &message); + void defaultAccountChanged(bool isDefault); // Account requests - void requestSetPictureUri(std::string pictureUri); + void lSetPictureUri(QString pictureUri); + void lSetDefaultAccount(); private: QString mContactAddress; QString mIdentityAddress; QString mPictureUri; + bool mIsDefaultAccount = false; LinphoneEnums::RegistrationState mRegistrationState; std::shared_ptr mAccountModel; + QSharedPointer mAccountModelConnection; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/core/account/AccountGui.cpp b/Linphone/core/account/AccountGui.cpp new file mode 100644 index 000000000..5e50d52fe --- /dev/null +++ b/Linphone/core/account/AccountGui.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2024 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 "AccountGui.hpp" +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(AccountGui) + +AccountGui::AccountGui(QSharedPointer core) { + mustBeInMainThread(getClassName()); + qDebug() << "[AccountGui] new" << this; + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); + mCore = core; +} + +AccountGui::~AccountGui() { + mustBeInMainThread("~" + getClassName()); + qDebug() << "[AccountGui] delete" << this; +} + +AccountCore *AccountGui::getCore() const { + return mCore.get(); +} diff --git a/Linphone/core/account/AccountGui.hpp b/Linphone/core/account/AccountGui.hpp new file mode 100644 index 000000000..f1d1592ae --- /dev/null +++ b/Linphone/core/account/AccountGui.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2024 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 . + */ + +#ifndef ACCOUNT_GUI_H_ +#define ACCOUNT_GUI_H_ + +#include "AccountCore.hpp" +#include +#include + +class AccountGui : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(AccountCore *core READ getCore CONSTANT) + +public: + AccountGui(QSharedPointer core); + ~AccountGui(); + AccountCore *getCore() const; + QSharedPointer mCore; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/account/AccountList.cpp b/Linphone/core/account/AccountList.cpp index 4a82d3154..09b9e1c8a 100644 --- a/Linphone/core/account/AccountList.cpp +++ b/Linphone/core/account/AccountList.cpp @@ -19,7 +19,8 @@ */ #include "AccountList.hpp" -#include "Account.hpp" +#include "AccountCore.hpp" +#include "AccountGui.hpp" #include "core/App.hpp" #include #include @@ -28,26 +29,80 @@ DEFINE_ABSTRACT_OBJECT(AccountList) +QSharedPointer AccountList::create() { + auto model = QSharedPointer(new AccountList(), &QObject::deleteLater); + model->moveToThread(App::getInstance()->thread()); + model->setSelf(model); + return model; +} + AccountList::AccountList(QObject *parent) : ListProxy(parent) { + qDebug() << "[AccountList] new" << this; mustBeInMainThread(getClassName()); - App::postModelAsync([=]() { - QList> accounts; - // Model thread. - mustBeInLinphoneThread(getClassName()); - auto linphoneAccounts = CoreModel::getInstance()->getCore()->getAccountList(); - for (auto it : linphoneAccounts) { - auto model = QSharedPointer(new Account(it), &QObject::deleteLater); - model->moveToThread(this->thread()); - accounts.push_back(model); - } - // Invoke for adding stuffs in caller thread - QMetaObject::invokeMethod(this, [this, accounts]() { - mustBeInMainThread(getClassName()); - add(accounts); - }); - }); + connect(CoreModel::getInstance().get(), &CoreModel::accountAdded, this, &AccountList::lUpdate); } AccountList::~AccountList() { + qDebug() << "[AccountList] delete" << this; mustBeInMainThread("~" + getClassName()); + if (mModelConnection) mModelConnection->deleteLater(); +} + +void AccountList::setSelf(QSharedPointer me) { + mModelConnection = QSharedPointer( + new SafeConnection(me.objectCast(), std::dynamic_pointer_cast(CoreModel::getInstance())), + &QObject::deleteLater); + mModelConnection->makeConnect(this, &AccountList::lUpdate, [this]() { + mModelConnection->invokeToModel([this]() { + QList> accounts; + // Model thread. + mustBeInLinphoneThread(getClassName()); + auto linphoneAccounts = CoreModel::getInstance()->getCore()->getAccountList(); + for (auto it : linphoneAccounts) { + auto model = AccountCore::create(it); + accounts.push_back(model); + } + mModelConnection->invokeToCore([this, accounts]() { + mustBeInMainThread(getClassName()); + clearData(); + add(accounts); + }); + }); + }); + + lUpdate(); +} + +AccountGui *AccountList::getDefaultAccount() const { + for (auto it : mList) { + auto account = it.objectCast(); + if (account->getIsDefaultAccount()) return new AccountGui(account); + } + return nullptr; +} +/* +void AccountList::update() { + App::postModelAsync([=]() { + QList> accounts; + // Model thread. + mustBeInLinphoneThread(getClassName()); + auto linphoneAccounts = CoreModel::getInstance()->getCore()->getAccountList(); + for (auto it : linphoneAccounts) { + auto model = AccountCore::create(it); + accounts.push_back(model); + } + // Invoke for adding stuffs in caller thread + QMetaObject::invokeMethod(this, [this, accounts]() { + mustBeInMainThread(getClassName()); + clearData(); + add(accounts); + }); + }); +} +*/ +QVariant AccountList::data(const QModelIndex &index, int role) const { + int row = index.row(); + if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant(); + if (role == Qt::DisplayRole) return QVariant::fromValue(new AccountGui(mList[row].objectCast())); + return QVariant(); } diff --git a/Linphone/core/account/AccountList.hpp b/Linphone/core/account/AccountList.hpp index 38b5d8b09..7a392d359 100644 --- a/Linphone/core/account/AccountList.hpp +++ b/Linphone/core/account/AccountList.hpp @@ -23,15 +23,28 @@ #include "../proxy/ListProxy.hpp" #include "tool/AbstractObject.hpp" +#include "tool/thread/SafeConnection.hpp" #include + +class AccountGui; // ============================================================================= class AccountList : public ListProxy, public AbstractObject { Q_OBJECT public: + static QSharedPointer create(); AccountList(QObject *parent = Q_NULLPTR); ~AccountList(); + void setSelf(QSharedPointer me); + + AccountGui *getDefaultAccount() const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; +signals: + void lUpdate(); + +private: + QSharedPointer mModelConnection; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/core/account/AccountProxy.cpp b/Linphone/core/account/AccountProxy.cpp index ecd83afdc..80c23c26e 100644 --- a/Linphone/core/account/AccountProxy.cpp +++ b/Linphone/core/account/AccountProxy.cpp @@ -19,15 +19,19 @@ */ #include "AccountProxy.hpp" -#include "Account.hpp" +#include "AccountGui.hpp" #include "AccountList.hpp" AccountProxy::AccountProxy(QObject *parent) : SortFilterProxy(parent) { - setSourceModel(new AccountList(this)); + qDebug() << "[AccountProxy] new" << this; + mList = AccountList::create(); + connect(mList.get(), &AccountList::countChanged, this, &AccountProxy::defaultAccountChanged); + setSourceModel(mList.get()); sort(0); } AccountProxy::~AccountProxy() { + qDebug() << "[AccountProxy] delete" << this; } QString AccountProxy::getFilterText() const { @@ -42,6 +46,13 @@ void AccountProxy::setFilterText(const QString &filter) { } } +AccountGui *AccountProxy::getDefaultAccount() const { + return dynamic_cast(sourceModel())->getDefaultAccount(); +} + +void AccountProxy::setDefaultAccount(AccountGui *account) { +} + bool AccountProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { bool show = (mFilterText.isEmpty() || mFilterText == "*"); if (!show) { @@ -50,8 +61,8 @@ bool AccountProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourcePare QRegularExpression::UseUnicodePropertiesOption); QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); auto model = sourceModel()->data(index); - auto account = model.value(); - show = account->getIdentityAddress().contains(search); + auto account = model.value(); + show = account->getCore()->getIdentityAddress().contains(search); } return show; @@ -61,5 +72,6 @@ bool AccountProxy::lessThan(const QModelIndex &left, const QModelIndex &right) c auto l = sourceModel()->data(left); auto r = sourceModel()->data(right); - return l.value()->getIdentityAddress() < r.value()->getIdentityAddress(); + return l.value()->getCore()->getIdentityAddress() < + r.value()->getCore()->getIdentityAddress(); } diff --git a/Linphone/core/account/AccountProxy.hpp b/Linphone/core/account/AccountProxy.hpp index fe5e1492f..cfc528bfb 100644 --- a/Linphone/core/account/AccountProxy.hpp +++ b/Linphone/core/account/AccountProxy.hpp @@ -22,6 +22,8 @@ #define ACCOUNT_PROXY_H_ #include "../proxy/SortFilterProxy.hpp" +#include "core/account/AccountGui.hpp" +#include "core/account/AccountList.hpp" // ============================================================================= @@ -29,6 +31,7 @@ class AccountProxy : public SortFilterProxy { Q_OBJECT Q_PROPERTY(QString filterText READ getFilterText WRITE setFilterText NOTIFY filterTextChanged) + Q_PROPERTY(AccountGui *defaultAccount READ getDefaultAccount WRITE setDefaultAccount NOTIFY defaultAccountChanged) public: AccountProxy(QObject *parent = Q_NULLPTR); @@ -37,14 +40,19 @@ public: QString getFilterText() const; void setFilterText(const QString &filter); + AccountGui *getDefaultAccount() const; + void setDefaultAccount(AccountGui *account); + signals: void filterTextChanged(); + void defaultAccountChanged(); protected: virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; QString mFilterText; + QSharedPointer mList; }; #endif diff --git a/Linphone/core/call/Call.cpp b/Linphone/core/call/Call.cpp deleted file mode 100644 index 3d281050d..000000000 --- a/Linphone/core/call/Call.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2010-2024 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 "Call.hpp" -#include "core/App.hpp" -#include "tool/Utils.hpp" - -DEFINE_ABSTRACT_OBJECT(Call) - -Call::Call(const std::shared_ptr &call) : QObject(nullptr) { - // Should be call from model Thread - mustBeInLinphoneThread(getClassName()); - mCallModel = Utils::makeQObject_ptr(call); - connect(mCallModel.get(), &CallModel::stateChanged, this, &Call::onStateChanged); - connect(this, &Call::lAccept, mCallModel.get(), &CallModel::accept); - connect(this, &Call::lDecline, mCallModel.get(), &CallModel::decline); - connect(this, &Call::lTerminate, mCallModel.get(), &CallModel::terminate); - mCallModel->setSelf(mCallModel); - mState = LinphoneEnums::fromLinphone(call->getState()); -} - -Call::~Call() { - mustBeInMainThread("~" + getClassName()); - emit mCallModel->removeListener(); -} - -LinphoneEnums::CallStatus Call::getStatus() const { - return mStatus; -} - -void Call::setStatus(LinphoneEnums::CallStatus status) { - mustBeInMainThread(log().arg(Q_FUNC_INFO)); - if (mStatus != status) { - mStatus = status; - emit statusChanged(mStatus); - } -} - -LinphoneEnums::CallState Call::getState() const { - return mState; -} - -void Call::setState(LinphoneEnums::CallState state, const QString &message) { - mustBeInMainThread(log().arg(Q_FUNC_INFO)); - if (mState != state) { - mState = state; - if (state == LinphoneEnums::CallState::Error) setLastErrorMessage(message); - emit stateChanged(mState); - } -} - -void Call::onStateChanged(linphone::Call::State state, const std::string &message) { - setState(LinphoneEnums::fromLinphone(state), Utils::coreStringToAppString(message)); -} - -QString Call::getLastErrorMessage() const { - return mLastErrorMessage; -} -void Call::setLastErrorMessage(const QString &message) { - if (mLastErrorMessage != message) { - mLastErrorMessage = message; - emit lastErrorMessageChanged(); - } -} diff --git a/Linphone/core/call/CallCore.cpp b/Linphone/core/call/CallCore.cpp new file mode 100644 index 000000000..a5d0ef088 --- /dev/null +++ b/Linphone/core/call/CallCore.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010-2024 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 "CallCore.hpp" +#include "core/App.hpp" +#include "model/object/VariantObject.hpp" +#include "tool/Utils.hpp" +#include "tool/thread/SafeConnection.hpp" + +DEFINE_ABSTRACT_OBJECT(CallCore) + +QSharedPointer CallCore::create(const std::shared_ptr &call) { + auto sharedPointer = QSharedPointer(new CallCore(call), &QObject::deleteLater); + sharedPointer->setSelf(sharedPointer); + sharedPointer->moveToThread(App::getInstance()->thread()); + return sharedPointer; +} + +CallCore::CallCore(const std::shared_ptr &call) : QObject(nullptr) { + qDebug() << "[CallCore] new" << this; + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + // Should be call from model Thread + mustBeInLinphoneThread(getClassName()); + mDuration = call->getDuration(); + mMicrophoneMuted = call->getMicrophoneMuted(); + mCallModel = Utils::makeQObject_ptr(call); + connect(mCallModel.get(), &CallModel::stateChanged, this, &CallCore::onStateChanged); + connect(this, &CallCore::lAccept, mCallModel.get(), &CallModel::accept); + connect(this, &CallCore::lDecline, mCallModel.get(), &CallModel::decline); + connect(this, &CallCore::lTerminate, mCallModel.get(), &CallModel::terminate); + mCallModel->setSelf(mCallModel); + mState = LinphoneEnums::fromLinphone(call->getState()); +} + +CallCore::~CallCore() { + qDebug() << "[CallCore] delete" << this; + mustBeInMainThread("~" + getClassName()); + emit mCallModel->removeListener(); +} + +void CallCore::setSelf(QSharedPointer me) { + mAccountModelConnection = QSharedPointer( + new SafeConnection(me.objectCast(), std::dynamic_pointer_cast(mCallModel)), + &QObject::deleteLater); + mAccountModelConnection->makeConnect(this, &CallCore::lSetMicrophoneMuted, [this](bool isMuted) { + mAccountModelConnection->invokeToModel([this, isMuted]() { mCallModel->setMicrophoneMuted(isMuted); }); + }); + mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::microphoneMutedChanged, [this](bool isMuted) { + mAccountModelConnection->invokeToCore([this, isMuted]() { setMicrophoneMuted(isMuted); }); + }); + mAccountModelConnection->makeConnect(mCallModel.get(), &CallModel::durationChanged, [this](int duration) { + mAccountModelConnection->invokeToCore([this, duration]() { setDuration(duration); }); + }); +} + +LinphoneEnums::CallStatus CallCore::getStatus() const { + return mStatus; +} + +void CallCore::setStatus(LinphoneEnums::CallStatus status) { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + if (mStatus != status) { + mStatus = status; + emit statusChanged(mStatus); + } +} + +LinphoneEnums::CallState CallCore::getState() const { + return mState; +} + +void CallCore::setState(LinphoneEnums::CallState state, const QString &message) { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + if (mState != state) { + mState = state; + if (state == LinphoneEnums::CallState::Error) setLastErrorMessage(message); + emit stateChanged(mState); + } +} + +void CallCore::onStateChanged(linphone::Call::State state, const std::string &message) { + setState(LinphoneEnums::fromLinphone(state), Utils::coreStringToAppString(message)); +} + +QString CallCore::getLastErrorMessage() const { + return mLastErrorMessage; +} +void CallCore::setLastErrorMessage(const QString &message) { + if (mLastErrorMessage != message) { + mLastErrorMessage = message; + emit lastErrorMessageChanged(); + } +} + +int CallCore::getDuration() { + return mDuration; +} + +void CallCore::setDuration(int duration) { + if (mDuration != duration) { + mDuration = duration; + emit durationChanged(mDuration); + } +} + +bool CallCore::getMicrophoneMuted() const { + return mMicrophoneMuted; +} + +void CallCore::setMicrophoneMuted(bool isMuted) { + if (mMicrophoneMuted != isMuted) { + mMicrophoneMuted = isMuted; + emit microphoneMutedChanged(); + } +} diff --git a/Linphone/core/call/Call.hpp b/Linphone/core/call/CallCore.hpp similarity index 58% rename from Linphone/core/call/Call.hpp rename to Linphone/core/call/CallCore.hpp index 006a17028..8fae5b676 100644 --- a/Linphone/core/call/Call.hpp +++ b/Linphone/core/call/CallCore.hpp @@ -18,8 +18,8 @@ * along with this program. If not, see . */ -#ifndef CALL_H_ -#define CALL_H_ +#ifndef CALL_CORE_H_ +#define CALL_CORE_H_ #include "model/call/CallModel.hpp" #include "tool/LinphoneEnums.hpp" @@ -27,17 +27,23 @@ #include #include -class Call : public QObject, public AbstractObject { +class SafeConnection; + +class CallCore : public QObject, public AbstractObject { Q_OBJECT Q_PROPERTY(LinphoneEnums::CallStatus status READ getStatus NOTIFY statusChanged) Q_PROPERTY(LinphoneEnums::CallState state READ getState NOTIFY stateChanged) Q_PROPERTY(QString lastErrorMessage READ getLastErrorMessage NOTIFY lastErrorMessageChanged) + Q_PROPERTY(int duration READ getDuration NOTIFY durationChanged); + Q_PROPERTY(bool microphoneMuted READ getMicrophoneMuted WRITE lSetMicrophoneMuted NOTIFY microphoneMutedChanged) public: // Should be call from model Thread. Will be automatically in App thread after initialization - Call(const std::shared_ptr &call); - ~Call(); + static QSharedPointer create(const std::shared_ptr &call); + CallCore(const std::shared_ptr &call); + ~CallCore(); + void setSelf(QSharedPointer me); LinphoneEnums::CallStatus getStatus() const; void setStatus(LinphoneEnums::CallStatus status); @@ -49,43 +55,56 @@ public: QString getLastErrorMessage() const; void setLastErrorMessage(const QString &message); + int getDuration(); + void setDuration(int duration); + + bool getMicrophoneMuted() const; + void setMicrophoneMuted(bool isMuted); + signals: void statusChanged(LinphoneEnums::CallStatus status); void stateChanged(LinphoneEnums::CallState state); void lastErrorMessageChanged(); + void durationChanged(int duration); + void microphoneMutedChanged(); // Linphone commands void lAccept(bool withVideo); // Accept an incoming call void lDecline(); // Decline an incoming call void lTerminate(); // Hangup a call - /* TODO - Q_INVOKABLE void acceptWithVideo(); - - Q_INVOKABLE void askForTransfer(); - Q_INVOKABLE void askForAttendedTransfer(); - Q_INVOKABLE bool transferTo(const QString &sipAddress); - Q_INVOKABLE bool transferToAnother(const QString &peerAddress); - - Q_INVOKABLE bool getRemoteVideoEnabled() const; - Q_INVOKABLE void acceptVideoRequest(); - Q_INVOKABLE void rejectVideoRequest(); - - Q_INVOKABLE void takeSnapshot(); - Q_INVOKABLE void startRecording(); - Q_INVOKABLE void stopRecording(); - - Q_INVOKABLE void sendDtmf(const QString &dtmf); - Q_INVOKABLE void verifyAuthenticationToken(bool verify); - Q_INVOKABLE void updateStreams(); - Q_INVOKABLE void toggleSpeakerMute(); - */ + void lSetMicrophoneMuted(bool isMuted); + + /* TODO + Q_INVOKABLE void acceptWithVideo(); + + Q_INVOKABLE void askForTransfer(); + Q_INVOKABLE void askForAttendedTransfer(); + Q_INVOKABLE bool transferTo(const QString &sipAddress); + Q_INVOKABLE bool transferToAnother(const QString &peerAddress); + + Q_INVOKABLE bool getRemoteVideoEnabled() const; + Q_INVOKABLE void acceptVideoRequest(); + Q_INVOKABLE void rejectVideoRequest(); + + Q_INVOKABLE void takeSnapshot(); + Q_INVOKABLE void startRecording(); + Q_INVOKABLE void stopRecording(); + + Q_INVOKABLE void sendDtmf(const QString &dtmf); + Q_INVOKABLE void verifyAuthenticationToken(bool verify); + Q_INVOKABLE void updateStreams(); + Q_INVOKABLE void toggleSpeakerMute(); + */ private: std::shared_ptr mCallModel; LinphoneEnums::CallStatus mStatus; LinphoneEnums::CallState mState; QString mLastErrorMessage; + int mDuration = 0; + bool mMicrophoneMuted; + QSharedPointer mAccountModelConnection; DECLARE_ABSTRACT_OBJECT }; -Q_DECLARE_METATYPE(Call *) +Q_DECLARE_METATYPE(CallCore *) #endif diff --git a/Linphone/core/call/CallGui.cpp b/Linphone/core/call/CallGui.cpp new file mode 100644 index 000000000..55edbd380 --- /dev/null +++ b/Linphone/core/call/CallGui.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2024 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 "CallGui.hpp" +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(CallGui) + +CallGui::CallGui(QSharedPointer core) { + mustBeInMainThread(getClassName()); + qDebug() << "[CallGui] new" << this; + App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); + mCore = core; +} + +CallGui::~CallGui() { + mustBeInMainThread("~" + getClassName()); + qDebug() << "[CallGui] delete" << this; +} + +CallCore *CallGui::getCore() const { + return mCore.get(); +} diff --git a/Linphone/core/call/CallGui.hpp b/Linphone/core/call/CallGui.hpp new file mode 100644 index 000000000..6a35f85e9 --- /dev/null +++ b/Linphone/core/call/CallGui.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2024 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 . + */ + +#ifndef CALL_GUI_H_ +#define CALL_GUI_H_ + +#include "CallCore.hpp" +#include +#include + +class CallGui : public QObject, public AbstractObject { + Q_OBJECT + + Q_PROPERTY(CallCore *core READ getCore CONSTANT) + +public: + CallGui(QSharedPointer core); + ~CallGui(); + CallCore *getCore() const; + QSharedPointer mCore; + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/core/notifier/Notifier.cpp b/Linphone/core/notifier/Notifier.cpp index f94003969..cf6363ae0 100644 --- a/Linphone/core/notifier/Notifier.cpp +++ b/Linphone/core/notifier/Notifier.cpp @@ -32,7 +32,7 @@ #include "Notifier.hpp" #include "core/App.hpp" -#include "core/call/Call.hpp" +#include "core/call/CallGui.hpp" #include "tool/LinphoneEnums.hpp" #include "tool/providers/ImageProvider.hpp" @@ -274,20 +274,23 @@ void Notifier::deleteNotification(QVariant notification) { void Notifier::notifyReceivedCall(const shared_ptr &call) { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); - auto model = new Call(call); - model->moveToThread(this->thread()); - App::postCoreAsync([this, model]() { + auto model = CallCore::create(call); + auto gui = new CallGui(model); + gui->moveToThread(App::getInstance()->thread()); + App::postCoreAsync([this, gui]() { mustBeInMainThread(getClassName()); QVariantMap map; - map["call"].setValue(model); + + map["call"].setValue(gui); CREATE_NOTIFICATION(Notifier::ReceivedCall, map) QObject::connect( - model, &Call::statusChanged, notification, [this, notification](LinphoneEnums::CallStatus status) { + gui->getCore(), &CallCore::statusChanged, notification, + [this, notification](LinphoneEnums::CallStatus status) { qInfo() << log().arg("Delete notification on call status : %1").arg(LinphoneEnums::toString(status)); deleteNotification(QVariant::fromValue(notification)); }); - QObject::connect(model, &Call::destroyed, notification, + QObject::connect(gui->getCore(), &CallCore::destroyed, notification, [this, notification]() { deleteNotification(QVariant::fromValue(notification)); }); }); } diff --git a/Linphone/core/notifier/Notifier.hpp b/Linphone/core/notifier/Notifier.hpp index 9f67e2468..0916a6d2c 100644 --- a/Linphone/core/notifier/Notifier.hpp +++ b/Linphone/core/notifier/Notifier.hpp @@ -23,10 +23,10 @@ #include -#include "core/call/Call.hpp" #include "tool/AbstractObject.hpp" #include #include +#include // ============================================================================= class QMutex; diff --git a/Linphone/model/CMakeLists.txt b/Linphone/model/CMakeLists.txt index 459b17810..aebbf6d7a 100644 --- a/Linphone/model/CMakeLists.txt +++ b/Linphone/model/CMakeLists.txt @@ -11,6 +11,7 @@ list(APPEND _LINPHONEAPP_SOURCES model/logger/LoggerModel.cpp model/logger/LoggerListener.cpp + model/object/SafeObject.cpp model/object/VariantObject.cpp model/setting/SettingsModel.cpp diff --git a/Linphone/model/account/AccountManager.cpp b/Linphone/model/account/AccountManager.cpp index 06c5d8d42..8eb5c8376 100644 --- a/Linphone/model/account/AccountManager.cpp +++ b/Linphone/model/account/AccountManager.cpp @@ -79,6 +79,7 @@ bool AccountManager::login(QString username, QString password) { connect(mAccountModel.get(), &AccountModel::registrationStateChanged, this, &AccountManager::onRegistrationStateChanged); core->addAccount(account); + emit CoreModel::getInstance()->accountAdded(); return true; } diff --git a/Linphone/model/account/AccountModel.cpp b/Linphone/model/account/AccountModel.cpp index 508add79e..18ba8e888 100644 --- a/Linphone/model/account/AccountModel.cpp +++ b/Linphone/model/account/AccountModel.cpp @@ -22,11 +22,16 @@ #include +#include "model/core/CoreModel.hpp" +#include "tool/Utils.hpp" + DEFINE_ABSTRACT_OBJECT(AccountModel) AccountModel::AccountModel(const std::shared_ptr &account, QObject *parent) : ::Listener(account, parent) { mustBeInLinphoneThread(getClassName()); + connect(CoreModel::getInstance().get(), &CoreModel::defaultAccountChanged, this, + &AccountModel::onDefaultAccountChanged); } AccountModel::~AccountModel() { @@ -39,11 +44,22 @@ void AccountModel::onRegistrationStateChanged(const std::shared_ptr(mMonitor); auto params = account->getParams()->clone(); - params->setPictureUri(uri); + params->setPictureUri(Utils::appStringToCoreString(uri)); account->setParams(params); emit pictureUriChanged(uri); } + +void AccountModel::onDefaultAccountChanged() { + emit defaultAccountChanged(CoreModel::getInstance()->getCore()->getDefaultAccount() == mMonitor); +} + +void AccountModel::setDefault() { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto core = CoreModel::getInstance()->getCore(); + core->setDefaultAccount(mMonitor); + emit CoreModel::getInstance()->defaultAccountChanged(); +} diff --git a/Linphone/model/account/AccountModel.hpp b/Linphone/model/account/AccountModel.hpp index 17f1c5fc6..bc648f9c3 100644 --- a/Linphone/model/account/AccountModel.hpp +++ b/Linphone/model/account/AccountModel.hpp @@ -38,15 +38,18 @@ public: virtual void onRegistrationStateChanged(const std::shared_ptr &account, linphone::RegistrationState state, const std::string &message) override; + void onDefaultAccountChanged(); - void setPictureUri(std::string uri); + void setPictureUri(QString uri); + void setDefault(); signals: void registrationStateChanged(const std::shared_ptr &account, linphone::RegistrationState state, const std::string &message); + void defaultAccountChanged(bool isDefault); - void pictureUriChanged(std::string uri); + void pictureUriChanged(QString uri); private: DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/model/call/CallModel.cpp b/Linphone/model/call/CallModel.cpp index 7ebd70328..c4417fa4a 100644 --- a/Linphone/model/call/CallModel.cpp +++ b/Linphone/model/call/CallModel.cpp @@ -29,6 +29,10 @@ DEFINE_ABSTRACT_OBJECT(CallModel) CallModel::CallModel(const std::shared_ptr &call, QObject *parent) : ::Listener(call, parent) { mustBeInLinphoneThread(getClassName()); + mDurationTimer.setInterval(1000); + mDurationTimer.setSingleShot(false); + connect(&mDurationTimer, &QTimer::timeout, this, [this]() { this->durationChanged(mMonitor->getDuration()); }); + mDurationTimer.start(); } CallModel::~CallModel() { @@ -65,6 +69,11 @@ void CallModel::terminate() { mMonitor->terminate(); } +void CallModel::setMicrophoneMuted(bool isMuted) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + mMonitor->setMicrophoneMuted(isMuted); + emit microphoneMutedChanged(isMuted); +} void CallModel::onDtmfReceived(const std::shared_ptr &call, int dtmf) { emit dtmfReceived(call, dtmf); } diff --git a/Linphone/model/call/CallModel.hpp b/Linphone/model/call/CallModel.hpp index 5314a08ee..009c0ddf9 100644 --- a/Linphone/model/call/CallModel.hpp +++ b/Linphone/model/call/CallModel.hpp @@ -25,6 +25,7 @@ #include "tool/AbstractObject.hpp" #include +#include #include class CallModel : public ::Listener, @@ -39,7 +40,14 @@ public: void decline(); void terminate(); + void setMicrophoneMuted(bool isMuted); + +signals: + void microphoneMutedChanged(bool isMuted); + void durationChanged(int); + private: + QTimer mDurationTimer; DECLARE_ABSTRACT_OBJECT //-------------------------------------------------------------------------------- diff --git a/Linphone/model/core/CoreModel.cpp b/Linphone/model/core/CoreModel.cpp index 26541024b..fe0ab9f7d 100644 --- a/Linphone/model/core/CoreModel.cpp +++ b/Linphone/model/core/CoreModel.cpp @@ -139,6 +139,10 @@ void CoreModel::setPathAfterStart() { //--------------------------------------------------------------------------------------------------------------------------- +void CoreModel::onAccountAdded() { + emit accountAdded(); +} + void CoreModel::onAccountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, diff --git a/Linphone/model/core/CoreModel.hpp b/Linphone/model/core/CoreModel.hpp index d4e746d9b..bdbfd7a8d 100644 --- a/Linphone/model/core/CoreModel.hpp +++ b/Linphone/model/core/CoreModel.hpp @@ -56,6 +56,7 @@ public: signals: void loggerInitialized(); + void defaultAccountChanged(); private: QString mConfigPath; @@ -71,6 +72,7 @@ private: //-------------------------------------------------------------------------------- // LINPHONE //-------------------------------------------------------------------------------- + virtual void onAccountAdded(); // override (Not yet implemented by SDK) virtual void onAccountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, @@ -152,6 +154,7 @@ private: const std::string &url) override; signals: + void accountAdded(); void accountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, diff --git a/Linphone/model/object/SafeObject.cpp b/Linphone/model/object/SafeObject.cpp new file mode 100644 index 000000000..21e029147 --- /dev/null +++ b/Linphone/model/object/SafeObject.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2024 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 "SafeObject.hpp" + +#include +#include + +#include "core/App.hpp" + +DEFINE_ABSTRACT_OBJECT(SafeObject) + +SafeObject::SafeObject(QObject *parent) { +} +SafeObject::SafeObject(QVariant defaultValue, QObject *parent) : mValue(defaultValue) { +} +SafeObject::~SafeObject() { +} + +QVariant SafeObject::getValue() const { + return mValue; +} + +void SafeObject::onSetValue(QVariant value) { + if (value != mValue) { + mValue = value; + emit valueChanged(mValue); + } +} diff --git a/Linphone/model/object/SafeObject.hpp b/Linphone/model/object/SafeObject.hpp new file mode 100644 index 000000000..655ee2d7a --- /dev/null +++ b/Linphone/model/object/SafeObject.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010-2024 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 . + */ + +#ifndef SAFE_OBJECT_H_ +#define SAFE_OBJECT_H_ + +#include "tool/AbstractObject.hpp" + +#include +#include + +class SafeObject : public QObject, public AbstractObject { + Q_OBJECT +public: + SafeObject(QObject *parent = nullptr); + SafeObject(QVariant defaultValue, QObject *parent = nullptr); + ~SafeObject(); + + QVariant getValue() const; + void onSetValue(QVariant value); +signals: + void requestValue(); + void setValue(QVariant value); + void valueChanged(QVariant value); + +private: + QVariant mValue; + + DECLARE_ABSTRACT_OBJECT +}; + +#endif diff --git a/Linphone/model/object/VariantObject.cpp b/Linphone/model/object/VariantObject.cpp index a5e7335ca..5a7e78a21 100644 --- a/Linphone/model/object/VariantObject.cpp +++ b/Linphone/model/object/VariantObject.cpp @@ -26,39 +26,29 @@ #include "core/App.hpp" DEFINE_ABSTRACT_OBJECT(VariantObject) - -VariantObject::VariantObject(QObject *parent) { - mThreadLocation = false; - mustBeInMainThread(getClassName()); - mCoreObject = nullptr; +VariantObject::VariantObject(QObject *parent) : VariantObject(QVariant()) { } +VariantObject::VariantObject(QVariant defaultValue, QObject *parent) { + mCoreObject = QSharedPointer::create(defaultValue); + mModelObject = QSharedPointer::create(); + mModelObject->moveToThread(CoreModel::getInstance()->thread()); -VariantObject::VariantObject(QVariant value, QObject *parent) : mValue(value) { - mThreadLocation = true; - mustBeInMainThread(getClassName()); - connect(this, &VariantObject::valueUpdated, this, &VariantObject::setValue); - mCoreObject = new VariantObject(nullptr); - mCoreObject->moveToThread(CoreModel::getInstance()->thread()); - connect(mCoreObject, &VariantObject::valueChanged, this, &VariantObject::setValue); - connect(mCoreObject, &VariantObject::valueChanged, mCoreObject, &QObject::deleteLater); + mConnection = QSharedPointer( + new SafeConnection(mCoreObject.objectCast(), mModelObject.objectCast()), + &QObject::deleteLater); + + mConnection->makeConnect(mCoreObject.get(), &SafeObject::setValue, &SafeObject::onSetValue); + mConnection->makeConnect(mModelObject.get(), &SafeObject::setValue, &SafeObject::onSetValue); + connect(mCoreObject.get(), &SafeObject::valueChanged, this, &VariantObject::valueChanged); } VariantObject::~VariantObject() { - if (mThreadLocation) mustBeInMainThread("~" + getClassName()); - else mustBeInLinphoneThread("~" + getClassName()); } QVariant VariantObject::getValue() const { - if (mThreadLocation) mustBeInMainThread(QString(gClassName) + " : " + Q_FUNC_INFO); - else mustBeInLinphoneThread(QString(gClassName) + " : " + Q_FUNC_INFO); - return mValue; + return mCoreObject->getValue(); } -void VariantObject::setValue(QVariant value) { - if (mThreadLocation) mustBeInMainThread(QString(gClassName) + " : " + Q_FUNC_INFO); - else mustBeInLinphoneThread(QString(gClassName) + " : " + Q_FUNC_INFO); - if (value != mValue) { - mValue = value; - emit valueChanged(mValue); - } +void VariantObject::requestValue() { + emit mCoreObject->requestValue(); } diff --git a/Linphone/model/object/VariantObject.hpp b/Linphone/model/object/VariantObject.hpp index a74043999..bb0546d28 100644 --- a/Linphone/model/object/VariantObject.hpp +++ b/Linphone/model/object/VariantObject.hpp @@ -24,35 +24,41 @@ #include "tool/AbstractObject.hpp" #include +#include #include -// Store the VariantObject on a propery and use value. -// Do not use direcly teh value like VariantObject.value : in this case if value change, VariantObject will be -// reevaluated. +#include "SafeObject.hpp" +#include "tool/thread/SafeConnection.hpp" class VariantObject : public QObject, public AbstractObject { Q_OBJECT + Q_PROPERTY(QVariant value READ getValue NOTIFY valueChanged) public: - Q_PROPERTY(QVariant value READ getValue WRITE setValue NOTIFY valueChanged) - VariantObject(QObject *parent = nullptr); - VariantObject(QVariant value, QObject *parent = nullptr); + VariantObject(QVariant defaultValue, QObject *parent = nullptr); ~VariantObject(); + template + void makeRequest(Func &&callable, Args &&...args) { + mConnection->makeConnect(mCoreObject.get(), &SafeObject::requestValue, [this, callable, args...]() { + mConnection->invokeToModel([this, callable, args...]() { mModelObject->setValue(callable(args...)); }); + }); + } + template + void makeUpdate(const typename QtPrivate::FunctionPointer::Object *sender, SenderClass signal) { + mConnection->makeConnect(sender, signal, + [this]() { mConnection->invokeToCore([this]() { mCoreObject->requestValue(); }); }); + } + QVariant getValue() const; - void setValue(QVariant value); - - // mCoreObject must be used to request update value : this object will be not be deleted by GUI so it is safe to use - // inside model thread. call emit updateValue() from coreObject to set value from model. - VariantObject *mCoreObject; // Ensure to use DeleteLater() after updating value + void requestValue(); + QSharedPointer mCoreObject, mModelObject; + QSharedPointer mConnection; signals: void valueChanged(QVariant value); - void valueUpdated(QVariant value); private: - QVariant mValue; - bool mThreadLocation = true; // true=Core, false=Model DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/model/tool/ToolModel.cpp b/Linphone/model/tool/ToolModel.cpp index 9416b1c5b..7f100a16a 100644 --- a/Linphone/model/tool/ToolModel.cpp +++ b/Linphone/model/tool/ToolModel.cpp @@ -68,9 +68,9 @@ QString ToolModel::getDisplayName(QString address) { return displayName.isEmpty() ? address : displayName; } -Call *ToolModel::startAudioCall(const QString &sipAddress, - const QString &prepareTransfertAddress, - const QHash &headers) { +QSharedPointer ToolModel::startAudioCall(const QString &sipAddress, + const QString &prepareTransfertAddress, + const QHash &headers) { bool waitRegistrationForCall = true; // getSettingsModel()->getWaitRegistrationForCall() std::shared_ptr core = CoreModel::getInstance()->getCore(); @@ -93,7 +93,7 @@ Call *ToolModel::startAudioCall(const QString &sipAddress, if (core->getDefaultAccount()) params->setAccount(core->getDefaultAccount()); // CallModel::setRecordFile(params, Utils::coreStringToAppString(address->getUsername())); auto call = core->inviteAddressWithParams(address, params); - return call ? new Call(call) : nullptr; + return call ? CallCore::create(call) : nullptr; /* TODO transfer diff --git a/Linphone/model/tool/ToolModel.hpp b/Linphone/model/tool/ToolModel.hpp index 192c0ab3c..5e3f0bb49 100644 --- a/Linphone/model/tool/ToolModel.hpp +++ b/Linphone/model/tool/ToolModel.hpp @@ -21,7 +21,7 @@ #ifndef TOOL_MODEL_H_ #define TOOL_MODEL_H_ -#include "core/call/Call.hpp" +#include "core/call/CallCore.hpp" #include "tool/AbstractObject.hpp" #include @@ -39,9 +39,9 @@ public: static QString getDisplayName(const std::shared_ptr &address); static QString getDisplayName(QString address); - static Call *startAudioCall(const QString &sipAddress, - const QString &prepareTransfertAddress = "", - const QHash &headers = {}); + static QSharedPointer startAudioCall(const QString &sipAddress, + const QString &prepareTransfertAddress = "", + const QHash &headers = {}); private: DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/tool/CMakeLists.txt b/Linphone/tool/CMakeLists.txt index 638ddecf8..6963882b4 100644 --- a/Linphone/tool/CMakeLists.txt +++ b/Linphone/tool/CMakeLists.txt @@ -3,6 +3,8 @@ list(APPEND _LINPHONEAPP_SOURCES tool/Utils.cpp tool/LinphoneEnums.cpp + tool/thread/SafeSharedPointer.hpp + tool/thread/SafeConnection.cpp tool/thread/Thread.cpp tool/providers/ImageProvider.cpp ) diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index 0d73b0917..217a850cf 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -21,7 +21,7 @@ #include "Utils.hpp" #include "core/App.hpp" -#include "model/call/CallModel.hpp" +#include "core/call/CallGui.hpp" #include "model/object/VariantObject.hpp" #include "model/tool/ToolModel.hpp" @@ -42,10 +42,11 @@ char *Utils::rstrstr(const char *a, const char *b) { VariantObject *Utils::getDisplayName(const QString &address) { VariantObject *data = new VariantObject(address); // Scope : GUI - App::postModelAsync([coreObject = data->mCoreObject, address]() mutable { + data->makeRequest([address]() { QString displayName = ToolModel::getDisplayName(address); - coreObject->setValue(displayName); + return displayName; }); + data->requestValue(); return data; } @@ -53,16 +54,27 @@ VariantObject *Utils::startAudioCall(const QString &sipAddress, const QString &prepareTransfertAddress, const QHash &headers) { VariantObject *data = new VariantObject(QVariant()); // Scope : GUI - qDebug() << "Calling " << sipAddress; - App::postModelAsync([coreObject = data->mCoreObject, sipAddress, prepareTransfertAddress, headers]() mutable { + + data->makeRequest([sipAddress, prepareTransfertAddress, headers]() { auto call = ToolModel::startAudioCall(sipAddress, prepareTransfertAddress, headers); - if (call && coreObject) { - call->moveToThread(App::getInstance()->thread()); - coreObject->setValue(QVariant::fromValue(call)); - // App::postCoreAsync([data, call]() { data->setValue(QVariant::fromValue(call)); }); - // emit coreObject->valueChanged(call); - } + if (call) { + return QVariant::fromValue(new CallGui(call)); + } else return QVariant(); }); + data->requestValue(); return data; } + +VariantObject *Utils::haveAccount() { + VariantObject *result = new VariantObject(); + + // Using connect ensure to have sender() and receiver() alive. + result->makeRequest([]() { + // Model + return CoreModel::getInstance()->getCore()->getAccountList().size() > 0; + }); + result->makeUpdate(CoreModel::getInstance().get(), &CoreModel::accountAdded); + result->requestValue(); + return result; +} diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index a184c0053..f7f417be9 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -52,6 +52,7 @@ public: Q_INVOKABLE static VariantObject *startAudioCall(const QString &sipAddress, const QString &prepareTransfertAddress = "", const QHash &headers = {}); + Q_INVOKABLE static VariantObject *haveAccount(); static inline QString coreStringToAppString(const std::string &str) { if (Constants::LinphoneLocaleEncoding == QString("UTF-8")) return QString::fromStdString(str); diff --git a/Linphone/tool/thread/SafeConnection.cpp b/Linphone/tool/thread/SafeConnection.cpp new file mode 100644 index 000000000..9180d1c34 --- /dev/null +++ b/Linphone/tool/thread/SafeConnection.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2024 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 "SafeConnection.hpp" + +SafeConnection::SafeConnection(SafeSharedPointer core, SafeSharedPointer model) + : mCore(core), mModel(model) { +} +SafeConnection::~SafeConnection() { + if (mCore.mCountRef != 0 || mModel.mCountRef != 0) + qCritical() << "[SafeConnection] Destruction while still having references"; +} diff --git a/Linphone/tool/thread/SafeConnection.hpp b/Linphone/tool/thread/SafeConnection.hpp new file mode 100644 index 000000000..6b5198533 --- /dev/null +++ b/Linphone/tool/thread/SafeConnection.hpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2010-2024 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 . + */ + +#ifndef SAFE_CONNECTION_H_ +#define SAFE_CONNECTION_H_ + +#include "SafeSharedPointer.hpp" +#include +#include + +// Use this class to protect sender/receiver from being deleted while running a +// signal/call + +/* + * ObjectGui : mainAcces for GUI. Its memory is managed by JavaScript. It contains a QSharedPointer of ObjectCore. + * ObjectCore : memory is CPP managed by QSharedPointer. + * ObjectModel: memory is managed by shared pointers and is running on Model thread. + * + * => ObjectGUI have QSharedPointer + * ObjectCore have std::shared_ptr and SafeConnection. + * + * Need: + * - static QSharedPointer create(Args...args); => It set self and moveToThread. + * - In GUI constructor : App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); + * - In Core/model : App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); + * - void setSelf(QSharedPointer me); => instantiate SafeConnection with : + * me.objectCast(), std::dynamic_pointer_cast()); + * + * Set connections in setSelf: + * - From model + * mSafeConnection->makeConnect( mModel.get() + * , &ObjectModel::signal, [this](params) { + * mSafeConnection->invokeToCore([this, params]() { this->slot(params); }); + * }); + * + *- From GUI + * mSafeConnection->makeConnect(this, &ObjectCore::lSignal, [this](params) { + * mSafeConnection->invokeToModel([this, params]() { mModel->slot(params); }); + * }); + * + * - Direct call can be call with only invokeToModel/invokeToCore + * + * + */ + +class SafeConnection : public QObject { +public: + SafeConnection(SafeSharedPointer a, SafeSharedPointer b); + ~SafeConnection(); + SafeSharedPointer mCore, mModel; + QMutex mLocker; + + template + static inline QMetaObject::Connection makeConnect(const typename QtPrivate::FunctionPointer::Object *sender, + Func1 signal, + Func2 slot, + Qt::ConnectionType type = Qt::AutoConnection) { + return connect(sender, signal, sender, slot, type); + } + + template + void invokeToModel(Func &&callable, Args &&...args) { + if (!tryLock()) return; + auto model = mModel.get(); + QMetaObject::invokeMethod(model, [&, model, callable, args...]() { // Is async + QMetaObject::invokeMethod(model, callable, args...); // Is Sync + unlock(); + }); + } + + // Will running call in Core + template + void invokeToCore(Func &&callable, Args &&...args) { + if (!tryLock()) return; + QMetaObject::invokeMethod(mCore.get(), [&, callable, args...]() { // Is async + QMetaObject::invokeMethod(mCore.get(), callable, args...); // Is Sync + unlock(); + }); + } + + bool tryLock() { + mLocker.lock(); + if (!mCore.lock() || !mModel.lock()) { // Direct locking + mCore.reset(); + mModel.reset(); + mLocker.unlock(); + return false; + } + mLocker.unlock(); + return true; + } + void unlock() { + mLocker.lock(); + mCore.unlock(); + mModel.unlock(); + mLocker.unlock(); + } +}; + +#endif diff --git a/Linphone/tool/thread/SafeSharedPointer.hpp b/Linphone/tool/thread/SafeSharedPointer.hpp new file mode 100644 index 000000000..653f23768 --- /dev/null +++ b/Linphone/tool/thread/SafeSharedPointer.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010-2024 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 . + */ + +#ifndef SAFE_SHARED_POINTER_H_ +#define SAFE_SHARED_POINTER_H_ + +#include +#include + +// Store a Qt/Std shared pointer +template +class SafeSharedPointer { +public: + QSharedPointer mQData; + QWeakPointer mQDataWeak; + std::shared_ptr mStdData; + std::weak_ptr mStdDataWeak; + int mCountRef = 0; + + SafeSharedPointer(QSharedPointer p) : mQDataWeak(p) { + } + SafeSharedPointer(std::shared_ptr p) : mStdDataWeak(p) { + } + + bool lock() { + if (mCountRef == 0) { + if (!mQDataWeak.isNull()) { + mQData = mQDataWeak.lock(); + if (mQData) ++mCountRef; + return !mQData.isNull(); + } else if (!mStdDataWeak.expired()) { + mStdData = mStdDataWeak.lock(); + if (mStdData) ++mCountRef; + return mStdData != nullptr; + } + return false; + } else { + ++mCountRef; + return true; + } + } + void unlock() { + if (mCountRef == 0) qWarning() << "[SafeConnection] too much unlocking"; + else if (--mCountRef == 0) { + mQData = nullptr; + mStdData = nullptr; + } + } + void reset() { + mQData = nullptr; + mStdData = nullptr; + mCountRef = 0; + } + A *get() { + if (mQData) return mQData.get(); + if (mStdData) return mStdData.get(); + return nullptr; + } +}; + +#endif diff --git a/Linphone/view/App/Main.qml b/Linphone/view/App/Main.qml index d32ed1c9c..8cd7bbc0b 100644 --- a/Linphone/view/App/Main.qml +++ b/Linphone/view/App/Main.qml @@ -84,12 +84,5 @@ Window { MainLayout { } } - - Component { - id: ongoingCallPage - OngoingCallPage { - } - } - } diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index ecf0abcf9..fe323b7b5 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -46,6 +46,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Prototype/PhoneNumberPrototype.qml view/Prototype/AccountsPrototype.qml view/Prototype/CallPrototype.qml + view/Prototype/ItemPrototype.qml ) list(APPEND _LINPHONEAPP_QML_SINGLETONS diff --git a/Linphone/view/Item/Notification/NotificationReceivedCall.qml b/Linphone/view/Item/Notification/NotificationReceivedCall.qml index 85c04416a..3ca19afd7 100644 --- a/Linphone/view/Item/Notification/NotificationReceivedCall.qml +++ b/Linphone/view/Item/Notification/NotificationReceivedCall.qml @@ -10,7 +10,7 @@ Notification { // --------------------------------------------------------------------------- readonly property var call: notificationData && notificationData.call - property var state: call.state + property var state: call.core.state onStateChanged:{ if(state != LinphoneEnums.CallState.IncomingReceived){ close() @@ -35,7 +35,7 @@ Notification { text: 'Accept' Layout.rightMargin: 20 onClicked: { - notification.call.lAccept() + notification.call.core.lAccept(true) } } Item{ @@ -46,7 +46,7 @@ Notification { text: 'Reject' Layout.rightMargin: 20 onClicked: { - notification.call.lDecline() + notification.call.core.lDecline() } } } diff --git a/Linphone/view/Prototype/CallPrototype.qml b/Linphone/view/Prototype/CallPrototype.qml index 7b75659eb..a17cebd79 100644 --- a/Linphone/view/Prototype/CallPrototype.qml +++ b/Linphone/view/Prototype/CallPrototype.qml @@ -12,25 +12,79 @@ Window{ onWidthChanged: console.log(width) property var callVarObject property var call: callVarObject ? callVarObject.value : null - property var callState: call && call.state - onCallStateChanged: console.log("State:" +callState) + property var callState: call && call.core.state + onCallStateChanged: { + console.log("State:" +callState) + if(callState == LinphoneEnums.CallState.Released) + callVarObject = undefined + } visible: true onCallChanged: console.log('New Call:' +call) + onClosing: { + accountStatus.defaultAccount = accountStatus + accountLayout.accounts = null + gc() + } + Component.onDestruction: gc() ColumnLayout{ anchors.fill: parent RowLayout { + id: accountLayout Layout.fillWidth: true - LoginForm{ + property AccountProxy accounts: AccountProxy{id: accountProxy} + property var haveAccountVar: UtilsCpp.haveAccount() + property var haveAccount: haveAccountVar ? haveAccountVar.value : false + onHaveAccountChanged: { + console.log("HaveAccount: " +haveAccount) + logStack.replace(haveAccount ? accountListComponent : loginComponent) } + Control.StackView{ + id: logStack + Layout.preferredHeight: 250 + Layout.preferredWidth: 250 + //initialItem: loginComponent + } + Component{ + id: accountListComponent + ListView{ + id: accountList + Layout.fillHeight: true + model: AccountProxy{} + delegate:Rectangle{ + color: "#11111111" + height: 20 + width: accountList.width + Text{ + + text: modelData.core.identityAddress + } + } + } + } + Component{ + id: loginComponent + LoginForm{} + } Rectangle{ + id: accountStatus Layout.preferredWidth: 50 Layout.preferredHeight: 50 + property var defaultAccount: accountProxy.defaultAccount + property var state: accountProxy.count > 0 && defaultAccount? defaultAccount.registrationState : LoginPageCpp.registrationState + onStateChanged: console.log("State:"+state) - color: LoginPageCpp.registrationState === LinphoneEnums.RegistrationState.Ok + color: state === LinphoneEnums.RegistrationState.Ok ? 'green' - : LoginPageCpp.registrationState === LinphoneEnums.RegistrationState.Failed || LoginPageCpp.registrationState === LinphoneEnums.RegistrationState.None + : state === LinphoneEnums.RegistrationState.Failed || state === LinphoneEnums.RegistrationState.None ? 'red' : 'orange' + MouseArea{ + anchors.fill: parent + onClicked:{ + logStack.replace(loginComponent) + gc() + } + } } TextInput { id: usernameToCall @@ -41,6 +95,7 @@ Window{ text: 'Call' onClicked: { mainItem.callVarObject = UtilsCpp.startAudioCall(usernameToCall.inputText + "@sip.linphone.org") + proto.component1 = comp } } } @@ -48,10 +103,10 @@ Window{ Rectangle{ Layout.fillWidth: true Layout.preferredHeight: 50 - color: call - ? call.state === LinphoneEnums.CallState.StreamsRunning + color: call + ? call.core.state === LinphoneEnums.CallState.StreamsRunning ? 'green' - : call.state === LinphoneEnums.CallState.Released + : call.core.state === LinphoneEnums.CallState.Released ? 'pink' : 'orange' : 'red' @@ -68,13 +123,26 @@ Window{ } Text{ id: errorMessageText - text: mainItem.call ? mainItem.call.lastErrorMessage : '' + text: mainItem.call ? mainItem.call.core.lastErrorMessage : '' color: 'red' } + ItemPrototype{ + id: proto + Layout.fillHeight: true + Layout.fillWidth: true + } Item{ Layout.fillHeight: true Layout.fillWidth: true } } + Component{ + id: comp + Rectangle{ + width: 100 + height: width + color: 'pink' + } + } } diff --git a/Linphone/view/Prototype/ItemPrototype.qml b/Linphone/view/Prototype/ItemPrototype.qml new file mode 100644 index 000000000..c85d765e0 --- /dev/null +++ b/Linphone/view/Prototype/ItemPrototype.qml @@ -0,0 +1,54 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.0 +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 + +Rectangle{ + id: mainItem + property Component component1: comp1 + property Component component2: comp2 + property bool step: false + color: 'black' + onStepChanged: { + stack.replace(step ? component1 : component2) + } + Timer{ + id: delay + interval: 1000 + onTriggered: mainItem.step = !mainItem.step + repeat: true + running: true + } + Control.StackView{ + id: stack + anchors.fill: parent + /* + anchors.top: parent.top + anchors.right: parent.right + anchors.rightMargin: parent.width/2 + anchors.left: parent.left + anchors.leftMargin: parent.width/2 + anchors.bottom: parent.bottom*/ + initialItem : Rectangle{width: 100 + height: width + color: 'orange'} + } + Component{ + id: comp1 + Rectangle{ + width: 100 + height: width + color: 'red' + } + } + + Component{ + id: comp2 + Rectangle{ + width: 100 + height: width + color: 'green' + } + } +}