File indexing completed on 2024-05-05 17:44:54

0001 /*
0002     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "waylandstartuptasksmodel.h"
0008 #include "libtaskmanager_debug.h"
0009 #include "tasktools.h"
0010 
0011 #include <KConfigGroup>
0012 #include <KConfigWatcher>
0013 #include <KWayland/Client/connection_thread.h>
0014 #include <KWayland/Client/plasmawindowmanagement.h>
0015 #include <KWayland/Client/registry.h>
0016 
0017 #include <QDebug>
0018 #include <QTimer>
0019 #include <QUrl>
0020 
0021 namespace TaskManager
0022 {
0023 class Q_DECL_HIDDEN WaylandStartupTasksModel::Private
0024 {
0025 public:
0026     Private(WaylandStartupTasksModel *q);
0027 
0028     void addActivation(KWayland::Client::PlasmaActivation *activation);
0029     void removeActivation(KWayland::Client::PlasmaActivation *activation);
0030 
0031     void init();
0032     void loadConfig();
0033 
0034     struct Startup {
0035         QString name;
0036         QIcon icon;
0037         QString applicationId;
0038         QUrl launcherUrl;
0039         KWayland::Client::PlasmaActivation *activation;
0040     };
0041 
0042     WaylandStartupTasksModel *q;
0043     KConfigWatcher::Ptr configWatcher = nullptr;
0044     KWayland::Client::PlasmaActivationFeedback *feedback = nullptr;
0045     KWayland::Client::Registry *registry = nullptr;
0046     QVector<Startup> startups;
0047     std::chrono::seconds startupTimeout = std::chrono::seconds::zero();
0048 };
0049 
0050 WaylandStartupTasksModel::Private::Private(WaylandStartupTasksModel *q)
0051     : q(q)
0052 {
0053 }
0054 
0055 void WaylandStartupTasksModel::Private::init()
0056 {
0057     configWatcher = KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("klaunchrc"), KConfig::NoGlobals));
0058     QObject::connect(configWatcher.data(), &KConfigWatcher::configChanged, q, [this] {
0059         loadConfig();
0060     });
0061 
0062     loadConfig();
0063 }
0064 
0065 void WaylandStartupTasksModel::Private::loadConfig()
0066 {
0067     KConfigGroup feedbackConfig(configWatcher->config(), "FeedbackStyle");
0068 
0069     if (!feedbackConfig.readEntry("TaskbarButton", true)) {
0070         delete feedback;
0071         feedback = nullptr;
0072         delete registry;
0073         registry = nullptr;
0074 
0075         q->beginResetModel();
0076         startups.clear();
0077         q->endResetModel();
0078         return;
0079     }
0080 
0081     const KConfigGroup taskbarButtonConfig(configWatcher->config(), "TaskbarButtonSettings");
0082     startupTimeout = std::chrono::seconds(taskbarButtonConfig.readEntry("Timeout", 5));
0083 
0084     if (!registry) {
0085         using namespace KWayland::Client;
0086 
0087         ConnectionThread *connection = ConnectionThread::fromApplication(q);
0088         if (!connection) {
0089             return;
0090         }
0091 
0092         registry = new Registry(q);
0093         registry->create(connection);
0094 
0095         QObject::connect(registry, &Registry::plasmaActivationFeedbackAnnounced, q, [this](quint32 name, quint32 version) {
0096             feedback = registry->createPlasmaActivationFeedback(name, version, q);
0097 
0098             QObject::connect(feedback, &PlasmaActivationFeedback::interfaceAboutToBeReleased, q, [this] {
0099                 q->beginResetModel();
0100                 startups.clear();
0101                 q->endResetModel();
0102             });
0103 
0104             QObject::connect(feedback, &PlasmaActivationFeedback::activation, q, [this](PlasmaActivation *activation) {
0105                 addActivation(activation);
0106             });
0107         });
0108 
0109         registry->setup();
0110     }
0111 }
0112 
0113 void WaylandStartupTasksModel::Private::addActivation(KWayland::Client::PlasmaActivation *activation)
0114 {
0115     QObject::connect(activation, &KWayland::Client::PlasmaActivation::applicationId, q, [this, activation](const QString &appId) {
0116         // The application id is guaranteed to be the desktop filename without ".desktop"
0117         const QString desktopFileName = appId + QLatin1String(".desktop");
0118         const QString desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, desktopFileName);
0119         if (desktopFilePath.isEmpty()) {
0120             qCWarning(TASKMANAGER_DEBUG) << "Got invalid activation app_id:" << appId;
0121             return;
0122         }
0123 
0124         const QUrl launcherUrl(QStringLiteral("applications:") + desktopFileName);
0125         const AppData appData = appDataFromUrl(QUrl::fromLocalFile(desktopFilePath));
0126 
0127         const int count = startups.count();
0128         q->beginInsertRows(QModelIndex(), count, count);
0129         startups.append(Startup{
0130             .name = appData.name,
0131             .icon = appData.icon,
0132             .applicationId = appId,
0133             .launcherUrl = launcherUrl,
0134             .activation = activation,
0135         });
0136         q->endInsertRows();
0137 
0138         // Remove the activation if it doesn't finish within certain time interval.
0139         QTimer *timeoutTimer = new QTimer(activation);
0140         QObject::connect(timeoutTimer, &QTimer::timeout, q, [this, activation]() {
0141             removeActivation(activation);
0142         });
0143         timeoutTimer->setSingleShot(true);
0144         timeoutTimer->start(startupTimeout);
0145     });
0146 
0147     QObject::connect(activation, &KWayland::Client::PlasmaActivation::finished, q, [this, activation]() {
0148         removeActivation(activation);
0149     });
0150 }
0151 
0152 void WaylandStartupTasksModel::Private::removeActivation(KWayland::Client::PlasmaActivation *activation)
0153 {
0154     int position = -1;
0155     for (int i = 0; i < startups.count(); ++i) {
0156         if (startups[i].activation == activation) {
0157             position = i;
0158             break;
0159         }
0160     }
0161     if (position != -1) {
0162         q->beginRemoveRows(QModelIndex(), position, position);
0163         startups.removeAt(position);
0164         q->endRemoveRows();
0165     }
0166 }
0167 
0168 WaylandStartupTasksModel::WaylandStartupTasksModel(QObject *parent)
0169     : AbstractTasksModel(parent)
0170     , d(new Private(this))
0171 {
0172     d->init();
0173 }
0174 
0175 WaylandStartupTasksModel::~WaylandStartupTasksModel()
0176 {
0177 }
0178 
0179 QVariant WaylandStartupTasksModel::data(const QModelIndex &index, int role) const
0180 {
0181     if (!index.isValid() || index.row() >= d->startups.count()) {
0182         return QVariant();
0183     }
0184 
0185     const auto &data = d->startups[index.row()];
0186     if (role == Qt::DisplayRole) {
0187         return data.name;
0188     } else if (role == Qt::DecorationRole) {
0189         return data.icon;
0190     } else if (role == AppId) {
0191         return data.applicationId;
0192     } else if (role == AppName) {
0193         return data.name;
0194     } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) {
0195         return data.launcherUrl;
0196     } else if (role == IsStartup) {
0197         return true;
0198     } else if (role == CanLaunchNewInstance) {
0199         return false;
0200     }
0201 
0202     return QVariant();
0203 }
0204 
0205 int WaylandStartupTasksModel::rowCount(const QModelIndex &parent) const
0206 {
0207     return parent.isValid() ? 0 : d->startups.count();
0208 }
0209 
0210 } // namespace TaskManager