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