Searchable Clipboard Manager, like ctrl-r in bash

Solution 1:

Introduction

As stated in the comments, I've written a simple clipboard manager indicator with use of Python 3 and a few zenity dialogs, which allows manipulating clipboard contents, as well as has search functionality.

Search functionality, in particular, makes use of Python's re module, which means you can use regex expressions for more fine-grained search. Each text entry has 4 options in its submenu: insert text into clipboard, append text to the end of what's currently in cliboard, prepend text to the beginning of current contents, and remove it from history.

Installation

In terminal , run the following commands:

sudo add-apt-repository ppa:1047481448-2/sergkolo
sudo apt-get update
sudo apt-get install indicator-bulletin

Demo

In the animation below you can see demonstration of text being copied, clipboard appended to with the indicator, and finally the test string is searched via simple search dialog. The found contents are displayed in a simple text view dialog with timestamp in user's locale (if it's set). The found text then can be copied via selecting text and using standard Ctrl+C shortcut. enter image description here

In case anybody is wondering:

  • animation is made with silentcast app
  • desktop and icon theme are Ubuntu Kylin theme, 16.04 version
  • Yes, that's Ubuntu (16.04) with launcher on the bottom.

Further development

The indicator was made in one day, thus it is fairly minimalistic in nature as of right now. In future there will be additional features added, such as preferences dialog. The menu length and text width are currently hard-coded to 10 items in the indicator and 30 characters max for each entry.

If you want to make a feature request, please submit an issue ticket on project's GitHub page, where its source code is also available.

Updates:

February 14,2017:

  • Implemented file operations feature - loading a text file into clipboard and writing clipboard contents to file.
  • Implemented displaying range of entries by date. The option calls for zenity forms dialog which requires starting and ending date in YYYY/MM/DD/HH:SS format. Effectively, this is log reading within date range, same as this
  • couple bug fixes

