1 // SPDX-License-Identifier: GPL-3.0-or-later
2 // SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
3 
4 #include "desktopfileparse.h"
5 #include <QDir>
6 #include <QFile>
7 #include <QLocale>
8 #include <QString>
9 #include <QTextStream>
10 
DesktopFileParser()11 DesktopFileParser::DesktopFileParser()
12 {
13     QString locale = QLocale().name();
14     QString localeShort = QLocale().name().left(2);
15     m_localeName = QStringLiteral("Name[%1]").arg(locale);
16     m_localeDescription = QStringLiteral("Comment[%1]").arg(locale);
17     m_localeNameShort = QStringLiteral("Name[%1]").arg(localeShort);
18     m_localeDescriptionShort = QStringLiteral("Comment[%1]").arg(localeShort);
19     m_defaultIcon =
20       QIcon::fromTheme(QStringLiteral("application-x-executable"));
21 }
22 
parseDesktopFile(const QString & fileName,bool & ok) const23 DesktopAppData DesktopFileParser::parseDesktopFile(const QString& fileName,
24                                                    bool& ok) const
25 {
26     DesktopAppData res;
27     ok = true;
28     QFile file(fileName);
29     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
30         ok = false;
31         return res;
32     }
33     bool nameLocaleSet = false;
34     bool descriptionLocaleSet = false;
35     bool isApplication = false;
36     QTextStream in(&file);
37     // enter the desktop entry definition
38     while (!in.atEnd() && in.readLine() != QLatin1String("[Desktop Entry]")) {
39     }
40     // start parsing
41     while (!in.atEnd()) {
42         QString line = in.readLine();
43         if (line.startsWith(QLatin1String("Icon"))) {
44             res.icon = QIcon::fromTheme(
45               line.mid(line.indexOf(QLatin1String("=")) + 1).trimmed(),
46               m_defaultIcon);
47         } else if (!nameLocaleSet && line.startsWith(QLatin1String("Name"))) {
48             if (line.startsWith(m_localeName) ||
49                 line.startsWith(m_localeNameShort)) {
50                 res.name =
51                   line.mid(line.indexOf(QLatin1String("=")) + 1).trimmed();
52                 nameLocaleSet = true;
53             } else if (line.startsWith(QLatin1String("Name="))) {
54                 res.name =
55                   line.mid(line.indexOf(QLatin1String("=")) + 1).trimmed();
56             }
57         } else if (!descriptionLocaleSet &&
58                    line.startsWith(QLatin1String("Comment"))) {
59             if (line.startsWith(m_localeDescription) ||
60                 line.startsWith(m_localeDescriptionShort)) {
61                 res.description =
62                   line.mid(line.indexOf(QLatin1String("=")) + 1).trimmed();
63                 descriptionLocaleSet = true;
64             } else if (line.startsWith(QLatin1String("Comment="))) {
65                 res.description =
66                   line.mid(line.indexOf(QLatin1String("=")) + 1).trimmed();
67             }
68         } else if (line.startsWith(QLatin1String("Exec"))) {
69             if (line.contains(QLatin1String("%"))) {
70                 res.exec =
71                   line.mid(line.indexOf(QLatin1String("=")) + 1).trimmed();
72             } else {
73                 ok = false;
74                 break;
75             }
76         } else if (line.startsWith(QLatin1String("Type"))) {
77             if (line.contains(QLatin1String("Application"))) {
78                 isApplication = true;
79             }
80         } else if (line.startsWith(QLatin1String("Categories"))) {
81             res.categories = line.mid(line.indexOf(QLatin1String("=")) + 1)
82                                .split(QStringLiteral(";"));
83         } else if (line == QLatin1String("NoDisplay=true")) {
84             ok = false;
85             break;
86         } else if (line == QLatin1String("Terminal=true")) {
87             res.showInTerminal = true;
88         }
89         // ignore the other entries
90         else if (line.startsWith(QLatin1String("["))) {
91             break;
92         }
93     }
94     file.close();
95     if (res.exec.isEmpty() || res.name.isEmpty() || !isApplication) {
96         ok = false;
97     }
98     return res;
99 }
100 
processDirectory(const QDir & dir)101 int DesktopFileParser::processDirectory(const QDir& dir)
102 {
103     QStringList entries = dir.entryList(QDir::NoDotAndDotDot | QDir::Files);
104     bool ok;
105     int length = m_appList.length();
106     for (QString file : entries) {
107         DesktopAppData app = parseDesktopFile(dir.absoluteFilePath(file), ok);
108         if (ok) {
109             m_appList.append(app);
110         }
111     }
112     return m_appList.length() - length;
113 }
114 
getAppsByCategory(const QString & category)115 QVector<DesktopAppData> DesktopFileParser::getAppsByCategory(
116   const QString& category)
117 {
118     QVector<DesktopAppData> res;
119     for (const DesktopAppData& app : m_appList) {
120         if (app.categories.contains(category)) {
121             res.append(app);
122         }
123     }
124     return res;
125 }
126 
getAppsByCategory(const QStringList & categories)127 QMap<QString, QVector<DesktopAppData>> DesktopFileParser::getAppsByCategory(
128   const QStringList& categories)
129 {
130     QMap<QString, QVector<DesktopAppData>> res;
131     for (const DesktopAppData& app : m_appList) {
132         for (const QString& category : categories) {
133             if (app.categories.contains(category)) {
134                 res[category].append(app);
135             }
136         }
137     }
138     return res;
139 }
140