Emacs, pinentry, and notifications
Posted on
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.