Shallow Thoughts : : Sep
Akkana's Musings on Open Source Computing and Technology, Science, and Nature.
Wed, 26 Sep 2012
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: programming, python, xchat, irc
[
22:13 Sep 26, 2012
More programming |
permalink to this entry |
]
Fri, 21 Sep 2012
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.
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.
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: astronomy, photography
[
21:35 Sep 21, 2012
More science/astro |
permalink to this entry |
]
Wed, 19 Sep 2012
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: programming, python
[
22:07 Sep 19, 2012
More programming |
permalink to this entry |
]
Thu, 13 Sep 2012
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: humor, golf
[
22:50 Sep 13, 2012
More humor |
permalink to this entry |
]
Sat, 08 Sep 2012
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: linux, laptop
[
15:34 Sep 08, 2012
More linux/install |
permalink to this entry |
]
Wed, 05 Sep 2012
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: programming, pho, image viewer
[
13:36 Sep 05, 2012
More programming |
permalink to this entry |
]
Sun, 02 Sep 2012
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
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: gimp, programming, python
[
18:34 Sep 02, 2012
More gimp |
permalink to this entry |
]