Wallpaper Rotation on Ubuntu using Ruby and Flickr

Wallpaper Rotation on Ubuntu using Ruby and Flickr

By Filip van Laenen

Overload, 21(115):14-20, June 2013


Repetitive tasks are ideal candidates for scripting. Filip van Laenen walks us through a simple example.

One of the benefits of being a programmer is that you can set up your computer to do things just the way you want, even if there’s no program for it. I once wrote a podcatcher that downloads and manipulates podcasts so that I can listen to them in the right order. One reason for doing that was that I wanted to learn more about the technology involved, but I also felt that the podcatching programs that I had tried out didn’t really do what I wanted them to do. May’s issue of C Vu [ vanLaenen13 ] contains another example: a script to back up my computer files just the way I want it. In this article, I’ll explain a little program [ WRUF ] that I wrote to rotate the wallpaper on my Ubuntu laptop because I didn’t feel other wallpaper rotation tools did what I wanted them to do.

The requirements

Let’s start by sketching out the requirements for our little program. Basically, what I wanted was something that could change the wallpaper on my laptop once in a while. If possible, the wallpaper should be decorated with a small calendar, and some information about what the picture is about and where it comes from. An obvious choice to look for interesting pictures is Flickr [ Flickr ], a photo sharing website that I use to share my personal pictures with family and friends.

There are of course many other sources one could use to fetch interesting pictures from. Competitors of Flickr like Instagram and Picasa spring to mind, but also NASA’s picture of the day, or press agencies like Reuters are good sources of pictures that could serve as wallpaper.

Flickr REST API

Flickr provides a REST API [ FlickrAPI ] through which you can search for pictures using keywords, one of the search modes being searching for the most ‘interesting’ pictures [ Google ]. You can use the same REST API to fetch pictures from groups, your private photostream, your favorites, or the photostream of friends and family. If you authenticate yourself, you can also use the same API to fetch private pictures, update information or upload pictures.

If you’re interested in learning more about REST and how you can document a REST API, I think the Flickr REST API website is a great place to start. I think the format they use works really well, and when you browse through the documentation, you’ll get a lot of inspiration about how resources and parameters should be named, default values, etc.

Accessing Flickr from Ruby

If you want to access Flickr from Ruby, there are basically two alternatives. One alternative is to use the REST API directly, but it’s also possible to use one of the many specialized libraries (or gems in Ruby-speak). Both have advantages and drawbacks. If you only need to access a very limited set of services (resources) of the Flickr REST API, and you’re not already used to using one of the Ruby Flickr libraries, accessing the Flickr REST API directly is not a bad choice. Just finding out which library is the right one for your project, e.g. based on activity in the project, the documentation and the API, may in itself take more time than implementing a simple REST call or two. On the other hand, if you’re going to access many of the Flickr REST API resources, using one of the libraries may be a better idea.

In the case of WRUF, I chose to access the Flickr REST API directly in a class called FlickrSearcher . Listing 1 shows the class’s method that deals with executing a basic REST call and returning its result. Accessing a Flickr REST API resource is then as simple as building up the correct form data, invoking the do_rest_request method, and filtering the data we’re interested in from the result that’s returned by the method.

FlickRestServicesUri = 
   'http://api.flickr.com/services/rest/'
def do_rest_request(form_data)
  uri = URI.parse(FlickRestServicesUri)
  http = Net::HTTP.new(uri.host, uri.port)
  request = Net::HTTP::Get.new(uri.path)
  request.set_form_data(form_data)
  request = Net::HTTP::Get.new(uri.path + '?' +
     request.body)
  response = http.request(request)
  case response 
  when Net::HTTPSuccess, Net::HTTPRedirection
    return REXML::Document.new(response.body)
  else
    raise "An error occured while trying to access Flickr."
  end
end
			
Listing 1

