Sunday, March 30, 2008

Fun with Cygwin's /dev/clipboard

I'd say that /dev/clipboard is surely among one of the least known handy things on Cygwin. The idea is simple -- the Windows clipboard is accessible as a file, /dev/clipboard.

I often use /dev/clipboard to bridge the gap between GUI tools and line-oriented tools. For example, if you're wondering how many words are in a section of text in a document you're looking at, you can put that text on the clipboard and then do this:
wc -w /dev/clipboard
If you want to insert the output of ls into a document you're editing, send it to the clipboard,
ls *.java > /dev/clipboard
and then paste it in. (Yes, this task is simpler with Emacs, vi, et al.!)

You can save some typing by symlinking /cb to /dev/clipboard:
ln -s /dev/clipboard /cb
Some non-Cygwin programs don't interact well with /dev/clipboard. For example, directing the output of javap to the clipboard leaves it unchanged:
% date > /cb
% javap Hello >/cb
% cat /cb
Sun Mar 30 15:23:24 USMST 2008
To compensate, I wrote a trivial script, tocb:
% cat tocb
cat >/dev/clipboard
Instead of redirecting output to /cb, I pipe into tocb:
% javap Hello | tocb
% cat /cb
Compiled from "Hello.java"
public class Hello extends java.lang.Object{
public Hello();
}
I also wrote a trivial counterpart for tocb named fromcb:
% cat fromcb
cat /dev/clipboard
Incidentally, Mac OS X has similar commands named pbcopy and pbpaste.

I got to wondering if a single script, call it "cb", could act like fromcb when on the left end of a pipeline, and act like tocb on the right end of a pipeline. Like this:
% cal | cb # puts output of cal onto the clipboard
% cb | wc # puts the clipboard contents onto standard output
Cygwin supports the isatty(file_descriptor) library function, which queries whether the specified file descriptor is connected to a terminal. (Some programs, ls to name one, change their behavior depending on whether they're writing to a terminal.) Linux has isatty(1), which simply wraps isatty(3) in a program. My Cygwin installation doesn't have isatty(1), and I was too lazy to see if it's in some package I haven't installed, but it's trivial to write:
% cat isatty.c
main(int argc, char **argv)
{
exit(!isatty(atoi(argv[1])));
}
With that in hand it's easy to write a cb that that reads or writes the clipboard depending on which end of a pipeline it's on. Here it is:
% cat cb
if isatty 0
then
cat /dev/clipboard
else
cat >/dev/clipboard
fi
Usage:
% cal | cb
% cb | cat -n
1 March 2008
2 Su Mo Tu We Th Fr Sa
3 1
4 2 3 4 5 6 7 8
5 9 10 11 12 13 14 15
6 16 17 18 19 20 21 22
7 23 24 25 26 27 28 29
8 30 31
You can put cb on both ends of a pipeline to transform the clipboard contents:
% date | cb
% cb | tr a-z A-Z | cb
% cb
SUN MAR 30 16:03:17 USMST 2008
p.s.
Maybe there is nothing new under the sun -- I just now noticed that xsel (c. 2001) apparently uses isatty to determine whether it should fetch or set clipboard contents. :)