Add 01_AccountLogin qt tutorial.

This commit is contained in:
Ghislain MARY
2024-04-30 14:15:11 +02:00
parent 53a8bbba8f
commit 28c66b6398
15 changed files with 769 additions and 0 deletions

1
qt/01_AccountLogin/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build/

View File

@@ -0,0 +1,52 @@
cmake_minimum_required(VERSION 3.22)
project(01_AccountLogin LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt5 REQUIRED COMPONENTS Core Gui Qml Quick)
find_package(LinphoneCxx REQUIRED)
set(CMAKE_AUTOMOC ON)
SET(CMAKE_AUTOUIC ON)
set(SOURCES
"src/main.cpp"
"src/App.cpp"
"src/CoreHandler.cpp"
"src/CoreListener.cpp"
"src/CoreManager.cpp"
)
set(QRC_RESOURCES resources.qrc)
set(QML_SOURCES)
file(STRINGS ${QRC_RESOURCES} QRC_RESOURCES_CONTENT)
foreach(line ${QRC_RESOURCES_CONTENT})
set(result)
string(REGEX REPLACE
"^[ \t]*<[ \t]*file[ \t]*>[ \t]*(.+\\.[a-z]+)[ \t]*<[ \t]*/[ \t]*file[ \t]*>[ \t]*$"
"\\1"
result
"${line}"
)
string(REGEX MATCH "\\.[a-z]+$" is_ui ${result})
if(NOT ${is_ui} STREQUAL "")
list(APPEND QML_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/${result}")
endif()
endforeach()
get_filename_component(SDK_PATH "${CMAKE_PREFIX_PATH}" REALPATH)
find_path(MSPLUGINS_PATH "plugins" PATH_SUFFIXES "lib/mediastreamer" "lib64/mediastreamer" REQUIRED)
set(MSPLUGINS_PATH "${MSPLUGINS_PATH}/plugins")
add_executable(01_AccountLogin ${SOURCES} ${QML_SOURCES} ${QRC_RESOURCES})
target_compile_definitions(01_AccountLogin PRIVATE "SDK_PATH=\"${SDK_PATH}\"" "MSPLUGINS_PATH=\"${MSPLUGINS_PATH}\"")
target_include_directories(01_AccountLogin PRIVATE ${LINPHONECXX_INCLUDE_DIRS})
target_link_libraries(01_AccountLogin PRIVATE Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick ${LINPHONECXX_LIBRARIES})
set_target_properties(01_AccountLogin PROPERTIES AUTORCC ON)
set_target_properties(01_AccountLogin PROPERTIES
WIN32_EXECUTABLE ON
MACOSX_BUNDLE ON
)

View File

@@ -0,0 +1,15 @@
# Account Login tutorial
This project will walk you through the different steps of logging in and out of a SIP account.
If you do not yet have a SIP account, please create one here : https://www.linphone.org/freesip/home
## How to build
In the following instructions, replace **<PATH-TO-SDK>** by the real path where your SDK is located, e.g. *~/projects/linphone-sdk/build-default/linphone-sdk/desktop/*
mkdir build
cd build
cmake .. -DCMAKE_PREFIX_PATH=<PATH-TO-SDK>
cmake --build .

View File

@@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file>ui/MainPage.qml</file>
<file>ui/RegistrationPage.qml</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,47 @@
#include <QDir>
#include <QQmlContext>
#include "App.hpp"
#include "CoreManager.hpp"
using namespace std;
using namespace linphone;
App::App(int &argc, char *argv[]) : QGuiApplication(argc, argv)
{
setOrganizationName("Belledonne Communications");
setOrganizationDomain("belledonne-communications.com");
setApplicationName(QFileInfo(applicationFilePath()).baseName());
}
App::~App()
{
}
void App::init()
{
registerTypes();
mEngine = new QQmlApplicationEngine();
mEngine->load(QUrl("qrc:/ui/MainPage.qml"));
if (mEngine->rootObjects().isEmpty())
qFatal("Unable to open main window.");
// Initialize the CoreManager singleton and add it to the Qml context.
CoreManager::init(this);
auto coreManager = CoreManager::getInstance();
QQmlContext *ctx = mEngine->rootContext();
ctx->setContextProperty("coreManager", coreManager);
}
void App::stop()
{
CoreManager::uninit();
}
void App::registerTypes()
{
qRegisterMetaType<string>();
qRegisterMetaType<RegistrationState>();
qRegisterMetaType<shared_ptr<Account>>();
}

View File

@@ -0,0 +1,25 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <linphone++/linphone.hh>
class App : public QGuiApplication
{
Q_OBJECT
public:
App(int &argc, char *argv[]);
~App();
void init();
void stop();
private:
void registerTypes();
QQmlApplicationEngine *mEngine = nullptr;
};
Q_DECLARE_METATYPE(std::string);
Q_DECLARE_METATYPE(linphone::RegistrationState);
Q_DECLARE_METATYPE(std::shared_ptr<linphone::Account>);

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* 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 <QDebug>
#include "CoreHandler.hpp"
#include "CoreListener.hpp"
#include "CoreManager.hpp"
CoreHandler::CoreHandler()
{
mCoreListener = std::make_shared<CoreListener>();
connectTo(mCoreListener.get());
}
void CoreHandler::setListener(std::shared_ptr<linphone::Core> core)
{
core->addListener(mCoreListener);
}
void CoreHandler::removeListener(std::shared_ptr<linphone::Core> core)
{
core->removeListener(mCoreListener);
}
void CoreHandler::onAccountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &, const std::shared_ptr<linphone::Account> &account, linphone::RegistrationState state, const std::string &message)
{
emit registrationStateChanged(account, state, message);
}
void CoreHandler::onGlobalStateChanged(const std::shared_ptr<linphone::Core> &, linphone::GlobalState state, const std::string &message)
{
switch (state)
{
case linphone::GlobalState::On:
qInfo() << "Core is running " << QString::fromStdString(message);
break;
case linphone::GlobalState::Off:
qInfo() << "Core is stopped " << QString::fromStdString(message);
emit coreStopped();
break;
case linphone::GlobalState::Startup:
qInfo() << "Core is starting" << QString::fromStdString(message);
emit coreStarting();
break;
default:
break;
}
}
void CoreHandler::connectTo(CoreListener *listener)
{
connect(listener, &CoreListener::accountRegistrationStateChanged, this, &CoreHandler::onAccountRegistrationStateChanged);
connect(listener, &CoreListener::globalStateChanged, this, &CoreHandler::onGlobalStateChanged);
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* 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/>.
*/
#pragma once
#include <linphone++/linphone.hh>
#include <QObject>
class CoreListener;
class CoreHandler : public QObject
{
Q_OBJECT
public:
CoreHandler();
void setListener(std::shared_ptr<linphone::Core> core);
void removeListener(std::shared_ptr<linphone::Core> core);
signals:
void coreStarting();
void coreStopped();
void registrationStateChanged(const std::shared_ptr<linphone::Account> &account, linphone::RegistrationState state, const std::string &message);
public slots:
void onAccountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account, linphone::RegistrationState state, const std::string &message);
void onGlobalStateChanged(const std::shared_ptr<linphone::Core> &core, linphone::GlobalState gstate, const std::string &message);
private:
void connectTo(CoreListener *listener);
std::shared_ptr<CoreListener> mCoreListener;
};

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* 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 "CoreListener.hpp"
CoreListener::CoreListener(QObject *parent) : QObject(parent)
{
}
void CoreListener::onAccountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account, linphone::RegistrationState state, const std::string &message)
{
emit accountRegistrationStateChanged(core, account, state, message);
}
void CoreListener::onGlobalStateChanged(const std::shared_ptr<linphone::Core> &core, linphone::GlobalState gstate, const std::string &message)
{
emit globalStateChanged(core, gstate, message);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* 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/>.
*/
#pragma once
#include <linphone++/linphone.hh>
#include <QObject>
class CoreListener : public QObject, public linphone::CoreListener
{
Q_OBJECT
public:
CoreListener(QObject *parent = nullptr);
virtual ~CoreListener() = default;
virtual void onAccountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account, linphone::RegistrationState state, const std::string &message) override;
virtual void onGlobalStateChanged(const std::shared_ptr<linphone::Core> &core, linphone::GlobalState gstate, const std::string &message) override;
signals:
void accountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account, linphone::RegistrationState state, const std::string &message);
void globalStateChanged(const std::shared_ptr<linphone::Core> &core, linphone::GlobalState gstate, const std::string &message);
};

