Skip to content

Instantly share code, notes, and snippets.

@progfolio
Last active April 3, 2024 16:02
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save progfolio/af627354f87542879de3ddc30a31adc1 to your computer and use it in GitHub Desktop.
Save progfolio/af627354f87542879de3ddc30a31adc1 to your computer and use it in GitHub Desktop.
Emacs as an Org capture server

Emacs as an Org capture server

The Concept

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.

Running a server daemon

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"

Running a client

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.

Elisp setup

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.

my/delete-capture-frame

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)

my/org-capture-frame

This function is a modernized version of the info found here:

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.

@vic
Copy link

vic commented Jul 20, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment