From 1e065cfa72a183b1b315edccd6eeeff7068289ee Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 11 Jun 2024 19:03:13 +0000 Subject: [PATCH] Help Page (including Log enable/clear/send) --- Linphone/CMakeLists.txt | 7 +- Linphone/core/App.cpp | 24 +++ Linphone/core/setting/SettingsCore.cpp | 72 +++++++++ Linphone/core/setting/SettingsCore.hpp | 34 +++- Linphone/data/config/linphonerc-factory | 2 +- Linphone/data/image/debug.svg | 3 + Linphone/data/image/license.svg | 3 + Linphone/data/image/world.svg | 3 + Linphone/model/core/CoreModel.cpp | 5 + Linphone/model/core/CoreModel.hpp | 1 + Linphone/model/logger/LoggerModel.cpp | 12 +- Linphone/model/logger/LoggerModel.hpp | 2 +- Linphone/model/setting/SettingsModel.cpp | 148 +++++++++++++----- Linphone/model/setting/SettingsModel.hpp | 24 +++ Linphone/tool/AbstractObject.hpp | 3 + Linphone/tool/Constants.hpp | 3 +- Linphone/view/App/Layout/MainLayout.qml | 33 +++- .../Layout/Settings/DebugSettingsLayout.qml | 87 ++++++++++ Linphone/view/CMakeLists.txt | 5 + .../view/Item/Help/HelpIconLabelButton.qml | 51 ++++++ Linphone/view/Item/MediumButton.qml | 17 ++ Linphone/view/Page/Main/HelpPage.qml | 126 +++++++++++++++ Linphone/view/Style/AppIcons.qml | 5 +- Linphone/view/Style/Typography.qml | 7 + 24 files changed, 626 insertions(+), 51 deletions(-) create mode 100644 Linphone/data/image/debug.svg create mode 100644 Linphone/data/image/license.svg create mode 100644 Linphone/data/image/world.svg create mode 100644 Linphone/view/App/Layout/Settings/DebugSettingsLayout.qml create mode 100644 Linphone/view/Item/Help/HelpIconLabelButton.qml create mode 100644 Linphone/view/Item/MediumButton.qml create mode 100644 Linphone/view/Page/Main/HelpPage.qml diff --git a/Linphone/CMakeLists.txt b/Linphone/CMakeLists.txt index a81637fc9..659486d5f 100644 --- a/Linphone/CMakeLists.txt +++ b/Linphone/CMakeLists.txt @@ -53,7 +53,12 @@ if(NOT LINPHONEAPP_VERSION) bc_compute_full_version(LINPHONEAPP_VERSION) endif() include(application_info.cmake) - +string(TIMESTAMP CURRENT_YEAR "%Y") +if(NOT APPLICATION_START_LICENCE OR "${CURRENT_YEAR}" STREQUAL "${APPLICATION_START_LICENCE}") + set(COPYRIGHT_RANGE_DATE "${APPLICATION_START_LICENCE}") +else() + set(COPYRIGHT_RANGE_DATE "${APPLICATION_START_LICENCE}-${CURRENT_YEAR}") +endif() if(MEDIASTREAMER2_PLUGINS_LOCATION) diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index e34ad3177..2b557d413 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -160,6 +160,30 @@ void App::init() { mEngine->addImportPath(":/"); mEngine->rootContext()->setContextProperty("applicationDirPath", QGuiApplication::applicationDirPath()); +#ifdef APPLICATION_VENDOR + mEngine->rootContext()->setContextProperty("applicationVendor", APPLICATION_VENDOR); +#else + mEngine->rootContext()->setContextProperty("applicationVendor", ""); +#endif +#ifdef APPLICATION_LICENCE + mEngine->rootContext()->setContextProperty("applicationLicence", APPLICATION_LICENCE); +#else + mEngine->rootContext()->setContextProperty("applicationLicence", ""); +#endif +#ifdef APPLICATION_LICENCE_URL + mEngine->rootContext()->setContextProperty("applicationLicenceUrl", APPLICATION_LICENCE_URL); +#else + mEngine->rootContext()->setContextProperty("applicationLicenceUrl", ""); +#endif +#ifdef COPYRIGHT_RANGE_DATE + mEngine->rootContext()->setContextProperty("copyrightRangeDate", COPYRIGHT_RANGE_DATE); +#else + mEngine->rootContext()->setContextProperty("copyrightRangeDate", ""); +#endif + mEngine->rootContext()->setContextProperty("applicationName", APPLICATION_NAME); + mEngine->rootContext()->setContextProperty("executableName", EXECUTABLE_NAME); + + initCppInterfaces(); mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider()); mEngine->addImageProvider(AvatarProvider::ProviderId, new AvatarProvider()); diff --git a/Linphone/core/setting/SettingsCore.cpp b/Linphone/core/setting/SettingsCore.cpp index 689b61464..6fcebea0c 100644 --- a/Linphone/core/setting/SettingsCore.cpp +++ b/Linphone/core/setting/SettingsCore.cpp @@ -63,6 +63,11 @@ Settings::Settings(QObject *parent) : QObject(parent) { mVideoDevice = mSettingsModel->getVideoDevice(); mVideoDevices = mSettingsModel->getVideoDevices(); + //Logs + mLogsEnabled = mSettingsModel->getLogsEnabled(); + mFullLogsEnabled = mSettingsModel->getFullLogsEnabled(); + mLogsFolder = mSettingsModel->getLogsFolder(); + mLogsEmail = mSettingsModel->getLogsEmail(); } Settings::~Settings() { @@ -229,6 +234,45 @@ void Settings::setSelf(QSharedPointer me) { emit videoDevicesChanged(); }); }); + + // Logs + mSettingsModelConnection->makeConnectToCore(&Settings::setLogsEnabled, [this](const bool status) { + mSettingsModelConnection->invokeToModel( + [this, status]() { mSettingsModel->setLogsEnabled(status); }); + }); + + mSettingsModelConnection->makeConnectToModel(&SettingsModel::logsEnabledChanged, [this](const bool status) { + mSettingsModelConnection->invokeToCore( + [this, status]() { + mLogsEnabled = status; + emit logsEnabledChanged(); + }); + }); + + mSettingsModelConnection->makeConnectToCore(&Settings::setFullLogsEnabled, [this](const bool status) { + mSettingsModelConnection->invokeToModel( + [this, status]() { mSettingsModel->setFullLogsEnabled(status); }); + }); + + mSettingsModelConnection->makeConnectToModel(&SettingsModel::fullLogsEnabledChanged, [this](const bool status) { + mSettingsModelConnection->invokeToCore( + [this, status]() { + mFullLogsEnabled = status; + emit fullLogsEnabledChanged(); + }); + }); + + auto coreModelConnection = QSharedPointer>( + new SafeConnection(me, CoreModel::getInstance()), &QObject::deleteLater); + + coreModelConnection->makeConnectToModel(&CoreModel::logCollectionUploadStateChanged, [this](auto core, auto state, auto info) { + mSettingsModelConnection->invokeToCore( + [this, state, info]() { + if (state == linphone::Core::LogCollectionUploadState::Delivered || state == linphone::Core::LogCollectionUploadState::NotDelivered) { + emit logsUploadTerminated(state == linphone::Core::LogCollectionUploadState::Delivered, Utils::coreStringToAppString(info)); + } + }); + }); } QString Settings::getConfigPath(const QCommandLineParser &parser) { @@ -319,3 +363,31 @@ void Settings::updateMicVolume() const { [this]() { mSettingsModel->getMicVolume(); } ); } + +bool Settings::getLogsEnabled () const { + return mLogsEnabled; +} + +bool Settings::getFullLogsEnabled () const { + return mFullLogsEnabled; +} + +void Settings::cleanLogs () const { + mSettingsModelConnection->invokeToModel( + [this]() { mSettingsModel->cleanLogs(); } + ); +} + +void Settings::sendLogs () const { + mSettingsModelConnection->invokeToModel( + [this]() { mSettingsModel->sendLogs(); } + ); +} + +QString Settings::getLogsEmail () const { + return mLogsEmail; +} + +QString Settings::getLogsFolder () const { + return mLogsFolder; +} diff --git a/Linphone/core/setting/SettingsCore.hpp b/Linphone/core/setting/SettingsCore.hpp index 81b84a501..0c533f92e 100644 --- a/Linphone/core/setting/SettingsCore.hpp +++ b/Linphone/core/setting/SettingsCore.hpp @@ -57,6 +57,12 @@ class Settings : public QObject, public AbstractObject { Q_PROPERTY(QString videoDevice READ getVideoDevice WRITE setVideoDevice NOTIFY videoDeviceChanged) Q_PROPERTY(float micVolume MEMBER _dummy_int NOTIFY micVolumeChanged) + + Q_PROPERTY(bool logsEnabled READ getLogsEnabled WRITE setLogsEnabled NOTIFY logsEnabledChanged) + Q_PROPERTY(bool fullLogsEnabled READ getFullLogsEnabled WRITE setFullLogsEnabled NOTIFY fullLogsEnabledChanged) + Q_PROPERTY(QString logsEmail READ getLogsEmail) + Q_PROPERTY(QString logsFolder READ getLogsFolder) + public: static QSharedPointer create(); @@ -104,6 +110,15 @@ public: Q_INVOKABLE void closeCallSettings(); Q_INVOKABLE void updateMicVolume() const; + bool getLogsEnabled () const; + bool getFullLogsEnabled () const; + + Q_INVOKABLE void cleanLogs () const; + Q_INVOKABLE void sendLogs () const; + QString getLogsEmail () const; + QString getLogsFolder () const; + + signals: // Security @@ -145,12 +160,21 @@ signals: void echoCancellationCalibrationChanged(); void micVolumeChanged(float volume); - + + void logsEnabledChanged (); + void fullLogsEnabledChanged (); + + void setLogsEnabled (bool status); + void setFullLogsEnabled (bool status); + + void logsUploadTerminated (bool status, QString url); + void logsEmailChanged (const QString &email); + void logsFolderChanged (const QString &folder); private: std::shared_ptr mSettingsModel; - // Dummy properties (for properties that use values from core received throuh signals) + // Dummy properties (for properties that use values from core received through signals) int _dummy_int = 0; // Security @@ -177,6 +201,12 @@ private: float mPlaybackGain; int mEchoCancellationCalibration; + //Debug logs + bool mLogsEnabled; + bool mFullLogsEnabled; + QString mLogsFolder; + QString mLogsEmail; + QSettings mAppSettings; QSharedPointer> mSettingsModelConnection; diff --git a/Linphone/data/config/linphonerc-factory b/Linphone/data/config/linphonerc-factory index c567927d3..697e1b59e 100644 --- a/Linphone/data/config/linphonerc-factory +++ b/Linphone/data/config/linphonerc-factory @@ -21,7 +21,7 @@ username_regex=^[a-z0-9+_.\-]*$ lime_update_threshold=86400 [misc] -#log_collection_upload_server_url=https://www.linphone.org:444/lft.php +log_collection_upload_server_url=https://www.linphone.org:444/lft.php aggregate_imdn=1 enable_basic_to_client_group_chat_room_migration=0 enable_simple_group_chat_message_state=0 diff --git a/Linphone/data/image/debug.svg b/Linphone/data/image/debug.svg new file mode 100644 index 000000000..44aeb17bc --- /dev/null +++ b/Linphone/data/image/debug.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/image/license.svg b/Linphone/data/image/license.svg new file mode 100644 index 000000000..c0fe5eb1d --- /dev/null +++ b/Linphone/data/image/license.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/data/image/world.svg b/Linphone/data/image/world.svg new file mode 100644 index 000000000..f99770e42 --- /dev/null +++ b/Linphone/data/image/world.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/model/core/CoreModel.cpp b/Linphone/model/core/CoreModel.cpp index 02cdfb109..af7b9222e 100644 --- a/Linphone/model/core/CoreModel.cpp +++ b/Linphone/model/core/CoreModel.cpp @@ -110,6 +110,11 @@ std::shared_ptr CoreModel::getCore() { return mCore; } +std::shared_ptr CoreModel::getLogger() { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + return mLogger; +} + //------------------------------------------------------------------------------- void CoreModel::setConfigPath(QString path) { if (mConfigPath != path) { diff --git a/Linphone/model/core/CoreModel.hpp b/Linphone/model/core/CoreModel.hpp index 071eaf136..31cac7e06 100644 --- a/Linphone/model/core/CoreModel.hpp +++ b/Linphone/model/core/CoreModel.hpp @@ -45,6 +45,7 @@ public: static std::shared_ptr getInstance(); std::shared_ptr getCore(); + std::shared_ptr getLogger(); void start(); void setConfigPath(QString path); diff --git a/Linphone/model/logger/LoggerModel.cpp b/Linphone/model/logger/LoggerModel.cpp index 2318f9ad2..cf6f9c7b8 100644 --- a/Linphone/model/logger/LoggerModel.cpp +++ b/Linphone/model/logger/LoggerModel.cpp @@ -30,6 +30,7 @@ #include "LoggerModel.hpp" #include "tool/Constants.hpp" #include "tool/Utils.hpp" +#include "model/setting/SettingsModel.hpp" #include "core/logger/QtLogger.hpp" // ----------------------------------------------------------------------------- @@ -122,12 +123,11 @@ void LoggerModel::enable(bool status) { : linphone::LogCollectionState::Disabled); } -void LoggerModel::init(const std::shared_ptr &config) { - // TODO update from config - // const QString folder = SettingsModel::getLogsFolder(config); - // linphone::Core::setLogCollectionPath(Utils::appStringToCoreString(folder)); - // enableFullLogs(SettingsModel::getFullLogsEnabled(config)); - // enable(SettingsModel::getLogsEnabled(config)); +void LoggerModel::applyConfig(const std::shared_ptr &config) { + const QString folder = SettingsModel::getLogsFolder(config); + linphone::Core::setLogCollectionPath(Utils::appStringToCoreString(folder)); + enableFullLogs(SettingsModel::getFullLogsEnabled(config)); + enable(SettingsModel::getLogsEnabled(config)); } void LoggerModel::init() { diff --git a/Linphone/model/logger/LoggerModel.hpp b/Linphone/model/logger/LoggerModel.hpp index 5a566937d..22846b624 100644 --- a/Linphone/model/logger/LoggerModel.hpp +++ b/Linphone/model/logger/LoggerModel.hpp @@ -43,7 +43,7 @@ public: void enableQtOnly(const bool &enable); void init(); - void init(const std::shared_ptr &config); + void applyConfig(const std::shared_ptr &config); void onQtLog(QtMsgType type, QString msg); // Received from Qt void onLinphoneLog(const std::shared_ptr &, diff --git a/Linphone/model/setting/SettingsModel.cpp b/Linphone/model/setting/SettingsModel.cpp index dd557698b..173764646 100644 --- a/Linphone/model/setting/SettingsModel.cpp +++ b/Linphone/model/setting/SettingsModel.cpp @@ -22,6 +22,8 @@ #include "model/core/CoreModel.hpp" #include "tool/Utils.hpp" #include "model/tool/ToolModel.hpp" +#include "core/path/Paths.hpp" + // ============================================================================= @@ -32,13 +34,14 @@ using namespace std; const std::string SettingsModel::UiSection("ui"); SettingsModel::SettingsModel(QObject *parent) : QObject(parent) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); auto core = CoreModel::getInstance()->getCore(); mConfig = core->getConfig(); + CoreModel::getInstance()->getLogger()->applyConfig(mConfig); } SettingsModel::~SettingsModel() { - mustBeInLinphoneThread("~" + getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); } bool SettingsModel::isReadOnly(const std::string §ion, const std::string &name) const { @@ -51,7 +54,7 @@ std::string SettingsModel::getEntryFullName(const std::string §ion, const st } QStringList SettingsModel::getVideoDevices() const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); auto core = CoreModel::getInstance()->getCore(); QStringList result; for (auto &device : core->getVideoDevicesList()) { @@ -61,14 +64,14 @@ QStringList SettingsModel::getVideoDevices() const { } QString SettingsModel::getVideoDevice () const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return Utils::coreStringToAppString( CoreModel::getInstance()->getCore()->getVideoDevice() ); } void SettingsModel::setVideoDevice (const QString &device) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); CoreModel::getInstance()->getCore()->setVideoDevice( Utils::appStringToCoreString(device) ); @@ -80,24 +83,24 @@ void SettingsModel::setVideoDevice (const QString &device) { // ============================================================================= bool SettingsModel::getIsInCall() const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return CoreModel::getInstance()->getCore()->getCallsNb() != 0; } void SettingsModel::resetCaptureGraph() { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); deleteCaptureGraph(); createCaptureGraph(); } void SettingsModel::createCaptureGraph() { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mSimpleCaptureGraph = new MediastreamerUtils::SimpleCaptureGraph(Utils::appStringToCoreString(getCaptureDevice()), Utils::appStringToCoreString(getPlaybackDevice())); mSimpleCaptureGraph->start(); emit captureGraphRunningChanged(getCaptureGraphRunning()); } void SettingsModel::startCaptureGraph() { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); if (!getIsInCall()) { if (!mSimpleCaptureGraph) { qDebug() << "Starting capture graph [" << mCaptureGraphListenerCount << "]"; @@ -107,7 +110,7 @@ void SettingsModel::startCaptureGraph() { } } void SettingsModel::stopCaptureGraph() { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); if (mCaptureGraphListenerCount > 0) { if (--mCaptureGraphListenerCount == 0) { qDebug() << "Stopping capture graph [" << mCaptureGraphListenerCount << "]"; @@ -116,14 +119,14 @@ void SettingsModel::stopCaptureGraph() { } } void SettingsModel::stopCaptureGraphs() { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); if (mCaptureGraphListenerCount > 0) { mCaptureGraphListenerCount = 0; deleteCaptureGraph(); } } void SettingsModel::deleteCaptureGraph() { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); if (mSimpleCaptureGraph) { if (mSimpleCaptureGraph->isRunning()) { mSimpleCaptureGraph->stop(); @@ -135,7 +138,7 @@ void SettingsModel::deleteCaptureGraph() { //Force a call on the 'detect' method of all audio filters, updating new or removed devices void SettingsModel::accessCallSettings() { // Audio - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); CoreModel::getInstance()->getCore()->reloadSoundDevices(); emit captureDevicesChanged(getCaptureDevices()); emit playbackDevicesChanged(getPlaybackDevices()); @@ -156,18 +159,18 @@ void SettingsModel::accessCallSettings() { } void SettingsModel::closeCallSettings() { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); stopCaptureGraph(); emit captureGraphRunningChanged(getCaptureGraphRunning()); } bool SettingsModel::getCaptureGraphRunning() { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return mSimpleCaptureGraph && mSimpleCaptureGraph->isRunning() && !getIsInCall(); } float SettingsModel::getMicVolume() { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); float v = 0.0; if (mSimpleCaptureGraph && mSimpleCaptureGraph->isRunning()) { @@ -178,13 +181,13 @@ float SettingsModel::getMicVolume() { } float SettingsModel::getPlaybackGain() const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); float dbGain = CoreModel::getInstance()->getCore()->getPlaybackGainDb(); return MediastreamerUtils::dbToLinear(dbGain); } void SettingsModel::setPlaybackGain(float gain) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); float oldGain = getPlaybackGain(); CoreModel::getInstance()->getCore()->setPlaybackGainDb(MediastreamerUtils::linearToDb(gain)); if (mSimpleCaptureGraph && mSimpleCaptureGraph->isRunning()) { @@ -201,7 +204,7 @@ float SettingsModel::getCaptureGain() const { } void SettingsModel::setCaptureGain(float gain) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); float oldGain = getCaptureGain(); CoreModel::getInstance()->getCore()->setMicGainDb(MediastreamerUtils::linearToDb(gain)); if (mSimpleCaptureGraph && mSimpleCaptureGraph->isRunning()) { @@ -212,7 +215,7 @@ void SettingsModel::setCaptureGain(float gain) { } QStringList SettingsModel::getCaptureDevices () const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); shared_ptr core = CoreModel::getInstance()->getCore(); QStringList list; @@ -224,7 +227,7 @@ QStringList SettingsModel::getCaptureDevices () const { } QStringList SettingsModel::getPlaybackDevices () const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); shared_ptr core = CoreModel::getInstance()->getCore(); QStringList list; @@ -239,13 +242,13 @@ QStringList SettingsModel::getPlaybackDevices () const { // ----------------------------------------------------------------------------- QString SettingsModel::getCaptureDevice () const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); auto audioDevice = CoreModel::getInstance()->getCore()->getInputAudioDevice(); return Utils::coreStringToAppString(audioDevice? audioDevice->getId() : CoreModel::getInstance()->getCore()->getCaptureDevice()); } void SettingsModel::setCaptureDevice (const QString &device) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); std::string devId = Utils::appStringToCoreString(device); auto list = CoreModel::getInstance()->getCore()->getExtendedAudioDevices(); auto audioDevice = find_if(list.cbegin(), list.cend(), [&] ( const std::shared_ptr & audioItem) { @@ -263,13 +266,13 @@ void SettingsModel::setCaptureDevice (const QString &device) { // ----------------------------------------------------------------------------- QString SettingsModel::getPlaybackDevice () const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); auto audioDevice = CoreModel::getInstance()->getCore()->getOutputAudioDevice(); return Utils::coreStringToAppString(audioDevice? audioDevice->getId() : CoreModel::getInstance()->getCore()->getPlaybackDevice()); } void SettingsModel::setPlaybackDevice (const QString &device) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); std::string devId = Utils::appStringToCoreString(device); auto list = CoreModel::getInstance()->getCore()->getExtendedAudioDevices(); @@ -289,14 +292,14 @@ void SettingsModel::setPlaybackDevice (const QString &device) { // ----------------------------------------------------------------------------- QString SettingsModel::getRingerDevice () const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return Utils::coreStringToAppString( CoreModel::getInstance()->getCore()->getRingerDevice() ); } void SettingsModel::setRingerDevice (const QString &device) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); CoreModel::getInstance()->getCore()->setRingerDevice( Utils::appStringToCoreString(device) ); @@ -306,12 +309,12 @@ void SettingsModel::setRingerDevice (const QString &device) { // ----------------------------------------------------------------------------- bool SettingsModel::getVideoEnabled() const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return CoreModel::getInstance()->getCore()->videoEnabled(); } void SettingsModel::setVideoEnabled(const bool enabled) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); auto core = CoreModel::getInstance()->getCore(); core->enableVideoCapture(enabled); core->enableVideoDisplay(enabled); @@ -321,33 +324,33 @@ void SettingsModel::setVideoEnabled(const bool enabled) { // ----------------------------------------------------------------------------- bool SettingsModel::getEchoCancellationEnabled () const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return CoreModel::getInstance()->getCore()->echoCancellationEnabled(); } void SettingsModel::setEchoCancellationEnabled (bool status) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); CoreModel::getInstance()->getCore()->enableEchoCancellation(status); emit echoCancellationEnabledChanged(status); } void SettingsModel::startEchoCancellerCalibration(){ - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); CoreModel::getInstance()->getCore()->startEchoCancellerCalibration(); } int SettingsModel::getEchoCancellationCalibration()const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return CoreModel::getInstance()->getCore()->getEchoCancellationCalibration(); } bool SettingsModel::getAutomaticallyRecordCallsEnabled () const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return !!mConfig->getInt(UiSection, "automatically_record_calls", 0); } void SettingsModel::setAutomaticallyRecordCallsEnabled (bool enabled) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mConfig->setInt(UiSection, "automatically_record_calls", enabled); emit automaticallyRecordCallsEnabledChanged(enabled); } @@ -357,12 +360,83 @@ void SettingsModel::setAutomaticallyRecordCallsEnabled (bool enabled) { // ============================================================================= bool SettingsModel::getVfsEnabled () const { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return !!mConfig->getInt(UiSection, "vfs_enabled", 0); } void SettingsModel::setVfsEnabled (bool enabled) { - mustBeInLinphoneThread(getClassName()); + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mConfig->setInt(UiSection, "vfs_enabled", enabled); emit vfsEnabledChanged(enabled); } + +// ============================================================================= +// Logs. +// ============================================================================= + +bool SettingsModel::getLogsEnabled () const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + return getLogsEnabled(mConfig); +} + +void SettingsModel::setLogsEnabled (bool status) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + mConfig->setInt(UiSection, "logs_enabled", status); + CoreModel::getInstance()->getLogger()->enable(status); + emit logsEnabledChanged(status); +} + +bool SettingsModel::getFullLogsEnabled () const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + return getFullLogsEnabled(mConfig); +} + +void SettingsModel::setFullLogsEnabled (bool status) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + mConfig->setInt(UiSection, "full_logs_enabled", status); + CoreModel::getInstance()->getLogger()->enableFullLogs(status); + emit fullLogsEnabledChanged(status); +} + +bool SettingsModel::getLogsEnabled (const shared_ptr &config) { + mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO)); + return config ? config->getInt(UiSection, "logs_enabled", false) : true; +} + +bool SettingsModel::getFullLogsEnabled (const shared_ptr &config) { + mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO)); + return config ? config->getInt(UiSection, "full_logs_enabled", false) : false; +} + +QString SettingsModel::getLogsFolder () const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + return getLogsFolder(mConfig); +} + +QString SettingsModel::getLogsFolder (const shared_ptr &config) { + mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO)); + return config + ? Utils::coreStringToAppString(config->getString(UiSection, "logs_folder", Utils::appStringToCoreString(Paths::getLogsDirPath()))) + : Paths::getLogsDirPath(); +} + +void SettingsModel::cleanLogs () const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + CoreModel::getInstance()->getCore()->resetLogCollection(); +} + +void SettingsModel::sendLogs () const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto core = CoreModel::getInstance()->getCore(); + qInfo() << QStringLiteral("Send logs to: `%1` from `%2`.") + .arg(Utils::coreStringToAppString(core->getLogCollectionUploadServerUrl())) + .arg(Utils::coreStringToAppString(core->getLogCollectionPath())); + core->uploadLogCollection(); +} + +QString SettingsModel::getLogsEmail () const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + return Utils::coreStringToAppString( + mConfig->getString(UiSection, "logs_email", Constants::DefaultLogsEmail) + ); +} diff --git a/Linphone/model/setting/SettingsModel.hpp b/Linphone/model/setting/SettingsModel.hpp index 3ab3332ac..2701daa6d 100644 --- a/Linphone/model/setting/SettingsModel.hpp +++ b/Linphone/model/setting/SettingsModel.hpp @@ -101,6 +101,27 @@ public: QString getVideoDevice () const; void setVideoDevice (const QString &device); + + bool getLogsEnabled () const; + void setLogsEnabled (bool status); + + bool getFullLogsEnabled () const; + void setFullLogsEnabled (bool status); + + static bool getLogsEnabled (const std::shared_ptr &config); + static bool getFullLogsEnabled (const std::shared_ptr &config); + + QString getLogsFolder () const; + void setLogsFolder (const QString &folder); + static QString getLogsFolder (const std::shared_ptr &config); + + QString getLogsUploadUrl () const; + void setLogsUploadUrl (const QString &url); + + void cleanLogs () const; + void sendLogs () const; + + QString getLogsEmail () const; signals: @@ -132,6 +153,9 @@ signals: void videoDeviceChanged (const QString &device); void micVolumeChanged(float volume); + + void logsEnabledChanged (bool status); + void fullLogsEnabledChanged (bool status); private: MediastreamerUtils::SimpleCaptureGraph *mSimpleCaptureGraph = nullptr; diff --git a/Linphone/tool/AbstractObject.hpp b/Linphone/tool/AbstractObject.hpp index eb0078ac8..71239ba10 100644 --- a/Linphone/tool/AbstractObject.hpp +++ b/Linphone/tool/AbstractObject.hpp @@ -32,6 +32,9 @@ const char *CLASS_NAME::gClassName = #CLASS_NAME; \ QString CLASS_NAME::getClassName() const { \ return gClassName; \ + } \ + static inline QString sLog() { \ + return QStringLiteral("[%1]: %2").arg(#CLASS_NAME).arg("%1"); \ } #define DECLARE_GUI_OBJECT \ diff --git a/Linphone/tool/Constants.hpp b/Linphone/tool/Constants.hpp index 4b141896b..2f514c09a 100644 --- a/Linphone/tool/Constants.hpp +++ b/Linphone/tool/Constants.hpp @@ -69,7 +69,7 @@ public: static constexpr char PrivatePolicyUrl[] = "https://www.linphone.org/privacy-policy"; static constexpr char ContactUrl[] = "https://www.linphone.org/contact"; static constexpr char TranslationUrl[] = "https://weblate.linphone.org/projects/linphone-desktop/"; - + static constexpr int MaxMosaicParticipants = 6; // From 7, the mosaic quality will be limited to avoid useless computations @@ -99,6 +99,7 @@ public: Q_PROPERTY(int maxMosaicParticipants MEMBER MaxMosaicParticipants CONSTANT) Q_PROPERTY(QStringList reactionsList READ getReactionsList CONSTANT) + // For Webviews static constexpr char DefaultAssistantRegistrationUrl[] = "https://subscribe.linphone.org/register"; static constexpr char DefaultAssistantLoginUrl[] = "https://subscribe.linphone.org/login"; diff --git a/Linphone/view/App/Layout/MainLayout.qml b/Linphone/view/App/Layout/MainLayout.qml index 552b4f1ab..388fc3128 100644 --- a/Linphone/view/App/Layout/MainLayout.qml +++ b/Linphone/view/App/Layout/MainLayout.qml @@ -15,7 +15,8 @@ Item { id: mainItem property var callObj property bool settingsHidden: true - + property bool helpHidden: true + signal addAccountRequest() function goToNewCall() { @@ -140,6 +141,10 @@ Item { mainStackView.pop() mainItem.settingsHidden = true } + if (!mainItem.helpHidden) { + mainStackView.pop() + mainItem.helpHidden = true + } } } ColumnLayout { @@ -343,6 +348,10 @@ Item { text: qsTr("Paramètres") iconSource: AppIcons.settings onClicked: { + if (!mainItem.helpHidden) { + mainStackView.pop() + mainItem.helpHidden = true + } if (mainItem.settingsHidden) { mainStackView.push(settingsPageComponent) settingsButton.popup.close() @@ -363,6 +372,19 @@ Item { iconSize: 32 * DefaultStyle.dp text: qsTr("Aide") iconSource: AppIcons.question + onClicked: { + if (!mainItem.settingsHidden) { + mainStackView.pop() + mainItem.settingsHidden = true + } + if (mainItem.helpHidden) { + mainStackView.push(helpPageComponent) + settingsButton.popup.close() + mainItem.helpHidden = false + } else { + settingsButton.popup.close() + } + } } Rectangle { Layout.fillWidth: true @@ -408,6 +430,15 @@ Item { } } } + Component { + id: helpPageComponent + HelpPage { + onGoBack: { + mainStackView.pop() + mainItem.helpHidden = true + } + } + } Control.StackView { id: mainStackView property Transition noTransition: Transition { diff --git a/Linphone/view/App/Layout/Settings/DebugSettingsLayout.qml b/Linphone/view/App/Layout/Settings/DebugSettingsLayout.qml new file mode 100644 index 000000000..6be1516c3 --- /dev/null +++ b/Linphone/view/App/Layout/Settings/DebugSettingsLayout.qml @@ -0,0 +1,87 @@ + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as Control + +import Linphone +import SettingsCpp 1.0 +import UtilsCpp 1.0 + +GenericSettingsLayout { + Layout.fillWidth: true + Layout.fillHeight: true + id: mainItem + property string logsUrl + + Dialog { + id: deleteLogs + text: qsTr("Les traces de débogage seront supprimées. Souhaitez-vous continuer ?") + onAccepted: SettingsCpp.cleanLogs() + } + Dialog { + id: shareLogs + text: qsTr("Les traces de débogage ont été téléversées. Comment souhaitez-vous partager le lien ? ") + buttons: [ + Button { + text: qsTr("Presse-papier") + onClicked: { + shareLogs.close() + UtilsCpp.copyToClipboard(mainItem.logsUrl) + } + }, + Button { + text: qsTr("E-Mail") + onClicked: { + shareLogs.close() + if(!Qt.openUrlExternally( + 'mailto:' + encodeURIComponent(SettingsCpp.logsEmail) + + '?subject=' + encodeURIComponent(qsTr('Traces Linphone')) + + '&body=' + encodeURIComponent(mainItem.logsUrl) + )) + UtilsCpp.showInformationPopup(qsTr("Une erreur est survenue."), qsTr("Le partage par mail a échoué. Veuillez envoyer le lien %1 directement à l'adresse %2.").replace("%1",mainItem.logsUrl).replace("%2",SettingsCpp.logsEmail), false) + } + } + ] + } + Component { + id: debug + ColumnLayout { + spacing: 40 * DefaultStyle.dp + SwitchSetting { + titleText: qsTr("Activer les traces de débogage") + propertyName: "logsEnabled" + } + SwitchSetting { + titleText: qsTr("Activer les traces de débogage intégrales") + propertyName: "fullLogsEnabled" + } + MediumButton { + text: qsTr("Supprimer les traces") + onClicked: { + deleteLogs.open() + } + } + MediumButton { + text: qsTr("Partager les traces") + enabled: SettingsCpp.logsEnabled || SettingsCpp.fullLogsEnabled + onClicked: { + UtilsCpp.getMainWindow().showLoadingPopup(qsTr("Téléversement des traces en cours ...")) + SettingsCpp.sendLogs() + } + } + } + } + Connections { + target: SettingsCpp + onLogsUploadTerminated: { + UtilsCpp.getMainWindow().closeLoadingPopup() + if (status) { + mainItem.logsUrl = url + shareLogs.open() + } else { + UtilsCpp.showInformationPopup(qsTr("Une erreur est survenue."), qsTr("Le téléversement des traces a échoué. Vous pouvez partager les fichiers de trace directement depuis le répertoire suivant :") + SettingsCpp.logsFolder, false) + } + } + } + component: debug +} diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index b341d2aac..544381ec3 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -21,6 +21,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/App/Layout/Settings/GenericSettingsLayout.qml view/App/Layout/Settings/SecuritySettingsLayout.qml view/App/Layout/Settings/CallSettingsLayout.qml + view/App/Layout/Settings/DebugSettingsLayout.qml view/Item/Account/Accounts.qml @@ -51,6 +52,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/BusyIndicator.qml view/Item/Button.qml + view/Item/MediumButton.qml view/Item/Calendar.qml view/Item/CalendarComboBox.qml view/Item/Carousel.qml @@ -96,6 +98,8 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/Settings/SettingsFamily.qml view/Item/Settings/SwitchSetting.qml view/Item/Settings/ComboSetting.qml + + view/Item/Help/HelpIconLabelButton.qml view/Page/Login/LoginPage.qml view/Page/Login/RegisterPage.qml @@ -110,6 +114,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Page/Main/MeetingPage.qml view/Page/Main/SettingsPage.qml + view/Page/Main/HelpPage.qml view/Tool/utils.js # Prototypes diff --git a/Linphone/view/Item/Help/HelpIconLabelButton.qml b/Linphone/view/Item/Help/HelpIconLabelButton.qml new file mode 100644 index 000000000..4b2e054cf --- /dev/null +++ b/Linphone/view/Item/Help/HelpIconLabelButton.qml @@ -0,0 +1,51 @@ +import QtQuick 2.15 +import QtQuick.Effects +import QtQuick.Layouts +import Linphone + +MouseArea { + id: mainItem + property string iconSource + property string title + property string subTitle + property int iconSize: 32 * DefaultStyle.dp + hoverEnabled: true + width: content.implicitWidth + height: content.implicitHeight + cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor + RowLayout { + id: content + anchors.verticalCenter: parent.verticalCenter + anchors.fill:parent + EffectImage { + Layout.preferredWidth: mainItem.iconSize + Layout.preferredHeight: mainItem.iconSize + width: mainItem.iconSize + height: mainItem.iconSize + imageSource: mainItem.iconSource + colorizationColor: DefaultStyle.main1_500_main + } + ColumnLayout { + width: implicitWidth + height: implicitHeight + Layout.leftMargin: 16 * DefaultStyle.dp + Text { + Layout.fillWidth: true + text: mainItem.title + color: DefaultStyle.main2_600 + font: Typography.p2 + Layout.alignment: Qt.AlignBottom + verticalAlignment: Text.AlignBottom + } + Text { + Layout.alignment: Qt.AlignTop + verticalAlignment: Text.AlignTop + Layout.fillWidth: true + text: mainItem.subTitle + color: DefaultStyle.main2_500main + visible: subTitle.length > 0 + font: Typography.p1 + } + } + } +} diff --git a/Linphone/view/Item/MediumButton.qml b/Linphone/view/Item/MediumButton.qml new file mode 100644 index 000000000..51b210964 --- /dev/null +++ b/Linphone/view/Item/MediumButton.qml @@ -0,0 +1,17 @@ +import QtQuick 2.7 +import QtQuick.Controls.Basic 2.2 as Control +import QtQuick.Effects +import QtQuick.Layouts +import Linphone + +Button { + id: mainItem + textSize: Typography.b2.pixelSize + textWeight: Typography.b2.weight + color: DefaultStyle.main1_100 + textColor: DefaultStyle.main1_500_main + leftPadding: 16 * DefaultStyle.dp + rightPadding: 16 * DefaultStyle.dp + topPadding: 10 * DefaultStyle.dp + bottomPadding: 10 * DefaultStyle.dp +} diff --git a/Linphone/view/Page/Main/HelpPage.qml b/Linphone/view/Page/Main/HelpPage.qml new file mode 100644 index 000000000..0ce66764a --- /dev/null +++ b/Linphone/view/Page/Main/HelpPage.qml @@ -0,0 +1,126 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 +import ConstantsCpp 1.0 + +AbstractMainPage { + + id: mainItem + showDefaultItem: false + + signal goBack() + + leftPanelContent: ColumnLayout { + id: leftPanel + Layout.fillWidth: true + Layout.fillHeight: true + property int sideMargin: 45 * DefaultStyle.dp + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: leftPanel.sideMargin + Layout.rightMargin: leftPanel.sideMargin + Button { + Layout.preferredHeight: 24 * DefaultStyle.dp + Layout.preferredWidth: 24 * DefaultStyle.dp + icon.source: AppIcons.leftArrow + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + background: Item { + anchors.fill: parent + } + onClicked: { + mainItem.goBack() + } + } + Text { + text: qsTr("Aide") + color: DefaultStyle.main2_700 + font: Typography.h2 + } + Item { + Layout.fillWidth: true + } + } + Text { + Layout.leftMargin: leftPanel.sideMargin + Layout.rightMargin: leftPanel.sideMargin + Layout.topMargin: 41 * DefaultStyle.dp + Layout.fillWidth: true + text: qsTr("À propos de Linphone") + color: DefaultStyle.main2_600 + font: Typography.h3m + } + ColumnLayout { + Layout.fillWidth: true + Layout.leftMargin: leftPanel.sideMargin + Layout.rightMargin: leftPanel.sideMargin + Layout.topMargin: 24 * DefaultStyle.dp + spacing: 32 * DefaultStyle.dp + HelpIconLabelButton { + Layout.fillWidth: true + iconSource: AppIcons.detective + title: qsTr("Règles de confidentialité") + subTitle: qsTr("Comment Linphone récolte et utilise les informations") + onClicked: { + rightPanelStackView.clear() + Qt.openUrlExternally(ConstantsCpp.PrivatePolicyUrl) + } + } + HelpIconLabelButton { + Layout.fillWidth: true + iconSource: AppIcons.info + title: qsTr("Version") + subTitle: qsTr("1.0") + onClicked: {} + } + HelpIconLabelButton { + Layout.fillWidth: true + iconSource: AppIcons.license + title: qsTr("Licences GPLv3") + subTitle: (copyrightRangeDate || applicationVendor ? '\u00A9 ': '') + (copyrightRangeDate ? copyrightRangeDate : '')+ (applicationVendor ? ' ' + applicationVendor : '') + onClicked: { + rightPanelStackView.clear() + Qt.openUrlExternally(applicationLicenceUrl) + } + } + HelpIconLabelButton { + Layout.fillWidth: true + iconSource: AppIcons.world + title: qsTr("Contribuer à la traduction de Linphone") + onClicked: { + rightPanelStackView.clear() + Qt.openUrlExternally(ConstantsCpp.TranslationUrl) + } + } + } + Text { + Layout.leftMargin: leftPanel.sideMargin + Layout.rightMargin: leftPanel.sideMargin + Layout.topMargin: 32 * DefaultStyle.dp + Layout.fillWidth: true + text: qsTr("À propos de Linphone") + color: DefaultStyle.main2_600 + font: Typography.h3m + } + HelpIconLabelButton { + id: troubleShooting + Layout.fillWidth: true + Layout.leftMargin: leftPanel.sideMargin + Layout.rightMargin: leftPanel.sideMargin + Layout.topMargin: 24 * DefaultStyle.dp + iconSource: AppIcons.debug + title: qsTr("Dépannage") + onClicked: { + rightPanelStackView.clear() + rightPanelStackView.push("qrc:/Linphone/view/App/Layout/Settings/DebugSettingsLayout.qml", { titleText: troubleShooting.title }) + } + } + Item { + Layout.fillHeight: true + } + } +} diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index 571632229..a7d27dec7 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -94,5 +94,8 @@ QtObject { property string videoconferenceSelected: "image://internal/video-conference-selected.svg" property string switchOn: "image://internal/switch-on.svg" property string switchOff: "image://internal/switch-off.svg" - + property string license: "image://internal/license.svg" + property string debug: "image://internal/debug.svg" + property string world: "image://internal/world.svg" + property string detective: "image://internal/detective.svg" } diff --git a/Linphone/view/Style/Typography.qml b/Linphone/view/Style/Typography.qml index a5ceee20c..f34b104be 100644 --- a/Linphone/view/Style/Typography.qml +++ b/Linphone/view/Style/Typography.qml @@ -38,4 +38,11 @@ QtObject { weight: 400 * DefaultStyle.dp }) + // Bouton/B2 - Medium Bouton + property font b2: Qt.font( { + family: DefaultStyle.defaultFont, + pixelSize: 15 * DefaultStyle.dp, + weight: 600 * DefaultStyle.dp + }) + }