Planet Openbox!

July 02, 2009

Dumping a screensaver to a text file

I am only days away from completing a rather large real-life project, and finally getting back some of the free time I have lost over the past few months. July is looking much more promising than June, and I already have a stack of things I need to make notes on, as well as an [...]

Disabling mouse/keyboard wakeup

Suspend (sleep) works very well on the dual-Atom desktop. The only problem with it is that the mouse or keyboard wake it up. I don't mind the keyboard, but the mouse is quite sensitive, so a breeze through the window or a big truck driving by on the street can jiggle the mouse and wake the machine when I'm away.

I've been through all the BIOS screens looking for a setting to flip, but there's nothing there. Some web searching told me that under Windows, there's a setting you can change that will affect this, but I couldn't find anything similar for Linux, until finally drc clued me in to /proc/acpi/wakeup.

cat /proc/acpi/wakeup
will tell you all the events that can cause your machine to wake up from various sleep states.

Unfortunately, they're also obscurely coded. Here are mine:

Device  S-state   Status   Sysfs node
SLPB      S4    *enabled  
P32       S4     disabled  pci:0000:00:1e.0
UAR1      S4     enabled   pnp:00:0a
PEX0      S4     disabled  pci:0000:00:1c.0
PEX1      S4     disabled  
PEX2      S4     disabled  pci:0000:00:1c.2
PEX3      S4     disabled  pci:0000:00:1c.3
PEX4      S4     disabled  
PEX5      S4     disabled  
UHC1      S3     disabled  pci:0000:00:1d.0
UHC2      S3     disabled  pci:0000:00:1d.1
UHC3      S3     disabled  pci:0000:00:1d.2
UHC4      S3     disabled  pci:0000:00:1d.3
EHCI      S3     disabled  pci:0000:00:1d.7
AC9M      S4     disabled  
AZAL      S4     disabled  pci:0000:00:1b.0

What do all those symbols mean? I have no clue. Apparently the codes come from the BIOS's DSDT code, and since it varies from board to board, nobody has published tables of likely translations.

The only two wakeups that were enabled for me were SLPB and UAR1. SLPB apparently stands for SLeeP Button, and Rik suggested UAR probably stood for Universal Asynchronous Receiver (the more familiar term UART both receives and Transmits.) Some of the other devices in the list can possibly be identified by comparing their pci: codes against lspci, but not those two.

Time for some experimentation. You can toggle any of these by writing to the wakeup device:

echo UAR1 >/proc/acpi/wakeup

It turned out that to disable mouse and keyboard wakeup, I had to disable both SLPB and UAR1. With both disabled, the machine wakes up when I press the power button. (What the SLeeP Button is, if it's not the power button, I don't know.)

My mouse and keyboard are PS/2. For a USB mouse and keyboard, look for something like USB0, UHC0, USB1.

The UAR1 setting is remembered even across boots: there's no need to do anything to make sure the setting is remembered. But the SLPB setting resets every time I boot. So I edited /etc/rc.local and added this line:

echo SLPB >/proc/acpi/wakeup

We came, we saw, we moved on.

It is sad to see the demise of Metacity, but its legacy will not be forgotten. Obviously there is Mutter, and of course Compiz, but i think most modern window managers owe a tip of the hat to Metacity; it brought a love of standards, simplicity and of course integration.

It was not without the doubters though, and it certainly felt unloved toward the end (despite the ongoing work of Thomas Thurman and whoever wrote the compositor? Ian…?). The problem with Metacity, for me, was that it was just never that predictable, I’m not really fussy with focus issues but Metacity still seemed to grate on me, it always felt very incomplete, it always felt just not right - probably because people (I blame Ubuntu, but I blame them for everything) jumped on the Compiz bling wagon far too early when what was needed was boring bug fixes to Metacity for a better window manager experience. The problem with Compiz was that it became very apparent that those who shouted loudest were more interested in how their menus exploded rather than how their windows were managed.

The Metacity blog was always an interesting read as well which I guess will go out the window now, people just aren’t interested in window managers unless a) they tile (which is foolish) or b) they have stupid effects. I hope Mutter is a solid window manager with tasteful warranted effects that aid usability and maybe even accessibility. Gnome-shell right now look wholly underwhelming but that might be a good thing?

Metacity still has the best simple composite manager for my money and I’d love to see something like it in Openbox ;)

July 01, 2009

Gmail interface changed

I noticed some changes in the Gmail interface today and I'm not sure what to think of it yet. I used to change my site all the time; functionality, design... everything. I mean I just changed it recently--albeit a minor CSS change (went from a white background to a black one if you haven't noticed!)--but the functionality has stayed the same. Changes like this new Gmail change makes me realize that when people use a site as often as one might use the Gmail interface, they're used to a certain way of navigating... changing that is not necessarily good!

Using RSpec Ordered Message Expectations to Tighten your Specs

I quite enjoy the competitive undercurrent of ping pong pair programming. As the person writing the implementation code, it is fun to write something that will turn a test green, but still not necessarily do what my partner was expecting. Taking this approach has also been helpful for improving our specs. Take this example controller spec:

