Defining custom handlers for use with org-protocol

Table of Contents

{Back to Worg's tutorial index}

org-protocol intercepts calls from emacsclient to trigger custom actions without external dependencies. Please refer to this file for the basic setup required.

You might want to watch the screencast on youTube.

Defining custom handlers

org-protocol scanns the list of filenames passed to the emacs-server for "org-protocol:/sub-protocol:/" and triggers actions assossiated with sub-protocol through the custom variable org-protocol-protocol-alist.

To defun a custom org-protocol handler basically means to define two basic elements:

  1. a sub-protocol that triggers the action
  2. a function that consumes the data (i.e. the part of an URL that follows "org-protocol://sub-protocol://")

To install the custom handler's protocol, we add an entry to org-protocol-protocol-alist:

(add-to-list 'org-protocol-protocol-alist
             '("Hello World"
               :protocol "hello-world"
               :function my-hello-world))

The :protocol property is the sub-protocol, that triggers the action. Note, that names of protcols (or URL schemas) are only allowed to consist of a restricted set of characters. See rfc1738, section 2.1.

The :function is an arbitrary function that takes exactly one argument: the string that follows our protocol, found in a filename passed to emacs through emacsclient. All the three standard handlers split and decode that string using a helper function in org-protocol.el:

org-protocol-split-data (data &optional unhexify separator)

You may use different separators for your custom handlers and pass them to org-protocol-split-data.

Here is a simple definition:

(defun hello-world (data)
  "Say hello to the world."
  (message data)
nil)

Now the URL org-protocol://hello-world://encoded-data will call our fuction with the string "encoded-data". Hence an

emacsclient org-protocol://hello-world://encoded-data

will put "encoded-data" into the minibuffer.

Killing the client

If your handler uses interactive functions that could be canceld by the user by typing 'C-g', consider to supply the ':kill-client' property when you define the protocol.

This is what we did for the capture handler:

(defconst org-protocol-protocol-alist-default
  '(("org-capture" :protocol "capture"
     :function org-protocol-capture
     :kill-client t)
    ;; ...
    ))

Otherwise, if the user has an interactive property defined in her capture template, discarding it through 'C-g' would leed to emacsclient waiting for ever, thus to the appropriate questions when exiting emacs.

All filenames passing from emacsclient to the emacs will be ignored if you set :kill-client to a non-nil value.

Return values

Note, that our hello-world handler explicitly returns nil. This tells org-protocol to remove the filename from the list of files passed to the emacs-server. If more than one filename was supplied, all those filenames are searched for protocols. Only filenames without protocolls are passed to the emacs-server as usual.

Another possible return value is a string. If the string is a valid filename, and if that file can be read, org-protocol replaces the original filename with the one returned from the handler.

Using more than one value

Passing one argument to our custom handler is nice, but sometimes more parameters are needed. We would have to encode the the data and split it into parts using a separator.

This is where org-protocol-split-data comes into play. It takes a string as its first argument, an optional parameter to tell if the string should be considered URL-encoded UTF-8 text and finally an optional separator. By default, no URL-encoding is assumed and '/' is used as the separator.

The return value is a list of strings. If a non-nil value is supplied as the second argument, each elements of the returned list will be URL-decoded using org-protocol-unhex-string. If the second argument is a function, that function is used to decode each element of the list. The function should take a string as its only parameter, and return the decoded value 1.

This is a rewrite of our handler:

(defun hello-world (data)
  "Say hello to the world."
  (let* ((parts (org-protocol-split-data data nil '::my-separator::'))
         (one (car parts))
         (two (cadr parts))
         (three (caddr parts)))
    ;; ... do something with one, two and three
    )
  nil)

Using more than one value the greedy way

Finally, it is possible to define a greedy handler. Basically it will discard all the filenames from the servers list of files that follow the filename that triggered the handler.

A handler is greedy, if you add the :greedy property to org-protocol-protocol-alist, regardless of its return value:

(add-to-list 'org-protocol-protocol-alist
             '("Greedy"
               :protocol "greedy"
               :function my-greedy-handler
               :greedy t))

The one argument to greedy handlers is the rest of the list of filenames, the one that triggered the handler included. But read on, please.

The list of filenames

Here I have to admit, that I was lying all the time. emacsclient does not pass a list of filenames to the emacs-server. It's a list of lists. And the list is the list of emacsclient's arguments reversed.

As an example, the following commandline:

emacsclient org-protocol:/greedy:/one two three +15:42

is passed as

((/dir/three (15 . 42)) (/dir/two) (/dir/org-protocol:/greedy:/one))

to the emacs-server, where org-protocol grabs it and reverses it to make it look like this:

((/dir/org-protocol:/greedy:/one) (/dir/two) (/dir/three  (15 . 42)))

This is now, what our greedy handler will receive as its only parameter.

The "/dir/" prefix is added by emacsclient. It's the absolute path to its working directory.

You may set org-protocol-reverse-list-of-files to nil to inhibit the reversion. But that leads to unexpected results. In this example, the only filename left would be the one that triggered the actions. That seems not very greedy, and reversing the arguments on the commandline seems unnatural. Note though, that the sequence is not changed for the server.

Flatten the list of arguments

org-protocol.el provides a function to flatten the list of arguments for greedy handlers:

org-protocol-flatten-greedy (param-list &optional strip-path replacement)

This function takes the list of lists your greedy handler gets as its only parameter, and turns it into a flat list. Also, all prefixes and protocols are stripped from the element that triggered your handler.

This is, what the first parameter might look like:

(("/dir/org-protocol:/greedy:/one") ("/dir/two") ("/dir/three" (15 . 42)))

If only the first parameter is supplied, org-protocol-flatten-greedy will return this list:

("/dir/one" "/dir/two" "/dir/three" 15 42)

If you supply a non-nil value as the second parameter for the function:

("one" "two" "three" 15 42)

And, last not least, if you supply a replacement "REPL-" (must be a string):

("REPL-one" "REPL-two" "REPL-three" 15 42)

Note, that this works exactly this way regardless of your setting of "org-protocol-reverse-list-of-files". The sequence of the returned list will always reflect the sequence of arguments on the command line.

General remarks

emacsclient compresses double and tripple slashes to one. That's why it doesn't really matter how many slashes succeed the scheme part of the URL, also known as protocol.

This behaviour is the main reasons, why the slash was choosen as the default separator for data fields. Keeping the slashes is insecure, since some of the data fields could contain double or tripple slashes themselves.

Footnotes:

1

The function feature was added with the Org-mode 6.26 release (commit 6a9acfa9a3ec4ad889951d02c9809f55ac7491fb).

Documentation from the http://orgmode.org/worg/ website (either in its HTML format or in its Org format) is licensed under the GNU Free Documentation License version 1.3 or later. The code examples and css stylesheets are licensed under the GNU General Public License v3 or later.