Listing 2 shows how we build up the form data in order to search for an interesting picture. [ Flickr2 ] The first parameter we have to set is the method name parameter, which is the search method in this case. Next, we add the API key for our application. Flickr uses this key to keep track of the applications that use its API (and probably also to blacklist you if you don't behave properly). Applying for a key doesn’t take much time, and is free as long as you’re not going to use it for commercial activities. [ FlickrKey ]

PhotosSearchMethod = 'flickr.photos.search'
ApiKey = <Your Application's API Key>

def create_form_data_to_search_photos(tags, i)
  form_data = {'method' => PhotosSearchMethod,
           'api_key' => ApiKey,
           'extras' => 'o_dims,original_format',
           'format' => 'rest',
           'media' => 'photos',
           'page' => i.to_s,
           'safe_search' => '1',
           'sort' => 'interestingness-desc',
           'tag_mode' => 'any'}
  if (tags != nil)
    form_data['tags'] = tags.join(',')
  end
  return form_data
end
			
Listing 2

The search method doesn’t require any authentication, so we’re not adding our user_id or any other authentication information. The rest of the form data then controls how the search is performed. First, the extras parameter lists the additional information we’d like to see included in the search result. We need the original dimensions of the picture in order to filter the ones that are too small, and in addition we need to know the original format (typically JPEG) in order to build up the picture URL correctly. We specify REST as the format for the response in the format parameter, so that we can use XPath to extract information from the search result. Media is set to photos, so we don’t get any videos in our search result. The page parameter is used to specify the search result page we want to return. The parameter safe_search is set to 1 , in order to filter out pictures that would be ‘too interesting’. Notice that since we call the search method unauthenticated, search results will already be filtered to be safe, so this is just a precaution. The sort parameter is set to descending by interestingness, so we get the most interesting pictures first. Finally, if the method is called with a set of tags, we add them as a comma-separated list. Notice that tags that are prefixed with a minus sign ( - ) will be used to exclude matches from the result. We also set the tag mode to any , which results in an OR combination of the tags (the default). If you want an AND combination, you have to set this parameter to all .

Framing a picture

Unless we’ve chosen some very particular keywords, a search on Flickr will return a vast number of photos. Of course, these photos will have a wide range of dimensions, and not all of them will fit the desired desktop size. How do we select the photos that do fit?

In plain words, the rule to select photos is not so difficult. First of all, the photo should be larger than the desktop size in absolute terms. Furthermore, the ratio between height and width for the photo and the desktop should be equal within a given margin of tolerance. Finally, the photo should be one that we haven’t used before.

Listing 3 shows how this selection process is implemented in WRUF. One of the great things about Ruby is that it has blocks (and lambda expressions). These blocks can be used as parameters, and probably the most common way to use them is in the API for collections. In this listing, blocks are used as a parameter for the methods select and reject. They are used by these methods to filter the initial collection of search results down to a collection of photos that can be used as wallpapers. Have a look at the first call, which says that only those elements ( e ) should be retained ( select ed) which have an attribute called o_width that when converted to an integer is larger or equal to our desired field width. Notice also that the select method returns the resulting collection, so that we can chain all calls together without having to assign and reassign to a local variable.

def get_photo_info(info_set, history)
  return info_set.get_elements ('rsp/photos/photo') \
   .select{|e| e.attributes['o_width'].to_i >= @width} \
   .select{|e| e.attributes['o_height'].to_i >= @height} \
   .select{|e| (e.attributes['o_height'].to_f / \
               e.attributes['o_width'].to_f) / \
               (@height.to_f / @width.to_f) < \
               1.to_f + @tolerance} \
   .select{|e| (@height.to_f / @width.to_f) / \
               (e.attributes['o_height'].to_f / \
               e.attributes['o_width'].to_f) < \
               1.to_f + @tolerance} \
   .reject{|e| history.include?(get_photo_url(e))} \
   .first
end
			
Listing 3

In order to get the initial collection, we use an XPath expression on the XML object that was returned by the Flickr search method call. And once we’ve narrowed the collection of photos down to the ones that can be used as a wallpaper, we simply call first to return the first element. If the resulting collection turns out to be empty, the method will return null , and a new call to the Flickr search method should be issued in order to get the next search page.

I’m sure there are more efficient ways to filter out the photos with the right ratio than the two-pass filtering I use. However, performance hasn’t been an issue yet, so I haven’t cared to look into it more deeply. Considering that computers are terribly fast at the simple arithmetic involved in the calculations in Listing 3, that the program will run in the background anyway, and that it won’t do the calculations for more than a couple of hundreds, or at worst a couple of thousands photos once a day or so, just writing the lines in this paragraph probably cost me more time than I’ll ever be able to save.

Keeping track of history

Talking about time, our little program also keeps track of history. In our case, it’s sufficient to store the URLs of all the photos we’ve used as wallpaper so far, and match any potential wallpaper candidates against the list. If we’re going to switch wallpaper only once a day, the list won’t be longer than a couple of hundred URLs in the course of a year. We can therefore store the URLs in a simple flat file, one URL on every line. Adding a URL to the history file is then as simple as appending it to the end. Reading the history file is simple too: just create an empty array, and add every line as a new element to it.

There are of course alternatives to storing the URLs in a flat file. One option would have been to use an XML file, but that would only have made sense if the data structure would have been more complicated. The same is true for storing the URLs in a database, but in addition to that, using a database would have added a dependency to the system, and complicated matters substantially. Not using a database at all is a big feature, especially at installation time.

At the same time, it should be noted that our particular requirement for handling history –never ever reuse a photo as a wallpaper– simplified matters substantially. Other users may prefer to be able to reuse photos as wallpaper after a certain number of days. I’ve found out that as long as your keywords are ‘normal’, Flickr has such a vast amount of interesting photos that there’s really no need to reuse any of them. You could probably change wallpaper every hour or every minute, and there would still be enough photos to chose from.

Decorating a picture through SVG

Before I set a photo as a wallpaper, I would like to decorate it with its title, its author, its URL, and a little calendar. Title, author and URL are useful in case I want to look up the photo on the internet (e.g. because I like it and would like to favorite it in Flickr). Of maybe I just want to see what the picture is about and who created it. The calendar is more of a gimmick, but I like to have it on my wallpaper for quick reference.

The strategy I chose to decorate the photo is to include it in an SVG image, and add the texts on top of it. SVG [ SVG ] stands for Scalable Vector Graphics, an XML format to define, well, vector graphics. Listing 4 shows how an SVG file to decorate a wallpaper photo typically looks like. Let’s walk through it.

<?xml version='1.0' standalone='no'?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg height='768' version='1.1' width='1366' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg'>
  <image height='911' xlink:href='0ac9b2fc1f8ab4b73b26608259e11683a03f90dd.jpg' width='1366.0' x='-0.0' y='-71'/>
  <g id='photo_info'>
    <text fill='#FFCC11' font-family='Ubuntu' font-size='16' font-weight='bold' x='68' y='649'>Man Made Cascade, Virginia Water</text>
    <text fill='#FFCC11' font-family='Ubuntu' font-size='12' x='68' y='673'>flatworldsedge @ Flickr</text>
    <text fill='#FFCC11' font-family='Ubuntu' font-size='12' x='68' y='691'>http://www.flickr.com/photos/flatworldsedge/5252778530/</text>
  </g>
  <g font-family='Ubuntu' font-size='32' font-weight='bold' id='calendar' text-anchor='middle' transform='translate(1297,76)'>
    <g id='last_week' opacity='0.2'>
      <text fill='#FFCC11' x='-268' y='0'>29</text>
      <text fill='#FFCC11' x='-224' y='0'>30</text>
      <text fill='#FFCC11' x='-179' y='0'>1</text>
      <text fill='#FFCC11' x='-134' y='0'>2</text>
      <text fill='#FFCC11' x='-89' y='0'>3</text>
      <text fill='#FFCC11' x='-44' y='0'>4</text>
      <text fill='#FF0000' x='0' y='0'>5</text>
    </g>
    <g id='this_week'>
      <text fill='#FFCC11' opacity='0.2' x='-268' y='44'>6</text>
      <text fill='#FFCC11' opacity='0.2' x='-224' y='44'>7</text>
      <text fill='#FFCC11' opacity='0.2' x='-179' y='44'>8</text>
      <text fill='#FFCC11' opacity='1' x='-134' y='44'>9</text>
      <text fill='#FFCC11' opacity='0.5' x='-89' y='44'>10</text>
      <text fill='#FFCC11' opacity='0.5' x='-44' y='44'>11</text>
      <text fill='#FF0000' opacity='0.5' x='0' y='44'>12</text>
    </g>
    <g id='next_two_weeks' opacity='0.5'>
      <text fill='#FFCC11' x='-268' y='89'>13</text>
      …
      <text fill='#FFCC11' x='-44' y='134'>25</text>
      <text fill='#FF0000' x='0' y='134'>26</text>
    </g>
  </g>
</svg>
			
Listing 4

The file starts with an XML header, defining it as an SVG 1.1 document. The svg element is the root element, and it also sets the dimensions of the image as 768 × 1366. In SVG, elements are drawn on top of each other in the same order as they appear in the XML document, so the first thing we want to draw is the photo. The image element does just that, referring to the file name where the photo can be found. The photo is scaled proportionally so that either the height or the width match the size of the screen, with the non-matching dimension being slightly larger. Using the x and y attributes, the photo is also the positioned such that its middle will be in the middle of the SVG image, and therefore also of the screen.

Notice that the name of the photo file isn’t the real name of the photo as it can be found on Flickr, but a SHA-1 digest of its URL. There are two reasons for doing so. First of all, since the URLs will be unique, this will result in unique file names. In addition to that, there won’t be any issues with problematic characters, since the file name will consist of hexadecimal characters only.

Groups of elements can be defined using the g element. This SVG file has five of them, and as Listing 4 illustrates, groups can be nested. Besides having an id to identify them, groups can be used to set common attributes to all its elements in a DRY-fashion. Of course, these attributes will only apply to those elements in the group for which they are relevant.

The first group prints some information about the photo on the wallpaper image, using text elements. Notice that the fill and font-family attributes could have been defined on the g element instead of on each text element. Consider it a bit of technical debt, a consequence of my experimenting with different colors and fonts when I was writing the program. The attributes on the text elements are pretty self-explanatory: font-family , font-size and font-weight define the family, the size and the weight of the font to be used, and x and y where the text should be positioned. The fill attribute sets the color of text. Finding the right colour turned out to be a bit of a challenge though.

I don’t know in advance what will be the dominant colour of the photo, and I don’t know of a method to inspect the photo in that sense either. (I’m sure there exist tools for that, and using one of them would be an obvious nice feature to add when I have more time.) I therefore had to pick a colour that would work well in most occasions, and found out that FFCC11, a colour close to gold (FFD700), was a good choice. An initial thought was to use a contrasting stroke too (e.g. black stroke with yellow fill), but that turned out not to work. The text is too small in order for the stroke to have a good effect. A better alternative would probably have been to put a semi-transparent rectangle behind the text, but since that would have hidden part of the photo too, I didn’t want to do that. Besides, using a semi-transparent rectangle wouldn’t have worked for the calendar anyway. The calendar is too big, so it would have hidden a rather large part of the photo.

My calendar consists of the current week, last week, and the next two weeks – four weeks in total. Days in the past are made almost completely transparent (opacity 20%), and days in the future half-transparent (opacity 50%). Weekdays, including Saturdays, have the same colour as the other texts, but Sundays are marked in red (FF0000). An obvious improvement would be to mark bank holidays in red too. Notice that for the calendar, I did use group attributes to set common attributes across all elements. In addition to that, I used the transform attribute to translate the calendar to the right place in the image. Alternatively I could have added the two numbers to the x and y coordinated of every text element, but I think my solution makes it more clear where which element of the calendar goes.

It should be noted that I chose to generate the content of the SVG file as an XML document using REXML::Document . Just as there exist specialized libraries to access Flickr, there exist specialized libraries to create SVG files. My feeling is that it’s easier to create SVG files through XML, as it gives you full flexibility and you need to know SVG anyway to use the SVG libraries. Your mileage may of course vary…

Converting SVG to PNG

One drawback of using SVG to decorate the wallpaper is that it has to be converted back to JPG or PNG. Decorating the JPG photo directly would probably have been the most elegant solution. An alternative approach would have been to convert the photo to PNG, and do the decorating in PNG. But that would have involved a conversion too, so it probably wouldn’t have saved us much compared to using SVG. In either case, I didn’t find a Ruby library to draw text directly on JPG or PNG images, so that’s also a reason why I used SVG.

The conversion from SVG to PNG is the part that I’m the least satisfied with in WRUF. I never managed to find a good Ruby library that could do the job, so I had no choice but to make a system call to rsvg-convert. Rsvg-convert is one of the tools provided by the librsvg2-bin package, and it can convert SVG images into PNG raster images.

The conversion tool works fine, and the system call to rsvg-convert in itself isn’t a problem either. But if I would like to port the program to another operating system in the future, this will be one of the issues. Preferably I should migrate to a Ruby library that can convert SVG images into PNG, the alternative being to find similar tool in the target operating system and make a system call to that.

Setting the wallpaper

Setting the resulting image as the current wallpaper is done through a system call too, and will therefore have to be adjusted to specific operating systems too. But contrary to the conversion of SVG images into PNG images, this is something that can be expected. Even within the same operating system there may be differences from one version to another, as I discovered myself. Ubuntu 11.10 and newer versions use e.g. gsettings set org.gnome.desktop.background to set the wallpaper, whereas earlier versions used gconftool-2.

Command-line user interface

So far we’ve described how the main program works, but now we still have to get it started. This is done from a Shell script that calls the main Ruby program. But the Shell script can do more than just calling the main program: it can also start the initialization, or print some help text, version, copyright and warranty information. But let’s start with just running the program.

There are basically two ways I want to start the program from: manually from the command-line, or automatically from Cron (e.g. every hour). I have therefore linked /usr/bin/wruf to wherever the main Shell script resides, so that I don’t need to remember where I’ve put it. Running the program is therefore as simple as typing wruf run on the command-line, or adding a line with a call to /usr/bin/wruf run to crontab. It is then the task of the Shell script to find out what all the local directories are, and to call the Ruby program with the correct parameters. Listing 5 shows most of the main Shell script.

#!/bin/sh
# (Header with copyright information omitted.)

ACTION="$1" 

export WRUFDIR="/opt/wruf" 
export LOCALWRUFDIR="${HOME}/.wruf" 
export RUBY="ruby" 

# (Some magic to make WRUF run from Cron 
# omitted.)
VERSION="1.1a1" 
COPYRIGHTYEAR="2011" 

case "$ACTION" in 
  init) 
    ${WRUFDIR}/wruf_init.sh 
    ;; 
  run) 
    ${WRUFDIR}/wruf_run.sh 
    ;; 
  tags) 
    ${WRUFDIR}/wruf_tags.sh 
    ;; 
  current) 
    ${WRUFDIR}/wruf_current.sh $2 
    ;; 
  help) 
    echo "Wallpaper Rotator Using Flickr (WRUF) v${VERSION}" 
    echo "Copyright © ${COPYRIGHTYEAR} Filip van Laenen <f.a.vanlaenen@ieee.org>" 
    echo 
    echo "Usage:" 
    echo "  wruf action [parameters]" 
    echo 
    echo "where actions and parameters include:" 
    echo "  init              initialize WRUF" 
    echo "  run               run WRUF" 
    echo "  tags              manage the tags used by WRUF in an interactive dialogue" 
    echo "  current dislike   rotate the wallpaper regardless of when it was rotated last" 
    echo "  help              show this message" 
    echo "  version           show the version information" 
    echo "  copyright         show the copyright information" 
    echo "  warranty          show the warranty information" 
    ;; 
  version) 
    echo "Wallpaper Rotator Using Flickr (WRUF) v${VERSION}" 
    echo "Copyright © ${COPYRIGHTYEAR} Filip van Laenen <f.a.vanlaenen@ieee.org>" 
    echo "This program comes with ABSOLUTELY NO WARRANTY; for details run 'wruf warranty'." 
    echo "This is free software, and you are welcome to redistribute it" 
    echo "under certain conditions; run 'wruf copyright' for details." 
    ;; 
  copyright) 
    echo "Wallpaper Rotator Using Flickr (WRUF) v${VERSION}" 
    echo "Copyright © ${COPYRIGHTYEAR} Filip van Laenen <f.a.vanlaenen@ieee.org>" 
    echo 
    echo "This program is free software: you can redistribute it and/or modify" 
    echo "it under the terms of the GNU General Public License as published by" 
    echo "the Free Software Foundation, either version 3 of the License, or" 
    echo "(at your option) any later version." 
    echo 
    echo "This program is distributed in the hope that it will be useful," 
    echo "but WITHOUT ANY WARRANTY; without even the implied warranty of" 
    echo "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the" 
    echo "GNU General Public License for more details." 
    echo 
    echo "You should have received a copy of the GNU General Public License" 
    echo "along with this program.  If not, see <http://www.gnu.org/licenses/>." 
    ;; 
  warranty) 
    echo "Wallpaper Rotator Using Flickr (WRUF) v${VERSION}" 
    echo "Copyright © ${COPYRIGHTYEAR} Filip van Laenen <f.a.vanlaenen@ieee.org>" 
    echo 
    echo "There is no warranty for the program, to the extent permitted by applicable law." 
