| http://www.devx.com | Printed from http://www.devx.com/opensource/Article/37233/1954 |
|
Build a Shared Clipboard Utility in Python
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
|
|||||||||
Here's a quick scenario that illustrates the basics. Suppose you're running both a Mac and a Windows machine. You select and copy the text "The shared clipboard is awesome!" from within a text editor on the Mac (see Figure 1). Having done that, you can now paste that exact same text into an application on a separate Windows machine (see Figure 2). The shared clipboard works in the background, making clipboard interactions between machines as simple as copying and pasting between applications on the same machine.
A shared clipboard is particularly useful for people that work on multiple machines at the same time. For example, I typically work with between two and three physical machines running Windows XP, Vista, Linux, and Mac OS X Leopard; some of those OSs run in virtual machines (VMs). I often look up something on the web or in a file and need to paste it in some application on another machine. Before I developed the shared clipboard I had several inconvenient options: I could switch to the target machine and look up the content again; I could copy the content on one machine and send an IM or email to the other machine, open the email or IM client, copy the content, and paste it in its final destination; or I could create a file containing the content on a shared network drive, save it, and then open the file from the other machine. All these options take extra time, but using the shared clipboard to copy and paste across machines takes essentially no more time than a copy and paste operation on a single machine. It important to understand that SharedClipboard is designed to help a single user copy and paste between multiple machines, not to share information between users. Different users will want different content in their clipboards. Sharing the clipboard will cause them to overwrite each other's clipboard contents, perhaps causing people to lose data before they had a chance to paste it. How Does It Work? The shared clipboard is based on a very simple idea: you keep a little program called SharedClipboard running on each physical machine from which you wish to share a clipboard. This program monitors the machine's clipboard and a special shared file. For example, suppose you had three machines: A, B, and C. Whenever you copy some text into the clipboard of machine A, the SharedClipboard instance running on that machine overwrites the contents of the shared file. Immediately, the SharedClipboard instances on the other two machines (B and C) that monitor the shared file will detect that its content has changed—and will copy the file's contents into their own local clipboards, so it's ready to be pasted. To prevent infinite loops that repeatedly write the same content to the clipboard and the file, SharedClipboard checks whether the clipboard contents are identical to the contents of the shared file, before modifying it; if they are, it doesn't alter the file. Race conditions are ignored. If you were to simultaneously copy some data into the clipboard of more than one machine (you need to be quick!), one of the machines will grab the shared file first, modify it, and then the other machine will open and modify the file. It may not be clear exactly which machine's clipboard content will end up in the shared file and available to all machines, but such contention won't break the SharedClipboard itself. In practice, such contention is all but impossible for a single user. SharedClipboard and Virtual Machines The shared clipboard works through a single instance when you're running virtual machines, because VMs typically let you share the clipboard of the host OS. In other words, even if you're running multiple VMs with different operating systems, they can all share the physical machine's clipboard. Therefore, running SharedClipboard on any one of the virtual machines or on the host OS lets all benefit from it. SharedClipboard in a Multiuser Environment As described so far, the shared clipboard is designed to be used by a single user. But what if you have multiple users on a single LAN, each controlling several machines, and they all wish (naturally) to use the shared clipboard? No problem. The shared file that each instance of SharedClipboard uses is a command-line argument to the program. Each user should launch a SharedClipboard instance on each of their physical machines, passing the same shared filename to each instance. They won't be able to share data with each other through their SharedClipboard instances, but as long as different users use different shared files (typically, that file would reside on one of their machines or a network share) they will not overwrite each other's clipboards. The size of the shared file for each user is always the size of the last copied content, so it doesn't grow over time and won't clog up the drive.
|
| 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:
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).
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.
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.
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.
|
| Windows Shared Clipboard The Windows shared clipboard module (WindowsSharedClipboard.py, shown in Listing 2), is implemented on top of the win32clipboard module of the Win32 extensions for Python. There is a one-to-one relationship between the abstract clipboard interface and the win32clipboard interface, so each method implementation requires only a line or two; win32clipboard does the heavy lifting. First, the code imports the win32clipboard module. The openClipboard() and closeClipboard() just call the corresponding win32clipboard methods. The closeClipboard() method also swallows any exception after printing it to the console. There is nothing it can do to recover if the error is a permanent condition. It handles the exception because in the main loop the call to closeClipboard() is not wrapped with a try-except block (it's in the outer try-finally block).
The getClipboardData() function checks if there is text content in the clipboard and, if so, returns it (or returns None if there isn't). Any exceptions propagate up, and get caught by the try-except block in the main loop.
The setClipboardData() function is even simpler. It just empties the clipboard and puts the new content into it (as text). Again, exceptions propagate upward by design.
Implementation for Mac OS X Leopard (Carbon) There are actually two separate modules for the Mac shared clipboard because the Carbon module kept crashing on Mac OS Tiger. The pbcopy/pbpaste-based module should work on any Mac OS X version. On Leopard, the Carbon-based module works fine. The CarbonSharedClipboard.py module (see Listing 3) implements the clipboard interface on top of the Mac's native Carbon API, providing clipboard (or rather "pasteboard" as it is called on the Mac) access. The module begins with a couple of imports statements from Carbon.Scrap, which corresponds more or less to the clipboard on Windows. It also imports the MacOS to be able to access the MacOs.Error exception type. Note that openClipboard() and closeClipboard() do nothing here.
This implementation of the getClipboardData() function has a try-except block to catch exceptions. It gets the current scrap and tries to return any data associated with the 'TEXT' flavor, which is simply the text content of the Mac pasteboard. If it fails, the module raises an exception. If no text content exists in the clipboard the getScrapFlavorData call raises a -102 MacOS.Error. Because that's common, in this case the code just returns an empty string. However, for any other error, it re-raises the exception, which then propagates to the main loop for handling.
The setClipboardData() function is much more straightforward: clear the current scrap, get it, put the text into the pasteboard, and you're done.
Implementation for All Mac OS X VersionsThe other Mac module (MacSharedClipboard.py, in Listing 4) implements the clipboard interface on top of two command-line programs called pbcopy (which copies text into the clipboard) and pbpaste (which pastes whatever text is in the clipboard). The prefix "pb" stands for "pasteboard," the Mac term for clipboard. This module begins by importing the subprocess module—a standard Python library module (new in Python 2.4) used to launch and interact with external processes. It is very handy for a variety of tasks. Again, the openClipboard() and closeClipboard() functions are empty:
The getClipboardData() function launches the pbpaste command line tool and intercepts its standard output. It waits for the tool to finish running, reads the data and returns it. You know the drill about exceptions by now.
In this module, the setClipboardData() function is essentially a mirror image of getClipboardData(). It launches the pbcopy tool, intercepts the standard input, writes the data to the pasteboard, closes the standard input and wait for the tool to finish running.
Possible Extensions to SharedClipboardAfter you've implemented the basic SharedClipboard discussed in this article, you might consider extending the utility by giving it additional capabilities, such as:
Gigi Sayfan specializes in cross-platform object-oriented programming in C/C++/C#/Python/Java with an emphasis on large-scale distributed systems. He is currently trying to build brain-inspired intelligent machines at Numenta.
|
| DevX is a division of Jupitermedia Corporation © Copyright 2007 Jupitermedia Corporation. All Rights Reserved. Legal Notices |