February 15,2017:

  • Added editing submenu. Users now can convert clipboard contents to upper/lower case, trim words/characters from beginning or end of the text, and replace expressions(uses python's re regex).

February 17,2017:

  • Added option for opening a history item in text file ( similar to bash's fcedit). So far this only opens default program set for the plain-text filetype.
  • couple improvements and bug fixes.

February 19,2017:

  • Added base64 operations (encode/decode).

March 3rd,2017:

  • Added "Pinned submenu" and ability to "pin" specific items from clipboard history. Code refactored, certain parts rewritten for repeated use.

Solution 2:

Recommendations from within Ask Ubuntu

From this (Clipboard Manager for Ubuntu 16.04) we learn from users:

  • Clipit is the best among all.
  • CopyQ is a cross-platform, well-designed and fully-featured clipboard manager (my favorite). It features among others a (very handy) command line interface.

From this (Ubuntu 14.04 Clipboard Manager?) we learn from users:

  • Glipper is a clipboard manager, it can be installed from the Software Center.
  • I use ClipIt is a Parcelite fork with Ubuntu menu integration. (This package was listed in first section above)
  • I'm using GPaste
  • You can try Keepboard. It is easy to use and seems to be stable and reliable.
  • Diodon is another good option available for both GTK and Unity. Works pretty much like the others in the answers already given. However, you can search your recent 'clips' using Dash. (You are already using this version though and don't like it.)
  • I use Clippy, a docklet that works with Plank (I don't use Unity). I think Clippy also comes with Docky.

Recommendations outside of Ask Ubuntu

Not to be outdone by Ubuntu users this (tecmint.com - 10 best clipboard managers for Linux) recommends:

  • CopyQ is an advanced clipboard manager which is available on most if not all platforms.
  • GPaste is a powerful and great clipboard manager for GNOME based distributions, but can work on a variety of desktop environments as well
  • Diodon is a light weight but yet powerful clipboard manager designed to work best when integrated with Unity and GNOME desktop environments. (Once again you do not like this package)
  • Pastie is a simple clipboard manager for Ubuntu and makes use of the AppIndicator. It has some cool features.
  • Parcellite is a stripped down, lightweight GTK+2, basic-features clipboard manager for Linux.
  • Glipper is a clipboard management tool for GNOME desktop environment, users can extend its functionality using plugins. It now uses the App Indicator to support Unity and Gnome Classic desktop environments in Ubuntu.
  • Clipit is a lightweight GTK+ clipboard manager. It is feature rich and actually forked from Parcellite, but includes some extra features and bug fixes.
  • Keepboard is a cross-platform clipboard manager that allows users to save clipboard history.
  • I've removed some from the list those that do not work with Ubuntu. So it doesn't add up to 10 in the article title.

There are other reviews of the same packages listed above found in (makeuseof.com - 6 Tools to manage Linux clipboard) and in (maketecheasier.com - Enhance Linux clipboard with clipboard manager).

Summary

At the end of the day the package you choose is the one that you are most comfortable with. No one can say for sure which package that will be. Almost all of them offer searchable history like you want it but which one has the interface best attuned to your way of thinking?

Solution 3:

1. Minimalistic clipboard utility to quickly search (dynamically updated) clipboard history

With the setup below, it is extremely easy to search recently copied (strings from) text, and paste it with a double click into your documents. Setting up is not the simplest of all (currently), usage is extremely straightforward though.

How it works in practice

  1. Press a shortcut, a small window appears:

    enter image description here

  2. Type one or more characters to limit the selection:

    enter image description here

  3. Double click on the string you'd like to insert:

    enter image description here

  4. The string is pasted in your document, the window closes itself:

    enter image description here

Set up

The setup includes two small scripts

script 1; a background script to watch for changes in the clipboard

import time
import pyperclip
import os

# set the minimum length of a string to be listed
minlength = 3
# set the number of recently copied strings to be remembered
size = 500

clipdb = os.path.join(os.environ["HOME"], ".wordlist")

def update_words(words):
    try:
        currwords = open(clipdb).read().splitlines()
    except FileNotFoundError:
        currwords = []
    add = list(set(words.split()))
    newwords = [s for s in add+currwords if len(s) >= minlength][:size]
    open(clipdb, "wt").write("\n".join(newwords))

clp1 = ""

while True:
    time.sleep(0.2)
    clp2 = pyperclip.paste()
    if clp2 != clp1:
        update_words(clp2)
    clp1 = clp2

script 2; to call from a shortcut key

#!/usr/bin/env python3
import subprocess
import os
import sys
from tkinter import*
from operator import itemgetter
import pyperclip
import time

def sortlist(l):
    low = sorted(list(enumerate([s.lower() for s in set(l)])), key=itemgetter(1))
    return [l[i] for i in [item[0] for item in low]]   

def limit(*args):
    listbox.delete(0, END)
    for w in [s for s in show if s.lower().startswith(insert.get())]:
        listbox.insert(END, w)

def getpaste(*args):
    test = listbox.get(ACTIVE)
    pyperclip.copy(test)
    master.destroy()
    subprocess.Popen(["xdotool", "key", "Control_L+v"])

clipdb = os.path.join(os.environ["HOME"], ".wordlist")

try:
    currwords = open(clipdb).read().splitlines()
except FileNotFoundError:
    pass
else:   
    show = sortlist(currwords)
    master = Tk()
    master.resizable(False, False)
    master.title("Insert")
    listbox = Listbox(
        master,
        highlightbackground = "white",
        highlightthickness=0,
        highlightcolor="blue",
        relief=FLAT,
        )
    listbox.bind(
        "<Double-Button-1>",
        getpaste,
        )
    listbox.pack()
    insert = Entry(
        master,
        highlightbackground = "white",
        highlightthickness=1,
        highlightcolor="#C8C8C8",
        relief=FLAT,
        )
    insert.pack()
    insert.bind("<KeyRelease>", limit)
    insert.focus_set()

    for item in show:
        listbox.insert(END, item)
    mainloop()

How to set up

  1. The script needs python3-pyperclip, python3-tkinter and xdotool:

    sudo apt-get install xdotool python3-pyperclip python3-tkinter
    
  2. Copy script 1 into an empty file, save it as watch_clipboard.py
  3. Copy script 2 into an empty file, save it as paste_recent.py
  4. (Test-) run script 1 as a background script with the command:

    python3 /path/to/watch_clipboard.py
    

    Start copying tekst, which is automatically remembered (up to an arbitrary number of strings, remembering the last n- strings).

  5. Add script 2 to a shortcut: choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command:

    python3 /path/to/paste_recent.py
    

    Open a gedit window and call the little window with your shortcut, and use the utility as explained in the introduction.

  6. If all works fine, add script 1 to Startup Applications: Dash > Startup Applications > Add. Add the command:

    python3 /path/to/watch_clipboard.py
    

Options

In the head section of the script, you can set the minimum length of a string, to be listed in the clipboard history (number of characters):

minlength = 1

Also, you can set how many strings should be kept in history (size of the database):

size = 500

Notes

  • Currently, the utility works with all applications which are compatible with Ctrl+Alt+V. The version below also pastes in gnome-terminal.

2. Indicator version (single-script, simple setup) with optional keyboard shortcut, also pasting correctly in gnome-terminal

enter image description here

#!/usr/bin/env python3
import subprocess
import os
import time
import signal
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import Gtk, AppIndicator3
from threading import Thread
import pyperclip
import sys
from tkinter import*
from operator import itemgetter

#---
clipdb = os.path.join(os.environ["HOME"], ".wordlist")
minlength = 5
size = 500

arg = sys.argv[1]
pyperclip.copy("")

def get(cmd): return subprocess.check_output(cmd).decode("utf-8").strip()

def check_terminal():
    return get(["xdotool", "getwindowfocus"]) in get(["xdotool", "search", "--class", "terminal"])

def getfromfile():
    try:
        return open(clipdb).read().splitlines()
    except FileNotFoundError:
        return []

def update_words(words):
    currwords = getfromfile()
    add = list(set(words.split()))
    newwords = [s for s in add+currwords if len(s) >= minlength][:size]
    open(clipdb, "wt").write("\n".join(newwords))

class SearchClip():

    def __init__(self):
        self.currwords = list(set(getfromfile()))
        self.showwin()

    def sortlist(self, l):
        low = sorted(list(enumerate([s.lower() for s in set(l)])), key=itemgetter(1))
        return [l[i] for i in [item[0] for item in low]]

    def limit(self, *args):
        self.listbox.delete(0, END)
        for w in [s for s in self.show if s.lower().startswith(self.insert.get())]:
            self.listbox.insert(END, w)

    def getpaste(self, *args):
        test = self.listbox.get(ACTIVE)
        pyperclip.copy(test)
        self.master.destroy()
        time.sleep(0.2)
        cmd = ["xdotool", "key", "Control_L+Shift+v"] if\
              check_terminal() else ["xdotool", "key", "Control_L+v"]
        subprocess.Popen(cmd)

    def showwin(self):
        self.show = self.sortlist(self.currwords)
        self.master = Tk()
        self.master.resizable(False, False)
        self.master.title("Insert")
        self.listbox = Listbox(
            self.master,
            highlightbackground = "white",
            highlightthickness=0,
            highlightcolor="blue",
            relief=FLAT,
            )
        self.listbox.bind(
            "<Double-Button-1>",
            self.getpaste,
            )
        self.listbox.pack()
        self.insert = Entry(
            self.master,
            highlightbackground = "white",
            highlightthickness=1,
            highlightcolor="#C8C8C8",
            relief=FLAT,
            )
        self.insert.pack()
        self.insert.bind("<KeyRelease>", self.limit)
        self.insert.focus_set()
        for item in self.show:
            self.listbox.insert(END, item)
        mainloop()

class ClipTrip():

    def __init__(self):
        self.app = "ClipTrip"
        iconpath = "mail-attachment"
        self.indicator = AppIndicator3.Indicator.new(
            self.app, iconpath,
            AppIndicator3.IndicatorCategory.OTHER)
        self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)       
        self.indicator.set_menu(self.create_menu())        
        self.watchclip = Thread(target=self.clipwatch)
        self.watchclip.setDaemon(True)
        self.watchclip.start()

    def clipwatch(self):
        clp1 = ""
        while True:
            time.sleep(0.2)
            clp2 = pyperclip.paste()
            if clp2 != clp1:
                update_words(clp2)
            clp1 = clp2

    def create_menu(self):
        self.menu = Gtk.Menu()
        self.item_search = Gtk.MenuItem('Search clipboard history')
        self.item_search.connect('activate', run_search)
        self.menu.append(self.item_search)
        menu_sep = Gtk.SeparatorMenuItem()
        self.menu.append(menu_sep)
        self.item_quit = Gtk.MenuItem('Quit')
        self.item_quit.connect('activate', self.stop)
        self.menu.append(self.item_quit)
        self.menu.show_all()
        return self.menu

    def stop(self, source):
        Gtk.main_quit()

def run_search(*args):
    SearchClip()

if arg == "run":
    ClipTrip()
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    Gtk.main()
elif arg == "search":
    run_search()

Set up

This version has pretty much the same functionality as the scripts in [1], but as an indicator and simpler to set up:

  1. Like in option 1, the script needs python3-pyperclip, python3-tkinter and xdotool:

    sudo apt-get install xdotool python3-pyperclip python3-tkinter
    
  2. Copy the script into an empty file, save as history_indicator.py

  3. Run it by the command:

    python3 /path/to/history_indicator.py run
    

    to start the indicator + background script. Choose Search clipboard history from the menu to call the little window.

    or

    To call the window in an alternative way, run

    python3 /path/to/history_indicator.py search