Make a terminal based clipboard manager just using bash

Bharat Kalluri / 2021-01-29

Why

The idea was not actually to make a clipboard manager. I was under the understanding that everything in linux is a file, so a clipboard must also be a file somewhere. After reading for a while, I realized that clipboard are actually in memory and the display server manager manages it. Interesting

Also, I've learnt that fedora manages the clipboard using xsel. Looks like fedora uses xsel temporarily, until a wayland based clipboard manager. wl-clipboard is an interesting project, a pure wayland clipboard implementation. Also, there are three clipboards in linux.

  • Primary: Whatever you select will be stored here, immaterial of if you copy it or not.
  • Secondary: This is a custom clipboard applications can use. For example, when you copy text in VS code, this is the clipboard used.
  • Clipboard: This is the normal clipboard

There are more questions. How do images get copied? How do you copy rich text with formatting?

This is what I love. Something as simple as a OS clipboard has so much depth!

How

So here's the plan. Have a file at the home directory which will have all the clipboard history. Let us name this ~/.clipboard_history. Monitor the clipboard using xsel -b (which will get back the text on the clipboard), see if this is the latest entry is the same as the one on the clipboard. If no, then add a line at the end of ~/.clipboard_history. If yes, then ignore and move on. There is one problem though, what happens when you copy with something that has new lines. We cannot have new lines in the file, since each line should contain one copied entity. The straightforward solution is to encode the data to base64 and store the encoded data in the file.

The clipboard management is done through a simple fzf command. On selection, will copy the contents to the primary clipboard. Using the same xsel -b.

Using FZF for selecting clipboard history

Now to the implementation

CLIPBOARD_HISTORY_FILE="$HOME/.clipboard_history"

populate_clipboard_history() {
    touch $CLIPBOARD_HISTORY_FILE
    XSEL_OUT="$(xsel -b | base64 -w 0)"
    LAST_LINE_IN_HISTORY="$(tail -1 $CLIPBOARD_HISTORY_FILE)"

    if [[ $(expr ${#XSEL_OUT}) > 0 ]]; then
        if [[ $LAST_LINE_IN_HISTORY != "$XSEL_OUT" ]]; then
            printf "$XSEL_OUT\n" >>$CLIPBOARD_HISTORY_FILE
        fi
    fi
}

populate_clipboard_history is the function to store all copied text to ~/.clipboard_history. It creates the file, if it exists touch does not do anything. So there are no problems. Next we store the encoded clipboard contents in XSEL_OUT and the last line of clipboard history in LAST_LINE_IN_HISTORY. If there is something in the clipboard and it is not the same as what was last captured, then we write that encoded contents to ~/.clipboard_history. So, the capture piece is sorted.

trigger_clipboard_history_search() {
    while read HISTORY_ITEM; do
        DECODED_HISTORY_ITEM=$(printf "$HISTORY_ITEM" | base64 --decode | tr "\n" "\t\t")
        printf "$DECODED_HISTORY_ITEM\n"
    done <$CLIPBOARD_HISTORY_FILE | fzf --height 40% | tr "\t\t" "\n" | xsel -b
}

trigger_clipboard_history_search gets all the contents from the history file. Decodes them and pipes them to fzf. fzf presents them in a neat form. Once something gets selected, it is transformed and piped back to xsel so that it gets back to the clipboard.

One thing to notice, I am converting all new line chars to two tabs. This is because fzf does not allow entities to have new lines. I also understand this messes up with any text which has two tabs (python code). But that is a problem I will solve sometime later.

Now, alias a shortcut to trigger this function and you have a functional clipboard manager in the terminal.

One more todo item, we need to run populate_clipboard_history every second. This can be simply done by while sleep 1; do populate_clipboard_history; done &. Run this on startup.

Hand crafted by Bharat Kalluri