File indexing completed on 2024-04-14 04:51:43

0001 /*
0002  * SPDX-FileCopyrightText: 2019 Weixuan XIAO <veyx.shaw@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include <QApplication>
0008 #include <QDebug>
0009 #include <QFile>
0010 #include <QIcon>
0011 #include <QMessageBox>
0012 #include <QStandardPaths>
0013 #include <QThread>
0014 
0015 #include <KLocalizedString>
0016 
0017 #include <dbushelper.h>
0018 
0019 #include "indicatorhelper.h"
0020 
0021 #include "serviceregister_mac.h"
0022 
0023 #include <kdeconnectconfig.h>
0024 
0025 IndicatorHelper::IndicatorHelper()
0026 {
0027     registerServices();
0028 
0029     // Use a hardcoded QPixmap because QIcon::fromTheme will instantiate a QPlatformTheme theme
0030     // which could try to use DBus before we have started it and cache an invalid DBus session
0031     // in QDBusConnectionManager
0032     const QString iconPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdeconnect-icons"), QStandardPaths::LocateDirectory);
0033     QPixmap splashPixmap(iconPath + QStringLiteral("/hicolor/scalable/apps/kdeconnect.svg"));
0034     m_splashScreen = new QSplashScreen(splashPixmap);
0035 
0036     // Icon is white, set the text color to black
0037     m_splashScreen->showMessage(i18n("Launching") + QStringLiteral("\n"), Qt::AlignHCenter | Qt::AlignBottom, Qt::black);
0038     m_splashScreen->show();
0039 }
0040 
0041 IndicatorHelper::~IndicatorHelper()
0042 {
0043     if (m_splashScreen != nullptr) {
0044         delete m_splashScreen;
0045         m_splashScreen = nullptr;
0046     }
0047 }
0048 
0049 void IndicatorHelper::preInit()
0050 {
0051 }
0052 
0053 void IndicatorHelper::postInit()
0054 {
0055     m_splashScreen->finish(nullptr);
0056 }
0057 
0058 void IndicatorHelper::iconPathHook()
0059 {
0060     const QString iconPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdeconnect-icons"), QStandardPaths::LocateDirectory);
0061     if (!iconPath.isNull()) {
0062         QStringList themeSearchPaths = QIcon::themeSearchPaths();
0063         themeSearchPaths << iconPath;
0064         QIcon::setThemeSearchPaths(themeSearchPaths);
0065     }
0066 }
0067 
0068 int IndicatorHelper::daemonHook(QProcess &kdeconnectd)
0069 {
0070     // This flag marks whether a session DBus daemon is installed and run
0071     bool hasUsableSessionBus = true;
0072     // Use another bus instance for detecting, avoid session bus cache in Qt
0073     if (!QDBusConnection::connectToBus(QDBusConnection::SessionBus, QStringLiteral("kdeconnect-test-client")).isConnected()) {
0074         qDebug() << "Default session bus not detected, will use private D-Bus.";
0075 
0076         // Unset launchctl env and private dbus addr file, avoid block
0077         DBusHelper::macosUnsetLaunchctlEnv();
0078         QFile privateDBusAddressFile(KdeConnectConfig::instance().privateDBusAddressPath());
0079         if (privateDBusAddressFile.exists())
0080             privateDBusAddressFile.resize(0);
0081 
0082         // Update session bus usability state
0083         hasUsableSessionBus = false;
0084     }
0085 
0086     // Start daemon
0087     m_splashScreen->showMessage(i18n("Launching daemon") + QStringLiteral("\n"), Qt::AlignHCenter | Qt::AlignBottom, Qt::black);
0088 
0089     // Here we will try to bring our private session D-Bus
0090     if (!hasUsableSessionBus) {
0091         qDebug() << "Launching private session D-Bus.";
0092         DBusHelper::macosUnsetLaunchctlEnv();
0093         DBusHelper::launchDBusDaemon();
0094         // Wait for dbus daemon env
0095         QProcess getLaunchdDBusEnv;
0096         m_splashScreen->showMessage(i18n("Waiting D-Bus") + QStringLiteral("\n"), Qt::AlignHCenter | Qt::AlignBottom, Qt::black);
0097         int retry = 0;
0098         getLaunchdDBusEnv.setProgram(QStringLiteral("launchctl"));
0099         getLaunchdDBusEnv.setArguments({QStringLiteral("getenv"), QStringLiteral(KDECONNECT_SESSION_DBUS_LAUNCHD_ENV)});
0100         getLaunchdDBusEnv.start();
0101         getLaunchdDBusEnv.waitForFinished();
0102 
0103         QString launchdDBusEnv = QString::fromLocal8Bit(getLaunchdDBusEnv.readAllStandardOutput());
0104 
0105         if (!launchdDBusEnv.isEmpty() && QDBusConnection::sessionBus().isConnected()) {
0106             qDebug() << "Private D-Bus daemon launched and connected.";
0107             hasUsableSessionBus = true;
0108         } else if (!launchdDBusEnv.isEmpty()) {
0109             // Show a warning and exit
0110             qCritical() << "Invalid " << KDECONNECT_SESSION_DBUS_LAUNCHD_ENV << "env: \"" << launchdDBusEnv << "\"";
0111 
0112             QMessageBox::critical(nullptr,
0113                                   i18n("KDE Connect"),
0114                                   i18n("Cannot connect to DBus\n"
0115                                        "KDE Connect will quit"),
0116                                   QMessageBox::Abort,
0117                                   QMessageBox::Abort);
0118             // End the program
0119             return -1;
0120         } else {
0121             // Show a warning and exit
0122             qCritical() << "Fail to get launchctl" << KDECONNECT_SESSION_DBUS_LAUNCHD_ENV << "env";
0123 
0124             QMessageBox::critical(nullptr,
0125                                   i18n("KDE Connect"),
0126                                   i18n("Cannot connect to DBus\n"
0127                                        "KDE Connect will quit"),
0128                                   QMessageBox::Abort,
0129                                   QMessageBox::Abort);
0130             return -2;
0131         }
0132 
0133         // After D-Bus setting up, everything should go fine
0134         QIcon kdeconnectIcon = QIcon::fromTheme(QStringLiteral("kdeconnect"));
0135         m_splashScreen->setPixmap(QPixmap(kdeconnectIcon.pixmap(256, 256)));
0136     }
0137 
0138     // Start kdeconnectd, the daemon will not duplicate when there is already one
0139     if (QString daemon = QCoreApplication::applicationDirPath() + QLatin1String("/kdeconnectd"); QFile::exists(daemon)) {
0140         kdeconnectd.setProgram(daemon);
0141     } else if (QString daemon = QLatin1String(qgetenv("craftRoot")) + QLatin1String("/../lib/libexec/kdeconnectd"); QFile::exists(daemon)) {
0142         kdeconnectd.setProgram(daemon);
0143     } else {
0144         QMessageBox::critical(nullptr, i18n("KDE Connect"), i18n("Cannot find kdeconnectd"), QMessageBox::Abort, QMessageBox::Abort);
0145         return -1;
0146     }
0147     kdeconnectd.startDetached();
0148 
0149     m_splashScreen->showMessage(i18n("Loading modules") + QStringLiteral("\n"), Qt::AlignHCenter | Qt::AlignBottom, Qt::white);
0150 
0151     return 0;
0152 }
0153 
0154 void IndicatorHelper::systrayIconHook(KStatusNotifierItem &systray)
0155 {
0156     const QString iconPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdeconnect-icons"), QStandardPaths::LocateDirectory);
0157     if (!iconPath.isNull()) {
0158         auto icon = QIcon::fromTheme(QStringLiteral("kdeconnectindicator"));
0159         icon.setIsMask(true); // Make icon adapt to menu bar color
0160         systray.setIconByPixmap(icon);
0161     } else {
0162         // We are in macOS dev env, just continue
0163         qWarning() << "Fail to find indicator icon, continue anyway";
0164     }
0165 }