Clipetty is a minor mode for terminal (TTY) users that sends text that you kill in Emacs to your Operating System's clipboard. If you predominately use Emacs in GUI (X-Windows, macOS, Windows) frames you don't need Clipetty.
For this to work you need to be using a terminal emulator that supports OSC 52 escape sequences. See the Terminals section below to check if your favorite terminal emulator is on the list.
Here are some reasons why you might want to use Clipetty instead of the native functionality found in Emacs' xterm.el:
- Clipetty supports both GNU screen and Tmux, either locally or over SSH
- Clipetty supports nested GNU Screen or Tmux sessions
- Clipetty allows for seamless detach/re-attach with Tmux on remote hosts
- Clipetty is compatible with the Kitty terminal emulator
The recommended way to get Clipetty is as a package from the MELPA repository, which tracks the master branch of this repository and will almost always be up-to-date. You can also get Clipetty from MELPA Stable, which tracks the most recent stable release.
If you want Clipetty to work everywhere, you can enable global-clipetty-mode
,
otherwise you can enable clipetty-mode
on a buffer-by-buffer basis.
If you're using use-package
you can add this to your init.el
file:
(use-package clipetty
:ensure t
:hook (after-init . global-clipetty-mode))
You can manually install Clipetty from MELPA with M-x package-install RET clipetty RET
. Afterwards you can add the following to your init.el
file:
(require 'clipetty)
(global-clipetty-mode)
Sometimes it can be annoying to have Clipetty overwrite your system clipboard
every time you kill something. Let's say you want to paste something from your
system clipboard into Emacs, but before you can paste it, you need to do some
minor editing (like hitting C-k
or M-d
) which kills some text. Now you've
overwritten your system clipboard (which doesn't have a kill ring) and you have
to copy it all over again. Doh!
You can invoke Clipetty explicitly from a key binding to copy a region to
the clipboard rather than using either the local or global minor modes. To that
end, Clipetty has a function called clipetty-kill-ring-save
which I like to bind
to M-w
like so:
(use-package clipetty
:ensure t
:bind ("M-w" . clipetty-kill-ring-save))
Clipetty does its magic by hooking Emacs' interprogram-cut-function
, which is
what happens when you activate clipetty-mode
. When the mode is active, every
time you kill a line or region Clipetty gets sent the content that is destined
for the kill ring. The clipetty-cut
function takes this content, converts it to
base64, wraps it in an ANSI OSC 52 escape sequence, and then sends it to your
terminal. Terminal programs which support OSC 52 commands will react to this by
stripping off the escape sequence, decoding the base64 content, and then
inserting the resulting string into the system clipboard.
- xterm (Unix – Where OSC 52 escape sequences originated)
- iTerm2 (macOS)
- Alacritty (macOS, Linux, BSD, Windows)
- kitty (macOS, Linux)
- mintty (Windows)
- hterm (Javascript)
- st (Unix)
- mlterm (Unix, Windows, macOS)
This is not an exhaustive list, these are just the ones I know about. Submit a PR if you know of any I missed.
Alacritty versions prior to 0.4.2 suffer from an issue that will cause any attempted copy of more than 765 bytes to fail to be inserted into the OS clipboard.
The kitty
terminal gets honorable mention for extending the xterm
protocol to
support larger clipboards. While Clipetty at this time does not support Kitty's
larger clipboard, it is compatible, which means you don't have to disable
Kitty's protocol extension with no-append
.
If you're running Emacs under a terminal multiplexer like tmux
or screen
,
these programs will intercept these ANSI OSC 52 escape sequences, and if they
don't think your terminal supports OSC 52 (i.e. you don't have a very specific
terminfo(5)
capability) they'll not pass them on to your outer terminal. With
enough tweaking you can prevent them from doing this, but it can be a
challenge. Running Emacs on a remote host with nested terminal multiplexers
(something I often do) can further complicate things.
Clipetty attempts to deal with this problem by looking for environment variables
that indicate you're using a terminal multiplexer, and then wrapping the OSC 52
escape sequence in a "Device Control String" (DCS). This presence of a DCS tells
tmux
or screen
to unwrap the message and send it along unmolested, where it
can be interpreted by the outer terminal. Clipetty handles the case of nested
terminal multiplexers by writing the DCS wrapped OSC 52 escape sequence directly
to your $SSH_TTY
thereby bypassing the terminal multiplexer on the remote host
entirely.
Let's say you SSH into a host, start tmux
, and then run Emacs. A little later
you detach your session and log out. You then SSH back into the same host, and
re-attach your session. Your Emacs process is still running right where you left
it, but the $SSH_TTY
environment variable it inherited from the shell is now
stale (or longer accurate) as it still points to your old SSH tty. This means
that Clipetty will no longer function in tmux
windows that were created during
your previous login until you manually update the $SSH_TTY
environment
variable.
Thankfully in tmux
there is an easy way of dealing with this problem! Add the
following to your .tmux.conf
file:
set -ag update-environment "SSH_TTY"
This will tell tmux
to update its local $SSH_TTY
environment variable when
you re-attach, and Clipetty will ask tmux
about it rather than relying on the
(possibly stale) variable that Emacs inherited from the shell.
You can run M-x customize-group RET clipetty RET
to use Emacs' Easy
Customization Interface or you can manually set some of the variables below in your
init.el
:
The clipetty-assume-nested-mux
variable, when set to a non-nill value, tells
Clipetty to assume that if you're running a terminal mulitplexer on a remote
host that it's nested – that is to say that you're also running the same
terminal multiplexer on the local host.
(setq clipetty-assume-nested-mux nil)
The clipetty-tmux-ssh-tty
variable tells Clipetty how to run tmux
to query it's
local SSH_TTY
environment variable. This default assumes that tmux
is on your
PATH. If tmux
lives elsewhere for you, or it is named something else, you can
change it here.
(setq clipetty-tmux-ssh-tty "tmux show-environment SSH_TTY")
This code was inspired by osc52.el
(part of the Chromium OS) which was very
helpful in showing me how this could be done, but lacked support for tmux
and
didn't have support for nested terminal multiplexers. I'd also like to thank
Suraj N. Kurapati, as I learned a lot by studying his shell script yank
. Thanks
to everyone on the Freenode #emacs IRC channel for helping me out, especially
bpalmer
who graciously reviewed my code and offered great suggestions.