This article explains how to cycle randomly through a directory structure of graphic images and display them on your desktop background. GNOME, KDE and other X desktops/window managers are covered. This is a good exercise in shell scripting and piping, and it shows off the programs find and xargs. We also cover how to select only images of a given size, say 1024x768.
So, you have a broadband connection to the Net, and you have a lot of nice images you'd like to display in your desktop background. Because you use wget's -r option to get your images, you also have a nice directory structure mirroring your source Web sites. In fact, it would be really nice if you could cycle through them at random. You also would like to use it with any desktop or window manager that runs on XFree86. On the way, you'd like to learn a few useful shell scripting techniques.
The result is a shell script, called Wallpaper, shown in Listing 1. You can make variants, such as Wallpaper1024x768 or WallpaperAirplanes, by changing some variables.
The first thing to do in our script is to specify to the shell that this is a shell script, with the traditional "shebang", #! line. Next, include a time stamp and a brief description of what the script does.
Now you need a short function that prints out an error message. The larger a script is, the more possible errors there are, so it is a good idea to do this as a function. Then, you can call the function from any place your code might detect an error.
Now, we set up some standard variables; you probably want to change them for your computer. You also have the option to override them from the command line. $imagepath is the absolute path, the path from root, to the top of the directory tree where all our images are stored. $duration indicates how long to wait, in seconds, between slides.
$size is a regular expression, suitably escaped or modified to prevent shell expansion, used to select the size(s) of the images we are going to use. As you can see from the default, we can select multiple sizes by specifying a suitable regex. The shell uses some characters, such as open and close parentheses, the vertical bar and the ampersand, for its own purposes. By preceding each one with a backslash, we prevent the shell from interpreting those characters and force the shell to pass them intact to our program. The backslash is the shell's escape character.
The final variable sets our verbosity level. With $verbosity turned on, Wallpaper leaves a trail of the image names it displays and shows other information.
The next several lines take our command-line options (if any) and process them. We pass the built-in function getops a string that indicates which options we accept, and we indicate with a colon which ones expect a parameter. For example, the first option we accept is -d, and it accepts a parameter. We also pass to getopts the variable Option. As we process options, the contents of Option indicates which option we're working on.
The case statement processes the options. For example, the parameter of the -d option is used to set the duration, the time between images. Other parameters override the default path, size and verbosity values. You are welcome to add more options to override other variables, such as the names of the files for which we're looking.
The next bit of code tests which desktop we are using. This is important because GNOME doesn't let us use the same technique to display our wallpaper that other window managers or desktops use. This test works on Red Hat distributions, and it may work on other distributions. If it doesn't work on your computer, you may have to do some detective work. Alternatively, you could hard code it in your private copy of the script. As we see about five lines down, if the variable desktop equals 0, we're running GNOME; otherwise we're running something else.
The next three lines show some of the parameters if $verbosity is turned on, which is useful for debugging. Next, we determine if we are running GNOME. X allows programs to write directly to the background. In X nomenclature, this is the root window, and all other windows are children of the root window. As we'll see later on with the non-GNOME branch of the code, we can use any number of programs to write to the root window. But GNOME doesn't let us write to the root window, so we have to do something else.
GNOME allows us to set all sorts of characteristics by using a tree structure of variables. If you look at that structure in the program gconf-editor, it looks suspiciously like Windows' registries. Fortunately, it is implemented in a much more robust manner. The superficial resemblance allows those familiar with Windows' registries to work with GNOME's configuration. The background characteristics are set at /desktop/gnome/background/, and the two entries that interest us right now are picture_options and background.
picture_options determines the way GNOME displays the background. The value must be a text string, one of Wallpaper, Centered, Scaled, Stretched or No Picture. For our purposes, the last is useless; if picture_options is set to No Picture, GNOME can't display our pictures. For more information on what these do, see "1.2. Customizing the Desktop Background" under "Using the Basic Preference Tools" in the GNOME help. Or, use the background preferences tool (Start Here icon on your desktop --> Preferences --> Background) to experiment with them.
Fortunately, we are not restricted to a GUI tool to set options. We can use gconftool-2 to set our picture options, in this case to scaled.
The next thing we need to do is build an array in Bash, called pictures, and populate it with the files to be displayed. We use two different lines of code, depending on whether a $size has been specified. If no size is specified, we use find to search the image tree for files that fit the file specification *.jpg. We pipe the results of that to another program, called randomize. More on how randomize works comes later on; for now treat it as a black box.
If we have a size specified, things get a bit hairier. We take the output from find and pipe it to the ImageMagick tool identify. identify examines a file and returns all sorts of interesting information about it. Fortunately, we can use a format string rather like sprintf's format string to indicate exactly what information we want and how identify should show it. We specify that we want the path to it, a literal slash, followed by the file name. That is followed by a space, the width, a literal x and then the height.
But, what's this xargs thing just ahead of identify? The output from a lot of programs--ls, for example--normally is some neat display of several lines, with multiple entries per line. However, when sending to a pipe, many programs place one per line. Try ls | less to see how it works; try ls | wc to see how many lines are generated. So, if we simply feed the output from randomize to a display program, the display program sees one file name, displays it for the specified duration and then exits. For the next picture, the shell would have to reload the display program. That sounds like a lot of disk activity.
We use the program xargs to solve that problem. xargs takes whatever is piped into it and flattens it as much as possible. To see how this works, try ls | xargs echo | less; to see how many lines are generated, try ls | xargs echo | wc. The combination of find and xargs allows you to create some sophisticated file search specifications and operate on the results efficiently. Do you want to get rid of old cruft like old backups or core files? find <file specification> | xargs rm will do it. See info find for more information.
The output from identify is passed to grep, where we filter for the size we want using the regex previously specified. However, the output from grep is the complete line from identify. We're done with the size, so we need to get rid of it. To do this, we pipe the output to cut, where we select the first field (the file name), using a space as the field delimiter. That output is passed to randomize. That output is assigned to the array pictures.
Then we make a verbose announcement about what the rest of the script should do. This statement shows how to get the count of elements of an array.
After that we test to see if the array has any entries in it, again using the count of elements of the array. If there are no elements, the user probably made a mistake in specifying the image path or the size regex. If that's the case, we probably shouldn't attempt to display anything at all.
In the line that starts with for ele in..., if there are any elements, we start to walk through the array one element at a time. Everything down to the done is executed for each element, using $ele as an index into the array. If $verbosity is turned on, we show the user which file we're about to display. Then we use gconftool-2 to set the background.
The last thing in the do loop is to put the script to sleep for the specified count of time. Because we are using GNU sleep, you can specify the interval in seconds, minutes or hours. See man 1 sleep or info sleep for more information.
The code for non-GNOME desktops or window managers uses a different approach. First, we can write directly to the root window. In this case, we use John Bradley's shareware program, xv. You can substitute here any program that can take command-line arguments, write multiple images to the root window and pause between writes. The ImageMagick program display also works. If you use it, don't forget to multiply the duration by 100. See man display for details.
We first check the verbosity level to see if we should announce that we are not running GNOME. We take one of two different routes, depending on whether the variable $size is set. If no size is specified, we use find, as we did above, to locate all files that fit the file specification .jpg under the path listed in the variable $imagepath. We pipe that to randomize, which does its thing and passes the output to xv by way of xargs. We then tell xv to display each image in the root window for the specified length of time. We use a root window display mode of 4, which means that xv centers the image and tiles around it if need be.
If a size is specified, we handle it in the same way as we did for GNOME: pipe the output of find to identify with a suitable output format string and grep on the results for the size we want. We select the full path from grep's output and send the results to randomize.
randomize, Listing 2, is a Perl script. It takes a series of lines from standard in, re-orders them into a random pattern and sends them to standard out. It is based on the fisher_yates_shuffle Perl function found on page 122 of The Perl Cookbook, (see Resources).
Many Web sites use characters in file and directory names that can cause problems, such as spaces and characters with special meanings for the shell. You probably will find it easier to rename them than to provide for them in your scripts.
The result of our work is a small script that does something useful, more or less. It also shows some interesting shell scripting tricks that you can use in your own projects.
A few exercises for the student:
Add an option for selecting the file name(s) to be selected.
Extend the size selection code to select a range of sizes, say from 800 to 1024 by 600 to 768, inclusive.
Write a script to pull in an image from the Net, say from a Web cam, and display it on your background. Run it as a cron job to catch the period of the Web cam. Try Yellowstone National Park's Web cams for starters (see Resources).
Write a shell script to detect problematic file names and fix them. Hint: man rename.
Penguin Computing's wallpaper gallery, just the thing when your colleagues want help with that other operating system.
Perl Cookbook, 2nd Edition. Every Perl hacker should have a copy.
Charles Curley lives in Wyoming, where he has deer, buffalo, archaeologists, hot springs and the Wind River Canyon for neighbors.