Vladimir-csp / xdg-terminal-exec

Proposal for XDG Default Terminal Execution Specification and shell-based reference implementation.
GNU General Public License v3.0
77 stars 12 forks source link

xdg-terminal-exec

Proposal for XDG Default Terminal Execution Specification and reference shell-based implementation. The proposal PR can be found here.

(!) Please be advised that while this spec is in proposed state, backwards compatibility is maintained as best effort and is not guaranteed.

Since v0.12.0 Desktop entry string keys and Exec are handled (expanded and dequoted) strictly according to Desktop Entry Spec.

v0.11.0 introduced some important changes:

Default Terminal Execution Specification

This spec consists of three parts:

  1. Additional keys for the desktop-entry-spec that allow terminals to define how they are invoked.
  2. The configuration spec for defining and customizing default terminals in context of Desktop Environments and user overrides. This is crafted in image of mime-apps-spec using different config files in similar structure, governed by basedir-spec.
  3. A CLI interface to launch the configured terminal from (2) with the cli options corresponding to keys from (1). Working reference implementation of this CLI interface can be found here:

Terminal emulators (with their exec arguments) are described by stock Desktop Entries located in applications subdirs of XDG data hierarchy.

Preferred terminals are configured by listing their entry IDs in configuration files. Optionally an entry ID can be suffixed with action ID, delimited by : (entry-id.desktop:action-id).

Desktop entry for a terminal

Stock Desktop Entries for terminal emulator applications are used. Entry eligible for selection should have TerminalEmulator Category and X-TerminalArgExec= key (X-TerminalArgExec= while the Specification is in proposed status) which contains command execution argument: an argument to be placed before command if it is requested. If the terminal accepts commands without special argument, this key should be explicitly set to an empty value (execution argument will be omitted). Although in this case it is recommended to use -- if the terminal handles it correctly.

Additional argument keys

Implementations should expect these keys prefixed with X- while the Specification is in proposed status.

If argument expects a value and is defined as ending with =, value should be appended to the same argument without a white space.

Since terminal emulators have varying set of features, any option support is considred best effort.

Whether launched terminal process waits for command to finish or exits immediately (i.e. after sending IPC request to a master process) is not defined by the this Specification. Some IPC-using terminals provide separate entries or actions for launching separate processes without IPC.

Configuration

Location

Configuration files are named ${desktop}-xdg-terminals.list or xdg-terminals.list and placed in XDG config hierarchy.

${desktop} here is a lowercased string that can be matched (case-insensitively) against items of $XDG_CURRENT_DESKTOP (a colon-separated list of names for the current DE) in order of decreasing priority.

Lower priority fallback config files (for upstream/distribution usage) are paced in system part of XDG data hierarchy within xdg-terminal-exec subdirs.

Default paths for configuration and data are resolved into (in order of decreasing priority):

File format

The format is a simple newline-separated list with decreasing priority.

Empty lines and lines starting with # are ignored, dangling whitespaces are trimmed.

Line format:

terminal.desktop or terminal.desktop:action: marks entry for explicit selection.

-terminal.desktop excludes entry from fallback selection.

+terminal.desktop protects entry from fallback exclusion.

Special directives for modifying behavior of implementations may be present in config files. Directives that are not listed by this Specification and are not understood by a particular implementation should be discarded.

Directives should not contain .desktop substring in them. And it is recommended to start directives with a symbol that is not valid for entry ID, i.e. /.

Priority of selecting an entry

Syntax

xdg-terminal-exec [-[-]options ...] [command [arguments ...]]

A set of arguments each starting with - and located at the beginning of the input command line is considered to be options processed by the implementation and should always be discarded from the resulting command line, inlcuding -e or any matching execution argument. Option processing should end on --, -e, or matching execution argument.

Each option should be monolithic: as a single argument, value (if applicable) delimited by =. Recognized options:

Requested options then translated into arguments according to the keys existing in the terminal's Desktop Entry (or discarded otherwise).

If a command (with or witout its arguments) is given, and TerminalArgExec= key is not explicitly empty, then the value TerminalArgExec= (defaulting to -e) is appended as the next argument.

Next the command and arguments are passed as is.

The resulting command is executed without forking.

Limitations and compliance of terminals

There is no mechanism for handling special quoting and arguments/strings that may be required for some terminals.

The expected behavior is modelled after xterm and is arguably common for a majority of terminals. This example:

xdg-terminal-exec nano "some file with spaces"\ and\ unquoted\ spaces second\ file

is expected to launch nano editing two files named some file with spaces and unquoted spaces and second file.

Notes

Arguments

IMHO xterm -e handling of arguments is the golden standard, any terminal that fails to do so should be bugreported.

Some examples of compliant terminals: xterm, alacritty, kitty, foot, qterminal

Terminals that use -e but mangle arguments: sakura

This shell-based implementation and performance

Setting DEBUG env to a truthy value will output verbose messages to stderr.

The shell code itself is quite optimized and fast, especially when using a slick sh implementation like dash.

The most taxing part of the algorithm is reading all the Desktop Entry files for parsing in search of an applicable terminal among them.

Having a valid entry specified in *xdg-terminals.list speeds up the process significantly, shifting the bottleneck to find calls for composing the list of desktop entries.

Compatibility mode and TerminalArgExec defaults

This implementation is currently set to compatibility mode by default: Previous style [X-]ExecArg execution argument is supported, its presense is not enforced and value defaults to -e. Defaults can also be amended by /execarg_default:entry.desktop:arg directives for specific Entry IDs. It is not part of the Spec, but a way to make things work until the Spec is made official and upstream starts shipping TerminalArgExec.

Compat mode is enabled by default and can be controlled by first encountered /execarg_compat|/execarg_strict direcive in the configs or XTE_EXECARG_COMPAT env var (truthy or falsy value, has priority).

Cache

This implementation can also cache selected terminal for fast read at a cost of reading one file (${XDG_CACHE_HOME:-$HOME/.cache}/xdg-terminal-exec) and feeding md5sum the value of $XDG_CURRENT_DESKTOP and output of ls -LRl for all possible config file and data dir paths in one go. Valid cache bypasses reading of any other file.

This feature is enabled by default and can be controlled by first encountered /enable_cache|/disable_cache direcive in the configs or XTE_CACHE_ENABLED env var (truthy or falsy value, has priority).

Unless XTE_CACHE_ENABLED is false, an attempt at reading the cache file is always performed though. Its existence translates into initial assumption about cache feature state. If cache is invalid (which it should be if a config was edited to disable the cache), usual process of reading configs and entries will occur. The cache file is always removed when the script knows for sure that the cache feature is disabled.

Caveat: If the cache was enabled solely via truthy XTE_CACHE_ENABLED value, and the var was later removed, the script will not know that the cache is disabled until one of three things happens: cache file is removed, something invalidates it (like touching/editing a config/entry), or a run with falsy XTE_CACHE_ENABLED is performed.

Exec command parsing, escape sequences.

Strings in Desktop Entries have a set of escape sequences to be expanded. On top of that, each arg in Exec key is expected to have them too, along with other special characters. This means escape sequences in Exec are expected to be expanded twice: in string value, then in argument "dequoting".

This implementaton now does proper expansion, dequoting and command tokenization. It will invalidate entries that fail those rules.