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

0001 /*
0002     SPDX-FileCopyrightText: 2016 Eike Hein <hein@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 "xstartuptasksmodel.h"
0008 
0009 #include <KApplicationTrader>
0010 #include <KConfig>
0011 #include <KConfigGroup>
0012 #include <KDirWatch>
0013 #include <KService>
0014 #include <KStartupInfo>
0015 
0016 #include <QIcon>
0017 #include <QTimer>
0018 #include <QUrl>
0019 
0020 namespace TaskManager
0021 {
0022 class Q_DECL_HIDDEN XStartupTasksModel::Private
0023 {
0024 public:
0025     Private(XStartupTasksModel *q);
0026     KDirWatch *configWatcher = nullptr;
0027     KStartupInfo *startupInfo = nullptr;
0028     QVector<KStartupInfoId> startups;
0029     QHash<QByteArray, KStartupInfoData> startupData;
0030     QHash<QByteArray, QUrl> launcherUrls;
0031 
0032     void init();
0033     void loadConfig();
0034     QUrl launcherUrl(const KStartupInfoData &data);
0035 
0036 private:
0037     XStartupTasksModel *q;
0038 };
0039 
0040 XStartupTasksModel::Private::Private(XStartupTasksModel *q)
0041     : q(q)
0042 {
0043 }
0044 
0045 void XStartupTasksModel::Private::init()
0046 {
0047     configWatcher = new KDirWatch(q);
0048     configWatcher->addFile(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/klaunchrc"));
0049 
0050     QObject::connect(configWatcher, &KDirWatch::dirty, [this] {
0051         loadConfig();
0052     });
0053     QObject::connect(configWatcher, &KDirWatch::created, [this] {
0054         loadConfig();
0055     });
0056     QObject::connect(configWatcher, &KDirWatch::deleted, [this] {
0057         loadConfig();
0058     });
0059 
0060     loadConfig();
0061 }
0062 
0063 void XStartupTasksModel::Private::loadConfig()
0064 {
0065     const KConfig _c("klaunchrc");
0066     KConfigGroup c(&_c, "FeedbackStyle");
0067 
0068     if (!c.readEntry("TaskbarButton", true)) {
0069         delete startupInfo;
0070         startupInfo = nullptr;
0071 
0072         q->beginResetModel();
0073         startups.clear();
0074         startupData.clear();
0075         q->endResetModel();
0076 
0077         return;
0078     }
0079 
0080     if (!startupInfo) {
0081         startupInfo = new KStartupInfo(KStartupInfo::CleanOnCantDetect, q);
0082 
0083         QObject::connect(startupInfo, &KStartupInfo::gotNewStartup, q, [this](const KStartupInfoId &id, const KStartupInfoData &data) {
0084             if (startups.contains(id)) {
0085                 return;
0086             }
0087 
0088             const QString appId = data.applicationId();
0089             const QString bin = data.bin();
0090 
0091             foreach (const KStartupInfoData &known, startupData) {
0092                 // Reject if we already have a startup notification for this app.
0093                 if (known.applicationId() == appId && known.bin() == bin) {
0094                     return;
0095                 }
0096             }
0097 
0098             const int count = startups.count();
0099             q->beginInsertRows(QModelIndex(), count, count);
0100             startups.append(id);
0101             startupData.insert(id.id(), data);
0102             launcherUrls.insert(id.id(), launcherUrl(data));
0103             q->endInsertRows();
0104         });
0105 
0106         QObject::connect(startupInfo, &KStartupInfo::gotRemoveStartup, q, [this](const KStartupInfoId &id) {
0107             // The order in which startups are cancelled and corresponding
0108             // windows appear is not reliable. Add some grace time to make
0109             // an overlap more likely, giving a proxy some time to arbitrate
0110             // between the two.
0111             QTimer::singleShot(500, q, [this, id]() {
0112                 const int row = startups.indexOf(id);
0113 
0114                 if (row != -1) {
0115                     q->beginRemoveRows(QModelIndex(), row, row);
0116                     startups.removeAt(row);
0117                     startupData.remove(id.id());
0118                     launcherUrls.remove(id.id());
0119                     q->endRemoveRows();
0120                 }
0121             });
0122         });
0123 
0124         QObject::connect(startupInfo, &KStartupInfo::gotStartupChange, q, [this](const KStartupInfoId &id, const KStartupInfoData &data) {
0125             const int row = startups.indexOf(id);
0126             if (row != -1) {
0127                 startupData.insert(id.id(), data);
0128                 launcherUrls.insert(id.id(), launcherUrl(data));
0129                 QModelIndex idx = q->index(row);
0130                 Q_EMIT q->dataChanged(idx, idx);
0131             }
0132         });
0133     }
0134 
0135     c = KConfigGroup(&_c, "TaskbarButtonSettings");
0136     startupInfo->setTimeout(c.readEntry("Timeout", 5));
0137 }
0138 
0139 QUrl XStartupTasksModel::Private::launcherUrl(const KStartupInfoData &data)
0140 {
0141     QUrl launcherUrl;
0142     KService::List services;
0143 
0144     QString appId = data.applicationId();
0145 
0146     // Try to match via desktop filename ...
0147     if (!appId.isEmpty() && appId.endsWith(QLatin1String(".desktop"))) {
0148         if (appId.startsWith(QLatin1String("/"))) {
0149             // Even if we have an absolute path, try resolving to a service first (Bug 385594)
0150             KService::Ptr service = KService::serviceByDesktopPath(appId);
0151             if (!service) { // No luck, just return it verbatim
0152                 launcherUrl = QUrl::fromLocalFile(appId);
0153                 return launcherUrl;
0154             }
0155 
0156             // Fall-through to menuId() handling below
0157             services = {service};
0158         } else {
0159             // turn into KService desktop entry name
0160             appId.chop(strlen(".desktop"));
0161 
0162             services = KApplicationTrader::query([&appId](const KService::Ptr &service) {
0163                 return service->desktopEntryName().compare(appId, Qt::CaseInsensitive) == 0;
0164             });
0165         }
0166     }
0167 
0168     const QString wmClass = data.WMClass();
0169 
0170     // Try StartupWMClass.
0171     if (services.empty() && !wmClass.isEmpty()) {
0172         services = KApplicationTrader::query([&wmClass](const KService::Ptr &service) {
0173             return service->property(QStringLiteral("StartupWMClass")).toString().compare(wmClass, Qt::CaseInsensitive) == 0;
0174         });
0175     }
0176 
0177     const QString name = data.findName();
0178 
0179     // Try via name ...
0180     if (services.empty() && !name.isEmpty()) {
0181         services = KApplicationTrader::query([&name](const KService::Ptr &service) {
0182             return service->name().compare(name, Qt::CaseInsensitive) == 0;
0183         });
0184     }
0185 
0186     if (!services.empty()) {
0187         const QString &menuId = services.at(0)->menuId();
0188 
0189         // applications: URLs are used to refer to applications by their KService::menuId
0190         // (i.e. .desktop file name) rather than the absolute path to a .desktop file.
0191         if (!menuId.isEmpty()) {
0192             return QUrl(QStringLiteral("applications:") + menuId);
0193         }
0194 
0195         QString path = services.at(0)->entryPath();
0196 
0197         if (path.isEmpty()) {
0198             path = services.at(0)->exec();
0199         }
0200 
0201         if (!path.isEmpty()) {
0202             launcherUrl = QUrl::fromLocalFile(path);
0203         }
0204     }
0205 
0206     return launcherUrl;
0207 }
0208 
0209 XStartupTasksModel::XStartupTasksModel(QObject *parent)
0210     : AbstractTasksModel(parent)
0211     , d(new Private(this))
0212 {
0213     d->init();
0214 }
0215 
0216 XStartupTasksModel::~XStartupTasksModel()
0217 {
0218 }
0219 
0220 QVariant XStartupTasksModel::data(const QModelIndex &index, int role) const
0221 {
0222     if (!index.isValid() || index.row() >= d->startups.count()) {
0223         return QVariant();
0224     }
0225 
0226     const QByteArray &id = d->startups.at(index.row()).id();
0227 
0228     if (!d->startupData.contains(id)) {
0229         return QVariant();
0230     }
0231 
0232     const KStartupInfoData &data = d->startupData.value(id);
0233 
0234     if (role == Qt::DisplayRole) {
0235         return data.findName();
0236     } else if (role == Qt::DecorationRole) {
0237         return QIcon::fromTheme(data.findIcon(), QIcon::fromTheme(QLatin1String("unknown")));
0238     } else if (role == AppId) {
0239         QString idFromPath = QUrl::fromLocalFile(data.applicationId()).fileName();
0240 
0241         if (idFromPath.endsWith(QLatin1String(".desktop"))) {
0242             idFromPath = idFromPath.left(idFromPath.length() - 8);
0243         }
0244 
0245         return idFromPath;
0246     } else if (role == AppName) {
0247         return data.findName();
0248     } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) {
0249         return d->launcherUrls.value(id);
0250     } else if (role == IsStartup) {
0251         return true;
0252     } else if (role == IsVirtualDesktopsChangeable) {
0253         return false;
0254     } else if (role == VirtualDesktops) {
0255         return QVariantList() << QVariant(data.desktop());
0256     } else if (role == IsOnAllVirtualDesktops) {
0257         return (data.desktop() == 0);
0258     } else if (role == CanLaunchNewInstance) {
0259         return false;
0260     }
0261 
0262     return QVariant();
0263 }
0264 
0265 int XStartupTasksModel::rowCount(const QModelIndex &parent) const
0266 {
0267     return parent.isValid() ? 0 : d->startups.count();
0268 }
0269 
0270 } // namespace TaskManager