describe ArticlesController do
  describe "handling create" do
    before(:each) do
      @article = mock_model(Article, :save => nil)
      Article.stub(:new).and_return(@article)
      
      @user = mock_model(User)
      controller.stub(:current_user).and_return(@user)
    end
    
    it "should build a new article from posted data" do
      Article.should_receive(:new).with('title' => 'Test Post')
      post :create, :article => {:title => 'Test Post'}
    end
    
    it "should assign the current user as the article's author" do
      @article.should_receive(:author=).with(@user)
      post :create
    end
    
    it "should save the article"
      @article.should_receive(:save)
      post :create
    end
  end
end

This looks like a reasonable set of concise, clear examples, but you can easily make them all pass and without building a controller action that does what you expect:

class ArticlesController < ApplicationController
  def create
    @article = Article.new(params[:article])
    @article.save
    @article.author = current_user
  end
end

This satisfies the examples, but saving the article before assigning the current user as author isn’t what we would have intended. Enter RSpec’s ordered message expectations. These allow you to specify the order in which you expect an object to receive message calls.

describe ArticlesController do
  describe "handling create" do
    it "should save the article after assigning the current user as author"
      @article.should_receive(:author=).with(@user).ordered
      @article.should_receive(:save).ordered
      post :create
    end
  end
end

This example would fail with the above controller action, and force us to write it properly:

class ArticlesController < ApplicationController
  def create
    @article = Article.new(params[:article])
    @article.author = current_user
    @article.save
  end
end

The result is a controller that does what you expect, a stronger set of specs, and an increased capacity for true behaviour driven development. Win, win, win!

De-@wip Your Cucumber Stories

We’ve been really hitting the Cucumber hard in the recent few iterations of our current project. Now that we’re ping pong pair programming, the Cucumber story is the first thing we write, and we revisit regularly during the red-green-refactor cycle.

To make this easy, we place a @wip work in progress tag at the top of the current stories:

Feature: Make coffee

  # This one is done.
  Scenario: First coffee of the day
  
  # This is the one we're working on.
  @wip
  Scenario: Afternoon perk up

Then we use cucumber -t wip to run just the stories in progress. Once the stories are green and we’re happy to move on, we often forget to remove the @wip tags before committing. I wrote a little sed command in a bash alias to make this easier:

# Remove any @wip tags from Cucumber features.
alias dewip="sed -E -i '' -e '/^[[:blank:]]*@wip$/d;s/,[[:blank:]]*@wip//g;s/@wip,[[:blank:]]*//g' features/**/*.feature"

Throw it in your .bash_profile for ease of access! Here’s a gist for your forking pleasure.

Rails Templates as the New Geek Code

Anyone who’s been around the Internet long enough should remember The Geek Code. This meme sought to provide a – however obsbcure – succinct textual distillation of the attributes and interests of any geek.

GED/J d-- s:++>: a-- C++(++++) ULU++ P+ L++ E---- W+(-) N+++ o+ K+++ w---
O- M+ V-- PS++>$ PE++>$ Y++ PGP++ t- 5+++ X++ R+++>$ tv+ b+ DI+++ D+++
G+++++ e++ h r-- y++**

This geek code block represents someone trained in education and law who is tall and dresses casually, knows his way around Linux and Ultrix pretty well, hates emacs, but loves indulging in a little Dilbert. Look it up, it’s elaborate.

Today, a Rails developer can provide a template for generating new apps that can uniquely embody all their development tools and preferences in a single place.

gem 'haml'
gem 'mislav-will_paginate',   :lib => 'will_paginate',  :source => 'http://gems.github.com'
gem 'chriseppstein-compass',  :lib => 'compass',        :source => 'http://gems.github.com'
gem 'thoughtbot-paperclip',   :lib => 'paperclip',      :source => 'http://gems.github.com'   if yes?('Paperclip gem?')
gem 'authlogic'                                                                               if yes?('Authlogic gem?')

You’re a haml guy? Right on. Plus compass for CSS! You must like really things semantic.

file '.testgems',
%q{config.gem 'rspec'
config.gem 'rspec-rails'
config.gem 'notahat-machinist', :lib => 'machinist',  :source => 'http://gems.github.com'
config.gem 'ianwhite-pickle',   :lib => 'pickle',     :source => 'http://gems.github.com'
config.gem 'webrat'
config.gem 'cucumber'
}
run 'cat .testgems >> config/environments/test.rb && rm .testgems'

RSpec along with Cucumber backed by Machinist and Pickle for test data factories. You must be Australian. But that’s cool, that’s a pretty helpful combo for tests.

git :init
git :add => '.'
git :commit => '-a -m "Initial commit from AMC Rails template"'

Building a complete Rails template has been really useful for us at the AMC. We’re often asked to bring up quick, single-purpose apps alongside the many components that we’re building in our mostly service-oriented architecture.

