Xfce Custom Actions
I'm not one for graphical interfaces, never mind complex ones like Xfce. But I have to admit Xfce did make me fall in love with the idea of custom actions.
1 Custom Actions?
Xfce's file manager, Thunar, offers the concept of custom actions, command lines which can be executed from a graphical, contextual menu when you right-click on files. Other desktop environment such as GNOME with Nautilus are capable of similar things.
I'm not normally using custom actions myself, but I found them terrific for quickly cooking up tailored operations for users who wouldn't usually resort to command-line interfaces, without me having to write a full-fledged application e.g. with Qt. Here's how Thunar does it.
2 Defining and Using Custom Actions
Thunar can be used seamlessly from any window manager and running the thunar
command will open a window showing your files. The documentation explains that, in order to define a custom action, one follows the Edit menu → Configure custom actions... which opens a dialogue from which they can be added, removed or edited.
A custom action is defined by its name, description, an icon, a command line which accepts what a shell also would – pipes, redirections, control operators, environment variable assignments, command substitution, tilde expansion, the lot. All this, in itself, already makes custom actions very useful to quickly make complex commands available graphically, as would launchers in a panel. What makes custom actions tremendous is that they're designed to act on files selected in the file manager.
A number of command parameters can be used anywhere in the command line to refer to files in various ways. For instance, the %f
parameter resolves to the path of the first selected file, while %F
is the path of each selected file, all given in the same command line to the command. Thunar ensures that files with annoying characters such as spaces are correctly quoted for the command to make out the right arguments. What's also interesting, is that a command line with %f
will only be available as a custom action in the contextual menu if you select a single file. The %d
and %D
parameters work much the same way, except that they will have the directory name (as in dirname()
) be passed to the command. Likewise, %n
and %N
will have the base name (as in basename()
) be passed to the command.
Thunar custom actions go beyond counting how many files are selected when choosing whether or not to display their entry in the contextual menu: they also let you specify a file pattern with the usual globbing syntax we all know and love. More conveniently still, you can have files match by type in their broad sense, such that you can target by and large target image files, rather than having to tediously devise a pattern involving *.jpg
*.png
*.gif
*.cr2
*.nef
*.JPG
*.PNG
*.GIF
*.CR2
*.NEF
. Other types Thunar knows about include sounds, texts, videos and directories. In fact, even right-clicking in the empty space of an icon window will target a directory. They thought of everything.
3 Examples
The web swarms with ready-to-use examples of custom actions. Once I understood their potential, I took pleasure in writing these ones, literally on a whim, as I felt the need for them:
- If you have a bunch of PDFs which you wish to print duplex without leaving any blank page, a straightforward custom action is to call
pdfjoin %F
to join them into a single document. The matching pattern should be*.pdf
, Other Files. - Listing files in a directory with a graphical file manager is a bit of a faff, far from the comfort of a shell which lets you do anything from the output of an
ls
command. With custom actions, just runls | gvim -
, something you want to make possible for directories only. - During Tango events, cortinas are 30-seconds pieces of unrelated music marking a break between sets of pieces. I wrote a custom action which, given selected music files, runs
ffmpeg -r 30
on each of them. This involves looping through the arguments, and as this wasn't exactly a one-liner any longer, I chose to write a small Python script to iterate oversys.argv
. A neat example of how you can implement a custom action in any language you fancy. I set it all up to appear in the contextual menu only if audio files are selected, passed to my script with%F
.
4 Debugging
I said earlier on that the point in custom actions was that I didn't have to write a full-fledged graphical application with, say, Qt. I'm not going there either even if I want to display stderr in dialogues. Thankfully, there's zenity, which is a command which lets you display fairly sophisticated GTK+ dialogues. See it as xmessage on steroids.
For development purposes however, simply printing messages to stdout or stderr does the business of displaying them to the terminal where you started Thunar from.
5 Nautilus Has Python Extensions
Armed with my carefully-set up custom actions, and well on my way to settle down with Thunar, I was heartbroken when I learned it can't undo operations, such as file deletions, moves, and the like. But Nautilus can. And so I wondered what the equivalent of custom actions in Nautilus are like. It's not as glorious as with Thunar, in fairness. You can't have icons – not that I normally care about such things, but since we're in that futile, graphical world, I thought I might as well. Targeting files by pattern and types isn't as seamless. And right-clicking in the empty space of an icon window won't let you target a directory. What's more, the whole situation in the GNOME world is much less clear, as they seem to be in the middle of a transition period towards thinking how they're going to implement their approach of custom actions.
At the time of writing, the way to go is to install the python-nautilus
package which lets you add Python extensions to the contextual menu. Note that it comes with a README file which states that these bindings are still regarded as unstable. The package helpfully comes with examples in /usr/share/doc/python-nautilus/examples
which are enough to get you started. And in practice, it does the business, and lets you get there fairly easily. In its simplest form, an extension looks like:
from gi.repository import Nautilus, GObject
class MakeCortinas(GObject.GObject, Nautilus.MenuProvider):
def get_file_items(self, _, files):
for fle in files:
if fle.get_mime_type().split('/')[0] != 'audio':
return
item = Nautilus.MenuItem(name='Nautilus::makecortinas',
label='Make Cortinas')
item.connect('activate', makecortinas, files)
return item,
I'll spare you the boring details of the makecortinas()
function. In essence, there's a class and its get_file_items()
function to implement. This function ...
- ... can first prematurely return if the files aren't what they should be. Even though you can't tick a checkbox labelled e.g. Audio Files, each file object that Nautilus provides you with has a
get_mime_type()
method and you can easily match e.g. audio files by looking at what comes before the slash in the mime type. - ... creates a
Nautilus.MenuItem()
with a uniquename
and thelabel
which will become visible in the contextual menu. - ... runs the item's
connect()
function to connect it to a Python function that does all the work. - ... returns the item.
Annoyingly, you need to restart Nautilus for any change you made to take place, something that isn't necessary with Thunar's custom actions. While Thunar doesn't particularly advertise where in the filesystem custom actions are kept since it's all graphical, the Nautilus extension documentation makes it immediately clear that it's up to you to copy them to ~/.local/share/nautilus-python/extensions
or /usr/share/nautilus-python/extensions
, which is an intention I appreciate. In the end, it took me little effort to rewrite my Thunar custom actions into Nautilus extensions. Let's just hope they still work with the next version.
6 References
- Custom Actions
- Zenity
xmessage(1)
- How do I customize the context menu in Nautilus? shows a discussion where solutions all seem to become outdated as Nautilus evolves.
- printrectoverso, printlabels, printfilelist and makecortinas are examples of Nautilus extensions I wrote.