mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 11:28:07 +00:00
Remove storing core from GUI on fields. Core should never be stored in GUI because they are managed by CPP and not QML. Fix crashes on account settings. Add missing exception verbosing.
1133 lines
43 KiB
C++
1133 lines
43 KiB
C++
/*
|
|
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
|
*
|
|
* This file is part of linphone-desktop
|
|
* (see https://www.linphone.org).
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "tool/LinphoneEnums.hpp"
|
|
|
|
#include "App.hpp"
|
|
|
|
#include <QAction>
|
|
#include <QCoreApplication>
|
|
#include <QDirIterator>
|
|
#include <QFileSelector>
|
|
#include <QFontDatabase>
|
|
#include <QGuiApplication>
|
|
#include <QLibraryInfo>
|
|
#include <QMenu>
|
|
#include <QProcessEnvironment>
|
|
#include <QQmlComponent>
|
|
#include <QQmlContext>
|
|
#include <QQmlFileSelector>
|
|
#include <QQuickWindow>
|
|
#include <QSystemTrayIcon>
|
|
#include <QTimer>
|
|
#include <QTranslator>
|
|
|
|
#include "core/account/AccountCore.hpp"
|
|
#include "core/account/AccountDeviceGui.hpp"
|
|
#include "core/account/AccountDeviceProxy.hpp"
|
|
#include "core/address-books/carddav/CarddavGui.hpp"
|
|
#include "core/address-books/carddav/CarddavProxy.hpp"
|
|
#include "core/address-books/ldap/LdapGui.hpp"
|
|
#include "core/address-books/ldap/LdapProxy.hpp"
|
|
#include "core/call-history/CallHistoryProxy.hpp"
|
|
#include "core/call/CallCore.hpp"
|
|
#include "core/call/CallGui.hpp"
|
|
#include "core/call/CallList.hpp"
|
|
#include "core/call/CallProxy.hpp"
|
|
#include "core/camera/CameraGui.hpp"
|
|
#include "core/conference/ConferenceGui.hpp"
|
|
#include "core/conference/ConferenceInfoGui.hpp"
|
|
#include "core/conference/ConferenceInfoProxy.hpp"
|
|
#include "core/fps-counter/FPSCounter.hpp"
|
|
#include "core/friend/FriendCore.hpp"
|
|
#include "core/friend/FriendGui.hpp"
|
|
#include "core/logger/QtLogger.hpp"
|
|
#include "core/login/LoginPage.hpp"
|
|
#include "core/notifier/Notifier.hpp"
|
|
#include "core/participant/ParticipantDeviceProxy.hpp"
|
|
#include "core/participant/ParticipantGui.hpp"
|
|
#include "core/participant/ParticipantProxy.hpp"
|
|
#include "core/payload-type/PayloadTypeCore.hpp"
|
|
#include "core/payload-type/PayloadTypeGui.hpp"
|
|
#include "core/payload-type/PayloadTypeProxy.hpp"
|
|
#include "core/phone-number/PhoneNumber.hpp"
|
|
#include "core/phone-number/PhoneNumberProxy.hpp"
|
|
#include "core/register/RegisterPage.hpp"
|
|
#include "core/screen/ScreenList.hpp"
|
|
#include "core/screen/ScreenProxy.hpp"
|
|
#include "core/search/MagicSearchProxy.hpp"
|
|
#include "core/setting/SettingsCore.hpp"
|
|
#include "core/singleapplication/singleapplication.h"
|
|
#include "core/timezone/TimeZoneProxy.hpp"
|
|
#include "core/variant/VariantList.hpp"
|
|
#include "core/videoSource/VideoSourceDescriptorGui.hpp"
|
|
#include "model/object/VariantObject.hpp"
|
|
#include "model/tool/ToolModel.hpp"
|
|
#include "tool/Constants.hpp"
|
|
#include "tool/EnumsToString.hpp"
|
|
#include "tool/Utils.hpp"
|
|
#include "tool/native/DesktopTools.hpp"
|
|
#include "tool/providers/AvatarProvider.hpp"
|
|
#include "tool/providers/ImageProvider.hpp"
|
|
#include "tool/providers/ScreenProvider.hpp"
|
|
#include "tool/request/CallbackHelper.hpp"
|
|
#include "tool/request/RequestDialog.hpp"
|
|
#include "tool/thread/Thread.hpp"
|
|
|
|
DEFINE_ABSTRACT_OBJECT(App)
|
|
|
|
#ifdef Q_OS_LINUX
|
|
const QString AutoStartDirectory(QDir::homePath().append(QStringLiteral("/.config/autostart/")));
|
|
const QString ApplicationsDirectory(QDir::homePath().append(QStringLiteral("/.local/share/applications/")));
|
|
const QString IconsDirectory(QDir::homePath().append(QStringLiteral("/.local/share/icons/hicolor/scalable/apps/")));
|
|
#elif defined(Q_OS_MACOS)
|
|
const QString OsascriptExecutable(QStringLiteral("osascript"));
|
|
#else
|
|
const QString
|
|
AutoStartSettingsFilePath(QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"));
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Autostart
|
|
// -----------------------------------------------------------------------------
|
|
|
|
#ifdef Q_OS_LINUX
|
|
bool App::autoStartEnabled() {
|
|
const QString confPath(AutoStartDirectory + EXECUTABLE_NAME ".desktop");
|
|
QFile file(confPath);
|
|
if (!QDir(AutoStartDirectory).exists() || !file.exists()) return false;
|
|
if (!file.open(QFile::ReadOnly)) {
|
|
qWarning() << "Unable to open autostart file in read only: `" << confPath << "`.";
|
|
return false;
|
|
}
|
|
|
|
// Check if installation is done via Flatpak, AppImage, or classic package
|
|
// in order to check if there is a correct exec path for autostart
|
|
|
|
QString exec = getApplicationPath();
|
|
|
|
QTextStream in(&file);
|
|
QString autoStartConf = in.readAll();
|
|
|
|
int index = -1;
|
|
// check if the Exec part of the autostart ini file not corresponding to our executable (old desktop entry with
|
|
// wrong version in filename)
|
|
if (autoStartConf.indexOf(QString("Exec=" + exec + " ")) <
|
|
0) { // On autostart, there is the option --minimized so there is one space.
|
|
// replace file
|
|
setAutoStart(true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#elif defined(Q_OS_MACOS)
|
|
static inline QString getMacOsBundlePath() {
|
|
QDir dir(QCoreApplication::applicationDirPath());
|
|
if (dir.dirName() != QLatin1String("MacOS")) return QString();
|
|
|
|
dir.cdUp();
|
|
dir.cdUp();
|
|
|
|
QString path(dir.path());
|
|
if (path.length() > 0 && path.right(1) == "/") path.chop(1);
|
|
return path;
|
|
}
|
|
|
|
static inline QString getMacOsBundleName() {
|
|
return QFileInfo(getMacOsBundlePath()).baseName();
|
|
}
|
|
|
|
bool App::autoStartEnabled() {
|
|
const QByteArray expectedWord(getMacOsBundleName().toUtf8());
|
|
if (expectedWord.isEmpty()) {
|
|
qInfo() << QStringLiteral("Application is not installed. Autostart unavailable.");
|
|
return false;
|
|
}
|
|
|
|
QProcess process;
|
|
process.start(OsascriptExecutable,
|
|
{"-e", "tell application \"System Events\" to get the name of every login item"});
|
|
if (!process.waitForFinished()) {
|
|
qWarning() << QStringLiteral("Unable to execute properly: `%1` (%2).")
|
|
.arg(OsascriptExecutable)
|
|
.arg(process.errorString());
|
|
return false;
|
|
}
|
|
|
|
// TODO: Move in utils?
|
|
const QByteArray buf(process.readAll());
|
|
for (const char *p = buf.data(), *word = p, *end = p + buf.length(); p <= end; ++p) {
|
|
switch (*p) {
|
|
case ' ':
|
|
case '\r':
|
|
case '\n':
|
|
case '\t':
|
|
case '\0':
|
|
if (word != p) {
|
|
if (!strncmp(word, expectedWord, size_t(p - word))) return true;
|
|
word = p + 1;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#else
|
|
bool App::autoStartEnabled() {
|
|
return QSettings(AutoStartSettingsFilePath, QSettings::NativeFormat).value(EXECUTABLE_NAME).isValid();
|
|
}
|
|
#endif // ifdef Q_OS_LINUX
|
|
|
|
#ifdef Q_OS_LINUX
|
|
|
|
void App::setAutoStart(bool enabled) {
|
|
if (enabled == mAutoStart) return;
|
|
|
|
QDir dir(AutoStartDirectory);
|
|
if (!dir.exists() && !dir.mkpath(AutoStartDirectory)) {
|
|
qWarning() << QStringLiteral("Unable to build autostart dir path: `%1`.").arg(AutoStartDirectory);
|
|
return;
|
|
}
|
|
|
|
const QString confPath(AutoStartDirectory + EXECUTABLE_NAME ".desktop");
|
|
if (generateDesktopFile(confPath, !enabled, true)) {
|
|
mAutoStart = enabled;
|
|
}
|
|
}
|
|
|
|
#elif defined(Q_OS_MACOS)
|
|
|
|
void App::setAutoStart(bool enabled) {
|
|
if (enabled == mAutoStart) return;
|
|
|
|
if (getMacOsBundlePath().isEmpty()) {
|
|
qWarning() << QStringLiteral("Application is not installed. Unable to change autostart state.");
|
|
return;
|
|
}
|
|
|
|
if (enabled)
|
|
QProcess::execute(OsascriptExecutable,
|
|
{"-e", "tell application \"System Events\" to make login item at end with properties"
|
|
"{ path: \"" +
|
|
getMacOsBundlePath() + "\", hidden: false }"});
|
|
else
|
|
QProcess::execute(OsascriptExecutable, {"-e", "tell application \"System Events\" to delete login item \"" +
|
|
getMacOsBundleName() + "\""});
|
|
|
|
mAutoStart = enabled;
|
|
}
|
|
|
|
#else
|
|
|
|
void App::setAutoStart(bool enabled) {
|
|
QSettings settings(AutoStartSettingsFilePath, QSettings::NativeFormat);
|
|
QString parameters;
|
|
if (!mSettings->getExitOnClose()) parameters = " --minimized";
|
|
if (enabled) settings.setValue(EXECUTABLE_NAME, QDir::toNativeSeparators(applicationFilePath()) + parameters);
|
|
else settings.remove(EXECUTABLE_NAME);
|
|
|
|
mAutoStart = enabled;
|
|
}
|
|
|
|
#endif // ifdef Q_OS_LINUX
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// End Autostart
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
App::App(int &argc, char *argv[])
|
|
: SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
|
|
// Do not use APPLICATION_NAME here.
|
|
// The EXECUTABLE_NAME will be used in qt standard paths. It's our goal.
|
|
QThread::currentThread()->setPriority(QThread::HighPriority);
|
|
QCoreApplication::setApplicationName(EXECUTABLE_NAME);
|
|
QApplication::setOrganizationDomain(EXECUTABLE_NAME);
|
|
QCoreApplication::setApplicationVersion(APPLICATION_SEMVER);
|
|
// If not OpenGL, createRender is never call.
|
|
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
|
|
setWindowIcon(QIcon(Constants::WindowIconPath));
|
|
lInfo() << "Loading Fonts";
|
|
QDirIterator it(":/font/", QDirIterator::Subdirectories);
|
|
while (it.hasNext()) {
|
|
QString ttf = it.next();
|
|
// lDebug()<< ttf;
|
|
auto id = QFontDatabase::addApplicationFont(ttf);
|
|
}
|
|
|
|
//-------------------
|
|
mLinphoneThread = new Thread(this);
|
|
|
|
init();
|
|
lInfo() << QStringLiteral("Starting application " APPLICATION_NAME " (bin: " EXECUTABLE_NAME
|
|
"). Version:%1 Os:%2 Qt:%3")
|
|
.arg(applicationVersion())
|
|
.arg(Utils::getOsProduct())
|
|
.arg(qVersion());
|
|
|
|
mCurrentDate = QDate::currentDate();
|
|
mAutoStart = autoStartEnabled();
|
|
mDateUpdateTimer.setInterval(60000);
|
|
mDateUpdateTimer.setSingleShot(false);
|
|
connect(&mDateUpdateTimer, &QTimer::timeout, this, [this] {
|
|
auto date = QDate::currentDate();
|
|
if (date != mCurrentDate) {
|
|
mCurrentDate = date;
|
|
emit currentDateChanged();
|
|
}
|
|
});
|
|
mDateUpdateTimer.start();
|
|
}
|
|
|
|
App::~App() {
|
|
}
|
|
|
|
void App::setSelf(QSharedPointer<App>(me)) {
|
|
mCoreModelConnection = QSharedPointer<SafeConnection<App, CoreModel>>(
|
|
new SafeConnection<App, CoreModel>(me, CoreModel::getInstance()), &QObject::deleteLater);
|
|
mCoreModelConnection->makeConnectToModel(&CoreModel::callCreated,
|
|
[this](const std::shared_ptr<linphone::Call> &call) {
|
|
if (call->getDir() == linphone::Call::Dir::Incoming) return;
|
|
auto callCore = CallCore::create(call);
|
|
mCoreModelConnection->invokeToCore([this, callCore] {
|
|
auto callGui = new CallGui(callCore);
|
|
auto win = getCallsWindow(QVariant::fromValue(callGui));
|
|
Utils::smartShowWindow(win);
|
|
auto mainwin = getMainWindow();
|
|
QMetaObject::invokeMethod(mainwin, "callCreated");
|
|
lDebug() << "App : call created" << callGui;
|
|
});
|
|
});
|
|
mCoreModelConnection->makeConnectToModel(&CoreModel::requestRestart, [this]() {
|
|
mCoreModelConnection->invokeToCore([this]() {
|
|
lInfo() << log().arg("Restarting");
|
|
restart();
|
|
});
|
|
});
|
|
mCoreModelConnection->makeConnectToModel(&CoreModel::requestFetchConfig, [this](QString path) {
|
|
mCoreModelConnection->invokeToCore([this, path]() {
|
|
auto callback = [this, path]() {
|
|
RequestDialog *obj = new RequestDialog(
|
|
tr("Voulez-vous télécharger et appliquer la configuration depuis cette adresse ?"), path);
|
|
connect(obj, &RequestDialog::result, this, [this, obj, path](int result) {
|
|
if (result == 1) {
|
|
mCoreModelConnection->invokeToModel(
|
|
[this, path]() { CoreModel::getInstance()->setFetchConfig(path); });
|
|
} else if (result == 0) {
|
|
mCoreModelConnection->invokeToModel([]() { CliModel::getInstance()->resetProcesses(); });
|
|
}
|
|
obj->deleteLater();
|
|
});
|
|
QMetaObject::invokeMethod(getMainWindow(), "showConfirmationPopup", QVariant::fromValue(obj));
|
|
};
|
|
if (!getMainWindow()) { // Delay
|
|
connect(this, &App::mainWindowChanged, this, callback, Qt::SingleShotConnection);
|
|
} else {
|
|
callback();
|
|
}
|
|
});
|
|
});
|
|
mCoreModelConnection->makeConnectToModel(
|
|
&CoreModel::globalStateChanged,
|
|
[this](const std::shared_ptr<linphone::Core> &core, linphone::GlobalState gstate, const std::string &message) {
|
|
if (gstate == linphone::GlobalState::On) {
|
|
mCoreModelConnection->invokeToCore([this] { setCoreStarted(true); });
|
|
}
|
|
});
|
|
mCoreModelConnection->makeConnectToModel(&CoreModel::authenticationRequested, &App::onAuthenticationRequested);
|
|
|
|
// Synchronize state for because linphoneCore was ran before any connections.
|
|
mCoreModelConnection->invokeToModel([this]() {
|
|
auto state = CoreModel::getInstance()->getCore()->getGlobalState();
|
|
mCoreModelConnection->invokeToCore([this, state] { setCoreStarted(state == linphone::GlobalState::On); });
|
|
});
|
|
//---------------------------------------------------------------------------------------------
|
|
mCliModelConnection = QSharedPointer<SafeConnection<App, CliModel>>(
|
|
new SafeConnection<App, CliModel>(me, CliModel::getInstance()), &QObject::deleteLater);
|
|
mCliModelConnection->makeConnectToCore(&App::receivedMessage, [this](int, const QByteArray &byteArray) {
|
|
QString command(byteArray);
|
|
if (command.isEmpty()) {
|
|
lDebug() << log().arg("Check with CliModel for commands");
|
|
mCliModelConnection->invokeToModel([]() { CliModel::getInstance()->runProcess(); });
|
|
} else {
|
|
qInfo() << QStringLiteral("Received command from other application: `%1`.").arg(command);
|
|
mCliModelConnection->invokeToModel([command]() { CliModel::getInstance()->executeCommand(command); });
|
|
}
|
|
});
|
|
mCliModelConnection->makeConnectToModel(&CliModel::showMainWindow, [this]() {
|
|
mCliModelConnection->invokeToCore([this]() { Utils::smartShowWindow(getMainWindow()); });
|
|
});
|
|
}
|
|
|
|
App *App::getInstance() {
|
|
return dynamic_cast<App *>(QApplication::instance());
|
|
}
|
|
|
|
QThread *App::getLinphoneThread() {
|
|
return App::getInstance()->mLinphoneThread;
|
|
}
|
|
|
|
Notifier *App::getNotifier() const {
|
|
return mNotifier;
|
|
}
|
|
//-----------------------------------------------------------
|
|
// Initializations
|
|
//-----------------------------------------------------------
|
|
|
|
void App::init() {
|
|
// Console Commands
|
|
createCommandParser();
|
|
mParser->parse(this->arguments());
|
|
// TODO : Update languages for command translations.
|
|
|
|
createCommandParser(); // Recreate parser in order to use translations from config.
|
|
mParser->process(*this);
|
|
|
|
if (mParser->isSet("help")) {
|
|
mParser->showHelp();
|
|
::exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
if (mParser->isSet("version")) {
|
|
mParser->showVersion();
|
|
::exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
if (!mLinphoneThread->isRunning()) {
|
|
lInfo() << log().arg("Starting Thread");
|
|
mLinphoneThread->start();
|
|
while (!mLinphoneThread->getThreadId()) // Wait for running thread
|
|
QThread::msleep(100);
|
|
}
|
|
|
|
lInfo() << log().arg("Display server : %1").arg(platformName());
|
|
}
|
|
|
|
void App::initCore() {
|
|
// Core. Manage the logger so it must be instantiate at first.
|
|
CoreModel::create("", mLinphoneThread);
|
|
if (mParser->isSet("verbose")) QtLogger::enableVerbose(true);
|
|
if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true);
|
|
QMetaObject::invokeMethod(
|
|
mLinphoneThread->getThreadId(),
|
|
[this, settings = mSettings]() mutable {
|
|
lInfo() << log().arg("Updating downloaded codec files");
|
|
ToolModel::updateCodecs(); // removing codec updates suffic (.in) before the core is created.
|
|
lInfo() << log().arg("Starting Core");
|
|
CoreModel::getInstance()->start();
|
|
ToolModel::loadDownloadedCodecs();
|
|
lDebug() << log().arg("Creating SettingsModel");
|
|
SettingsModel::create();
|
|
lDebug() << log().arg("Creating SettingsCore");
|
|
if (!settings) settings = SettingsCore::create();
|
|
lDebug() << log().arg("Creating Ui");
|
|
QMetaObject::invokeMethod(App::getInstance()->thread(), [this, settings] {
|
|
// Initialize DestopTools here to have logs into files in case of errors.
|
|
DesktopTools::init();
|
|
// QML
|
|
mEngine = new QQmlApplicationEngine(this);
|
|
assert(mEngine);
|
|
// Provide `+custom` folders for custom components and `5.9` for old components.
|
|
QStringList selectors("custom");
|
|
const QVersionNumber &version = QLibraryInfo::version();
|
|
if (version.majorVersion() == 5 && version.minorVersion() == 9) selectors.push_back("5.9");
|
|
auto selector = new QQmlFileSelector(mEngine, mEngine);
|
|
selector->setExtraSelectors(selectors);
|
|
lInfo() << log().arg("Activated selectors:") << selector->selector()->allSelectors();
|
|
|
|
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());
|
|
mEngine->addImageProvider(ScreenProvider::ProviderId, new ScreenProvider());
|
|
mEngine->addImageProvider(WindowProvider::ProviderId, new WindowProvider());
|
|
mEngine->addImageProvider(WindowIconProvider::ProviderId, new WindowIconProvider());
|
|
|
|
// Enable notifications.
|
|
mNotifier = new Notifier(mEngine);
|
|
mEngine->setObjectOwnership(settings.get(), QQmlEngine::CppOwnership);
|
|
mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
|
|
if (!mAccountList) setAccountList(AccountList::create());
|
|
else mAccountList->lUpdate();
|
|
if (!mCallList) setCallList(CallList::create());
|
|
else mCallList->lUpdate();
|
|
if (!mSettings) {
|
|
mSettings = settings;
|
|
setLocale(settings->getConfigLocale());
|
|
setAutoStart(settings->getAutoStart());
|
|
setQuitOnLastWindowClosed(settings->getExitOnClose());
|
|
mEngine->setObjectOwnership(mSettings.get(), QQmlEngine::CppOwnership);
|
|
|
|
connect(mSettings.get(), &SettingsCore::exitOnCloseChanged, this, &App::onExitOnCloseChanged,
|
|
Qt::UniqueConnection);
|
|
QObject::connect(mSettings.get(), &SettingsCore::autoStartChanged, [this]() {
|
|
mustBeInMainThread(log().arg(Q_FUNC_INFO));
|
|
setAutoStart(mSettings->getAutoStart());
|
|
});
|
|
QObject::connect(mSettings.get(), &SettingsCore::configLocaleChanged, [this]() {
|
|
mustBeInMainThread(log().arg(Q_FUNC_INFO));
|
|
if (mSettings) setLocale(mSettings->getConfigLocale());
|
|
});
|
|
connect(mSettings.get(), &SettingsCore::exitOnCloseChanged, this, &App::onExitOnCloseChanged,
|
|
Qt::UniqueConnection);
|
|
} else {
|
|
setLocale(settings->getConfigLocale());
|
|
setAutoStart(settings->getAutoStart());
|
|
setQuitOnLastWindowClosed(settings->getExitOnClose());
|
|
}
|
|
const QUrl url("qrc:/qt/qml/Linphone/view/Page/Window/Main/MainWindow.qml");
|
|
QObject::connect(
|
|
mEngine, &QQmlApplicationEngine::objectCreated, this,
|
|
[this, url](QObject *obj, const QUrl &objUrl) {
|
|
if (url == objUrl) {
|
|
if (!obj) {
|
|
lCritical() << log().arg("MainWindow.qml couldn't be load. The app will exit");
|
|
exit(-1);
|
|
}
|
|
auto window = qobject_cast<QQuickWindow *>(obj);
|
|
setMainWindow(window);
|
|
#ifndef __APPLE__
|
|
// Enable TrayIconSystem.
|
|
if (!QSystemTrayIcon::isSystemTrayAvailable())
|
|
qWarning("System tray not found on this system.");
|
|
else setSysTrayIcon();
|
|
#endif // ifndef __APPLE__
|
|
static bool firstOpen = true;
|
|
if (!firstOpen || !mParser->isSet("minimized")) {
|
|
lDebug() << log().arg("Openning window");
|
|
window->show();
|
|
} else lInfo() << log().arg("Stay minimized");
|
|
firstOpen = false;
|
|
}
|
|
},
|
|
Qt::QueuedConnection);
|
|
|
|
Utils::checkDownloadedCodecsUpdates();
|
|
|
|
mEngine->load(url);
|
|
});
|
|
},
|
|
Qt::BlockingQueuedConnection);
|
|
}
|
|
|
|
void App::initCppInterfaces() {
|
|
qmlRegisterSingletonType<App>(Constants::MainQmlUri, 1, 0, "AppCpp",
|
|
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return App::getInstance(); });
|
|
qmlRegisterSingletonType<LoginPage>(
|
|
Constants::MainQmlUri, 1, 0, "LoginPageCpp", [](QQmlEngine *engine, QJSEngine *) -> QObject * {
|
|
static auto loginPage = new LoginPage();
|
|
App::getInstance()->mEngine->setObjectOwnership(loginPage, QQmlEngine::CppOwnership);
|
|
return loginPage;
|
|
});
|
|
qmlRegisterSingletonType<RegisterPage>(
|
|
Constants::MainQmlUri, 1, 0, "RegisterPageCpp", [](QQmlEngine *engine, QJSEngine *) -> QObject * {
|
|
static RegisterPage *registerPage = new RegisterPage();
|
|
App::getInstance()->mEngine->setObjectOwnership(registerPage, QQmlEngine::CppOwnership);
|
|
return registerPage;
|
|
});
|
|
qmlRegisterSingletonType<Constants>(
|
|
"ConstantsCpp", 1, 0, "ConstantsCpp",
|
|
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new Constants(engine); });
|
|
qmlRegisterSingletonType<Utils>("UtilsCpp", 1, 0, "UtilsCpp",
|
|
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new Utils(engine); });
|
|
qmlRegisterSingletonType<DesktopTools>(
|
|
"DesktopToolsCpp", 1, 0, "DesktopToolsCpp",
|
|
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new DesktopTools(engine); });
|
|
qmlRegisterSingletonType<EnumsToString>(
|
|
"EnumsToStringCpp", 1, 0, "EnumsToStringCpp",
|
|
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new EnumsToString(engine); });
|
|
|
|
qmlRegisterSingletonType<SettingsCore>(
|
|
"SettingsCpp", 1, 0, "SettingsCpp",
|
|
[this](QQmlEngine *engine, QJSEngine *) -> QObject * { return mSettings.get(); });
|
|
|
|
qmlRegisterType<PhoneNumberProxy>(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy");
|
|
qmlRegisterType<VariantObject>(Constants::MainQmlUri, 1, 0, "VariantObject");
|
|
qmlRegisterType<VariantList>(Constants::MainQmlUri, 1, 0, "VariantList");
|
|
|
|
qmlRegisterType<ParticipantProxy>(Constants::MainQmlUri, 1, 0, "ParticipantProxy");
|
|
qmlRegisterType<ParticipantGui>(Constants::MainQmlUri, 1, 0, "ParticipantGui");
|
|
qmlRegisterType<ConferenceInfoProxy>(Constants::MainQmlUri, 1, 0, "ConferenceInfoProxy");
|
|
qmlRegisterType<ConferenceInfoGui>(Constants::MainQmlUri, 1, 0, "ConferenceInfoGui");
|
|
|
|
qmlRegisterType<PhoneNumberProxy>(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy");
|
|
qmlRegisterUncreatableType<PhoneNumber>(Constants::MainQmlUri, 1, 0, "PhoneNumber", QLatin1String("Uncreatable"));
|
|
qmlRegisterType<AccountGui>(Constants::MainQmlUri, 1, 0, "AccountGui");
|
|
qmlRegisterType<AccountProxy>(Constants::MainQmlUri, 1, 0, "AccountProxy");
|
|
qmlRegisterType<AccountDeviceProxy>(Constants::MainQmlUri, 1, 0, "AccountDeviceProxy");
|
|
qmlRegisterType<AccountDeviceGui>(Constants::MainQmlUri, 1, 0, "AccountDeviceGui");
|
|
qmlRegisterUncreatableType<AccountCore>(Constants::MainQmlUri, 1, 0, "AccountCore", QLatin1String("Uncreatable"));
|
|
qmlRegisterUncreatableType<CallCore>(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable"));
|
|
qmlRegisterType<CallHistoryProxy>(Constants::MainQmlUri, 1, 0, "CallHistoryProxy");
|
|
qmlRegisterType<CallGui>(Constants::MainQmlUri, 1, 0, "CallGui");
|
|
qmlRegisterType<CallProxy>(Constants::MainQmlUri, 1, 0, "CallProxy");
|
|
qmlRegisterUncreatableType<ConferenceCore>(Constants::MainQmlUri, 1, 0, "ConferenceCore",
|
|
QLatin1String("Uncreatable"));
|
|
qmlRegisterType<ConferenceGui>(Constants::MainQmlUri, 1, 0, "ConferenceGui");
|
|
qmlRegisterType<FriendGui>(Constants::MainQmlUri, 1, 0, "FriendGui");
|
|
qmlRegisterUncreatableType<FriendCore>(Constants::MainQmlUri, 1, 0, "FriendCore", QLatin1String("Uncreatable"));
|
|
qmlRegisterType<MagicSearchProxy>(Constants::MainQmlUri, 1, 0, "MagicSearchProxy");
|
|
qmlRegisterType<MagicSearchList>(Constants::MainQmlUri, 1, 0, "MagicSearchList");
|
|
qmlRegisterType<CameraGui>(Constants::MainQmlUri, 1, 0, "CameraGui");
|
|
qmlRegisterType<FPSCounter>(Constants::MainQmlUri, 1, 0, "FPSCounter");
|
|
|
|
qmlRegisterType<TimeZoneProxy>(Constants::MainQmlUri, 1, 0, "TimeZoneProxy");
|
|
|
|
qmlRegisterType<ParticipantDeviceGui>(Constants::MainQmlUri, 1, 0, "ParticipantDeviceGui");
|
|
qmlRegisterType<ParticipantDeviceProxy>(Constants::MainQmlUri, 1, 0, "ParticipantDeviceProxy");
|
|
|
|
qmlRegisterUncreatableType<ScreenList>(Constants::MainQmlUri, 1, 0, "ScreenList", QLatin1String("Uncreatable"));
|
|
qmlRegisterType<ScreenProxy>(Constants::MainQmlUri, 1, 0, "ScreenProxy");
|
|
|
|
qmlRegisterUncreatableType<VideoSourceDescriptorCore>(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorCore",
|
|
QLatin1String("Uncreatable"));
|
|
qmlRegisterType<VideoSourceDescriptorGui>(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorGui");
|
|
|
|
qmlRegisterUncreatableType<RequestDialog>(Constants::MainQmlUri, 1, 0, "RequestDialog",
|
|
QLatin1String("Uncreatable"));
|
|
qmlRegisterType<LdapGui>(Constants::MainQmlUri, 1, 0, "LdapGui");
|
|
qmlRegisterType<LdapProxy>(Constants::MainQmlUri, 1, 0, "LdapProxy");
|
|
qmlRegisterType<CarddavGui>(Constants::MainQmlUri, 1, 0, "CarddavGui");
|
|
qmlRegisterType<CarddavProxy>(Constants::MainQmlUri, 1, 0, "CarddavProxy");
|
|
qmlRegisterType<PayloadTypeGui>(Constants::MainQmlUri, 1, 0, "PayloadTypeGui");
|
|
qmlRegisterType<PayloadTypeProxy>(Constants::MainQmlUri, 1, 0, "PayloadTypeProxy");
|
|
qmlRegisterType<PayloadTypeCore>(Constants::MainQmlUri, 1, 0, "PayloadTypeCore");
|
|
qmlRegisterType<PayloadTypeCore>(Constants::MainQmlUri, 1, 0, "DownloadablePayloadTypeCore");
|
|
|
|
LinphoneEnums::registerMetaTypes();
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
void App::clean() {
|
|
mDateUpdateTimer.stop();
|
|
if (mEngine) {
|
|
mEngine->clearComponentCache();
|
|
mEngine->clearSingletons();
|
|
delete mEngine;
|
|
}
|
|
mEngine = nullptr;
|
|
mSettings = nullptr; // Need it because of SettingsModel singleton for letting thread to remove it.
|
|
// Wait 500ms to let time for log te be stored.
|
|
// mNotifier destroyed in mEngine deletion as it is its parent
|
|
// Hack: exec() must be used to process cleaning QSharedPointers memory. processEvents doesn't work.
|
|
QTimer::singleShot(500, [this]() { exit(0); });
|
|
exec();
|
|
if (mLinphoneThread) {
|
|
mLinphoneThread->exit();
|
|
mLinphoneThread->wait();
|
|
delete mLinphoneThread;
|
|
}
|
|
}
|
|
void App::restart() {
|
|
mCoreModelConnection->invokeToModel([this]() {
|
|
CoreModel::getInstance()->getCore()->stop();
|
|
mCoreModelConnection->invokeToCore([this]() {
|
|
closeCallsWindow();
|
|
setMainWindow(nullptr);
|
|
mEngine->clearComponentCache();
|
|
mEngine->clearSingletons();
|
|
delete mEngine;
|
|
mEngine = nullptr;
|
|
// if (mSettings) mSettings.reset();
|
|
initCore();
|
|
// Retrieve self from current Core/Model connection and reset Qt connections.
|
|
// auto oldConnection = mCoreModelConnection;
|
|
// oldConnection->mCore.lock();
|
|
// auto me = oldConnection->mCore.mQData;
|
|
// setSelf(me);
|
|
// oldConnection->mCore.unlock();
|
|
exit((int)StatusCode::gRestartCode);
|
|
});
|
|
});
|
|
}
|
|
void App::createCommandParser() {
|
|
if (!mParser) delete mParser;
|
|
|
|
mParser = new QCommandLineParser();
|
|
mParser->setApplicationDescription(tr("A free (libre) SIP video-phone."));
|
|
mParser->addPositionalArgument(
|
|
"command", tr("Send an order to the application towards a command line").replace("%1", APPLICATION_NAME),
|
|
"[command]");
|
|
mParser->addOptions({
|
|
{{"h", "help"}, tr("Show this help")},
|
|
//{"cli-help", tr("commandLineOptionCliHelp").replace("%1", APPLICATION_NAME)},
|
|
{{"v", "version"}, tr("Show app version")},
|
|
//{"config", tr("commandLineOptionConfig").replace("%1", EXECUTABLE_NAME), tr("commandLineOptionConfigArg")},
|
|
{"fetch-config",
|
|
tr("Specify the linphone configuration file to be fetched. It will be merged with the current "
|
|
"configuration.")
|
|
.replace("%1", EXECUTABLE_NAME),
|
|
tr("URL, path or file")},
|
|
//{{"c", "call"}, tr("commandLineOptionCall").replace("%1", EXECUTABLE_NAME), tr("commandLineOptionCallArg")},
|
|
#ifndef Q_OS_MACOS
|
|
//{"minimized", tr("commandLineOptionMinimized")},
|
|
#endif // ifndef Q_OS_MACOS
|
|
{{"V", "verbose"}, tr("Log to stdout some debug information while running")},
|
|
{"qt-logs-only", tr("Print only logs from the application")},
|
|
});
|
|
}
|
|
// Should be call only at first start
|
|
void App::sendCommand() {
|
|
auto arguments = mParser->positionalArguments();
|
|
if (mParser->isSet("fetch-config")) arguments << "fetch-config=" + mParser->value("fetch-config");
|
|
static bool firstStart = true; // We can't erase positional arguments. So we get them on each restart.
|
|
if (firstStart) {
|
|
firstStart = false;
|
|
if (isSecondary()) { // Send to primary
|
|
if (arguments.size() > 0) {
|
|
lDebug() << log().arg("Sending") << arguments;
|
|
for (auto i : arguments) {
|
|
sendMessage(i.toLocal8Bit(), -1);
|
|
}
|
|
} else {
|
|
lDebug() << log().arg("No arguments. Sending show command");
|
|
sendMessage("show", -1);
|
|
}
|
|
} else if (arguments.size() > 0) { // Execute
|
|
lDebug() << log().arg("Executing ") << arguments;
|
|
for (auto i : arguments) {
|
|
QString command(i);
|
|
receivedMessage(0, i.toLocal8Bit());
|
|
}
|
|
}
|
|
} else if (isPrimary()) {
|
|
lDebug() << log().arg("Run waiting process");
|
|
receivedMessage(0, "");
|
|
}
|
|
}
|
|
|
|
bool App::getCoreStarted() const {
|
|
return mCoreStarted;
|
|
}
|
|
|
|
void App::setCoreStarted(bool started) {
|
|
if (mCoreStarted != started) {
|
|
mCoreStarted = started;
|
|
emit coreStartedChanged(mCoreStarted);
|
|
}
|
|
}
|
|
|
|
static QObject *findParentWindow(QObject *item) {
|
|
return !item || item->isWindowType() ? item : findParentWindow(item->parent());
|
|
}
|
|
|
|
bool App::notify(QObject *receiver, QEvent *event) {
|
|
bool done = true;
|
|
try {
|
|
done = QApplication::notify(receiver, event);
|
|
} catch (const std::exception &ex) {
|
|
lCritical() << log().arg("Exception has been catch in notify: %1").arg(ex.what());
|
|
} catch (...) {
|
|
lCritical() << log().arg("Generic exeption has been catch in notify");
|
|
}
|
|
if (event->type() == QEvent::MouseButtonPress) {
|
|
auto window = findParentWindow(receiver);
|
|
if (getMainWindow() == window) {
|
|
auto defaultAccountCore = mAccountList->getDefaultAccountCore();
|
|
if (defaultAccountCore && defaultAccountCore->getUnreadCallNotifications() > 0) {
|
|
emit defaultAccountCore->lResetMissedCalls();
|
|
}
|
|
}
|
|
}
|
|
return done;
|
|
}
|
|
|
|
QQuickWindow *App::getCallsWindow(QVariant callGui) {
|
|
mustBeInMainThread(getClassName());
|
|
if (!mCallsWindow) {
|
|
const QUrl callUrl("qrc:/qt/qml/Linphone/view/Page/Window/Call/CallsWindow.qml");
|
|
|
|
lInfo() << log().arg("Creating subwindow: `%1`.").arg(callUrl.toString());
|
|
|
|
QQmlComponent component(mEngine, callUrl);
|
|
if (component.isError()) {
|
|
qWarning() << component.errors();
|
|
abort();
|
|
}
|
|
lInfo() << log().arg("Subwindow status: `%1`.").arg(component.status());
|
|
|
|
QObject *object = nullptr;
|
|
// if (!callGui.isNull() && callGui.isValid()) object = component.createWithInitialProperties({{"call",
|
|
// callGui}});
|
|
object = component.create();
|
|
Q_ASSERT(object);
|
|
if (!object) {
|
|
lCritical() << log().arg("Calls window could not be created.");
|
|
return nullptr;
|
|
}
|
|
|
|
// QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);
|
|
object->setParent(mEngine);
|
|
|
|
auto window = qobject_cast<QQuickWindow *>(object);
|
|
Q_ASSERT(window);
|
|
if (!window) {
|
|
lCritical() << log().arg("Calls window could not be created.");
|
|
return nullptr;
|
|
}
|
|
// window->setParent(mMainWindow);
|
|
mCallsWindow = window;
|
|
}
|
|
if (!callGui.isNull() && callGui.isValid()) mCallsWindow->setProperty("call", callGui);
|
|
return mCallsWindow;
|
|
}
|
|
|
|
void App::setCallsWindowProperty(const char *id, QVariant property) {
|
|
if (mCallsWindow) mCallsWindow->setProperty(id, property);
|
|
}
|
|
|
|
void App::closeCallsWindow() {
|
|
if (mCallsWindow) {
|
|
mCallsWindow->close();
|
|
mCallsWindow->deleteLater();
|
|
mCallsWindow = nullptr;
|
|
}
|
|
}
|
|
|
|
QQuickWindow *App::getMainWindow() const {
|
|
return mMainWindow;
|
|
}
|
|
|
|
void App::setMainWindow(QQuickWindow *data) {
|
|
if (mMainWindow != data) {
|
|
mMainWindow = data;
|
|
emit mainWindowChanged();
|
|
}
|
|
}
|
|
|
|
QSharedPointer<AccountList> App::getAccountList() const {
|
|
return mAccountList;
|
|
}
|
|
|
|
void App::setAccountList(QSharedPointer<AccountList> data) {
|
|
if (mAccountList != data) {
|
|
mAccountList = data;
|
|
emit accountsChanged();
|
|
}
|
|
}
|
|
|
|
AccountList *App::getAccounts() const {
|
|
return mAccountList.get();
|
|
}
|
|
|
|
QSharedPointer<CallList> App::getCallList() const {
|
|
return mCallList;
|
|
}
|
|
|
|
void App::setCallList(QSharedPointer<CallList> data) {
|
|
if (mCallList != data) {
|
|
mCallList = data;
|
|
emit callsChanged();
|
|
}
|
|
}
|
|
|
|
CallList *App::getCalls() const {
|
|
return mCallList.get();
|
|
}
|
|
|
|
QSharedPointer<SettingsCore> App::getSettings() const {
|
|
return mSettings;
|
|
}
|
|
|
|
void App::onExitOnCloseChanged() {
|
|
setSysTrayIcon(); // Restore button depends from this option
|
|
if (mSettings) setQuitOnLastWindowClosed(mSettings->getExitOnClose());
|
|
if (mSettings) setAutoStart(mSettings->getAutoStart());
|
|
}
|
|
|
|
void App::onAuthenticationRequested(const std::shared_ptr<linphone::Core> &core,
|
|
const std::shared_ptr<linphone::AuthInfo> &authInfo,
|
|
linphone::AuthMethod method) {
|
|
mCoreModelConnection->invokeToCore([this, core, authInfo, method]() {
|
|
auto window = App::getInstance()->getMainWindow();
|
|
if (!window) {
|
|
// Note: we can do connection with shared pointers because of SingleShotConnection
|
|
connect(
|
|
this, &App::mainWindowChanged, this,
|
|
[this, core, authInfo, method]() { onAuthenticationRequested(core, authInfo, method); },
|
|
Qt::SingleShotConnection);
|
|
} else {
|
|
if (method == linphone::AuthMethod::HttpDigest) {
|
|
lInfo() << log().arg("Received HttpDigest");
|
|
auto username = Utils::coreStringToAppString(authInfo->getUsername());
|
|
auto domain = Utils::coreStringToAppString(authInfo->getDomain());
|
|
CallbackHelper *callback = new CallbackHelper();
|
|
auto cb = [this, authInfo, core](QVariant vPassword) {
|
|
mCoreModelConnection->invokeToModel([this, core, authInfo, vPassword] {
|
|
QString password = vPassword.toString();
|
|
mustBeInLinphoneThread("[App] reauthenticate");
|
|
if (password.isEmpty()) {
|
|
lDebug() << log().arg("ERROR : empty password");
|
|
} else {
|
|
lDebug() << log()
|
|
.arg("Reset password for %1")
|
|
.arg(Utils::coreStringToAppString(authInfo->getUsername()));
|
|
authInfo->setPassword(Utils::appStringToCoreString(password));
|
|
core->addAuthInfo(authInfo);
|
|
core->refreshRegisters();
|
|
}
|
|
});
|
|
};
|
|
connect(callback, &CallbackHelper::cb, cb);
|
|
QMetaObject::invokeMethod(window, "reauthenticateAccount", Qt::DirectConnection,
|
|
Q_ARG(QVariant, username), Q_ARG(QVariant, domain),
|
|
QVariant::fromValue(callback));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
#ifdef Q_OS_LINUX
|
|
QString App::getApplicationPath() const {
|
|
const QString binPath(QCoreApplication::applicationFilePath());
|
|
|
|
// Check if installation is done via Flatpak, AppImage, or classic package
|
|
// in order to rewrite a correct exec path for autostart
|
|
QString exec;
|
|
qDebug() << "binpath=" << binPath;
|
|
if (binPath.startsWith("/app")) { // Flatpak
|
|
exec = QStringLiteral("flatpak run " APPLICATION_ID);
|
|
qDebug() << "exec path autostart set flatpak=" << exec;
|
|
} else if (binPath.startsWith("/tmp/.mount")) { // Appimage
|
|
exec = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
|
|
qDebug() << "exec path autostart set appimage=" << exec;
|
|
} else { // classic package
|
|
exec = binPath;
|
|
qDebug() << "exec path autostart set classic package=" << exec;
|
|
}
|
|
return exec;
|
|
}
|
|
|
|
void App::exportDesktopFile() {
|
|
QDir dir(ApplicationsDirectory);
|
|
if (!dir.exists() && !dir.mkpath(ApplicationsDirectory)) {
|
|
qWarning() << QStringLiteral("Unable to build applications dir path: `%1`.").arg(ApplicationsDirectory);
|
|
return;
|
|
}
|
|
|
|
const QString confPath(ApplicationsDirectory + EXECUTABLE_NAME ".desktop");
|
|
if (generateDesktopFile(confPath, true, false)) generateDesktopFile(confPath, false, false);
|
|
}
|
|
|
|
bool App::generateDesktopFile(const QString &confPath, bool remove, bool openInBackground) {
|
|
qInfo() << QStringLiteral("Updating `%1`...").arg(confPath);
|
|
QFile file(confPath);
|
|
|
|
if (remove) {
|
|
if (file.exists() && !file.remove()) {
|
|
qWarning() << QLatin1String("Unable to remove autostart file: `" EXECUTABLE_NAME ".desktop`.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (!file.open(QFile::WriteOnly)) {
|
|
qWarning() << "Unable to open autostart file: `" EXECUTABLE_NAME ".desktop`.";
|
|
return false;
|
|
}
|
|
|
|
QString exec = getApplicationPath();
|
|
|
|
QDir dir;
|
|
QString iconPath;
|
|
bool haveIcon = false;
|
|
if (!dir.mkpath(IconsDirectory)) // Scalable icons folder may be created
|
|
qWarning() << "Cannot create scalable icon path at " << IconsDirectory;
|
|
else {
|
|
iconPath = IconsDirectory + EXECUTABLE_NAME + ".svg";
|
|
QFile icon(Constants::WindowIconPath);
|
|
if (!QFile(iconPath).exists()) { // Keep old icon but copy if it doesn't exist
|
|
haveIcon = icon.copy(iconPath);
|
|
if (!haveIcon) qWarning() << "Couldn't copy icon svg into " << iconPath;
|
|
else { // Update permissions
|
|
QFile icon(iconPath);
|
|
icon.setPermissions(icon.permissions() | QFileDevice::WriteOwner);
|
|
}
|
|
} else {
|
|
qInfo() << "Icon already exists in " << IconsDirectory << ". It is not replaced.";
|
|
haveIcon = true;
|
|
}
|
|
}
|
|
|
|
QTextStream(&file) << QString("[Desktop Entry]\n"
|
|
"Name=" APPLICATION_NAME "\n"
|
|
"GenericName=SIP Phone\n"
|
|
"Comment=" APPLICATION_DESCRIPTION "\n"
|
|
"Type=Application\n")
|
|
<< (openInBackground ? "Exec=" + exec + " --minimized %u\n" : "Exec=" + exec + " %u\n")
|
|
<< (haveIcon ? "Icon=" + iconPath + "\n" : "Icon=" EXECUTABLE_NAME "\n")
|
|
<< "Terminal=false\n"
|
|
"Categories=Network;Telephony;\n"
|
|
"MimeType=x-scheme-handler/sip-" EXECUTABLE_NAME ";x-scheme-handler/sips-" EXECUTABLE_NAME
|
|
";x-scheme-handler/" EXECUTABLE_NAME "-sip;x-scheme-handler/" EXECUTABLE_NAME
|
|
"-sips;x-scheme-handler/sip;x-scheme-handler/sips;x-scheme-handler/tel;x-scheme-handler/"
|
|
"callto;x-scheme-handler/" EXECUTABLE_NAME "-config;\n"
|
|
"X-PulseAudio-Properties=media.role=phone\n";
|
|
|
|
return true;
|
|
}
|
|
#elif defined(Q_OS_MACOS)
|
|
// On MAC, URI handlers call the application with no arguments and pass them in event loop.
|
|
bool App::event(QEvent *event) {
|
|
if (event->type() == QEvent::FileOpen) {
|
|
const QString url = static_cast<QFileOpenEvent *>(event)->url().toString();
|
|
if (isSecondary()) {
|
|
sendMessage(url.toLocal8Bit(), -1);
|
|
::exit(EXIT_SUCCESS);
|
|
}
|
|
receivedMessage(0, url.toLocal8Bit());
|
|
} else if (event->type() == QEvent::ApplicationStateChange) {
|
|
auto state = static_cast<QApplicationStateChangeEvent *>(event);
|
|
if (state->applicationState() == Qt::ApplicationActive) Utils::smartShowWindow(getMainWindow());
|
|
}
|
|
|
|
return SingleApplication::event(event);
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------
|
|
// System tray
|
|
//-----------------------------------------------------------
|
|
|
|
void App::setSysTrayIcon() {
|
|
QQuickWindow *root = getMainWindow();
|
|
QSystemTrayIcon *systemTrayIcon =
|
|
(mSystemTrayIcon ? mSystemTrayIcon
|
|
: new QSystemTrayIcon(nullptr)); // Workaround : QSystemTrayIcon cannot be deleted because
|
|
// of setContextMenu (indirectly)
|
|
|
|
// trayIcon: Right click actions.
|
|
QAction *restoreAction = nullptr;
|
|
if (mSettings && !mSettings->getExitOnClose()) {
|
|
restoreAction = new QAction(root);
|
|
auto setRestoreActionText = [restoreAction](bool visible) {
|
|
restoreAction->setText(visible ? tr("Cacher") : tr("Afficher"));
|
|
};
|
|
setRestoreActionText(root->isVisible());
|
|
connect(root, &QWindow::visibleChanged, restoreAction, setRestoreActionText);
|
|
|
|
root->connect(restoreAction, &QAction::triggered, this, [this, restoreAction](bool checked) {
|
|
auto mainWindow = getMainWindow();
|
|
if (mainWindow->isVisible()) {
|
|
mainWindow->close();
|
|
} else {
|
|
mainWindow->show();
|
|
}
|
|
});
|
|
}
|
|
|
|
QAction *quitAction = new QAction(tr("Quit"), root);
|
|
root->connect(quitAction, &QAction::triggered, this, &App::quit);
|
|
|
|
// trayIcon: Left click actions.
|
|
QMenu *menu = mSystemTrayIcon ? mSystemTrayIcon->contextMenu() : new QMenu();
|
|
menu->clear();
|
|
menu->setTitle(APPLICATION_NAME);
|
|
// Build trayIcon menu.
|
|
if (restoreAction) {
|
|
menu->addAction(restoreAction);
|
|
menu->addSeparator();
|
|
}
|
|
menu->addAction(quitAction);
|
|
if (!mSystemTrayIcon) {
|
|
systemTrayIcon->setContextMenu(menu); // This is a Qt bug. We cannot call setContextMenu more than once. So
|
|
// we have to keep an instance of the menu.
|
|
connect(systemTrayIcon, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason) {
|
|
// Left-Click and Double Left-Click
|
|
if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::DoubleClick) {
|
|
auto mainWindow = getMainWindow();
|
|
if (mainWindow) mainWindow->show();
|
|
}
|
|
});
|
|
}
|
|
systemTrayIcon->setIcon(QIcon(Constants::WindowIconPath));
|
|
systemTrayIcon->setToolTip(APPLICATION_NAME);
|
|
systemTrayIcon->show();
|
|
if (!mSystemTrayIcon) mSystemTrayIcon = systemTrayIcon;
|
|
if (!QSystemTrayIcon::isSystemTrayAvailable()) qInfo() << "System tray is not available";
|
|
}
|
|
|
|
//-----------------------------------------------------------
|
|
// Locale TODO - App only in French now.
|
|
//-----------------------------------------------------------
|
|
|
|
void App::setLocale(QString configLocale) {
|
|
mLocale = QLocale(QLocale::French);
|
|
}
|
|
|
|
QLocale App::getLocale() {
|
|
return mLocale;
|
|
}
|
|
|
|
//-----------------------------------------------------------
|
|
// Version infos.
|
|
//-----------------------------------------------------------
|
|
|
|
QString App::getShortApplicationVersion() {
|
|
#ifdef LINPHONEAPP_SHORT_VERSION
|
|
return QStringLiteral(LINPHONEAPP_SHORT_VERSION);
|
|
#else
|
|
return tr("inconnue");
|
|
#endif
|
|
}
|
|
|
|
QString App::getGitBranchName() {
|
|
#ifdef GIT_BRANCH_NAME
|
|
return QStringLiteral(GIT_BRANCH_NAME);
|
|
#else
|
|
return tr("inconnue");
|
|
#endif
|
|
}
|
|
|
|
QString App::getSdkVersion() {
|
|
#ifdef LINPHONESDK_VERSION
|
|
return QStringLiteral(LINPHONESDK_VERSION);
|
|
#else
|
|
return tr("inconnue");
|
|
#endif
|
|
}
|