View File

@@ -0,0 +1,235 @@
#include <QDebug>
#include "CoreHandler.hpp"
#include "CoreManager.hpp"
using namespace std;
using namespace linphone;
CoreManager *CoreManager::mInstance = nullptr;
CoreManager::CoreManager(QObject *parent) : QObject(parent)
{
mHandler = QSharedPointer<CoreHandler>::create();
CoreHandler *coreHandler = mHandler.get();
QObject::connect(coreHandler, &CoreHandler::coreStarting, this, &CoreManager::startIterate, Qt::QueuedConnection);
QObject::connect(coreHandler, &CoreHandler::coreStopped, this, &CoreManager::stopIterate, Qt::QueuedConnection);
QObject::connect(coreHandler, &CoreHandler::registrationStateChanged, this, &CoreManager::onRegistrationStateChanged, Qt::QueuedConnection);
// Delay the creation of the core so that the CoreManager instance is
// already set.
QTimer::singleShot(10, [this]()
{ createLinphoneCore(); });
}
CoreManager::~CoreManager()
{
mHandler->removeListener(mCore);
mHandler = nullptr;
mCore = nullptr;
}
void CoreManager::init(QObject *parent)
{
if (mInstance)
return;
mInstance = new CoreManager(parent);
}
void CoreManager::uninit()
{
if (mInstance)
{
mInstance->stopIterate();
auto core = mInstance->mCore;
delete mInstance;
mInstance = nullptr;
core->stop();
}
}
CoreManager *CoreManager::getInstance()
{
return mInstance;
}
void CoreManager::startIterate()
{
// Start a timer to call the core iterate every 20 ms.
mIterateTimer = new QTimer(this);
mIterateTimer->setInterval(20);
QObject::connect(mIterateTimer, &QTimer::timeout, this, &CoreManager::iterate);
qInfo() << QStringLiteral("Start iterate");
mIterateTimer->start();
}
void CoreManager::stopIterate()
{
qInfo() << QStringLiteral("Stop iterate");
mIterateTimer->stop();
mIterateTimer->deleteLater(); // Allow the timer to continue its stuff
mIterateTimer = nullptr;
}
void CoreManager::login(QString identity, QString password, QString transport)
{
if (mLoginButtonEnabled)
{
setProperty("loginButtonEnabled", false);
// To configure a SIP account, we need an Account object and an AuthInfo object
// The first one is how to connect to the proxy server, the second one stores the credentials
// Here we are creating an AuthInfo object from the identity Address and password provided by the user.
shared_ptr<Address> address = Factory::get()->createAddress(identity.toStdString());
// The AuthInfo can be created from the Factory as it's only a data class
// userID is set to null as it's the same as the username in our case
// ha1 is set to null as we are using the clear text password. Upon first register, the hash will be computed automatically.
// The realm will be determined automatically from the first register, as well as the algorithm
shared_ptr<AuthInfo> authInfo = Factory::get()->createAuthInfo(address->getUsername(), "", password.toStdString(), "", "", address->getDomain());
// And we add it to the Core
mCore->addAuthInfo(authInfo);
// Then we create an AccountParams object.
// It contains the account informations needed by the core
shared_ptr<AccountParams> accountParams = mCore->createAccountParams();
// A SIP account is identified by an identity address that we can construct from the username and domain
accountParams->setIdentityAddress(address);
// We also need to configure where the proxy server is located
shared_ptr<Address> serverAddr = Factory::get()->createAddress("sip:" + address->getDomain());
// We use the Address object to easily set the transport protocol
if (transport == "tls")
{
serverAddr->setTransport(TransportType::Tls);
}
else if (transport == "tcp")
{
serverAddr->setTransport(TransportType::Tcp);
}
else
{
serverAddr->setTransport(TransportType::Udp);
}
accountParams->setServerAddress(serverAddr);
// If enableRegister is set to true, when this account will be added to the core it will
// automatically try to connect.
accountParams->enableRegister(true);
// We can now create an Account object from the AccountParams ...
shared_ptr<Account> account = mCore->createAccount(accountParams);
// ... and add it to the core, launching the connection process.
mCore->addAccount(account);
// Also set the newly added account as default
mCore->setDefaultAccount(account);
}
}
void CoreManager::logout()
{
if (mLogoutButtonEnabled)
{
setProperty("logoutButtonEnabled", false);
// Setting enableRegister to false on a connected Account object will
// launch the logout action.
shared_ptr<Account> account = mCore->getDefaultAccount();
if (account)
{
// BUT BE CAREFUL : the Params of an account are read-only
// You MUST Clone it :
shared_ptr<AccountParams> accountParams = account->getParams()->clone();
// Then you can modify the clone :
accountParams->enableRegister(false);
// And finally setting the new Params value triggers the changes, here the logout.
account->setParams(accountParams);
}
}
}
void CoreManager::onRegistrationStateChanged(const shared_ptr<Account> &account, RegistrationState state, const string &message)
{
setProperty("registerText", QString::fromStdString("Your registration state is : " + message));
switch (state)
{
// If the Account was logged out, we clear the Core.
case RegistrationState::Cleared:
case RegistrationState::None:
mCore->clearAllAuthInfo();
mCore->clearAccounts();
logoutGuiChanges();
break;
case RegistrationState::Ok:
loginGuiChanges();
break;
case RegistrationState::Progress:
loginInProgressGuiChanges();
break;
case RegistrationState::Failed:
loginFailedGuiChanges();
break;
default:
break;
}
}
void CoreManager::logoutGuiChanges()
{
setProperty("loginButtonEnabled", true);
setProperty("logoutButtonEnabled", false);
setProperty("loginText", "You are logged out");
}
void CoreManager::loginFailedGuiChanges()
{
setProperty("loginButtonEnabled", true);
setProperty("logoutButtonEnabled", false);
setProperty("loginText", "Login failed, try again");
}
void CoreManager::loginGuiChanges()
{
setProperty("loginButtonEnabled", false);
setProperty("logoutButtonEnabled", true);
setProperty("loginText", QString::fromStdString("You are logged in, with identity " + mCore->getIdentity() + "."));
}
void CoreManager::loginInProgressGuiChanges()
{
setProperty("loginButtonEnabled", false);
setProperty("logoutButtonEnabled", false);
setProperty("loginText", QString::fromStdString("Login in progress, with identity " + mCore->getIdentity() + "."));
}
void CoreManager::createLinphoneCore()
{
// Setting linphone log level to message.
auto loggingService = LoggingService::get();
loggingService->setLogLevel(LogLevel::Message);
// Configure paths.
string assetsPath = string(SDK_PATH) + "/share";
Factory::get()->setTopResourcesDir(assetsPath);
Factory::get()->setDataResourcesDir(assetsPath);
Factory::get()->setSoundResourcesDir(assetsPath + "/sounds/linphone");
Factory::get()->setRingResourcesDir(Factory::get()->getSoundResourcesDir() + "/rings");
Factory::get()->setImageResourcesDir(assetsPath + "/images");
Factory::get()->setMspluginsDir(MSPLUGINS_PATH);
// Create a core from the factory.
mCore = Factory::get()->createCore("", "", nullptr);
mCore->setRootCa(assetsPath + "/linphone/rootca.pem");
// Listen for core events.
mHandler->setListener(mCore);
// Start the core.
mCore->start();
}
void CoreManager::iterate()
{
if (mCore)
mCore->iterate();
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include <QObject>
#include <QSharedPointer>
#include <QTimer>
#include <linphone++/linphone.hh>
class CoreHandler;
class CoreManager : public QObject
{
Q_OBJECT
Q_PROPERTY(QString loginText MEMBER mLoginText NOTIFY loginTextChanged)
Q_PROPERTY(QString registerText MEMBER mRegisterText NOTIFY registerTextChanged)
Q_PROPERTY(bool loginButtonEnabled MEMBER mLoginButtonEnabled NOTIFY loginButtonEnabledChanged)
Q_PROPERTY(bool logoutButtonEnabled MEMBER mLogoutButtonEnabled NOTIFY logoutButtonEnabledChanged)
public:
static void init(QObject *parent);
static void uninit();
static CoreManager *getInstance();
signals:
void loginTextChanged(QString);
void registerTextChanged(QString);
void loginButtonEnabledChanged(bool);
void logoutButtonEnabledChanged(bool);
public slots:
void startIterate();
void stopIterate();
void login(QString identity, QString password, QString transport);
void logout();
void onRegistrationStateChanged(const std::shared_ptr<linphone::Account> &account, linphone::RegistrationState state, const std::string &message);
private:
CoreManager(QObject *parent);
~CoreManager();
void logoutGuiChanges();
void loginFailedGuiChanges();
void loginGuiChanges();
void loginInProgressGuiChanges();
void createLinphoneCore();
void iterate();
std::shared_ptr<linphone::Core> mCore = nullptr;
QSharedPointer<CoreHandler> mHandler;
QTimer *mIterateTimer = nullptr;
QString mLoginText;
QString mRegisterText;
bool mLoginButtonEnabled = true;
bool mLogoutButtonEnabled = false;
static CoreManager *mInstance;
};

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* 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 <QCoreApplication>
#include "App.hpp"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
App app(argc, argv);
app.init();
app.exec();
app.stop();
}

