Shallow Thoughts : : Sep

Akkana's Musings on Open Source Computing and Technology, Science, and Nature.

Wed, 26 Sep 2012

Writing xchat scripts in Python (to play sound alerts)

I use xchat as my IRC client. Mostly I like it, but its sound alerts aren't quite as configurable as I'd like. I have a few channels, like my Bitlbee Twitter feed, where I want a much more subtle alert, or no alert at all. And I want an easy way of turning sounds on and off, in case I get busy with something and need to minimize distractions.

Years ago I grabbed a perl xchat plug-in called "Smet's NickSound" that did something close to what I wanted. I've hacked a few things into it. But every time I try to customize it any further, I'm hit with the pain of write-only Perl. I've written Perl scripts, honest. But I always have a really hard time reading anyone else's Perl code and figuring out what it's doing. When I dove in again recently to try to figure out why I was getting so many alerts when first starting up xchat, I finally decided: learning how to write a Python xchat script couldn't be any harder than reverse engineering a Perl one.

First, of course, I looked for an existing nick sound Python script ... and totally struck out. In fact, mostly I struck out on finding any xchat Python scripts at all. I know there are Python bindings for xchat, because there's documentation for them. But sample plug-ins? Nope. For some reason, nobody's writing xchat plug-ins in Python.

I eventually found two minimal examples: this very simple example and the more elaborate utf8decoder. I was able to put them together and cobble up a working nick sound plug-in. It's easy once you have an example to work from to help you figure out the event hook arguments.

So here's my own little example, which may help the next person trying to learn xchat Python scripting: chatsounds.py on github.

Tags: , , ,
[ 22:13 Sep 26, 2012    More programming | permalink to this entry | ]

Fri, 21 Sep 2012

Farewell, Space Shuttle Endeavour

[Space shuttle Endeavour flyby] This morning, the last space shuttle, Endeavour, made a piggyback fly-by of California cities prior to landing at LAX, where it will be trucked to its final resting place in Exposition Park. And what science and astronomy fan could resist a once in a lifetime chance to see the last shuttle in flight, piggyback on its 747 transporter?

Events kept me busy all morning, so I was late getting away. Fortunately I'd expected that and planned for it. While watching the flyby from Griffith Observatory sounded great, I suspected there would be huge crowds, no parking and there's no way I could get there in time. The Times suggested Universal City -- which I took to mean that there would be huge crowds and traffic there too. So I picked a place off the map, Blair Dr., that looked like it was easy to get to, reasonably high and located in between Griffith and Universal.

It turned out to be a good choice. There were plenty of people there, but I found a parking spot a few blocks away from where everybody was hanging out and walked back to the viewpoint where I'd seen the crowds.

[Universal Studios back lot] I looked down and the first thing I saw was a smashed jumbo jet among the wreckage of some houses. Um ... not the way I wanted to see the shuttle! But then I realized I was looking at the Universal Studios back lot. Right. Though binoculars I could even see the tram where the folks on the studio tour went right by the "plane crash". And I could look across to Universal City, where the crowds made me happy I'd decided against going there -- I bet they had some traffic jams too.

The crowd was friendly and everybody was sharing the latest rumors of the shuttle's location -- "It just flew over Santa Barbara!" "It's over West Hollywood -- get ready!" "Nope, now it's going west again, might be a while." That helped with the wait in the hot sun.

[Space shuttle Endeavour flyby] Finally, "It's coming!" And we could see it, passing south of the crowds at Universal City and coming this way ... and disappearing behind some trees. We all shifted around so we'd see it when it cleared the trees.

Only it didn't! We only got brief glimpses of it, between branches, as the shuttle flew off toward Griffith Observatory. Oh no! Were we in exactly the wrong location?

Then the word spread, from people farther down the road -- "It's turning -- get ready for another pass!" This time it came by south of us, giving us all a beautiful clear view as the 747 flew by with the shuttle and its two fighter-plane escorts.

We hung around for a few more minutes, hoping for another pass, but eventually we dispersed. The shuttle and its escorts flew on to LAX, where it will be unloaded and trucked to Exposition Park. I feel lucky to have gotten such a beautiful view of the last shuttle flight.

Photos: Space shuttle Endeavour flyover.

