g_app_info_get_executable() doesn't take into account shell-style quoting
g_app_info_get_executable()
gives the wrong results if the executable is quoted.
To reproduce
Have .desktop files similar to these:
$ cat ~/.local/share/applications/exe-with-spaces.desktop
[Desktop Entry]
Name=Executable has spaces
Exec="/home/smcv/bin/hello world" --friendly
Icon=face-monkey-symbolic
Terminal=true
Type=Application
$ cat ~/.local/share/applications/quoted-exe.desktop
[Desktop Entry]
Name=Executable is quoted
Exec="/home/smcv/bin/hello" --exemplary
Icon=face-smile-symbolic
Terminal=true
Type=Application
where /home/smcv/bin/hello
and /home/smcv/bin/hello world
exist and are executable (their contents don't matter, I used shell scripts that print a message and prompt for input). GNOME Shell can launch these toy "applications" successfully.
Then parse them with GDesktopAppInfo
, which could be done from C code, but is easier to do as a throwaway prototype by using GObject-Introspection:
$ python3
>>> from gi.repository import Gio, GLib
>>> exe_with_spaces = Gio.DesktopAppInfo.new("exe-with-spaces.desktop")
>>> quoted_exe = Gio.DesktopAppInfo.new("quoted-exe.desktop")
>>> exe_with_spaces.get_commandline()
'"/home/smcv/bin/hello world" --friendly'
>>> quoted_exe.get_commandline()
'"/home/smcv/bin/hello" --exemplary'
>>> GLib.shell_parse_argv(quoted_exe.get_commandline())
(True, argvp=['/home/smcv/bin/hello', '--exemplary'])
>>> GLib.shell_parse_argv(exe_with_spaces.get_commandline())
(True, argvp=['/home/smcv/bin/hello world', '--friendly'])
>>> quoted_exe.get_executable()
...
>>> exe_with_spaces.get_executable()
...
Expected result
get_executable()
should take the string from get_commandline()
, split it as if via g_shell_parse_argv()
, and take the first "word". In my example, I would expect /home/smcv/bin/hello
and /home/smcv/bin/hello world
, the same as argvp[0]
.
Actual result
get_executable()
just splits get_commandline()
on whitespace, without taking into account the shell-style quoting specified by the Desktop Entry specification:
>>> quoted_exe.get_executable()
'"/home/smcv/bin/hello"'
>>> exe_with_spaces.get_executable()
'"/home/smcv/bin/hello'
Real-world relevance
Steam creates .desktop files for Steam games, and parses .desktop files for non-Steam games that are added to the Steam library. At the moment, it uses its own .desktop implementation, which is considerably less correct than GLib's. I'd like it to use GDesktopAppInfo on input and GKeyFile on output.
Games originally written on Windows and later ported to Linux often have spaces in the name of their installation directory, which means their .desktop files need to use shell-style quoting for argv[0]
.
Workaround
Replace
char *executable;
executable = g_strdup (g_app_info_get_executable (appinfo));
with
int argc;
char **argv;
char *executable = NULL;
if (g_shell_parse_argv (g_app_info_get_commandline (appinfo), &argc, &argv, &error))
executable = g_strdup (argv[0]);
else
... handle error ...
g_strfreev (argv);