From 29ac626f8b0db28a2732c227c32d1787de5347e9 Mon Sep 17 00:00:00 2001 From: Peio Rigaux Date: Mon, 14 Mar 2022 18:54:47 +0100 Subject: [PATCH] Fixes autostart of app after appimage update. We now check if the application path entered in the desktop entry matches the binary of the actual app. If this is not the case, we rewrite the conf file. --- linphone-app/src/app/App.cpp | 242 +++++++++++++++++++++-------------- 1 file changed, 146 insertions(+), 96 deletions(-) diff --git a/linphone-app/src/app/App.cpp b/linphone-app/src/app/App.cpp index 8369e1001..9d1558b22 100644 --- a/linphone-app/src/app/App.cpp +++ b/linphone-app/src/app/App.cpp @@ -75,18 +75,68 @@ const QString AutoStartSettingsFilePath( // ----------------------------------------------------------------------------- #ifdef Q_OS_LINUX -static inline bool autoStartEnabled () { - return QDir(AutoStartDirectory).exists() && QFile(AutoStartDirectory + EXECUTABLE_NAME ".desktop").exists(); + +const QString getAutoStartPathFromBinaryPath(){ + + 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 flatpak=" << exec; + } + else if (binPath.startsWith("/tmp/.mount")) { //Appimage + exec = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); + qDebug() << "exec path autostart appimage=" << exec; + } + else { //classic package + exec = binPath; + qDebug() << "exec path autostart classic package=" << exec; + } + + return exec; + +} + +static bool autoStartEnabled () { + const QString confPath(AutoStartDirectory + EXECUTABLE_NAME ".desktop"); + QFile file(confPath); + 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 = getAutoStartPathFromBinaryPath(); + + QTextStream in(&file); + QString autoStartConf = in.readAll(); + + bool isCurrentAutoStartValid = true; + + int index = -1; + // is the Exec part of the autostart ini file not corresponding to our executable (old desktop entry with wrong version in filename) ? + // indexOff returns -1 if substring is not found + if (autoStartConf.indexOf(QString("Exex=" + exec)) < 0) { + isCurrentAutoStartValid = false; + } + + return QDir(AutoStartDirectory).exists() && QFile(AutoStartDirectory + EXECUTABLE_NAME ".desktop").exists() && isCurrentAutoStartValid; } #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); @@ -103,14 +153,14 @@ static inline bool autoStartEnabled () { 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) { @@ -129,7 +179,7 @@ static inline bool autoStartEnabled () { break; } } - + return false; } #else @@ -192,43 +242,43 @@ bool App::setFetchConfig (QCommandLineParser *parser) { App::App (int &argc, char *argv[]) : SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) { - + connect(this, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(stateChanged(Qt::ApplicationState))); - + setWindowIcon(QIcon(Constants::WindowIconPath)); - + createParser(); mParser->process(*this); - + // Initialize logger. shared_ptr config = Utils::getConfigIfExists (QString::fromStdString(getConfigPathIfExists(*mParser))); Logger::init(config); if (mParser->isSet("verbose")) Logger::getInstance()->setVerbose(true); - + // List available locales. for (const auto &locale : QDir(Constants::LanguagePath).entryList()) mAvailableLocales << QLocale(locale); - + // Init locale. mTranslator = new DefaultTranslator(this); mDefaultTranslator = new DefaultTranslator(this); initLocale(config); - + if (mParser->isSet("help")) { mParser->showHelp(); } - + if (mParser->isSet("cli-help")) { Cli::showHelp(); ::exit(EXIT_SUCCESS); } - + if (mParser->isSet("version")) mParser->showVersion(); - + mAutoStart = autoStartEnabled(); - + qInfo() << QStringLiteral("Starting " APPLICATION_NAME " (bin: " EXECUTABLE_NAME ")"); qInfo() << QStringLiteral("Use locale: %1").arg(mLocale); } @@ -270,20 +320,20 @@ void App::processArguments(QHash args){ static QQuickWindow *createSubWindow (QQmlApplicationEngine *engine, const char *path) { qInfo() << QStringLiteral("Creating subwindow: `%1`.").arg(path); - + QQmlComponent component(engine, QUrl(path)); if (component.isError()) { qWarning() << component.errors(); abort(); } qInfo() << QStringLiteral("Subwindow status: `%1`.").arg(component.status()); - + QObject *object = component.create(); Q_ASSERT(object); - + QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership); object->setParent(engine); - + return qobject_cast(object); } @@ -294,16 +344,16 @@ void App::initContentApp () { shared_ptr config; bool mustBeIconified = false; bool needRestart = true; - + // Destroy qml components and linphone core if necessary. if (mEngine) { needRestart = false; setFetchConfig(mParser); setOpened(false); qInfo() << QStringLiteral("Restarting app..."); - + delete mEngine; - + mNotifier = nullptr; // CoreManager::uninit(); @@ -322,35 +372,35 @@ void App::initContentApp () { // Update and download codecs. VideoCodecsModel::updateCodecs(); VideoCodecsModel::downloadUpdatableCodecs(this); - + // Don't quit if last window is closed!!! setQuitOnLastWindowClosed(false); - + // Deal with received messages and CLI. QObject::connect(this, &App::receivedMessage, this, [](int, const QByteArray &byteArray) { QString command(byteArray); qInfo() << QStringLiteral("Received command from other application: `%1`.").arg(command); Cli::executeCommand(command); }); - + #ifndef Q_OS_MACOS mustBeIconified = mParser->isSet("iconified"); #endif // ifndef Q_OS_MACOS mColorListModel = new ColorListModel(); mImageListModel = new ImageListModel(); } - + // Change colors if necessary. mColorListModel->useConfig(config); mImageListModel->useConfig(config); - + // Init core. CoreManager::init(this, Utils::coreStringToAppString(configPath)); - - + + // Init engine content. mEngine = new QQmlApplicationEngine(); - + // Provide `+custom` folders for custom components and `5.9` for old components. { QStringList selectors("custom"); @@ -360,18 +410,18 @@ void App::initContentApp () { (new QQmlFileSelector(mEngine, mEngine))->setExtraSelectors(selectors); } qInfo() << QStringLiteral("Activated selectors:") << QQmlFileSelector::get(mEngine)->selector()->allSelectors(); - + // Set modules paths. mEngine->addImportPath(":/ui/modules"); mEngine->addImportPath(":/ui/scripts"); mEngine->addImportPath(":/ui/views"); - + // Provide avatars/thumbnails providers. mEngine->addImageProvider(AvatarProvider::ProviderId, new AvatarProvider()); mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider()); mEngine->addImageProvider(ExternalImageProvider::ProviderId, new ExternalImageProvider()); mEngine->addImageProvider(ThumbnailProvider::ProviderId, new ThumbnailProvider()); - + mEngine->rootContext()->setContextProperty("applicationName", APPLICATION_NAME); #ifdef APPLICATION_URL @@ -379,7 +429,7 @@ void App::initContentApp () { #else mEngine->rootContext()->setContextProperty("applicationUrl", ""); #endif - + #ifdef APPLICATION_VENDOR mEngine->rootContext()->setContextProperty("applicationVendor", APPLICATION_VENDOR); #else @@ -393,21 +443,21 @@ void App::initContentApp () { mEngine->rootContext()->setContextProperty("copyrightRangeDate", COPYRIGHT_RANGE_DATE); mEngine->rootContext()->setContextProperty("Colors", mColorListModel->getQmlData()); mEngine->rootContext()->setContextProperty("Images", mImageListModel->getQmlData()); - + registerTypes(); registerSharedTypes(); registerToolTypes(); registerSharedToolTypes(); - + // Enable notifications. mNotifier = new Notifier(mEngine); - + // Load main view. qInfo() << QStringLiteral("Loading main view..."); mEngine->load(QUrl(Constants::QmlViewMainWindow)); if (mEngine->rootObjects().isEmpty()) qFatal("Unable to open main window."); - + QObject::connect( CoreManager::getInstance(), &CoreManager::coreManagerInitialized, CoreManager::getInstance(), @@ -416,7 +466,7 @@ void App::initContentApp () { openAppAfterInit(mustBeIconified); } ); - + // Execute command argument if needed. const QString commandArgument = getCommandArgument(); if (!commandArgument.isEmpty()) { @@ -445,10 +495,10 @@ bool App::event (QEvent *event) { sendMessage(url.toLocal8Bit(), -1); ::exit(EXIT_SUCCESS); } - + Cli::executeCommand(url); } - + return SingleApplication::event(event); } @@ -461,7 +511,7 @@ QQuickWindow *App::getCallsWindow () const { SettingsModel::UiSection, "disable_calls_window", 0 )) return nullptr; - + return mCallsWindow; } @@ -501,7 +551,7 @@ void App::stateChanged(Qt::ApplicationState pState) { void App::createParser () { delete mParser; - + mParser = new QCommandLineParser(); mParser->setApplicationDescription(tr("applicationDescription")); mParser->addPositionalArgument("command", tr("commandLineDescription").replace("%1", APPLICATION_NAME), "[command]"); @@ -596,7 +646,7 @@ static inline void registerSharedToolType (const char *name) { void App::registerTypes () { qInfo() << QStringLiteral("Registering types..."); - + qRegisterMetaType>(); qRegisterMetaType(); qRegisterMetaType>(); @@ -610,7 +660,7 @@ void App::registerTypes () { qRegisterMetaType>(); //qRegisterMetaType>(); LinphoneEnums::registerMetaTypes(); - + registerType("AssistantModel"); registerType("AuthenticationNotifier"); registerType("CallsListProxyModel"); @@ -630,8 +680,8 @@ void App::registerTypes () { registerType("SipAddressesProxyModel"); registerType("SearchSipAddressesModel"); registerType("SearchSipAddressesProxyModel"); - - + + registerType("ColorProxyModel"); registerType("ImageColorsProxyModel"); registerType("ImageProxyModel"); @@ -639,15 +689,15 @@ void App::registerTypes () { registerType("ParticipantProxyModel"); registerType("SoundPlayer"); registerType("TelephoneNumbersModel"); - + registerSingletonType("AudioCodecsModel"); registerSingletonType("OwnPresenceModel"); registerSingletonType("Presence"); //registerSingletonType("TimelineModel"); registerSingletonType("UrlHandlers"); registerSingletonType("VideoCodecsModel"); - - + + registerUncreatableType("CallModel"); registerUncreatableType("ChatCallModel"); registerUncreatableType("ChatMessageModel"); @@ -664,7 +714,7 @@ void App::registerTypes () { registerUncreatableType("LdapModel"); registerUncreatableType("RecorderModel"); registerUncreatableType("SearchResultModel"); - registerUncreatableType("SipAddressObserver"); + registerUncreatableType("SipAddressObserver"); registerUncreatableType("VcardModel"); registerUncreatableType("TimelineModel"); registerUncreatableType("TunnelModel"); @@ -677,35 +727,35 @@ void App::registerTypes () { registerUncreatableType("ParticipantDeviceProxyModel"); registerUncreatableType("ParticipantImdnStateModel"); registerUncreatableType("ParticipantImdnStateListModel"); - - - + + + qmlRegisterUncreatableMetaObject(LinphoneEnums::staticMetaObject, "LinphoneEnums", 1, 0, "LinphoneEnums", "Only enums"); } void App::registerSharedTypes () { qInfo() << QStringLiteral("Registering shared types..."); - + registerSharedSingletonType("App"); registerSharedSingletonType("CoreManager"); registerSharedSingletonType("SettingsModel"); registerSharedSingletonType("AccountSettingsModel"); - registerSharedSingletonType("SipAddressesModel"); + registerSharedSingletonType("SipAddressesModel"); registerSharedSingletonType("CallsListModel"); registerSharedSingletonType("ContactsListModel"); registerSharedSingletonType("ContactsImporterListModel"); registerSharedSingletonType("LdapListModel"); registerSharedSingletonType("TimelineListModel"); registerSharedSingletonType("RecorderManager"); - + //qmlRegisterSingletonType(Constants::MainQmlUri, 1, 0, "ColorList", mColorListModel); - + //registerSharedSingletonType("ColorCpp"); } void App::registerToolTypes () { qInfo() << QStringLiteral("Registering tool types..."); - + registerToolType("Clipboard"); registerToolType("DesktopTools"); registerToolType("TextToSpeech"); @@ -717,7 +767,7 @@ void App::registerToolTypes () { void App::registerSharedToolTypes () { qInfo() << QStringLiteral("Registering shared tool types..."); - + registerSharedToolType("ColorsList"); } @@ -726,18 +776,18 @@ void App::registerSharedToolTypes () { void App::setTrayIcon () { QQuickWindow *root = getMainWindow(); QSystemTrayIcon* systemTrayIcon = (mSystemTrayIcon?mSystemTrayIcon : new QSystemTrayIcon(nullptr));// Workaround : QSystemTrayIcon cannot be deleted because of setContextMenu (indirectly) - + // trayIcon: Right click actions. QAction *settingsAction = new QAction(tr("settings"), root); root->connect(settingsAction, &QAction::triggered, root, [this] { App::smartShowWindow(getSettingsWindow()); }); - + QAction *updateCheckAction = new QAction(tr("checkForUpdates"), root); root->connect(updateCheckAction, &QAction::triggered, root, [this] { checkForUpdates(true); }); - + QAction *aboutAction = new QAction(tr("about"), root); root->connect(aboutAction, &QAction::triggered, root, [root] { App::smartShowWindow(root); @@ -746,15 +796,15 @@ void App::setTrayIcon () { Q_ARG(QVariant, QUrl(Constants::AboutPath)), Q_ARG(QVariant, QVariant()), Q_ARG(QVariant, QVariant()) ); }); - + QAction *restoreAction = new QAction(tr("restore"), root); root->connect(restoreAction, &QAction::triggered, root, [root] { smartShowWindow(root); }); - + QAction *quitAction = new QAction(tr("quit"), root); root->connect(quitAction, &QAction::triggered, this, &App::quit); - + // trayIcon: Left click actions. static QMenu *menu = new QMenu();// Static : Workaround about a bug with setContextMenu where it cannot be called more than once. root->connect(systemTrayIcon, &QSystemTrayIcon::activated, [root]( @@ -792,27 +842,27 @@ void App::setTrayIcon () { void App::initLocale (const shared_ptr &config) { // Try to use preferred locale. QString locale; - + // Use english. This default translator is used if there are no found translations in others loads mLocale = Constants::DefaultLocale; if (!installLocale(*this, *mDefaultTranslator, QLocale(mLocale))) qFatal("Unable to install default translator."); - + if (config) locale = Utils::coreStringToAppString(config->getString(SettingsModel::UiSection, "locale", "")); - + if (!locale.isEmpty() && installLocale(*this, *mTranslator, QLocale(locale))) { mLocale = locale; return; } - + // Try to use system locale. QLocale sysLocale = QLocale::system(); if (installLocale(*this, *mTranslator, sysLocale)) { mLocale = sysLocale.name(); return; } - + } QString App::getConfigLocale () const { @@ -827,7 +877,7 @@ void App::setConfigLocale (const QString &locale) { CoreManager::getInstance()->getCore()->getConfig()->setString( SettingsModel::UiSection, "locale", Utils::appStringToCoreString(locale) ); - + emit configLocaleChanged(locale); } @@ -842,35 +892,35 @@ QString App::getLocale () const { 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"); qInfo() << QStringLiteral("Updating `%1`...").arg(confPath); QFile file(confPath); - + if (!enabled) { if (file.exists() && !file.remove()) { qWarning() << QLatin1String("Unable to remove autostart file: `" EXECUTABLE_NAME ".desktop`."); return; } - + mAutoStart = enabled; emit autoStartChanged(enabled); return; } - + if (!file.open(QFile::WriteOnly)) { qWarning() << "Unable to open autostart file: `" EXECUTABLE_NAME ".desktop`."; return; } - + const QString binPath(applicationFilePath()); - + // Check if installation is done via Flatpak, AppImage, or classic package // in order to rewrite a correct exec path for autostart QString exec; @@ -887,7 +937,7 @@ void App::setAutoStart (bool enabled) { exec = binPath; qDebug() << "exec path autostart set classic package=" << exec; } - + QTextStream(&file) << QString( "[Desktop Entry]\n" "Name=" APPLICATION_NAME "\n" @@ -899,7 +949,7 @@ void App::setAutoStart (bool enabled) { "Categories=Network;Telephony;\n" "MimeType=x-scheme-handler/sip-" EXECUTABLE_NAME ";x-scheme-handler/sip;x-scheme-handler/sips-" EXECUTABLE_NAME ";x-scheme-handler/sips;x-scheme-handler/tel;x-scheme-handler/callto;\n" ); - + mAutoStart = enabled; emit autoStartChanged(enabled); } @@ -909,12 +959,12 @@ void App::setAutoStart (bool enabled) { 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" @@ -924,7 +974,7 @@ void App::setAutoStart (bool enabled) { QProcess::execute(OsascriptExecutable, { "-e", "tell application \"System Events\" to delete login item \"" + getMacOsBundleName() + "\"" }); - + mAutoStart = enabled; emit autoStartChanged(enabled); } @@ -934,13 +984,13 @@ void App::setAutoStart (bool enabled) { 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; emit autoStartChanged(enabled); } @@ -962,9 +1012,9 @@ void App::openAppAfterInit (bool mustBeIconified) { core->setNatPolicy(core->getNatPolicy()); } }); - + QQuickWindow *mainWindow = getMainWindow(); - + #ifndef __APPLE__ // Enable TrayIconSystem. if (!QSystemTrayIcon::isSystemTrayAvailable()) @@ -972,21 +1022,21 @@ void App::openAppAfterInit (bool mustBeIconified) { else setTrayIcon(); #endif // ifndef __APPLE__ - + // Display Assistant if it does not exist proxy config. if (coreManager->getCore()->getAccountList().empty()) QMetaObject::invokeMethod(mainWindow, "setView", Q_ARG(QVariant, Constants::AssistantViewName), Q_ARG(QVariant, QString("")), Q_ARG(QVariant, QString(""))); - + #ifdef ENABLE_UPDATE_CHECK QTimer *timer = new QTimer(mEngine); timer->setInterval(Constants::VersionUpdateCheckInterval); - + QObject::connect(timer, &QTimer::timeout, this, &App::checkForUpdate); timer->start(); - + checkForUpdates(); #endif // ifdef ENABLE_UPDATE_CHECK - + if(setFetchConfig(mParser)) restart(); else{