# (The rest of the warranty text omitted.)
    ;; 
  *)
    echo "Wallpaper Rotator Using Flickr (WRUF) v${VERSION}" 
    echo "Copyright © ${COPYRIGHTYEAR} Filip van Laenen <f.a.vanlaenen@ieee.org>"
    echo 
    echo "Usage: wruf {init|run|tags|current|help|version|warranty|copyright}" >&2 
    echo "Type 'wruf help' to get more information." 
    exit 1 
    ;; 
esac 
			
Listing 5

First, the script saves the first parameter as ACTION . Then it stores where it expects the local directories to be in some local variables, and how to call Ruby. After that, it does some magic to make WRUF run from Cron. As it turns out, setting the wallpaper doesn't work just like that if you run the program in the background. Finally, it sets the version number and the copyright year to be used in the various messages further down.

The rest of the Shell script is a case statement on the ACTION variable. For every action, it either calls another Shell script to perform a specific task, or prints out some text. I suppose the help, version, copyright and warranty information could have been printed out by the main Ruby program too, but it seems like overkill to start a Ruby program just to print out some text. A big argument against putting this in the Shell script is that it makes the whole program more dependent on Shell scripting. This may again make it harder to port WRUF to a different operating system. A big argument in favour of it is that this way, printing out the help message doesn’t depend on having the local directories initialized correctly, or even having Ruby installed.