Tags: ,
[ 21:35 Sep 21, 2012    More science/astro | permalink to this entry | ]

Wed, 19 Sep 2012

Python: Do two dates span a particular day of the week or month?

When I'm using my RSS reader FeedMe, I normally check every feed every day. But that can be wasteful: some feeds, like World Wide Words, only update once a week. A few feeds update even less often, like serialized books that come out once a month or whenever the author has time to add something new.

So I decided it would be nice to add some "when" logic to FeedMe, so I could add when = Sat in the config section for World Wide Words and have it only update once a week.

That sounded trivial -- a little python parsing logic to tell days from numbers, a few calls to time.localtime() and I was done.

Except of course I wasn't. Because sometimes, like when I'm on vacation, I don't always update every day. If I missed a Saturday, then I'd never see that week's edition of World Wide Words. And that would be terrible!

So what I really needed was a way to ask, "Has a Saturday occurred (including today) since the last time I ran feedme?"

The last time I ran feedme is easy to determine: it's in the last modified date of the cache file. Or, in more Pythonic terms, it's statbuf = os.stat(cachefile).st_mtime. And of course I can get the current time with time.localtime(). But how do I figure out whether a given week or month day falls between those two dates?

I'm sure this particular wheel has been invented many times. There's probably even a nifty Python library somewhere to do it. But how do you google for that? I tried to think of keywords and found nothing. So I went for a nice walk in the redwoods and thought about it for a bit, and came up with a solution.

Turns out for the week day case, you can just use modular arithmetic: if (weekday_2 - target_weekday) % 7 < (weekday_2 - weekday_1) then the day does indeed fall between the two dates.

Things are a little more complicated for the day of the month, though, because you don't know whether you need mod 30 or 31 or 29 or 28, so you either have to make your own table, or import the calendar module just so you can call calendar.monthrange().

I decided it was easier to use logic: if the difference between the two dates is greater than 31, then it definitely includes any month day. Otherwise, check whether they're in the same month or not, and do greater than/less than comparisons on the three dates.

Throw in a bunch of conversion to make it easy to call, and a bunch of unit tests to make sure everything works and my later tweaks don't break anything, and I had a nice function I could call from Feedme.

falls_between.py on github

Tags: ,
[ 22:07 Sep 19, 2012    More programming | permalink to this entry | ]

Thu, 13 Sep 2012

A difference of opinion regarding golf

[2 parking lot signs: ] The parking lot for Fremont Older Open Space Preserve adjoins a golfing range. There seems to be some difference of opinion regarding the responsibility if an errant golf ball should hit a car. One sign declares

DANGER: Flying golf balls, Park at your own risk
while the one right under it, put up by the open space district, advises
Golfers are responsible for damage caued by golf balls

I just hope I never have to find out who's right.

Tags: ,
[ 22:50 Sep 13, 2012    More humor | permalink to this entry | ]

Sat, 08 Sep 2012

Touchpad and trackball blues: Emulate the middle button, and touchpad disable

In setting up a laptop -- Debian "Squeeze" with a Gnome 2 desktop -- for an invalid who will be doing most of her computing from bed, we hit a snag. Two snags, actually: both related to the switching between the trackpad and an external trackball.

Disabling and re-enabling the trackpad

First, the trackpad gets in the way when she's typing. "Disable touchpad while typing" was already set, but it doesn't actually work -- the mouse was always moving when her palm brushed against it.

On her desktop computer, she's always used a Logitech trackball -- never really got the hang of mice, but that trackball always worked well for her. And fortunately, unlike a mouse, a trackball works just fine from bed.

Once the trackball is working, there's really no need to have the trackpad enabled. So why not just turn it off when the external trackball is there? I thought I'd once seen a preference like that ... but it was nowhere to be found in the Gnome 2 desktop.

It turns out the easiest way to disable a trackpad is this:

synclient TouchpadOff=1

Using 0 instead of 1 turns it back on. So we gave her shell aliases for both these commands. A web search will show various approaches to writing udev rules to run something like that automatically, but she felt it was easy enough to type a command when she switches modes, so we're going with that for now.

Emulate the middle button on an external mouse or trackball

We thought we were done -- until we tried to paste that alias into her shell and discovered that 2-button paste doesn't work for external mice in Squeeze.