Our template is available on GitHub for your perusal and reuse. It does everything from setting up plugins and gem dependencies, to generating a deploy script using our custom capistrano extensions, to including a default layout and stylesheet. This is how we roll, captured in a single file.

Importing Legacy Data in Rails

How Did we Get Here?

Our current work project is a long-overdue rebuild of a critical business system as a modern Rails web app. We’re building this thing according to agile practices to the best of our ability. Each week we provide a new, working release that is an incremental improvement on the last. We’re not looking to replace the current system in a single swoop, and expect the new system to work alongside the old one for quite a while.

This means that we’ll need to maintain the same data in both systems for the duration of their lives together. We don’t want the new Rails app to access the data directly from the old app’s database, since this would prevent us from following the conventions that makes working with Rails so pleasant. Instead, we’ve come up with a way to import the legacy data into a new database.

In doing this, I’ve built a Rails plugin to make the experience easier. It is called Acts as Importable and it is now available on GitHub. This article will show our technique for importing the legacy data and how Acts as Importable helps.

Accessing the Legacy Data

We use ActiveRecord to access the legacy data. It takes a bit of legwork to shoehorn the legacy schema into ActiveRecord models, but once that is done, we have satisfactory access to the data we want to import.

The first thing we do is provide a Legacy::Base model from which all other legacy models can inherit. This provides a single place to define access to the legacy database.

class Legacy::Base < ActiveRecord::Base
  self.abstract_class = true
  establish_connection "legacy_#{Rails.env}"

  acts_as_importable
end

You can setup the extra database connections in config/database.yml, like below. Our legacy connection is to an MS SQL server via ODBC. Tim Lucas has an excellent tutorial on how to get that set up.

legacy_development:
  adapter: sqlserver
  mode: odbc
  dsn: LEGACY
  autocommit: true
  host: localhost
  username: NTDOMAIN\username
  password: password

Now, here are a couple of the legacy models that inherit from Legacy::Base to access the legacy database. For the sake of this article, consider the code examples to come from a quiz application with models for categories, questions, and responses.

class Legacy::Question < Legacy::Base
  set_table_name  'quiz_questions'
  set_primary_key 'QuestionNumber'
  
  belongs_to :category,
             :class_name  => 'Legacy::Category',
             :foreign_key => 'CategoryCode'
end

class Legacy::Category < Legacy::Base
  set_table_name 'quiz_categories'
  set_primary_key 'Code'
end

These are simple examples, but you get the idea. set_table_name and set_primary_key are your friends when you have table names and keys that defy Rails’ conventions. If you want to use ActiveRecord associations to access your legacy data, you will also want to become familiar with the options available for specifying things like the class, join table and column names. Check the rdocs for has_many, belongs_to and has_and_belongs_to_many and look out for options like :class_name, :foreign_key, :join_table, and :association_foreign_key.

Converting the Legacy Data

Each of the legacy models provides a to_model method that returns a new model ready to be saved into the new, non-legacy database.

# New model
class Category < ActiveRecord::Base
end

# Legacy model
class Legacy::Category < Legacy::Base
  set_table_name 'quiz_categories'
  set_primary_key 'Code'

  def to_model
    ::Category.new do |c|
      c.name = self.Description
    end
  end
end

Importing the Legacy Data

Now that the import rules are defined for all the legacy models we care about, how do we get them all into the new database? This is where Acts as Importable comes in. This plugin helps to smoothen the import of an entire system’s worth of legacy data. You saw it enabled above in the Legacy::Base class:

class Legacy::Base < ActiveRecord::Base
  self.abstract_class = true
  establish_connection "legacy_#{Rails.env}"

  acts_as_importable
end

Acts as importable will let you import your legacy models in different ways:

# Import a single question
Legacy::Question.import(123)
# or
Legacy::Question.find(123).import

# Import all questions
Legacy::Question.import_all

# Import all the questions, 1000 at a time
Legacy::Question.import_all_in_batches

These are all different ways of instantiating your legacy models, calling the to_model method, and saving the returned object. The value of the plugin is that it adds a little bit of extra smarts to this basic premise.

belongs_to Relationships & Caching

Acts as Importable provides a lookup class method for finding the new ID of an imported legacy model. When each legacy model is imported using the above methods, Acts as Importable saves the legacy model’s class name and ID along with the rest of the data in new model (be sure to provide legacy_class and legacy_id columns for this to work). The first time you lookup a legacy record, it uses ActiveRecord::Base#find with the appropriate values for legacy_class and legacy_id as conditions. It will save the ID in a lookup hash in memory, so that the next time you want to lookup from the same ID, the result is returned without a trip to the database.

This is really best seen in action:

# New models
class Question < ActiveRecord::Base
  belongs_to :category
  has_many :responses, :dependent => :destroy
end
class Category < ActiveRecord::Base
  has_many :questions
end

# Legacy model
class Legacy::Question < Legacy::Base
  def to_model
    ::Question.new do |q|
      # import the category association
      q.category_id = Legacy::Category.lookup(self.category.try(:id__))
    end
  end
