/* * 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 . */ #include "tool/LinphoneEnums.hpp" #include "App.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "core/account/AccountCore.hpp" #include "core/account/AccountDeviceGui.hpp" #include "core/account/AccountDeviceProxy.hpp" #include "core/address-books/LdapGui.hpp" #include "core/address-books/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/ParticipantDeviceCore.hpp" #include "core/participant/ParticipantDeviceProxy.hpp" #include "core/participant/ParticipantGui.hpp" #include "core/participant/ParticipantProxy.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/TimeZone.hpp" #include "core/timezone/TimeZoneProxy.hpp" #include "core/variant/VariantList.hpp" #include "core/videoSource/VideoSourceDescriptorGui.hpp" #include "model/object/VariantObject.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/AuthenticationDialog.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 --iconified 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) { if (enabled == mAutoStart) return; QSettings settings(AutoStartSettingsFilePath, QSettings::NativeFormat); if (enabled) settings.setValue(EXECUTABLE_NAME, QDir::toNativeSeparators(applicationFilePath())); 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. QCoreApplication::setApplicationName(EXECUTABLE_NAME); QApplication::setOrganizationDomain(EXECUTABLE_NAME); QCoreApplication::setApplicationVersion(APPLICATION_SEMVER); // If not OpenGL, createRender is never call. QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); // Ignore vertical sync. This way, we avoid blinking on resizes(and other refresh like layouts etc.). auto ignoreVSync = QSurfaceFormat::defaultFormat(); ignoreVSync.setSwapInterval(0); QSurfaceFormat::setDefaultFormat(ignoreVSync); 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()); mAutoStart = autoStartEnabled(); } App::~App() { } void App::setSelf(QSharedPointer(me)) { mCoreModelConnection = QSharedPointer>( new SafeConnection(me, CoreModel::getInstance()), &QObject::deleteLater); mCoreModelConnection->makeConnectToModel(&CoreModel::callCreated, [this](const std::shared_ptr &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); 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::authenticationRequested, [this](const std::shared_ptr &core, const std::shared_ptr &authInfo, linphone::AuthMethod method) { mCoreModelConnection->invokeToCore([this, core, authInfo, method]() { if (method == linphone::AuthMethod::HttpDigest) { auto window = App::getInstance()->getMainWindow(); auto username = authInfo->getUsername(); auto domain = authInfo->getDomain(); AuthenticationDialog *obj = new AuthenticationDialog(Utils::coreStringToAppString(username), Utils::coreStringToAppString(domain)); connect(obj, &AuthenticationDialog::result, this, [this, obj, authInfo, core](QString password) { mCoreModelConnection->invokeToModel([this, core, authInfo, password] { mustBeInLinphoneThread("[App] reauthenticate"); if (password.isEmpty()) { lDebug() << "ERROR : empty password"; } else { lDebug() << "reset password for" << authInfo->getUsername(); authInfo->setPassword(Utils::appStringToCoreString(password)); core->addAuthInfo(authInfo); core->refreshRegisters(); } }); obj->deleteLater(); }); QMetaObject::invokeMethod(window, "reauthenticateAccount", QVariant::fromValue(obj)); } }); }); //--------------------------------------------------------------------------------------------- mCliModelConnection = QSharedPointer>( new SafeConnection(me, CliModel::getInstance()), &QObject::deleteLater); mCliModelConnection->makeConnectToCore(&App::receivedMessage, [this](int, const QByteArray &byteArray) { QString command(byteArray); if (command.isEmpty()) mCliModelConnection->invokeToModel([command]() { 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(QApplication::instance()); } 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 (!mLinphoneThread->isRunning()) { lDebug() << log().arg("Starting Thread"); mLinphoneThread->start(); } setQuitOnLastWindowClosed(true); // TODO: use settings to set it 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]() mutable { CoreModel::getInstance()->start(); auto settings = SettingsCore::create(); QMetaObject::invokeMethod(App::getInstance()->thread(), [this, settings] { // 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->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()); mEngine->addImageProvider(ScreenProvider::ProviderId, new ScreenProvider()); mEngine->addImageProvider(WindowProvider::ProviderId, new WindowProvider()); mEngine->addImageProvider(WindowIconProvider::ProviderId, new WindowIconProvider()); // Enable notifications. mNotifier = new Notifier(mEngine); mSettings = settings; mEngine->setObjectOwnership(mSettings.get(), QQmlEngine::CppOwnership); mAccountList = AccountList::create(); mCallList = CallList::create(); const QUrl url(u"qrc:/Linphone/view/Page/Window/Main/MainWindow.qml"_qs); 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); } setMainWindow(qobject_cast(obj)); QMetaObject::invokeMethod(obj, "initStackViewItem"); Q_ASSERT(mMainWindow); } }, Qt::QueuedConnection); QObject::connect(mSettings.get(), &SettingsCore::autoStartChanged, [this]() { mustBeInMainThread(log().arg(Q_FUNC_INFO)); setAutoStart(mSettings->getAutoStart()); }); mEngine->load(url); }); // coreModel.reset(); }, Qt::BlockingQueuedConnection); } void App::initCppInterfaces() { qmlRegisterSingletonType( Constants::MainQmlUri, 1, 0, "LoginPageCpp", [](QQmlEngine *engine, QJSEngine *) -> QObject * { static auto loginPage = new LoginPage(engine); App::getInstance()->mEngine->setObjectOwnership(loginPage, QQmlEngine::CppOwnership); return loginPage; }); qmlRegisterSingletonType( Constants::MainQmlUri, 1, 0, "RegisterPageCpp", [](QQmlEngine *engine, QJSEngine *) -> QObject * { static RegisterPage *registerPage = new RegisterPage(); App::getInstance()->mEngine->setObjectOwnership(registerPage, QQmlEngine::CppOwnership); return registerPage; }); qmlRegisterSingletonType( "ConstantsCpp", 1, 0, "ConstantsCpp", [](QQmlEngine *engine, QJSEngine *) -> QObject * { return new Constants(engine); }); qmlRegisterSingletonType("UtilsCpp", 1, 0, "UtilsCpp", [](QQmlEngine *engine, QJSEngine *) -> QObject * { return new Utils(engine); }); qmlRegisterSingletonType( "DesktopToolsCpp", 1, 0, "DesktopToolsCpp", [](QQmlEngine *engine, QJSEngine *) -> QObject * { return new DesktopTools(engine); }); qmlRegisterSingletonType( "EnumsToStringCpp", 1, 0, "EnumsToStringCpp", [](QQmlEngine *engine, QJSEngine *) -> QObject * { return new EnumsToString(engine); }); qmlRegisterSingletonType( "SettingsCpp", 1, 0, "SettingsCpp", [this](QQmlEngine *engine, QJSEngine *) -> QObject * { return mSettings.get(); }); qmlRegisterSingletonType( "LinphoneAccountsCpp", 1, 0, "LinphoneAccountsCpp", [this](QQmlEngine *engine, QJSEngine *) -> QObject * { return mAccountList.get(); }); qmlRegisterSingletonType( "LinphoneCallsCpp", 1, 0, "LinphoneCallsCpp", [this](QQmlEngine *engine, QJSEngine *) -> QObject * { return mCallList.get(); }); qmlRegisterType(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "VariantObject"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "VariantList"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ParticipantProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ParticipantGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ConferenceInfoProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ConferenceInfoGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "PhoneNumber", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountDeviceProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "AccountDeviceGui"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "AccountCore", QLatin1String("Uncreatable")); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallHistoryProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallProxy"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "ConferenceCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ConferenceGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "FriendGui"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "FriendCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "MagicSearchProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "MagicSearchList"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CameraGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "FPSCounter"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "TimeZoneProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ParticipantDeviceGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ParticipantDeviceProxy"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "ScreenList", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "ScreenProxy"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "VideoSourceDescriptorGui"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "RequestDialog", QLatin1String("Uncreatable")); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "AuthenticationDialogCpp", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "LdapGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "LdapProxy"); LinphoneEnums::registerMetaTypes(); } //------------------------------------------------------------ void App::clean() { delete mEngine; mEngine = nullptr; // Wait 500ms to let time for log te be stored. // mNotifier destroyed in mEngine deletion as it is its parent qApp->processEvents(QEventLoop::AllEvents, 500); if (mLinphoneThread) { mLinphoneThread->exit(); mLinphoneThread->wait(); delete mLinphoneThread; } } void App::restart() { mCoreModelConnection->invokeToModel([this]() { CoreModel::getInstance()->getCore()->stop(); mCoreModelConnection->invokeToCore([this]() { mEngine->deleteLater(); 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("applicationDescription")); mParser->addPositionalArgument("command", tr("commandLineDescription").replace("%1", APPLICATION_NAME), "[command]"); mParser->addOptions({ {{"h", "help"}, tr("commandLineOptionHelp")}, {"cli-help", tr("commandLineOptionCliHelp").replace("%1", APPLICATION_NAME)}, {{"v", "version"}, tr("commandLineOptionVersion")}, {"config", tr("commandLineOptionConfig").replace("%1", EXECUTABLE_NAME), tr("commandLineOptionConfigArg")}, {"fetch-config", tr("commandLineOptionFetchConfig").replace("%1", EXECUTABLE_NAME), tr("commandLineOptionFetchConfigArg")}, {{"c", "call"}, tr("commandLineOptionCall").replace("%1", EXECUTABLE_NAME), tr("commandLineOptionCallArg")}, #ifndef Q_OS_MACOS {"iconified", tr("commandLineOptionIconified")}, #endif // ifndef Q_OS_MACOS {{"V", "verbose"}, tr("commandLineOptionVerbose")}, {"qt-logs-only", tr("commandLineOptionQtLogsOnly")}, }); } // Should be call only at first start void App::sendCommand() { auto arguments = mParser->positionalArguments(); static bool firstStart = true; // We can't erase positional arguments. So we get them on each restart. if (firstStart && arguments.size() > 0) { firstStart = false; if (isSecondary()) { // Send to primary lDebug() << "Sending " << arguments; for (auto i : arguments) { sendMessage(i.toLocal8Bit(), -1); } } else { // Execute lDebug() << "Executing " << arguments; for (auto i : arguments) { QString command(i); receivedMessage(0, i.toLocal8Bit()); } } } else if (isPrimary()) { // Run waiting process receivedMessage(0, ""); } } 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"); } catch (...) { lCritical() << log().arg("Generic exeption has been catch in notify"); } return done; } QQuickWindow *App::getCallsWindow(QVariant callGui) { mustBeInMainThread(getClassName()); if (!mCallsWindow) { const QUrl callUrl("qrc:/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(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 App::getAccountList() const { return mAccountList; } QSharedPointer App::getCallList() const { return mCallList; } QSharedPointer App::getSettings() const { return mSettings; } #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 + " --iconified %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(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(event); if (state->applicationState() == Qt::ApplicationActive) Utils::smartShowWindow(getMainWindow()); } return SingleApplication::event(event); } #endif //----------------------------------------------------------- // AutoStart //-----------------------------------------------------------