When I wrote the program, I was a bit surprised that I needed both a warranty and a license text—I thought a license text was all I needed. But when you think of it, the purpose of the license text is to handle how the program can be used and reused by others. The warranty, however, makes sure that nobody can come after me if my program decides to delete a user’s disk. If you want to open source your code, you should probably have both.

YAML ain’t markup language

Now that we have a running program, we still need to initialize it. In order to run the program properly we need to know the dimensions of the screen we’re going to produce wallpaper for, the tolerance for how much the dimensions of a picture can deviate, the minimum number of hours between the rotation of the wallpaper, and the tags WRUF should use when searching on Flickr.

Notice that WRUF keeps control over when it’s time to rotate the wallpaper. Since I don’t keep my laptop running the whole day, I can’t set up a Cron job at a specific hour to rotate the wallpaper once a day. Instead, I have a Cron job that runs WRUF once every hour, so that it rotates the wallpaper whenever time’s up. This also means that I can run WRUF at start-up, without it causing the wallpaper to be rotated a second or even a third time during the same day just because I had to reboot. On the other hand, sometimes you’ll want to change the wallpaper immediately, e.g. because WRUF happened to pick a photo you don’t like. This is why I needed to implement the current dislike function too, as mentioned in the help text in Listing 5, in addition to run .

