File indexing completed on 2024-05-05 05:38:39

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