Emacs, pinentry, and notifications

Posted on

emacs · pinentry · gpg · gentoo

In which I figure out how advice-add works so I get desktop notifications when gpg triggers pinentry to prompt for a passphrase.

I have been expanding my Emacs usage, reading manuals and tinkering with my configuration, and recently managed to address a small annoyance with Emacs as pinentry dialog. When GPG needs to prompt for a passphrase, I have Emacs set up as the pinentry dialog https://github.com/ueno/pinentry-el, https://packages.gentoo.org/packages/app-emacs/pinentry where a little buffer pops up for me to enter my passphrase. This works great when I am already working in Emacs, but there’s an issue when I run a command in a terminal outside of Emacs and GPG needs to call pinentry: Examples of commands I sometimes in a terminal are ssh to connect someplace as I have GPG configured to provide an SSH key, or git commit --gpg-sign. a prompt does appear in Emacs, but there’s no indication in the terminal that something is happening elsewhere. In the moment, it can be difficult to ascertain whether the command is still running or paused waiting for the pinentry prompt to be addressed. It happened frequently that a command failed because I missed the pinentry prompt and it timed out, and that has been bothering me.

What I wanted was some indication that Emacs is waiting for a passphrase entry. At first, I looked for options or hooks in GPG to print a little notice when pinentry is called but I have yet to find something like that. Then I discovered (notifications-notify) https://www.gnu.org/software/emacs/manual/html_node/elisp/Desktop-Notifications.html that can be used to generate a desktop notification and started looking at pinentry.el to see if there was a place to hook in and generate notifications. You may already be thinking “but pinentry-mode implicitly defines a hook, just use that!” I didn’t know that at the time, but I’ll get there! I didn’t find any explicitly defined hooks for this purpose though. I briefly considered patching the pinentry.el sources and simply inject the notification code. Gentoo has a great feature usually referred to as user patches https://wiki.gentoo.org/wiki//etc/portage/patches that makes patching sources quite trivial. Before I got that far though I found (advice-add) https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Functions.html that essentially allows hooking into any function and eventually came up with this:

(defun pinentry-notify-before (orig-fun &rest args)
  (notifications-notify :title "Emacs" :body "please enter passphrase"))
(advice-add 'pinentry--prompt :before #'pinentry-notify-before)

It took some trial-and-error to figure out the correct signature for pinentry-notify-before, but it’s simple and works great! I found the advice-add documentation sparse and it seems written to rely on the reader already understanding add-function— neither of which I had encountered before. pinentry--prompt is the function that opens a prompt buffer and just before it’s called we inject a call to notifications-notify and the result is a nice little desktop notification letting us know there’s a prompt that needs attention.

Since then I was chatting to the good people in #gentoo-lisp and learned that all major modes implicitly defines a hook that is called when the mode is activated. In our case, that’s pinentry-prompt-mode-hook. An arguably simpler solution can then be something like this:

(defun pinentry-notify-hook ()
  (notifications-notify :title "Emacs" :body "please enter passphrase"))
(add-hook 'pinentry-prompt-mode-hook 'pinentry-notify-hook)

Both of these solutions work and the little desktop notifications on pinentry prompts is working great for me. I am much less worried about missing a pinentry prompt and I have learned a handful of new things about Emacs.