"Send to .zip" Right-Click in Thunar (and More XFCE Custom Actions)

A small XFCE customization, start to finish

I wanted what every other file manager seems to ship with: select a few files, right-click, Send to .zip, done. Thunar — XFCE’s file manager — doesn’t have it out of the box, but it has something better: a clean, scriptable extension point called Custom Actions. Here’s how I wired it up on this machine, and how you can do the same by hand.


The idea

A Thunar Custom Action is just a menu entry that runs a shell command, with the current selection passed in as arguments. Thunar substitutes a few tokens for you:

Token Meaning
%f first selected item, absolute path
%F all selected items, absolute paths
%n first selected item, basename only
%N all selected items, basenames only
%d directory containing the selection

You can cram the whole zip command into that one field. I didn’t — inline shell quoting inside an XML attribute is a quoting nightmare and impossible to tweak later. Instead I put the logic in a tiny script and pointed the action at it. The action becomes a one-liner; all the real work lives somewhere editable.


What I actually did

This machine already had everything it needs:

thunar 4.20.7 (Xfce 4.20)
/usr/bin/zip
/usr/bin/7z
~/.local/bin   (exists and is on $PATH)

1. The helper script

I dropped this at ~/.local/bin/thunar-zip and made it executable (chmod +x):

#!/usr/bin/env bash
# thunar-zip — "Send to .zip" helper for Thunar custom actions.
# Called as: thunar-zip FILE [FILE...]   (absolute paths, i.e. Thunar's %F)
# Creates an archive next to the selection. If one item is selected the
# archive is named after it; if several are selected it is named after the
# parent folder. Zips basenames only so the archive has no leading path junk.
set -euo pipefail

[ "$#" -ge 1 ] || { echo "thunar-zip: no files given" >&2; exit 1; }

first=$1
dir=$(dirname -- "$first")
cd -- "$dir"

if [ "$#" -eq 1 ]; then
    base=$(basename -- "$first")
    out="${base%.*}.zip"
    [ "$out" != ".zip" ] || out="$base.zip"   # dotfiles / no extension
else
    out="$(basename -- "$dir").zip"
fi

# Don't clobber: append -1, -2, ... if the target already exists.
if [ -e "$out" ]; then
    stem=${out%.zip}; n=1
    while [ -e "${stem}-${n}.zip" ]; do n=$((n + 1)); done
    out="${stem}-${n}.zip"
fi

# Feed basenames via stdin (-@) so names with spaces are handled safely.
for f in "$@"; do basename -- "$f"; done | zip -r "$out" -@

Three decisions worth calling out:

  • Basenames, not full paths. If you just run zip archive.zip %F, the archive ends up with the entire absolute path baked in (home/user/Documents/...). Feeding basenames via zip -@ (read names from stdin) keeps the archive flat and clean, and stdin-with-one-name-per-line survives spaces in filenames where a plain argument list might not.
  • Smart naming. One file → report.pdf becomes report.zip. Several files → archive named after the folder they live in.
  • No clobbering. Run it twice and you get report.zip, then report-1.zip, never an overwrite. set -euo pipefail means it bails loudly instead of producing a half-broken archive.

2. The Thunar action

Custom actions live in ~/.config/Thunar/uca.xml. I appended one <action> block (leaving the existing entries untouched), just before the closing </actions>:

<action>
	<icon>package-x-generic</icon>
	<name>Send to .zip</name>
	<submenu></submenu>
	<unique-id>1782000000000000-1</unique-id>
	<command>thunar-zip %F</command>
	<description>Compress the selected file(s) into a .zip archive</description>
	<range>*</range>
	<patterns>*</patterns>
	<directories/>
	<audio-files/>
	<image-files/>
	<other-files/>
	<text-files/>
	<video-files/>
</action>

The <...-files/> and <directories/> tags are the Appearance Conditions — they decide which kinds of selections show the menu entry. All of them listed = it shows for everything, including folders. %F passes the whole selection to the script.

3. Verify

I smoke-tested the script directly before trusting it:

$ thunar-zip /tmp/a.txt "/tmp/b file.txt"
  adding: a.txt (stored 0%)
  adding: b file.txt (stored 0%)

The archive contained a.txt and b file.txt — flat, spaces intact, no path junk. A second run on the same target correctly produced ...-1.zip.


How to do this yourself (the GUI way)

If you’d rather not touch XML, Thunar has a full editor for this:

  1. Open Thunar → Edit → Configure custom actions…
  2. Click the + (Add) button.
  3. Basic tab:
    • Name: Send to .zip
    • Description: Compress the selected file(s) into a .zip archive
    • Command: thunar-zip %F (or, if you skip the helper script, an inline version — see below)
    • Icon: pick something like package-x-generic.
  4. Appearance Conditions tab:
    • File Pattern: *
    • Tick every “Appears if selection contains” box (directories, audio, image, text, video, other files) so it shows for any selection.
  5. OK. The entry appears immediately — no restart, no logout.

If you don’t want a separate script, you can put the logic straight in the Command field. A serviceable inline version:

sh -c 'cd "$(dirname "$1")"; for f in "$@"; do basename -- "$f"; done | zip -r "archive.zip" -@' _ %F

…but that always names the archive archive.zip and the quoting is fiddly, which is exactly why I preferred the script.


How to do this yourself (the file-editing way)

  1. Save the script to ~/.local/bin/thunar-zip and run chmod +x ~/.local/bin/thunar-zip. (Make sure ~/.local/bin is on your $PATHecho $PATH to check.)
  2. Open ~/.config/Thunar/uca.xml in a text editor.
  3. Paste the <action>…</action> block from above just before the final </actions> line. Give it a <unique-id> that isn’t already used.
  4. Save. If Thunar is already running, the menu picks it up the next time you right-click; if not, thunar -q then reopen forces a reload.