end

Setting the id for associations directly like this is important for conserving trips to the database importing a model’s belongs_to relationships, just like the one above. Be wary when using lookups for large data sets, however, as it will likely consume quite a bit of memory to store the ID mappings.

As an aside, we use #try(:id__) on the legacy model to provide the ID for the lookup, because #id on a nil value actually returns its #object_id. If the associated object doesn’t exist, we don’t want to pass a bogus ID. Acts as Importable provides Object#try and the ActiveRecord::Base#id__ to id alias for you.

The lookups will work automatically if the class name of the model you’re importing is the same as the legacy model’s, eg. Legacy::Question to Question. If the class name of the model you’re creating is different, you can tell that to Acts as Importable so that the lookups can continue to work:

# New model
class DifferentThing < ActiveRecord::Base
end

# Legacy model
class Legacy::Thing
  acts_as_importable :to => 'DifferentThing'
  
  def to_model
    ::DifferentThing.new
  end
end

has_many Relationships & Building

You can import has_many relationships quite easily, using the #build method on the association proxy. Here it is in action, expanding on the Question’s #to_model method from above:

# New models
class Question < ActiveRecord::Base
  belongs_to :category
end
class Response < ActiveRecord::Base
  belongs_to :question
end

# Legacy model
class Legacy::Question < Legacy::Base
  def to_model
    ::Question.new do |q|
      q.category_id = Legacy::Category.lookup(self.category.try(:id__))
      
      # Build the responses
      (1..5).each do |i|
        q.responses.build(:body => self.send(:"response_#{i}"))
      end
    end
  end
end

When you save the new model, the newly built associated models are saved too.

Importing Large Sets of Legacy Records

We ran into some slowness importing legacy models with large numbers of records, or with other models with large amounts of data in particular fields. To get around this, we use #import_all_in_batches, which only retrieves 1000 models at a time for processing. This is based on Jamis Buck’s technique for faking cursors in ActiveRecord, and as such, it requires a numeric primary key for the legacy models (you’d normally expect this to be the case, but it isn’t for a few of our legacy tables).

Idempotence of Imports

As I mentioned in the introduction, the legacy app we’re replacing will remain in use as we incrementally build the new system. We will need to continue to synchronise the legacy data with the new system during this time. We’ll therefore need our import process to be idempotent, meaning that multiple imports can run and result in the same set of data at the other end. Mostly, this just means that we’ll want to avoid creation of duplicate records in the new database.

We approach this pretty simply. Each night the old records are deleted and new ones re-imported in their place. You’ll want to pick an approach that best suits your situation.

There is one complication, however, insofar as the new system is used to create some entirely new content that relates to the imported models. If models are deleted and re-imported from the legacy system every night, their IDs would be different each time. To get around this, we hard-code the ID of certain imported models to the same value as their respective legacy model’s ID. This is very simply done:

class Legacy::Question < Legacy::Base
  def to_model
    ::Question.new do |q|
      # Hard-code the ID
      q.id = self.id
      
      q.category_id = Legacy::Category.lookup(self.category.try(:id__))
      
      (1..5).each do |i|
        q.responses.build(:body => self.send(:"response_#{i}"))
      end
    end
  end
end

Final steps

There are two final pieces to our import system. Firstly, a way to control the order of the imports:

class Legacy::Importer
  def self.run
    Legacy::Category.import_all
    Legacy::Question.import_all_in_batches

    # Flush all the lookup tables
    Legacy::Category.flush_lookups!
    Legacy::Question.flush_lookups!
  end
end

It is important to control the order if your imported models are going to relate to each other. Some records will need to exist before the others can link to them.

And finally, a rake task:

namespace :legacy do
  desc "Import the legacy data."
  task :import => :environment do
    Legacy::Importer.run
  end
end

That’s it! This approach certainly works to our liking, but I would love to hear your thoughts on this issue. Please feel free to post a comment below.

Further Reading

These articles were of great use in getting our legacy imports up and running:

Adaptive script/console Shell Alias for both Rails and Sinatra

Like many keystroke-efficient Rails hackers, I’ve long had a line in my .bash_profile file to alias sc to script/console, along with a host of other tricks.

This shortcut was more than sufficient until recently, when I started writing Sinatra apps. The minimal framework that it is, Sinatra doesn’t provide a console script like Rails, but I found you can easily achieve the same effect by running irb -r your_sinatra_app.rb.

Not wanting my fingers to have to deviate from habits long held, I changed my sc alias into a full-blown bash function that will drop you into a Rails console, Sinatra console or just a plain irb console based on your location within the filesystem:

function sc {
  if [ -x script/console ]; then
    script/console
  else
    sinatra_rb=`egrep -l "^require.+sinatra.$" *.rb 2>/dev/null`
    if [ -e $sinatra_rb ]; then
      irb -r $sinatra_rb
    else
      irb
    fi
  fi
}

Throw it in your .bash_profile and have fun!

Ancsa's pictures in Canada

