|< Day Day Up >|
Strong-Arming the System Brute Force Behavior Modification
Sometimes, there just isn't a configuration option available to let you make something work the way you want it to. The GUI tools don't have a button for you to click, the configuration files for the software don't list an option for you, and the Defaults database contains no useful parameters. If you're willing to apply what you've learned so far in this book, there still might be ways for you to make your system do what you want. The key is remembering that underneath it all, Mac OS X is running Unix, and the Unix user experience is fundamentally the product of many programs running simultaneously, each providing specific functionality. If you can localize the behavior you want to modify to a single program, you can approach reaching your configuration goal as an exercise in replacing that program's functionality with something that does what you want, instead of what the current version does.
The Sneaky Way Inserting Imposters
Depending on exactly what you're trying to change, there are two primary ways to go about this. The less obnoxious way is to interpose some software of your own devising between what the system is trying to do and what it's actually doing. Because most everything is a small, special-purpose program, you can often insert an imposter program that looks and talks to the system like the program it thinks it's calling. The imposter can then call the actual program (or not, if you don't need to) with any modifications to inputs that you want, unrestricted by what the system allows you to conveniently configure.
Let's take the Command-Shift-3/Command-Shift-4 screen capture facility that's built into the operating system as an example. Pressing Command-Shift-3 takes a screenshot of what currently appears on the screen. Command-Shift-4 lets you select a region of the screen or a particular window to save an image of instead. Both of these functions unfortunately save their output as Portable Network Graphics PNG files, or Adobe Portable Document Format .pdf files. Darned inconvenient, right? If you want to use images captured this way in some truly portable fashion, for example to build a web page, you have to use Preview to export them as some more universally supported image-file format, such as GIF or JPEG, or find some other way to post-process the .png or .pdf files. Wouldn't it be more convenient if the system just saved the screenshots in TIFF format, as it did in Mac OS X 10.1 and earlier?
If you really want that functionality, you're willing to strong-arm the system into giving it to you even though it doesn't appear to be an option, and you accept the consequences of the changes you'll be making, there is a way to accomplish your goal. The solution requires replacing bits of the software underlying the user interface with things that do what you want, instead of what Apple made them do. The consequence is that your system will no longer be quite as Apple delivered it, and there's no telling what an Apple software update will do when it encounters these modified files.
The key to solving the problem is to recognize that when you press Command-Shift-3 or Command-Shift-4, the GUI invokes a command-line application, /usr/sbin/screencapture. The easiest way to find this out is by running top at the command line and watching the process listing while taking a few screenshots. Armed with this tidbit of information, you should already begin to see the possibilities. At the command line, screencapture indicates that its options are as shown in Table 16.12. In Mac OS X versions prior to 10.4, none of these options gave any hope that you could control the file type. The -t <format> option appeared in early developer releases of Tiger, but Apple so far (as of March, 2005) admits no knowledge of how a user would control the settings for this parameter. No matter: If you have administrative access to your machine, there's hardly anything that you can't do with it if you put your mind to it.
The fact that the screen image is captured by a command-line application should immediately bring to mind a possible way that a solution might be approached. You can write small command-line programs, right? You learned how to do this in Chapter 15, when you learned about shell scripts. A shell script looks for all the world just like any other program, but you can fill it with the automated execution of any command-line commands that you want.
So, what would happen when you press Command-Shift-3, if you were to find the screencapture program as delivered by Apple, rename it so that the system couldn't find it, and then replace it with a shell script of your own devising? Presuming that you write a syntactically correct shell script, no more and no less than exactly what you put in your shell script. Let's see what happens: You'll find the screencapture program in /usr/sbin/. As root, move it to /usr/sbin/screencapture-o.
brezup:ray ray $ su Password: brezup:root ray # cd /usr/sbin brezup:root sbin # mv screencapture screencapture-o
Now replace it with a small shell script so that you can see what's being passed to the screencapture program when Command-Shift-3 and Command-Shift-4 are pressed.
brezup:root sbin # cat > screencapture #!/bin/csh echo "option 0 $0" > /tmp/screencapopts echo "option 1 $1" >> /tmp/screencapopts echo "option 2 $2" >> /tmp/screencapopts echo "option 3 $3" >> /tmp/screencapopts
Press Control-d to end the cat session; then make the new screencapture script executable.
brezup:root sbin # chmod 755 screencapture
Press Command-Shift-3 and see what happens depending on whether you're Tiger, or a previous version of Mac OS X, you'll see two slightly different behaviors:
<Command-Shift-3> brezup:root sbin # cat /tmp/screencapopts option 0 /usr/sbin/screencapture option 1 -f option 2 -tpng option 3 /Volumes/Wills_Data/ray/Desktop/Picture 1.pdf
<Command-Shift-3> brezup:root sbin # cat /tmp/screencapopts option 0 /usr/sbin/screencapture option 1 -f option 2 /Volumes/Wills_Data/ray/Desktop/Picture 1.pdf option 3
Also check Command-Shift-4 and both variants with Control held down as well (the Control variants are supposed to place the capture on the clipboard):
<Command-Shift-4> brezup:root sbin # cat /tmp/screencapopts option 0 /usr/sbin/screencapture option 1 -i option 2 -tpng option 3 /Volumes/Wills_Data/ray/Desktop/Picture 2.pdf <Command-Control-Shift-3> brezup:root sbin # cat /tmp/screencapopts option 0 /usr/sbin/screencapture option 1 -c option 2 -tpng option 3 <Command-Control-Shift-4> brezup:root sbin # cat /tmp/screencapopts option 0 /usr/sbin/screencapture option 1 -ic option 2 -tpng option 3
(Panther, and earlier-version users will see similar output, lacking the -tpng parameter.)
From these, it's clear that the options are always passed as the first parameter to the command (which is apparently what that do-nothing -f option is for filling space as parameter 1 when no real parameter is required), and the filename, if there is one, is always parameter 3 on Tiger, and parameter 2 in earlier versions. This is lucky for us. We don't need to do any fancy option parsing. So long as we can figure out how to either pass the parameters we want, instead of the hard-coded PNG format, or to convert the output of Apple's screencapture (now screencapture-o) into a friendlier file format, we can just pass options and parameters straight from our script to it, and all should be well.
If we are working with Tiger, our immediate task is now simple: how to change the -tpng parameter to something we prefer. This requires nothing more than rewriting our new screencapture script so that it calls Apple's screencapture (now screencapture-o), and passes a -t<format> option with our preferred format instead of -tpng. To summarize, the following things must be done to make a completely functional shell script wrapper for screencapture-o, which will force the output into whatever file format we prefer:
A script, stored in /usr/sbin/screencapture, such as this would do the trick:
#!/bin/csh set options="$1"; set type="-ttif"; set filename="$3"; /usr/sbin/screencapture-o $options $type "$filename"; exit
The only problem with this, is that parameter 3, the filename, is being passed in by some external process, and it's still being sent in the form of Picture #.png, rather than Picture #.<ourformat>, as we'd prefer. There are a number of ways to work around this problem. Because we're already overwriting the type information, the method that comes to mind first might be to also overwrite the supplied filename with one of our own choosing. Using what you know about shell scripting and rewriting file suffixes, you might construct a script such as this:
#!/bin/csh set options="$1"; set type="tif"; set typeoption = "-t$type"; set filebase="$3:r"; set filename="$filebase.$type"; /usr/sbin/screencapture-o $options $typeoption "$filename"; exit
This comes so close to working perfectly that it hurts. Unfortunately, whatever is passing in the filename is also what's controlling the <#> part of the Picture <#>.<format> name. It knows about only png (or in earlier Mac OS Xs, pdf) file extensions, so it picks the number for the file based on the already existing Picture <#>.png files on your Desktop, regardless of what <format> you've told screencapture to write.
All is not lost, however. There are few things a computer can do to keep a determined user from realizing his perfect configuration. If the computer won't create nice incremental numbers for us, we can always come up with ways to make our own incrementing filenames. Substituting the date for the <#> portion seems like a quick and dirty way of doing things how many times are you going to capture multiple pictures in the same second? Possibly even better, this would make all your screencapture filenames completely unique, so you'd no longer need to rename them from Picture # to something useful when you moved them off your desktop.
#!/bin/csh set options="$1"; set toldtype="$2"; if ( $%3 > 0 ) then set type="tif"; set typeoption = "-t$type"; set toldfile = "$3" set datestr=`date "+%y%m%d-%H:%M:%S"` set wrkdir = "$toldfile:h" set outfile="$wrkdir/Picture $datestr.type" \rm -f "$toldfile" /usr/sbin/screencapture-o $options $typeoption "$outfile" exit endif /usr/sbin/screencapture-o $options $toldtype exit
This might be beginning to look a little bit complex, and it contains a couple things that we haven't discussed in the text, but it's actually pretty easy to understand when broken down into parts.
To most quickly begin to understand what the script does, take the case where the if statement fails that is, when the third option, $3, contains no text (there is no filename). In this case, execution falls through to the endif statement, and the only thing executed is /usr/sbin/screencapture -o $options $typeoption. It's as if this script weren't even there, which is exactly what we want to happen. If there is no filename, it's because the user held down the Control key, and wants the data to go to the clipboard, so we don't want to fiddle with the call in any way (if you look back at the things we caught in our screencapopts experiments earlier, however, you'll see that Apple sets a file type even when sending the data to the clipboard, so this might be a parameter that could be usefully modified in some situations as well).
If there is a filename in $3, we need to process it as follows:
It's a little difficult to demonstrate in the static text of a printed book, but when installed, this works exactly as described. Using the Command-Shift-3/4 key combinations in the system now results in a file with a name such as Picture 050309-03/00/58.tif appearing on the desktop in the Finder.
If you're working in Panther or some other earlier version of Mac OS X, you don't have the option of directly controlling the file type that Apple's screencapture writes. Instead, you would need to find a way to convert the fixed format output from Apple's screencapture into whatever format you preferred. Because Tiger gives you the ability to control the format, that isn't necessary here, but the general technique is applicable to any other situation where you're hoping to more completely control the system's operation. If you find yourself in this situation, it's easy to extend the script to do internal processing on the file. Simply have screencapture write it to a temporary filename, process that file to your heart's content, and then write it to the final filename you're hoping to use. In the case of converting the old-style .pdf files from screencapture into TIFFs, this can easily be accomplished by using the Ghostscript application that was installed for supporting additional printing features. When we previously visited this software in Chapter 5, "Configuring Tiger Hardware Support and Preferences," we were interested only in using it to convert between different printer-language formats, for driving oddball, unsupported printers. In fact, it's good for converting between all manner of image file formats (examine the output of gs -h for a listing of supported output formats), and lends itself nicely to grabbing the output from screencapture and making it into whatever you prefer.
I'm partial to storing my images as TIFFs, so I'm going to use the tiff24nc output format, which is uncompressed 24-bit TIFF. Fiddling around at the command line, I find that the syntax shown in the following line, converts a .pdf file into a TIFF format file for me.
/usr/local/bin/gs -q -dBATCH -dNOPAUSE -sDEVICE=tiff24nc -sOutputFile=<tiffile> <pdffile>
brezup:root Desktop # /usr/local/bin/gs -q -dBATCH -dNOPAUSE -sDEVICE=tiff24nc-sOutputFile="Picture 1.tif" "Picture 1.pdf"
creates the file Picture 1.tif in my current directory, and it is a properly formatted TIFF file (there's a reason I've used .tif instead of .tiff, which will be explained shortly).
Even with a screencapture that insists on writing PDFs (and doesn't accept a -t parameter as option 2), the TIFF file final format version of our little hack can be accomplished with a relatively simple script, like this:
#!/bin/csh set options="$1"; if ( $%2 > 0 ) then set pdffile = "$2" set datestr=`date "+%y%m%d-%H:%M:%S"` set wrkdir = "$pdffile:h" set tmpfile="$wrkdir/.Picture $datestr.tmp" set tiffile="$wrkdir/Picture $datestr.tif" \rm -f "$pdffile" /usr/sbin/screencapture-o $options "$tmpfile" gs -q -dBATCH -dNOPAUSE -sDEVICE=tiff24nc -sOutputFile="$tiffile" "$tmpfile" \rm -f "$tmpfile" exit endif /usr/sbin/screencapture-o $options exit
Note that there's very little change from the version that uses screencapture's new -t option. This version needs only to save the output from screencapture into a temporary file (named much like the final file, only with a . preceding the name, to prevent it from appearing in the Finder), and to pass that temporary file through Ghostscript (gs) as a filter, to convert it into the TIFF file output I want. If I wanted to string together a bunch of netpbm filters to tweak the image further, send it to Mail to have it automatically emailed somewhere, or dump it to the printer so that Command-Shift-3 saved, and simultaneously printed a copy, all of this can be easily added to the script after it has the capture saved to disk.
The Brutal Way Organ Transplants
Sometimes, inserting imposters isn't a clean solution. Other times, it just can't give you all the functionality you really want. In the screencapture example given previously, the most annoying issue remaining is that the filename is a bit clunky. Apple's default "Picture #" names are elegant, if somewhat less than informative. There's no easy way to get that functionality out of a screencapture script, though, because it's actually some part of the GUI that's working out what the next available filename is, and it's doing it based on a .png (or .pdf) suffix. We could work out some csh syntax to list all .tif files and find the highest numbered instance, but that would be some ugly csh code, and Apple's already done the work, it's just not quite accessible to us. Fixing Apple's software so that it does what we want would be more elegant, but how can we do this without Apple's source code?
Remember back in the early chapters covering Unix when we said that Unix doesn't really know or care what's in a file; that if you tried to execute a datafile, Unix would let you; and that likewise you could read applications like they were giant text files with text editors? Well, emacs is your application-modifying friend.
If you dig around the /System/Library/CoreServices/ directory, you'll eventually find that SystemUIServer is the part of the system that's calling screencapture when you press the Command-Shift-3/4 key combinations. I found it by using grep from /System/Library/CoreServices/, as in grep screencapture /System/Library/CoreServices/*/*/*/* 2>&1 | grep "matches".
If you run strings on the file that matches, the following interesting tidbits show up:
brezup:root sbin # strings -3 /System/Library/CoreServices/SystemUIServer.app/Contents /MacOS/SystemUIServer . . . screen capture threw exception while handling hot key -fC -cC -ic dvderror Screen grabs are unavailable during DVD playback. dvderrormessage Please quit DVD Player first. grabicon.icns OSXDisableScreenGrab location Picture png screen capture: Unable to get directory (Desktop) to write files to. %@ %@ %@ %@(%@) %@%@.%@ ScreenCapture.m . . .
The particularly interesting bits are the Picture and png lines. There are a few other bits that look suspiciously like Apple's planning on making this an option that you can configure, but right now this appears to be bits of the SystemUIServer that specify chunks of the filename we're trying to control. If this is how SystemUIServer finds and picks names for screencapture, why not just change the contents of SystemUIServer itself?
If you're sure that you want to try this, make a backup copy of /System/Library/CoreServices/SystemUIServer.app/Contents/MacOS/SystemUIServer; then fire up emacs on the file (not the backup) and search for and change the bit of the file containing png so that it contains tif instead. I used Control-s to incrementally search for png, and found only a single instance of it in the file. I then carefully replaced just the letters png with tif, and saved the file. If you're following along, reboot your machine, and if you can still log on, things are going well.
Presuming that you've successfully made the modification, what you've just done is modify a part of the GUI server so that it no longer passes Picture #.png to screencapture. Instead, it passes Picture #.tif. Because the UI handles the collision detection and incrementing the internal number properly, we no longer need to deal with that in our screencapture script. As a matter of fact, presuming we're not trying to convert into a file format that screencapture can't write itself, we don't need our screencapture imposter script at all the system should now pick an appropriate filename, and send along the tif file type for the original screencapture just as though Apple built it to do that from the start.
Even if we're trying to do something more complicated and still require the screencapture imposter, it can be a significantly less complicated script. The SystemUIServer code will be handling collision detection and creating a good filename for us, so that's no longer necessary. All we require now is the code to do whatever additional modifications we desire on the file, and to put it in its final resting place. And, as before, it does in fact work as you might hope TIFF files, or files of whichever type you've specified in your modifications to SystemUIServer, with convenient "Picture #" names appear on the desktop in response to Command-Shift-3/4.
These examples could, of course, be made much more sophisticated if you were inclined to experiment with the shell scripts. Want Command-Shift-3 to both make a screen capture and print a copy of the file? Easy! Just send the file that screencapture-o writes off to lpr in your script. Need all of your screencaptures to be reduced to grayscale? Pipe them through some netpbm tools before writing them. The possibilities are just about limitless.
|< Day Day Up >|