Skip to content

Instantly share code, notes, and snippets.

@ernstki
Last active December 3, 2025 11:40
Show Gist options
  • Select an option

  • Save ernstki/c6b72b00e11ce81b398e1cbceae7591c to your computer and use it in GitHub Desktop.

Select an option

Save ernstki/c6b72b00e11ce81b398e1cbceae7591c to your computer and use it in GitHub Desktop.
Stupid Linux `.desktop` and elementary OS Contractor tricks

elementary OS's Contractor service uses standard Linux .desktop files (spec) with a .contract extension to provide context (secondary-click) menu entries for specific MIME types. System-wide apps put these in /usr/share/contractor, whatever the Flatpak version of that is, and you can create your own in ~/.local/share/contractor. Below are some examples.

Contractor actions for all MIME types

If you want a specific Contractor action to apply to all filetypes, including device special files, directories, whatever, you can exploit the ! (exclusion) operator in the MimeType key, like this:

MimeType=!nothing;

This does not appear to be mentioned anywhere in the spec), so it's probably an unofficial extension that might not work in other contexts or desktop environments. See this Unix & Linux SE post for possible alternatives.

Disabling Contractor actions

Simply rename them:

mv tesseract-text.contract{,.disabled}

The Contractor daemon picks up on the changes (usually) and there is (usually) no need to quit and reopen Files.

Quoting

The discussion about quoting the Exec key in the spec is bonkers.

Arguments may be quoted in whole. If an argument contains a reserved character the argument must be quoted. The rules for quoting of arguments is also applicable to the executable name or path of the executable program as provided.

Quoting must be done by enclosing the argument between double quotes and escaping the double quote character, backtick character ("`"), dollar sign ("$") and backslash character ("\") by preceding it with an additional backslash character. Implementations must undo quoting before expanding field codes and before passing the argument to the executable program. Reserved characters are space (" "), tab, newline, double quote, single quote ("'"), backslash character ("\"), greater-than sign (">"), less-than sign ("<"), tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon (";"), dollar sign ("$"), asterisk ("*"), question mark ("?"), hash mark ("#"), parenthesis ("(") and (")") and backtick character ("`").

Note that the general escape rule for values of type string states that the backslash character can be escaped as ("\\") as well and that this escape rule is applied before the quoting rule. As such, to unambiguously represent a literal backslash character in a quoted argument in a desktop entry file requires the use of four successive backslash characters ("\\\\"). Likewise, a literal dollar sign in a quoted argument in a desktop entry file is unambiguously represented with ("\\$").

I think it's basically shell quoting rules, except I had to escape the backslash in convert-to-pnm.contract, below.

Debugging

Just wrap everything in sh -c 'echo …', which, for anything sufficiently complex, you're already wrapping it in sh -c anyway, probably. Escape any semicolons or redirections by prefixing them with doubled-up backslashes, e.g.

Exec=sh -c'some command \\> outfile \\; notify-send …'`

…so that they print instead of doing the actual thing. It appears that both stdout and stderr go to the syslog (journald), so there's no need to add >&2 here.

References

# for use with elementary OS's Contractor (context menu for Files)
# save in ~/.local/share/contractor
[Contractor Entry]
Name=Convert to PNM
Icon=image-x-generic
Description=Convert PNGs to PPMs
MimeType=image/png;
TryExec=pngtopnm
# from the spec
# > a literal dollar sign in a quoted argument in a desktop entry file is
# > unambiguously represented with ("\\$").
# but haha, you don't need to do that here because… who knows?!
Exec=sh -c 'parallel pngtopnm {} \\> {.}.pnm ::: "$@"' parallel %F
# vim: ft=ini
[Contractor Entry]
Name=Notify
Icon=dialog-information
Description=Create a notify-send notification with the current filename(s)
MimeType=!nothing;
# %i is expanded as '--icon <Icon>'!
TryExec=notify-send
Exec=notify-send %i -u low "Message from Files" "%F"
# vim: ft=ini
[Contractor Entry]
Name=Open Terminal Here
Icon=terminal
Description=Open Terminal at the current directory
MimeType=inode;
# see https://github.com/elementary/terminal/issues/498
Exec=io.elementary.terminal -w %u
Gettext-Domain=io.elementary.terminal
# vim: ft=ini
[Contractor Entry]
Name=Copy Recognized Text to Clipboard
Icon=scanner
Description=Recognize text in an image using Tesseract and copy it to the clipboard
MimeType=image
TryExec=tesseract
TryExec=notify-send
TryExec=xclip
Exec=sh -c 'tesseract "$1" - text | xclip -sel clip -i; notify-send %i "OCR complete" "Result copied to clipboard."' tesseract %f
# vim: ft=ini
[Contractor Entry]
Name=Convert to Searchable PDF
Icon=scanner
Description=Recognize text in an image using Tesseract, write to a searchable PDF
MimeType=image
TryExec=parallel
TryExec=tesseract
TryExec=notify-send
Exec=sh -c 'parallel tesseract {} {.} pdf ::: "$@"; notify-send %i "PDF conversion complete" "$# items converted."' parallel %F
# vim: ft=ini
[Contractor Entry]
Name=Recognize Text
Icon=scanner
Description=Recognize text in an image using Tesseract
MimeType=image
TryExec=parallel
TryExec=tesseract
TryExec=notify-send
TryExec=xclip
Exec=sh -c 'parallel tesseract {} {.} text ::: "$@"; xclip -sel clip -i < "${1%%.*}".txt; notify-send %i "OCR complete" "First result (alphabetically) copied to clipboard."' parallel %F
# vim: ft=ini
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment