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).
This commit is contained in:
Julien Wadel 2023-11-21 18:02:26 +01:00
parent 3fad8ee49e
commit cd82964b23
44 changed files with 1202 additions and 316 deletions

View file

@ -30,8 +30,10 @@
#include <QQmlFileSelector>
#include <QTimer>
#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<PhoneNumber>(Constants::MainQmlUri, 1, 0, "PhoneNumber", QLatin1String("Uncreatable"));
qmlRegisterType<AccountProxy>(Constants::MainQmlUri, 1, 0, "AccountProxy");
qmlRegisterUncreatableType<Account>(Constants::MainQmlUri, 1, 0, "Account", QLatin1String("Uncreatable"));
qmlRegisterUncreatableType<Call>(Constants::MainQmlUri, 1, 0, "Call", QLatin1String("Uncreatable"));
qmlRegisterType<AccountGui>(Constants::MainQmlUri, 1, 0, "AccountGui");
qmlRegisterUncreatableType<AccountCore>(Constants::MainQmlUri, 1, 0, "AccountCore", QLatin1String("Uncreatable"));
qmlRegisterType<CallGui>(Constants::MainQmlUri, 1, 0, "CallGui");
qmlRegisterUncreatableType<CallCore>(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;
}

View file

@ -74,6 +74,7 @@ public:
void onLoggerInitialized();
QQmlApplicationEngine *mEngine = nullptr;
bool notify(QObject *receiver, QEvent *event);
private:
void createCommandParser();

View file

