File indexing completed on 2024-04-28 05:52:24
0001 /* 0002 * Copyright 2010-2012 Bart Kroon <bart@tarmack.eu> 0003 * Copyright 2012, 2013 Martin Sandsmark <martin.sandsmark@kde.org> 0004 * 0005 * Redistribution and use in source and binary forms, with or without 0006 * modification, are permitted provided that the following conditions 0007 * are met: 0008 * 0009 * 1. Redistributions of source code must retain the above copyright 0010 * notice, this list of conditions and the following disclaimer. 0011 * 2. Redistributions in binary form must reproduce the above copyright 0012 * notice, this list of conditions and the following disclaimer in the 0013 * documentation and/or other materials provided with the distribution. 0014 * 0015 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 0016 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 0017 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 0018 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 0019 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 0020 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 0021 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 0022 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 0023 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 0024 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 0025 */ 0026 0027 #include "Applications.h" 0028 0029 #include <QDebug> 0030 #include <KLocalizedString> 0031 #include <QProcess> 0032 #include <QSettings> 0033 #include <QRegularExpression> 0034 #include <QFileInfo> 0035 #include <QDateTime> 0036 #include <QFileSystemWatcher> 0037 #include <QStandardPaths> 0038 #include <QDir> 0039 0040 Applications::Applications(QObject *parent) : 0041 Provider(parent), 0042 m_fsWatcher(new QFileSystemWatcher(this)) 0043 { 0044 for (const QString &dirPath : QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)) { 0045 m_fsWatcher->addPath(dirPath); 0046 loadDir(dirPath); 0047 } 0048 for (const QString &dirPath : QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)) { 0049 m_fsWatcher->addPath(dirPath + QStringLiteral("/kservices5/")); 0050 loadDir(dirPath + QStringLiteral("/kservices5/")); // kcm files 0051 } 0052 0053 connect(m_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &Applications::onDirectoryChanged); 0054 } 0055 0056 Applications::~Applications() 0057 { 0058 } 0059 0060 ProviderResult *Applications::createApp(const Application &service) 0061 { 0062 ProviderResult *app = new ProviderResult; 0063 app->name = service.name; 0064 app->completion = app->name; 0065 app->icon = service.icon; 0066 app->object = this; 0067 0068 QString exec = service.exec; 0069 exec.remove(QRegularExpression("\\%[fFuUdDnNickvm]")); 0070 0071 app->program = exec; 0072 app->priority = service.lastModified; 0073 0074 if (service.exec.contains("kcmshell")) { // so sue me 0075 app->type = i18n("Open control module"); 0076 } else { 0077 app->type = i18n("Run application"); 0078 } 0079 0080 return app; 0081 } 0082 0083 QList<ProviderResult *> Applications::getResults(QString term) 0084 { 0085 QList<ProviderResult*> list; 0086 term = term.toLower(); // we lowercase the keywords when indexing 0087 0088 qint64 currentSecsSinceEpoch = QDateTime::currentSecsSinceEpoch(); 0089 0090 for (const Application &application : m_applications) { 0091 if (!application.keywords.contains(term)) { 0092 continue; 0093 } 0094 ProviderResult *app = createApp(application); 0095 if (app->name.isEmpty()) { 0096 delete app; 0097 continue; 0098 } 0099 app->priority = currentSecsSinceEpoch - app->priority; 0100 //if (service->isApplication()) app->priority *= 1.1; 0101 0102 list.append(app); 0103 } 0104 return list; 0105 } 0106 0107 int Applications::launch(const QString &selected) 0108 { 0109 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 0110 if (QProcess::startDetached(selected)) { 0111 return 0; 0112 } else { 0113 return 1; 0114 } 0115 #else 0116 const QStringList commandAndArgs = QProcess::splitCommand(selected); 0117 0118 if (QProcess::startDetached(commandAndArgs[0], commandAndArgs.mid(1))) { 0119 return 0; 0120 } else { 0121 return 1; 0122 } 0123 #endif 0124 } 0125 0126 void Applications::onDirectoryChanged(const QString &path) 0127 { 0128 QMutableHashIterator<QString, Application> it(m_applications); 0129 while (it.hasNext()) { 0130 it.next(); 0131 if (it.key().startsWith(path)) { 0132 it.remove(); 0133 } 0134 } 0135 loadDir(path); 0136 } 0137 0138 void Applications::loadDir(const QString &path) 0139 { 0140 for (const QFileInfo &file : QDir(path).entryInfoList(QStringList("*.desktop"))) { 0141 Application app = loadDesktopFile(file); 0142 if (!app.isValid()) { 0143 continue; 0144 } 0145 m_applications[file.absoluteFilePath()] = app; 0146 } 0147 } 0148 0149 Applications::Application Applications::loadDesktopFile(const QFileInfo &fileInfo) 0150 { 0151 // Ugliest implementation of .desktop file reading ever 0152 // Don't remember why I didn't use QSettings 0153 0154 QFile file(fileInfo.absoluteFilePath()); 0155 if (!file.open(QIODevice::ReadOnly)) { 0156 qWarning() << "Failed to open" << fileInfo.fileName(); 0157 return {}; 0158 } 0159 0160 bool inCorrectGroup = false; 0161 0162 Application app; 0163 app.lastModified = fileInfo.lastModified().toSecsSinceEpoch(); 0164 0165 // Don't necessarily need to change this all the time, but the language can 0166 // in theory be changed by the user. And this function isn't called often 0167 // enough to be in a hot path. 0168 QSet<QString> wantedLanguages; 0169 { // Because Qt suddenly decided that being True C++™ was more important than ease of use 0170 const QStringList languages = QLocale::system().uiLanguages(); 0171 wantedLanguages = QSet<QString>(languages.begin(), languages.end()); 0172 wantedLanguages.insert(QLocale::system().bcp47Name()); 0173 wantedLanguages.insert("en"); 0174 } 0175 0176 while (!file.atEnd()) { 0177 const QString line = QString::fromLocal8Bit(file.readLine()).simplified(); 0178 0179 if (line.startsWith('[')) { 0180 inCorrectGroup = (line == "[Desktop Entry]"); 0181 continue; 0182 } 0183 0184 if (!inCorrectGroup) { 0185 continue; 0186 } 0187 0188 const int separatorPos = line.indexOf('='); 0189 if (separatorPos == -1) { 0190 continue; 0191 } 0192 const QStringRef value = line.midRef(separatorPos + 1); 0193 0194 const int openBracketPos = line.indexOf('['); 0195 0196 // There is a bracket in the Key part, e. g. Foo[lang]=asdf 0197 // These are translated versions of the entries. 0198 if (openBracketPos != -1 && openBracketPos < separatorPos) { 0199 const int closeBracketPos = line.indexOf(']'); 0200 if (closeBracketPos < openBracketPos) { 0201 qWarning() << "Invalid brackets in" << fileInfo.fileName() << line; 0202 continue; 0203 } 0204 if (!wantedLanguages.contains(line.mid(openBracketPos + 1, closeBracketPos - openBracketPos - 1))) { 0205 continue; 0206 } 0207 } 0208 0209 if (line.startsWith("Name")) { 0210 app.name = value.toString(); 0211 continue; 0212 } 0213 if (line.startsWith("Keywords")) { 0214 app.keywords = value.toString(); 0215 continue; 0216 } 0217 0218 0219 if (line.startsWith("Icon")) { 0220 app.icon = value.toString(); 0221 continue; 0222 } 0223 0224 if (line.startsWith("Exec")) { 0225 app.exec = value.toString(); 0226 continue; 0227 } 0228 0229 if (line.startsWith("NoDisplay=") && line.contains("true", Qt::CaseInsensitive)) { 0230 return {}; 0231 } 0232 } 0233 0234 if (app.name.isEmpty()) { 0235 qWarning() << fileInfo.absoluteFilePath() << "missing name"; 0236 app.name = fileInfo.baseName(); 0237 } 0238 0239 if (!app.isValid()) { 0240 return {}; 0241 } 0242 0243 if (!app.keywords.contains(app.name, Qt::CaseInsensitive)) { 0244 app.keywords += ';' + app.name; 0245 } 0246 if (!app.keywords.contains(app.exec, Qt::CaseInsensitive)) { 0247 app.keywords += ';' + app.exec; 0248 } 0249 app.keywords = app.keywords.toLower(); 0250 0251 if (app.icon.isEmpty()) { 0252 app.icon = "application-x-executable"; 0253 } 0254 0255 return app; 0256 } 0257 0258 0259 // kate: indent-mode cstyle; space-indent on; indent-width 4;