diff --git a/qt/02_IncomingCall/.gitignore b/qt/02_IncomingCall/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/qt/02_IncomingCall/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/qt/02_IncomingCall/CMakeLists.txt b/qt/02_IncomingCall/CMakeLists.txt new file mode 100644 index 0000000..29a7a9c --- /dev/null +++ b/qt/02_IncomingCall/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.22) + +project(02_IncomingCall 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(02_IncomingCall ${SOURCES} ${QML_SOURCES} ${QRC_RESOURCES}) + +target_compile_definitions(02_IncomingCall PRIVATE "SDK_PATH=\"${SDK_PATH}\"" "MSPLUGINS_PATH=\"${MSPLUGINS_PATH}\"") +target_include_directories(02_IncomingCall PRIVATE ${LINPHONECXX_INCLUDE_DIRS}) +target_link_libraries(02_IncomingCall PRIVATE Qt5::Core Qt5::Gui Qt5::Qml Qt5::Quick ${LINPHONECXX_LIBRARIES}) + +set_target_properties(02_IncomingCall PROPERTIES AUTORCC ON) +set_target_properties(02_IncomingCall PROPERTIES + WIN32_EXECUTABLE ON + MACOSX_BUNDLE ON +) \ No newline at end of file diff --git a/qt/02_IncomingCall/README.md b/qt/02_IncomingCall/README.md new file mode 100644 index 0000000..37ed764 --- /dev/null +++ b/qt/02_IncomingCall/README.md @@ -0,0 +1,15 @@ +# Incoming call tutorial + +This time we are going to receive our first calls! + +If you don't have SIP friends to test with, you can also install Linphone on your mobile device (Android or iOS) and call yourself with a different account. + + +## How to build + +In the following instructions, replace **** 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= + cmake --build . diff --git a/qt/02_IncomingCall/resources.qrc b/qt/02_IncomingCall/resources.qrc new file mode 100644 index 0000000..dcb9f20 --- /dev/null +++ b/qt/02_IncomingCall/resources.qrc @@ -0,0 +1,7 @@ + + + ui/MainPage.qml + ui/CallPage.qml + ui/RegistrationPage.qml + + diff --git a/qt/02_IncomingCall/src/App.cpp b/qt/02_IncomingCall/src/App.cpp new file mode 100644 index 0000000..518a6b2 --- /dev/null +++ b/qt/02_IncomingCall/src/App.cpp @@ -0,0 +1,49 @@ +#include +#include + +#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(); + qRegisterMetaType(); + qRegisterMetaType>(); + qRegisterMetaType(); + qRegisterMetaType>(); +} diff --git a/qt/02_IncomingCall/src/App.hpp b/qt/02_IncomingCall/src/App.hpp new file mode 100644 index 0000000..d469dbb --- /dev/null +++ b/qt/02_IncomingCall/src/App.hpp @@ -0,0 +1,27 @@ +#include +#include + +#include + +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); +Q_DECLARE_METATYPE(linphone::Call::State); +Q_DECLARE_METATYPE(std::shared_ptr); diff --git a/qt/02_IncomingCall/src/CoreHandler.cpp b/qt/02_IncomingCall/src/CoreHandler.cpp new file mode 100644 index 0000000..a5f2b01 --- /dev/null +++ b/qt/02_IncomingCall/src/CoreHandler.cpp @@ -0,0 +1,75 @@ +/* + * 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 . + */ + +#include + +#include "CoreHandler.hpp" +#include "CoreListener.hpp" +#include "CoreManager.hpp" + +CoreHandler::CoreHandler() +{ + mCoreListener = std::make_shared(); + connectTo(mCoreListener.get()); +} + +void CoreHandler::setListener(std::shared_ptr core) +{ + core->addListener(mCoreListener); +} + +void CoreHandler::removeListener(std::shared_ptr core) +{ + core->removeListener(mCoreListener); +} + +void CoreHandler::onAccountRegistrationStateChanged(const std::shared_ptr &, const std::shared_ptr &account, linphone::RegistrationState state, const std::string &message) +{ + emit registrationStateChanged(account, state, message); +} + +void CoreHandler::onCallStateStateChanged(const std::shared_ptr &, const std::shared_ptr &call, linphone::Call::State state, const std::string &message) +{ + emit callStateChanged(call, state, message); +} + +void CoreHandler::onGlobalStateChanged(const std::shared_ptr &, 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::callStateChanged, this, &CoreHandler::onCallStateStateChanged); + connect(listener, &CoreListener::globalStateChanged, this, &CoreHandler::onGlobalStateChanged); +} \ No newline at end of file diff --git a/qt/02_IncomingCall/src/CoreHandler.hpp b/qt/02_IncomingCall/src/CoreHandler.hpp new file mode 100644 index 0000000..c0aa500 --- /dev/null +++ b/qt/02_IncomingCall/src/CoreHandler.hpp @@ -0,0 +1,49 @@ +/* + * 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 . + */ + +#pragma once + +#include +#include + +class CoreListener; + +class CoreHandler : public QObject +{ + Q_OBJECT + +public: + CoreHandler(); + + void setListener(std::shared_ptr core); + void removeListener(std::shared_ptr core); + +signals: + void coreStarting(); + void coreStopped(); + void callStateChanged(const std::shared_ptr, linphone::Call::State state, const std::string &message); + void registrationStateChanged(const std::shared_ptr &account, linphone::RegistrationState state, const std::string &message); + +public slots: + void onAccountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, const std::string &message); + void onCallStateStateChanged(const std::shared_ptr &core, const std::shared_ptr &call, linphone::Call::State state, const std::string &message); + void onGlobalStateChanged(const std::shared_ptr &core, linphone::GlobalState gstate, const std::string &message); + +private: + void connectTo(CoreListener *listener); + std::shared_ptr mCoreListener; +}; \ No newline at end of file diff --git a/qt/02_IncomingCall/src/CoreListener.cpp b/qt/02_IncomingCall/src/CoreListener.cpp new file mode 100644 index 0000000..2fc73fe --- /dev/null +++ b/qt/02_IncomingCall/src/CoreListener.cpp @@ -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 . + */ + +#include "CoreListener.hpp" + +CoreListener::CoreListener(QObject *parent) : QObject(parent) +{ +} + +void CoreListener::onAccountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, const std::string &message) +{ + emit accountRegistrationStateChanged(core, account, state, message); +} + +void CoreListener::onCallStateChanged(const std::shared_ptr &core, const std::shared_ptr &call, linphone::Call::State state, const std::string &message) +{ + emit callStateChanged(core, call, state, message); +} + +void CoreListener::onGlobalStateChanged(const std::shared_ptr &core, linphone::GlobalState gstate, const std::string &message) +{ + emit globalStateChanged(core, gstate, message); +} diff --git a/qt/02_IncomingCall/src/CoreListener.hpp b/qt/02_IncomingCall/src/CoreListener.hpp new file mode 100644 index 0000000..69501cf --- /dev/null +++ b/qt/02_IncomingCall/src/CoreListener.hpp @@ -0,0 +1,39 @@ +/* + * 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 . + */ + +#pragma once + +#include +#include + +class CoreListener : public QObject, public linphone::CoreListener +{ + Q_OBJECT + +public: + CoreListener(QObject *parent = nullptr); + virtual ~CoreListener() = default; + + virtual void onAccountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, const std::string &message) override; + virtual void onCallStateChanged(const std::shared_ptr &core, const std::shared_ptr &call, linphone::Call::State state, const std::string &message) override; + virtual void onGlobalStateChanged(const std::shared_ptr &core, linphone::GlobalState gstate, const std::string &message) override; + +signals: + void accountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, const std::string &message); + void callStateChanged(const std::shared_ptr &core, const std::shared_ptr &call, linphone::Call::State state, const std::string &message); + void globalStateChanged(const std::shared_ptr &core, linphone::GlobalState gstate, const std::string &message); +}; \ No newline at end of file diff --git a/qt/02_IncomingCall/src/CoreManager.cpp b/qt/02_IncomingCall/src/CoreManager.cpp new file mode 100644 index 0000000..6ccfcd6 --- /dev/null +++ b/qt/02_IncomingCall/src/CoreManager.cpp @@ -0,0 +1,285 @@ +#include + +#include "CoreHandler.hpp" +#include "CoreManager.hpp" + +using namespace std; +using namespace linphone; + +CoreManager *CoreManager::mInstance = nullptr; + +CoreManager::CoreManager(QObject *parent) : QObject(parent) +{ + mPage = QString("qrc:/ui/RegistrationPage.qml"); + mHeaderText = QString("Registration Form"); + mSoundButtonText = QString("Switch off sound"); + mMicrophoneButtonText = QString("Mute"); + mHandler = QSharedPointer::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::callStateChanged, this, &CoreManager::onCallStateChanged, 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) +{ + if (mLoginButtonEnabled) + { + setProperty("loginButtonEnabled", false); + + shared_ptr
address = Factory::get()->createAddress(identity.toStdString()); + shared_ptr authInfo = Factory::get()->createAuthInfo(address->getUsername(), "", password.toStdString(), "", "", address->getDomain()); + mCore->addAuthInfo(authInfo); + + shared_ptr accountParams = mCore->createAccountParams(); + accountParams->setIdentityAddress(address); + string serverAddr = "sip:" + address->getDomain() + ";transport=tls"; + accountParams->setServerAddr(serverAddr); + + accountParams->enableRegister(true); + + shared_ptr account = mCore->createAccount(accountParams); + mCore->addAccount(account); + mCore->setDefaultAccount(account); + } +} + +void CoreManager::logout() +{ + shared_ptr account = mCore->getDefaultAccount(); + if (account) + { + shared_ptr accountParams = account->getParams()->clone(); + accountParams->enableRegister(false); + account->setParams(accountParams); + } +} + +void CoreManager::onCallStateChanged(const std::shared_ptr &call, linphone::Call::State state, const std::string &message) +{ + setProperty("callStateText", QString::fromStdString("Your call state is: " + message)); + switch (state) + { + case Call::State::IncomingReceived: + // When you receive a call the Call::State is incoming receive. By default you can only have one current call, + // so if a call is in progress or one is already ringing the second remote call will be decline with the reason + // "Busy". If you want to implement a multi call app you can increase Core::setMaxCalls. + // Here we store the incoming call reference so we can accept or decline the call on user input. + mIncomingCall = call; + // And we update the GUI to notify the user of the incoming call. + setProperty("incomingCallVisible", true); + setProperty("incomingCallText", QString::fromStdString(mIncomingCall->getRemoteAddress()->asString())); + break; + + case Call::State::StreamsRunning: + // The StreamsRunning state is the default one during a call. + callInProgressGuiUpdates(); + break; + + case Call::State::Error: + case Call::State::End: + case Call::State::Released: + // By default after 30 seconds of ringing without accept or decline a call is + // automatically ended. + mIncomingCall = nullptr; + endingCallGuiUpdates(); + break; + } +} + +void CoreManager::onRegistrationStateChanged(const shared_ptr &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(); + setProperty("loginButtonEnabled", true); + break; + case RegistrationState::Ok: + setProperty("loginButtonEnabled", false); + setProperty("headerText", QString::fromStdString("Hello " + mCore->getDefaultProxyConfig()->findAuthInfo()->getUsername())); + setProperty("page", QString("qrc:/ui/CallPage.qml")); + break; + case RegistrationState::Progress: + setProperty("loginButtonEnabled", false); + break; + case RegistrationState::Failed: + setProperty("loginButtonEnabled", true); + break; + default: + break; + } +} + +void CoreManager::hangup() +{ + // Simply call terminateAllCalls to hang out. + mCore->terminateAllCalls(); +} + +void CoreManager::soundButtonClicked() +{ + if (toggleSpeaker()) + { + setProperty("soundButtonText", QString("Switch on Sound")); + } + else + { + setProperty("soundButtonText", QString("Switch off Sound")); + } +} + +void CoreManager::microphoneButtonClicked() +{ + if (toggleMicrophone()) + { + setProperty("microphoneButtonText", QString("Mute")); + } + else + { + setProperty("microphoneButtonText", QString("Unmute")); + } +} + +void CoreManager::answer() +{ + if (mIncomingCall) + { + // To accept a call only use the accept() method on the call object. + // If we wanted, we could create a CallParams object and answer using this object to make changes to the call configuration. + mIncomingCall->accept(); + mIncomingCall = nullptr; + } +} + +void CoreManager::decline() +{ + if (mIncomingCall) + { + // You have to give a Reason to decline a call. This info is sent to the remote. + mIncomingCall->decline(Reason::Declined); + mIncomingCall = nullptr; + } +} + +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(); +} + +bool CoreManager::toggleSpeaker() +{ + // Calling setSpeakerMuted(true) on a Call object disables the sound output of this call. + bool newValue = !mCore->getCurrentCall()->getSpeakerMuted(); + mCore->getCurrentCall()->setSpeakerMuted(newValue); + return newValue; +} + +bool CoreManager::toggleMicrophone() +{ + // The following toggles the microphone, disabling completely / enabling the sound capture from the device microphone + bool newValue = !mCore->micEnabled(); + mCore->enableMic(newValue); + return newValue; +} + +void CoreManager::callInProgressGuiUpdates() +{ + setProperty("incomingCallVisible", false); + setProperty("inCallButtonsEnabled", true); +} + +void CoreManager::endingCallGuiUpdates() +{ + setProperty("incomingCallVisible", false); + setProperty("inCallButtonsEnabled", false); + setProperty("soundButtonText", "Switch off Sound"); + setProperty("microphoneButtonText", "Mute"); +} \ No newline at end of file diff --git a/qt/02_IncomingCall/src/CoreManager.hpp b/qt/02_IncomingCall/src/CoreManager.hpp new file mode 100644 index 0000000..231fdb6 --- /dev/null +++ b/qt/02_IncomingCall/src/CoreManager.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +#include + +class CoreHandler; + +class CoreManager : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString page MEMBER mPage NOTIFY pageChanged); + Q_PROPERTY(QString headerText MEMBER mHeaderText NOTIFY headerTextChanged) + Q_PROPERTY(QString registerText MEMBER mRegisterText NOTIFY registerTextChanged) + Q_PROPERTY(bool loginButtonEnabled MEMBER mLoginButtonEnabled NOTIFY loginButtonEnabledChanged) + Q_PROPERTY(QString callStateText MEMBER mCallStateText NOTIFY callStateTextChanged) + Q_PROPERTY(bool incomingCallVisible MEMBER mIncomingCallVisible NOTIFY incomingCallVisibleChanged) + Q_PROPERTY(QString incomingCallText MEMBER mIncomingCallText NOTIFY incomingCallTextChanged) + Q_PROPERTY(bool inCallButtonsEnabled MEMBER mInCallButtonsEnabled NOTIFY inCallButtonsEnabledChanged) + Q_PROPERTY(QString soundButtonText MEMBER mSoundButtonText NOTIFY soundButtonTextChanged) + Q_PROPERTY(QString microphoneButtonText MEMBER mMicrophoneButtonText NOTIFY microphoneButtonTextChanged) + +public: + static void init(QObject *parent); + static void uninit(); + static CoreManager *getInstance(); + +signals: + void pageChanged(QString); + void headerTextChanged(QString); + void registerTextChanged(QString); + void loginButtonEnabledChanged(bool); + void callStateTextChanged(QString); + void incomingCallVisibleChanged(bool); + void incomingCallTextChanged(QString); + void inCallButtonsEnabledChanged(bool); + void soundButtonTextChanged(QString); + void microphoneButtonTextChanged(QString); + +public slots: + void startIterate(); + void stopIterate(); + void login(QString identity, QString password); + void logout(); + void onCallStateChanged(const std::shared_ptr &call, linphone::Call::State state, const std::string &message); + void onRegistrationStateChanged(const std::shared_ptr &account, linphone::RegistrationState state, const std::string &message); + void hangup(); + void soundButtonClicked(); + void microphoneButtonClicked(); + void answer(); + void decline(); + +private: + CoreManager(QObject *parent); + ~CoreManager(); + + void createLinphoneCore(); + void iterate(); + bool toggleSpeaker(); + bool toggleMicrophone(); + void callInProgressGuiUpdates(); + void endingCallGuiUpdates(); + + std::shared_ptr mCore = nullptr; + std::shared_ptr mIncomingCall = nullptr; + QSharedPointer mHandler; + QTimer *mIterateTimer = nullptr; + + QString mPage; + QString mHeaderText; + QString mRegisterText; + bool mLoginButtonEnabled = true; + QString mCallStateText; + bool mIncomingCallVisible = false; + QString mIncomingCallText; + bool mInCallButtonsEnabled = false; + QString mSoundButtonText; + QString mMicrophoneButtonText; + + static CoreManager *mInstance; +}; \ No newline at end of file diff --git a/qt/02_IncomingCall/src/main.cpp b/qt/02_IncomingCall/src/main.cpp new file mode 100644 index 0000000..521f7f7 --- /dev/null +++ b/qt/02_IncomingCall/src/main.cpp @@ -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 . + */ + +#include + +#include "App.hpp" + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + App app(argc, argv); + app.init(); + app.exec(); + app.stop(); +} diff --git a/qt/02_IncomingCall/ui/CallPage.qml b/qt/02_IncomingCall/ui/CallPage.qml new file mode 100644 index 0000000..0ce63d2 --- /dev/null +++ b/qt/02_IncomingCall/ui/CallPage.qml @@ -0,0 +1,77 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +GridLayout { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.fillHeight: true + columns: 1 + + ColumnLayout { + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter + + Text { + text: coreManager.callStateText + } + } + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + spacing: 20 + + Button { + text: "Hang up" + enabled: coreManager.inCallButtonsEnabled + onClicked: { + coreManager.hangup() + } + } + + Button { + text: coreManager.soundButtonText + enabled: coreManager.inCallButtonsEnabled + onClicked: { + coreManager.soundButtonClicked() + } + } + + Button { + text: coreManager.microphoneButtonText + enabled: coreManager.inCallButtonsEnabled + onClicked: { + coreManager.microphoneButtonClicked() + } + } + } + + GridLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + Layout.margins: 20 + columnSpacing: 20 + columns: 2 + visible: coreManager.incomingCallVisible + + Text { + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 2 + text: "You have a call from: " + coreManager.incomingCallText + } + + Button { + text: "Answer" + onClicked: { + coreManager.answer() + } + } + Button { + text: "Decline" + onClicked: { + coreManager.decline() + } + } + } +} \ No newline at end of file diff --git a/qt/02_IncomingCall/ui/MainPage.qml b/qt/02_IncomingCall/ui/MainPage.qml new file mode 100644 index 0000000..6c287a4 --- /dev/null +++ b/qt/02_IncomingCall/ui/MainPage.qml @@ -0,0 +1,35 @@ +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 + + header: Rectangle { + color: "lightgray"; + height: 40; + width: window.width + + Text { + text: coreManager.headerText + font.bold: true + font.capitalization: Font.AllUppercase + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + height: parent.height + } + } + + // Main content + Loader { + id: contentLoader + anchors.fill: parent + source: coreManager.page + } +} diff --git a/qt/02_IncomingCall/ui/RegistrationPage.qml b/qt/02_IncomingCall/ui/RegistrationPage.qml new file mode 100644 index 0000000..db94b39 --- /dev/null +++ b/qt/02_IncomingCall/ui/RegistrationPage.qml @@ -0,0 +1,57 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +ColumnLayout { + //Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + 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" + } + } + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + + Button { + text: "Login" + enabled: coreManager.loginButtonEnabled && identityTextField.text.length !== 0 && passwordTextField.text.length !== 0 + onClicked: { + coreManager.login(identityTextField.text, passwordTextField.text) + } + } + } + + ColumnLayout { + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter + + Text { + text: coreManager.registerText + } + } +}