From c1b38dc675df63b66c725e9ad7586ca967fa53e4 Mon Sep 17 00:00:00 2001 From: Ronan Abhamon Date: Wed, 22 Mar 2017 17:02:23 +0100 Subject: [PATCH] feat(app): build linphone core in a specific thread --- linphone-desktop/CMakeLists.txt | 2 +- linphone-desktop/src/app/App.cpp | 97 +++--- linphone-desktop/src/app/App.hpp | 5 +- .../src/components/core/CoreManager.cpp | 47 ++- .../src/components/core/CoreManager.hpp | 16 + linphone-desktop/src/main.cpp | 12 +- .../ui/views/App/Main/MainWindow.js | 21 +- .../ui/views/App/Main/MainWindow.qml | 286 +++++++++--------- 8 files changed, 280 insertions(+), 206 deletions(-) diff --git a/linphone-desktop/CMakeLists.txt b/linphone-desktop/CMakeLists.txt index fd3e13e85..ceffc749b 100644 --- a/linphone-desktop/CMakeLists.txt +++ b/linphone-desktop/CMakeLists.txt @@ -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) diff --git a/linphone-desktop/src/app/App.cpp b/linphone-desktop/src/app/App.cpp index cae45390f..a5601d7ad 100644 --- a/linphone-desktop/src/app/App.cpp +++ b/linphone-desktop/src/app/App.cpp @@ -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(); diff --git a/linphone-desktop/src/app/App.hpp b/linphone-desktop/src/app/App.hpp index 2fd4330e6..9c95c41da 100644 --- a/linphone-desktop/src/app/App.hpp +++ b/linphone-desktop/src/app/App.hpp @@ -24,13 +24,12 @@ #define APP_H_ #include "../components/notifier/Notifier.hpp" +#include "../externals/single-application/SingleApplication.hpp" #include #include #include -#include "../externals/single-application/SingleApplication.hpp" - // ============================================================================= class DefaultTranslator; @@ -49,6 +48,8 @@ public: void initContentApp (); void parseArgs (); + void tryToUsePreferredLocale (); + QQmlEngine *getEngine () { return &m_engine; } diff --git a/linphone-desktop/src/components/core/CoreManager.cpp b/linphone-desktop/src/components/core/CoreManager.cpp index b4aac924c..5687caaba 100644 --- a/linphone-desktop/src/components/core/CoreManager.cpp +++ b/linphone-desktop/src/components/core/CoreManager.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include 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()) { - // TODO: activate migration when ready to switch to this new version - // Paths::migrate(); + m_promise_build = QtConcurrent::run(this, &CoreManager::createLinphoneCore, config_path); - setResourcesPaths(); + QObject::connect( + &m_promise_watcher, &QFutureWatcher::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 = linphone::Factory::get()->createCore(m_handlers, Paths::getConfigFilepath(config_path), ""); + emit m_instance->linphoneCoreCreated(); + } + ); - m_core->setVideoDisplayFilter("MSOGL"); - m_core->usePreviewWindow(true); - - 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(); +} diff --git a/linphone-desktop/src/components/core/CoreManager.hpp b/linphone-desktop/src/components/core/CoreManager.hpp index b2b1bb08e..7f1699ecd 100644 --- a/linphone-desktop/src/components/core/CoreManager.hpp +++ b/linphone-desktop/src/components/core/CoreManager.hpp @@ -30,6 +30,8 @@ #include "CoreHandlers.hpp" +#include +#include #include // ============================================================================= @@ -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 m_core; std::shared_ptr m_handlers; @@ -119,6 +132,9 @@ private: QTimer *m_cbs_timer; + QFuture m_promise_build; + QFutureWatcher m_promise_watcher; + QMutex m_mutex_video_render; static CoreManager *m_instance; diff --git a/linphone-desktop/src/main.cpp b/linphone-desktop/src/main.cpp index 1e6623076..25f0fb828 100644 --- a/linphone-desktop/src/main.cpp +++ b/linphone-desktop/src/main.cpp @@ -20,10 +20,7 @@ * Author: Ronan Abhamon */ -#include - #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(); } diff --git a/linphone-desktop/ui/views/App/Main/MainWindow.js b/linphone-desktop/ui/views/App/Main/MainWindow.js index 6b4e5869f..afa3bb58d 100644 --- a/linphone-desktop/ui/views/App/Main/MainWindow.js +++ b/linphone-desktop/ui/views/App/Main/MainWindow.js @@ -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() diff --git a/linphone-desktop/ui/views/App/Main/MainWindow.qml b/linphone-desktop/ui/views/App/Main/MainWindow.qml index 4312238aa..6a54c279a 100644 --- a/linphone-desktop/ui/views/App/Main/MainWindow.qml +++ b/linphone-desktop/ui/views/App/Main/MainWindow.qml @@ -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. - // ------------------------------------------------------------------------- - - ToolBar { - Layout.fillWidth: true - Layout.preferredHeight: MainWindowStyle.toolBar.height - - background: MainWindowStyle.toolBar.background - - RowLayout { - anchors { - fill: parent - leftMargin: MainWindowStyle.toolBar.leftMargin - rightMargin: MainWindowStyle.toolBar.rightMargin - } - spacing: MainWindowStyle.toolBar.spacing - - Collapse { - id: collapse - - Layout.fillHeight: parent.height - target: window - targetHeight: MainWindowStyle.minimumHeight - visible: Qt.platform.os !== 'linux' - - Component.onCompleted: setCollapsed(true) - } - - AccountStatus { - id: accountStatus - - Layout.fillHeight: parent.height - Layout.preferredWidth: MainWindowStyle.accountStatus.width - - account: AccountSettingsModel - presence: PresenceStatusModel - - TooltipArea { - text: AccountSettingsModel.sipAddress - } - - onClicked: Logic.manageAccounts() - } - - 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 - - Layout.fillWidth: true - - entryHeight: MainWindowStyle.searchBox.entryHeight - maxMenuHeight: MainWindowStyle.searchBox.maxHeight - placeholderText: qsTr('mainSearchBarPlaceholder') - - model: SmartSearchBarModel {} - - onAddContact: window.setView('ContactEdit', { - sipAddress: sipAddress - }) - - onEntryClicked: window.setView(entry.contact ? 'ContactEdit' : 'Conversation', { - sipAddress: entry.sipAddress - }) - - onLaunchCall: CallsListModel.launchAudioCall(sipAddress) - onLaunchChat: window.setView('Conversation', { - sipAddress: sipAddress - }) - - onLaunchVideoCall: CallsListModel.launchVideoCall(sipAddress) - } - } - } - - // ------------------------------------------------------------------------- - // Content. - // ------------------------------------------------------------------------- - - RowLayout { - Layout.fillHeight: true - Layout.fillWidth: true + 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 spacing: 0 - // Main menu. - ColumnLayout { - Layout.maximumWidth: MainWindowStyle.menu.width - Layout.preferredWidth: MainWindowStyle.menu.width + // ----------------------------------------------------------------------- + // Toolbar properties. + // ----------------------------------------------------------------------- - spacing: 0 + ToolBar { + Layout.fillWidth: true + Layout.preferredHeight: MainWindowStyle.toolBar.height - Menu { - id: menu + background: MainWindowStyle.toolBar.background - entryHeight: MainWindowStyle.menu.entryHeight - entryWidth: MainWindowStyle.menu.width + RowLayout { + anchors { + fill: parent + leftMargin: MainWindowStyle.toolBar.leftMargin + rightMargin: MainWindowStyle.toolBar.rightMargin + } + spacing: MainWindowStyle.toolBar.spacing - entries: [{ - entryName: qsTr('homeEntry'), - icon: 'home' - }, { - entryName: qsTr('contactsEntry'), - icon: 'contact' - }] + Collapse { + id: collapse - onEntrySelected: !entry ? setView('Home') : setView('Contacts') - } + Layout.fillHeight: parent.height + target: window + targetHeight: MainWindowStyle.minimumHeight + visible: Qt.platform.os !== 'linux' - // History. - Timeline { - id: timeline + Component.onCompleted: setCollapsed(true) + } - Layout.fillHeight: true - Layout.fillWidth: true - model: TimelineModel + AccountStatus { + id: accountStatus - onEntrySelected: setView('Conversation', { sipAddress: entry }) + Layout.fillHeight: parent.height + Layout.preferredWidth: MainWindowStyle.accountStatus.width + + account: AccountSettingsModel + presence: PresenceStatusModel + + TooltipArea { + text: AccountSettingsModel.sipAddress + } + + onClicked: Logic.manageAccounts() + } + + 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 + + Layout.fillWidth: true + + entryHeight: MainWindowStyle.searchBox.entryHeight + maxMenuHeight: MainWindowStyle.searchBox.maxHeight + placeholderText: qsTr('mainSearchBarPlaceholder') + + model: SmartSearchBarModel {} + + onAddContact: window.setView('ContactEdit', { + sipAddress: sipAddress + }) + + onEntryClicked: window.setView(entry.contact ? 'ContactEdit' : 'Conversation', { + sipAddress: entry.sipAddress + }) + + onLaunchCall: CallsListModel.launchAudioCall(sipAddress) + onLaunchChat: window.setView('Conversation', { + sipAddress: sipAddress + }) + + onLaunchVideoCall: CallsListModel.launchVideoCall(sipAddress) + } } } - // Main content. - Loader { - id: contentLoader + // ----------------------------------------------------------------------- + // Content. + // ----------------------------------------------------------------------- + RowLayout { Layout.fillHeight: true Layout.fillWidth: true - source: 'Home.qml' + spacing: 0 + + // Main menu. + ColumnLayout { + Layout.maximumWidth: MainWindowStyle.menu.width + Layout.preferredWidth: MainWindowStyle.menu.width + + spacing: 0 + + Menu { + id: menu + + entryHeight: MainWindowStyle.menu.entryHeight + entryWidth: MainWindowStyle.menu.width + + entries: [{ + entryName: qsTr('homeEntry'), + icon: 'home' + }, { + entryName: qsTr('contactsEntry'), + icon: 'contact' + }] + + onEntrySelected: !entry ? setView('Home') : setView('Contacts') + } + + // History. + Timeline { + id: timeline + + Layout.fillHeight: true + Layout.fillWidth: true + model: TimelineModel + + onEntrySelected: setView('Conversation', { sipAddress: entry }) + } + } + + // Main content. + Loader { + id: contentLoader + + Layout.fillHeight: true + Layout.fillWidth: true + + 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() } }