Build a Shared Clipboard Utility in Python : Page 2
A shared clipboard lets you copy and paste data seamlessly across machines—it's the perfect productivity tool if you work with multiple machines in parallel.
by Gigi Sayfan
Feb 28, 2008
Page 2 of 3
Shared Clipboard Design
The shared clipboard design is very simple. It needs to work with different clipboard APIs on different platforms, but the main algorithm is the same. The program employs a simple generic clipboard interface consisting of the following four operations:
The code consists of different modules that implement this simple interface in terms of specific APIs. The main module, SharedClipboard.py, selects the proper module at runtime based on the platform and/or configuration, and simply works in terms of the generic interface. This design has a couple of benefits. First, the main algorithm is expressed in generic terms and is not a mess of specific API calls. The design is also extensible to new platforms and APIs (as long as they can be abstracted using the interface operations).
Shared Clipboard Implementation
You can see the full code for the main module (SharedClipboard.py) in Listing 1, but here's how it works. First, it imports some standard library modules (os, sys, time). The sys.platform attribute lets Python detect which platform the program is running on. Based on the platform, it imports all the attributes from the appropriate module using the from X import * notation. That makes all the interface methods available (openClipboard, closeClipboard, getClipboardData, setClipboardData).
if sys.platform == 'darwin':
from MacSharedClipboard import *
from CarbonSharedClipboard import *
elif sys.platform == 'win32':
from WindowsSharedClipboard import *
Note that if the platform is 'darwin' (Mac OS X), the module must make one further decision based the value of the use_carbon Boolean flag.
The monitorClipboard function contains the main loop. It initializes the variable prev_data to an empty string and starts a loop (I don't want to say infinite loop, because I have personally terminated this loop many times, so I know for a fact it is not infinite). Next, it opens the clipboard using by calling the openClipboard() method. The actual implantation code for openClipboard comes from one of the pre-selected platform-specific modules. If the openClipboard call fails, the code raises an exception—although the program simply catches the exception quietly and the code continues to the next loop iteration, which sleeps for a second and then attempts to open the clipboard again. That's because it's likely that some other program had the clipboard open temporarily. There is no need to panic and exit.
If the clipboard opens successfully the code enters two nested try blocks. The external try-finally block just ensures that the program will call closeClipboard() if openClipboard() succeeded. The inner try-except block handles any exceptions raised during the main operation.
Author's Note: The nested try blocks are necessary for backward compatibility with older version of Python. Python 2.5 introduced an improvement to the exception-handling syntax (along with many other improvements) so now you could write the following equivalent construct:
except Exception, e:
This new syntax is much nicer because you save a potentially confusing nested block and a whole level of indentation. You can read more about Python 2.5 in my three-part series (see the Related Resources section of this article in the left column for links).
The core of the SharedClipboard program first checks whether something new has been placed in the clipboard. If so, it writes the clipboard data to the shared file, and updates the prev_data variable which keeps the last-written data so it can compare that to the clipboard contents during the next iteration. This practice prevents the program from writing the same content repeatedly if it just sits in the clipboard because the user forgot to paste it.
If there is nothing new in the clipboard, it then checks the shared file for something new—perhaps the clipboard contents from a different machine has been written to the shared file since the last check. If there is something new in the file copies that content to the local clipboard, once again updating the prev_data variable with the latest content.
data = getClipboardData()
if data and data != prev_data:
print 'writing %s to file' % data
prev_data = data
data = open(clipboard_file, 'r').read()
if data != prev_data:
print 'putting %s in clipboard' % data
prev_data = data
You might think from looking at the preceding code that you could just place the line prev_data = data outside of the if-else block, because it appears to be duplicated in both the if and the else portions of the block. But you can't—there is a case when there is nothing in the clipboard (just after you paste the clipboard contents) and if you externalize this line then it will result in the shared file's contents being put into the clipboard repeatedly.
That leaves only the "main" function, which is pretty standard stuff, so you only need to read about it if you are new to Python. Python doesn't really have a main function. Any code that's not inside a function or a class definition gets executed when the hosting module is executed or imported (see the sidebar "Why Doesn't SharedClipboard Use a Clipboard Class" for more about Python and classes). However, it is common practice to have some conditional code running, such as this block:
This code executes only when the module is executed directly and not when it is imported. The SharedClipboard program follows this convention and immediately calls a main() function. Note that this convention is not required. Most Python programmers simply write their "main" code immediately following the check for __name__=='__main__'. The main() function verifies that the arguments (sys.argv) contain a filename (for the shared file) and launches monitorClipboard. It could be a little more thorough and check that the file exists, is writable, and that the SharedClipboard has sufficient permissions to access it for writing, but as I wrote it sheerly as a utility for my own private use, I kept it lightweight. If such an issue should arise, the shared clipboard will just not work—and I'll notice when I try to paste something I copied on a different machine. For debugging purposes, the following error will be printed on the SharedClipboard console window (which I keep minimized) for me to stare at.
usage = \
"""Usage: python SharedClipboard.py <shared clipboard filename>
The filename should refer to a writable existing file. The file
should be on a shared location visible and (writable) to all the
shared clipboard instances on all machines.
if len(sys.argv) != 2 or not os.path.isfile(sys.argv):
clipboard_file = sys.argv