Posts Tagged ‘Code’

(on Technorati ,

Script: Wireless Strength Polling/Logging/Graphing

Initially, I wrote this script to give me frequent feedback on the signal strength. This is useful when adjusting antennas to that sweet spot that give stronger signals; especially if you’re testing some homemade tinfoil parabolic reflectors! 😉 If you have a portable wireless device, like a netbook, you can ssh into your (wireless) desktop and run wireless-strength to get realtime feedback on adjustments to the access point’s antenna… assuming the connection doesn’t break. 😛 And, of course, you can just walk around running it on your mobile device to create a kind of wireless heatmap.

If you have gnuplot, you can also generate graphs from the data with ws-plot. This is me walking around my house with my notebook:

I started in my room (40% 🙁 ), which is where the first peak is – near the window. Left my room, back to 40%, peak near window again, then bathroom… 40%. The climb from 40-80% is me walking towards the TV room (PS3 lives in a solid 80% zone, at least!). Walked upstairs, got 100% in most areas (that’s where the Access Point is) – tried a bedroom, dropped to 40%.

Download the scripts and get more details here:

Nerdy Ramblings

This script originated years back, but I recently tried using it on my laptop and it didn’t work! Unacceptable! The original parsed the output of iwconfig. But what showed up as “Quality=30/70” on my desktop, would show up as “Quality:4” on my laptop. COMPLETELY DIFFERENT. Even the character after “Quality” was different! o.O The output of iwconfig is driver dependent, which is why I looked to network-manager. I figured there’s probably a nice dbus command I can send to network-manager for that purpose – but I got tired of looking and decided to just parse output again. :/ Luckily, network-manager includes nm-tool. It’s certainly not a clean solution, but it works for now.

I also used updating rewriting the script as an excuse to get better acquainted with git – which I’m really liking.

Every time I do a bash script, I vow to do the next script in Python. I like Python and I don’t like Bash… but there’s a certain… nativeness or dependency-free elegance to bash scripts. Still, I hate writing them, and the next script’s in Python! 😛

File List Applet – GNOME Panel Applet

This is kind of a proof of concept I’ve been playing with. The idea is that finding a file within a folder is often easier by type, and you are often only interested in the most recently modified file. The problem with a file manager is that although you can easily sort by either type or modification time, you cannot filter your view of all the other files you’re not interested in. I previously wanted to address this issue within Nautilus, (and I still believe this functionality would be wonderful in Nautilus), but I ended up doing this much less ambitious applet as a proof of concept.

This applet will let you add any number of folders to it, and will try to categorize the files automatically and intelligently. Currently, it’s more automatic than intelligent as it just looks at the mime-type. Even so, I’ve found it especially useful for keeping track of all my downloads:

Steven is catching up on the latest on and has downloaded a couple screencasts demoing the latest and greatest. These files are typically 2-10 megabytes, so they didn’t download instantly. Steven continues reading and forgot about the screencasts until a couple hours later. At that time, he can simply click on the File List Applet, select Downloads, select Video, and look at the top of the list for the newest files. Steven is happy. When finished, he can follow the same process to delete them – without once opening his file manager and being assaulted with ALL the files in his Downloads folder.

Ultimately, I would like to extend the idea to provide the same type/subtype menu system for all files under all folders – a type of summary – but I have not implemented that yet. There are other features in the cooking pot, as well, but I have to get started on some “RL” tasks… like my resume. 🙂


I had a problem recording audio, so I ended up typing as narration. Unfortunately, this makes the YouTube one pretty unwatchable, but you can give the “HQ” version a try.


No tarball yet as it’s still extremely rough.
Browse the source here.
Branch the source: bzr branch


Update 2009/04/05: Updated install instructions here. (Some people don’t look at the comments….)

Installing will require some manual modifications.

  1. First, make sure you have the following packages (Ubuntu): python-xdg, python-gnome2-desktop, python-gtk2, python-pyinotify
  2. Then branch the source.
  3. Adjust the FileListApplet.server file’s location to wherever you keep it.
  4. Then copy FileListApplet.server to /usr/lib/bonobo/servers/.
  5. Restart the bonobo-activation-server. killall bonobo-activation-server
  6. Add it to the panel like other applets.

Update 2008/12/05: Added a couple screenshots.

open-with for the command-line

Update 2008/11/18: Use xargs 😛

Here’s a bash script that you can pipe output into and tell it to run a specific program with the output as arguments. I’ve named it open-with and placed it in my personal script directory: /home/steve/bin/. Look within the script at a couple of the examples for how to use it.

PROG=`basename $0`
DESC="Read arguments from standard input and run a specified program
with them.  Meant to be used as output for a pipe."
# List (with details), all the files that include \"downloads\" in their name:
  find /home/ -iname \"*downloads*\" | $PROG \"ls -l\"
# Queue all AVI files in current directory in vlc:
  ls *.avi | $PROG vlc
# View all time-related icons with eye-of-gnome:
  find /usr/share/icons/gnome/ -iregex \".*[^mul]time.*\" | open-with eog
function Usage() {
  echo "DESCRIPTION: $DESC" ; echo
  echo "USAGE: $USAGE" ; echo
# Quick check to see it's being called correctly, if not, print Usage and exit
if [ ${#@} -ne 1 ]
files=()						# empty array
while read -r					# read from stdin
	files+=( "$REPLY" )			# add result of read to array
# assume $1 is a valid program
$1 "${files[@]}"				# pass arguments to specified program


You’re sitting at the command line and have a list of images (in different locations) that you would like to browse. Most image viewers let you iterate over a set of images, but only within the same directory. What would be great is if they accepted input from stdin through a pipe!

$ cat my_list_of_images | my_image_viewer

I didn’t find anything that did that, but using the open-with script, you can do something similar:

$ cat my_list_of_images | open-with my_image_viewer

Browsing a select list of images is actually kind of nice. It’s like a playlist for your image viewer – a viewlist. 🙂 Anyway, I’m sure there’s some problems with this script. Feel free to provide suggestions in the comments. But I certainly don’t want to look at it for a while….

I hate shell scripting

With a passion. It’s not a great surprise… many programmers do. I’m willing to go on record and state that I hate it even more than PERL programming. I rarely do it, and when I decide to try something that seems like it would be simple, it turns out taking forever due mostly to quirks. The rest of this post is a bit about how I went about writing this script, which ended up being mostly given to me by some #bash gurus on IRC. And it’s a bit of a rant.

Although not many GUI programs seem to accept stdin as input, most accept filenames as arguments:

$ eog image1.jpg image2.jpg image3.jpg "/home/steve/image seven.jpg"

So I figured I would just have to convert the list of images into an acceptable format: quoted, absolute filenames, separated by space. Doing this depends entirely on the format the list is currently in, but it’s likely a list of unquoted filenames separated by newlines:

/home/steve/my images/wow.jpg

For me, my test list looked like this:


I was looking for icons of clocks or representations of time, and I obtained that list using find:

$ find /usr/share/icons/gnome/ -iregex ".*[^mul]time.*"

So I can’t pipe it into my image viewer, but I can use the output as the command-line arguments if I change the newlines to spaces. find has an option for formatting the output which is perfect:

$ find /usr/share/icons/gnome/ -iregex ".*[^mul]time.*" -printf "'%p' "
'/usr/share/icons/gnome/48x48/stock/generic/stock_timezone.png' '/usr/share/icons/gnome/16x16/stock/generic/stock_timezone.png' '/usr/share/icons/gnome/16x16/stock/generic/stock_timer.png' '/usr/share/icons/gnome/16x16/stock/generic/stock_timer_stopped.png' '/usr/share/icons/gnome/16x16/stock/form/stock_form-time-field.png' '/usr/share/icons/gnome/24x24/stock/generic/stock_timezone.png' '/usr/share/icons/gnome/24x24/stock/generic/stock_timer.png' '/usr/share/icons/gnome/24x24/stock/generic/stock_timer_stopped.png' '/usr/share/icons/gnome/24x24/stock/form/stock_form-time-field.png'

That’s great for me, because I’m using find. But not very useful if I’m not, so I wanted something more generic. Of course, there are many ways to do this, and again, I’m by no means a command-line guru. But here’s how I started:

$ find /usr/share/icons/gnome/ -iregex ".*[^mul]time.*" | sed -e 's/^/"/' | sed -e :a -e '$!N;s/\n/" /; ta' | tr '\n' '"'

This wrapped the lines in double-quotes and join them together with a space in between. The find command is the same as before, minus the formatting because that’s what I was trying to find an alternative to. The output of find is piped into sed, which adds a " at the beginning of each line. This output is then sent to another sed which replaces the newline character at the end of each line with a closing double-quote and a space, joining all lines into a single line. Finally, that output is piped into tr which replaces the one remaining newline with a final double-quote.

If the files don’t include spaces or other troublesome characters (mine didn’t), then you could get away with simply changing the newlines into spaces. But again, I wanted something generic.

$ find /usr/share/icons/gnome/ -iregex ".*[^mul]time.*" | tr '\n' ' '

Anyway, now that we have something that creates the desired input, we just have to wrap it in back-ticks and put the whole mess as the argument to the image viewer! In my case, I’m using eye-of-gnome (or eog).

$ eog `find /usr/share/icons/gnome/ -iregex ".*[^mul]time.*" | sed -e 's/^/"/' | sed -e :a -e '$!N;s/\n/" /; ta' | tr '\n' '"'`

Wait a second. That doesn’t actually work. Why not? Running the backtick contents by itself seemed to produce the correct output. Copying this output verbatim as arguments to eog worked as expected. The problem was that when the quoted arguments were manually entered on the command-line, bash (silently) escapes the filenames, removing the quotes, adding backslashes before space,s etc. But when the pipeline is wrapped in backticks, the result is not escaped, and eog complains about not being able to find files that begin with quotes. Fine. I was not about to write a bash “escape” script – something like that should already exist, right? And it should be built in to bash! Perhaps it’s even called “escape“. Well, if there exists such a built-in, I couldn’t find it. But I had to be going about this the wrong way. Heading over to IRC, it was kind of difficult to explain the problem, but ferret eventually gave me the meat of the script above:

files=(); while read -r; do files+=( "$REPLY" ); done < <(find /usr/share/icons/gnome/ -iregex ".*[^mul]time.*"); eog "${files[@]}"

It worked! I thanked him and began studying it. There were still a couple things I didn’t understand:

  • Why two angle brackets with a space between them? I understand the first one is probably redirecting input, but I don’t understand that one next to the opening parenthesis.
    Answer: The <(command) actually puts command‘s output in a temporary file and produces that filename. So <(command) becomes tempfile. (Thanks, kojiro.)
  • If I open some video or audio files in totem using open-with, there’s an odd delay. Using vlc doesn’t produce a delay, however.

And to think this was originally going to be a micro-blog post in Twitter and Wow. It’s safe to say I still hate shell scripting. 🙂

WordPress Upgrade Script

Even though the WordPress upgrade is easy, it’s troublesome. So I wrote a script to do it for me. Yep. There’s lots of these out there. And a lot of hosts (including mine) have a one-click install/update thing. But for some reason, I decided to write my own script. In python. Got to use a bunch of modules I’ve never used before, so it was a good experience.

How to Use

Run this script from the directory that contains your wordpress directory, on your server. I think it requires Python version 2.3. I ran it with 2.3.5. Use python -V to check the version. There’s two methods to run it.

With Prompts

1: Be prompted to push the Enter key to continue at each major step. Nice for the first time.


Example output:

python ../src/wp-upgrade/ 
Current WordPress Version: '2.5'
Newest WordPress Version:  2.6.1
Downloading wordpress-2.6.1.tar.gz ...
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
Create working wordpress directory... 
			[[Press Enter to Continue]]
Update Wordpress root contents...  
			[[Press Enter to Continue]]
'/tmp/tmprpJTKu/wordpress' -> 'wordpress.working'
wp-trackback.php,  wp-config-sample.php,  wp-settings.php,  wp-rss2.php,  readme.html,  index.php,  wp-links-opml.php,  wp-pass.php,  wp-feed.php,  wp-register.php,  wp-rdf.php,  wp-rss.php,  wp-commentsrss2.php,  license.txt,  wp-comments-post.php,  wp-blog-header.php,  wp-load.php,  wp-mail.php,  wp-atom.php,  wp-cron.php,  wp-app.php,  xmlrpc.php,  wp-login.php,  
Replace wp-admin and wp-includes... 
			[[Press Enter to Continue]]
Update default themes and plugins...  
			[[Press Enter to Continue]]
'/tmp/tmprpJTKu/wordpress/wp-content' -> 'wordpress.working/wp-content'
'/tmp/tmprpJTKu/wordpress/wp-content/plugins' -> 'wordpress.working/wp-content/plugins'
akismet,  hello.php,  
'/tmp/tmprpJTKu/wordpress/wp-content/themes' -> 'wordpress.working/wp-content/themes'
default,  classic,  
Backup original, Rename working....
			[[Press Enter to Continue]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
### VISIT '' in your browser. ###
After that, you're All Done!
Go re-enable all your plugins and make sure everything works.
If you need to, you can always roll back by renaming the backup to 'wordpress'.
			[[Press Enter to Continue]]

Without Prompts

2. Do everything without a prompt. Do this by passing the -q option. The -q is for quiet and prints the same messages, but doesn’t wait for the user to press Enter. Blasts through the whole upgrade in one step.

python -q

Example output would be the same as the above, minus the “[[Push Enter to Continue]]” bits.

What’s it do?

If you look at the above output, which is from my own site, you can see what it does. It will compare your installed wordpress version and to the latest available from If your version is older, it will download the new one, extract it, perform the appropriate steps to update the old one. Note that the default themes and plugins will be overwritten, which is fine as long as you didn’t customize them. After it’s done, your wordpress directory should be up-to-date (you just have to visit the upgrade page), and you should have a wordpress 2.5 backup containing the directory as it was before running the script (and 2.5 would be the appropriate version). Also, there’s two variables you will (probably) want to customize: wpsite and wpdir. That’s it, basically.


Browse the source and download the script here.

python treeview toggle

Had this post sitting around. Seems finished. May be helpful to someone. /me waves Wand of Publish +1

I was confused when I was playing around with this basic concept: adding a toggle widget to the treeview in pygtk. The reason for this is an inconsistency in the api model – or at least how I perceived it. With a regular toggle button, you create the widget and can manipulate it once it is drawn. The checkmark is toggled and the “toggled” signal is emitted. I only connect a signal handler to the toggled signal after. However, following this same logic, I created a list with a column of toggle widgets and tried clicking them… but nothing happened. The problem here was the toggled value was linked to the list’s data, and the data wasn’t actually changing. Even though the toggle signal was being emitted, the checkmark wasn’t being toggled because the data wasn’t changing, so I thought there was a problem with my code and the signal wasn’t being emitted. But actually, it would make more sense if it was a “clicked” signal that was being emitted, not the “toggled” signal.

Example source code:

#!/usr/bin/env python
# example
import pygtk
import gtk
import gobject
class BasicTreeViewToggleExample:
    # close the window and quit
    def delete_event(self, widget, event, data=None):
        return False
    def column_toggled(self, cell, path, model):
        # get toggled iter, and value at column 0
        iter = model.get_iter((int(path),))
        val = model.get_value(iter, 0)
        # toggle the value
        val = not val
        # set new value
        model.set(iter, 0, val)
    def __init__(self):
        # create a new window
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title("Basic TreeView Toggle Example")
        self.window.set_size_request(200, 200)
        self.window.connect("delete_event", self.delete_event)
        # create a ListStore with two columns to use as the model
        self.model = gtk.ListStore(gobject.TYPE_BOOLEAN, str)
        # create some list items
        for item in range(5):
            self.model.append([False, 'item %i' % item])
        # create the TreeView using the model
        self.view = gtk.TreeView(self.model)
        # create a CellRendererText to render the data
        self.cellrenderer_text = gtk.CellRendererText()
        self.cellrenderer_toggle = gtk.CellRendererToggle()
        self.cellrenderer_toggle.connect('toggled', self.column_toggled, self.model)
        # create the TreeViewColumns to display the data
        self.tvcolumntext = gtk.TreeViewColumn('TreeViewColumn 1')
        self.tvcolumntoggle = gtk.TreeViewColumn('tog', self.cellrenderer_toggle, active=0)
        # add the TreeViewColumns to the TreeView
        # add the cell to the tvcolumn and allow it to expand
        self.tvcolumntoggle.pack_start(self.cellrenderer_toggle, False)
        self.tvcolumntext.pack_start(self.cellrenderer_text, True)
        # set the cell "text" attribute to column 0 - retrieve text
        # from that column in treestore
        self.tvcolumntext.add_attribute(self.cellrenderer_text, 'text', 1)
        # make it searchable
        # Allow sorting on the column
        # Allow drag and drop reordering of rows
        self.sw = gtk.ScrolledWindow()
def main():
if __name__ == "__main__":
    tvexample = BasicTreeViewToggleExample()

AttrDict Python Module

I’ve been doing quite a bit of Python hacking in my recent free time. A couple days ago, I had the desire for a dictionary that I could access key values just like object attribute names. For example, in the dictionary d={'key':'value'}, I wanted to be able to use d.key to return 'value'. I thought this would be a common desire and probably already exists (and might even be built-in somehow), so I went on to #python and asked the folk there. Nobody knew of anything. So I set off to write my own. I called it AttrDict.

Yesterday, I found something similar already exists. It’s called ObDict. Similar? Very! 😛 But different enough that I continued mine… and have a somewhat complete python module of my own. So far it’s been a nice opportunity to learn more about Python objects and use the unittest module for unit testing.

It’s not 100% coverage of the dict interface, but it’s pretty close and I think it’s usable enough that I can slap a quick release together and work on other stuff. Bugs/patches/testing/suggestions welcome.


Wooops, forgot to write a bit more about it. Here’s the docstring from the module:

Simple extension of the built-in dictionary so that dictionary keys are mirrored
as object attributes. This is for convenience. You can do things like this:

d = dict() #standard dict, for comparison
a = AttrDict() #

d[‘ok’] = ‘this is ok’
d.ok -> AttributeError raised
d.ok = ‘test’ -> AttributeError raised

You cannot assign attributes to standard Python dicts.

a[‘ok’] = ‘this is ok’
a.ok -> ‘this is ok’ #attribute is automatically created
a.ok = ‘changed’
a[‘ok’] -> ‘changed’ #and the dict value is automatically updated
a.ok2 = ‘new value’ #adding new attribute, ok2
a[‘ok2’] -> ‘new value’ #dict key is automatically created

This introduces a limitation on the dictionary keys such
that they must be strings and provide valid Python syntax for accessing.

For example:

{‘123′:’valid’} #is a valid dictionary


mydict.123 #is not valid Python syntax.

Attempting to create a key that cannot be accessed through an attribute name
will raise an AttributeError Exception.

This module has not been built for optmization. Some of the method
documentation has been taken from Python’s dict documentation. For more info
on a particular method, refer to that. I’ve tried to mirror the effects of
the standard dict as closely as possible.

Snakes in an Office

I’ve mentioned before that my job (whose contract is almost up) doesn’t require programming. But that doesn’t mean that programming is not useful. When I first got my laptop, I asked the tech guy about the process of installing new software. It went something like this:

  1. Write a formal request for the software you would like installed and submit it to the tech support department.
  2. Wait for review and approval.
  3. Wait for them to install it.

A pretty standard (and painful) process in large organizations, unfortunately. I asked him, “So if I want to install Python I just submit one of these requests?”

Insead of answering my question, he asked me one in return. “What would you want to install Python for?!” I think he actually spat and curled his lips at the thought.

“I don’t know… writing basic scripts.” I really didn’t know what at the time, but I knew it would be useful. And I wanted to use it.

He proceeded to look at me as if I had suggested bathing in tomato sauce as a remedy for headaches.

So I was under the impression that I couldn’t install new software and didn’t want to bother going through all that formal cock holding; I went without my Firefox, my Python. I’ve since discovered that I can install things. (Sorta.)

Anyway, I was using Python at work today, and I just love it. Previously, I had been fumbling around with VBA in Excel and Word. I had a button set up in Word that would run a macro/VBA to export the form’s contents to a CSV file in a particular folder. I could open a Word document anywhere, press the export button, and the CSV would be created/overwritten in that folder. Then I had a button set up in Excel to run a macro that would import all CSV files in that folder as rows in the spreadsheet. (This was actually really useful, and if you have to deal with a lot of similar tasks in Office, use VBA and macros.) There was an inconsistency in the number of rows I had in my spreadsheet and the number of docs I had… so I wanted to compare the exported CSVs with the available DOCs.

Enter, the Python interpreter (great for little tasks and as a substitute shell on windows).

The word documents were in a bunch of different folders (for various reasons) but once I had a variable, originals, containing a list of all the documents, and a variable, exported, containing a list of all the CSV, files, I was rolling.

>>> len(originals), len(exported)
(198,200) # Hmmmm, looks like a job for sets!
>>> s1,s2 = (set(originals), set(exported))
>>> len(s1.difference(s2))
>>> # What the...
>>> originals
['doc1.doc', 'doc2.doc', ...., 'doc200.doc']
>>> exported
['doc1.txt', 'doc2.txt', ..., 'doc200.txt']
>>> # Oh yeah, different extensions. Duh...
... # Bus leaves SOON!
... # Need to quickly strip the extensions and rebuild the sets....
>>> s1,s2 = ( set([s[:-4] for s in s1]), set([s[:-4] for s in s2]) )
>>> s1
['doc1', 'doc2', ... , 'doc200']
>>> # Cool
>>> s1.difference(s2)
(['doc38', 'doc72'])

AHA!! Caught my bus. Python is great. 😀 and Image Orientation

I was really puzzled… and in fact, I’m still puzzled, but at least it works now.

Update: I’m a bit less puzzled now. I wasn’t using the correct version of (see comments). This post has been updated to correct any information. I also updated the CODE style because posting python code when whitespace is ignored is a little confusing. ^.^

I’m now using for loading EXIF information from photos for my little project, PhotoFile. Today, I wanted the image orientation information. Looking in the source of, you’ll find:

0x0112: ('Orientation',
{1: 'Horizontal (normal)',
2: 'Mirrored horizontal',
3: 'Rotated 180',
4: 'Mirrored vertical',
5: 'Mirrored horizontal then rotated 90 CCW',
6: 'Rotated 90 CW',
7: 'Mirrored horizontal then rotated 90 CW',
8: 'Rotated 90 CCW'}),
0x9003: ('DateTimeOriginal', ),

So, I tried:

image = open("photo.jpg", "rb")
tags = EXIF.process_file(image)

Using tags["DateTimeOriginal"] works fine. But using tags["Orientation"] does not work. So finally (it took an unfortunate amount of time to do this), I tried:

for tag in tags.keys():
if "orientation" in str(tag).lower(): print tag

To correct the above, I’m a little lazy with composition, so it’s easier to just post some output from a python session:

>$ python
>>> import EXIF
>>> p = open("photo.jpg", "rb")
>>> tags = EXIF.process_file(p)
>>> for key in tags.keys():
... if "orientation" in str(key).lower() or \
... "datetime" in str(key).lower(): print key, ": ", tags[key]
EXIF DateTimeOriginal : 2006:03:19 14:22:40
Image DateTime : 2006:03:19 14:22:40
EXIF DateTimeDigitized : 2006:03:19 14:22:40
Image Orientation : Rotated 90 CCW

There is indeed no “Orientation” tag but there is an “Image Orientation” tag. The string “Image Orientation” appears nowhere in the source, and I couldn’t find any documentation on it. It looks like the tag keys are set by reading them from the EXIF information within the file appending the key to the classification (as pointed out by Shirley – see comments), but even the EXIF spec lists the tag as “Orientation”. If anybody knows why this is the case wants to clarify further, please post in the comments.


So if you’re using and want access to the image’s orientation, use “Image Orientation” for the key.

How to open a folder with the default file-manager in mono/C#

using System.Diagnostics;

Process.Start (“file:///home/”);

Did a bit of IRC channel ping-pong today. Went over to #f-spot to ask a user-related question about f-spot, and got pulled in by curiosity on a totally unrelated topic: opening a folder with the default file-manager. Initiated some discussion on #mono and discovered this was, in fact, not trivial – which seemed odd. But I’ve never personally written any C#, or much desktop code at all, for that matter. So I started playing around with various suggestions from the good folks in #mono, and writing my very first C# application.

(Yeah, basically a one-liner. You gotta start somewhere! ^_^) Anyway, for something so simple, there seemed to be a lot of uncertainty and discussion about it (even from the man, himself! – He claims his memory is fading…), so I figure it’s worth documenting. More info can be seen here under Process.

On FreeDesktop systems it will use xdg-open, if not, it will try to use gnome-open or kfmclient to open the files.

Not sure if this will work on Windows. The file:// prefix is required.

Update: According to ccoish, this will work on Windows.

Passing this info back to #f-spot resulted in a patch to allow you to open the folder containing your photo. Thanks, Gabriel! Open source is cool. 🙂

Not sure if I’ll look at C#/Mono much more, was kinda gonna do the Python thing for a while… but this was a fun distraction. 🙂