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
Press a shortcut, a small window appears:
Type one or more characters to limit the selection:
Double click on the string you'd like to insert:
The string is pasted in your document, the window closes itself:
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
The script needs python3-pyperclip
, python3-tkinter
and xdotool
:
sudo apt-get install xdotool python3-pyperclip python3-tkinter
- Copy script 1 into an empty file, save it as
watch_clipboard.py
- Copy script 2 into an empty file, save it as
paste_recent.py
(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).
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.
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
#!/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:
Like in option 1, the script needs python3-pyperclip
, python3-tkinter
and xdotool
:
sudo apt-get install xdotool python3-pyperclip python3-tkinter
Copy the script into an empty file, save as history_indicator.py
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