/* SPDX-FileCopyrightText: 2016 Eike Hein SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "xstartuptasksmodel.h" #include #include #include #include #include #include #include #include #include using namespace Qt::StringLiterals; namespace TaskManager { class Q_DECL_HIDDEN XStartupTasksModel::Private { public: Private(XStartupTasksModel *q); KDirWatch *configWatcher = nullptr; KStartupInfo *startupInfo = nullptr; QList startups; QHash startupData; QHash launcherUrls; void init(); void loadConfig(); QUrl launcherUrl(const KStartupInfoData &data); private: XStartupTasksModel *q; }; XStartupTasksModel::Private::Private(XStartupTasksModel *q) : q(q) { } void XStartupTasksModel::Private::init() { configWatcher = new KDirWatch(q); configWatcher->addFile(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/klaunchrc")); QObject::connect(configWatcher, &KDirWatch::dirty, [this] { loadConfig(); }); QObject::connect(configWatcher, &KDirWatch::created, [this] { loadConfig(); }); QObject::connect(configWatcher, &KDirWatch::deleted, [this] { loadConfig(); }); loadConfig(); } void XStartupTasksModel::Private::loadConfig() { const KConfig _c(u"klaunchrc"_s); KConfigGroup c(&_c, u"FeedbackStyle"_s); if (!c.readEntry("TaskbarButton", true)) { delete startupInfo; startupInfo = nullptr; q->beginResetModel(); startups.clear(); startupData.clear(); q->endResetModel(); return; } if (!startupInfo) { startupInfo = new KStartupInfo(KStartupInfo::CleanOnCantDetect, q); QObject::connect(startupInfo, &KStartupInfo::gotNewStartup, q, [this](const KStartupInfoId &id, const KStartupInfoData &data) { if (startups.contains(id)) { return; } const QString appId = data.applicationId(); const QString bin = data.bin(); for (const KStartupInfoData &known : std::as_const(startupData)) { // Reject if we already have a startup notification for this app. if (known.applicationId() == appId && known.bin() == bin) { return; } } const int count = startups.count(); q->beginInsertRows(QModelIndex(), count, count); startups.append(id); startupData.insert(id.id(), data); launcherUrls.insert(id.id(), launcherUrl(data)); q->endInsertRows(); }); QObject::connect(startupInfo, &KStartupInfo::gotRemoveStartup, q, [this](const KStartupInfoId &id) { // The order in which startups are cancelled and corresponding // windows appear is not reliable. Add some grace time to make // an overlap more likely, giving a proxy some time to arbitrate // between the two. QTimer::singleShot(500, q, [this, id]() { const int row = startups.indexOf(id); if (row != -1) { q->beginRemoveRows(QModelIndex(), row, row); startups.removeAt(row); startupData.remove(id.id()); launcherUrls.remove(id.id()); q->endRemoveRows(); } }); }); QObject::connect(startupInfo, &KStartupInfo::gotStartupChange, q, [this](const KStartupInfoId &id, const KStartupInfoData &data) { const int row = startups.indexOf(id); if (row != -1) { startupData.insert(id.id(), data); launcherUrls.insert(id.id(), launcherUrl(data)); QModelIndex idx = q->index(row); Q_EMIT q->dataChanged(idx, idx); } }); } c = KConfigGroup(&_c, u"TaskbarButtonSettings"_s); startupInfo->setTimeout(c.readEntry("Timeout", 5)); } QUrl XStartupTasksModel::Private::launcherUrl(const KStartupInfoData &data) { QUrl launcherUrl; KService::List services; QString appId = data.applicationId(); // Try to match via desktop filename ... if (!appId.isEmpty() && appId.endsWith(QLatin1String(".desktop"))) { if (appId.startsWith(QLatin1Char('/'))) { // Even if we have an absolute path, try resolving to a service first (Bug 385594) KService::Ptr service = KService::serviceByDesktopPath(appId); if (!service) { // No luck, just return it verbatim launcherUrl = QUrl::fromLocalFile(appId); return launcherUrl; } // Fall-through to menuId() handling below services = {service}; } else { // turn into KService desktop entry name appId.chop(strlen(".desktop")); services = KApplicationTrader::query([&appId](const KService::Ptr &service) { return service->desktopEntryName().compare(appId, Qt::CaseInsensitive) == 0; }); } } const QString wmClass = QString::fromLocal8Bit(data.WMClass()); // Try StartupWMClass. if (services.empty() && !wmClass.isEmpty()) { services = KApplicationTrader::query([&wmClass](const KService::Ptr &service) { return service->property(QStringLiteral("StartupWMClass")).compare(wmClass, Qt::CaseInsensitive) == 0; }); } const QString name = data.findName(); // Try via name ... if (services.empty() && !name.isEmpty()) { services = KApplicationTrader::query([&name](const KService::Ptr &service) { return service->name().compare(name, Qt::CaseInsensitive) == 0; }); } if (!services.empty()) { const QString &menuId = services.at(0)->menuId(); // applications: URLs are used to refer to applications by their KService::menuId // (i.e. .desktop file name) rather than the absolute path to a .desktop file. if (!menuId.isEmpty()) { return QUrl(QString(u"applications:" + menuId)); } QString path = services.at(0)->entryPath(); if (path.isEmpty()) { path = services.at(0)->exec(); } if (!path.isEmpty()) { launcherUrl = QUrl::fromLocalFile(path); } } return launcherUrl; } XStartupTasksModel::XStartupTasksModel(QObject *parent) : AbstractTasksModel(parent) , d(new Private(this)) { d->init(); } XStartupTasksModel::~XStartupTasksModel() { } QVariant XStartupTasksModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= d->startups.count()) { return QVariant(); } const QByteArray &id = d->startups.at(index.row()).id(); if (!d->startupData.contains(id)) { return QVariant(); } const KStartupInfoData &data = d->startupData.value(id); if (role == Qt::DisplayRole) { return data.findName(); } else if (role == Qt::DecorationRole) { return QIcon::fromTheme(data.findIcon(), QIcon::fromTheme(QLatin1String("unknown"))); } else if (role == AppId) { QString idFromPath = QUrl::fromLocalFile(data.applicationId()).fileName(); if (idFromPath.endsWith(QLatin1String(".desktop"))) { idFromPath = idFromPath.left(idFromPath.length() - 8); } return idFromPath; } else if (role == AppName) { return data.findName(); } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) { return d->launcherUrls.value(id); } else if (role == IsStartup) { return true; } else if (role == IsVirtualDesktopsChangeable) { return false; } else if (role == VirtualDesktops) { return QVariantList() << QVariant(data.desktop()); } else if (role == IsOnAllVirtualDesktops) { return (data.desktop() == 0); } else if (role == CanLaunchNewInstance) { return false; } return AbstractTasksModel::data(index, role); } int XStartupTasksModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : d->startups.count(); } } // namespace TaskManager