View File

@@ -0,0 +1,19 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ApplicationWindow {
id: window
visible: true
title: "Account Login"
width: 640
height: 480
// Main content
Loader {
id: contentLoader
anchors.fill: parent
source: 'qrc:/ui/RegistrationPage.qml'
}
}

View File

@@ -0,0 +1,95 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.fillHeight: true
GridLayout {
Layout.fillWidth: true
Layout.margins: 20
columnSpacing: 20
columns: 2
Text {
text: "Identity:"
}
TextField {
id: identityTextField
Layout.fillWidth: true
text: "sip:"
}
Text {
text: "Password:"
}
TextField {
id: passwordTextField
echoMode: TextInput.Password
Layout.fillWidth: true
placeholderText: "my password"
}
Text {
text: "Transport:"
}
RowLayout {
Layout.fillWidth: true
RadioButton {
id: tlsButton
text: "TLS"
checked: true
}
RadioButton {
id: tcpButton
text: "TCP"
}
RadioButton {
id: udpButton
text: "UDP"
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
spacing: 20
Button {
id: loginButton
text: "Login"
enabled: coreManager.loginButtonEnabled && identityTextField.text.length != 0 && passwordTextField.text.length != 0
onClicked: {
var transport = "tls"
if (tcpButton.checked) { transport = "tcp" }
else if (udpButton.checked) { transport = "udp" }
coreManager.login(identityTextField.text, passwordTextField.text, transport)
}
}
Button {
id: logoutButton
text: "Logout"
enabled: coreManager.logoutButtonEnabled
onClicked: {
coreManager.logout()
}
}
}
ColumnLayout {
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
Text {
id: loginText
text: coreManager.loginText
}
Text {
id: registrationText
text: coreManager.registerText
}
}
}