File indexing completed on 2024-12-15 05:02:05
0001 /* 0002 SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)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 "ServicesRunner.h" 0008 0009 #include <QDebug> 0010 0011 #include <KService> 0012 #include <KServiceTypeTrader> 0013 #include <KLocalizedString> 0014 #include <KPluginFactory> 0015 0016 // BLADE_EXPORT_PLUGIN(servicesrunner, ServicesRunner, "blade-plugin-services.json") 0017 0018 ServicesRunner::ServicesRunner(QObject *parent, const QVariantList &args) 0019 : AbstractRunner(parent) 0020 { 0021 Q_UNUSED(args) 0022 } 0023 0024 ServicesRunner::~ServicesRunner() 0025 { 0026 } 0027 0028 void ServicesRunner::cancelQuery() 0029 { 0030 AbstractRunner::cancelQuery(); 0031 m_seen.clear(); 0032 } 0033 0034 void ServicesRunner::query() 0035 { 0036 Q_EMIT startedProcessingQuery(); 0037 0038 ResultList results; 0039 0040 QString term = queryString(); 0041 0042 QSet<QString> seen; 0043 QString query; 0044 0045 if (term.length() > 1) { 0046 // Search for applications which are executable and case-insensitively match the search term 0047 // See http://techbase.kde.org/Development/Tutorials/Services/Traders#The_KTrader_Query_Language 0048 // if the following is unclear to you. 0049 query = QStringLiteral("exist Exec and ('%1' =~ Name)").arg(term); 0050 const KService::List services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), query); 0051 0052 for (const KService::Ptr &service: services) { 0053 qDebug() << service->name() << "is an exact match!" << service->storageId() << service->exec(); 0054 if (!service->noDisplay() && service->property(QStringLiteral("NotShowIn"), QVariant::String) != "KDE") { 0055 Result result; 0056 // result.setType(Plasma::QueryMatch::ExactMatch); 0057 setupResult(service, result); 0058 result.relevance = 1; 0059 results << result; 0060 seen.insert(service->storageId()); 0061 seen.insert(service->exec()); 0062 } 0063 } 0064 } 0065 0066 // If the term length is < 3, no real point searching the Keywords and GenericName 0067 if (term.length() < 3) { 0068 query = QStringLiteral("exist Exec and ( (exist Name and '%1' ~~ Name) or ('%1' ~~ Exec) )").arg(term); 0069 } else { 0070 // Search for applications which are executable and the term case-insensitive matches any of 0071 // * a substring of one of the keywords 0072 // * a substring of the GenericName field 0073 // * a substring of the Name field 0074 // Note that before asking for the content of e.g. Keywords and GenericName we need to ask if 0075 // they exist to prevent a tree evaluation error if they are not defined. 0076 query = QStringLiteral("exist Exec and ( (exist Keywords and '%1' ~subin Keywords) or (exist GenericName and '%1' ~~ GenericName) or (exist Name and '%1' ~~ Name) or ('%1' ~~ Exec) )").arg(term); 0077 } 0078 0079 KService::List services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), query); 0080 services += KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), query); 0081 0082 qDebug() << "got " << services.count() << " services from " << query; 0083 for (const KService::Ptr &service: services) { 0084 if (service->noDisplay()) { 0085 continue; 0086 } 0087 0088 const QString id = service->storageId(); 0089 const QString name = service->desktopEntryName(); 0090 const QString exec = service->exec(); 0091 0092 if (seen.contains(id) || seen.contains(exec)) { 0093 qDebug() << "already seen" << id << exec; 0094 continue; 0095 } 0096 0097 qDebug() << "haven't seen" << id << "so processing now"; 0098 seen.insert(id); 0099 seen.insert(exec); 0100 0101 Result result; 0102 // result.setType(Plasma::QueryMatch::PossibleMatch); 0103 setupResult(service, result); 0104 qreal relevance(0.6); 0105 0106 // If the term was < 3 chars and NOT at the beginning of the App's name or Exec, then 0107 // chances are the user doesn't want that app. 0108 if (term.length() < 3) { 0109 if (name.startsWith(term) || exec.startsWith(term)) { 0110 relevance = 0.9; 0111 } else { 0112 continue; 0113 } 0114 } else if (service->name().contains(term, Qt::CaseInsensitive)) { 0115 relevance = 0.8; 0116 0117 if (service->name().startsWith(term, Qt::CaseInsensitive)) { 0118 relevance += 0.1; 0119 } 0120 } else if (service->genericName().contains(term, Qt::CaseInsensitive)) { 0121 relevance = 0.65; 0122 0123 if (service->genericName().startsWith(term, Qt::CaseInsensitive)) { 0124 relevance += 0.05; 0125 } 0126 } else if (service->exec().contains(term, Qt::CaseInsensitive)) { 0127 relevance = 0.7; 0128 0129 if (service->exec().startsWith(term, Qt::CaseInsensitive)) { 0130 relevance += 0.05; 0131 } 0132 } 0133 0134 if (service->categories().contains(QStringLiteral("KDE")) || service->serviceTypes().contains(QStringLiteral("KCModule"))) { 0135 // qDebug() << "found a kde thing" << id << match.subtext() << relevance; 0136 if (id.startsWith(QLatin1String("kde-"))) { 0137 qDebug() << "old" << service->name(); 0138 if (!service->isApplication()) { 0139 // avoid showing old kcms and what not 0140 continue; 0141 } 0142 0143 // This is an older version, let's disambiguate it 0144 QString subtext(QStringLiteral("KDE3")); 0145 0146 if (!result.description.isEmpty()) { 0147 subtext.append(", " + result.description); 0148 } 0149 0150 result.description = subtext; 0151 0152 } else { 0153 relevance += .09; 0154 } 0155 } 0156 0157 qDebug() << service->name() << "is this relevant:" << relevance; 0158 result.relevance = relevance; 0159 // if (service->serviceTypes().contains(QStringLiteral("KCModule"))) { 0160 // result.matchCategory = i18n("System Settings"); 0161 // } 0162 results << result; 0163 } 0164 0165 //search for applications whose categories contains the query 0166 query = QStringLiteral("exist Exec and (exist Categories and '%1' ~subin Categories)").arg(term); 0167 services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), query); 0168 0169 for (const KService::Ptr &service: services) { 0170 qDebug() << service->name() << "is an exact match!" << service->storageId() << service->exec(); 0171 if (!service->noDisplay()) { 0172 QString id = service->storageId(); 0173 QString exec = service->exec(); 0174 if (seen.contains(id) || seen.contains(exec)) { 0175 qDebug() << "already seen" << id << exec; 0176 continue; 0177 } 0178 Result result; 0179 // result.setType(Plasma::QueryMatch::PossibleMatch); 0180 setupResult(service, result); 0181 0182 qreal relevance = 0.6; 0183 if (service->categories().contains(QStringLiteral("X-KDE-More")) || 0184 !service->showInCurrentDesktop()) { 0185 relevance = 0.5; 0186 } 0187 0188 if (service->isApplication()) { 0189 relevance += .04; 0190 } 0191 0192 result.relevance = relevance; 0193 results << result; 0194 } 0195 } 0196 0197 // search for jump list actions 0198 if (term.length() >= 3) { 0199 query = QStringLiteral("exist Actions"); // doesn't work 0200 services = KServiceTypeTrader::self()->query(QStringLiteral("Application"));//, query); 0201 0202 for (const KService::Ptr &service: services) { 0203 if (service->noDisplay()) { 0204 continue; 0205 } 0206 0207 for (const KServiceAction &action: service->actions()) { 0208 if (action.text().isEmpty() || action.exec().isEmpty() || seen.contains(action.exec())) { 0209 continue; 0210 } 0211 0212 if (!action.text().contains(term, Qt::CaseInsensitive)) { 0213 continue; 0214 } 0215 0216 Result result; 0217 // result.setType(Plasma::QueryMatch::HelperMatch); 0218 if (!action.icon().isEmpty()) { 0219 result.icon = action.icon(); 0220 } else { 0221 result.icon = service->icon(); 0222 } 0223 result.title = i18nc("Jump list search result, %1 is action (eg. open new tab, %2 is application (eg. browser)", 0224 "%1 - %2", action.text(), service->name()); 0225 // result.data = action.exec(); 0226 0227 qreal relevance = 0.5; 0228 if (action.text().startsWith(term, Qt::CaseInsensitive)) { 0229 relevance += 0.05; 0230 } 0231 0232 result.relevance = relevance; 0233 0234 results << result; 0235 } 0236 } 0237 } 0238 0239 Q_EMIT reportNewResults(results); 0240 0241 Q_EMIT finishedProcessingQuery(); 0242 } 0243 0244 void ServicesRunner::setupResult(const KService::Ptr &service, Result &result) 0245 { 0246 const auto name = service->name(); 0247 0248 result.title = name; 0249 result.url = QUrl("application:/" + service->storageId()); 0250 // result.data = service->storageId(); 0251 // result.resultId = service->storageId(); 0252 0253 result.description = 0254 (!service->genericName().isEmpty() && service->genericName() != name) ? 0255 service->genericName() 0256 : (!service->comment().isEmpty()) ? 0257 service->comment() 0258 : result.description; 0259 0260 result.matchedText = result.title + " " + result.description; 0261 0262 if (!service->icon().isEmpty()) { 0263 result.icon = service->icon(); 0264 } 0265 } 0266 0267 #include "ServicesRunner.moc" 0268