Sane Behavior for OS Clipboards
An easily searchable clipboard manager that retains extended history, and has a semantic understanding of the data it’s copying, is a part of key workflows; and should be configured by default.
Where does it hurt?
I expect system clipboards to do one thing – store text and let me paste it later. If you’re counting along and got two things, you are off-by-one. This is one of the two hard problems in CS.
For reasons I will never understand, the default macOS
and linux
clipboards hold a max of one (1) item.
This sucks! Let’s walk through why…
Imagine you are copying two sections from File 1
to File 2
.
┌────────────────────────────────────┐ ┌─────────────────────────┐
│ File 1 │ │ File 2 │
├────────────────────────────────────┤ ├─────────────────────────┤
│ First section to copy │ ────────> │ First section to copy │
│ You don't care about this section │ │ Second section to copy │
│ Second section to copy │ └─────────────────────────┘
└────────────────────────────────────┘
You open VS Code (we’ll ignore vim
workflows for this example, since you can use registers as a clipboard manager), and split the screen vertically: File 1
on the left, File 2
on the right.
- Copy the first section from
File 1
- Drag cursor to select text (or click and use
Shift+Arrows
) Ctrl-C
- Drag cursor to select text (or click and use
- Paste it into
File 2
- Change file focus, and place cursor where you want to paste
Ctrl-V
- Copy the second section from
File 1
- Change file focus again, and select text
Ctrl-C
- Paste it into
File 2
- Change file focus yet again
Ctrl-V
You’re done – but you had to change file focus three times. That’s two more times than you should have to.
What *should* happen?
With a clipboard that retained your copy history, the workflow could be:
- Copy the first section from
File 1
- Select text
Ctrl-C
- Copy the second section from
File 1
- Select text
Ctrl-C
- Paste both sections into
File 2
- Change file focus
Ctrl-V
- Open clipboard history, select item at index 11, and paste again
Moving at the speed of thought: vim
and tmux
If you read this section, and your eyes glaze over, don’t worry! Skip to Clipboard Manager Setup and come back to vim
some other time :) You will get a bunch of value out of this post anyways.
You can perform the above task in vim
using the default settings, without ever taking your hands off the keyboard. You’ll break things build stable infra faster by learning CLI / modal-editing workflows (and obsessing over nvim
and tmux
configs).
Although, what limits me is definitely not the speed at which I can operate my computer. If only it were as straightforward to type better things as it is to type things faster :)
vim
Workflow
Here’s a vim
workflow that doesn’t require a clipboard manager (using vim
registers instead):
# Open the first file
$ vim file1.txt
# Move cursor to the first character (0) of the first line (gg) -- which is F
gg0
# Yank the line (yy) into the a register ("a)
"ayy
# Go down two lines
2j
# Yank the second line (yy) into the b register ("b)
"byy
# Open the second file in a vertical split (vs)
:vs file2.txt
# Move focus (Ctrl-w) to next window (l)
Ctrl-w l
# Paste (p) the a register ("a)
"ap
# Add a newline (o) and immediately go back to normal mode (<ESC>)
o<ESC>
# Paste (p) the b register ("b)
"bp
# Save the file (:w) and quit (:q)
:wq
Registers are awesome for working inside of vim
but aren’t exposed outside of vim
. This means we can’t use them as the clipboard manager, even though they roughly have the behavior I’m looking for. Here’s a peek at what’s currently in my vim
registers while I’m working on this article.
- The register name is on the left
- Its content is displayed in the middle
- Some of them have nice descriptions on the right
You can click on the image to make it larger
There are two special registers for the system clipboard, called selection registers. If you want to copy text from inside vim
and paste it outside of vim
, you need to copy to them explicitly ("+y
). For more reasons I will never understand, copying to the system clipboard isn’t the default copy behavior.
Tell vim
to copy to the system clipboard by default by adding the following setting in your init.lua
file:
vim.opt.clipboard = "unnamedplus"
After doing that, the workflow becomes:
# Open the first file
$ vim file1.txt
gg0
# Copy line to system clipboard
yy
2j
yy
:vs file2.txt
Ctrl-w l
# Paste from system clipboard register
p
# Open the clipboard manager -- read the next section!
Shift-Alt-Super-V
# Set the clipboard to the second-to-last item in the clipboard history
Down Arrow
# Paste from clipboard
p
:wq
Which is … pretty slick. If (when) you become a vim
addict, you’ll be able to do this without thinking about it.
tmux
To make tmux
and the system clipboard play nicely together, I use tmux-yank
in my tmux.conf
.
Clipboard Manager Setup
Now that vim
and tmux
are hooked up to the system clipboard, I need to set up the clipboard manager to keep track of the history, searching, etc. I primarily work on macOS
and Linux
machines, and have a clipboard manager workflow for each.
Clipboard Manager Requirements
- Efficient clipboard history storage and retrieval
- User-friendly and searchable clipboard history
- Media (images/videos) support in the clipboard
- Optional media preview
- Consistent keychord for the clipboard manager across all OSes
- Unique keybinding to avoid conflicts with other tools
Keychord Configuration
I set the macOS
keychord to Shift+Cmd+Option+V
and the Linux keychord to Shift+Alt+Super+V
.
You might be thinking: “Man, you literally just said you wanted to use the same keychords on every system”. I use a Razer Chroma Ornata keyboard for my Linux machine, and the built-in Apple Keyboard for my macOS machine. The keychords are identical in terms of where the keys are on the keyboards, so my muscle memory works no matter which system I’m on.
Ubuntu Linux (with GNOME)
I use greenclip
integrated with rofi
to manage my clipboard history. Note that it does not provide media previews, and can only handle small images. If that’s a critical feature set, check out Pano.
Using GNOME Keyboard Custom Shortcuts, I map rofi -modi 'clipboard:~/bin/greenclip print' -show clipboard
to the keychord I mentioned above.
macOS
I install Maccy
with brew
. It’s a performant, free and open-source, native macOS app that is intuitive to use.
-
macOS
has a reputation for Just Working™ -
The
macOS
clipboard manager does NOT Just Work™
Interesting note: Cut / Copy / Paste
was invented by Larry Tesler in the 70s. Tesler became the systems software lead for the Apple Lisa, and eventually Apple’s chief scientist in 1993.
Windows
Surprisingly, Windows
seems to be the only OS that gets this part right. The default clipboard manager has a built-in clipboard history. However, the default file manager (in 2024!!!) still reports all file sizes in KB
, so rest assured that there is plenty left to complain about.
Sane Defaults
It’s great that I can make this all work. But it’s kind of ridiculous that I need to configure it myself. I consider these features to be basic functionality that should be configured by default.
Jeff Atwood (Stack Overflow co-founder) had similar things to say about the Windows Vista introduction of typing to search for files, executables, emails, etc in the Start Menu:
… with Vista, typing to navigate is now quite literally the cornerstone of the operating system. I’ve gone from tedious, manually defined hotkeys and shortcuts in Windows XP to simply typing what I want and letting the computer find it for me. It also utterly obsoletes the Start, Run menu because it works for file paths.
There are dozens of third-party solutions that deliver very similar interactive full-text search UI experiences. But there’s one key difference between those solutions and the one in Vista: I have to install them. You may argue that, in the near term, I also have to install Vista. Fair enough. But over the next five years, millions of users will buy computers with Vista pre-installed. And they’ll immediately benefit from the built-in, default full-text search UI that’s accessible right out of the box with a single press of the Windows key.
Jeff Atwood — “The Power of Defaults”, 2007
I wish macOS and GNOME provided these clipboard manager features by default – batteries included. More people would use them, and the effort to figure out how to nicely build and integrate these features wouldn’t be duplicated by me and other engineers.
Some Security Issues with the Clipboard Manager Experience
I’d love to see clipboard managers provide first class support for sensitive strings (passwords, SSNs, SSH private keys, etc). The current “all text in the clipboard is equal” approach has a few flaws:
-
Imagine you get toast confirmations when you copy a string. If you are streaming your desktop somewhere and you copy a password from a password manager, the toast will leak your password.
-
Some clipboard managers provide a “private mode” command to stop clipboard history logging for a period of time. If you forget to turn it on, or your clipboard manager doesn’t support this, you’re forced to clear the entire clipboard history to remove sensitive strings.
greenclip
doesn’t have this feature, and also does not let you delete individual history lines easily. You can only clear the entire clipboard history. I’d add the feature, but it’s written in Haskell, and I’d rather rewrite it in … literally any other language
-
greenclip
will happily write your copied password to the clipboard manager cache file, where it can be read by any program running with your user permissions. This problem is not really unique togreenclip
. I’m just using it as an example since it’s what I use.
I’ll demonstrate this with a password copied from 1Password
.
$ ls -lah $XDG_CACHE_HOME/greenclip.history
.rw------- alichtman alichtman 6.2 KB Sun Jan 14 2024 02:57:41 PM /home/alichtman/.cache/greenclip.history
$ xxd -l 200 $XDG_CACHE_HOME/greenclip.history
00000000: 0000 0000 0000 0064 0000 0000 0000 0000 .......d........
00000010: 0000 0000 0000 0000 1531 7061 7373 776f .........1passwo
00000020: 7264 5465 7374 5061 7373 776f 7264 0000 rdTestPassword..
00000030: 0000 0000 0000 0000 0000 0000 0000 1531 ...............1
00000040: 7061 7373 776f 7264 5465 7374 5573 6572 passwordTestUser
00000050: 6e61 6d65 0000 0000 0000 0000 0000 0000 name............
00000060: 0000 0000 5359 6f75 2061 7265 2065 6974 ....SYou are eit
00000070: 6865 7220 7265 7370 6f6e 7369 626c 6520 her responsible
00000080: 666f 7220 636c 6561 7269 6e67 2074 6865 for clearing the
00000090: 2063 6163 6865 206f 6e20 796f 7572 206f cache on your o
000000a0: 776e 2028 6024 2067 7265 656e 636c 6970 wn (`$ greenclip
000000b0: 2063 6c65 6172 6029 0000 0000 0000 0000 clear`)........
000000c0: 0000 0000 0000 0000 ........
The test username and test password are clearly seen in the first 50B
of the file. You are either responsible for clearing the cache on your own ($ greenclip clear
) or waiting for the sensitive string to get paged out of the cache (the default is 50 copied items).
In addition to the clipboard text being sniffable by any application that can run as you, it’s just not great to leave sensitive data sitting around unencrypted.
The native macOS
clipboard doesn’t suffer from the same issue that greenclip
does since it only remembers one item. A short time after copying a password to the clipboard, many password managers will copy an empty string – effectively removing the sensitive data from the clipboard.
How Could Schematized Clipboard Data Help?
I want my clipboard to understand what kind of data it’s holding.
If I copy an SSH private key and go to paste it into a GitHub comment, I want my clipboard manager to go:
Or if I copy my Google password out of 1Password and some random app tries to read the clipboard:
Or maybe you want to lock down what programs can write to your clipboard when you copy a bitcoin address.
There seems to be a lot of opportunity here.
What a Schematized Clipboard Protocol Could Look Like
Clients (like 1Password, or other password managers) would send their passwords to the clipboard with something like the following format:
{
"data": "Data to be copied to the clipboard",
"type": ClipboardDataType.PASSWORD
"magicString": "0xSOMEMAGIC"
}
Clipboard managers would attempt to parse every copied string into this format. If it fails to parse properly, the clipboard manager will assume that it’s raw text and treat it as such. The only edge case here is where you’re copying a string that matches this format, but is really meant to be raw text (like if you copied the JSON
blob above.)
ClipboardDataType
might look something like:
enum ClipboardDataType {
PASSWORD,
SSH_KEY,
UNSENSITIVE,
SENSITIVE_GENERIC,
...
}
It would be great to then be able to configure what datatypes can go where, and what kind of limitations you want to enable. For example, “never let me paste an SSH key into Discord”.
Thanks
grht
for providing feedback on this post.
-
Since all (useful) clipboards will store history using a stack, the last item (located at index 0) copied will be pasted when you press
Ctrl-V
. The item at index 1 will be the second-to-last thing you copied, and so on. ↩