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