My cousin Anita from Hungary is staying with us for 6 weeks. View her photo journal at http://imgs.miklos.ca/archives/ancsa_2009/

June 30, 2009

Going to GUADEC

GUADEC Sponsored

Thanks to the amazing support from the GNOME Travel Committee, I will be attending my first GUADEC this year in Gran Canaria! I am very thankful for their commitment to accomodate my needs and I’ll try to make the most of it!

I’ll be arriving July 3rd around lunch time and will stay until the 11th, so there will be plenty of time to meet up and discuss. I’m mostly interested in localization, accessibility, and tests automation but am equaly interested in community building and the organization toward GNOME 3.0!

If you see some really fast crutches running around the terminals in Boston or Madrid, come by and say hi to me! :)

June 28, 2009

A Beginner's Guide to Free Software Programming Languages

Linux Planet asked me for an intro article for prospective programmers, explaining the pros and cons of various programming languages. Here it is: A Beginner's Guide to Free Software Programming Languages

June 24, 2009

Keeping external kernel modules from being deleted

I've been enjoying my random system beeps, different every day. At least up until yesterday, when I didn't seem to have one. Today I didn't have one either, and discovered that was because the beep module was no longer loaded.

Why not? Well, I updated my kernel to tweak some ACPI parameters (fruitlessly, as it turns out; I'm trying to get powertop to give me more information but I haven't found the magic combination of kernel parameters it wants on this machine) and so I did a make modules_install. And it seems that make modules_install starts out by doing rm -rf /lib/modules/VERSION/kernel which removed my externally built beep module along with everything else.

I couldn't find documentation on this, but I did find Intel Wireless bug 556 which talks about the issue. Apparently somewhere along the way 2.6 started doing this rm -rf, but you can get around it by installing outside the kernel directory.

In other words, instead of

cp beep.ko /lib/modules/2.6.29.4/kernel/drivers/input/misc/
do
cp beep.ko /lib/modules/2.6.29.4/drivers/input/misc/
Then your external module won't get wiped out at the next modules_install.

I've let the maintainer of Fancy Beeper know about this, so it won't be a problem for that module, but it's a good tip to know about in general -- I'm sure there are lots of modules that hit this problem.

June 23, 2009

All good things must come to an end, but all bad things can continue forever

It has come to my attention that I have generally knocked out a blag every month, it seems a shame to break this amazing run.

I have nothing much to reckon really, but I have started to learn how to use GIT, which is sort of interesting. I’m using github as it seems the easiest way to get started and i don’t know enough to overly criticism it in anyway. I am more or less incapable of coding to an acceptable standard, so my repos are all theme related. I hope to add a couple of little hacks when I move on to learning how to fork or whatever, and possibly some php frame work stuff - which isn’t real code ;) But right now I just need *something* to learn with.

It is mostly older stuff that I update a bit but never really publically make any ‘releases’

They are all here. There are a couple of icon themes (Eggshell should really be a branch of Ecru) and a meta-theme (metacity, openbox, gtk) and other themes maybe.

Anyway that is all.

June 21, 2009

Pytopo 0.8 released

On my last Mojave trip, I spent a lot of the evenings hacking on PyTopo.

I was going to try to stick to OpenStreetMap and other existing mapping applications like TangoGPS, a neat little smartphone app for downloading OpenStreetMap tiles that also runs on the desktop -- but really, there still isn't any mapping app that works well enough for exploring maps when you have no net connection.

In particular, uploading my GPS track logs after a day of mapping, I discovered that Tango really wasn't a good way of exploring them, and I already know Merkaartor, nice as it is for entering new OSM data, isn't very good at working offline. There I was, with PyTopo and a boring hotel room; I couldn't stop myself from tweaking a bit.

Adding tracklogs was gratifyingly easy. But other aspects of the code bother me, and when I started looking at what I might need to do to display those Tango/OSM tiles ... well, I've known for a while that some day I'd need to refactor PyTopo's code, and now was the time.

Surprisingly, I completed most of the refactoring on the trip. But even after the refactoring, displaying those OSM tiles turned out to be a lot harder than I'd hoped, because I couldn't find any reliable way of mapping a tile name to the coordinates of that tile. I haven't found any documentation on that anywhere, and Tango and several other programs all do it differently and get slightly different coordinates. That one problem was to occupy my spare time for weeks after I got home, and I still don't have it solved.

But meanwhile, the rest of the refactoring was done, nice features like track logs were working, and I've had to move on to other projects. I am going to finish the OSM tile MapCollection class, but why hold up a release with a lot of useful changes just for that?

So here's PyTopo 0.8, and the couple of known problems with the new features will have to wait for 0.9.

June 19, 2009

Python: show all methods in a given object or module

A silly little thing, but something that Python books mostly don't mention and I can never find via Google:

How do you find all the methods in a given class, object or module?

Ideally the documentation would tell you. Wouldn't that be nice? But in the real world, you can't count on that, and examining all of an object's available methods can often give you a good guess at how to do whatever you're trying to do.

Python objects keep their symbol table in a dictionary called __dict__ (that's two underscores on either end of the word). So just look at object.__dict__. If you just want the names of the functions, use object.__dict__.keys().

Thanks to JanC for suggesting dir(object) and help(object), which can be more helpful -- not all objects have a __dict__.

ugh

Well its getting hot, and nothing like having to wear long sleeves in your job when your outside all day in the blistering sun !!

June 18, 2009

Random beeps

A couple of year ago I figured out how to make custom system beep sounds on Linux, like MacOS has done forever. But then I changed machines and somehow never got around to setting it up on any other machine.

But the Intel dual-Atom board doesn't seem to support a system beep -- there's no obvious place on the motherboard to plug in the connector going to the case speaker. How odd!

With the alternative being no beep at all, I dusted off my old blog post and went to see if Fancy Beeper Daemon kernel module still existed. Happily, it does, and it's up-to-date for current kernels, so all I had to do was download the latest and build it. Easy! Then I added "beep" to the list of automatically loaded modules in /etc/modules, blacklisted the pcspkr module using the /etc/modprobe.d/00local technique, and I was all set.

Except for the really important question: what sound to choose? I did a little web searching for free sounds and downloaded some samples to try out. Then I added a few bird calls from my Stokes Field Guide to Western Bird Songs CD, editing them in audacity to make them shorter and more appropriate for system beeps.

But I still couldn't decide on just one ... and why should I? I've really been enjoying my random wallpaper: every time I log in, I get a different desktop background. It's fun to see a new picture every day. Why not do the same for my system beep?

That's no problem, using the same randomline script I use for wallpaper. I just put this in my .xinitrc:

$HOME/bin/mybeepd `find $HOME/Music/beeps -name "*.wav" | randomline` &
and now I get a different beep sound each day.

Yesterday it was a loon. Today it's a cow mooing.

June 14, 2009

Programming With PyGTK, part 3: Key events and object oriented Python

Part 3 of "Graphical Python Programming With PyGTK" uses object-oriented Python to clean up the code from Part 2, and also adds handling of key events to get rid of that silly Quit button. PythonGTK Programming part 3: Screensaver, Objects, and User Input

June 13, 2009

June 12, 2009

A Table of Closed versus Open Formats

My last Toastmasters speech was on open formats: why you should use open formats rather than closed/proprietary ones and the risks of closed formats.

To make it clearer, I wanted to print out handouts people could take home summarizing some of the most common closed formats, along with open alternatives.

Surely there are lots of such tables on the web, I thought. I'll just find one and customize it a little for this specific audience.

To my surprise, I couldn't find a single one. Even openformats.org didn't have very much.

So I started one: Open vs. Closed Formats. It's far from complete, so I hope I'll continue to get contributions to flesh it out more.

And the talk? It went over very well, and people appreciated the handout. There's a limit to how much information you can get across in under ten minutes, but I think I got the point across. The talk itself, such as it is, is here: Open up!

June 10, 2009

Bing thinks we're WHERE?

Lots has been written about Bing, Microsoft's new search engine. It's better than Google, it's worse than Google, it'll never catch up to Google. Farhad Manjoo of Slate had perhaps the best reason to use Bing: "If you switch, Google's going to do some awesome things to try to win you back."

[Bing in Omniweb thinks we're in Portugal] But what I want to know about Bing is this: Why does it think we're in Portugal when Dave runs it under Omniweb on Mac?

In every other browser it gives the screen you've probably seen, with side menus (and a horizontal scrollbar if your window isn't wide enough, ugh) and some sort of pretty picture as a background. In Omniweb, you get a cleaner layout with no sidebars or horizontal scrollbars, a different pretty picture -- often prettier than the one you get on all the other browsers, though both images change daily -- and a set of togglebuttons that don't show up in any of the other browsers, letting you restrict results to only English or only results from Portugal.

Why does it think we're in Portugal when Dave uses Omniweb?

Equally puzzling, why do only people in Portugal have the option of restricting the results to English only?

June 08, 2009

Three Dot Five

Celebrating my 35th birthday today! A year older, a year (hopefully) wiser, and every day glad to have the life I have! Can’t wait to see what the next year will bring! :)

No X acceleration (DRI) in Jaunty: solved

I upgraded to Ubuntu's current 9.04 release, "Jaunty Jackalope", quite a while ago, but I haven't been able to use it because its X server crashes or hangs regularly. (Fortunately I only upgraded a copy of my working 8.10 "Intrepid" install, on a separate partition.)

The really puzzling thing, though, wasn't the crashes, but the fact that X acceleration didn't work at all. Programs like tuxracer (etracer) and Google earth would display at something like one frame update every two seconds, and glxinfo | grep renderer said

OpenGL renderer string: Software Rasterizer

But that was all on my old desktop machine, with an ATI Radeon 9000 card that I know no one cares about much. I have a new machine now! An Intel dual Atom D945GCLF2D board with 945 graphics. Finally, a graphics chip that's supported! Now everything would work!