In order to keep things simple, I use YAML [ YAML ] to store the settings in a settings file. YAML is “ a human friendly data serialization standard for all programming languages ”, as the official YAML Web Site defines it. Human friendliness is not a big issue for using YAML in this case, even though it’s always nice to be able to inspect what’s stored in the settings file during development or debugging. The biggest reason for using it in WRUF is that it has been included in the standard library for Ruby since version 1.8, and the API for using it is very compact and easy to understand. Listing 6 shows how the settings file is stored and read, together with an example of how such a settings file looks.

# Storing the settings file using a 
# settings object:
open(file_name, "w") { |file|
  file.write(settings.to_yaml)
}
# Reading the settings file into a settings object:
settings = YAML::load(read_file(file_name))
# Sample settings file:
--- !ruby/object:WrufSettings
dimensions:
- 1366
- 768
hours: 18
tags:
- landscape
- forest
- sea
- mountain
- mountains
- river
- clouds
tolerance: 0.25
			
Listing 6

The method to_yaml converts a Ruby object into a YAML string, which can then be stored directly in a file. The load method from the YAML module does the reverse: it creates a Ruby object from a YAML string or an IO stream. The sample settings file shows how primitive attributes (the hours, an integer, and the tolerance, a float) are stored, but also arrays (the dimensions and the tags).