The full menu

One action is lonely, so I built out a small toolbox. The archive actions group under a Compress submenu and the image ones under Images (set the <submenu> field — same value on several actions nests them together); the rest sit at the top level. Every one follows the same pattern: a tiny script in ~/.local/bin, an <action> in uca.xml pointing at it.

Menu entry Script What it does
Compress → Send to .zip thunar-zip zip archive, basenames, smart-named, no clobber
Compress → Send to .7z thunar-7z same, but .7z (better compression)
Compress → Extract here thunar-extract extracts each archive into its own subfolder (tarbomb-safe)
Images → Resize to 1080p (copy) thunar-img-resize non-destructive copy shrunk to fit 1920×1080
Images → Strip metadata (copy) thunar-img-strip non-destructive copy with EXIF/GPS removed
Images → Set as wallpaper thunar-setwallpaper sets the XFCE desktop backdrop
Copy path to clipboard thunar-copypath absolute path(s) → clipboard (needs xclip)
SHA-256 checksum thunar-sha256 writes CHECKSUMS.sha256 beside the selection
Open as root pkexec thunar %f root file-manager window (inline, no script)

A few design choices that recur across these:

  • Non-destructive by default. The image actions never edit your originals — they write name-1080p.png / name-stripped.png copies. A right-click that silently mangles your only copy of a photo is a bad right-click.
  • Tarbomb-safe extraction. thunar-extract makes a folder per archive and unpacks into it, so a badly-packed archive can’t litter your Downloads folder with 400 loose files.
  • %f vs %F. Set as wallpaper uses %f (single file) and <range>1</range> so it only appears for one selected image. Everything else uses %F to act on the whole selection.
  • Graceful dependencies. Copy path needs xclip (or xsel), which isn’t installed here yet — the script prints sudo apt install xclip instead of failing silently.

The new helper scripts

~/.local/bin/thunar-7z — identical in spirit to thunar-zip; it passes basenames as a shell array to 7z a so spaces survive and no path junk leaks in.

~/.local/bin/thunar-extract

#!/usr/bin/env bash
set -euo pipefail
[ "$#" -ge 1 ] || { echo "thunar-extract: no files given" >&2; exit 1; }
for arc in "$@"; do
    [ -f "$arc" ] || { echo "skip (not a file): $arc" >&2; continue; }
    dir=$(dirname -- "$arc"); name=$(basename -- "$arc")
    case "$name" in
        *.tar.gz|*.tgz)         stem=${name%.tar.gz}; stem=${stem%.tgz} ;;
        *.tar.bz2|*.tbz2|*.tbz) stem=${name%.tar.bz2}; stem=${stem%.tbz2}; stem=${stem%.tbz} ;;
        *.tar.xz|*.txz)         stem=${name%.tar.xz}; stem=${stem%.txz} ;;
        *.tar.zst|*.tzst)       stem=${name%.tar.zst}; stem=${stem%.tzst} ;;
        *.tar)                  stem=${name%.tar} ;;
        *)                      stem=${name%.*} ;;
    esac
    dest="$dir/$stem"
    if [ -e "$dest" ]; then n=1; while [ -e "${dest}-${n}" ]; do n=$((n+1)); done; dest="${dest}-${n}"; fi
    mkdir -p -- "$dest"
    case "$name" in
        *.tar|*.tar.*|*.tgz|*.tbz2|*.tbz|*.txz|*.tzst) tar -xf "$arc" -C "$dest" ;;
        *)                                             7z x -y -o"$dest" -- "$arc" >/dev/null ;;
    esac
    echo "Extracted -> $dest"
done

~/.local/bin/thunar-img-resizeconvert "$f" -resize '1920x1080>' "<name>-1080p.<ext>". The trailing > is the magic bit: ImageMagick only shrinks, so a small image is left alone instead of being blown up and blurred.

~/.local/bin/thunar-img-stripconvert "$f" -strip "<name>-stripped.<ext>". Handy before posting a photo publicly, since phone JPEGs carry GPS coordinates.

~/.local/bin/thunar-setwallpaper — this version of xfdesktop has no --set-wallpaper flag, so the script walks every backdrop property with xfconf-query and reloads:

xfconf-query -c xfce4-desktop -l | grep '/last-image$' | while read -r prop; do
    xfconf-query -c xfce4-desktop -p "$prop" -s "$img"
done
xfdesktop --reload

~/.local/bin/thunar-sha256 — writes CHECKSUMS.sha256 (basenames, so the file verifies with a plain sha256sum -c CHECKSUMS.sha256).

~/.local/bin/thunar-copypath — tries xclip, then xsel, then tells you what to install.

All of them start with set -euo pipefail (fail loud, never produce a half-finished result) and were smoke-tested from the command line before being trusted in the menu.

Tools you’ll need

On this machine these were already present: zip, 7z, tar, sha256sum, ImageMagick (convert/mogrify), xfconf-query, xfdesktop, pkexec. The only gap was the clipboard helper:

sudo apt install xclip

Going further

The whole pattern — menu entry → small script on your PATH — generalizes to anything: upload to a server, open in a specific editor, run a linter, convert audio. Thunar just hands your script the selection and gets out of the way. Some natural next additions:

  • More archive formats — clone thunar-zip for .tar.gz or .tar.zst.
  • Checksum verify — an action on *.sha256 running sha256sum -c %f.
  • Run in a terminal — for long or interactive jobs, launch via xfce4-terminal --hold -x your-command %F so the output window stays open.

To edit any of this later: the scripts live in ~/.local/bin/thunar-* and the menu definitions in ~/.config/Thunar/uca.xml (or use Edit → Configure custom actions… in Thunar). Changes take effect on the next right-click — no restart required.