Commit c1b38dc6 authored by Ronan Abhamon's avatar Ronan Abhamon

feat(app): build linphone core in a specific thread

parent 5944653b
......@@ -62,7 +62,7 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
# Define packages, libs, sources, headers, resources and languages.
# ------------------------------------------------------------------------------
set(QT5_PACKAGES Core Gui Quick Widgets QuickControls2 Svg LinguistTools Network)
set(QT5_PACKAGES Core Gui Quick Widgets QuickControls2 Svg LinguistTools Network Concurrent)
find_package(BcToolbox REQUIRED)
find_package(Belcard REQUIRED)
......
......@@ -95,6 +95,10 @@ App::~App () {
// -----------------------------------------------------------------------------
void App::initContentApp () {
// Init core.
CoreManager::init(this, m_parser.value("config"));
qInfo() << "Activated selectors:" << QQmlFileSelector::get(&m_engine)->selector()->allSelectors();
// Avoid double free.
m_engine.setObjectOwnership(this, QQmlEngine::CppOwnership);
......@@ -117,66 +121,44 @@ void App::initContentApp () {
// Don't quit if last window is closed!!!
setQuitOnLastWindowClosed(false);
// Init core.
CoreManager::init(nullptr, m_parser.value("config"));
qInfo() << "Core manager initialized.";
qInfo() << "Activated selectors:" << QQmlFileSelector::get(&m_engine)->selector()->allSelectors();
// Try to use preferred locale.
{
QString locale = getConfigLocale();
if (!locale.isEmpty()) {
DefaultTranslator *translator = new DefaultTranslator(this);
if (installLocale(*this, *translator, QLocale(locale))) {
// Use config.
m_translator->deleteLater();
m_translator = translator;
m_locale = locale;
qInfo() << QStringLiteral("Use preferred locale: %1").arg(locale);
} else {
// Reset config.
setConfigLocale("");
translator->deleteLater();
}
}
}
// Register types.
registerTypes();
// Enable notifications.
m_notifier = new Notifier(this);
{
CoreManager *core = CoreManager::getInstance();
core->enableHandlers();
core->setParent(this);
}
// Load main view.
qInfo() << "Loading main view...";
m_engine.load(QUrl(QML_VIEW_MAIN_WINDOW));
if (m_engine.rootObjects().isEmpty())
qFatal("Unable to open main window.");
#ifndef __APPLE__
// Enable TrayIconSystem.
if (!QSystemTrayIcon::isSystemTrayAvailable())
qWarning("System tray not found on this system.");
else
setTrayIcon();
if (!m_parser.isSet("iconified"))
getMainWindow()->showNormal();
#else
getMainWindow()->showNormal();
#endif // ifndef __APPLE__
CoreManager *core = CoreManager::getInstance();
if (m_parser.isSet("selftest"))
QTimer::singleShot(300, this, &App::quit);
QObject::connect(core, &CoreManager::linphoneCoreCreated, this, &App::quit);
else
QObject::connect(
core, &CoreManager::linphoneCoreCreated, this, [core, this]() {
tryToUsePreferredLocale();
qInfo() << QStringLiteral("Linphone core created.");
core->enableHandlers();
#ifndef __APPLE__
// Enable TrayIconSystem.
if (!QSystemTrayIcon::isSystemTrayAvailable())
qWarning("System tray not found on this system.");
else
setTrayIcon();
if (!m_parser.isSet("iconified"))
getMainWindow()->showNormal();
#else
getMainWindow()->showNormal();
#endif // ifndef __APPLE__
}
);
QObject::connect(
this, &App::receivedMessage, this, [this](int, QByteArray message) {
......@@ -213,6 +195,29 @@ void App::parseArgs () {
// -----------------------------------------------------------------------------
void App::tryToUsePreferredLocale () {
QString locale = getConfigLocale();
if (!locale.isEmpty()) {
DefaultTranslator *translator = new DefaultTranslator(this);
if (installLocale(*this, *translator, QLocale(locale))) {
// Use config.
m_translator->deleteLater();
m_translator = translator;
m_locale = locale;
qInfo() << QStringLiteral("Use preferred locale: %1").arg(locale);
} else {
// Reset config.
setConfigLocale("");
translator->deleteLater();
}
}
}
// -----------------------------------------------------------------------------
inline QQuickWindow *createSubWindow (App *app, const char *path) {
QQmlEngine *engine = app->getEngine();
......
......@@ -24,13 +24,12 @@
#define APP_H_
#include "../components/notifier/Notifier.hpp"
#include "../externals/single-application/SingleApplication.hpp"
#include <QCommandLineParser>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include "../externals/single-application/SingleApplication.hpp"
// =============================================================================
class DefaultTranslator;
......@@ -49,6 +48,8 @@ public:
void initContentApp ();
void parseArgs ();
void tryToUsePreferredLocale ();
QQmlEngine *getEngine () {
return &m_engine;
}
......
......@@ -28,6 +28,7 @@
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QtConcurrent>
#include <QTimer>
using namespace std;
......@@ -37,35 +38,34 @@ using namespace std;
CoreManager *CoreManager::m_instance = nullptr;
CoreManager::CoreManager (QObject *parent, const QString &config_path) : QObject(parent), m_handlers(make_shared<CoreHandlers>()) {
// TODO: activate migration when ready to switch to this new version
// Paths::migrate();
setResourcesPaths();
m_promise_build = QtConcurrent::run(this, &CoreManager::createLinphoneCore, config_path);
m_core = linphone::Factory::get()->createCore(m_handlers, Paths::getConfigFilepath(config_path), "");
QObject::connect(
&m_promise_watcher, &QFutureWatcher<void>::finished, this, []() {
m_instance->m_calls_list_model = new CallsListModel(m_instance);
m_instance->m_contacts_list_model = new ContactsListModel(m_instance);
m_instance->m_sip_addresses_model = new SipAddressesModel(m_instance);
m_instance->m_settings_model = new SettingsModel(m_instance);
m_core->setVideoDisplayFilter("MSOGL");
m_core->usePreviewWindow(true);
emit m_instance->linphoneCoreCreated();
}
);
setDatabasesPaths();
setOtherPaths();
m_promise_watcher.setFuture(m_promise_build);
}
void CoreManager::enableHandlers () {
m_cbs_timer->start();
}
// -----------------------------------------------------------------------------
void CoreManager::init (QObject *parent, const QString &config_path) {
if (m_instance)
return;
m_instance = new CoreManager(parent, config_path);
m_instance->m_calls_list_model = new CallsListModel(m_instance);
m_instance->m_contacts_list_model = new ContactsListModel(m_instance);
m_instance->m_sip_addresses_model = new SipAddressesModel(m_instance);
m_instance->m_settings_model = new SettingsModel(m_instance);
QTimer *timer = m_instance->m_cbs_timer = new QTimer(m_instance);
timer->setInterval(20);
......@@ -117,3 +117,22 @@ void CoreManager::setResourcesPaths () {
factory->setTopResourcesDir(::Utils::qStringToLinphoneString(datadir.absolutePath()));
}
}
// -----------------------------------------------------------------------------
void CoreManager::createLinphoneCore (const QString &config_path) {
qInfo() << QStringLiteral("Launch async linphone core creation.");
// TODO: activate migration when ready to switch to this new version
// Paths::migrate();
setResourcesPaths();
m_core = linphone::Factory::get()->createCore(m_handlers, Paths::getConfigFilepath(config_path), "");
m_core->setVideoDisplayFilter("MSOGL");
m_core->usePreviewWindow(true);
setDatabasesPaths();
setOtherPaths();
}
......@@ -30,6 +30,8 @@
#include "CoreHandlers.hpp"
#include <QFuture>
#include <QFutureWatcher>
#include <QMutex>
// =============================================================================
......@@ -39,6 +41,8 @@ class QTimer;
class CoreManager : public QObject {
Q_OBJECT;
Q_PROPERTY(bool linphoneCoreCreated READ getLinphoneCoreCreated NOTIFY linphoneCoreCreated);
public:
~CoreManager () = default;
......@@ -102,6 +106,9 @@ public:
Q_INVOKABLE void forceRefreshRegisters ();
signals:
void linphoneCoreCreated ();
private:
CoreManager (QObject *parent, const QString &config_path);
......@@ -109,6 +116,12 @@ private:
void setOtherPaths ();
void setResourcesPaths ();
void createLinphoneCore (const QString &config_path);
bool getLinphoneCoreCreated () {
return m_promise_build.isFinished();
}
std::shared_ptr<linphone::Core> m_core;
std::shared_ptr<CoreHandlers> m_handlers;
......@@ -119,6 +132,9 @@ private:
QTimer *m_cbs_timer;
QFuture<void> m_promise_build;
QFutureWatcher<void> m_promise_watcher;
QMutex m_mutex_video_render;
static CoreManager *m_instance;
......
......@@ -20,10 +20,7 @@
* Author: Ronan Abhamon
*/
#include <iostream>
#include "app/App.hpp"
#include "app/Logger.hpp"
using namespace std;
......@@ -33,6 +30,10 @@ int main (int argc, char *argv[]) {
// Disable QML cache. Avoid malformed cache.
qputenv("QML_DISABLE_DISK_CACHE", "true");
// ---------------------------------------------------------------------------
// OpenGL properties.
// ---------------------------------------------------------------------------
// Options to get a nice video render.
#ifdef _WIN32
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true);
......@@ -58,6 +59,10 @@ int main (int argc, char *argv[]) {
QSurfaceFormat::setDefaultFormat(format);
}
// ---------------------------------------------------------------------------
// App creation.
// ---------------------------------------------------------------------------
App app(argc, argv);
app.parseArgs();
......@@ -69,5 +74,6 @@ int main (int argc, char *argv[]) {
app.initContentApp();
// Run!
qInfo() << "Running app...";
return app.exec();
}
......@@ -8,6 +8,16 @@
// =============================================================================
function handleActiveFocusItemChanged (activeFocusItem) {
var smartSearchBar = window._smartSearchBar
if (activeFocusItem == null && smartSearchBar) {
smartSearchBar.hideMenu()
}
}
// -----------------------------------------------------------------------------
function lockView (info) {
window._lockedInfo = info
}
......@@ -24,10 +34,12 @@ function setView (view, props) {
window.setVisible(true)
}
collapse.setCollapsed(true)
var item = mainLoader.item
item.collapse.setCollapsed(true)
updateSelectedEntry(view, props)
window._currentView = view
contentLoader.setSource(view + '.qml', props || {})
item.contentLoader.setSource(view + '.qml', props || {})
}
var lockedInfo = window._lockedInfo
......@@ -57,6 +69,11 @@ function manageAccounts () {
// -----------------------------------------------------------------------------
function updateSelectedEntry (view, props) {
var item = mainLoader.item
var menu = item.menu
var timeline = item.timeline
if (view === 'Home' || view === 'Contacts') {
menu.setSelectedEntry(view === 'Home' ? 0 : 1)
timeline.resetSelectedEntry()
......
......@@ -39,7 +39,6 @@ ApplicationWindow {
maximumHeight: MainWindowStyle.toolBar.height
minimumHeight: MainWindowStyle.toolBar.height
minimumWidth: MainWindowStyle.minimumWidth
width: MainWindowStyle.width
title: MainWindowStyle.title
......@@ -49,12 +48,12 @@ ApplicationWindow {
// ---------------------------------------------------------------------------
menuBar: MainWindowMenuBar {
hide: !collapse.isCollapsed
hide: mainLoader.item ? !mainLoader.item.collapse.isCollapsed : true
}
// ---------------------------------------------------------------------------
onActiveFocusItemChanged: activeFocusItem == null && smartSearchBar.hideMenu()
onActiveFocusItemChanged: Logic.handleActiveFocusItemChanged(activeFocusItem)
// ---------------------------------------------------------------------------
......@@ -65,160 +64,171 @@ ApplicationWindow {
// ---------------------------------------------------------------------------
ColumnLayout {
id: container
Loader {
id: mainLoader
active: CoreManager.linphoneCoreCreated
anchors.fill: parent
spacing: 0
// -------------------------------------------------------------------------
// Toolbar properties.
// -------------------------------------------------------------------------
sourceComponent: ColumnLayout {
// Workaround to get these properties in `MainWindow.js`.
// TODO: Find better Workaround.
readonly property alias collapse: collapse
readonly property alias contentLoader: contentLoader
readonly property alias menu: menu
readonly property alias timeline: timeline
ToolBar {
Layout.fillWidth: true
Layout.preferredHeight: MainWindowStyle.toolBar.height
spacing: 0
background: MainWindowStyle.toolBar.background
// -----------------------------------------------------------------------
// Toolbar properties.
// -----------------------------------------------------------------------
RowLayout {
anchors {
fill: parent
leftMargin: MainWindowStyle.toolBar.leftMargin
rightMargin: MainWindowStyle.toolBar.rightMargin
}
spacing: MainWindowStyle.toolBar.spacing
ToolBar {
Layout.fillWidth: true
Layout.preferredHeight: MainWindowStyle.toolBar.height
Collapse {
id: collapse
background: MainWindowStyle.toolBar.background
Layout.fillHeight: parent.height
target: window
targetHeight: MainWindowStyle.minimumHeight
visible: Qt.platform.os !== 'linux'
RowLayout {
anchors {
fill: parent
leftMargin: MainWindowStyle.toolBar.leftMargin
rightMargin: MainWindowStyle.toolBar.rightMargin
}
spacing: MainWindowStyle.toolBar.spacing
Component.onCompleted: setCollapsed(true)
}
Collapse {
id: collapse
AccountStatus {
id: accountStatus
Layout.fillHeight: parent.height
target: window
targetHeight: MainWindowStyle.minimumHeight
visible: Qt.platform.os !== 'linux'
Layout.fillHeight: parent.height
Layout.preferredWidth: MainWindowStyle.accountStatus.width
Component.onCompleted: setCollapsed(true)
}
account: AccountSettingsModel
presence: PresenceStatusModel
AccountStatus {
id: accountStatus
TooltipArea {
text: AccountSettingsModel.sipAddress
}
Layout.fillHeight: parent.height
Layout.preferredWidth: MainWindowStyle.accountStatus.width
onClicked: Logic.manageAccounts()
}
account: AccountSettingsModel
presence: PresenceStatusModel
Column {
width: MainWindowStyle.autoAnswerStatus.width
TooltipArea {
text: AccountSettingsModel.sipAddress
}
Icon {
icon: SettingsModel.autoAnswerStatus
? 'auto_answer'
: ''
iconSize: MainWindowStyle.autoAnswerStatus.iconSize
onClicked: Logic.manageAccounts()
}
Text {
clip: true
color: MainWindowStyle.autoAnswerStatus.text.color
font.pointSize: MainWindowStyle.autoAnswerStatus.text.fontSize
text: qsTr('autoAnswerStatus')
visible: SettingsModel.autoAnswerStatus
width: parent.width
Column {
width: MainWindowStyle.autoAnswerStatus.width
Icon {
icon: SettingsModel.autoAnswerStatus
? 'auto_answer'
: ''
iconSize: MainWindowStyle.autoAnswerStatus.iconSize
}
Text {
clip: true
color: MainWindowStyle.autoAnswerStatus.text.color
font.pointSize: MainWindowStyle.autoAnswerStatus.text.fontSize
text: qsTr('autoAnswerStatus')
visible: SettingsModel.autoAnswerStatus
width: parent.width
}
}
}
SmartSearchBar {
id: smartSearchBar
SmartSearchBar {
id: smartSearchBar
Layout.fillWidth: true
Layout.fillWidth: true
entryHeight: MainWindowStyle.searchBox.entryHeight
maxMenuHeight: MainWindowStyle.searchBox.maxHeight
placeholderText: qsTr('mainSearchBarPlaceholder')
entryHeight: MainWindowStyle.searchBox.entryHeight
maxMenuHeight: MainWindowStyle.searchBox.maxHeight
placeholderText: qsTr('mainSearchBarPlaceholder')
model: SmartSearchBarModel {}
model: SmartSearchBarModel {}
onAddContact: window.setView('ContactEdit', {
sipAddress: sipAddress
})
onAddContact: window.setView('ContactEdit', {
sipAddress: sipAddress
})
onEntryClicked: window.setView(entry.contact ? 'ContactEdit' : 'Conversation', {
sipAddress: entry.sipAddress
})
onEntryClicked: window.setView(entry.contact ? 'ContactEdit' : 'Conversation', {
sipAddress: entry.sipAddress
})
onLaunchCall: CallsListModel.launchAudioCall(sipAddress)
onLaunchChat: window.setView('Conversation', {
sipAddress: sipAddress
})
onLaunchCall: CallsListModel.launchAudioCall(sipAddress)
onLaunchChat: window.setView('Conversation', {
sipAddress: sipAddress
})
onLaunchVideoCall: CallsListModel.launchVideoCall(sipAddress)
onLaunchVideoCall: CallsListModel.launchVideoCall(sipAddress)
}
}
}
}
// -------------------------------------------------------------------------
// Content.
// -------------------------------------------------------------------------
// -----------------------------------------------------------------------
// Content.
// -----------------------------------------------------------------------
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 0
spacing: 0
// Main menu.
ColumnLayout {
Layout.maximumWidth: MainWindowStyle.menu.width
Layout.preferredWidth: MainWindowStyle.menu.width
// Main menu.
ColumnLayout {
Layout.maximumWidth: MainWindowStyle.menu.width
Layout.preferredWidth: MainWindowStyle.menu.width
spacing: 0
spacing: 0
Menu {
id: menu
Menu {
id: menu
entryHeight: MainWindowStyle.menu.entryHeight
entryWidth: MainWindowStyle.menu.width
entryHeight: MainWindowStyle.menu.entryHeight
entryWidth: MainWindowStyle.menu.width
entries: [{
entryName: qsTr('homeEntry'),
icon: 'home'
}, {
entryName: qsTr('contactsEntry'),
icon: 'contact'
}]
entries: [{
entryName: qsTr('homeEntry'),
icon: 'home'
}, {
entryName: qsTr('contactsEntry'),
icon: 'contact'
}]
onEntrySelected: !entry ? setView('Home') : setView('Contacts')
}
onEntrySelected: !entry ? setView('Home') : setView('Contacts')
}
// History.
Timeline {
id: timeline
// History.
Timeline {
id: timeline
Layout.fillHeight: true
Layout.fillWidth: true
model: TimelineModel
Layout.fillHeight: true
Layout.fillWidth: true
model: TimelineModel
onEntrySelected: setView('Conversation', { sipAddress: entry })
onEntrySelected: setView('Conversation', { sipAddress: entry })
}
}
}
// Main content.
Loader {
id: contentLoader
// Main content.
Loader {
id: contentLoader
Layout.fillHeight: true
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
source: 'Home.qml'
source: 'Home.qml'
}
}
}
}
......@@ -239,9 +249,9 @@ ApplicationWindow {
flat: true
height: accountStatus.height
height: MainWindowStyle.toolBar.height
width: MainWindowStyle.toolBar.leftMargin
onClicked: CoreManager.forceRefreshRegisters()
onClicked: CoreManager.linphoneCoreCreated && CoreManager.forceRefreshRegisters()
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment