Geoff Garside

Jan 17 2009

My first Nu script

I get bored, quite easily and my desktop picture is no exception to this. Thankfully in Mac OS X, I believe Windows too, you can have a rotating desktop picture. By rotating, I don’t mean spinning, rather I mean you provide a directory of images and the OS displays them in a random order. So every 5 minutes I get another image.

Occasionally it will display and image which I am very bored of, or just don’t like. In this case I then have to flip through the images in my Desktop Pictures directory to find the offending image and remove it. Every time I had to do this I was sure there was an easier way. A couple months ago I found that the ~/Library/Preferences/com.apple.desktop.plist file contained the information which I required to get the image file name, and more portably the path used for the Desktop Pictures. Originally I wrote up a small bash script to grep the information I needed from the defaults read output, but I never liked this. The reason I never liked this was that the ChangePath and LastName property list keys appeared something like three times in the file. So I was having to tail the grep output to only select the last one, as this is the one I cared about.

What I really wanted was a way to use the defaults program to access a slightly more complicated key structure than it would allow.

Along came Nu

Nu is a lisp like language written by Tim Burks which allows access to the Cocoa and Foundation API’s in a scripting like shell (through nush).

The most I’d really used Nu for at this point was just playing around with the Cocoa API’s, messing with functions and seeing how things worked. This is one of the most frequent uses I have of Nu because it lets me essentially debug interactively in a sensible way.

Given my rather amateur usage of Nu so far I wanted to try and use it in its own right. Given my issues with the defaults program for accessing complex key paths, and knowing an NSDictionary would do this easily I thought this would be a good little dip in the sparkling Nu waters.

The script below is what I came up with, it is pretty simple, and pretty self explanatory if you already know the Cocoa API’s. As a simple walkthrough though

  1. Check to see if the script was called with the “-rm” argument
    1. Set shouldOfferToRemove to true if it did, nil if not
  2. Read in the com.apple.desktop.plist file contents
  3. Get the dictionary for the Background/default key
  4. Extract the ChangePath value which is the path to the Desktop Pictures directory.
  5. Extract the LastName value which is the file name of the image file
  6. Print the name of the image file
  7. Prompt the user to delete the file if requested
    1. Delete the file if instructed

The most complicated portion of the script is the part where the response to either delete or leave the file is read.

(set input (((NSString alloc)
    initWithData:((NSFileHandle fileHandleWithStandardInput) availableData)
    encoding:NSUTF8StringEncoding)
        stringByTrimmingCharactersInSet:
            (NSCharacterSet whitespaceAndNewlineCharacterSet)))

this section could be broken down a bit to something more like the following

(set stdin (NSFileHandle fileHandleWithStandardInput))
(set inputData (stdin availableData))
(set inputString ((NSString alloc) initWithData:inputData
  encoding:NSUTF8StringEncoding))
(set trimSet (NSCharacterSet whitespaceAndNewlineCharacterSet))
(set input (inputString stringByTrimmingCharactersInSet:trimSet))

which does give a better indication of the operations being carried out, firstly a file handle to the input stream is obtained, then the availableData is read. The availableData returns whatever the user types before hitting Enter. The next couple of lines convert the NSData into an NSString and then trim any whitespace or newline characters in the string. This lets the script more easily compare the input against “y” and “Y”.

So without further ado, here is the script (also on gist)

#!/usr/bin/env nush
; Useful script for when you have a rotating background on Mac OS X
; this script will tell you which image is currently being displayed.
; If you add -rm when calling the command then it will provide you with
; prompt to let you delete the background.
;
; Installation:
;  Copy this into something like ~/bin/current_background and then
;  $ chmod 750 ~/bin/current_background
;  then use :D
;
; Usage: (assuming you call it current_background)
; Show the current background image filename
;  $ current_background
;  Current desktop image is background-1.jpg
; Show and optionally delete the current background image file
;  $ current_background -rm
;  Current desktop image is background-1.jpg
;  Are you sure you want to delete background-1.jpg [y/N]: 
;  y
;  Background background-1.jpg has been deleted
;
; Bugs:
;  1. When prompting for user input I can't seem to flush stdout manually
;     so I have to print a new line character in order to show the prompt.
;

(set processInfo (NSProcessInfo processInfo))
(set args (processInfo arguments))

(if (and (eq (args count) 3) (eq (args objectAtIndex:2) "-rm"))
    (set shouldOfferToRemove t)
    (else (set shouldOfferToRemove nil)))

;; Get the dictionary
(set plist ("~/Library/Preferences/com.apple.desktop.plist" stringByExpandingTildeInPath))
(set prefs (NSDictionary dictionaryWithContentsOfFile:plist))

;; Drill down to the dictionary key we care about
(set default ((prefs valueForKey:"Background") valueForKey:"default"))

;; Extract the fields we need
(set changePath (default valueForKey:"ChangePath"))
(set currentImage (default valueForKey:"LastName"))

;; Print the information
(puts "Current desktop image is #{currentImage}")

;; Offer to delete
(if (eq t shouldOfferToRemove)
    (print "Are you sure you want to delete #{currentImage} [y/N]: \n")
    (set input (((NSString alloc)
        initWithData:((NSFileHandle fileHandleWithStandardInput) availableData)
        encoding:NSUTF8StringEncoding)
            stringByTrimmingCharactersInSet:
                (NSCharacterSet whitespaceAndNewlineCharacterSet)))
    (if (or (input isEqualToString:"y") (input isEqualToString:"Y"))
        (set fm (NSFileManager defaultManager))
        (set path (changePath stringByAppendingPathComponent:currentImage))
        (if (fm removeFileAtPath:path handler:nil)
            (puts "Background #{currentImage} has been deleted")
            (else (puts "Background #{currentImage} could not be deleted")))
        (else
            (puts "Background #{currentImage} has not been deleted"))))
Comments
blog comments powered by Disqus