Clipboard teleportation

Peter Shawhan - July 23, 2004

I am fortunate to have two computers to use at work: a Linux desktop machine and a lightweight Windows laptop. I use the Linux machine to write and run programs, and the laptop for email, web browsing, writing documents, and working remotely from home. During the workday, they sit next to each other on my desk, and I switch back and forth between them frequently. The only problem with this arrangement is that I often will get an email message or view a web page containing commands to be executed on the Linux command line; or, I will have some program output text on the Linux machine that I want to send to someone in an email message. In that case, the half-meter distance between the two machines on my desk stands in the way of a simple cut-and-paste.

To get around this problem, I invested half a day in writing a pair of Tcl scripts which let me easily copy the contents of the Windows clipboard to the X (PRIMARY) selection on the Linux machine, and vice versa. I call this client-server system teleclip.

  • The server script, called teleclip, runs on the Linux machine as a background wish process. (To start it, type 'teleclip &'.) It has a hidden (withdrawn) window with a text widget which holds any text sent to it by the client. The text is selected as soon as it is received, so that it can be pasted into any other window with a simple click of the middle mouse button. Selecting text in any window on the Linux machine immediately makes it available for the client to receive upon request.
  • The client script, called teleclip_client.tcl, runs under wish on the Windows machine. (To start it, double-click on the file. Alternatively, for convenient startup, I put a shortcut to it in the 'Quick Launch' area of my Windows taskbar.) It creates a little window with 'Send' and 'Receive' buttons. To send some text, I select it, Copy it to the Windows clipboard, and then click on the 'Send' button. To get whatever text is selected on the Linux machine, I click on the 'Receive' button and then Paste it where I want it.

This system works well for me as long as the selected text is not too long; I have had some trouble with selections longer than a few thousand characters (?), which is presumably related to the way the selection mechanism divides larger amounts of data into chunks, but I have not bothered to track this down. Note that there is no security in this system other than the obscurity of the server host name and port number (which I have changed in the scripts below to protect the innocent) and the fact that the server ignores input which does not match its very limited protocol.

The server script (for the Linux machine) : teleclip

 #!/usr/bin/wish
 # teleclip server for unix
 # Written by Peter Shawhan, 21 Sept 2002

 #-- Hard-code the port number
 set serverPort 33333

 ##=========================================================================
 proc Main {} {

     #-- Withdraw the main window
     wm withdraw .

     #-- Open a listening socket
     global serverPort
     if [catch {socket -server Connect $serverPort} lsock] {
         puts stderr "Error opening listening socket: $lsock"
         exit 1
     }

     #-- Create a text widget, but do not pack it; it's just for holding
     #-- clipboard data that we receive
     text .text

     return
 }

 ##=========================================================================
 proc Connect { sock addr port } {
     #-- Handle a client connection
     fconfigure $sock -buffering full -blocking 0
     fileevent $sock readable "Input $sock"
     set ::buf($sock) ""
     return
 }

 ##=========================================================================
 proc Input { sock } {
     #-- Handle input from the client

     upvar #0 buf($sock) buf

     if { [eof $sock] || [catch {read $sock} data] } {
         #-- Socket was closed by client, or there was an error reading from it
         catch { close $sock }
         unset ::buf($sock)
         return
     }

     append buf $data

     #-- If input is complete, act on it
     if { [info complete $buf] } {
         switch -exact -- [lindex $buf 0] {
             "cput" {
                 if { [llength $buf] >= 2 } {
                     #-- Stick the data into the hidden text widget
                     .text delete 1.0 end
                     .text insert end [lindex $buf 1]
                     .text tag add sel 1.0 end-1char
                     #-- Delete the buffer contents that we've dealt with
                     #-- (typically this deletes the entire buffer)
                     set buf [lrange $buf 2 end]
                 }
             }
             "cget" {
                 #-- Send contents of PRIMARY selection (since this is unix)
                 puts $sock [list [selection get] ]
                 flush $sock
                 set buf [lrange $buf 1 end]
             }
             default {
                 #-- Just ignore this input from the client
                 set buf [lrange $buf 1 end]
             }
         }
     }

     return
 }

 ##=========================================================================
 #-- Now start the program and enter the event loop
 Main

The client script (for the Windows machine) : teleclip_client.tcl

 # teleclip client for Windows/Macintosh
 # Written by Peter Shawhan, 21 Sept 2002

 #-- Hard-code server's Internet address and port number
 set serverIP teleclip.ligo.caltech.edu
 set serverPort 33333

 ##=========================================================================
 proc Main {} {

     global serverIP serverPort buf

     #-- Open a socket connection to the server
     if [catch {socket $serverIP $serverPort} sock] {
         #-- Display an error message
         message .message -aspect 10000 -justify center \
                 -text "Error connecting to teleclip server\nat $serverIP:\n$sock"
         pack .message
         return
     }
     fconfigure $sock -buffering full -blocking 0

     #-- Set up a handler for receiving clipboard data over the socket
     fileevent $sock readable "Input $sock"
     set ::buf ""

     #-- Create "Send" and "Receive" buttons, and pack them in the window
     button .send -text Send -command "Send $sock"
     button .receive -text Receive -command "Receive $sock"
     pack .send .receive -side left

     return
 }

 ##=========================================================================
 proc Send { sock } {
     #-- Send the contents of the clipboard; ignore errors
     catch {
         puts $sock [list "cput" [selection get -selection CLIPBOARD] ]
         flush $sock
     }
     return
 }

 ##=========================================================================
 proc Receive { sock } {
     #-- Send a request for clipboard data; the data coming back will be
     #-- handled by the fileevent
     puts $sock "cget"
     flush $sock
     return
 }

 ##=========================================================================
 proc Input { sock } {

     global serverIP buf

     if { [eof $sock] || [catch {read $sock} data] } {
         catch { close $sock }
         #-- Replace the buttons with an error message
         destroy .send .receive
         message .message -aspect 10000 -justify center\
                 -text "teleclip server at\n$serverIP disconnected"
         pack .message
         return
     }

     append buf $data

     #-- If input is complete, stick it into the clipboard
     if { [info complete $buf] } {
         clipboard clear
         clipboard append -- [lindex $buf 0]
         set buf ""
     }

     return   
 }

 ##=========================================================================
 #-- Now start the program and enter the event loop
 Main