@ -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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "Account.hpp"
#include "tool/Utils.hpp"
DEFINE_ABSTRACT_OBJECT(Account)
Account::Account(const std::shared_ptr<linphone::Account> &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<AccountModel>(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<linphone::Account> &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();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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> AccountCore::create(const std::shared_ptr<linphone::Account> &account) {
auto model = QSharedPointer<AccountCore>(new AccountCore(account), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread());
model->setSelf(model);
return model;
}
AccountCore::AccountCore(const std::shared_ptr<linphone::Account> &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<AccountModel>(account); // OK
mAccountModel->setSelf(mAccountModel);
}
AccountCore::~AccountCore() {
mustBeInMainThread("~" + getClassName());
emit mAccountModel->removeListener();
}
void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
mAccountModelConnection = QSharedPointer<SafeConnection>(
new SafeConnection(me.objectCast<QObject>(), std::dynamic_pointer_cast<QObject>(mAccountModel)));
mAccountModelConnection->makeConnect(mAccountModel.get(), &AccountModel::registrationStateChanged,
[this](const std::shared_ptr<linphone::Account> &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<linphone::Account> &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();
}

View file

@ -18,54 +18,63 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <QObject>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
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<AccountCore> create(const std::shared_ptr<linphone::Account> &account);
// Should be call from model Thread. Will be automatically in App thread after initialization
Account(const std::shared_ptr<linphone::Account> &account);
~Account();
AccountCore(const std::shared_ptr<linphone::Account> &account);
~AccountCore();
void setSelf(QSharedPointer<AccountCore> 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<linphone::Account> &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<AccountModel> mAccountModel;
QSharedPointer<SafeConnection> mAccountModelConnection;
DECLARE_ABSTRACT_OBJECT
};

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "AccountGui.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(AccountGui)
AccountGui::AccountGui(QSharedPointer<AccountCore> 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();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACCOUNT_GUI_H_
#define ACCOUNT_GUI_H_
#include "AccountCore.hpp"
#include <QObject>
#include <QSharedPointer>
class AccountGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(AccountCore *core READ getCore CONSTANT)
public:
AccountGui(QSharedPointer<AccountCore> core);
~AccountGui();
AccountCore *getCore() const;
QSharedPointer<AccountCore> mCore;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -19,7 +19,8 @@
*/
#include "AccountList.hpp"
#include "Account.hpp"
#include "AccountCore.hpp"
#include "AccountGui.hpp"
#include "core/App.hpp"
#include <QSharedPointer>
#include <linphone++/linphone.hh>
@ -28,26 +29,80 @@
DEFINE_ABSTRACT_OBJECT(AccountList)
QSharedPointer<AccountList> AccountList::create() {
auto model = QSharedPointer<AccountList>(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<QSharedPointer<Account>> accounts;
// Model thread.
mustBeInLinphoneThread(getClassName());
auto linphoneAccounts = CoreModel::getInstance()->getCore()->getAccountList();
for (auto it : linphoneAccounts) {
auto model = QSharedPointer<Account>(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<AccountList> me) {
mModelConnection = QSharedPointer<SafeConnection>(
new SafeConnection(me.objectCast<QObject>(), std::dynamic_pointer_cast<QObject>(CoreModel::getInstance())),
&QObject::deleteLater);
mModelConnection->makeConnect(this, &AccountList::lUpdate, [this]() {
mModelConnection->invokeToModel([this]() {
QList<QSharedPointer<AccountCore>> 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<AccountCore>();
if (account->getIsDefaultAccount()) return new AccountGui(account);
}
return nullptr;
}
/*
void AccountList::update() {
App::postModelAsync([=]() {
QList<QSharedPointer<AccountCore>> 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<AccountCore>()));
return QVariant();
}

View file

@ -23,15 +23,28 @@
#include "../proxy/ListProxy.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QLocale>
class AccountGui;
// =============================================================================
class AccountList : public ListProxy, public AbstractObject {
Q_OBJECT
public:
static QSharedPointer<AccountList> create();
AccountList(QObject *parent = Q_NULLPTR);
~AccountList();
void setSelf(QSharedPointer<AccountList> me);
AccountGui *getDefaultAccount() const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void lUpdate();
private:
QSharedPointer<SafeConnection> mModelConnection;
DECLARE_ABSTRACT_OBJECT
};

View file

@ -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<AccountList *>(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<Account *>();
show = account->getIdentityAddress().contains(search);
auto account = model.value<AccountGui *>();
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<Account *>()->getIdentityAddress() < r.value<Account *>()->getIdentityAddress();
return l.value<AccountGui *>()->getCore()->getIdentityAddress() <
r.value<AccountGui *>()->getCore()->getIdentityAddress();
}

View file

@ -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<AccountList> mList;
};
#endif

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "Call.hpp"
#include "core/App.hpp"
#include "tool/Utils.hpp"
DEFINE_ABSTRACT_OBJECT(Call)
Call::Call(const std::shared_ptr<linphone::Call> &call) : QObject(nullptr) {
// Should be call from model Thread
mustBeInLinphoneThread(getClassName());
mCallModel = Utils::makeQObject_ptr<CallModel>(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();
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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> CallCore::create(const std::shared_ptr<linphone::Call> &call) {
auto sharedPointer = QSharedPointer<CallCore>(new CallCore(call), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
CallCore::CallCore(const std::shared_ptr<linphone::Call> &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<CallModel>(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<CallCore> me) {
mAccountModelConnection = QSharedPointer<SafeConnection>(
new SafeConnection(me.objectCast<QObject>(), std::dynamic_pointer_cast<QObject>(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();
}
}

View file

@ -18,8 +18,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <QSharedPointer>
#include <linphone++/linphone.hh>
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<linphone::Call> &call);
~Call();
static QSharedPointer<CallCore> create(const std::shared_ptr<linphone::Call> &call);
CallCore(const std::shared_ptr<linphone::Call> &call);
~CallCore();
void setSelf(QSharedPointer<CallCore> 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<CallModel> mCallModel;
LinphoneEnums::CallStatus mStatus;
LinphoneEnums::CallState mState;
QString mLastErrorMessage;
int mDuration = 0;
bool mMicrophoneMuted;
QSharedPointer<SafeConnection> mAccountModelConnection;
DECLARE_ABSTRACT_OBJECT
};
Q_DECLARE_METATYPE(Call *)
Q_DECLARE_METATYPE(CallCore *)
#endif

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "CallGui.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(CallGui)
CallGui::CallGui(QSharedPointer<CallCore> 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();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef CALL_GUI_H_
#define CALL_GUI_H_
#include "CallCore.hpp"
#include <QObject>
#include <QSharedPointer>
class CallGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(CallCore *core READ getCore CONSTANT)
public:
CallGui(QSharedPointer<CallCore> core);
~CallGui();
CallCore *getCore() const;
QSharedPointer<CallCore> mCore;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -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<linphone::Call> &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)); });
});
}

View file

@ -23,10 +23,10 @@
#include <memory>
#include "core/call/Call.hpp"
#include "tool/AbstractObject.hpp"
#include <QHash>
#include <QObject>
#include <linphone++/linphone.hh>
// =============================================================================
class QMutex;

View file

@ -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

View file

@ -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;
}

View file

@ -22,11 +22,16 @@
#include <QDebug>
#include "model/core/CoreModel.hpp"
#include "tool/Utils.hpp"
DEFINE_ABSTRACT_OBJECT(AccountModel)
AccountModel::AccountModel(const std::shared_ptr<linphone::Account> &account, QObject *parent)
: ::Listener<linphone::Account, linphone::AccountListener>(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<linphone::Ac
emit registrationStateChanged(account, state, message);
}
void AccountModel::setPictureUri(std::string uri) {
void AccountModel::setPictureUri(QString uri) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto account = std::dynamic_pointer_cast<linphone::Account>(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();
}

View file

@ -38,15 +38,18 @@ public:
virtual void onRegistrationStateChanged(const std::shared_ptr<linphone::Account> &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<linphone::Account> &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

View file

@ -29,6 +29,10 @@ DEFINE_ABSTRACT_OBJECT(CallModel)
CallModel::CallModel(const std::shared_ptr<linphone::Call> &call, QObject *parent)
: ::Listener<linphone::Call, linphone::CallListener>(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<linphone::Call> &call, int dtmf) {
emit dtmfReceived(call, dtmf);
}

View file

@ -25,6 +25,7 @@
#include "tool/AbstractObject.hpp"
#include <QObject>
#include <QTimer>
#include <linphone++/linphone.hh>
class CallModel : public ::Listener<linphone::Call, linphone::CallListener>,
@ -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
//--------------------------------------------------------------------------------

View file

@ -139,6 +139,10 @@ void CoreModel::setPathAfterStart() {
//---------------------------------------------------------------------------------------------------------------------------
void CoreModel::onAccountAdded() {
emit accountAdded();
}
void CoreModel::onAccountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account,
linphone::RegistrationState state,

View file

@ -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<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account,
linphone::RegistrationState state,
@ -152,6 +154,7 @@ private:
const std::string &url) override;
signals:
void accountAdded();
void accountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account,
linphone::RegistrationState state,

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "SafeObject.hpp"
#include <QDebug>
#include <QTest>
#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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef SAFE_OBJECT_H_
#define SAFE_OBJECT_H_
#include "tool/AbstractObject.hpp"
#include <QObject>
#include <QVariant>
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

View file

@ -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<SafeObject>::create(defaultValue);
mModelObject = QSharedPointer<SafeObject>::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<SafeConnection>(
new SafeConnection(mCoreObject.objectCast<QObject>(), mModelObject.objectCast<QObject>()),
&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();
}

View file

@ -24,35 +24,41 @@
#include "tool/AbstractObject.hpp"
#include <QObject>
#include <QSharedPointer>
#include <QVariant>
// 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 <typename Func, typename... Args>
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 <typename SenderClass>
void makeUpdate(const typename QtPrivate::FunctionPointer<SenderClass>::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<SafeObject> mCoreObject, mModelObject;
QSharedPointer<SafeConnection> mConnection;
signals:
void valueChanged(QVariant value);
void valueUpdated(QVariant value);
private:
QVariant mValue;
bool mThreadLocation = true; // true=Core, false=Model
DECLARE_ABSTRACT_OBJECT
};

View file

@ -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<QString, QString> &headers) {
QSharedPointer<CallCore> ToolModel::startAudioCall(const QString &sipAddress,
const QString &prepareTransfertAddress,
const QHash<QString, QString> &headers) {
bool waitRegistrationForCall = true; // getSettingsModel()->getWaitRegistrationForCall()
std::shared_ptr<linphone::Core> 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

View file

@ -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 <QHash>
@ -39,9 +39,9 @@ public:
static QString getDisplayName(const std::shared_ptr<const linphone::Address> &address);
static QString getDisplayName(QString address);
static Call *startAudioCall(const QString &sipAddress,
const QString &prepareTransfertAddress = "",
const QHash<QString, QString> &headers = {});
static QSharedPointer<CallCore> startAudioCall(const QString &sipAddress,
const QString &prepareTransfertAddress = "",
const QHash<QString, QString> &headers = {});
private:
DECLARE_ABSTRACT_OBJECT

View file

@ -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
)

View file

@ -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<QString, QString> &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;
}

View file

@ -52,6 +52,7 @@ public:
Q_INVOKABLE static VariantObject *startAudioCall(const QString &sipAddress,
const QString &prepareTransfertAddress = "",
const QHash<QString, QString> &headers = {});
Q_INVOKABLE static VariantObject *haveAccount();
static inline QString coreStringToAppString(const std::string &str) {
if (Constants::LinphoneLocaleEncoding == QString("UTF-8")) return QString::fromStdString(str);

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "SafeConnection.hpp"
SafeConnection::SafeConnection(SafeSharedPointer<QObject> core, SafeSharedPointer<QObject> model)
: mCore(core), mModel(model) {
}
SafeConnection::~SafeConnection() {
if (mCore.mCountRef != 0 || mModel.mCountRef != 0)
qCritical() << "[SafeConnection] Destruction while still having references";
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef SAFE_CONNECTION_H_
#define SAFE_CONNECTION_H_
#include "SafeSharedPointer.hpp"
#include <QMutex>
#include <QObject>
// 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>
* ObjectCore have std::shared_ptr<ObjectModel> and SafeConnection.
*
* Need:
* - static QSharedPointer<ObjectCore> 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<ObjectCore> me); => instantiate SafeConnection with :
* me.objectCast<QObject>(), std::dynamic_pointer_cast<QObject>(<Model_stored_in_ObjectCore>));
*
* 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<QObject> a, SafeSharedPointer<QObject> b);
~SafeConnection();
SafeSharedPointer<QObject> mCore, mModel;
QMutex mLocker;
template <typename Func1, typename Func2>
static inline QMetaObject::Connection makeConnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender,
Func1 signal,
Func2 slot,
Qt::ConnectionType type = Qt::AutoConnection) {
return connect(sender, signal, sender, slot, type);
}
template <typename Func, typename... Args>
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 <typename Func, typename... Args>
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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef SAFE_SHARED_POINTER_H_
#define SAFE_SHARED_POINTER_H_
#include <QDebug>
#include <QSharedPointer>
// Store a Qt/Std shared pointer
template <typename A>
class SafeSharedPointer {
public:
QSharedPointer<A> mQData;
QWeakPointer<A> mQDataWeak;
std::shared_ptr<A> mStdData;
std::weak_ptr<A> mStdDataWeak;
int mCountRef = 0;
SafeSharedPointer(QSharedPointer<A> p) : mQDataWeak(p) {
}
SafeSharedPointer(std::shared_ptr<A> 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

View file

@ -84,12 +84,5 @@ Window {
MainLayout {
}
}
Component {
id: ongoingCallPage
OngoingCallPage {
}
}
}

View file

@ -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

View file

@ -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()
}
}
}

View file

@ -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'
}
}
}

View file

@ -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'
}
}
}