Usually, when you have a mouse-like device that has only two buttons, you can click the left and right buttons together to emulate a middle click. She'd been using that on her old Ubuntu Lucid install, and it works on pretty much every trackpad I've used. But it didn't work with the USB trackball on Squeeze.

Gnome used to have a preference for middle button emulation, but it's gone now. There's a program you can install called gpointing-device-settings that offers a 2-button emulation setting ... but it doesn't save the settings anywhere. And since it's a GUI program you can't make it part of your login or boot process -- you'd have to go through and click to set it every time. Not happening.

2-buttom emulation is an X setting -- one of the settings that used to be specified in Xorg.conf, and now wanders around to different places on every distro. A little web searching didn't turn up a likely candidate for Squeeze, but it did turn up a way that's probably more distro independent: the xinput command.

After installing xinput, you need the X ID of the external mouse or trackball. xinput list should show you something like this (plus more stuff for keyboards and possibly other devices):

$ xinput list
  Virtual core pointer                          id=2    [master pointer  (3)]
      Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
      SynPS/2 Synaptics TouchPad                id=10   [slave  pointer  (2)]
      Kensington Kensington USB/PS2 Orbit       id=13   [slave  pointer  (2)]

Once you have the id of the external device, list its properties:

$ xinput list-props 13                                       ~ 9:01PM
Device 'Kensington Kensington USB/PS2 Orbit':
        Device Enabled (132):   1
  ... long list of other properties ...
        Evdev Middle Button Emulation (303):    0
        Evdev Middle Button Timeout (304):      50
  ... more properties ...

You can see that middle button emulation is disabled (0). So turn it on:

$ xinput --set-prop 13 "Evdev Middle Button Emulation" 1

Click both buttons together, and sure enough -- a middle button paste! I added that to the alias that turns the trackpad off -- though of course, it could also be added to a udev rule that fires automatically when the mouse is plugged in.

Tags: ,
[ 15:34 Sep 08, 2012    More linux/install | permalink to this entry | ]

Wed, 05 Sep 2012

Pho 0.9.8 released

I decided to give myself a birthday present and release version 0.9.8 of Pho, my image viewer, at long last.

I've been using it essentially unchanged for many months now, occasionally tweaking things or fixing minor bugs ... but I haven't run into any bugs in quite a while, and think I've fixed all the pending ones. Been meaning to make a release for a long time, but somehow I keep getting sidetracked and forgetting about it.

