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