Subject: Re: bug#67736: 30.0.50; emacsclient.desktop fails with quoting-related error

From: Mekeor Melire <[email-masked]>
Date: Sun, 17 Dec 2023 00:14:10 +0000

2023-12-10 13:56 [email-masked]:

> We have all that complexity for a reason. Please look at Git history
> of that file to see how we arrived at what we have now.

Let's take a look at the commit-history of the Exec= line of the
emacsclient.desktop file [1]. It was introduced in the following
commit with a very simple and comprehensible Exec= line.

    Commit:  1a845a672dc73c8e98e6cb9bb734616e168e60ba
    Date:    Sun Aug 9 15:57:51 2020 +0200
    Author:  Carlos Pita <[email-masked]>
    Message: Add file, use emacsd as StartupWMClass.
    Code:    Exec=emacsclient -c %F

Then, there were discussions at bug#49195, bug#49505 and
emacs-devel-thread "GUI X-FreeDesktop integration" [2] which resulted
in the following commit which changed the Exec= line and made it much
more complex by invoking a shell:

    Commit:  cbf220bc31c0a00c45b22c140eda7854d81d991b
    Date:    Wed Jun 30 15:11:21 2021 +0200
    Author:  Peter Oliver <[email-masked]>
    Message:
        From .desktop files, reuse a frame or start a new Emacs as
        required. Automatically try to reuse an existing frame, open a
        new frame, or start a new Emacs daemon. Add actions for
        specific behaviours (bug#49195).
    Documentation:
        You may use the "Emacs (Client)" menu entry to connect to an
        Emacs server with emacsclient. The daemon starts if not
        already running.
    Code:
        Exec=sh -c 'if [ -n "$*" ]; then exec emacsclient
        --alternate-editor= --display="$DISPLAY" "$@"; else exec
        emacsclient --alternate-editor= --create-frame; fi'
        placeholder %F

The commit-message, the committed documentation and the
patch-submission-message (at bug#49195) explain that
emacsclient.desktop will now start a daemon when not already running.
This is achieved by passing --alternate-editor=.

But all of these resources fail to mention the reason for the shell
invocation. It can be reconstructed in a message [3] from Peter
Oliver, in a thread on emacs-devel:

    I believe that the default behaviour when opening a file from a
    desktop’s file manager should be to open it in an existing GUI
    frame if one exists, or a new GUI frame if one does not. Clicking
    Emacs in a desktop’s application launcher should open a new GUI
    frame.

    The trouble is that, if Emacs is running as a daemon with no
    frames, `emacsclient /path/to/foo` will create a new TTY frame
    rather than a GUI frame. Should this behaviour be changed so that
    a GUI frame is preferred if $DISPLAY is set? While we’re here,
    should we report an error if there is no display and no TTY?

I.e. the purpose of the shell invocation is to be able to use the
shell-conditional

    if [ -n "$*" ]

in order to check if no arguments (file-paths) have been passed to
emacsclient.desktop (i.e. e.g. if the user double-clicked a file in a
file-explorer for which emacsclient.desktop configured to be the default
application); or otherwise if there were no arguments passed (i.e. e.g.
if the user clicked on the emacsclient.desktop-icon in their
application-launcher).

In the first case ("Open one or more specified files in Emacsclient"),
--desktop=$DESKTOP is passed as additional command-line parameter;
whereas in the second case ("Open Emacsclient, but no file(s) in
particular"), --create-frame is passed additionally.

For me, personally, the emacsclient.desktop is unusable because of an
error at an earlier stage. Namely, something goes wrong when XDG invokes
the shell, due to the quoting/escaping syntax. The two last commits on
the Exec= line tried to fix this already but it still does not work for
me:

    Commit: 0a4b66f82752adeb808851a36eabd0554779b33c
    Author: Peter Oliver <[email-masked]>
    Date:   Wed Jul 7 22:04:01 2021 +0100
    Message:
        Valid quoting in .desktop files. Quote according to the rules
        in the Freedesktop.org Desktop Entry Specification.
    Code:
        Exec=sh -c "if [ -n \\"\\$*\\" ]; then exec emacsclient
        --alternate-editor= --display=\\"\\$DISPLAY\\" \\"\\$@\\";
        else exec emacsclient --alternate-editor= --create-frame; fi"
        placeholder %F

    Commit: d32091199ae5de590a83f1542a01d75fba000467
    Author: Ulrich Müller <[email-masked]>
    Date:   Mon Dec 19 16:51:20 2022 +0100
    Message:
        Use `sh` rather than `placeholder` as the command name of the
        shell wrapper. (Bug#60204)
    Code:
        Exec=sh -c "if [ -n \\"\\$*\\" ]; then exec emacsclient
        --alternate-editor= --display=\\"\\$DISPLAY\\" \\"\\$@\\";
        else exec emacsclient --alternate-editor= --create-frame; fi"
        sh %F

The reason for our struggle with the escaping-rules of the Exec-line
in .desktop-files are:

(1.) They are very complex. [5]

(2.) xdg-utils is buggy. [6] It does not implement its own
escaping-specification correctly. Other software projects do not run
into this bug because they do not have such uncommonly complex
Exec-lines in their .desktop-files. And it sounds like it'll take the
freedesktop.org-developers a long time to fix the bug, firstly because
it'll involve a rewrite of that part of xdg-utils in another programming
language (Python instead of Shell), and secondly because the project was
pretty dead for a long time until it only recently gained some momentum
in July 2023.

Note that I don't know if the .desktop-file works fine on MacOS. But I
doubt that it works for any GNU+Linux user because there haven't been
any releases of xdg-utils in recent years, AFAIK.

In this bug-report I do not want to raise the question which
command-line flags/parameters (--create-frame, --display,
--alternate-editor etc.) are passed to emacsclient by default. I'm
reporting that the .desktop-file does not work for me and probably for
none GNU+Linux-user at all. And because of the bug-, maintenance- and
project-related situation of xdg-utils, which indicates that a bug-fix
will take a long time, I'd like to propose to ditch the complex
shell-invocation (which necessarily involves the buggy escaping) and
instead use a simple and concise one-line, e.g.:

    Exec=emacsclient --alternate-editor= --create-frame %F

(Or maybe use the recently added --reuse-frame flag instead of
--create-frame.) Note that we would not loose the "Start an Emacs-Daemon
if not started" logic which was the justification for the commit which
introduced the shell-invocation (since we keep --alternate-editor=). We
would only loose the "create a frame if triggered without
file-arguments" logic which seems to be a rather personal taste anyway
because the respective message had not gotten any responses from others.
(See [3].)

[1]  You can easily do so yourself by calling
     `magit-log-trace-definition' when point is on the respective
     line.

[2]  Message-ID <[email-masked]>,
     https://yhetil.org/emacs-devel/[email-masked]/

[3]  Message-ID <[email-masked]>,
     https://yhetil.org/emacs-devel/[email-masked]/

[3]  The shell invocation looks like:

         sh -c COMMAND_STRING COMMAND_NAME ARGUMENTS

     where:

     - `sh' refers to a POSIX-compliant shell. (Often linked to bash,
     dash or zsh.) According to POSIX, the parameter `-c' enables
     passing and executing commands, COMMAND_STRING.

     - As COMMAND_NAME, which specifies the "name of the shell, which
     is used in warning and error messages"[4], we pass `sh', which I
     find okay but `shell-spawning-emacsclient' seems more useful.

     - As ARGUMENTS, we have `%F' which, according to the
     freedesktop.org-documentation, represents the names of the files
     to be opened [4]:

           A list of files. Use for apps that can open several local
           files at once. Each file is passed as a separate argument
           to the executable program.

[4]  POSIX-Specification:
     <https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html>.
     BASH-documentation: (info "(bash) Invoking Bash") or
     <https://www.gnu.org/software/bash/manual/html_node/Invoking-Bash.html>.
     Elaborative response on stack-exchange-network:
     <https://superuser.com/a/1526230>.

[5]  https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s07.html

[6]  https://gitlab.freedesktop.org/xdg/xdg-utils/-/issues/236 and https://gitlab.freedesktop.org/xdg/xdg-utils/-/issues/174