How to create your own clipboard manager using python and Tkinter

Prashant Gupta
7 min readOct 5, 2018

--

I’m sure everyone at some point in their lives must have wanted to be able to copy multiple items from different places, and then pick which one to paste at leisure. Being a software developer, one of my main skills is in copy-pasting, and working for a restrictive company I was advised not to use the publicly available clipboard management applications. That’s because they could be using the data you copy for their own use, and if you are like me, I tend to copy a lot of passwords, which I do not want anyone to see (for reasons other than security ;) That’s why I thought about writing my own clipboard manager (I call it Clippy), and learning Tkinter in the process.

Tkinter is Python’s standard graphical user interface (GUI) package built on top of Tcl (Tool command Language). I did my research and I found 2 options for GUIs in python that are recommended by most: Tkinter and PyQt5. I will not go into the details about their differences, but the biggest ones that I found were that Tkinter is built-in with python, and has better documentation. PyQt5, on the other hand, is

  1. An external module that needs to be downloaded separately.
  2. Is still relatively new, and so most of the articles and videos that I searched for were for PyQt4.
  3. PyQt5 is not backwards compatible with PyQt4.

That was enough to convince me to use Tkinter.

Meet Clippy

Enough background, let’s jump straight into the code! (You can find the link to the entire code in my github repo here). This is what Clippy looks like in action:

The application mainly consists of 2 elements, a GUI handler(Tkinter) and a clipboard handler(pyperclip).

Let’s see both of them in detail.

Tkinter module

The Tkinter module contains everything we need to work with the toolkit. The first thing we need to do for a Tkinter application is import the tkinter module (it’s named Tkinter in python 2.x and tkinter in python 3.x), and create the root element:

Once we do that, we create the root element, which is a basic window with a title bar. The root element needs to be created before creating any other widget.

The next command is the mainloop() function. This starts a never ending event loop, and the program stays in this loop until we close the main window. The way this works is that we create our application (define layout, callbacks etc.) and then call the mainloop function. Once the mainloop function is hit, our window becomes visible, and the application goes into Tkinter’s event loop, which can handle events from the user (through callbacks) or the windowing system (such as redraw events). It also includes display updates, which is why our application will not be visible until the mainloop function is executed.

It can be visualized through this diagram:

Flowchart for a typical Tkinter application

Once that is done, let’s talk about the different Tkinter widgets that we will use for our application.

Frame Widget

The first is the Frame widget. It is mainly used as a base widget for other widgets, and it can group the widgets into layouts. Think of it as a convenient place holder to keep everything else that we will build on our application. For the example application in this post, we will build everything on top of root itself, but Frame will come handy when there are a lot more widgets to handle.

Label Widget

We will use this to create clickable buttons which will contain the value of the copied text. Wait what? If Tkinter offers a button widget, why not use that instead? Yes, Tkinter does offer a button widget, and trust me, I tried using buttons in the first release. Turns out, Tkinter has some ongoing issues with mac, and no matter what I tried, I could not get the button widget to resize. So I turned to the label widget instead, and created a button out of it. Easy right? (It’s not much different code-wise)

label = Label(text="Hello", cursor="plus", relief=RAISED, pady=5,  wraplength=500)

This creates a simple label with Hello as text, what cursor to show when the mouse is moved over the label, and some other aspects to make it look like a button. (relief=RAISED). We will talk later about how to make it behave like a button (we will animate the clicking). The wraplength defines when the label’s text should be wrapped into multiple lines. It’s good aesthetics for longer copied text.

These are the basic widgets I will be using to create the application. For all other widgets, I would recommend checking out the official Tkinter documentation here.

Clipboard content

I used pyperclip to manage the clipboard content. It has the simplest documentation of any module that I have come across:

>>> import pyperclip
>>> pyperclip.copy('The text to be copied to the clipboard.')
>>> pyperclip.paste()
'The text to be copied to the clipboard.'

That’s it! Now what we want to do is to keep calling thepyperclip.paste() function indefinitely, and then handle any new text that it finds.

In order to avoid going through the pain of understanding multi-threading and making use of another thread to keep track of the clipboard content (which keeps calling the paste() function indefinitely in the background), Tkinter provides a neat functionality called after , which basically registers a callback function that is called after a given period of time. We can make the function call itself indefinitely by re-registering its callback inside itself. Example ahead.

Connecting it all together

Now we know the basics of Tkinter and pyperclip, we can see Clippy in action!

Creating a basic layout

For starters, we are going to create a label and put it on our application. It will reflect the content of the clipboard dynamically. We already saw how to create a basic label:

label = Label(root, text="", cursor="plus", relief=RAISED, pady=5,  wraplength=500)label.pack()

The text field is empty because we will populate it dynamically using the logic explained below.

The pack command is to set the label widget onto the root window. It basically packs widgets into rows or columns.

Checking for new clipboard value

We create a simple function, let’s call it updateClipboard, which we are going to call indefinitely, and it will keep checking the clipboard to see if there is anything new:

The code should be self explanatory.

Once we have the cliptext from our function, it’s time to process it.

Processing cliptext

We need to take care of one thing for now when we are processing clippings:

  • Removing all characters that are outside the Tcl range (U+0000-U+FFFF). So basically all characters with unicode value > 65535 need to be removed. (Not a fun fact: Smileys have a higher unicode value than allowed by tcl <sad smiley>)

Once our cliptext is nice and tidy, we can now add it to our label.

Updating label with value

Updating the label’s text is a piece of cake.

Copy label content to clipboard on clicking

Once the label is ready, we can bind a click event to that label, so that once we click it, the label text is copied for us to paste! We bind a click event to the label like this:

label.bind("<Button-1>", lambda event, labelElem=label:
onClick(labelElem))

A little explanation needed here for those uncomfortable with lambda functions. It took me some time as well coming from a java background, but trust me, they make your life so easy.

W3schools define lambda functions to be small anonymous functions. You can refer to w3schools for more info. So technically, what we have written above is something like this:

label.bind("<Button-1>",function(event, labelElem=label):
onClick(labelElem))

Since this is a bind event, Tkinter automatically adds an argument of an event type, which holds metadata of the event that happened (for example, we can get the x and y coordinate of the position at which the click happened using event.x and event.y). We need to account for this extra argument when we create our lambda function. For this use-case, we don’t care about that though, we just want to pass in the label element when we click on the label, so that we can extract the text from it in the onClick function.

We define the onClick function like this:

Let’s see what we have till now:

Before running this code, let’s copy the following line:

this is my first clipping 

Once the code is run, we see a tiny window showing something like this:

Copy this line:

this is my second clipping

and now we see it change automatically:

Voila! Our label updates with whatever we copy! Now click on the label, and you will see this is my second clipping printed on your console, and also copied onto your clipboard!

Enhancements (To be continued…)

There are infinite improvements that can be added onto this application. For starters, we can:

  1. Add test cases.
  2. Add animation to the click event so that the label feels like a button.
  3. Add multiple labels which can hold multiple copied values.
  4. Refresh the label values using LRU replacement policies.
  5. Add a menu item to clear all labels.
  6. Add another menu item to make this application be on top of all other applications always.

These improvements already exist in my application hosted in GitHub, the link to which is here. I will walk you through all these enhancements in the second part of this tutorial link.

Thank you for reading.

--

--