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