Installation

Finally some words on installing and creating an installation script for WRUF. Since this program is relatively simple, I simply pack everything that’s needed to run WRUF together in a tar-file. Right now, this includes the Ruby files, the Shell scripts, and the license text. Installation is then as simple as unpacking the tar-file, and then running the installation script.

The installation script first deletes the WRUF installation directory if it already exists, and then makes a clean copy of all Ruby files and Shell scripts. Then it makes the Shell scripts executable, and links /usr/bin/wruf to the main Shell script. At the end, it also installs the Log4r gem if it’s not already installed.

I chose to use /opt/wruf as the WRUF installation directory. According to the Linux Filesystem Hierarchy [ Linux ], “ [t]his directory is reserved for all the software and add-on packages that are not part of the default installation .” This makes WRUF system-wide available, but it also requires that the person installing WRUF has administrator access rights (and uses e.g. sudo to install the program). User settings, history and cached wallpaper photos and files are then stored in ${HOME}/.wruf , so that each user can have his own set of tags and other settings.

Feature backlog

Just like most hobby projects, WRUF is not complete, and it will probably never be. I already mentioned that there are lots of other sources of good photos that could be used as wallpaper, but for now, WRUF only searches through Flickr. But there are other alternatives too, like using local photos, or even creating random drawings in SVG. Other features that could be added include ‘liking’ the current wallpaper, e.g. by adding the photo to the user’s favorites in Flickr, putting other information on the wallpaper, like geo-information, updating the calendar regardless of whether the source photo should be rotated or not, or using dynamic tags like the current month or season. Auto-detection of the current desktop size would also be nice.

