The general idea is to run an Emacs server as a daemon which clients can quickly connect to via a bash script. The client executes org-capture and the frame closes upon finalizing or aborting the capture.
The first step is to get an Emacs daemon running as a server with your name of choice. For this example, I’m going to use “capture” as the server name.
From your terminal start the server daemon and give it a name
emacs --daemon="capture"
You can connect to this server with a client by launching Emacs from the terminal with the following arguments:
emacsclient --create-frame \
--socket-name 'capture' \
--alternate-editor='' \
--no-wait
We’ll make a script out of this and add more args after we set up things on the Elisp side.
In your init.el file (or in a separate package if you like) define the functions that make this server’s clients run org-capture and close.
This function deletes the frame after you finalize a capture:
(defun my/delete-capture-frame (&rest _)
"Delete frame with its name frame-parameter set to \"capture\"."
(if (equal "capture" (frame-parameter nil 'name))
(delete-frame)))
(advice-add 'org-capture-finalize :after #'my/delete-capture-frame)
This function is a modernized version of the info found here:
- http://cestlaz.github.io/posts/using-emacs-24-capture-2/
- https://stackoverflow.com/questions/23517372/hook-or-advice-when-aborting-org-capture-before-template-selection
This function runs when creating a new client. It selects the correct frame and forces org-capture to show the template selection menu in the main window instead of splitting it. It also closes the frame if you abort the capture process.
(defun my/org-capture-frame ()
"Run org-capture in its own frame."
(interactive)
(require 'cl-lib)
(select-frame-by-name "capture")
(delete-other-windows)
(cl-letf (((symbol-function 'switch-to-buffer-other-window) #'switch-to-buffer))
(condition-case err
(org-capture)
;; "q" signals (error "Abort") in `org-capture'
;; delete the newly created frame in this scenario.
(user-error (when (string= (cadr err) "Abort")
(delete-frame))))))
Now you can flesh out the emacsclient command from earlier to run your dedicated org-capture server:
#!/bin/bash emacsclient --create-frame \ --socket-name 'capture' \ --alternate-editor='' \ --frame-parameters='(quote (name . "capture"))' \ --no-wait \ --eval "(my/org-capture-frame)"
You can bind this script to a global keybinding and you should be able to capture from ‘outside’ Emacs via that binding.
Nice! I did an small shell script SPC to send keyboard macros to my emacs precisely for org-capturing, but I was missing this auto-close-frame bit. adding the after advice to
org-capture-finalize
. Thanks for sharing, will add something like this on my env.