Well, not quite -- there were major teething pains, including returning the first nonworking motherboard, but that's a separate article. Eventually I got it running nicely with Intrepid. DRI worked! Tuxracer worked! Even Google Earth worked! Unbelievable!

I copied the Jaunty install from my old machine to a partition on the new machine. Booted into it and -- no DRI. Just like on the Radeon.

Now, there's a huge pile of bugs in Ubuntu's bug system on problems with video on Jaunty, all grouped by graphics card manufacturer even though everybody seems to be seeing pretty much the same problems on every chipset. But hardly any of the bugs talk about not getting any DRI at all -- they're all about whether EXA acceleration works better or worse than XAA and whether it's worth trying UXA. I tried them all: EXA and UXA both gave me no DRI, while XAA crashed/rebooted the machine every time. Clearly, there was something about my install that was disabling DRI, regardless of graphics card. But I poked and prodded and couldn't figure out what it was.

The breakthrough came when, purely by accident, I ran that same glxinfo | grep renderer from a root shell. Guess what?

OpenGL renderer string: Mesa DRI Intel(R) 945G GEM 20090326 2009Q1 RC2 x86/MMX/SSE2

As me (non-root), it still said "Software Rasterizer." It was a simple permissions problem! But wait ... doesn't X run as root?

Well, it does, but the DRI part doesn't, as it turns out. (This is actually a good thing, sort of, in the long term: eventually the hope is to get X not to need root permissions either.)

