xref: /OK3568_Linux_fs/app/QLauncher/xdgdesktopfile.cpp (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun /* BEGIN_COMMON_COPYRIGHT_HEADER
2*4882a593Smuzhiyun  * (c)LGPL2+
3*4882a593Smuzhiyun  *
4*4882a593Smuzhiyun  * LXQt - a lightweight, Qt based, desktop toolset
5*4882a593Smuzhiyun  * https://lxqt.org
6*4882a593Smuzhiyun  *
7*4882a593Smuzhiyun  * Copyright: 2010-2011 Razor team
8*4882a593Smuzhiyun  * Authors:
9*4882a593Smuzhiyun  *   Alexander Sokoloff <sokoloff.a@gmail.com>
10*4882a593Smuzhiyun  *
11*4882a593Smuzhiyun  * This program or library is free software; you can redistribute it
12*4882a593Smuzhiyun  * and/or modify it under the terms of the GNU Lesser General Public
13*4882a593Smuzhiyun  * License as published by the Free Software Foundation; either
14*4882a593Smuzhiyun  * version 2.1 of the License, or (at your option) any later version.
15*4882a593Smuzhiyun  *
16*4882a593Smuzhiyun  * This library is distributed in the hope that it will be useful,
17*4882a593Smuzhiyun  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18*4882a593Smuzhiyun  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19*4882a593Smuzhiyun  * Lesser General Public License for more details.
20*4882a593Smuzhiyun 
21*4882a593Smuzhiyun  * You should have received a copy of the GNU Lesser General
22*4882a593Smuzhiyun  * Public License along with this library; if not, write to the
23*4882a593Smuzhiyun  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24*4882a593Smuzhiyun  * Boston, MA 02110-1301 USA
25*4882a593Smuzhiyun  *
26*4882a593Smuzhiyun  * END_COMMON_COPYRIGHT_HEADER */
27*4882a593Smuzhiyun 
28*4882a593Smuzhiyun //#include "desktopenvironment_p.cpp"
29*4882a593Smuzhiyun #include "xdgdesktopfile.h"
30*4882a593Smuzhiyun #include <stdlib.h>
31*4882a593Smuzhiyun #include <unistd.h>
32*4882a593Smuzhiyun #include <QDebug>
33*4882a593Smuzhiyun #include <QDesktopServices>
34*4882a593Smuzhiyun #include <QDir>
35*4882a593Smuzhiyun #include <QSharedData>
36*4882a593Smuzhiyun #include <QFile>
37*4882a593Smuzhiyun #include <QFileInfo>
38*4882a593Smuzhiyun #include <QHash>
39*4882a593Smuzhiyun #include <QList>
40*4882a593Smuzhiyun #include <QMimeDatabase>
41*4882a593Smuzhiyun #include <QMimeType>
42*4882a593Smuzhiyun #include <QProcess>
43*4882a593Smuzhiyun #include <QSettings>
44*4882a593Smuzhiyun #include <QStandardPaths>
45*4882a593Smuzhiyun #include <QTextStream>
46*4882a593Smuzhiyun #include <QUrl>
47*4882a593Smuzhiyun #include <QtAlgorithms>
48*4882a593Smuzhiyun #define DEFAULT_ICON ":/resources/icon.png"
49*4882a593Smuzhiyun 
50*4882a593Smuzhiyun /**
51*4882a593Smuzhiyun  *  See: http://standards.freedesktop.org/desktop-entry-spec
52*4882a593Smuzhiyun  */
53*4882a593Smuzhiyun 
54*4882a593Smuzhiyun // A list of executables that can't be run with QProcess::startDetached(). They
55*4882a593Smuzhiyun // will be run with QProcess::start()
56*4882a593Smuzhiyun static const QStringList nonDetachExecs = QStringList()
57*4882a593Smuzhiyun     << QLatin1String("pkexec");
58*4882a593Smuzhiyun 
59*4882a593Smuzhiyun 
60*4882a593Smuzhiyun static const QLatin1String nameKey("Name");
61*4882a593Smuzhiyun static const QLatin1String typeKey("Type");
62*4882a593Smuzhiyun static const QLatin1String ApplicationStr("Application");
63*4882a593Smuzhiyun static const QLatin1String LinkStr("Link");
64*4882a593Smuzhiyun static const QLatin1String DirectoryStr("Directory");
65*4882a593Smuzhiyun static const QLatin1String execKey("Exec");
66*4882a593Smuzhiyun static const QLatin1String iconKey("Icon");
67*4882a593Smuzhiyun static const QString userDirectoryString[8] =
68*4882a593Smuzhiyun {
69*4882a593Smuzhiyun     QLatin1String("Desktop"),
70*4882a593Smuzhiyun     QLatin1String("Download"),
71*4882a593Smuzhiyun     QLatin1String("Templates"),
72*4882a593Smuzhiyun     QLatin1String("Publicshare"),
73*4882a593Smuzhiyun     QLatin1String("Documents"),
74*4882a593Smuzhiyun     QLatin1String("Music"),
75*4882a593Smuzhiyun     QLatin1String("Pictures"),
76*4882a593Smuzhiyun     QLatin1String("Videos")
77*4882a593Smuzhiyun };
78*4882a593Smuzhiyun 
79*4882a593Smuzhiyun // Helper functions prototypes
80*4882a593Smuzhiyun bool checkTryExec(const QString& progName);
81*4882a593Smuzhiyun QString &doEscape(QString& str, const QHash<QChar,QChar> &repl);
82*4882a593Smuzhiyun QString &doUnEscape(QString& str, const QHash<QChar,QChar> &repl);
83*4882a593Smuzhiyun QString &escape(QString& str);
84*4882a593Smuzhiyun QString &escapeExec(QString& str);
85*4882a593Smuzhiyun QString expandDynamicUrl(QString url);
86*4882a593Smuzhiyun QString expandEnvVariables(const QString str);
87*4882a593Smuzhiyun QStringList expandEnvVariables(const QStringList strs);
88*4882a593Smuzhiyun QString findDesktopFile(const QString& dirName, const QString& desktopName);
89*4882a593Smuzhiyun QString findDesktopFile(const QString& desktopName);
90*4882a593Smuzhiyun static QStringList parseCombinedArgString(const QString &program);
91*4882a593Smuzhiyun bool read(const QString &prefix);
92*4882a593Smuzhiyun void replaceVar(QString &str, const QString &varName, const QString &after);
93*4882a593Smuzhiyun QString &unEscape(QString& str);
94*4882a593Smuzhiyun QString &unEscapeExec(QString& str);
95*4882a593Smuzhiyun void loadMimeCacheDir(const QString& dirName, QHash<QString, QList<XdgDesktopFile*> > & cache);
96*4882a593Smuzhiyun QStringList dataDirs(const QString &postfix);
97*4882a593Smuzhiyun 
98*4882a593Smuzhiyun 
doUnEscape(QString & str,const QHash<QChar,QChar> & repl)99*4882a593Smuzhiyun QString &doUnEscape(QString& str, const QHash<QChar,QChar> &repl)
100*4882a593Smuzhiyun {
101*4882a593Smuzhiyun     int n = 0;
102*4882a593Smuzhiyun     while (1)
103*4882a593Smuzhiyun     {
104*4882a593Smuzhiyun         n=str.indexOf(QLatin1String("\\"), n);
105*4882a593Smuzhiyun         if (n < 0 || n > str.length() - 2)
106*4882a593Smuzhiyun             break;
107*4882a593Smuzhiyun 
108*4882a593Smuzhiyun         if (repl.contains(str.at(n+1)))
109*4882a593Smuzhiyun         {
110*4882a593Smuzhiyun             str.replace(n, 2, repl.value(str.at(n+1)));
111*4882a593Smuzhiyun         }
112*4882a593Smuzhiyun 
113*4882a593Smuzhiyun         n++;
114*4882a593Smuzhiyun     }
115*4882a593Smuzhiyun 
116*4882a593Smuzhiyun     return str;
117*4882a593Smuzhiyun }
118*4882a593Smuzhiyun 
119*4882a593Smuzhiyun 
120*4882a593Smuzhiyun /************************************************
121*4882a593Smuzhiyun  The escape sequences \s, \n, \t, \r, and \\ are supported for values
122*4882a593Smuzhiyun  of type string and localestring, meaning ASCII space, newline, tab,
123*4882a593Smuzhiyun  carriage return, and backslash, respectively.
124*4882a593Smuzhiyun  ************************************************/
unEscape(QString & str)125*4882a593Smuzhiyun QString &unEscape(QString& str)
126*4882a593Smuzhiyun {
127*4882a593Smuzhiyun     QHash<QChar,QChar> repl;
128*4882a593Smuzhiyun     repl.insert(QLatin1Char('\\'), QLatin1Char('\\'));
129*4882a593Smuzhiyun     repl.insert(QLatin1Char('s'),  QLatin1Char(' '));
130*4882a593Smuzhiyun     repl.insert(QLatin1Char('n'),  QLatin1Char('\n'));
131*4882a593Smuzhiyun     repl.insert(QLatin1Char('t'),  QLatin1Char('\t'));
132*4882a593Smuzhiyun     repl.insert(QLatin1Char('r'),  QLatin1Char('\r'));
133*4882a593Smuzhiyun 
134*4882a593Smuzhiyun     return doUnEscape(str, repl);
135*4882a593Smuzhiyun }
136*4882a593Smuzhiyun 
137*4882a593Smuzhiyun 
138*4882a593Smuzhiyun /************************************************
139*4882a593Smuzhiyun  Quoting must be done by enclosing the argument between double quotes and
140*4882a593Smuzhiyun  escaping the
141*4882a593Smuzhiyun     double quote character,
142*4882a593Smuzhiyun     backtick character ("`"),
143*4882a593Smuzhiyun     dollar sign ("$") and
144*4882a593Smuzhiyun     backslash character ("\")
145*4882a593Smuzhiyun by preceding it with an additional backslash character.
146*4882a593Smuzhiyun Implementations must undo quoting before expanding field codes and before
147*4882a593Smuzhiyun passing the argument to the executable program.
148*4882a593Smuzhiyun 
149*4882a593Smuzhiyun Reserved characters are
150*4882a593Smuzhiyun     space (" "),
151*4882a593Smuzhiyun     tab,
152*4882a593Smuzhiyun     newline,
153*4882a593Smuzhiyun     double quote,
154*4882a593Smuzhiyun     single quote ("'"),
155*4882a593Smuzhiyun     backslash character ("\"),
156*4882a593Smuzhiyun     greater-than sign (">"),
157*4882a593Smuzhiyun     less-than sign ("<"),
158*4882a593Smuzhiyun     tilde ("~"),
159*4882a593Smuzhiyun     vertical bar ("|"),
160*4882a593Smuzhiyun     ampersand ("&"),
161*4882a593Smuzhiyun     semicolon (";"),
162*4882a593Smuzhiyun     dollar sign ("$"),
163*4882a593Smuzhiyun     asterisk ("*"),
164*4882a593Smuzhiyun     question mark ("?"),
165*4882a593Smuzhiyun     hash mark ("#"),
166*4882a593Smuzhiyun     parenthesis ("(") and (")")
167*4882a593Smuzhiyun     backtick character ("`").
168*4882a593Smuzhiyun 
169*4882a593Smuzhiyun Note that the general escape rule for values of type string states that the
170*4882a593Smuzhiyun backslash character can be escaped as ("\\") as well and that this escape
171*4882a593Smuzhiyun rule is applied before the quoting rule. As such, to unambiguously represent a
172*4882a593Smuzhiyun literal backslash character in a quoted argument in a desktop entry file
173*4882a593Smuzhiyun requires the use of four successive backslash characters ("\\\\").
174*4882a593Smuzhiyun Likewise, a literal dollar sign in a quoted argument in a desktop entry file
175*4882a593Smuzhiyun is unambiguously represented with ("\\$").
176*4882a593Smuzhiyun  ************************************************/
unEscapeExec(QString & str)177*4882a593Smuzhiyun QString &unEscapeExec(QString& str)
178*4882a593Smuzhiyun {
179*4882a593Smuzhiyun     unEscape(str);
180*4882a593Smuzhiyun     QHash<QChar,QChar> repl;
181*4882a593Smuzhiyun     // The parseCombinedArgString() splits the string by the space symbols,
182*4882a593Smuzhiyun     // we temporarily replace them on the special characters.
183*4882a593Smuzhiyun     // Replacement will reverse after the splitting.
184*4882a593Smuzhiyun     repl.insert(QLatin1Char(' '),  01);    // space
185*4882a593Smuzhiyun     repl.insert(QLatin1Char('\t'), 02);    // tab
186*4882a593Smuzhiyun     repl.insert(QLatin1Char('\n'), 03);    // newline,
187*4882a593Smuzhiyun 
188*4882a593Smuzhiyun     repl.insert(QLatin1Char('"'), QLatin1Char('"'));    // double quote,
189*4882a593Smuzhiyun     repl.insert(QLatin1Char('\''), QLatin1Char('\''));  // single quote ("'"),
190*4882a593Smuzhiyun     repl.insert(QLatin1Char('\\'), QLatin1Char('\\'));  // backslash character ("\"),
191*4882a593Smuzhiyun     repl.insert(QLatin1Char('>'), QLatin1Char('>'));    // greater-than sign (">"),
192*4882a593Smuzhiyun     repl.insert(QLatin1Char('<'), QLatin1Char('<'));    // less-than sign ("<"),
193*4882a593Smuzhiyun     repl.insert(QLatin1Char('~'), QLatin1Char('~'));    // tilde ("~"),
194*4882a593Smuzhiyun     repl.insert(QLatin1Char('|'), QLatin1Char('|'));    // vertical bar ("|"),
195*4882a593Smuzhiyun     repl.insert(QLatin1Char('&'), QLatin1Char('&'));    // ampersand ("&"),
196*4882a593Smuzhiyun     repl.insert(QLatin1Char(';'), QLatin1Char(';'));    // semicolon (";"),
197*4882a593Smuzhiyun     repl.insert(QLatin1Char('$'), QLatin1Char('$'));    // dollar sign ("$"),
198*4882a593Smuzhiyun     repl.insert(QLatin1Char('*'), QLatin1Char('*'));    // asterisk ("*"),
199*4882a593Smuzhiyun     repl.insert(QLatin1Char('?'), QLatin1Char('?'));    // question mark ("?"),
200*4882a593Smuzhiyun     repl.insert(QLatin1Char('#'), QLatin1Char('#'));    // hash mark ("#"),
201*4882a593Smuzhiyun     repl.insert(QLatin1Char('('), QLatin1Char('('));    // parenthesis ("(")
202*4882a593Smuzhiyun     repl.insert(QLatin1Char(')'), QLatin1Char(')'));    // parenthesis (")")
203*4882a593Smuzhiyun     repl.insert(QLatin1Char('`'), QLatin1Char('`'));    // backtick character ("`").
204*4882a593Smuzhiyun 
205*4882a593Smuzhiyun     return doUnEscape(str, repl);
206*4882a593Smuzhiyun }
207*4882a593Smuzhiyun 
fixBashShortcuts(QString & s)208*4882a593Smuzhiyun void fixBashShortcuts(QString &s)
209*4882a593Smuzhiyun {
210*4882a593Smuzhiyun     if (s.startsWith(QLatin1Char('~')))
211*4882a593Smuzhiyun         s = QFile::decodeName(qgetenv("HOME")) + (s).mid(1);
212*4882a593Smuzhiyun }
213*4882a593Smuzhiyun 
removeEndingSlash(QString & s)214*4882a593Smuzhiyun void removeEndingSlash(QString &s)
215*4882a593Smuzhiyun {
216*4882a593Smuzhiyun     // We don't check for empty strings. Caller must check it.
217*4882a593Smuzhiyun 
218*4882a593Smuzhiyun     // Remove the ending slash, except for root dirs.
219*4882a593Smuzhiyun     if (s.length() > 1 && s.endsWith(QLatin1Char('/')))
220*4882a593Smuzhiyun         s.chop(1);
221*4882a593Smuzhiyun }
222*4882a593Smuzhiyun 
cleanAndAddPostfix(QStringList & dirs,const QString & postfix)223*4882a593Smuzhiyun void cleanAndAddPostfix(QStringList &dirs, const QString& postfix)
224*4882a593Smuzhiyun {
225*4882a593Smuzhiyun     const int N = dirs.count();
226*4882a593Smuzhiyun     for(int i = 0; i < N; ++i)
227*4882a593Smuzhiyun     {
228*4882a593Smuzhiyun         fixBashShortcuts(dirs[i]);
229*4882a593Smuzhiyun         removeEndingSlash(dirs[i]);
230*4882a593Smuzhiyun         dirs[i].append(postfix);
231*4882a593Smuzhiyun     }
232*4882a593Smuzhiyun }
233*4882a593Smuzhiyun 
234*4882a593Smuzhiyun namespace
235*4882a593Smuzhiyun {
236*4882a593Smuzhiyun     /*!
237*4882a593Smuzhiyun      * Helper class for getting the keys for "Additional applications actions"
238*4882a593Smuzhiyun      * ([Desktop Action %s] sections)
239*4882a593Smuzhiyun      */
240*4882a593Smuzhiyun     class XdgDesktopAction : public XdgDesktopFile
241*4882a593Smuzhiyun     {
242*4882a593Smuzhiyun     public:
XdgDesktopAction(const XdgDesktopFile & parent,const QString & action)243*4882a593Smuzhiyun         XdgDesktopAction(const XdgDesktopFile & parent, const QString & action)
244*4882a593Smuzhiyun             : XdgDesktopFile(parent)
245*4882a593Smuzhiyun             , m_prefix(QString{QLatin1String("Desktop Action %1")}.arg(action))
246*4882a593Smuzhiyun         {}
247*4882a593Smuzhiyun 
248*4882a593Smuzhiyun     protected:
prefix() const249*4882a593Smuzhiyun         virtual QString prefix() const { return m_prefix; }
250*4882a593Smuzhiyun 
251*4882a593Smuzhiyun     private:
252*4882a593Smuzhiyun         const QString m_prefix;
253*4882a593Smuzhiyun     };
254*4882a593Smuzhiyun }
255*4882a593Smuzhiyun 
256*4882a593Smuzhiyun class XdgDesktopFileData: public QSharedData {
257*4882a593Smuzhiyun public:
258*4882a593Smuzhiyun     XdgDesktopFileData();
259*4882a593Smuzhiyun 
clear()260*4882a593Smuzhiyun     inline void clear() {
261*4882a593Smuzhiyun         mFileName.clear();
262*4882a593Smuzhiyun         mIsValid = false;
263*4882a593Smuzhiyun         mValidIsChecked = false;
264*4882a593Smuzhiyun         mIsShow.clear();
265*4882a593Smuzhiyun         mItems.clear();
266*4882a593Smuzhiyun         mType = XdgDesktopFile::UnknownType;
267*4882a593Smuzhiyun     }
268*4882a593Smuzhiyun     bool read(const QString &prefix);
269*4882a593Smuzhiyun     XdgDesktopFile::Type detectType(XdgDesktopFile *q) const;
270*4882a593Smuzhiyun     bool startApplicationDetached(const XdgDesktopFile *q, const QString & action, const QStringList& urls) const;
271*4882a593Smuzhiyun 
272*4882a593Smuzhiyun     QString mFileName;
273*4882a593Smuzhiyun     bool mIsValid;
274*4882a593Smuzhiyun     mutable bool mValidIsChecked;
275*4882a593Smuzhiyun     mutable QHash<QString, bool> mIsShow;
276*4882a593Smuzhiyun     QMap<QString, QVariant> mItems;
277*4882a593Smuzhiyun 
278*4882a593Smuzhiyun     XdgDesktopFile::Type mType;
279*4882a593Smuzhiyun };
280*4882a593Smuzhiyun 
281*4882a593Smuzhiyun 
XdgDesktopFileData()282*4882a593Smuzhiyun XdgDesktopFileData::XdgDesktopFileData():
283*4882a593Smuzhiyun     mFileName(),
284*4882a593Smuzhiyun     mIsValid(false),
285*4882a593Smuzhiyun     mValidIsChecked(false),
286*4882a593Smuzhiyun     mIsShow(),
287*4882a593Smuzhiyun     mItems(),
288*4882a593Smuzhiyun     mType(XdgDesktopFile::UnknownType)
289*4882a593Smuzhiyun {
290*4882a593Smuzhiyun }
291*4882a593Smuzhiyun 
292*4882a593Smuzhiyun 
read(const QString & prefix)293*4882a593Smuzhiyun bool XdgDesktopFileData::read(const QString &prefix)
294*4882a593Smuzhiyun {
295*4882a593Smuzhiyun     QFile file(mFileName);
296*4882a593Smuzhiyun 
297*4882a593Smuzhiyun     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
298*4882a593Smuzhiyun         return false;
299*4882a593Smuzhiyun 
300*4882a593Smuzhiyun     QString section;
301*4882a593Smuzhiyun     QTextStream stream(&file);
302*4882a593Smuzhiyun     bool prefixExists = false;
303*4882a593Smuzhiyun     while (!stream.atEnd()) {
304*4882a593Smuzhiyun         QString line = stream.readLine().trimmed();
305*4882a593Smuzhiyun 
306*4882a593Smuzhiyun         // Skip comments ......................
307*4882a593Smuzhiyun         if (line.startsWith(QLatin1Char('#')))
308*4882a593Smuzhiyun             continue;
309*4882a593Smuzhiyun 
310*4882a593Smuzhiyun 
311*4882a593Smuzhiyun         // Section ..............................
312*4882a593Smuzhiyun         if (line.startsWith(QLatin1Char('[')) && line.endsWith(QLatin1Char(']')))
313*4882a593Smuzhiyun         {
314*4882a593Smuzhiyun             section = line.mid(1, line.length()-2);
315*4882a593Smuzhiyun             if (section == prefix)
316*4882a593Smuzhiyun                 prefixExists = true;
317*4882a593Smuzhiyun 
318*4882a593Smuzhiyun             continue;
319*4882a593Smuzhiyun         }
320*4882a593Smuzhiyun 
321*4882a593Smuzhiyun         QString key = line.section(QLatin1Char('='), 0, 0).trimmed();
322*4882a593Smuzhiyun         QString value = line.section(QLatin1Char('='), 1).trimmed();
323*4882a593Smuzhiyun 
324*4882a593Smuzhiyun         if (key.isEmpty())
325*4882a593Smuzhiyun             continue;
326*4882a593Smuzhiyun 
327*4882a593Smuzhiyun         mItems[section + QLatin1Char('/') + key] = QVariant(value);
328*4882a593Smuzhiyun     }
329*4882a593Smuzhiyun 
330*4882a593Smuzhiyun 
331*4882a593Smuzhiyun     // Not check for empty prefix
332*4882a593Smuzhiyun     mIsValid = (prefix.isEmpty()) || prefixExists;
333*4882a593Smuzhiyun     return mIsValid;
334*4882a593Smuzhiyun }
335*4882a593Smuzhiyun 
detectType(XdgDesktopFile * q) const336*4882a593Smuzhiyun XdgDesktopFile::Type XdgDesktopFileData::detectType(XdgDesktopFile *q) const
337*4882a593Smuzhiyun {
338*4882a593Smuzhiyun     QString typeStr = q->value(typeKey).toString();
339*4882a593Smuzhiyun     if (typeStr == ApplicationStr)
340*4882a593Smuzhiyun         return XdgDesktopFile::ApplicationType;
341*4882a593Smuzhiyun 
342*4882a593Smuzhiyun     if (typeStr == LinkStr)
343*4882a593Smuzhiyun         return XdgDesktopFile::LinkType;
344*4882a593Smuzhiyun 
345*4882a593Smuzhiyun     if (typeStr == DirectoryStr)
346*4882a593Smuzhiyun         return XdgDesktopFile::DirectoryType;
347*4882a593Smuzhiyun 
348*4882a593Smuzhiyun     if (!q->value(execKey).toString().isEmpty())
349*4882a593Smuzhiyun         return XdgDesktopFile::ApplicationType;
350*4882a593Smuzhiyun 
351*4882a593Smuzhiyun     return XdgDesktopFile::UnknownType;
352*4882a593Smuzhiyun }
353*4882a593Smuzhiyun 
startApplicationDetached(const XdgDesktopFile * q,const QString & action,const QStringList & urls) const354*4882a593Smuzhiyun bool XdgDesktopFileData::startApplicationDetached(const XdgDesktopFile *q, const QString & action, const QStringList& urls) const
355*4882a593Smuzhiyun {
356*4882a593Smuzhiyun     //DBusActivatable handling
357*4882a593Smuzhiyun     if (q->value(QLatin1String("DBusActivatable"), false).toBool()) {
358*4882a593Smuzhiyun         /* WARNING: We fallback to use Exec when the DBusActivatable fails.
359*4882a593Smuzhiyun          *
360*4882a593Smuzhiyun          * This is a violation of the standard and we know it!
361*4882a593Smuzhiyun          *
362*4882a593Smuzhiyun          * From the Standard:
363*4882a593Smuzhiyun          * DBusActivatable	A boolean value specifying if D-Bus activation is
364*4882a593Smuzhiyun          * supported for this application. If this key is missing, the default
365*4882a593Smuzhiyun          * value is false. If the value is true then implementations should
366*4882a593Smuzhiyun          * ignore the Exec key and send a D-Bus message to launch the
367*4882a593Smuzhiyun          * application. See D-Bus Activation for more information on how this
368*4882a593Smuzhiyun          * works. Applications should still include Exec= lines in their desktop
369*4882a593Smuzhiyun          * files for compatibility with implementations that do not understand
370*4882a593Smuzhiyun          * the DBusActivatable key.
371*4882a593Smuzhiyun          *
372*4882a593Smuzhiyun          * So, why are we doing it ? In the benefit of user experience.
373*4882a593Smuzhiyun          * We first ignore the Exec line and in use the D-Bus to lauch the
374*4882a593Smuzhiyun          * application. But if it fails, we try the Exec method.
375*4882a593Smuzhiyun          *
376*4882a593Smuzhiyun          * We consider that this violation is more acceptable than an failure
377*4882a593Smuzhiyun          * in launching an application.
378*4882a593Smuzhiyun          */
379*4882a593Smuzhiyun 
380*4882a593Smuzhiyun             return false;
381*4882a593Smuzhiyun     }
382*4882a593Smuzhiyun     QStringList args = action.isEmpty()
383*4882a593Smuzhiyun         ? q->expandExecString(urls)
384*4882a593Smuzhiyun         : XdgDesktopAction{*q, action}.expandExecString(urls);
385*4882a593Smuzhiyun 
386*4882a593Smuzhiyun     if (args.isEmpty())
387*4882a593Smuzhiyun         return false;
388*4882a593Smuzhiyun 
389*4882a593Smuzhiyun     if (q->value(QLatin1String("Terminal")).toBool())
390*4882a593Smuzhiyun     {
391*4882a593Smuzhiyun         QString term = QString::fromLocal8Bit(qgetenv("TERM"));
392*4882a593Smuzhiyun         if (term.isEmpty())
393*4882a593Smuzhiyun             term = QLatin1String("xterm");
394*4882a593Smuzhiyun 
395*4882a593Smuzhiyun         args.prepend(QLatin1String("-e"));
396*4882a593Smuzhiyun         args.prepend(term);
397*4882a593Smuzhiyun     }
398*4882a593Smuzhiyun 
399*4882a593Smuzhiyun     bool nonDetach = false;
400*4882a593Smuzhiyun     for (const QString &s : nonDetachExecs)
401*4882a593Smuzhiyun     {
402*4882a593Smuzhiyun         for (const QString &a : const_cast<const QStringList&>(args))
403*4882a593Smuzhiyun         {
404*4882a593Smuzhiyun             if (a.contains(s))
405*4882a593Smuzhiyun             {
406*4882a593Smuzhiyun                 nonDetach = true;
407*4882a593Smuzhiyun             }
408*4882a593Smuzhiyun         }
409*4882a593Smuzhiyun     }
410*4882a593Smuzhiyun 
411*4882a593Smuzhiyun     QString cmd = args.takeFirst();
412*4882a593Smuzhiyun     QString workingDir = q->value(QLatin1String("Path")).toString();
413*4882a593Smuzhiyun     if (!workingDir.isEmpty() && !QDir(workingDir).exists())
414*4882a593Smuzhiyun 	    workingDir = QString();
415*4882a593Smuzhiyun 
416*4882a593Smuzhiyun     if (nonDetach)
417*4882a593Smuzhiyun     {
418*4882a593Smuzhiyun         QScopedPointer<QProcess> p(new QProcess);
419*4882a593Smuzhiyun         p->setStandardInputFile(QProcess::nullDevice());
420*4882a593Smuzhiyun         p->setProcessChannelMode(QProcess::ForwardedChannels);
421*4882a593Smuzhiyun         if (!workingDir.isEmpty())
422*4882a593Smuzhiyun             p->setWorkingDirectory(workingDir);
423*4882a593Smuzhiyun         p->start(cmd, args);
424*4882a593Smuzhiyun         bool started = p->waitForStarted();
425*4882a593Smuzhiyun         if (started)
426*4882a593Smuzhiyun         {
427*4882a593Smuzhiyun             QProcess* proc = p.take(); //release the pointer(will be selfdestroyed upon finish)
428*4882a593Smuzhiyun             QObject::connect(proc, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
429*4882a593Smuzhiyun                 proc, &QProcess::deleteLater);
430*4882a593Smuzhiyun         }
431*4882a593Smuzhiyun         return started;
432*4882a593Smuzhiyun     }
433*4882a593Smuzhiyun     else
434*4882a593Smuzhiyun     {
435*4882a593Smuzhiyun         return QProcess::startDetached(cmd, args, workingDir);
436*4882a593Smuzhiyun     }
437*4882a593Smuzhiyun }
438*4882a593Smuzhiyun 
XdgDesktopFile()439*4882a593Smuzhiyun XdgDesktopFile::XdgDesktopFile():
440*4882a593Smuzhiyun     d(new XdgDesktopFileData)
441*4882a593Smuzhiyun {
442*4882a593Smuzhiyun }
443*4882a593Smuzhiyun 
444*4882a593Smuzhiyun 
XdgDesktopFile(const XdgDesktopFile & other)445*4882a593Smuzhiyun XdgDesktopFile::XdgDesktopFile(const XdgDesktopFile& other):
446*4882a593Smuzhiyun     d(other.d)
447*4882a593Smuzhiyun {
448*4882a593Smuzhiyun }
449*4882a593Smuzhiyun 
~XdgDesktopFile()450*4882a593Smuzhiyun XdgDesktopFile::~XdgDesktopFile()
451*4882a593Smuzhiyun {
452*4882a593Smuzhiyun }
453*4882a593Smuzhiyun 
load(const QString & fileName)454*4882a593Smuzhiyun bool XdgDesktopFile::load(const QString& fileName)
455*4882a593Smuzhiyun {
456*4882a593Smuzhiyun     d->clear();
457*4882a593Smuzhiyun     if (fileName.startsWith(QDir::separator())) { // absolute path
458*4882a593Smuzhiyun         QFileInfo f(fileName);
459*4882a593Smuzhiyun         if (f.exists())
460*4882a593Smuzhiyun             d->mFileName = f.canonicalFilePath();
461*4882a593Smuzhiyun         else
462*4882a593Smuzhiyun             return false;
463*4882a593Smuzhiyun     } else { // relative path
464*4882a593Smuzhiyun         const QString r = findDesktopFile(fileName);
465*4882a593Smuzhiyun         if (r.isEmpty())
466*4882a593Smuzhiyun             return false;
467*4882a593Smuzhiyun         else
468*4882a593Smuzhiyun             d->mFileName = r;
469*4882a593Smuzhiyun     }
470*4882a593Smuzhiyun     d->read(prefix());
471*4882a593Smuzhiyun     d->mIsValid = d->mIsValid && check();
472*4882a593Smuzhiyun     d->mType = d->detectType(this);
473*4882a593Smuzhiyun     return isValid();
474*4882a593Smuzhiyun }
475*4882a593Smuzhiyun 
value(const QString & key,const QVariant & defaultValue) const476*4882a593Smuzhiyun QVariant XdgDesktopFile::value(const QString& key, const QVariant& defaultValue) const
477*4882a593Smuzhiyun {
478*4882a593Smuzhiyun     QString path = (!prefix().isEmpty()) ? prefix() + QLatin1Char('/') + key : key;
479*4882a593Smuzhiyun     QVariant res = d->mItems.value(path, defaultValue);
480*4882a593Smuzhiyun     if (res.type() == QVariant::String)
481*4882a593Smuzhiyun     {
482*4882a593Smuzhiyun         QString s = res.toString();
483*4882a593Smuzhiyun         return unEscape(s);
484*4882a593Smuzhiyun     }
485*4882a593Smuzhiyun 
486*4882a593Smuzhiyun     return res;
487*4882a593Smuzhiyun }
488*4882a593Smuzhiyun 
489*4882a593Smuzhiyun 
490*4882a593Smuzhiyun /************************************************
491*4882a593Smuzhiyun  LC_MESSAGES value      Possible keys in order of matching
492*4882a593Smuzhiyun  lang_COUNTRY@MODIFIER  lang_COUNTRY@MODIFIER, lang_COUNTRY, lang@MODIFIER, lang,
493*4882a593Smuzhiyun                         default value
494*4882a593Smuzhiyun  lang_COUNTRY           lang_COUNTRY, lang, default value
495*4882a593Smuzhiyun  lang@MODIFIER          lang@MODIFIER, lang, default value
496*4882a593Smuzhiyun  lang                   lang, default value
497*4882a593Smuzhiyun  ************************************************/
localizedKey(const QString & key) const498*4882a593Smuzhiyun QString XdgDesktopFile::localizedKey(const QString& key) const
499*4882a593Smuzhiyun {
500*4882a593Smuzhiyun     QString lang = QString::fromLocal8Bit(qgetenv("LC_MESSAGES"));
501*4882a593Smuzhiyun 
502*4882a593Smuzhiyun     if (lang.isEmpty())
503*4882a593Smuzhiyun         lang = QString::fromLocal8Bit(qgetenv("LC_ALL"));
504*4882a593Smuzhiyun 
505*4882a593Smuzhiyun     if (lang.isEmpty())
506*4882a593Smuzhiyun          lang = QString::fromLocal8Bit(qgetenv("LANG"));
507*4882a593Smuzhiyun 
508*4882a593Smuzhiyun     QString modifier = lang.section(QLatin1Char('@'), 1);
509*4882a593Smuzhiyun     if (!modifier.isEmpty())
510*4882a593Smuzhiyun         lang.truncate(lang.length() - modifier.length() - 1);
511*4882a593Smuzhiyun 
512*4882a593Smuzhiyun     QString encoding = lang.section(QLatin1Char('.'), 1);
513*4882a593Smuzhiyun     if (!encoding.isEmpty())
514*4882a593Smuzhiyun         lang.truncate(lang.length() - encoding.length() - 1);
515*4882a593Smuzhiyun 
516*4882a593Smuzhiyun 
517*4882a593Smuzhiyun     QString country = lang.section(QLatin1Char('_'), 1);
518*4882a593Smuzhiyun     if (!country.isEmpty())
519*4882a593Smuzhiyun         lang.truncate(lang.length() - country.length() - 1);
520*4882a593Smuzhiyun 
521*4882a593Smuzhiyun     //qDebug() << "LC_MESSAGES: " << qgetenv("LC_MESSAGES");
522*4882a593Smuzhiyun     //qDebug() << "Lang:" << lang;
523*4882a593Smuzhiyun     //qDebug() << "Country:" << country;
524*4882a593Smuzhiyun     //qDebug() << "Encoding:" << encoding;
525*4882a593Smuzhiyun     //qDebug() << "Modifier:" << modifier;
526*4882a593Smuzhiyun 
527*4882a593Smuzhiyun 
528*4882a593Smuzhiyun     if (!modifier.isEmpty() && !country.isEmpty())
529*4882a593Smuzhiyun     {
530*4882a593Smuzhiyun         QString k = QString::fromLatin1("%1[%2_%3@%4]").arg(key, lang, country, modifier);
531*4882a593Smuzhiyun         //qDebug() << "\t try " << k << contains(k);
532*4882a593Smuzhiyun         if (contains(k))
533*4882a593Smuzhiyun             return k;
534*4882a593Smuzhiyun     }
535*4882a593Smuzhiyun 
536*4882a593Smuzhiyun     if (!country.isEmpty())
537*4882a593Smuzhiyun     {
538*4882a593Smuzhiyun         QString k = QString::fromLatin1("%1[%2_%3]").arg(key, lang, country);
539*4882a593Smuzhiyun         //qDebug() << "\t try " << k  << contains(k);
540*4882a593Smuzhiyun         if (contains(k))
541*4882a593Smuzhiyun             return k;
542*4882a593Smuzhiyun     }
543*4882a593Smuzhiyun 
544*4882a593Smuzhiyun     if (!modifier.isEmpty())
545*4882a593Smuzhiyun     {
546*4882a593Smuzhiyun         QString k = QString::fromLatin1("%1[%2@%3]").arg(key, lang, modifier);
547*4882a593Smuzhiyun         //qDebug() << "\t try " << k  << contains(k);
548*4882a593Smuzhiyun         if (contains(k))
549*4882a593Smuzhiyun             return k;
550*4882a593Smuzhiyun     }
551*4882a593Smuzhiyun 
552*4882a593Smuzhiyun     QString k = QString::fromLatin1("%1[%2]").arg(key, lang);
553*4882a593Smuzhiyun     //qDebug() << "\t try " << k  << contains(k);
554*4882a593Smuzhiyun     if (contains(k))
555*4882a593Smuzhiyun         return k;
556*4882a593Smuzhiyun 
557*4882a593Smuzhiyun 
558*4882a593Smuzhiyun     //qDebug() << "\t try " << key  << contains(key);
559*4882a593Smuzhiyun     return key;
560*4882a593Smuzhiyun }
561*4882a593Smuzhiyun 
562*4882a593Smuzhiyun 
localizedValue(const QString & key,const QVariant & defaultValue) const563*4882a593Smuzhiyun QVariant XdgDesktopFile::localizedValue(const QString& key, const QVariant& defaultValue) const
564*4882a593Smuzhiyun {
565*4882a593Smuzhiyun     return value(localizedKey(key), defaultValue);
566*4882a593Smuzhiyun }
567*4882a593Smuzhiyun 
contains(const QString & key) const568*4882a593Smuzhiyun bool XdgDesktopFile::contains(const QString& key) const
569*4882a593Smuzhiyun {
570*4882a593Smuzhiyun     QString path = (!prefix().isEmpty()) ? prefix() + QLatin1Char('/') + key : key;
571*4882a593Smuzhiyun     return d->mItems.contains(path);
572*4882a593Smuzhiyun }
573*4882a593Smuzhiyun 
574*4882a593Smuzhiyun 
isValid() const575*4882a593Smuzhiyun bool XdgDesktopFile::isValid() const
576*4882a593Smuzhiyun {
577*4882a593Smuzhiyun     return d->mIsValid;
578*4882a593Smuzhiyun }
579*4882a593Smuzhiyun 
580*4882a593Smuzhiyun /************************************************
581*4882a593Smuzhiyun  Returns the QIcon corresponding to name in the current icon theme. If no such icon
582*4882a593Smuzhiyun  is found in the current theme fallback is return instead.
583*4882a593Smuzhiyun  ************************************************/
fromTheme(const QString & iconName,int size,const QIcon & fallback)584*4882a593Smuzhiyun QIcon XdgDesktopFile::fromTheme(const QString& iconName, int size, const QIcon& fallback)
585*4882a593Smuzhiyun {
586*4882a593Smuzhiyun     return QIcon::fromTheme(iconName, fallback);
587*4882a593Smuzhiyun }
588*4882a593Smuzhiyun 
icon(int size,const QIcon & fallback) const589*4882a593Smuzhiyun QIcon const XdgDesktopFile::icon(int size, const QIcon& fallback) const
590*4882a593Smuzhiyun {
591*4882a593Smuzhiyun     QIcon result = fromTheme(value(iconKey).toString(), size,  fallback);
592*4882a593Smuzhiyun 
593*4882a593Smuzhiyun     if (result.isNull() && type() == ApplicationType) {
594*4882a593Smuzhiyun         result = fromTheme(QLatin1String("application-x-executable.png"), size);
595*4882a593Smuzhiyun         // TODO Maybe defaults for other desktopfile types as well..
596*4882a593Smuzhiyun     }
597*4882a593Smuzhiyun 
598*4882a593Smuzhiyun     return result;
599*4882a593Smuzhiyun }
600*4882a593Smuzhiyun 
601*4882a593Smuzhiyun 
type() const602*4882a593Smuzhiyun XdgDesktopFile::Type XdgDesktopFile::type() const
603*4882a593Smuzhiyun {
604*4882a593Smuzhiyun     return d->mType;
605*4882a593Smuzhiyun }
606*4882a593Smuzhiyun 
607*4882a593Smuzhiyun 
608*4882a593Smuzhiyun /************************************************
609*4882a593Smuzhiyun  Starts the program defined in this desktop file in a new process, and detaches
610*4882a593Smuzhiyun  from it. Returns true on success; otherwise returns false. If the calling process
611*4882a593Smuzhiyun  exits, the detached process will continue to live.
612*4882a593Smuzhiyun 
613*4882a593Smuzhiyun  Urls - the list of URLs or files to open, can be empty (app launched without
614*4882a593Smuzhiyun   argument)
615*4882a593Smuzhiyun  If the function is successful then *pid is set to the process identifier of the
616*4882a593Smuzhiyun  started process.
617*4882a593Smuzhiyun  ************************************************/
startDetached(const QStringList & urls) const618*4882a593Smuzhiyun bool XdgDesktopFile::startDetached(const QStringList& urls) const
619*4882a593Smuzhiyun {
620*4882a593Smuzhiyun     switch(d->mType)
621*4882a593Smuzhiyun     {
622*4882a593Smuzhiyun     case ApplicationType:
623*4882a593Smuzhiyun         return d->startApplicationDetached(this, QString{}, urls);
624*4882a593Smuzhiyun         break;
625*4882a593Smuzhiyun 
626*4882a593Smuzhiyun     case LinkType:
627*4882a593Smuzhiyun         return false;
628*4882a593Smuzhiyun         break;
629*4882a593Smuzhiyun 
630*4882a593Smuzhiyun     default:
631*4882a593Smuzhiyun         return false;
632*4882a593Smuzhiyun     }
633*4882a593Smuzhiyun }
634*4882a593Smuzhiyun 
635*4882a593Smuzhiyun /************************************************
636*4882a593Smuzhiyun  This is an overloaded function.
637*4882a593Smuzhiyun  ************************************************/
startDetached(const QString & url) const638*4882a593Smuzhiyun bool XdgDesktopFile::startDetached(const QString& url) const
639*4882a593Smuzhiyun {
640*4882a593Smuzhiyun     if (url.isEmpty())
641*4882a593Smuzhiyun         return startDetached(QStringList());
642*4882a593Smuzhiyun     else
643*4882a593Smuzhiyun         return startDetached(QStringList(url));
644*4882a593Smuzhiyun }
645*4882a593Smuzhiyun 
646*4882a593Smuzhiyun 
parseCombinedArgString(const QString & program)647*4882a593Smuzhiyun static QStringList parseCombinedArgString(const QString &program)
648*4882a593Smuzhiyun {
649*4882a593Smuzhiyun     QStringList args;
650*4882a593Smuzhiyun     QString tmp;
651*4882a593Smuzhiyun     int quoteCount = 0;
652*4882a593Smuzhiyun     bool inQuote = false;
653*4882a593Smuzhiyun 
654*4882a593Smuzhiyun     // handle quoting. tokens can be surrounded by double quotes
655*4882a593Smuzhiyun     // "hello world". three consecutive double quotes represent
656*4882a593Smuzhiyun     // the quote character itself.
657*4882a593Smuzhiyun     for (int i = 0; i < program.size(); ++i) {
658*4882a593Smuzhiyun         if (program.at(i) == QLatin1Char('"')) {
659*4882a593Smuzhiyun             ++quoteCount;
660*4882a593Smuzhiyun             if (quoteCount == 3) {
661*4882a593Smuzhiyun                 // third consecutive quote
662*4882a593Smuzhiyun                 quoteCount = 0;
663*4882a593Smuzhiyun                 tmp += program.at(i);
664*4882a593Smuzhiyun             }
665*4882a593Smuzhiyun             continue;
666*4882a593Smuzhiyun         }
667*4882a593Smuzhiyun         if (quoteCount) {
668*4882a593Smuzhiyun             if (quoteCount == 1)
669*4882a593Smuzhiyun                 inQuote = !inQuote;
670*4882a593Smuzhiyun             quoteCount = 0;
671*4882a593Smuzhiyun         }
672*4882a593Smuzhiyun         if (!inQuote && program.at(i).isSpace()) {
673*4882a593Smuzhiyun             if (!tmp.isEmpty()) {
674*4882a593Smuzhiyun                 args += tmp;
675*4882a593Smuzhiyun                 tmp.clear();
676*4882a593Smuzhiyun             }
677*4882a593Smuzhiyun         } else {
678*4882a593Smuzhiyun             tmp += program.at(i);
679*4882a593Smuzhiyun         }
680*4882a593Smuzhiyun     }
681*4882a593Smuzhiyun     if (!tmp.isEmpty())
682*4882a593Smuzhiyun         args += tmp;
683*4882a593Smuzhiyun 
684*4882a593Smuzhiyun     return args;
685*4882a593Smuzhiyun }
686*4882a593Smuzhiyun 
687*4882a593Smuzhiyun 
replaceVar(QString & str,const QString & varName,const QString & after)688*4882a593Smuzhiyun void replaceVar(QString &str, const QString &varName, const QString &after)
689*4882a593Smuzhiyun {
690*4882a593Smuzhiyun     str.replace(QRegExp(QString::fromLatin1("\\$%1(?!\\w)").arg(varName)), after);
691*4882a593Smuzhiyun     str.replace(QRegExp(QString::fromLatin1("\\$\\{%1\\}").arg(varName)), after);
692*4882a593Smuzhiyun }
693*4882a593Smuzhiyun 
userDir(UserDirectory dir)694*4882a593Smuzhiyun QString userDir(UserDirectory dir)
695*4882a593Smuzhiyun {
696*4882a593Smuzhiyun     // possible values for UserDirectory
697*4882a593Smuzhiyun     Q_ASSERT(!(dir < Desktop || dir > Videos));
698*4882a593Smuzhiyun     if (dir < Desktop || dir > Videos)
699*4882a593Smuzhiyun         return QString();
700*4882a593Smuzhiyun 
701*4882a593Smuzhiyun     QString folderName = userDirectoryString[dir];
702*4882a593Smuzhiyun     QString fallback;
703*4882a593Smuzhiyun     const QString home = QFile::decodeName(qgetenv("HOME"));
704*4882a593Smuzhiyun 
705*4882a593Smuzhiyun     if (home.isEmpty())
706*4882a593Smuzhiyun         return QString::fromLatin1("/tmp");
707*4882a593Smuzhiyun     else if (dir == Desktop)
708*4882a593Smuzhiyun         fallback = QString::fromLatin1("%1/%2").arg(home, QLatin1String("Desktop"));
709*4882a593Smuzhiyun     else
710*4882a593Smuzhiyun         fallback = home;
711*4882a593Smuzhiyun 
712*4882a593Smuzhiyun     return fallback;
713*4882a593Smuzhiyun 
714*4882a593Smuzhiyun     QString s = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
715*4882a593Smuzhiyun     fixBashShortcuts(s);
716*4882a593Smuzhiyun     QDir d(s);
717*4882a593Smuzhiyun     if (!d.exists())
718*4882a593Smuzhiyun     {
719*4882a593Smuzhiyun         if (!d.mkpath(QLatin1String(".")))
720*4882a593Smuzhiyun         {
721*4882a593Smuzhiyun             qWarning() << QString::fromLatin1("Can't create %1 directory.").arg(d.absolutePath());
722*4882a593Smuzhiyun         }
723*4882a593Smuzhiyun     }
724*4882a593Smuzhiyun     QString r = d.absolutePath();
725*4882a593Smuzhiyun     removeEndingSlash(r);
726*4882a593Smuzhiyun     QString configDir(s);
727*4882a593Smuzhiyun     QFile configFile(configDir + QLatin1String("/user-dirs.dirs"));
728*4882a593Smuzhiyun     if (!configFile.exists())
729*4882a593Smuzhiyun         return fallback;
730*4882a593Smuzhiyun 
731*4882a593Smuzhiyun     if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text))
732*4882a593Smuzhiyun         return fallback;
733*4882a593Smuzhiyun 
734*4882a593Smuzhiyun     QString userDirVar(QLatin1String("XDG_") + folderName.toUpper() + QLatin1String("_DIR"));
735*4882a593Smuzhiyun     QTextStream in(&configFile);
736*4882a593Smuzhiyun     QString line;
737*4882a593Smuzhiyun     while (!in.atEnd())
738*4882a593Smuzhiyun     {
739*4882a593Smuzhiyun         line = in.readLine();
740*4882a593Smuzhiyun         if (line.contains(userDirVar))
741*4882a593Smuzhiyun         {
742*4882a593Smuzhiyun             configFile.close();
743*4882a593Smuzhiyun 
744*4882a593Smuzhiyun             // get path between quotes
745*4882a593Smuzhiyun             line = line.section(QLatin1Char('"'), 1, 1);
746*4882a593Smuzhiyun             if (line.isEmpty())
747*4882a593Smuzhiyun                 return fallback;
748*4882a593Smuzhiyun             line.replace(QLatin1String("$HOME"), QLatin1String("~"));
749*4882a593Smuzhiyun             fixBashShortcuts(line);
750*4882a593Smuzhiyun             return line;
751*4882a593Smuzhiyun         }
752*4882a593Smuzhiyun     }
753*4882a593Smuzhiyun 
754*4882a593Smuzhiyun     configFile.close();
755*4882a593Smuzhiyun     return fallback;
756*4882a593Smuzhiyun }
757*4882a593Smuzhiyun 
expandEnvVariables(const QString str)758*4882a593Smuzhiyun QString expandEnvVariables(const QString str)
759*4882a593Smuzhiyun {
760*4882a593Smuzhiyun     QString scheme = QUrl(str).scheme();
761*4882a593Smuzhiyun 
762*4882a593Smuzhiyun     if (scheme == QLatin1String("http")   || scheme == QLatin1String("https") || scheme == QLatin1String("shttp") ||
763*4882a593Smuzhiyun         scheme == QLatin1String("ftp")    || scheme == QLatin1String("ftps")  ||
764*4882a593Smuzhiyun         scheme == QLatin1String("pop")    || scheme == QLatin1String("pops")  ||
765*4882a593Smuzhiyun         scheme == QLatin1String("imap")   || scheme == QLatin1String("imaps") ||
766*4882a593Smuzhiyun         scheme == QLatin1String("mailto") ||
767*4882a593Smuzhiyun         scheme == QLatin1String("nntp")   ||
768*4882a593Smuzhiyun         scheme == QLatin1String("irc")    ||
769*4882a593Smuzhiyun         scheme == QLatin1String("telnet") ||
770*4882a593Smuzhiyun         scheme == QLatin1String("xmpp")   ||
771*4882a593Smuzhiyun         scheme == QLatin1String("nfs")
772*4882a593Smuzhiyun       )
773*4882a593Smuzhiyun         return str;
774*4882a593Smuzhiyun 
775*4882a593Smuzhiyun     const QString homeDir = QFile::decodeName(qgetenv("HOME"));
776*4882a593Smuzhiyun 
777*4882a593Smuzhiyun     QString res = str;
778*4882a593Smuzhiyun     res.replace(QRegExp(QString::fromLatin1("~(?=$|/)")), homeDir);
779*4882a593Smuzhiyun 
780*4882a593Smuzhiyun     replaceVar(res, QLatin1String("HOME"), homeDir);
781*4882a593Smuzhiyun     replaceVar(res, QLatin1String("USER"), QString::fromLocal8Bit(qgetenv("USER")));
782*4882a593Smuzhiyun 
783*4882a593Smuzhiyun     replaceVar(res, QLatin1String("XDG_DESKTOP_DIR"),   userDir(Desktop));
784*4882a593Smuzhiyun     replaceVar(res, QLatin1String("XDG_TEMPLATES_DIR"), userDir(Templates));
785*4882a593Smuzhiyun     replaceVar(res, QLatin1String("XDG_DOCUMENTS_DIR"), userDir(Documents));
786*4882a593Smuzhiyun     replaceVar(res, QLatin1String("XDG_MUSIC_DIR"), userDir(Music));
787*4882a593Smuzhiyun     replaceVar(res, QLatin1String("XDG_PICTURES_DIR"), userDir(Pictures));
788*4882a593Smuzhiyun     replaceVar(res, QLatin1String("XDG_VIDEOS_DIR"), userDir(Videos));
789*4882a593Smuzhiyun     replaceVar(res, QLatin1String("XDG_PHOTOS_DIR"), userDir(Pictures));
790*4882a593Smuzhiyun 
791*4882a593Smuzhiyun     return res;
792*4882a593Smuzhiyun }
793*4882a593Smuzhiyun 
794*4882a593Smuzhiyun 
expandExecString(const QStringList & urls) const795*4882a593Smuzhiyun QStringList XdgDesktopFile::expandExecString(const QStringList& urls) const
796*4882a593Smuzhiyun {
797*4882a593Smuzhiyun     if (d->mType != ApplicationType)
798*4882a593Smuzhiyun         return QStringList();
799*4882a593Smuzhiyun 
800*4882a593Smuzhiyun     QStringList result;
801*4882a593Smuzhiyun 
802*4882a593Smuzhiyun     QString execStr = value(execKey).toString();
803*4882a593Smuzhiyun     unEscapeExec(execStr);
804*4882a593Smuzhiyun     const QStringList tokens = parseCombinedArgString(execStr);
805*4882a593Smuzhiyun 
806*4882a593Smuzhiyun     for (QString token : tokens)
807*4882a593Smuzhiyun     {
808*4882a593Smuzhiyun         // The parseCombinedArgString() splits the string by the space symbols,
809*4882a593Smuzhiyun         // we temporarily replaced them on the special characters.
810*4882a593Smuzhiyun         // Now we reverse it.
811*4882a593Smuzhiyun         token.replace(01, QLatin1Char(' '));
812*4882a593Smuzhiyun         token.replace(02, QLatin1Char('\t'));
813*4882a593Smuzhiyun         token.replace(03, QLatin1Char('\n'));
814*4882a593Smuzhiyun 
815*4882a593Smuzhiyun         // ----------------------------------------------------------
816*4882a593Smuzhiyun         // A single file name, even if multiple files are selected.
817*4882a593Smuzhiyun         if (token == QLatin1String("%f"))
818*4882a593Smuzhiyun         {
819*4882a593Smuzhiyun             continue;
820*4882a593Smuzhiyun         }
821*4882a593Smuzhiyun 
822*4882a593Smuzhiyun         // ----------------------------------------------------------
823*4882a593Smuzhiyun         // A list of files. Use for apps that can open several local files at once.
824*4882a593Smuzhiyun         // Each file is passed as a separate argument to the executable program.
825*4882a593Smuzhiyun         if (token == QLatin1String("%F"))
826*4882a593Smuzhiyun         {
827*4882a593Smuzhiyun             continue;
828*4882a593Smuzhiyun         }
829*4882a593Smuzhiyun 
830*4882a593Smuzhiyun         // ----------------------------------------------------------
831*4882a593Smuzhiyun         // A single URL. Local files may either be passed as file: URLs or as file path.
832*4882a593Smuzhiyun         if (token == QLatin1String("%u"))
833*4882a593Smuzhiyun         {
834*4882a593Smuzhiyun             if (!urls.isEmpty())
835*4882a593Smuzhiyun             {
836*4882a593Smuzhiyun                 QUrl url;
837*4882a593Smuzhiyun                 url.setUrl(expandEnvVariables(urls.at(0)));
838*4882a593Smuzhiyun                 result << ((!url.toLocalFile().isEmpty()) ? url.toLocalFile() : QString::fromUtf8(url.toEncoded()));
839*4882a593Smuzhiyun             }
840*4882a593Smuzhiyun             continue;
841*4882a593Smuzhiyun         }
842*4882a593Smuzhiyun 
843*4882a593Smuzhiyun         // ----------------------------------------------------------
844*4882a593Smuzhiyun         // A list of URLs. Each URL is passed as a separate argument to the executable
845*4882a593Smuzhiyun         // program. Local files may either be passed as file: URLs or as file path.
846*4882a593Smuzhiyun         if (token == QLatin1String("%U"))
847*4882a593Smuzhiyun         {
848*4882a593Smuzhiyun             for (const QString &s : urls)
849*4882a593Smuzhiyun             {
850*4882a593Smuzhiyun                 QUrl url(expandEnvVariables(s));
851*4882a593Smuzhiyun                 result << ((!url.toLocalFile().isEmpty()) ? url.toLocalFile() : QString::fromUtf8(url.toEncoded()));
852*4882a593Smuzhiyun             }
853*4882a593Smuzhiyun             continue;
854*4882a593Smuzhiyun         }
855*4882a593Smuzhiyun 
856*4882a593Smuzhiyun         // ----------------------------------------------------------
857*4882a593Smuzhiyun         // The Icon key of the desktop entry expanded as two arguments, first --icon
858*4882a593Smuzhiyun         // and then the value of the Icon key. Should not expand to any arguments if
859*4882a593Smuzhiyun         // the Icon key is empty or missing.
860*4882a593Smuzhiyun         if (token == QLatin1String("%i"))
861*4882a593Smuzhiyun         {
862*4882a593Smuzhiyun             QString icon = value(iconKey).toString();
863*4882a593Smuzhiyun             if (!icon.isEmpty())
864*4882a593Smuzhiyun                 result << QLatin1String("-icon") << icon.replace(QLatin1Char('%'), QLatin1String("%%"));
865*4882a593Smuzhiyun             continue;
866*4882a593Smuzhiyun         }
867*4882a593Smuzhiyun 
868*4882a593Smuzhiyun 
869*4882a593Smuzhiyun         // ----------------------------------------------------------
870*4882a593Smuzhiyun         // The translated name of the application as listed in the appropriate Name key
871*4882a593Smuzhiyun         // in the desktop entry.
872*4882a593Smuzhiyun         if (token == QLatin1String("%c"))
873*4882a593Smuzhiyun         {
874*4882a593Smuzhiyun             result << localizedValue(nameKey).toString().replace(QLatin1Char('%'), QLatin1String("%%"));
875*4882a593Smuzhiyun             continue;
876*4882a593Smuzhiyun         }
877*4882a593Smuzhiyun 
878*4882a593Smuzhiyun         // ----------------------------------------------------------
879*4882a593Smuzhiyun         // The location of the desktop file as either a URI (if for example gotten from
880*4882a593Smuzhiyun         // the vfolder system) or a local filename or empty if no location is known.
881*4882a593Smuzhiyun         if (token == QLatin1String("%k"))
882*4882a593Smuzhiyun         {
883*4882a593Smuzhiyun             break;
884*4882a593Smuzhiyun         }
885*4882a593Smuzhiyun 
886*4882a593Smuzhiyun         // ----------------------------------------------------------
887*4882a593Smuzhiyun         // Deprecated.
888*4882a593Smuzhiyun         // Deprecated field codes should be removed from the command line and ignored.
889*4882a593Smuzhiyun         if (token == QLatin1String("%d") || token == QLatin1String("%D") ||
890*4882a593Smuzhiyun             token == QLatin1String("%n") || token == QLatin1String("%N") ||
891*4882a593Smuzhiyun             token == QLatin1String("%v") || token == QLatin1String("%m")
892*4882a593Smuzhiyun             )
893*4882a593Smuzhiyun         {
894*4882a593Smuzhiyun             continue;
895*4882a593Smuzhiyun         }
896*4882a593Smuzhiyun 
897*4882a593Smuzhiyun         // ----------------------------------------------------------
898*4882a593Smuzhiyun         result << expandEnvVariables(token);
899*4882a593Smuzhiyun     }
900*4882a593Smuzhiyun 
901*4882a593Smuzhiyun     return result;
902*4882a593Smuzhiyun }
903*4882a593Smuzhiyun 
904*4882a593Smuzhiyun 
findDesktopFile(const QString & dirName,const QString & desktopName)905*4882a593Smuzhiyun QString findDesktopFile(const QString& dirName, const QString& desktopName)
906*4882a593Smuzhiyun {
907*4882a593Smuzhiyun     QDir dir(dirName);
908*4882a593Smuzhiyun     QFileInfo fi(dir, desktopName);
909*4882a593Smuzhiyun 
910*4882a593Smuzhiyun     if (fi.exists())
911*4882a593Smuzhiyun         return fi.canonicalFilePath();
912*4882a593Smuzhiyun 
913*4882a593Smuzhiyun     // Working recursively ............
914*4882a593Smuzhiyun     const QFileInfoList dirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot);
915*4882a593Smuzhiyun     for (const QFileInfo &d : dirs)
916*4882a593Smuzhiyun     {
917*4882a593Smuzhiyun         QString cn = d.canonicalFilePath();
918*4882a593Smuzhiyun         if (dirName != cn)
919*4882a593Smuzhiyun         {
920*4882a593Smuzhiyun             QString f = findDesktopFile(cn, desktopName);
921*4882a593Smuzhiyun             if (!f.isEmpty())
922*4882a593Smuzhiyun                 return f;
923*4882a593Smuzhiyun         }
924*4882a593Smuzhiyun     }
925*4882a593Smuzhiyun 
926*4882a593Smuzhiyun     return QString();
927*4882a593Smuzhiyun }
928*4882a593Smuzhiyun 
findDesktopFile(const QString & desktopName)929*4882a593Smuzhiyun QString findDesktopFile(const QString& desktopName)
930*4882a593Smuzhiyun {
931*4882a593Smuzhiyun     QString d = QFile::decodeName(qgetenv("XDG_DATA_DIRS"));
932*4882a593Smuzhiyun     QStringList dirs = d.split(QLatin1Char(':'), QString::SkipEmptyParts);
933*4882a593Smuzhiyun 
934*4882a593Smuzhiyun     if (dirs.isEmpty()) {
935*4882a593Smuzhiyun         dirs.append(QString::fromLatin1("/usr/local/share"));
936*4882a593Smuzhiyun         dirs.append(QString::fromLatin1("/usr/share"));
937*4882a593Smuzhiyun     } else {
938*4882a593Smuzhiyun         QMutableListIterator<QString> it(dirs);
939*4882a593Smuzhiyun         while (it.hasNext()) {
940*4882a593Smuzhiyun             const QString dir = it.next();
941*4882a593Smuzhiyun             if (!dir.startsWith(QLatin1Char('/')))
942*4882a593Smuzhiyun                 it.remove();
943*4882a593Smuzhiyun         }
944*4882a593Smuzhiyun     }
945*4882a593Smuzhiyun 
946*4882a593Smuzhiyun     dirs.removeDuplicates();
947*4882a593Smuzhiyun     cleanAndAddPostfix(dirs, QString());
948*4882a593Smuzhiyun 
949*4882a593Smuzhiyun     QString s = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
950*4882a593Smuzhiyun     fixBashShortcuts(s);
951*4882a593Smuzhiyun     removeEndingSlash(s);
952*4882a593Smuzhiyun     dirs.prepend(s);
953*4882a593Smuzhiyun     for (const QString &dirName : const_cast<const QStringList&>(dirs))
954*4882a593Smuzhiyun     {
955*4882a593Smuzhiyun         QString f = findDesktopFile(dirName + QLatin1String("/applications"), desktopName);
956*4882a593Smuzhiyun         if (!f.isEmpty())
957*4882a593Smuzhiyun             return f;
958*4882a593Smuzhiyun     }
959*4882a593Smuzhiyun 
960*4882a593Smuzhiyun     return QString();
961*4882a593Smuzhiyun }
962*4882a593Smuzhiyun 
963*4882a593Smuzhiyun 
964*4882a593Smuzhiyun     // First, we look in following places for a default in specified order:
965*4882a593Smuzhiyun     // ~/.config/mimeapps.list
966*4882a593Smuzhiyun     // /etc/xdg/mimeapps.list
967*4882a593Smuzhiyun     // ~/.local/share/applications/mimeapps.list
968*4882a593Smuzhiyun     // /usr/local/share/applications/mimeapps.list
969*4882a593Smuzhiyun     // /usr/share/applications/mimeapps.list
970*4882a593Smuzhiyun 
971*4882a593Smuzhiyun 
972