Creating a wallpaper rotator for Ubuntu wasn’t a difficult task, but it involved many different technologies. First of all, we needed REST to access Flickr and find a good background photo for the wallpaper. Then we used SVG to decorate the photo, and converted it to PNG. In order to do the conversion, we had to make a system call from Ruby, just like for setting the resulting image as the new wallpaper. We kept track of history through a simple text file, and stored the settings using YAML. Finally, the core program was written in Ruby, with some Shell scripts on top of it to get it running and installed.

The program has been running on my laptop for more than a year now, and it has been working fine for me. Often it fetches great photos from Flickr, taken at amazing places like the Denali National Park & Reserve in Alaska. WRUF wasn’t only a good way to explore some interesting technologies, but also to see some of the most interesting photos on Flickr, and to learn about the extraordinary places where they’ve been taken.

References

[Flickr] See http://www.flickr.com

[Flickr2] For a detailed overview of the parameters that can be set, see http://www.flickr.com/services/api/flickr.photos.search.html

[FlickrAPI] See http://www.flickr.com/services/api/ for the Flickr API.

[FlickrKey] See http://www.flickr.com/services/api/misc.api_keys.html for more information about Flickr API keys and how to apply for one.

[Google] Patent submitted as United States Patent Application 20060242139 and at the time of writing still pending. http://www.google.com/patents/US20060242139

[Linux] See http://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/

[SVG] See http://www.w3.org/Graphics/SVG/ for more information about SVG, including the standards

[vanLaenen13] ‘Tar-based Back-ups’ C Vu Vol 25 Issue 2, May 2013.

[WRUF] The source code can be downloaded from https://github.com/filipvanlaenen/wruf . Feel free to copy the code and create your own branch as long as you respect the software license.

[YAML] See http://yaml.org/ and http://en.wikipedia.org/wiki/YAML for more information about YAML.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.