Armed with the keyword "permissions" I went back to the web, and the Troubleshooting Intel Performance page on the Ubuntu wiki, and found the solution right away. (I'd looked at that page before but never got past the part right at the beginning that says it's for problems involving EXA vs. UXA vs. XAA, which mine clearly wasn't).

The Solution

In Jaunty, the user has to be in group video to use DRI in X. But if you've upgraded from an Ubuntu version prior to Jaunty, where this wasn't required, you're probably not in that group. The upgrader (I used do-release-upgrade) doesn't check for this or warn you that you have desktop users who aren't in the video group, so you're on your own to find out about the problem. Fixing it is easy, though: edit /etc/group as root and add your user(s) to the group.

You might think this would have been an error worth reporting, say, at X startup, or in glxinfo, or even in /var/log/Xorg.0.log. You'd think wrong. Xorg.0.log blithely claims that DRI is enabled and everything is fine, and there's no indication of an error anywhere else.

I hope this article makes it easier for other people with this problem to find the solution.

June 07, 2009

another week

Well another week of work over with, whew. Mandatory OT coming up shortly so less and less time to do things a guy wants to do.

June 04, 2009

Bullfrogs in stereo

bullfrog The Walden West pond is hopping -- literally! This afternoon around 3pm the pond's resident bullfrogs, who normally just float quietly in the scum on the surface, would suddenly hop out of the water for no obvious reason, then settle back down a few feet away. One pair was apparently mating like that, the larger frog hopping onto the back of the smaller frog, then immediately off again. And the pond was full of sound, sometimes with two or more frogs booming at once. Bullfrogs in stereo!

I didn't have the SLR along, but some of the frogs were close enough (and calm enough not to submerge when we got near them) that I was able to get a few decent shots.

But I really wanted to capture that sound. So I put the camera in video mode and shot a series of videos hoping to catch some of the music ... and did. They sound like this: bullfrog (mp3, 24kb).

Despite the title of this entry, the recording doesn't have any interesting stereo effects; the only microphone was the one built in to my Canon A540. It did okay, though! You'll just have to use your imagination to place two frogs as you listen, one 20 feet to the left and the other 15 feet to the right.

How to extract the audio from a camera video

(Non open source people can quit reading here.)

Extracting the audio was a little tricky. I found lots of pages ostensibly telling me how to do it with mencoder, but none of them seemed to work. This did:

mplayer -vc null -af volume=15 -vo null -ao pcm -benchmark mvi_8992.avi

I added that -af volume=15 argument to make the sound louder, since it was a bit quiet as it came from the camera.

That produced a file named audiodump.wav, which I turned into an mp3 like this:

lame audiodump.wav bullfrogs.mp3

June 02, 2009

A GPX file manager

Someone on the OSM newbies list asked how he could strip waypoints out of a GPX track file. Seems he has track logs of an interesting and mostly-unmapped place that he wants to add to openstreetmap, but there are some waypoints that shouldn't be included, and he wanted a good way of separating them out before uploading.

Most of the replies involved "just edit the XML." Sure, GPX files are pretty simple and readable XML -- but a user shouldn't ever have to do that! Gpsman and gpsbabel were also mentioned, but they're not terribly easy to use either.

That reminded me that I had another XML-parsing task I'd been wanting to write in Python: a way to split track files from my Garmin GPS.

Sometimes, after a day of mapping, I end up with several track segments in the same track log file. Maybe I mapped several different trails; maybe I didn't get a chance to upload one day's mapping before going out the next day. Invariably some of the segments are of zero length (I don't know why the Garmin does that, but it always does). Applications like merkaartor don't like this one bit, so I usually end up editing the XML file and splitting it into segments by hand. I'm comfortable with XML -- but it's still silly.

I already have some basic XML parsing as part of PyTopo and Ellie, so I know the parsing very easy to do. So, spurred on by the posting on OSM-newbies, I wrote a little GPX parser/splitter called gpxmgr. gpxmgr -l file.gpx can show you how many track logs are in the file; gpxmgr -w file.gpx can write new files for each non-zero track log. Add -p if you want to be prompted for each filename (otherwise it'll use the name of the track log, which might be something like "ACTIVE\ LOG\ #2").

How, you may wonder, does that help the original poster's need to separate out waypoints from track files? It doesn't. See, my GPS won't save tracklogs and waypoints in the same file, even if you want them that way; you have to use two separate gpsbabel commands to upload a track file and a waypoint file. So I don't actually know what a tracklog-plus-waypoint file looks like. If anyone wants to use gpxmgr to manage waypoints as well as tracks, send me a sample GPX file that combines them both.

May 31, 2009

Faking Gnome

I was trying to get the adobe air iplayer thingy working in openbox, it turns out it is crap anyway.

But to find out that it is was a waste of time I had to figure out how to pretend I had a gnome session going on; so it would even bother to load in the first place.

Normally you will get the:
Unknown desktop manager, only Gnome and KDE are supported

It turns out gnome session doesn’t really do much in this area and the checks performed are poor and depreciated.

Anyway, you can just dump these in your openbox session or .xinitrc or somewhere?

export DESKTOP_SESSION="gnome"
export GNOME_DESKTOP_SESSION_ID="openbox"

The first line might not even be needed, but it is nice anyway.

Another side effect of this is that it will work for other apps (which may suck less than the iplayer app), notably tweetdeck which the kids love, apparently.

I guess you could make a new openbox session like this which would be very fake gnome-y.


#!/bin/sh
if test -n "$1"; then
echo "Syntax: openbox-session"
echo
echo "See the openbox-session(1) manpage for help."
exit
fi


export OOO_FORCE_DESKTOP="gnome"
export WINDOW_MANAGER="/usr/bin/openbox"
export DESKTOP_SESSION="gnome"
export GNOME_DESKTOP_SESSION_ID="openbox"


if test -e $AUTOSTART; then
. $AUTOSTART
else
if test -e $GLOBALAUTOSTART; then
. $GLOBALAUTOSTART
fi
fi


AUTOSTART="$HOME/.config/openbox/autostart.sh"
GLOBALAUTOSTART="/etc/xdg/openbox/autostart.sh"


exec /usr/bin/openbox "$@"

May 28, 2009

A Fistfull of News

Been quite busy at work these days, as well as playing the host to my parents during the holidays. I did keep up with what was happening around the world, thanks to my G1 (I love this gadget!), and wanted to share a few nice nuggets.

  • First of all, the birth of Lotte, the online translation editor! This new feature will help the Transifex guys deliver a fatal 1,2 punch combination in the fight against bad translation tools and processes!

  • As if Dimitris and his crew didn’t have enough on their plate (I tell you, they have macchiato running through their veins), a new fledgling web portal portal for translators is in the makings. Think of a place where translators can offers their skills to projects in need of translators and you can see where this could lead.
  • Still speaking of translations, hanging out on #transifex on Freenode I picked up that the Mercurial guys really like Transifex and what it has to offer their current legion of translators! Sounds like some experimenting may take place soon between these 2 projects (I’ll keep my fingers crossed!).
  • On a different subject, I have started working on a Snowy Appliance yesterday and will have something done and consumable by this weekend. Don’t know what snowy is? Then check Paul Cutler’s blog post and the links within it. Want to lend me a hand? Ping me on IRC, email, comment here, Twitter, etc, etc.
  • I’m running this year for the GNOME Board of Directors! Take a look at the list of candidates and if you’re a GNOME Foundation member, remember to vote! If you’re not a member, what are you waiting for?
  • My 35th birthday is fast approaching (June 8th actually) and I will be updating my wishlist juuuuust in case someone feels like surprising me, hint hint

May 26, 2009

Getting on the bus

I have been working on a little aggregator for my ‘homepage‘ which is a glorified index page to many directories of files and crap, but over time I have become fond of it and decided it might need some love. The best idea I could come up with was to aggregate all the feeds of sites I use, such as twitter, my blag and flickr (for now) as a sort of centralised hub.

I am going to have to re-assess all my hosting and whatnot at some point (for money reasons) and centralise everything in one place, I have a few domains but I will probably keep chalkskeletons, even though the comic/original idea is very dead, I have had it a while and i am fond of it.

I am currently auditioning a centralised bookmark things, obviously delicious is an option as is google bookmarks, but i am not sure what is out there these days?

Blog entries aggregated on this page are owned by, and represent the opinion of the author.
Hosted by Tim Riley
Administered (to a degree) by David Barr