This should rationalize the version number again ... the official releases have been 0.9.7-preN forever, but there was an unofficial 0.9.7 and even a 0.9.8 that snuck in along with some patches I got from David Gardner. It's been confusing. So now it's officially 0.9.8, and any figure versions will start with 0.9.9, and we might even see a 1.0 one of these days. (I suppose it's time -- Pho is ten years old!)

So here it is: Pho 0.9.8. I think it's working well. If you're already a Pho user, or if you want a lightweight image viewer that's also good at triaging and annotating large batches of images, you might want to take a look.

Tags: , ,
[ 13:36 Sep 05, 2012    More programming | permalink to this entry | ]

Sun, 02 Sep 2012

GIMP plug-in to export scaled-down versions of images

In a discussion on Google+arising from my Save/Export clean plug-in, someone said to the world in general

PLEASE provide an option to select the size of the export. Having to scale the XCF then export then throw out the result without saving is really awkward.

I thought, What a good idea! Suppose you're editing a large image, with layers and text and other jazz, saving in GIMP's native XCF format, but you want to export a smaller version for the web. Every time you make a significant change, you have to: Scale (remembering the scale size or percentage you're targeting); Save a Copy (or Export in GIMP 2.8); then Undo the Scale. If you forget the Undo, you're in deep trouble and might end up overwriting the XCF original with a scaled-down version.

If I had a plug-in that would export to another file type (such as JPG) with a scale factor, remembering that scale factor so I didn't have to, it would save me both effort and risk.

And that sounded pretty easy to write, using some of the tricks I'd learned from my Save/Export Clean and wallpaper scripts. So I wrote export-scaled.py

Update: Please consider using Saver instead. Saver integrates the various functions I used to have in different save/export plug-ins; it should do everything export-scaled.py does and more, and export-scaled.py is no longer maintained. If you need something export-scaled.py does that saver doesn't do as well, please let me know.

It's still brand new, so if anyone tries it, I'd appreciate knowing if it's useful or if you have any problems with it.

Geeky programming commentary

(Folks not interested in the programming details can stop reading now.)

Linked input fields

[screenshot: linked input fields] One fun project was writing a set of linked text entries for the dialog:
Scale to: Percentage 100 % Width: 640 Height: 480

Change any one of the three, and the other two change automatically. There's no chain link between width and height: It's assumed that if you're exporting a scaled copy, you won't want to change the image's aspect ratio, so any one of the three is enough.

That turned out to be surprisingly hard to do with GTK SpinBoxes: I had to read their values as strings and parse them, because the numeric values kept snapping back to their original values as soon as focus went to another field.

Image parasites

Another fun challenge was how to save the scale ratio, so the second time you call up the plug-in on the same image it uses whatever values you used the first time. If you're going to scale to 50%, you don't want to have to type that in every time. And of course, you want it to remember the exported file path, so you don't have to navigate there every time.

For that, I used GIMP parasites: little arbitrary pieces of data you can attach to any image. I've known about parasites for a long time, but I'd never had occasion to use them in a Python plug-in before. I was pleased to find that they were documented in the official GIMP Python documentation, and they worked just as documented. It was easy to test them, too: in the Python console (Filters->Python->Console...), type something like

img = gimp_image_list()[0]
img.parasite_list()
img.parasite_find(img.parasite_list()[0])
and so forth. Nice!

Not prompting for JPG settings

My plug-in was almost done. But when I ran it and told it to save to filenamecopy.jpg, it prompted me with that annoying JPEG settings dialog. Okay, being prompted once isn't so bad. But then when I exported a second time, it prompted me again, and didn't remember the values from before. So the question was, what controls whether the settings dialog is shown, and how could I prevent it?

Of course, I could prompt the user for JPEG quality, then call jpeg-save-file directly -- but what if you want to export to PNG or GIF or some other format? I needed something more general

Turns out, nobody really remembers how this works, and it's not documented anywhere. Some people thought that passing run_mode=RUN_WITH_LAST_VALS when I called pdb.gimp_file_save() would do the trick, but it didn't help.

So I guessed that there might be a parasite that was storing those settings: if the JPEG save plug-in sees the parasite, it uses those values and doesn't prompt. Using the Python console technique I just mentioned, I tried checking the parasites on a newly created image and on an image read in from an existing JPG file, then saving each one as JPG and checking the parasite list afterward.

Bingo! When you read in a JPG file, it has a parasite called 'jpeg-settings'. (The new image doesn't have this, naturally). But after you write a file to JPG from within GIMP, it has not only 'jpeg-settings' but also a second parasite, 'jpeg-save-options'.

So I made the plug-in check the scaled image after saving it, looking for any parasites with names ending in either -settings or -save-options; any such parasites are copied to the original image. Then, the next time you invoke Export Scaled, it does the same search, and copies those parasites to the scaled image before calling gimp-file-save.

That darned invisible JPG settings dialog

One niggling annoyance remained. The first time you get the JPG settings dialog, it pops up invisibly, under the Export dialog you're using. So if you didn't know to look for it by moving the dialog, you'd think the plug-in had frozen. GIMP 2.6 had a bug where that happened every time I saved, so I assumed there was nothing I can do about it.

GIMP 2.8 has fixed that bug -- yet it still happened when my plug-in called gimp_file_save: the JPG dialog popped up under the currently active dialog, at least under Openbox.

There isn't any way to pass window IDs through gimp_file_save so the JPG dialog pops up as transient to a particular window. But a few days after I wrote the export-scaled, I realized there was still something I could do: hide the dialog when the user clicks Save. Then make sure that I show it again if any errors occur during saving.

Of course, it wasn't quite that simple. Calling chooser.hide() by itself does nothing, because X is asynchronous and things don't happen in any predictable order. But it's possible to force X to sync the display: chooser.get_display().sync().

I'm not sure how robust this is going to be -- but it seems to work well in the testing I've done so far, and it's really nice to get that huge GTK file chooser dialog out of the way as soon as possible.

Tags: , ,
[ 18:34 Sep 02, 2012    More gimp | permalink to this entry | ]