Initial versioning.
authorFred Lee <PhaethonH@gmail.com>
Wed, 29 Jun 2011 08:32:07 -0700
changeset 0 5bc1cc448eb5
child 1 83b6104a256b
Initial versioning.
layouts.py
main.py
predefs.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layouts.py	Wed Jun 29 08:32:07 2011 -0700
@@ -0,0 +1,1787 @@
+
+# encoding=utf8
+
+keyboard = { }
+
+
+ATTR_SHIFTER = 0x0001
+
+
+keyboard['en/US 101'] = [
+  [
+    [ 142, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    # width, height, keysym (app-specific), keytop (displayed), flags
+    [ 6, 1, "esc", "Esc" ],
+    [ 5, 1, "", "" ],
+    [ 6, 1, "function-1", "F1" ],
+    [ 6, 1, "function-2", "F2" ],
+    [ 6, 1, "function-3", "F3" ],
+    [ 6, 1, "function-4", "F4" ],
+    [ 3, 1, "", "" ],
+    [ 6, 1, "function-5", "F5" ],
+    [ 6, 1, "function-6", "F6" ],
+    [ 6, 1, "function-7", "F7" ],
+    [ 6, 1, "function-8", "F8" ],
+    [ 3, 1, "", "" ],
+    [ 6, 1, "function-9", "F9" ],
+    [ 6, 1, "function-10", "F10" ],
+    [ 6, 1, "function-11", "F11" ],
+    [ 6, 1, "function-12", "F12" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "print", "Print" ],
+    [ 6, 1, "scroll", "Scroll" ],
+    [ 6, 1, "pause", "Pause" ],
+  ],
+
+  # Row spacing.
+  [ ],
+
+  # Row 2
+  [
+    # width, height, keysym, keytop, flags
+    [ 6, 1, "`", "`" ],
+    [ 6, 1, "1", "1" ],
+    [ 6, 1, "2", "2" ],
+    [ 6, 1, "3", "3" ],
+    [ 6, 1, "4", "4" ],
+    [ 6, 1, "5", "5" ],
+    [ 6, 1, "6", "6" ],
+    [ 6, 1, "7", "7" ],
+    [ 6, 1, "8", "8" ],
+    [ 6, 1, "9", "9" ],
+    [ 6, 1, "0", "0" ],
+    [ 6, 1, "-", "-" ],
+    [ 6, 1, "=", "=" ],
+    [ 6, 1, "\\", "\\" ],
+    [ 6, 1, "backspace", "BS" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-insert", "Ins" ],
+    [ 6, 1, "cursor-home", "Home" ],
+    [ 6, 1, "cursor-pagedown", "PgUp" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "numlock", "NL" ],
+    [ 6, 1, "keypad-divide", "/" ],
+    [ 6, 1, "keypad-multiply", "*" ],
+    [ 6, 1, "keypad-minus", "-" ],
+  ],
+
+  # Row 3
+  [
+    [ 9, 1, "tab", "Tab" ],
+    [ 6, 1, "q", "Q" ],
+    [ 6, 1, "w", "W" ],
+    [ 6, 1, "e", "E" ],
+    [ 6, 1, "r", "R" ],
+    [ 6, 1, "t", "T" ],
+    [ 6, 1, "y", "Y" ],
+    [ 6, 1, "u", "U" ],
+    [ 6, 1, "i", "I" ],
+    [ 6, 1, "o", "O" ],
+    [ 6, 1, "p", "P" ],
+    [ 6, 1, "[", "[" ],
+    [ 6, 1, "]", "]" ],
+    [ 9, 1, "\\", "\\" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-delete", "Del" ],
+    [ 6, 1, "cursor-end", "End" ],
+    [ 6, 1, "cursor-pagedown", "PgDn" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-7", "7" ],
+    [ 6, 1, "keypad-8", "8" ],
+    [ 6, 1, "keypad-9", "9" ],
+    [ 6, 2, "keypad-plus", "+" ],
+  ],
+
+  # Row 4
+  [
+    [ 11, 1, "", "Caps" ],
+    [ 6, 1, "a", "A" ],
+    [ 6, 1, "s", "S" ],
+    [ 6, 1, "d", "D" ],
+    [ 6, 1, "f", "F" ],
+    [ 6, 1, "g", "G" ],
+    [ 6, 1, "h", "H" ],
+    [ 6, 1, "j", "J" ],
+    [ 6, 1, "k", "K" ],
+    [ 6, 1, "l", "L" ],
+    [ 6, 1, ";", ";" ],
+    [ 6, 1, "'", "'" ],
+    [ 13, 1, "return", "Enter" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-4", "4" ],
+    [ 6, 1, "keypad-5", "5" ],
+    [ 6, 1, "keypad-6", "6" ],
+  ],
+
+  # Row 5
+  [
+    [ 13, 1, "shift", "Shift" ],
+    [ 6, 1, "z", "Z" ],
+    [ 6, 1, "x", "X" ],
+    [ 6, 1, "c", "C" ],
+    [ 6, 1, "v", "V" ],
+    [ 6, 1, "b", "B" ],
+    [ 6, 1, "n", "N" ],
+    [ 6, 1, "m", "M" ],
+    [ 6, 1, ",", "," ],
+    [ 6, 1, ".", "." ],
+    [ 6, 1, "/", "/" ],
+    [ 17, 1, "shift", "Shift" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "cursor-up", "Up" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-1", "1" ],
+    [ 6, 1, "keypad-2", "2" ],
+    [ 6, 1, "keypad-3", "3" ],
+    [ 6, 2, "keypad-enter", "RET" ],
+  ],
+
+  # Row 6
+  [
+    [ 8, 1, "ctrl", "Ctrl" ],
+    [ 7, 1, "", "" ],
+    [ 8, 1, "alt", "Alt" ],
+    [ 44, 1, "space", "Space" ],
+    [ 8, 1, "alt", "Alt" ],
+    [ 7, 1, "", "" ],
+    [ 8, 1, "ctrl", "Ctrl" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-left", "Left" ],
+    [ 6, 1, "cursor-down", "Down" ],
+    [ 6, 1, "cursor-right", "Right" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 12, 1, "keypad-0", "0" ],
+    [ 6, 1, "keypad-.", "." ],
+  ]
+]
+
+
+keyboard['en/US 104'] = [
+  [
+    [ 142, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    # width, height, keysym (app-specific), keytop (displayed), flags
+    [ 6, 1, "esc", "Esc" ],
+    [ 4, 1, "", "" ],
+    [ 6, 1, "function-1", "F1" ],
+    [ 6, 1, "function-2", "F2" ],
+    [ 6, 1, "function-3", "F3" ],
+    [ 6, 1, "function-4", "F4" ],
+    [ 4, 1, "", "" ],
+    [ 6, 1, "function-5", "F5" ],
+    [ 6, 1, "function-6", "F6" ],
+    [ 6, 1, "function-7", "F7" ],
+    [ 6, 1, "function-8", "F8" ],
+    [ 4, 1, "", "" ],
+    [ 6, 1, "function-9", "F9" ],
+    [ 6, 1, "function-10", "F10" ],
+    [ 6, 1, "function-11", "F11" ],
+    [ 6, 1, "function-12", "F12" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "print", "Print" ],
+    [ 6, 1, "scroll", "Scroll" ],
+    [ 6, 1, "pause", "Pause" ],
+  ],
+
+  # Row spacing.
+  [ ],
+
+  # Row 2
+  [
+    # width, height, keysym, keytop, flags
+    [ 6, 1, "`", "`" ],
+    [ 6, 1, "1", "1" ],
+    [ 6, 1, "2", "2" ],
+    [ 6, 1, "3", "3" ],
+    [ 6, 1, "4", "4" ],
+    [ 6, 1, "5", "5" ],
+    [ 6, 1, "6", "6" ],
+    [ 6, 1, "7", "7" ],
+    [ 6, 1, "8", "8" ],
+    [ 6, 1, "9", "9" ],
+    [ 6, 1, "0", "0" ],
+    [ 6, 1, "-", "-" ],
+    [ 6, 1, "=", "=" ],
+    [ 6, 1, "\\", "\\" ],
+    [ 6, 1, "backspace", "BS" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-insert", "Ins" ],
+    [ 6, 1, "cursor-home", "Home" ],
+    [ 6, 1, "cursor-pagedown", "PgUp" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "numlock", "NL" ],
+    [ 6, 1, "keypad-divide", "/" ],
+    [ 6, 1, "keypad-multiply", "*" ],
+    [ 6, 1, "keypad-minus", "-" ],
+  ],
+
+  # Row 3
+  [
+    [ 9, 1, "tab", "Tab" ],
+    [ 6, 1, "q", "Q" ],
+    [ 6, 1, "w", "W" ],
+    [ 6, 1, "e", "E" ],
+    [ 6, 1, "r", "R" ],
+    [ 6, 1, "t", "T" ],
+    [ 6, 1, "y", "Y" ],
+    [ 6, 1, "u", "U" ],
+    [ 6, 1, "i", "I" ],
+    [ 6, 1, "o", "O" ],
+    [ 6, 1, "p", "P" ],
+    [ 6, 1, "[", "[" ],
+    [ 6, 1, "]", "]" ],
+    [ 9, 1, "\\", "\\" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-delete", "Del" ],
+    [ 6, 1, "cursor-end", "End" ],
+    [ 6, 1, "cursor-pagedown", "PgDn" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-7", "7" ],
+    [ 6, 1, "keypad-8", "8" ],
+    [ 6, 1, "keypad-9", "9" ],
+    [ 6, 2, "keypad-plus", "+" ],
+  ],
+
+  # Row 4
+  [
+    [ 11, 1, "", "Caps" ],
+    [ 6, 1, "a", "A" ],
+    [ 6, 1, "s", "S" ],
+    [ 6, 1, "d", "D" ],
+    [ 6, 1, "f", "F" ],
+    [ 6, 1, "g", "G" ],
+    [ 6, 1, "h", "H" ],
+    [ 6, 1, "j", "J" ],
+    [ 6, 1, "k", "K" ],
+    [ 6, 1, "l", "L" ],
+    [ 6, 1, ";", ";" ],
+    [ 6, 1, "'", "'" ],
+    [ 13, 1, "return", "Enter" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-4", "4" ],
+    [ 6, 1, "keypad-5", "5" ],
+    [ 6, 1, "keypad-6", "6" ],
+  ],
+
+  # Row 5
+  [
+    [ 13, 1, "shift", "Shift" ],
+    [ 6, 1, "z", "Z" ],
+    [ 6, 1, "x", "X" ],
+    [ 6, 1, "c", "C" ],
+    [ 6, 1, "v", "V" ],
+    [ 6, 1, "b", "B" ],
+    [ 6, 1, "n", "N" ],
+    [ 6, 1, "m", "M" ],
+    [ 6, 1, ",", "," ],
+    [ 6, 1, ".", "." ],
+    [ 6, 1, "/", "/" ],
+    [ 17, 1, "shift", "Shift" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "cursor-up", "Up" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-1", "1" ],
+    [ 6, 1, "keypad-2", "2" ],
+    [ 6, 1, "keypad-3", "3" ],
+    [ 6, 2, "keypad-enter", "RET" ],
+  ],
+
+  # Row 6
+  [
+    [ 8, 1, "ctrl", "Ctrl" ],
+    [ 7, 1, "logo", "Logo" ],
+    [ 8, 1, "alt", "Alt" ],
+    [ 37, 1, "space", "Space" ],
+    [ 8, 1, "alt", "Alt" ],
+    [ 7, 1, "logo", "Logo" ],
+    [ 7, 1, "menu", "Menu" ],
+    [ 8, 1, "ctrl", "Ctrl" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-left", "Left" ],
+    [ 6, 1, "cursor-down", "Down" ],
+    [ 6, 1, "cursor-right", "Right" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 12, 1, "keypad-0", "0" ],
+    [ 6, 1, "keypad-.", "." ],
+  ]
+]
+
+
+
+keyboard['en/US-dvorak 104'] = [
+  [
+    [ 142, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    # width, height, keysym (app-specific), keytop (displayed), flags
+    [ 6, 1, "esc", "Esc" ],
+    [ 5, 1, "", "" ],
+    [ 6, 1, "function-1", "F1" ],
+    [ 6, 1, "function-2", "F2" ],
+    [ 6, 1, "function-3", "F3" ],
+    [ 6, 1, "function-4", "F4" ],
+    [ 3, 1, "", "" ],
+    [ 6, 1, "function-5", "F5" ],
+    [ 6, 1, "function-6", "F6" ],
+    [ 6, 1, "function-7", "F7" ],
+    [ 6, 1, "function-8", "F8" ],
+    [ 3, 1, "", "" ],
+    [ 6, 1, "function-9", "F9" ],
+    [ 6, 1, "function-10", "F10" ],
+    [ 6, 1, "function-11", "F11" ],
+    [ 6, 1, "function-12", "F12" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "print", "Print" ],
+    [ 6, 1, "scroll", "Scroll" ],
+    [ 6, 1, "pause", "Pause" ],
+  ],
+
+  # Row spacing.
+  [ ],
+
+  # Row 2
+  [
+    # width, height, keysym, keytop, flags
+    [ 6, 1, "`", "`" ],
+    [ 6, 1, "1", "1" ],
+    [ 6, 1, "2", "2" ],
+    [ 6, 1, "3", "3" ],
+    [ 6, 1, "4", "4" ],
+    [ 6, 1, "5", "5" ],
+    [ 6, 1, "6", "6" ],
+    [ 6, 1, "7", "7" ],
+    [ 6, 1, "8", "8" ],
+    [ 6, 1, "9", "9" ],
+    [ 6, 1, "0", "0" ],
+    [ 6, 1, "[", "[" ],
+    [ 6, 1, "]", "]" ],
+    [ 12, 1, "backspace", "BS" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-insert", "Ins" ],
+    [ 6, 1, "cursor-home", "Home" ],
+    [ 6, 1, "cursor-pagedown", "PgUp" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "numlock", "NL" ],
+    [ 6, 1, "keypad-divide", "/" ],
+    [ 6, 1, "keypad-multiply", "*" ],
+    [ 6, 1, "keypad-minus", "-" ],
+  ],
+
+  # Row 3
+  [
+    [ 9, 1, "tab", "Tab" ],
+    [ 6, 1, "'", "'" ],
+    [ 6, 1, ",", "," ],
+    [ 6, 1, ".", "." ],
+    [ 6, 1, "p", "P" ],
+    [ 6, 1, "y", "Y" ],
+    [ 6, 1, "f", "F" ],
+    [ 6, 1, "g", "G" ],
+    [ 6, 1, "c", "C" ],
+    [ 6, 1, "r", "R" ],
+    [ 6, 1, "l", "L" ],
+    [ 6, 1, "/", "/" ],
+    [ 6, 1, "=", "=" ],
+    [ 9, 1, "\\", "\\" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-delete", "Del" ],
+    [ 6, 1, "cursor-end", "End" ],
+    [ 6, 1, "cursor-pagedown", "PgDn" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-7", "7" ],
+    [ 6, 1, "keypad-8", "8" ],
+    [ 6, 1, "keypad-9", "9" ],
+    [ 6, 2, "keypad-plus", "+" ],
+  ],
+
+  # Row 4
+  [
+    [ 11, 1, "", "Caps" ],
+    [ 6, 1, "a", "A" ],
+    [ 6, 1, "o", "O" ],
+    [ 6, 1, "e", "E" ],
+    [ 6, 1, "u", "U" ],
+    [ 6, 1, "i", "I" ],
+    [ 6, 1, "d", "D" ],
+    [ 6, 1, "h", "H" ],
+    [ 6, 1, "t", "T" ],
+    [ 6, 1, "n", "N" ],
+    [ 6, 1, "s", "S" ],
+    [ 6, 1, "-", "-" ],
+    [ 13, 1, "return", "Enter" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-4", "4" ],
+    [ 6, 1, "keypad-5", "5" ],
+    [ 6, 1, "keypad-6", "6" ],
+  ],
+
+  # Row 5
+  [
+    [ 13, 1, "shift", "Shift" ],
+    [ 6, 1, ";", ";" ],
+    [ 6, 1, "q", "Q" ],
+    [ 6, 1, "j", "J" ],
+    [ 6, 1, "k", "K" ],
+    [ 6, 1, "x", "X" ],
+    [ 6, 1, "b", "B" ],
+    [ 6, 1, "m", "M" ],
+    [ 6, 1, "w", "W" ],
+    [ 6, 1, "v", "V" ],
+    [ 6, 1, "z", "Z" ],
+    [ 17, 1, "shift", "Shift" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "cursor-up", "Up" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-1", "1" ],
+    [ 6, 1, "keypad-2", "2" ],
+    [ 6, 1, "keypad-3", "3" ],
+    [ 6, 2, "keypad-enter", "RET" ],
+  ],
+
+  # Row 6
+  [
+    [ 8, 1, "ctrl", "Ctrl" ],
+    [ 7, 1, "logo", "Logo" ],
+    [ 8, 1, "alt", "Alt" ],
+    [ 37, 1, "space", "Space" ],
+    [ 8, 1, "alt", "Alt" ],
+    [ 7, 1, "logo", "Logo" ],
+    [ 7, 1, "menu", "Menu" ],
+    [ 8, 1, "ctrl", "Ctrl" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-left", "Left" ],
+    [ 6, 1, "cursor-down", "Down" ],
+    [ 6, 1, "cursor-right", "Right" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 12, 1, "keypad-0", "0" ],
+    [ 6, 1, "keypad-.", "." ],
+  ]
+]
+
+
+
+keyboard['en/US AppleProAlFull' ] = [
+  [
+    [ 142, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    # width, height, keysym (app-specific), keytop (displayed), flags
+    [ 6, 1, "esc", "Esc" ],
+    [ 7, 1, "function-1", "F1" ],
+    [ 6, 1, "function-2", "F2" ],
+    [ 6, 1, "function-3", "F3" ],
+    [ 6, 1, "function-4", "F4" ],
+    [ 6, 1, "function-5", "F5" ],
+    [ 6, 1, "function-6", "F6" ],
+    [ 6, 1, "function-7", "F7" ],
+    [ 6, 1, "function-8", "F8" ],
+    [ 7, 1, "function-9", "F9" ],
+    [ 6, 1, "function-10", "F10" ],
+    [ 6, 1, "function-11", "F11" ],
+    [ 6, 1, "function-12", "F12" ],
+    [ 6, 1, "eject", "eject" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "function-13", "F13" ],
+    [ 6, 1, "function-14", "F14" ],
+    [ 6, 1, "function-15", "F15" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "function-16", "F16" ],
+    [ 6, 1, "function-17", "F17" ],
+    [ 6, 1, "function-18", "F18" ],
+    [ 6, 1, "function-19", "F19" ],
+  ],
+
+  # Row 2
+  [
+    # width, height, keysym, keytop, flags
+    [ 6, 1, "`", "`" ],
+    [ 6, 1, "1", "1" ],
+    [ 6, 1, "2", "2" ],
+    [ 6, 1, "3", "3" ],
+    [ 6, 1, "4", "4" ],
+    [ 6, 1, "5", "5" ],
+    [ 6, 1, "6", "6" ],
+    [ 6, 1, "7", "7" ],
+    [ 6, 1, "8", "8" ],
+    [ 6, 1, "9", "9" ],
+    [ 6, 1, "0", "0" ],
+    [ 6, 1, "-", "-" ],
+    [ 6, 1, "=", "=" ],
+    [ 8, 1, "backspace", "BS" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "Fn" ],
+    [ 6, 1, "cursor-home", "Home" ],
+    [ 6, 1, "cursor-pagedown", "PgUp" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-clear", "clear" ],
+    [ 6, 1, "keypad-equal", "=" ],
+    [ 6, 1, "keypad-divide", "/" ],
+    [ 6, 1, "keypad-multiply", "*" ],
+#    [ 6, 1, "keypad-minus", "-" ],
+  ],
+
+  # Row 3
+  [
+    [ 9, 1, "tab", "Tab" ],
+    [ 6, 1, "q", "Q" ],
+    [ 6, 1, "w", "W" ],
+    [ 6, 1, "e", "E" ],
+    [ 6, 1, "r", "R" ],
+    [ 6, 1, "t", "T" ],
+    [ 6, 1, "y", "Y" ],
+    [ 6, 1, "u", "U" ],
+    [ 6, 1, "i", "I" ],
+    [ 6, 1, "o", "O" ],
+    [ 6, 1, "p", "P" ],
+    [ 6, 1, "[", "[" ],
+    [ 6, 1, "]", "]" ],
+    [ 5, 1, "\\", "\\" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-delete", "Del" ],
+    [ 6, 1, "cursor-end", "End" ],
+    [ 6, 1, "cursor-pagedown", "PgDn" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-7", "7" ],
+    [ 6, 1, "keypad-8", "8" ],
+    [ 6, 1, "keypad-9", "9" ],
+#    [ 6, 2, "keypad-plus", "+" ],
+    [ 6, 1, "keypad-plus", "-" ],
+  ],
+
+  # Row 4
+  [
+    [ 11, 1, "", "Caps" ],
+    [ 6, 1, "a", "A" ],
+    [ 6, 1, "s", "S" ],
+    [ 6, 1, "d", "D" ],
+    [ 6, 1, "f", "F" ],
+    [ 6, 1, "g", "G" ],
+    [ 6, 1, "h", "H" ],
+    [ 6, 1, "j", "J" ],
+    [ 6, 1, "k", "K" ],
+    [ 6, 1, "l", "L" ],
+    [ 6, 1, ";", ";" ],
+    [ 6, 1, "'", "'" ],
+    [ 9, 1, "return", "Return" ],
+    [ 6, 1, "cursor-insert", "Ins" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-4", "4" ],
+    [ 6, 1, "keypad-5", "5" ],
+    [ 6, 1, "keypad-6", "6" ],
+    [ 6, 1, "keypad-add", "+" ],
+  ],
+
+  # Row 5
+  [
+    [ 13, 1, "shift", "Shift" ],
+    [ 6, 1, "z", "Z" ],
+    [ 6, 1, "x", "X" ],
+    [ 6, 1, "c", "C" ],
+    [ 6, 1, "v", "V" ],
+    [ 6, 1, "b", "B" ],
+    [ 6, 1, "n", "N" ],
+    [ 6, 1, "m", "M" ],
+    [ 6, 1, ",", "," ],
+    [ 6, 1, ".", "." ],
+    [ 6, 1, "/", "/" ],
+    [ 13, 1, "shift", "Shift" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "cursor-up", "Up" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-1", "1" ],
+    [ 6, 1, "keypad-2", "2" ],
+    [ 6, 1, "keypad-3", "3" ],
+    [ 6, 2, "keypad-enter", "ENT" ],
+  ],
+
+  # Row 6
+  [
+    [ 9, 1, "ctrl", "Ctrl" ],
+    [ 7, 1, "alt", "Opt" ],
+    [ 9, 1, "command", "Cmd" ],
+    [ 36, 1, "space", "Space" ],
+    [ 9, 1, "command", "Cmd" ],
+    [ 7, 1, "alt", "Opt" ],
+    [ 9, 1, "ctrl", "Ctrl" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-left", "Left" ],
+    [ 6, 1, "cursor-down", "Down" ],
+    [ 6, 1, "cursor-right", "Right" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 12, 1, "keypad-0", "0" ],
+    [ 6, 1, "keypad-.", "." ],
+  ]
+]
+
+
+
+keyboard['en/US HappyHacking' ] = [
+  [
+    [ 142, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    # width, height, keysym (app-specific), keytop (displayed), flags
+    [ 6, 1, "esc", "Esc" ],
+    [ 6, 1, "1", "1" ],
+    [ 6, 1, "2", "2" ],
+    [ 6, 1, "3", "3" ],
+    [ 6, 1, "4", "4" ],
+    [ 6, 1, "5", "5" ],
+    [ 6, 1, "6", "6" ],
+    [ 6, 1, "7", "7" ],
+    [ 6, 1, "8", "8" ],
+    [ 6, 1, "9", "9" ],
+    [ 6, 1, "0", "0" ],
+    [ 6, 1, "-", "-" ],
+    [ 6, 1, "=", "=" ],
+    [ 6, 1, "\\", "\\" ],
+    [ 6, 1, "`", "`" ],
+  ],
+
+  # Row 3
+  [
+    [ 9, 1, "tab", "Tab" ],
+    [ 6, 1, "q", "Q" ],
+    [ 6, 1, "w", "W" ],
+    [ 6, 1, "e", "E" ],
+    [ 6, 1, "r", "R" ],
+    [ 6, 1, "t", "T" ],
+    [ 6, 1, "y", "Y" ],
+    [ 6, 1, "u", "U" ],
+    [ 6, 1, "i", "I" ],
+    [ 6, 1, "o", "O" ],
+    [ 6, 1, "p", "P" ],
+    [ 6, 1, "[", "[" ],
+    [ 6, 1, "]", "]" ],
+    [ 9, 1, "cursor-delete", "Delete" ],
+  ],
+
+  # Row 4
+  [
+    [ 11, 1, "ctrl", "Control" ],
+    [ 6, 1, "a", "A" ],
+    [ 6, 1, "s", "S" ],
+    [ 6, 1, "d", "D" ],
+    [ 6, 1, "f", "F" ],
+    [ 6, 1, "g", "G" ],
+    [ 6, 1, "h", "H" ],
+    [ 6, 1, "j", "J" ],
+    [ 6, 1, "k", "K" ],
+    [ 6, 1, "l", "L" ],
+    [ 6, 1, ";", ";" ],
+    [ 6, 1, "'", "'" ],
+    [ 13, 1, "return", "Return" ],
+  ],
+
+  # Row 5
+  [
+    [ 13, 1, "shift", "Shift" ],
+    [ 6, 1, "z", "Z" ],
+    [ 6, 1, "x", "X" ],
+    [ 6, 1, "c", "C" ],
+    [ 6, 1, "v", "V" ],
+    [ 6, 1, "b", "B" ],
+    [ 6, 1, "n", "N" ],
+    [ 6, 1, "m", "M" ],
+    [ 6, 1, ",", "," ],
+    [ 6, 1, ".", "." ],
+    [ 6, 1, "/", "/" ],
+    [ 11, 1, "shift", "Shift" ],
+    [ 6, 1, "", "Fn" ],
+  ],
+
+  # Row 6
+  [
+    [ 9, 1, "", "" ],
+    [ 7, 1, "alt", "Alt" ],
+    [ 9, 1, "meta", "Meta" ],
+    [ 34, 1, "space", "Space" ],
+    [ 9, 1, "meta", "Meta" ],
+    [ 7, 1, "alt", "Alt" ],
+    [ 9, 1, "", "" ],
+  ]
+]
+
+
+keyboard['en/US HappyHackingPro2' ] = [
+  [
+    [ 142, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    # width, height, keysym (app-specific), keytop (displayed), flags
+    [ 6, 1, "esc", " " ],
+    [ 6, 1, "1", " " ],
+    [ 6, 1, "2", " " ],
+    [ 6, 1, "3", " " ],
+    [ 6, 1, "4", " " ],
+    [ 6, 1, "5", " " ],
+    [ 6, 1, "6", " " ],
+    [ 6, 1, "7", " " ],
+    [ 6, 1, "8", " " ],
+    [ 6, 1, "9", " " ],
+    [ 6, 1, "0", " " ],
+    [ 6, 1, "-", " " ],
+    [ 6, 1, "=", " " ],
+    [ 6, 1, "\\", " " ],
+    [ 6, 1, "`", " " ],
+  ],
+
+  # Row 3
+  [
+    [ 9, 1, "tab", " " ],
+    [ 6, 1, "q", " " ],
+    [ 6, 1, "w", " " ],
+    [ 6, 1, "e", " " ],
+    [ 6, 1, "r", " " ],
+    [ 6, 1, "t", " " ],
+    [ 6, 1, "y", " " ],
+    [ 6, 1, "u", " " ],
+    [ 6, 1, "i", " " ],
+    [ 6, 1, "o", " " ],
+    [ 6, 1, "p", " " ],
+    [ 6, 1, "[", " " ],
+    [ 6, 1, "]", " " ],
+    [ 9, 1, "cursor-delete", " " ],
+  ],
+
+  # Row 4
+  [
+    [ 11, 1, "ctrl", " " ],
+    [ 6, 1, "a", " " ],
+    [ 6, 1, "s", " " ],
+    [ 6, 1, "d", " " ],
+    [ 6, 1, "f", " " ],
+    [ 6, 1, "g", " " ],
+    [ 6, 1, "h", " " ],
+    [ 6, 1, "j", " " ],
+    [ 6, 1, "k", " " ],
+    [ 6, 1, "l", " " ],
+    [ 6, 1, ";", " " ],
+    [ 6, 1, "'", " " ],
+    [ 13, 1, "return", " " ],
+  ],
+
+  # Row 5
+  [
+    [ 13, 1, "shift", " " ],
+    [ 6, 1, "z", " " ],
+    [ 6, 1, "x", " " ],
+    [ 6, 1, "c", " " ],
+    [ 6, 1, "v", " " ],
+    [ 6, 1, "b", " " ],
+    [ 6, 1, "n", " " ],
+    [ 6, 1, "m", " " ],
+    [ 6, 1, ",", " " ],
+    [ 6, 1, ".", " " ],
+    [ 6, 1, "/", " " ],
+    [ 11, 1, "shift", " " ],
+    [ 6, 1, "", " " ],
+  ],
+
+  # Row 6
+  [
+    [ 9, 1, "", "" ],
+    [ 7, 1, "alt", " " ],
+    [ 9, 1, "meta", " " ],
+    [ 34, 1, "space", " " ],
+    [ 9, 1, "meta", " " ],
+    [ 7, 1, "alt", " " ],
+    [ 9, 1, "", "" ],
+  ]
+]
+
+
+
+keyboard['en/US HappyHackingLite2' ] = [
+  [
+    [ 142, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    # width, height, keysym (app-specific), keytop (displayed), flags
+    [ 92, 1, "", "" ],
+    [ 1, 1, "", "" ],
+    [ 6, 1, "function-1", "F1" ],
+    [ 6, 1, "function-2", "F2" ],
+    [ 6, 1, "function-3", "F3" ],
+    [ 6, 1, "function-4", "F4" ],
+    [ 6, 1, "function-5", "F5" ],
+    [ 6, 1, "function-6", "F6" ],
+    [ 6, 1, "function-7", "F7" ],
+  ],
+
+  # Row 2
+  [
+    # width, height, keysym (app-specific), keytop (displayed), flags
+    [ 6, 1, "esc", "Esc" ],
+    [ 6, 1, "1", "1" ],
+    [ 6, 1, "2", "2" ],
+    [ 6, 1, "3", "3" ],
+    [ 6, 1, "4", "4" ],
+    [ 6, 1, "5", "5" ],
+    [ 6, 1, "6", "6" ],
+    [ 6, 1, "7", "7" ],
+    [ 6, 1, "8", "8" ],
+    [ 6, 1, "9", "9" ],
+    [ 6, 1, "0", "0" ],
+    [ 6, 1, "-", "-" ],
+    [ 6, 1, "=", "=" ],
+    [ 6, 1, "\\", "\\" ],
+    [ 6, 1, "`", "`" ],
+
+    [ 3, 1, "", "" ],
+    [ 2, 1, "", "" ],
+    [ 6, 1, "function-8", "F8" ],
+    [ 6, 1, "function-9", "F9" ],
+    [ 6, 1, "function-10", "F10" ],
+    [ 6, 1, "function-11", "F11" ],
+    [ 6, 1, "function-12", "F12" ],
+    [ 6, 1, "cursor-insert", "Ins" ],
+    [ 6, 1, "cursor-delete", "Del" ],
+  ],
+
+  # Row 3
+  [
+    [ 9, 1, "tab", "Tab" ],
+    [ 6, 1, "q", "Q" ],
+    [ 6, 1, "w", "W" ],
+    [ 6, 1, "e", "E" ],
+    [ 6, 1, "r", "R" ],
+    [ 6, 1, "t", "T" ],
+    [ 6, 1, "y", "Y" ],
+    [ 6, 1, "u", "U" ],
+    [ 6, 1, "i", "I" ],
+    [ 6, 1, "o", "O" ],
+    [ 6, 1, "p", "P" ],
+    [ 6, 1, "[", "[" ],
+    [ 6, 1, "]", "]" ],
+    [ 9, 1, "cursor-delete", "Delete" ],
+
+    [ 3, 1, "", "" ],
+    [ 5, 1, "", "U" ],
+    [ 6, 1, "print", "Print" ],
+    [ 6, 1, "scroll", "Scroll Lock" ],
+    [ 6, 1, "pause", "Pause" ],
+    [ 6, 1, "up", "Up" ],
+    [ 6, 1, "", "]" ],
+    [ 9, 1, "backspace", "BS" ],
+  ],
+
+  # Row 4
+  [
+    [ 11, 1, "ctrl", "Control" ],
+    [ 6, 1, "a", "A" ],
+    [ 6, 1, "s", "S" ],
+    [ 6, 1, "d", "D" ],
+    [ 6, 1, "f", "F" ],
+    [ 6, 1, "g", "G" ],
+    [ 6, 1, "h", "H" ],
+    [ 6, 1, "j", "J" ],
+    [ 6, 1, "k", "K" ],
+    [ 6, 1, "l", "L" ],
+    [ 6, 1, ";", ";" ],
+    [ 6, 1, "'", "'" ],
+    [ 13, 1, "return", "Return" ],
+
+    [ 3, 1, "", "" ],
+    [ 1, 1, "", "" ],
+    [ 6, 1, "", "J" ],
+    [ 6, 1, "cursor-home", "Home" ],
+    [ 6, 1, "cursor-pageup", "PgUp" ],
+    [ 6, 1, "cursor-left", "Left" ],
+    [ 6, 1, "cursor-right", "Right" ],
+    [ 13, 1, "", "Enter" ],
+  ],
+
+  # Row 5
+  [
+    [ 13, 1, "shift", "Shift" ],
+    [ 6, 1, "z", "Z" ],
+    [ 6, 1, "x", "X" ],
+    [ 6, 1, "c", "C" ],
+    [ 6, 1, "v", "V" ],
+    [ 6, 1, "b", "B" ],
+    [ 6, 1, "n", "N" ],
+    [ 6, 1, "m", "M" ],
+    [ 6, 1, ",", "," ],
+    [ 6, 1, ".", "." ],
+    [ 6, 1, "/", "/" ],
+    [ 11, 1, "shift", "Shift" ],
+    [ 6, 1, "", "Fn" ],
+
+    [ 3, 1, "", "" ],
+    [ 3, 1, "", "" ],
+    [ 6, 1, "", "M" ],
+    [ 6, 1, "cursor-end", "End" ],
+    [ 6, 1, "cursor-pagedown", "PgDn" ],
+    [ 6, 1, "cursor-down", "Down" ],
+    [ 11, 1, "", "Shift" ],
+    [ 6, 1, "", "Fn" ],
+  ],
+
+  # Row 6
+  [
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "Fn" ],
+    [ 6, 1, "alt", "Alt" ],
+    [ 6, 1, "meta", "Meta" ],
+    [ 35, 1, "space", "Space" ],
+    [ 6, 1, "meta", "Meta" ],
+    [ 6, 1, "alt", "Alt" ],
+    [ 2, 1, "", "" ],
+    [ 6, 1, "", "" ],
+    [ 5, 1, "cursor-up", "Up" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+    [ 13, 1, "", "Space" ],
+    [ 6, 1, "logo", "Win" ],
+    [ 6, 1, "", "Alt" ],
+    [ 2, 1, "", "" ],
+    [ 6, 1, "", "" ],
+    [ 5, 1, "cursor-pageup", "PgUp" ],
+    [ 6, 1, "", "" ],
+  ],
+
+  # Row 6.5
+  [
+    [ 73, 1, "", "" ],
+    [ 6, 1, "cursor-left", "Left" ],
+    [ 5, 1, "cursor-down", "Down" ],
+    [ 6, 1, "cursor-right", "Right" ],
+
+    [ 3, 1, "", "" ],
+    [ 27, 1, "", "" ],
+    [ 6, 1, "cursor-home", "Home" ],
+    [ 5, 1, "cursor-pagedown", "PgDn" ],
+    [ 6, 1, "cursor-end", "End" ],
+  ]
+]
+
+
+
+keyboard['de/DE 104'] = [
+  [
+    [ 142, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    # width, height, keysym (app-specific), keytop (displayed), flags
+    [ 6, 1, "esc", "Esc" ],
+    [ 5, 1, "", "" ],
+    [ 6, 1, "function-1", "F1" ],
+    [ 6, 1, "function-2", "F2" ],
+    [ 6, 1, "function-3", "F3" ],
+    [ 6, 1, "function-4", "F4" ],
+    [ 3, 1, "", "" ],
+    [ 6, 1, "function-5", "F5" ],
+    [ 6, 1, "function-6", "F6" ],
+    [ 6, 1, "function-7", "F7" ],
+    [ 6, 1, "function-8", "F8" ],
+    [ 3, 1, "", "" ],
+    [ 6, 1, "function-9", "F9" ],
+    [ 6, 1, "function-10", "F10" ],
+    [ 6, 1, "function-11", "F11" ],
+    [ 6, 1, "function-12", "F12" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "print", "Druck" ],
+    [ 6, 1, "scroll", "Rolln" ],
+    [ 6, 1, "pause", "Pause" ],
+  ],
+
+  # Row spacing.
+  [ ],
+
+  # Row 2
+  [
+    # width, height, keysym, keytop, flags
+    [ 6, 1, "^", "^" ],
+    [ 6, 1, "1", "1" ],
+    [ 6, 1, "2", "2" ],
+    [ 6, 1, "3", "3" ],
+    [ 6, 1, "4", "4" ],
+    [ 6, 1, "5", "5" ],
+    [ 6, 1, "6", "6" ],
+    [ 6, 1, "7", "7" ],
+    [ 6, 1, "8", "8" ],
+    [ 6, 1, "9", "9" ],
+    [ 6, 1, "0", "0" ],
+    [ 6, 1, "ß", "ß" ],
+    [ 6, 1, "acute", "´" ],
+    [ 12, 1, "backspace", "BS" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-insert", "Einfg" ],
+    [ 6, 1, "cursor-home", "Pos1" ],
+    [ 6, 1, "cursor-pagedown", "Bild↑" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "numlock", "NL" ],
+    [ 6, 1, "keypad-divide", "÷" ],
+    [ 6, 1, "keypad-multiply", "×" ],
+    [ 6, 1, "keypad-minus", "-" ],
+  ],
+
+  # Row 3
+  [
+    [ 9, 1, "tab", "Tab" ],
+    [ 6, 1, "q", "Q" ],
+    [ 6, 1, "w", "W" ],
+    [ 6, 1, "e", "E" ],
+    [ 6, 1, "r", "R" ],
+    [ 6, 1, "t", "T" ],
+    [ 6, 1, "z", "Z" ],
+    [ 6, 1, "u", "U" ],
+    [ 6, 1, "i", "I" ],
+    [ 6, 1, "o", "O" ],
+    [ 6, 1, "p", "P" ],
+    [ 6, 1, "ü", "Ü" ],
+    [ 6, 1, "+", "+" ],
+    [ 9, 1, "return", "Enter" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-delete", "Entf" ],
+    [ 6, 1, "cursor-end", "Ende" ],
+    [ 6, 1, "cursor-pagedown", "Bild↓" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-7", "7" ],
+    [ 6, 1, "keypad-8", "8" ],
+    [ 6, 1, "keypad-9", "9" ],
+    [ 6, 2, "keypad-plus", "+" ],
+  ],
+
+  # Row 4
+  [
+    [ 11, 1, "", "Caps" ],
+    [ 6, 1, "a", "A" ],
+    [ 6, 1, "s", "S" ],
+    [ 6, 1, "d", "D" ],
+    [ 6, 1, "f", "F" ],
+    [ 6, 1, "g", "G" ],
+    [ 6, 1, "h", "H" ],
+    [ 6, 1, "j", "J" ],
+    [ 6, 1, "k", "K" ],
+    [ 6, 1, "l", "L" ],
+    [ 6, 1, "ö", "Ö" ],
+    [ 6, 1, "ä", "Ä" ],
+    [ 6, 1, "#", "#" ],
+    [ 7, 1, "return", "Enter" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-4", "4" ],
+    [ 6, 1, "keypad-5", "5" ],
+    [ 6, 1, "keypad-6", "6" ],
+  ],
+
+  # Row 5
+  [
+    [ 7, 1, "shift", "Shift" ],
+    [ 6, 1, "less-than", "<" ],
+    [ 6, 1, "y", "Y" ],
+    [ 6, 1, "x", "X" ],
+    [ 6, 1, "c", "C" ],
+    [ 6, 1, "v", "V" ],
+    [ 6, 1, "b", "B" ],
+    [ 6, 1, "n", "N" ],
+    [ 6, 1, "m", "M" ],
+    [ 6, 1, ",", "," ],
+    [ 6, 1, ".", "." ],
+    [ 6, 1, "-", "-" ],
+    [ 11, 1, "shift", "Shift" ],
+    [ 6, 1, "", "♦" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "cursor-up", "Up" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-1", "1" ],
+    [ 6, 1, "keypad-2", "2" ],
+    [ 6, 1, "keypad-3", "3" ],
+    [ 6, 2, "keypad-enter", "RET" ],
+  ],
+
+  # Row 6
+  [
+    [ 8, 1, "ctrl", "Strg" ],
+    [ 7, 1, "logo", "◊" ],
+    [ 8, 1, "alt", "Alt" ],
+    [ 44, 1, "space", "Space" ],
+    [ 8, 1, "altgr", "AltGr" ],
+    [ 7, 1, "logo", "Logo" ],
+    [ 8, 1, "ctrl", "Strg" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-left", "Left" ],
+    [ 6, 1, "cursor-down", "Down" ],
+    [ 6, 1, "cursor-right", "Right" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 12, 1, "keypad-0", "0" ],
+    [ 6, 1, "keypad-.", "." ],
+  ]
+]
+
+
+keyboard['en/GB 104'] = [
+  [
+    [ 142, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    # width, height, keysym (app-specific), keytop (displayed), flags
+    [ 6, 1, "esc", "Esc" ],
+    [ 5, 1, "", "" ],
+    [ 6, 1, "function-1", "F1" ],
+    [ 6, 1, "function-2", "F2" ],
+    [ 6, 1, "function-3", "F3" ],
+    [ 6, 1, "function-4", "F4" ],
+    [ 3, 1, "", "" ],
+    [ 6, 1, "function-5", "F5" ],
+    [ 6, 1, "function-6", "F6" ],
+    [ 6, 1, "function-7", "F7" ],
+    [ 6, 1, "function-8", "F8" ],
+    [ 3, 1, "", "" ],
+    [ 6, 1, "function-9", "F9" ],
+    [ 6, 1, "function-10", "F10" ],
+    [ 6, 1, "function-11", "F11" ],
+    [ 6, 1, "function-12", "F12" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "print", "Print" ],
+    [ 6, 1, "scroll", "Scroll" ],
+    [ 6, 1, "pause", "Pause" ],
+  ],
+
+  # Row spacing.
+  [ ],
+
+  # Row 2
+  [
+    # width, height, keysym, keytop, flags
+    [ 6, 1, "`", "`" ],
+    [ 6, 1, "1", "1" ],
+    [ 6, 1, "2", "2" ],
+    [ 6, 1, "3", "3" ],
+    [ 6, 1, "4", "4" ],
+    [ 6, 1, "5", "5" ],
+    [ 6, 1, "6", "6" ],
+    [ 6, 1, "7", "7" ],
+    [ 6, 1, "8", "8" ],
+    [ 6, 1, "9", "9" ],
+    [ 6, 1, "0", "0" ],
+    [ 6, 1, "-", "-" ],
+    [ 6, 1, "=", "=" ],
+    [ 12, 1, "backspace", "BS" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-insert", "Ins" ],
+    [ 6, 1, "cursor-home", "Home" ],
+    [ 6, 1, "cursor-pagedown", "PgUp" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "numlock", "NL" ],
+    [ 6, 1, "keypad-divide", "/" ],
+    [ 6, 1, "keypad-multiply", "*" ],
+    [ 6, 1, "keypad-minus", "-" ],
+  ],
+
+  # Row 3
+  [
+    [ 9, 1, "tab", "Tab" ],
+    [ 6, 1, "q", "Q" ],
+    [ 6, 1, "w", "W" ],
+    [ 6, 1, "e", "E" ],
+    [ 6, 1, "r", "R" ],
+    [ 6, 1, "t", "T" ],
+    [ 6, 1, "y", "Y" ],
+    [ 6, 1, "u", "U" ],
+    [ 6, 1, "i", "I" ],
+    [ 6, 1, "o", "O" ],
+    [ 6, 1, "p", "P" ],
+    [ 6, 1, "[", "[" ],
+    [ 6, 1, "]", "]" ],
+    [ 9, 1, "return", "Enter" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-delete", "Del" ],
+    [ 6, 1, "cursor-end", "End" ],
+    [ 6, 1, "cursor-pagedown", "PgDn" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-7", "7" ],
+    [ 6, 1, "keypad-8", "8" ],
+    [ 6, 1, "keypad-9", "9" ],
+    [ 6, 2, "keypad-plus", "+" ],
+  ],
+
+  # Row 4
+  [
+    [ 11, 1, "", "Caps" ],
+    [ 6, 1, "a", "A" ],
+    [ 6, 1, "s", "S" ],
+    [ 6, 1, "d", "D" ],
+    [ 6, 1, "f", "F" ],
+    [ 6, 1, "g", "G" ],
+    [ 6, 1, "h", "H" ],
+    [ 6, 1, "j", "J" ],
+    [ 6, 1, "k", "K" ],
+    [ 6, 1, "l", "L" ],
+    [ 6, 1, ";", ";" ],
+    [ 6, 1, "'", "'" ],
+    [ 6, 1, "#", "#" ],
+    [ 7, 1, "return", "Enter" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-4", "4" ],
+    [ 6, 1, "keypad-5", "5" ],
+    [ 6, 1, "keypad-6", "6" ],
+  ],
+
+  # Row 5
+  [
+    [ 7, 1, "shift", "Shift" ],
+    [ 6, 1, "\\", "\\" ],
+    [ 6, 1, "z", "Z" ],
+    [ 6, 1, "x", "X" ],
+    [ 6, 1, "c", "C" ],
+    [ 6, 1, "v", "V" ],
+    [ 6, 1, "b", "B" ],
+    [ 6, 1, "n", "N" ],
+    [ 6, 1, "m", "M" ],
+    [ 6, 1, ",", "," ],
+    [ 6, 1, ".", "." ],
+    [ 6, 1, "/", "/" ],
+    [ 17, 1, "shift", "Shift" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "", "" ],
+    [ 6, 1, "cursor-up", "Up" ],
+    [ 6, 1, "", "" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "keypad-1", "1" ],
+    [ 6, 1, "keypad-2", "2" ],
+    [ 6, 1, "keypad-3", "3" ],
+    [ 6, 2, "keypad-enter", "RET" ],
+  ],
+
+  # Row 6
+  [
+    [ 8, 1, "ctrl", "Ctrl" ],
+    [ 7, 1, "logo", "Logo" ],
+    [ 8, 1, "alt", "Alt" ],
+    [ 37, 1, "space", "Space" ],
+    [ 8, 1, "alt", "Alt" ],
+    [ 7, 1, "logo", "Logo" ],
+    [ 7, 1, "menu", "Menu" ],
+    [ 8, 1, "ctrl", "Ctrl" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 6, 1, "cursor-left", "Left" ],
+    [ 6, 1, "cursor-down", "Down" ],
+    [ 6, 1, "cursor-right", "Right" ],
+
+    [ 3, 1, "", "" ],
+
+    [ 12, 1, "keypad-0", "0" ],
+    [ 6, 1, "keypad-.", "." ],
+  ]
+]
+
+
+
+
+
+
+
+mouse = [
+  [
+    [ 160, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    [ 14, 1, "axis:mouse-0.0", "MOUSEX" ],
+    [ 14, 1, "axis:mouse-0.1", "MOUSEY" ],
+    [ 8, 1, "", "" ],
+    [ 14, 1, "mouse-0.0", "MOUSE0" ],
+    [ 8, 1, "mouse-0.1", "1" ],
+    [ 8, 1, "mouse-0.2", "2" ],
+    [ 8, 1, "mouse-0.3", "3" ],
+    [ 8, 1, "mouse-0.4", "4" ],
+    [ 8, 1, "mouse-0.5", "5" ],
+    [ 8, 1, "mouse-0.6", "6" ],
+    [ 8, 1, "mouse-0.7", "7" ],
+    [ 8, 1, "mouse-0.8", "8" ],
+    [ 8, 1, "mouse-0.9", "9" ],
+  ],
+#  [
+#    [ 14, 1, "", "MOUSE0" ],
+#    [ 14, 1, "", "MOUSE1" ],
+#    [ 14, 1, "", "MOUSE2" ],
+#    [ 14, 1, "", "MOUSE3" ],
+#    [ 14, 1, "", "MOUSE4" ],
+#    [ 14, 1, "", "MOUSE5" ],
+#    [ 14, 1, "", "MOUSE6" ],
+#    [ 14, 1, "", "MOUSE7" ],
+#    [ 14, 1, "", "MOUSE8" ],
+#    [ 14, 1, "", "MOUSE9" ],
+#  ],
+#
+#  # Row 2
+#  [
+#    [ 14, 1, "", "MOUSEX" ],
+#    [ 14, 1, "", "MOUSEY" ],
+#  ],
+]
+
+joystick = [
+  [
+    [ 200, 0, None, None ],
+  ],
+
+  # Row 1
+  [
+    [ 11, 1, "joystick-0.0", "JOY0" ],
+    [ 11, 1, "joystick-0.1", "JOY1" ],
+    [ 11, 1, "joystick-0.2", "JOY2" ],
+    [ 11, 1, "joystick-0.3", "JOY3" ],
+    [ 11, 1, "joystick-0.4", "JOY4" ],
+    [ 11, 1, "joystick-0.5", "JOY5" ],
+    [ 11, 1, "joystick-0.6", "JOY6" ],
+    [ 11, 1, "joystick-0.7", "JOY7" ],
+    [ 11, 1, "joystick-0.8", "JOY8" ],
+    [ 11, 1, "joystick-0.9", "JOY9" ],
+    [ 11, 1, "joystick-0.10", "JOY10" ],
+    [ 11, 1, "joystick-0.11", "JOY11" ],
+  ],
+
+  # Row 2
+  [
+    [ 11, 1, "joystick-0.12", "JOY12" ],
+    [ 11, 1, "joystick-0.13", "JOY13" ],
+    [ 11, 1, "joystick-0.14", "JOY14" ],
+    [ 11, 1, "joystick-0.15", "JOY15" ],
+    [ 11, 1, "joystick-0.16", "JOY16" ],
+    [ 11, 1, "joystick-0.17", "JOY17" ],
+    [ 11, 1, "joystick-0.18", "JOY18" ],
+    [ 11, 1, "joystick-0.19", "JOY19" ],
+    [ 11, 1, "joystick-0.20", "JOY20" ],
+    [ 11, 1, "joystick-0.21", "JOY21" ],
+    [ 11, 1, "joystick-0.22", "JOY22" ],
+    [ 11, 1, "joystick-0.23", "JOY23" ],
+  ],
+
+  # Row 3
+  [
+    [ 16, 1, "axis:joystick-0.0", "AXIS0" ],
+    [ 16, 1, "axis:joystick-0.1", "AXIS1" ],
+    [ 16, 1, "axis:joystick-0.2", "AXIS2" ],
+    [ 16, 1, "axis:joystick-0.3", "AXIS3" ],
+    [ 16, 1, "axis:joystick-0.4", "AXIS4" ],
+    [ 16, 1, "axis:joystick-0.5", "AXIS5" ],
+    [ 16, 1, "axis:joystick-0.6", "AXIS6" ],
+    [ 16, 1, "axis:joystick-0.7", "AXIS7" ],
+  ],
+
+  # Row 4
+  [
+    [ 14, 1, "digital-hatswitch:0.leftup", "DHAT0.↖" ],
+    [ 14, 1, "digital-hatswitch:0.up", "DHAT0.↑" ],
+    [ 14, 1, "digital-hatswitch:0.rightup", "DHAT0.↗" ],
+    [ 14, 1, "", "" ],
+    [ 14, 1, "digital-hatswitch:1.leftup", "DHAT1.↖" ],
+    [ 14, 1, "digital-hatswitch:1.up", "DHAT1.↑" ],
+    [ 14, 1, "digital-hatswitch:1.rightup", "DHAT1.↗" ],
+  ],
+
+  # Row 5
+  [
+    [ 14, 1, "digital-hatswitch:0.left", "DHAT0.←" ],
+    [ 14, 1, "digital-hatswitch:0.center", "DHAT0.⊙" ],
+    [ 14, 1, "digital-hatswitch:0.right", "DHAT0.→" ],
+    [ 14, 1, "", "" ],
+    [ 14, 1, "digital-hatswitch:1.left", "DHAT1.←" ],
+    [ 14, 1, "digital-hatswitch:1.center", "DHAT1.⊙" ],
+    [ 14, 1, "digital-hatswitch:1.right", "DHAT1.→" ],
+  ],
+
+  # Row 6
+  [
+    [ 14, 1, "digital-hatswitch:0.leftdown", "DHAT0.↙" ],
+    [ 14, 1, "digital-hatswitch:0.down", "DHAT0.↓" ],
+    [ 14, 1, "digital-hatswitch:0.rightdown", "DHAT0.↘" ],
+    [ 14, 1, "", "" ],
+    [ 14, 1, "digital-hatswitch:1.leftdown", "DHAT1.↙" ],
+    [ 14, 1, "digital-hatswitch:1.down", "DHAT1.↓" ],
+    [ 14, 1, "digital-hatswitch:1.rightdown", "DHAT1.↘" ],
+  ],
+
+  # Row 7
+  [
+    [ 13, 1, "hatswitch:0.0", "AHAT0.0" ],
+    [ 13, 1, "hatswitch:0.1", "AHAT0.1" ],
+    [ 13, 1, "hatswitch:0.2", "AHAT0.2" ],
+    [ 13, 1, "hatswitch:0.3", "AHAT0.3" ],
+    [ 13, 1, "hatswitch:0.4", "AHAT0.4" ],
+    [ 13, 1, "hatswitch:0.5", "AHAT0.5" ],
+    [ 13, 1, "hatswitch:0.6", "AHAT0.6" ],
+    [ 13, 1, "hatswitch:0.7", "AHAT0.7" ],
+    [ 13, 1, "hatswitch:0.8", "AHAT0.8" ],
+    [ 13, 1, "hatswitch:0.9", "AHAT0.9" ],
+  ],
+
+  # Row 8
+  [
+    [ 13, 1, "hatswitch:1.0", "AHAT1.0" ],
+    [ 13, 1, "hatswitch:1.1", "AHAT1.1" ],
+    [ 13, 1, "hatswitch:1.2", "AHAT1.2" ],
+    [ 13, 1, "hatswitch:1.3", "AHAT1.3" ],
+    [ 13, 1, "hatswitch:1.4", "AHAT1.4" ],
+    [ 13, 1, "hatswitch:1.5", "AHAT1.5" ],
+    [ 13, 1, "hatswitch:1.6", "AHAT1.6" ],
+    [ 13, 1, "hatswitch:1.7", "AHAT1.7" ],
+    [ 13, 1, "hatswitch:1.8", "AHAT1.8" ],
+    [ 13, 1, "hatswitch:1.9", "AHAT1.9" ],
+  ],
+]
+
+
+shiftmap = {}
+shiftmap['en/US 101'] = {
+  "`": "~",
+  "1": "!",
+  "2": "@",
+  "3": "#",
+  "4": "$",
+  "5": "%",
+  "6": "^",
+  "7": "&",
+  "8": "*",
+  "9": "(",
+  "0": ")",
+  "-": "_",
+  "=": "+",
+  "[": "{",
+  "]": "}",
+  "\\": "|",
+  ";": ":",
+  "'": "\"",
+  ",": "less-than",
+  ".": "greater-than",
+  "/": "?",
+  "a": "A",
+  "b": "B",
+  "c": "C",
+  "d": "D",
+  "e": "E",
+  "f": "F",
+  "g": "G",
+  "h": "H",
+  "i": "I",
+  "j": "J",
+  "k": "K",
+  "l": "L",
+  "m": "M",
+  "n": "N",
+  "o": "O",
+  "p": "P",
+  "q": "Q",
+  "r": "R",
+  "s": "S",
+  "t": "T",
+  "u": "U",
+  "v": "V",
+  "w": "W",
+  "x": "X",
+  "y": "Y",
+  "z": "Z",
+}
+
+
+shiftmap['de/DE 104'] = {
+  "^": "˚",  # circumflex, ring
+  "1": "!",
+  "2": "\"",
+  "3": "§",
+  "4": "$",
+  "5": "%",
+  "6": "&",
+  "7": "/",
+  "8": "(",
+  "9": ")",
+  "0": "=",
+  "ß": "?",
+  "´": "`",  # acute, grave
+  "ü": "Ü",
+  "+": "*",
+  "ö": "Ö",
+  "ä": "Ä",
+  "#": "'",
+  "less-than": "greater-than",
+  ",": ";",
+  ".": ":",
+  "-": "_",
+  "a": "A",
+  "b": "B",
+  "c": "C",
+  "d": "D",
+  "e": "E",
+  "f": "F",
+  "g": "G",
+  "h": "H",
+  "i": "I",
+  "j": "J",
+  "k": "K",
+  "l": "L",
+  "m": "M",
+  "n": "N",
+  "o": "O",
+  "p": "P",
+  "q": "Q",
+  "r": "R",
+  "s": "S",
+  "t": "T",
+  "u": "U",
+  "v": "V",
+  "w": "W",
+  "x": "X",
+  "y": "Y",
+  "z": "Z",
+}
+
+
+shiftmap['en/GB 104'] = {
+  "`": "¬",
+  "1": "!",
+  "2": "\"",
+  "3": "£",
+  "4": "$",
+  "5": "%",
+  "6": "^",
+  "7": "&",
+  "8": "*",
+  "9": "(",
+  "0": ")",
+  "-": "_",
+  "=": "+",
+  "[": "{",
+  "]": "}",
+  ";": ":",
+  "'": "@",
+  "#": "~",
+  "\\": "|",
+  ",": "less-than",
+  ".": "greater-than",
+  "/": "?",
+  "a": "A",
+  "b": "B",
+  "c": "C",
+  "d": "D",
+  "e": "E",
+  "f": "F",
+  "g": "G",
+  "h": "H",
+  "i": "I",
+  "j": "J",
+  "k": "K",
+  "l": "L",
+  "m": "M",
+  "n": "N",
+  "o": "O",
+  "p": "P",
+  "q": "Q",
+  "r": "R",
+  "s": "S",
+  "t": "T",
+  "u": "U",
+  "v": "V",
+  "w": "W",
+  "x": "X",
+  "y": "Y",
+  "z": "Z",
+}
+
+
+
+shiftmap['en/US 104'] = shiftmap['en/US 101']
+shiftmap['en/US-dvorak 104'] = shiftmap['en/US 101']
+shiftmap['en/US AppleProAlFull'] = shiftmap['en/US 101']
+shiftmap['en/US HappyHacking'] = shiftmap['en/US 101']
+shiftmap['en/US HappyHackingLite2'] = shiftmap['en/US 101']
+shiftmap['en/US HappyHackingPro2'] = shiftmap['en/US 101']
+
+
+unshiftmap = {}
+for name,mapping in shiftmap.items():
+  rmapping = {}
+  for k,v in mapping.items():
+    rmapping[v] = k
+  unshiftmap[name] = rmapping
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.py	Wed Jun 29 08:32:07 2011 -0700
@@ -0,0 +1,1826 @@
+#!/usr/bin/env python
+# Expecting python 2.6
+
+from __future__ import print_function
+
+LICENSE_LONG = r"""
+    VegaStrike 0.5.x PyKeyBinder v0.13
+    Copyright (C) 2011  Frederick Lee <phaethon@icculus.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+
+PACKAGE = "VegaStrike 0.5.x - PyKeyBinder"
+VERSION = "0.20"
+ICON = None
+DESCRIPTION = "Establish keybindings for VegaStrike 0.5.x"
+COPYRIGHT = "Copyright 2011 - distributed under terms of GPL 2.0 or later"
+LICENSE = LICENSE_LONG
+
+HOMEPAGE = "http://www.icculus.org/~phaethon/vegastrike/vegastrikebind/vegastrikebind.html"
+DEVELOPERS = ( "Frederick Lee <PhaethonH@gmail.com>", )
+
+
+
+#TODO:
+# [X] save
+# [X] the 'preset' menu
+# [X] saner state information on current layout
+# [X] factor out duplicated make_menu()
+
+
+
+# Initial (default) layout.
+LAYOUT0 = 'en/US 104'
+
+
+import threading
+import wx, wx.grid
+import xml, xml.dom.minidom
+import layouts, predefs
+
+try:
+  import pygame, pygame.joystick
+  pass
+except:
+  pass
+
+try:
+  import OpenGL.GLUT
+except:
+  pass
+
+
+
+
+def crumb (msg):
+  print(msg)
+
+
+
+
+# Vegastrike uses either SDL or GLUT for joystick interface, preferring SDL if available.
+
+
+
+
+class IJoystickReader (object):
+  """Interface for querying joystick properties."""
+  def OpenAll (self): pass
+  def CloseAll (self): pass
+  def CountDevices (self): pass
+  def CountAxes (self, idx): pass
+  def CountHats (self, idx): pass
+  def CountButtons (self, idx): pass
+
+class JoystickReaderDummy (IJoystickReader):
+  """Implements IJoystickReader.
+
+Dummy object (returns values from the commented-out sample values in vegastrike.config (from VegaStrike 0.5.0).  Provided so that those without an actual joystick, or one that reports back inaccurate values, can play around with joystick binds.
+"""
+  def __init__ (self):
+    self.joys = []
+    pass
+  def OpenAll (self):
+    self.joys = [ None ]  # dummy object.
+  def CloseAll (close):
+    pass
+  def CountDevices (self):
+    return 1
+  def CountAxes (self, idx):
+    return 8
+  def CountButtons (self, idx):
+    return 24
+  def CountHats (self, idx):
+    return 2
+
+
+class JoystickReaderWx (IJoystickReader):
+  """Implements IJoystickReader.
+
+Use wxWidgets library for querying joystick.
+"""
+  def __init__ (self):
+    self.joys = []
+    pass
+  def OpenAll (self):
+    js = wx.Joystick()
+#    maxjs = wx.Joystick.GetNumberJoysticks()
+    if js:
+      maxjs = js.GetNumberJoysticks()
+      for n in xrange(0, maxjs):
+        if n > wx.JOYSTICK1:
+          js = wx.Joystick(wx.JOYSTICK1 + n)
+        self.joys.append(js)
+    #crumb("JoystickReaderWx ready")
+    pass
+  def CloseAll (close):
+    for n in xrange(0, len(self.joys)):
+      del self.joys[n]
+    self.joys = []
+    pass
+  def CountDevices (self):
+#    return wx.Joystick.GetNumberJoysticks()
+    retval = 0
+    if self.joys:
+#      retval = self.joys[0].GetNumberJoysticks()
+      retval = len(self.joys)
+    else:
+      js = wx.Joystick()
+      retval = js.GetNumberJoysticks()
+      del js
+    #crumb("JoystickReaderWx: CountDevices=%d" % retval)
+    return retval
+  def CountAxes (self, idx):
+    retval = 0
+    if (0 <= idx) and (idx < len(self.joys)):
+      retval = self.joys[idx].GetNumberAxes()
+    #crumb("JoystickReaderWx: CountAxes=%d" % retval)
+    return retval
+  def CountButtons (self, idx):
+    retval = 0
+    if (0 <= idx) and (idx < len(self.joys)):
+      retval = self.joys[idx].GetNumberButtons()
+    #crumb("JoystickReaderWx: CountButtons=%d" % retval)
+    return retval
+  def CountHats (self, idx):
+    retval = 0
+    #crumb("JoystickReaderWx: CountHats=%d" % retval)
+    return retval
+
+class JoystickReaderSDL (IJoystickReader):
+  """Implements IJoystickReader.
+
+Uses SDL for querying joystick.
+
+This implementation is the preferred one over the others, as VegaStrike itself uses SDL for joystick.
+"""
+  def __init__ (self):
+    pygame.joystick.init()
+    self.joys = []
+    pass
+  def OpenAll (self):
+    maxjs = pygame.joystick.get_count()
+    self.joys = []  # Rely on gc to clean up (previously-)opened devices.
+    for n in xrange(0, maxjs):
+      js = pygame.joystick.Joystick(n)
+      js.init()
+      self.joys.append(js)
+    pass
+  def CloseAll (self):
+    pass
+  def CountDevices (self):
+    return pygame.joystick.get_count()
+  def CountAxes (self, idx):
+    retval = 0
+    if (0 <= idx) and (idx < len(self.joys)):
+      retval = self.joys[idx].get_numaxes()
+    return retval
+  def CountButtons (self, idx):
+    retval = 0
+    if (0 <= idx) and (idx < len(self.joys)):
+      retval = self.joys[idx].get_numbuttons()
+    return retval
+  def CountHats (self, idx):
+    retval = 0
+    if (0 <= idx) and (idx < len(self.joys)):
+      retval = self.joys[idx].get_numhats()
+    return retval
+
+class JoystickReaderGLUT (IJoystickReader):
+  # *** ABANDONED ***
+  """Implements IJoystickReader.
+
+Uses GLUT for querying joystick.  Implemented because I saw GLUT code in VegaStrike, but that piece doesn't seem to be used at all, thus abandoned.  But it remains anyway as a demonstration of something else implementing IJoystickReader.
+"""
+  def __init__ (self):
+    OpenGL.GLUT.glutInit(sys.argv)
+    valid = OpenGL.GLUT.glutDeviceGet(OpenGL.GLUT.GLUT_HAS_STICK)
+    pollrate = 50  # [ms] ; arbitrary for this app.
+    OpenGL.GLUT.glutJoystickFunc(self.jscallback, pollrate)
+    pass
+    
+class JoystickReader (object):
+  """Wrapper to the JoystickReader objects.  Attempts to test each one for a viable interface.
+
+Order of preference is:
+1. SDL
+2. wxWidgets
+3. dummy
+"""
+  def __init__ (self):
+    self.backend = None
+    if not self.backend:
+      try:
+        self.backend = JoystickReaderSDL()
+      except:
+        print("Error opening joystick with pygame.")
+    if not self.backend:
+      try:
+        self.backend = JoystickReaderWx()
+      except:
+        print ("Error opening joystick with wx.")
+    if not self.backend:
+      self.backend = JoystickReaderDummy()
+    self.backend.OpenAll()
+  def make_layout (self):
+    layoutdata = [ [] ]
+
+    njoys = self.backend.CountDevices()
+    #njoys = 2
+
+    for joyidx in xrange(0, njoys):
+      nbtns = 24
+      naxes = 9
+      nhats = 0
+
+      nbtns = self.backend.CountButtons(joyidx)
+      naxes = self.backend.CountAxes(joyidx)
+      nhats = self.backend.CountHats(joyidx)
+    
+      layoutrow = []
+    
+      # Joystick buttons.  12 to a row, 11 wide each.
+      for btnidx in xrange(0, nbtns):
+        if (btnidx % 12) == 0:
+          if layoutrow:
+            layoutdata.append(layoutrow)
+          layoutrow = []
+        ksym = "joystick-%d.%d" % (joyidx, btnidx)
+        ktop = "JOY%d" % btnidx
+        layoutrow.append( [11, 1, ksym, ktop ] )
+      if layoutrow:
+        layoutdata.append(layoutrow)
+    
+      layoutrow = []
+      # Joystick axes.  8 to a row, 16 wide each.
+      for axisidx in xrange(0, naxes):
+        if (axisidx % 8) == 0:
+          if layoutrow:
+            layoutdata.append(layoutrow)
+          layoutrow = []
+        ksym = "axis:joystick-%d.%d" % (joyidx, axisidx)
+        ktop = "AXIS%d" % axisidx
+        layoutrow.append( [16, 1, ksym, ktop] )
+      if layoutrow:
+        layoutdata.append(layoutrow)
+  
+    return layoutdata
+
+
+def build_joylayout ():
+  """Build the joystick layout description based on JoystickReader results."""
+
+  #crumb("build_joylayout...")
+  js = JoystickReader()
+  retval = js.make_layout()
+  #crumb("build_joylayout done: %s" % retval)
+  return retval
+
+  # TODO: elide dead code.
+  njoys = 1
+  nbtns = 24
+  naxes = 9
+
+  layoutdata = [ [] ]
+  layoutrow = []
+
+  joyidx = 0
+  # Joystick buttons.  12 to a row, 11 wide each.
+  for btnidx in xrange(0, nbtns):
+    if (btnidx % 12) == 0:
+      if layoutrow:
+        layoutdata.append(layoutrow)
+      layoutrow = []
+    ksym = "joystick-%d.%d" % (joyidx, btnidx)
+    ktop = "JOY%d" % btnidx
+    layoutrow.append( [11, 1, ksym, ktop ] )
+  if layoutrow:
+    layoutdata.append(layoutrow)
+
+  layoutrow = []
+  # Joystick axes.  8 to a row, 16 wide each.
+  for axisidx in xrange(0, naxes):
+    if (axisidx % 8) == 0:
+      if layoutrow:
+        layoutdata.append(layoutrow)
+      layoutrow = []
+    ksym = "axis:joystick-%d.%d" % (joyidx, axisidx)
+    ktop = "AXIS%d" % axisidx
+    layoutrow.append( [16, 1, ksym, ktop] )
+  if layoutrow:
+    layoutdata.append(layoutrow)
+
+  return layoutdata
+  pass
+
+
+
+
+
+def make_menu (self, data):
+  """Construct a menu from a list of lists....
+
+TODO: describe menu description format.
+"""
+  menu = wx.Menu()
+  for item in data:
+    if item is None:
+      # Separator
+      menu.AppendSeparator()
+      pass
+    elif type(item) == str:
+      lbl = item
+      menuitem = menu.Append(-1, lbl)
+      try:
+        wx.EVT_MENU(self, menuitem.GetId(), self.guiOneField)
+      except:
+        # Just ignore if guiOneField() does not exist.
+        pass
+      pass
+    elif (type(item) == list) or (type(item) == tuple):
+      if len(item) == 2:
+        # 2-field item.  Could be submenu or string-with-tooltip.
+        car = item[0]
+        cadr = item[1]
+#        crumb("type %s" % type(cadr))
+        if type(cadr) == list:
+          lbl = car
+          # Second field is list -- submenu.
+#          crumb("submenu <- %s %s" % (cadr, lbl))
+          submenu = make_menu(self, cadr)
+          menuitem = menu.AppendSubMenu(submenu, lbl)
+        else:
+          # Second field not a list -- string-with-tooltip.
+          (lbl, tooltip) = item
+#          crumb("Build 2-field menuitem (%s, %s)" % (lbl, tooltip))
+          op = menu.Append
+          if lbl:
+            if lbl[0] == '+':
+              lbl, op = lbl[1:], menu.AppendCheckItem
+            if lbl[0] == '@':
+              lbl, op = lbl[1:], menu.AppendRadioItem
+          if tooltip:
+            menuitem = apply(op, (-1, lbl, tooltip))
+            #crumb("GetHelp: %s" % menuitem.GetHelp())
+          else:
+            menuitem = apply(op, (-1, lbl))
+          try:
+            wx.EVT_MENU(self, menuitem.GetId(), self.guiTwoField)
+          except:
+            # Just ignore if guiTwoField() does not exist.
+            pass
+      else:
+        # Assume 4-field entry.
+#        crumb("4-field: %s" % item)
+        ( lbl, wxid, callback, tooltip ) = item
+        op = menu.Append
+        if lbl:
+          if lbl[0] == '+':
+            lbl, op = lbl[1:], menu.AppendCheckItem
+          if lbl[0] == '@':
+            lbl, op = lbl[1:], menu.AppendRadioItem
+        menuitem = apply(op, (wxid, lbl, tooltip))
+        if (callback is None) or (callback == self.guiNop):
+          menuitem.Enable(False)
+          pass
+        else:
+          wxid = menuitem.GetId()
+          wx.EVT_MENU(self, wxid, callback)
+  return menu
+
+
+
+
+
+
+
+
+
+class SDLKeyCapturer (wx.Button):
+
+  class SDLEventWatcher (threading.Thread):
+    def __init__ (self):
+      threading.Thread.__init__(self)
+      self.active = False
+      pass
+    def run (self):
+      self.active = True
+      js = JoystickReader()
+      prev_keyb = None
+      prev_mouseb = None
+      prev_mousem = None
+      prev_joyb = None
+      prev_joya = None
+      prev_joyh = None
+      while self.active:
+        # Query all devices.
+        keyb = pygame.key.get_pressed()
+        mouseb = pygame.mouse.get_pressed()
+        mousem = pygame.mouse.get_rel()
+        joyb = []
+        joya = []
+        joyh = []
+        n = js.backend.CountDevices()
+        for i in xrange(0, n):
+          jobj = js.backend.joys[i]
+          b = []
+          for j in xrange(0, jobj.get_numbuttons()):
+            b.append(jobj.get_button(j))
+          a = []
+          for j in xrange(0, jobj.get_numaxes()):
+            a.append(jobj.get_axis(j))
+          h = []
+          for j in xrange(0, jobj.get_numhats()):
+            h.append(jobj.get_hat(j))
+          pass
+        # Do stuff.
+        pass
+      pass
+      prev_keyb = keyb
+      prev_mouseb = mouseb
+      prev_mousem = mousem
+      prev_joyb = joyb
+      prev_joya = joya
+      prev_joyh = joyh
+#    def 
+
+  def __init__ (self, parent, wxid):
+    crumb("SDLKeyCapturer::<ctor>")
+    wx.Button.__init__(self, parent, wxid, "Press key")
+    # When getting focus, kick in SDL event watching.
+    wx.EVT_SET_FOCUS(self, self.OnFocus)
+    wx.EVT_KILL_FOCUS(self, self.OffFocus)
+
+  def OnFocus (self, evt):
+    # Kick in SDL event watching: keyboard, mouse, joystick.
+    crumb("SDLKeyCapturer::OnFocus")
+    pass
+
+  def OffFocus (self, evt):
+    # Kick out SDL event watching.
+    crumb("SDLKeyCapturer::OffFocus")
+    pass
+
+  def Reset (self, *args):
+    crumb("SDLKeyCapturer::Reset")
+  pass
+
+
+
+
+
+
+
+
+
+class VegastrikeFunctionBindPanel (wx.Panel):
+  """Set bindings from a function-centric view (pick action, then assign key)."""
+
+  class BindCellEditor (wx.grid.PyGridCellEditor):
+    # Takes pieces from demo program GridCustEditor.py
+    def __init__ (self):
+      wx.grid.PyGridCellEditor.__init__(self)
+      self.created = False
+      self.avatar = None
+      self.wxid = None
+      pass
+
+    def Create (self, parent, wxid, evtHandler):
+      crumb("BindCellEditor::Create")
+      self.created = True
+      #self.avatar = wx.StaticText(parent, wxid, "Press key")
+      #self.avatar = wx.TextCtrl(parent, wxid, "Press key")
+      #self.avatar = wx.Button(parent, wxid, "Press key")
+      self.avatar = SDLKeyCapturer(parent, wxid)
+      #self.avatar.SetEditable(False)
+      self.SetControl(self.avatar)
+      self.wxid = self.avatar.GetId()
+      if evtHandler:
+        crumb("-- evtHandler=%s" % evtHandler)
+        self.avatar.PushEventHandler(evtHandler)
+
+    def Show (self, show, attr):
+      wx.grid.PyGridCellEditor.Show(self, show, attr)
+      #self.base_Show(show, attr)
+
+    def SetSize (self, rect):
+      self.avatar.SetDimensions(rect.x, rect.y, rect.width+2, rect.height+2, wx.SIZE_ALLOW_MINUS_ONE)
+      pass
+
+    def IsAcceptedKey (self, evt):
+      crumb("IsAcceptedKey: %s" % evt)
+      return True
+
+    def BeginEdit (self, row, col, grid):
+      crumb("BindCellEditor::BeginEdit")
+      # Capture all key, mouse click, and joystick events.
+      #win = self.avatar.GetParent()
+      #win.Connect(self.wxid, -1, wx.wxEVT_KEY_DOWN, self.BindCaptureHandler)
+      #self.avatar.Connect(self.wxid, self.wxid, wx.wxEVT_KEY_DOWN, self.BindCaptureHandler)
+#      win = wx.GetApp().GetTopWindow()
+#      #win.Connect(self.wxid, -1, wx.wxEVT_KEY_DOWN, self.BindCaptureHandler)
+#      self.avatar.Bind(wx.EVT_KEY_DOWN, self.BindCaptureHandler)
+#      self.avatar.Bind(wx.EVT_KEY_UP, self.BindCaptureHandler)
+#      self.avatar.Bind(wx.EVT_JOY_BUTTON_DOWN, self.BindCaptureHandler)
+#      #self.avatar.Bind(wx.EVT_JOY_MOVE, self.BindCaptureHandler)
+#      self.avatar.Bind(wx.EVT_MOUSEWHEEL, self.BindCaptureHandler)
+#      #self.avatar.Bind(wx.EVT_MOTION, self.BindCaptureHandler)
+#      self.avatar.Bind(wx.EVT_LEFT_DOWN, self.BindCaptureHandler)
+#      self.avatar.Bind(wx.EVT_MIDDLE_DOWN, self.BindCaptureHandler)
+#      self.avatar.Bind(wx.EVT_RIGHT_DOWN, self.BindCaptureHandler)
+#      self.avatar.SetFocus()
+      #win.Bind(wx.EVT_KEY_DOWN, self.BindCaptureHandler)
+      self.avatar.SetFocus()
+      pass
+
+    def EndEdit (self, row, col, grid):
+      crumb("BindCellEditor::EndEdit")
+      #win = self.avatar.GetParent()
+      #win.Disconnect(self.wxid, wx.wxEVT_KEYDOWN, self.BindCaptureHandler)
+      #self.avatar.Disconnect(self.wxid, self.wxid, wx.wxEVT_KEY_DOWN, self.BindCaptureHandler)
+#      win = wx.GetApp().GetTopWindow()
+#      #win.Connect(self.wxid, -1, wx.wxEVT_KEY_DOWN, self.BindCaptureHandler)
+#      self.avatar.Unbind(wx.EVT_KEY_DOWN)
+#      unbindstuff = [
+#        wx.EVT_KEY_DOWN, wx.EVT_KEY_UP,
+#        wx.EVT_JOY_BUTTON_DOWN,
+#        wx.EVT_MOUSEWHEEL,
+#        wx.EVT_LEFT_DOWN, wx.EVT_MIDDLE_DOWN, wx.EVT_RIGHT_DOWN,
+#      ]
+#      for evt in unbindstuff:
+#        self.avatar.Unbind(evt)
+      #win.Unbind(wx.EVT_KEY_DOWN)
+      ksym = ''
+      grid.GetTable().SetValue(row, col, ksym)
+      pass
+
+    def Reset (self):
+      pass
+
+    def BindCaptureHandler (self, evt):
+      crumb("BindCaptureHandler %s" % evt)
+      pass
+
+    pass
+
+  def __init__ (self, parent, wxid=wx.ID_ANY):
+    wx.Panel.__init__(self, parent, wxid)
+    self.build_ui()
+
+  def build_ui (self):
+    self.celleditor = self.BindCellEditor()
+
+    topsizer = wx.BoxSizer(wx.VERTICAL)
+
+    table = wx.grid.Grid(self, wx.ID_ANY)
+    table.CreateGrid(0, 4)
+#    table.SetCellValue(0, 0, "cell")
+#    table.SetCellValue(0, 1, "cell")
+
+    table.SetRowLabelSize(240)
+    table.SetColLabelValue(0, "Primary")
+    table.SetColLabelValue(1, "Secondary")
+    table.SetColLabelValue(2, "Mouse")
+    table.SetColLabelValue(3, "Joystick")
+
+#    row = 1
+#    attr = table.GetTable().GetAttr(1, 1, 0)
+#    crumb("GetAttr: %s" % attr)
+##    attr.SetReadOnly(True)
+##    table.SetRowAttr(attr)
+
+    rownum = 0
+    table.SetRowLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTRE)
+    for category in predefs.Commands:
+      name = category[0]
+      cmds = category[1]
+
+      if cmds:
+        # Category divider/header
+        table.SetRowLabelValue(rownum, "== %s ==" % name)
+        for i in xrange(0, table.GetNumberCols()):
+          table.SetReadOnly(rownum, i, True)
+          #table.SetCellBackgroundColour(rownum, i, (wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)))
+          table.SetCellBackgroundColour(rownum, i, (0,0,0))
+        table.AppendRows(1, True)
+        rownum += 1
+        for cmd in cmds:
+          if type(cmd) == tuple:
+            cmd = cmd[1]
+          table.SetRowLabelValue(rownum, cmd)
+          table.AppendRows(1, True)
+          for i in xrange(0, table.GetNumberCols()):
+            #crumb("SetCellEditor(%d, %d, %s)" % (rownum, i, self.celleditor))
+            #table.SetCellEditor(rownum, i, self.celleditor.Clone())
+            table.SetCellEditor(rownum, i, self.BindCellEditor())
+          rownum += 1
+
+    topsizer.Add(table, 1, wx.EXPAND)
+    self.SetSizer(topsizer)
+    pass
+
+
+
+
+class VegastrikeLayoutBindPanel (wx.Panel):
+  """Set binds from a layout/keyboard centric view (pick key, then assign function)."""
+  RGB_NORMAL = None
+  RGB_BOUND =  (0xee, 0xee, 0xee)
+  RGB_HILITE = (0xcc, 0xcc, 0xff)
+  RGB_SELECT = (0xff, 0xff, 0x00)
+
+  def __init__ (self, parent, wxid, title=""):
+    """Constructor.  Set up object state.  Gui constructed delegated to build_ui()."""
+    wx.Panel.__init__(self, parent, wxid)
+
+    # ** state information **
+    # mapping from keysym name to wxID
+    self.keylookup = {}
+    # mapping of wxID to key description
+    self.btndata = {}
+    # Previously-selected key (wxID); should also be currently selected key after setting.
+    self.prevkey = None
+    # Previously-selected key's background prior to being selected.
+    self.prevbg = None
+    # Flyover mode (show hovering bind information over (bound) key)
+    self.flyover = False
+    # Current keyboard layout.
+    self.kbdlayout = LAYOUT0  # TODO: from preferences
+#    self.kbdlayout = 'en/US HappyHackingLite2'
+
+    self.build_ui()
+
+  def status (self, msg=None):
+    """Update status bar."""
+#    if msg:
+#      self.SetStatusText(msg)
+#    else:
+#      self.SetStatusText("")
+    self.GetParent().GetParent().status(msg)
+    pass
+
+  def build_ui (self):
+    """Construct GUI elements.  Separated out to allow delayed building, and/or using build code from a GUI building tool (e.g. Boa Constructor)."""
+    #crumb("BindPanel.build_ui")
+    topsizer = wx.BoxSizer(wx.VERTICAL)
+
+#    line = wx.BoxSizer(wx.HORIZONTAL)
+#    line.Add(wx.StaticText(self, -1, "Layout:"), flag=wx.ALIGN_CENTER)
+#    self.pickLayout = self.make_pickLayout()
+#    line.Add(self.pickLayout)
+#    topsizer.AddSizer(line)
+
+    #crumb("BindPanel.makeReviewPanel")
+    self.reviewpanel = self.make_reviewPanel()
+    topsizer.Add(self.reviewpanel, 1, flag=wx.EXPAND)
+
+    #crumb("BindPanel.make_inpdev(KEYBAORD)")
+    self.keyb = self.make_inpdev(self.kbdlayout)
+    topsizer.Add(self.keyb, 5, flag=wx.EXPAND|wx.ALL, border=1)
+
+    #crumb("BindPanel.make_inpdev(MOUSE)")
+#    self.mouse = self.make_inpdev('mouse')
+    self.mouse = self.make_inpdev(layouts.mouse)
+    topsizer.Add(self.mouse, 1, flag=wx.EXPAND|wx.ALL, border=1)
+
+    #crumb("BindPanel.make_inpdev(JOYSTICK)")
+#    if 1:
+#      data = build_joylayout()
+#      layouts.keyboard['joystick'] = data
+#    self.joy = self.make_inpdev('joystick')
+#    self.joy = self.make_inpdev(layouts.joystick)
+    joydata = build_joylayout()
+    #crumb("BindPanel.make_inpdev(JOYSTICK)...")
+    self.joy = self.make_inpdev(joydata)
+    topsizer.Add(self.joy, 5, flag=wx.EXPAND|wx.ALL, border=1)
+
+#    line = wx.BoxSizer(wx.HORIZONTAL)
+#    self.flyover = wx.CheckBox(self, -1, "Enable tooltip/flyover mode")
+#    line.Add(self.flyover, 1, wx.ALIGN_TOP)
+#    topsizer.Add(line)
+
+    self.SetSizer(topsizer)
+    #crumb("BindPanel ready.")
+    pass
+
+  def make_legend (self):
+    pass
+
+  def make_pickLayout (self):
+    # DEPRECATED
+    """Generate GUI control element for choosing a keyboard layout.
+
+Returns wxChoice object (wxControl).
+"""
+    pickLayout = wx.Choice(self, -1)
+    pickLayout.Append("en_US_101")
+    pickLayout.Append("en_US_104")
+    pickLayout.Append("de_DE_104")
+    return pickLayout
+
+  def make_reviewPanel (self):
+    """Build the GUI element
+
+Returns wxFlexGridSizer object (wxSizer)."""
+
+#    reviewpanel = wx.FlexGridSizer(2, 4)
+    reviewpanel = wx.FlexGridSizer(2, 3)
+    lbl0 = wx.StaticText(self, -1, "Modifier")
+    lbl1 = wx.StaticText(self, -1, "Key")
+    lbl2 = wx.StaticText(self, -1, "Command")
+#    lbl3 = wx.StaticText(self, -1, "Presets")
+#    map(lambda x: reviewpanel.AddGrowableCol(x), range(0, 4))
+#    map(lambda x: reviewpanel.AddGrowableCol(x), range(2, 4))
+#    map(lambda x: reviewpanel.AddGrowableCol(x), range(1, 3))
+    reviewpanel.AddGrowableCol(1, 1)
+    reviewpanel.AddGrowableCol(2, 3)
+    reviewpanel.Add(lbl0, 1, wx.ALIGN_CENTER)
+    reviewpanel.Add(lbl1, 1, wx.ALIGN_CENTER)
+    reviewpanel.Add(lbl2, 4, wx.ALIGN_CENTER)
+#    reviewpanel.Add(lbl3, 1, wx.ALIGN_CENTER)
+
+    self.pickModifier = wx.Choice(self, -1)
+    options = [ ('(none)', 0),
+                ('shift', 0x01),
+                ('ctrl', 0x02),
+                ('ctrl+shift', 0x03),
+                ('alt', 0x04),
+                ('alt+shift', 0x05),
+                ('ctrl+alt', 0x06),
+                ('ctrl+alt+shift', 0x07),
+              ]
+    for option in options:
+      (lbl, val) = option
+      self.pickModifier.Append(lbl)
+    self.pickModifier.SetSelection(0)
+    self.entryKey = wx.TextCtrl(self, -1, style=wx.TE_READONLY)
+    self.entryCommand = wx.TextCtrl(self, -1)
+#    self.entryCommand = wx.ComboBox(self, -1, wx.CB_DROPDOWN)
+    self.pickPreset = wx.Button(self, -1, "[v preset]")
+    cmdcell = wx.BoxSizer(wx.HORIZONTAL)
+    cmdcell.Add(self.entryCommand, 1)
+    cmdcell.Add(self.pickPreset)
+#    presetcell = wx.BoxSizer(wx.HORIZONTAL)
+#    self.usePreset = wx.Button(self, -1, "<- use")
+    #self.pickPreset = wx.Choice(self, -1)
+#    self.pickPreset = wx.ComboBox(self, -1, style=wx.CB_READONLY)
+
+#    presetcell.Add(self.usePreset)
+#    presetcell.Add(self.pickPreset, 1, wx.EXPAND)
+    reviewpanel.Add(self.pickModifier, 1)
+    reviewpanel.Add(self.entryKey, 1, wx.EXPAND)
+    #reviewpanel.Add(self.entryCommand, 4, wx.EXPAND)
+    reviewpanel.Add(cmdcell, 4, wx.EXPAND)
+#    reviewpanel.Add(presetcell, 1, wx.EXPAND)
+
+    self.presetMenu = self.make_menu(predefs.Commands)
+
+    self.reviewBind(None)
+    wx.EVT_BUTTON(self, self.pickPreset.GetId(), self.guiCommandPreset)
+    wx.EVT_CHOICE(self, self.pickModifier.GetId(), self.guiChangeModifier)
+    wx.EVT_TEXT(self, self.entryCommand.GetId(), self.guiChangeCommand)
+    return reviewpanel
+
+
+
+  def make_inpdev (self, layoutname=None, oldwidget=None):
+    """make_inpdev(self, layoutname=None, oldwidget=None)
+
+Build GUI component the reflects the keyboard(?) layout described by (or in) 'layoutname'.
+
+layoutname - if string, index in layouts.keyboard that specifies a layout description (i.e. a reference to).  If a list, a literal layout description.
+oldwidget - optional widget within which to (re)construct the layout.
+
+'oldwidget' is used to for changing layouts, where all the contents of self.keyb (et al.) are destroyed and rebuilt from the layout description.
+
+Returns new wxPanel, or oldwidget itself if provided.
+"""
+
+    if not layoutname:
+      layoutname = self.kbdlayout
+
+    if type(layoutname) == list:
+      # Assume direct description.
+      layout = layoutname
+    else:
+      layout = layouts.keyboard[layoutname]
+
+    #crumb("using layout data: %s" % layout)
+
+    if oldwidget:
+      inpdev = oldwidget
+      # Remove all children.
+#      inpdev.DestroyChildren()
+#      self.keylookup = {}
+#      self.btndata = {}
+      for child in self.GetChildren():
+        childid = child.GetId()
+        ksym = self.btndata.get(childid, [None,None,None,None])[2]
+        #crumb("%s" % self.btndata)
+        #crumb("DestroyChildren: wxid=%d, ksym=%s" % (childid, self.btndata[childid]))
+        if self.btndata.has_key(childid):
+          del self.btndata[childid]
+        if not ksym is None:
+          if self.keylookup.has_key(ksym):
+            #crumb("DestroyChildren: remove from keylookup: %s,%d" % (ksym, childid))
+            self.keylookup[ksym].remove(childid)
+      inpdev.Freeze()
+      inpdev.DestroyChildren()
+    else:
+      inpdev = wx.Panel(self, -1)
+      inpdev.Freeze()
+
+#    wx.StaticBox(inpdev, -1, "Input Device")
+    w0, h0 = 5, 28
+    ncols, nrow = 160, 8
+    rowidx, colidx = -1, 0
+    devwidth, devheight = 1, 1
+    keyid = 8
+    for keyrow in layout:
+      colidx = 1
+      for keycol in keyrow:
+        keydata = keycol
+        #crumb("keydata=%s" % keydata)
+        (kwidth, kheight, ksym, ktop) = keydata
+        if kheight == 0:
+          devwidth = kwidth
+          continue
+        if ktop is None:
+          ktop = ""
+        btn = None
+        if ktop:
+#          crumb("ktop='%s'" % ktop)
+          btn = wx.Button(inpdev, -1, ktop)
+#          btn = wx.ToggleButton(self, -1, ktop)
+#          btn.SetBackgroundColour((0xcc, 0xcc, 0xff))
+#          btn.SetBackgroundColour(self.RGB_BOUND)
+          if ksym:
+            wx.EVT_ENTER_WINDOW(btn, self.guiHoverKey)
+            wx.EVT_LEAVE_WINDOW(btn, self.guiHoverKey)
+            wx.EVT_BUTTON(self, btn.GetId(), self.guiSelectKey)
+            btn.SetName(ksym)
+            btn.ksym = ksym
+            if not self.keylookup.has_key(ksym):
+              self.keylookup[ksym] = []
+            self.keylookup[ksym].append(btn.GetId())
+            #crumb("LOOKUP [%s] => %d" % (ksym, btn.GetId()))
+            self.btndata[btn.GetId()] = keydata
+          else:
+            btn.Enable(0)
+          keyid += 1
+        else:
+          btn = wx.StaticText(inpdev, -1, "")
+        x, w = colidx * w0, kwidth * w0
+        y, h = rowidx * h0, kheight * h0
+        btn.SetSize((w, h))
+        btn.Move((x, y))
+        colidx += kwidth
+      rowidx += 1
+      devheight += 1
+    inpdev.SetSize((devwidth * w0, devheight * h0))
+    inpdev.Thaw()
+    #crumb("finished btndata = %s" % self.btndata)
+    return inpdev
+
+    inpdev0 = wx.Panel(self, -1)
+    #inpdev.SetParent(inpdev0)
+    sizer = wx.BoxSizer(wx.VERTICAL)
+    sizer.Add(wx.StaticBox(inpdev0, -1), 1, wx.EXPAND)
+    #wx.StaticBox(inpdev0, -1)
+    sizer.Add(inpdev, 1, wx.EXPAND)
+    inpdev0.SetSizer(sizer)
+    return inpdev0
+    pass
+
+
+  def reviewBind (self, keyid):
+    """Update the reviewBind view with the specified keyid.  Used for assigning command to key."""
+    #crumb("reviewBind: %s" % keyid)
+    if keyid is None:
+      self.entryCommand.Enable(False)
+      self.entryCommand.SetValue("")
+      self.entryKey.Enable(False)
+      self.entryKey.SetValue("")
+      self.pickPreset.Enable(False)
+      self.status(None)
+      return
+    else:
+      self.entryCommand.Enable(True)
+      self.entryKey.Enable(True)
+      self.pickPreset.Enable(True)
+    btn = self.FindWindowById(keyid)
+    ksym = btn.ksym
+    if (ksym is None) or (ksym == ''):
+      # no symbol, use keytop label.
+      ksym = btn.GetLabel()
+    # copy keysym name
+    self.entryKey.SetValue(ksym)
+    # copy binding
+    #self.entryCommand.SetValue("BINDING HERE")
+    binding = wx.GetApp().GetBinding(ksym)
+    if binding is None:
+      self.entryCommand.SetValue("")
+    else:
+      self.entryCommand.SetValue(binding)
+    self.status("Binding key '%s'" % ksym)
+
+
+  def guiNop (self, evt):
+    """Do nothing."""
+    pass
+
+  def guiChangeModifier (self, evt):
+    """Change in the shift modifier, drop-down list."""
+    response = evt.GetSelection()
+    #crumb("ChangeModifier => %s" % response)
+    wx.GetApp().UseModifierLevel(response)
+    evt.Skip()
+    pass
+
+  def guiHoverKey (self, evt):
+    """Mouse hovers over key.  Trigger flyover tooltip if enabled."""
+    wxid = evt.GetId()
+    if self.flyover:
+      if wxid:
+        #crumb("Hovering over: %s" % wxid)
+        #btn = self.FindWindowById(wxid)
+        btndata = self.btndata[wxid]
+        #ksym = btn.ksym
+        ksym = btndata[2]
+        binding = wx.GetApp().GetBinding(ksym)
+        #crumb("flyover binding => %s" % binding)
+        self.show_flyover(evt.GetPosition(), binding)
+      else:
+        self.show_flyover((0,0), None)
+    evt.Skip()
+    pass
+
+  def guiSelectKey (self, evt):
+    """User clicked on a key.  Should update the reviewPanel."""
+    #crumb("guiSelectKey")
+    keyid = evt.GetId()
+    if keyid:
+      obj = self.FindWindowById(keyid)
+      try:
+        ksym = obj.ksym
+      except:
+        ksym = obj.GetLable()
+      #crumb("selected obj [%d] = '%s'" % (keyid, obj.GetLabel()))
+      #crumb("selected obj [%d] = '%s'" % (keyid, ksym))
+      self.SetButtonBg(self.prevkey, self.prevbg)
+#      if self.prevkey:
+#        prevbtn = self.FindWindowById(self.prevkey)
+#        #prevbtn.SetBackgroundColour(self.prevbg)
+#        prevbtn.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
+      self.prevbg = self.SetButtonBg(keyid, self.RGB_SELECT)
+#      btn = self.FindWindowById(keyid)
+#      self.prevbg = btn.GetBackgroundColour()
+#      btn.SetBackgroundColour(self.RGB_SELECT)
+      self.prevkey = keyid
+
+      self.reviewBind(keyid)
+    evt.Skip()
+    pass
+
+  def guiOneField (self, evt):
+    """Handle one-field menu item in 'Presets' popup menu."""
+    #crumb("guiOneField")
+    wxid = evt.GetId()
+    menuitem = self.presetMenu.FindItemById(wxid)
+    lbl = menuitem.GetItemLabelText()
+    #crumb("Using preset %s" % lbl)
+    if (lbl[0] == '('):
+      lbl = ''
+    self.fill_command(lbl)
+
+  def guiTwoField (self, evt):
+    """Handle two-field menu item in 'Presets' popup menu."""
+    #crumb("guiTwoField")
+    wxid = evt.GetId()
+    menuitem = self.presetMenu.FindItemById(wxid)
+    lbl = menuitem.GetItemLabelText()
+    #crumb("Using preset %s" % lbl)
+    if (lbl[0] == '('):
+      lbl = ''
+    self.fill_command(lbl)
+
+  def guiChangeCommand (self, evt):
+    """User edited the 'command' text entry, either directly by typing or by using the 'Presets' popup menu."""
+    ksym = self.entryKey.GetValue()
+    cmd = self.entryCommand.GetValue()
+    wx.GetApp().SetBinding(None, ksym, cmd)
+    if cmd:
+      self.prevbg = self.RGB_BOUND
+    else:
+      self.prevbg = self.RGB_NORMAL
+    evt.Skip()
+
+  def SetButtonBg (self, wxid, rgb):
+    """Set button background color.  If rgb is None, then use normal system color."""
+    btn = None
+    if wxid is None:
+      return
+    if type(wxid) == int:
+      btn = self.FindWindowById(wxid)
+    else:
+      btn = wxid
+    prevrgb = btn.GetBackgroundColour()
+    if rgb is None:
+      btn.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
+    else:
+      btn.SetBackgroundColour(rgb)
+    return prevrgb
+
+  def guiCommandPreset (self, evt):
+    """User clicked on a button/control that opens up the 'Preset' popup menu."""
+    popup = self.PopupMenu(self.presetMenu, self.pickPreset.GetPosition())
+    pass
+
+  def make_menu (self, data):
+    """Construct a menu from a list of lists....
+
+TODO: describe menu description format.
+"""
+    return make_menu(self, data)
+
+  def load_config (self, path=None):
+    """Load configuration file.  Delegate to wxApp instance."""
+    wx.GetApp().LoadConfig(upath)
+
+  def show_flyover (self, pos, binding):
+    """Show the flyover display.  May be a tooltip window, or just showing in status bar, or something else.
+
+Call with binding=None to hide flyover text.
+"""
+    if binding is None:
+      # Hide/remove
+      self.status("")
+    else:
+      self.status(binding)
+
+  def update_all_keys (self):
+    """Update colors on all bind buttons, e.g. after a command was just assigned, or after loading a config file."""
+    for ksym in self.keylookup:
+      wxidlist = self.keylookup[ksym]
+      for wxid in wxidlist:
+#        wxid = self.keylookup[ksym]
+        btn = self.FindWindowById(wxid)
+        if not btn:
+          continue
+        binding = wx.GetApp().GetBinding(ksym)
+        if binding is None:
+          # Set normal.
+          if btn.GetId() == self.prevkey:
+            self.prevbg = self.RGB_NORMAL
+            self.reviewBind(self.prevkey)
+          else:
+            self.SetButtonBg(wxid, self.RGB_NORMAL)
+        else:
+          #crumb("Marking bound %d[%s] (prev=[%s])" % (btn.GetId(), btn.ksym, self.prevkey))
+          if btn.GetId() == self.prevkey:
+            self.prevbg = self.RGB_BOUND
+            self.reviewBind(self.prevkey)
+          else:
+            self.SetButtonBg(wxid, self.RGB_BOUND)
+          pass # if not binding
+        pass # for ksym
+      pass  # for ksymlist
+    pass
+
+  def change_layout (self, newlayoutname):
+    """Cause change in keyboard layout."""
+    #crumb("Change layout to '%s'" % newlayoutname)
+    self.make_inpdev(newlayoutname, self.keyb)
+    self.kbdlayout = newlayoutname
+    self.prevkey = None
+    self.prevbg = None
+    self.reviewBind(None)
+    wx.GetApp().UseModifierLevel()
+
+  def fill_command (self, cmdval):
+    """Paste content into the 'command' text entry (assign command to key).
+
+Used by the 'Presets' menu.
+"""
+    self.entryCommand.SetValue(cmdval)
+
+  pass
+
+
+
+
+
+
+
+
+class TestPanel (wx.Panel):
+  """testing puposes..."""
+  def __init__ (self, parent, wxid):
+    wx.Panel.__init__(self, parent, wxid)
+    wx.StaticText(self, -1, "Test Panel", (30, 20))
+
+
+
+class VegastrikeKeyBinderFrame (wx.Frame):
+  """Main window.  Contains menu bar, status bar, and the main frame with notebook tabs.
+"""
+  ID_FLYOVER = 1000
+  ID_VIEWFUNC = 1001
+  ID_REJOYSTICK = 1002
+  ID_KBDLAYOUT = 1003
+
+  def __init__ (self, parent, wxid, title):
+    """Initialize object state, build GUI."""
+    wx.Frame.__init__(self, parent, wxid, title)
+    self.build_ui()
+
+  def status (self, msg=None):
+    """Set status."""
+    if msg:
+      self.SetStatusText(msg)
+    else:
+      self.SetStatusText("")
+
+  def build_ui (self):
+    """Build GUI.  Delayed construction and/or call code generated by a GUI building tool (Boa Constructor)."""
+    #crumb("BinderFrame: init")
+    self.CreateStatusBar()
+#    self.SetStatusText("-- initialised statusbar --")
+    self.status("First, click on key to set binding")
+
+    #crumb("BinderFrame: make menubar")
+    menubar = self.make_menubar()
+    self.SetMenuBar(menubar)
+
+    topsizer = wx.BoxSizer(wx.VERTICAL)
+
+    #crumb("BinderFrame: notebook")
+    nb = wx.Notebook(self)
+#
+#    #crumb("BinderFrame: notebook page 1")
+    page = self.layoutpanel = VegastrikeLayoutBindPanel(nb, -1)
+    nb.AddPage(page, "Layout")
+#    page = TestPanel(nb, -1)
+#    nb.AddPage(page, "WTF")
+    #crumb("BinderFrame: notebook page 2")
+    # TODO: function-bind panel.
+#    page = self.funcpanel = VegastrikeFunctionBindPanel(nb, -1)
+#    nb.AddPage(page, "Function")
+#
+#    nb.SetSelection(1)
+
+    topsizer.Add(nb)
+
+    #crumb("BinderFrame: munge menu")
+    # Updated selected layout.
+    currlayout = self.layoutpanel.kbdlayout
+    # 1. Assumes all the layout-setting items are grouped in same submenu.
+    firstlayoutitem = self.GetMenuBar().FindItemById(self.ID_KBDLAYOUT)
+    if firstlayoutitem:
+      #crumb("firstlayoutitem=%s" % firstlayoutitem)
+      # 2. Get the menu the item belongs to.  Sibling items have same menuID.
+      layoutsubmenu = firstlayoutitem.GetMenu()
+      if layoutsubmenu:
+        #crumb("layoutsmenu=%s" % layoutsubmenu)
+        # 3. Search sibling items until label matches current keyboard layout.
+        for itemidx in xrange(0, layoutsubmenu.GetMenuItemCount()):
+          langitem = layoutsubmenu.FindItemByPosition(itemidx)
+          if langitem.GetItemLabelText() == currlayout:
+            # 4. Matching item - set checked.  Check final one if multi-match.
+            #crumb("langitem = %d/%s: %s" % (itemidx, langitem, langitem.GetItemLabelText()))
+            langitem.Check(1)
+    #crumb("BinderFrame: done")
+    pass
+
+  def make_legend (self):
+    # ???
+    pass
+
+
+  def guiNop (self, evt):
+    """Do nothing."""
+    pass
+
+  def guiQuit (self, evt):
+    """Handle GUI Quit action."""
+    print("Quitting")
+    self.Close(True)
+    pass
+
+  def guiAbout (self, evt):
+    """Handle GUI About action."""
+    #dlg = VegastrikeLayoutBindAbout(self, -1, "About %s %s" % (PACKAGE, VERSION))
+    #response = dlg.ShowModal()
+    aboutinfo = wx.AboutDialogInfo()
+    if ICON:
+      aboutinfo.SetIcon(ICON)
+    aboutinfo.SetName(PACKAGE)
+    aboutinfo.SetVersion(VERSION)
+    aboutinfo.SetDescription(DESCRIPTION)
+    aboutinfo.SetCopyright(COPYRIGHT)
+    aboutinfo.SetLicence(LICENSE)
+    aboutinfo.SetWebSite(HOMEPAGE)
+    for dev in DEVELOPERS:
+      aboutinfo.AddDeveloper(dev)
+
+    aboutbox = wx.AboutBox(aboutinfo)
+    pass
+
+  def guiHelp (self, evt):
+    """Handle GUI Help action."""
+    pass
+
+  def guiNew (self, evt):
+    """Handle GUI File>New action."""
+    pass
+
+  def guiLoad (self, evt):
+    """Handle GUI File>Load action."""
+    # Select a file.
+    title = "Open Vegastrike configuration file"
+    cwd = ""
+    fname = ""
+    wildcard = "|".join(["Vegastrike (*.config)|*.config", "Any (*.*)|*.*"])
+    style = wx.FD_OPEN | wx.FD_CHANGE_DIR
+    dlg = wx.FileDialog(self, title, cwd, fname, wildcard, style)
+    response = dlg.ShowModal()
+    if response == wx.ID_OK:
+      upath = dlg.GetPath()
+      crumb("Loading %s" % upath)
+      self.load_config(upath)
+    pass
+
+  def guiRevert (self, evt):
+    """Handle GUI File>Revert action."""
+    pass
+
+  def guiSave (self, evt):
+    """Handle GUI File>Save action."""
+    upath = wx.GetApp().GetConfigName()
+    if upath:
+      self.status("Saving to '%s'" % wx.GetApp().GetConfigName())
+      wx.GetApp().SyncConfig()
+      self.save_config(upath)
+    else:
+      self.guiSaveAs(evt)
+    pass
+
+  def guiSaveAs (self, evt):
+    """Handle GUI File>Save As action."""
+    self.status("Saving to named file...")
+    # Select a file.
+    title = "Save Vegastrike configuration file"
+    cwd = ""
+    fname = ""
+    wildcard = "|".join(["Vegastrike (*.config)|*.config", "Any (*.*)|*.*"])
+    style = wx.FD_SAVE | wx.FD_CHANGE_DIR
+    dlg = wx.FileDialog(self, title, cwd, fname, wildcard, style)
+    response = dlg.ShowModal()
+    if response == wx.ID_OK:
+      upath = dlg.GetPath()
+      crumb("Saving %s" % upath)
+      self.save_config(upath)
+    else:
+      self.status("Save cancelled")
+    pass
+
+  def guiDump (self, evt):
+    """Handle GUI File>Dump action; debug use."""
+    wx.GetApp().DumpConfig()
+
+
+  def guiCopy (self, evt):
+    """Handle GUI Edit>Copy action."""
+    pass
+
+  def guiCut (self, evt):
+    """Handle GUI Edit>Cut action."""
+    pass
+
+  def guiPaste (self, evt):
+    """Handle GUI Edit>Paste action."""
+    pass
+
+  def guiPrefs (self, evt):
+    """Handle GUI Preferences action."""
+    pass
+
+  def guiKeylayout (self, evt):
+    """Handle GUI Keyboard (layout) action."""
+    wxid = evt.GetId()
+    #crumb("guiKeylayout: %d" % wxid)
+    menuitem = self.GetMenuBar().FindItemById(wxid)
+    newlayoutname = menuitem.GetItemLabelText()
+    #crumb("switch to layout: %s" % newlayoutname)
+    self.layoutpanel.change_layout(newlayoutname)
+    pass
+
+  def guiFlyover (self, evt):
+    """Handle GUI Flyover [toggle] action."""
+    if evt.IsChecked():
+      self.layoutpanel.flyover = True
+      self.status("Flyover enabled.  Place mouse over key to view its binding.")
+    else:
+      self.layoutpanel.flyover = False
+      self.status("Flyover disabled.")
+    pass
+
+  def make_menubar (self):
+    """Build menubar object.
+Returns wxMenuBar.
+"""
+    menubar = wx.MenuBar()
+
+    # Collect names of all stored layouts.
+    layoutnames = [ n for n in layouts.keyboard ]
+    layoutnames.sort()
+    menudata_layouts = []
+    layoutid = self.ID_KBDLAYOUT  # First item only, siblings get -1.
+    for item in layoutnames:
+      #menuitemdata = ("@%s" % item, -1, self.guiKeylayout, '')
+      menuitemdata = ("@%s" % item, layoutid, self.guiKeylayout, '')
+      layoutid = -1   # yeah, it does it over and over again...
+      menudata_layouts.append(menuitemdata)
+
+    menudata_layouts0 = [
+      ('@en/US 101', -1, self.guiKeylayout, ''),
+      ('@en/US 104+deadkeys', -1, self.guiKeylayout, ''),
+      ('@en/US 104', -1, self.guiKeylayout, ''),
+    ]
+
+#    crumb("menudata=%s" % menudata_layouts)
+#    crumb("menudata=%s" % menudata_layouts0)
+
+    # item <- ( lbl, id, callback, tooltip )
+    data = [
+      ( '&File',
+          [
+#            ( '&New', wx.ID_NEW, self.guiNew, 'Start with blank bindings.' ),
+            ( '&New', wx.ID_NEW, self.guiNop, '' ),
+            ( '&Load', wx.ID_OPEN, self.guiLoad, '' ),
+#            ( '&Revert', wx.ID_REVERT, self.guiRevert, '' ),
+            ( '&Revert', wx.ID_REVERT, self.guiNop, '' ),
+            ( '&Save', wx.ID_SAVE, self.guiSave, '' ),
+            ( 'Save &As', wx.ID_SAVEAS, self.guiSaveAs, '' ),
+#            ( 'Dump Config [debug]', -1, self.guiDump, '' ),
+            None,
+            ( '&Quit', wx.ID_EXIT, self.guiQuit, '' ),
+          ] ),
+      ( '&Edit',
+          [
+            ( '&Copy', wx.ID_COPY, self.guiCopy, '' ),
+            ( 'C&ut', wx.ID_CUT, self.guiCut, '' ),
+            ( '&Paste', wx.ID_PASTE, self.guiPaste, '' ),
+            None,
+#            ( 'Prefere&nces', wx.ID_PREFERENCES, self.guiPrefs, '' ),
+            ( 'Prefere&nces', wx.ID_PREFERENCES, self.guiNop, '' ),
+          ] ),
+      ( '&View',
+          [
+            ( '&Layouts',
+#              [
+#                ('@en/US 101', -1, self.guiKeylayout, ''),
+#                ('@en/US 104+deadkeys', -1, self.guiKeylayout, ''),
+#                ('@en/US 104', -1, self.guiKeylayout, ''),
+#              ]
+              menudata_layouts
+                ),
+            ( 'Recheck &Joysticks', self.ID_REJOYSTICK, self.guiNop, 'Reconstruct joystick information from currently attached joysticks.' ),
+            ( '+&Flyover', self.ID_FLYOVER, self.guiFlyover, 'Place mouse over key to view its binding'),
+            None,
+            ( 'Change to Function &View', self.ID_VIEWFUNC, self.guiNop, 'View function-centric binding'),
+          ] ),
+      ( '&Help',
+          [
+            ( '&About', wx.ID_ABOUT, self.guiAbout, '' ),
+            None,
+            ( '&Content', wx.ID_HELP, self.guiHelp, '' ),
+          ] ),
+    ]
+    for baritem in data:
+      ( lbl, submenu ) = baritem
+      submenu = self.make_menu(submenu)
+      menubar.Append(submenu, lbl)
+
+    return menubar
+
+  def make_menu (self, data):
+    """Build menu.
+"""
+    return make_menu(self, data)
+
+  def load_config (self, path=None):
+    """Called by File>Load menu item."""
+    wx.GetApp().LoadConfig(path)
+
+  def save_config (self, path=None):
+    """Called by File>Save(As) menu item(s)."""
+    if wx.GetApp().SaveConfig(path):
+      self.status("Saved to file '%s'" % path)
+    else:
+      self.status("Failed to save file '%s'" % path)
+
+  def update_all_keys (self):
+    """Called by keyboard layout menu item."""
+    self.layoutpanel.update_all_keys()
+    pass
+
+
+
+
+
+
+
+
+
+
+
+class VegastrikeKeyBinderApp (wx.App):
+  """wxApp instance."""
+
+  MODVAL_SHIFT = 1
+  MODVAL_CTRL = 2
+  MODVAL_ALT = 4
+
+  def OnInit (self):
+    #crumb("AppInit.")
+    appname = "%s %s" % (PACKAGE, VERSION)
+    #frame = self.frame = VegastrikeLayoutBindFrame(None, -1, appname)
+    frame = self.frame = VegastrikeKeyBinderFrame(None, -1, appname)
+    frame.SetSize((700, 600))
+    frame.SetMinSize((320, 240))
+    frame.Show(True)
+    self.SetTopWindow(frame)
+
+    #crumb("AppInit: form bindings.")
+    # *** app internal state ***
+    # Dictionary for each modifier level (8 of them).
+    # Dictionary maps keysym => command.
+    self.bindings = []
+    for i in xrange(0, 8):
+      self.bindings.append({})
+    # Current modifier level.
+    self.modifier = 0
+    # Current selected keyboard layout
+    self.layoutname = None
+    # Path of current loaded configuration file.
+    self.cfgname = None
+    # Currently loaded configuration data.
+    self.cfgdom = None
+
+    #self.UseLayout("en/US 104")
+
+    # Elide later
+    #crumb("AppInit: Load config")
+    self.LoadConfig("./vegastrike.config")
+    self.SyncConfig()
+
+    return True
+
+  def GetLayout (self):
+    """Get currently selected keyboard layout name."""
+    return self.layoutname
+
+  def UseLayout (self, layoutname):
+    """Change keyboard layout."""
+    self.layoutname = layoutname
+    self.frame.layoutpanel.change_layout(layoutname)
+
+  def GetModifierLevel (self):
+    """Get current shift modifier level."""
+    return self.modifier
+
+  def UseModifierLevel (self, modlevel=None):
+    """Change shift modifier level."""
+    if not modlevel is None:
+      self.modifier = modlevel
+    #crumb("Using modifier level %d" % self.modifier)
+    # TODO: and do stuff... update all keys?
+    self.frame.update_all_keys()
+    pass
+
+  def GetBinding (self, modlevel__keysym, __keysym=None):
+    """Get binding for specified keysym (with optionally specified shift modifier level)."""
+    binding = None
+    modlevel, keysym = 0, None
+    if __keysym:
+      # 2-arg form
+      modlevel = modlevel__keysym
+      keysym = __keysym
+    else:
+      # 1-arg form
+      modlevel = self.modifier
+      keysym = modlevel__keysym
+    try:
+      binding = self.bindings[modlevel][keysym]
+    except (IndexError, KeyError):
+      binding = None
+    #crumb("retrieving binding '%d:%s' => %s" % (modlevel, keysym, binding))
+    return binding
+
+  def SetBinding (self, modlevel, keysym, command):
+    """Set binding for keysym at specified shift modifier level."""
+    # hack to handle the way Vegastrike config handles shift.
+#    layoutname = "en/US 101"
+    layoutname = self.frame.layoutpanel.kbdlayout
+    if modlevel is None:
+      modlevel = self.modifier
+    if keysym in layouts.unshiftmap[layoutname]:
+      # Shifted character.  Mark 'shift'.
+      modlevel += self.MODVAL_SHIFT
+      #crumb("Unshifting %s -> %s" % (keysym, layouts.unshiftmap[layoutname][keysym]))
+      keysym = layouts.unshiftmap[layoutname][keysym]
+
+    self.bindings[modlevel][keysym] = command
+    #crumb("/bind %d:%s '%s'" % (modlevel, keysym, command))
+
+#  def modattr2modlevel (self, attrstr, ksym=None):
+  def modattr2modlevel (self, attrstr):
+    """Convert modifier string in config file into modlevel value:
+shift => +=1
+ctrl => +=2
+alt => +=4
+"""
+    modlevel = 0
+    if attrstr:
+      if "ctrl" in attrstr:
+        modlevel += self.MODVAL_CTRL
+      if "alt" in attrstr:
+        modlevel += self.MODVAL_ALT
+      # any "shift" is implied in 'key' (e.g. shift+s => 'S')
+#    if ksym:
+#      if ksym in layouts.unshiftmap['en_US_101']:
+#        # Shifted character.  Mark 'shift'.
+#        modlevel += 1
+    return modlevel
+
+  def modlevel2modattr (self, modlevel):
+    """Convert modlevel to modifier string:
+modlevel & 1  =>  +'shift'  ??
+modlevel & 2  =>  +'ctrl'
+modlevel & 4  =>  +'alt'
+"""
+    modarr = []
+    if not modlevel:
+      modarr.append("none")
+    if modlevel & 0x02:
+      modarr.append("ctrl")
+    if modlevel & 0x04:
+      modarr.append("alt")
+    modattr = "+".join(modarr)
+    return modattr
+
+  def GetCandidateAB (self, domlist, modattr, ksym, eigenattr, battr):
+    """Retrieve bind node based on a keysym pattern <modlevel>:<EIGENATTR>-<A>.<B>
+"""
+    apos = ksym.find("-") + 1
+    bpos = ksym.find(".")
+    a = int(ksym[apos:bpos])
+    b = int(ksym[bpos+1:])
+    # Find by matching mouse number and button number.
+    candidates = [ n for n in domlist if n.hasAttribute(eigenattr) and int(n.getAttribute(eigenattr)) == a and int(n.getAttribute(battr)) == b ]
+    # Filter out just this modlevel.
+    candidates = [ n for n in candidates if (n.hasAttribute("modifier") and n.getAttribute("modifier") == modattr) or ( (not n.hasAttribute("modifier")) and (modattr == "none") ) ]
+    if candidates:
+      return candidates[0]
+    return None  # Not found.
+
+  def GetConfigName (self):
+    """Get currently loaded config file name (for use with File>Save)."""
+    return self.cfgname
+
+  def SyncConfig (self):
+    """Copy contents of active bindings (app data) into DOM tree, in preparation for saving."""
+    #crumb("SyncConfig")
+    # Update configuration DOM with current settings.
+    bindingsdom = self.cfgdom.getElementsByTagName("bindings")[0]
+
+    #crumb("bindings dump: %s" % self.bindings)
+
+    # Scour all entries in bindings
+    for modlevel in xrange(0, len(self.bindings)):
+      modattr = self.modlevel2modattr(modlevel)
+      for ksym in self.bindings[modlevel]:
+        bound = self.bindings[modlevel][ksym]
+        #crumb("synchronzing %d:%s" % (modlevel, ksym))
+        bindnode = None
+        bindnodes = self.cfgdom.getElementsByTagName("bind")
+        axisnodes = self.cfgdom.getElementsByTagName("axis")
+        if ksym.find("mouse-") == 0:
+          # Get those nodes that have attribute "mouse"
+          candidate = self.GetCandidateAB(bindnodes, modattr, ksym, "mouse", "button")
+          #crumb("mouse/candidates=%s" % candidate)
+          pass
+        elif ksym.find("joystick-") == 0:
+          candidate = self.GetCandidateAB(bindnodes, modattr, ksym, "joystick", "button")
+          #crumb("joystick/candidates=%s" % candidate)
+          pass
+        elif ksym.find("digital-hatswitch-") == 0:
+          candidate = self.GetCandidateAB(bindnodes, modattr, ksym, "digital-hatswitch", "button")
+          #crumb("digital-hatswitch/candidates=%s" % candidate)
+          pass
+        elif ksym.find("axis:mouse-") == 0:
+          candidate = self.GetCandidateAB(axisnodes, modattr, ksym, "mouse", "axis")
+          #crumb("axis:mouse/candidates=%s" % candidate)
+        elif ksym.find("axis:joystick-") == 0:
+          candidate = self.GetCandidateAB(axisnodes, modattr, ksym, "mouse", "axis")
+          #crumb("axis:joystick/candidates=%s" % candidate)
+          pass
+        else:
+          # Keyboard
+          # Find by matching dev number and button number.
+          keyname = ksym
+
+          if modlevel & self.MODVAL_SHIFT:
+            currlayout = self.frame.layoutpanel.kbdlayout
+            # Get shifted keysym
+            keyname = layouts.shiftmap[currlayout][ksym]
+            #crumb("from shift1 => keyname now '%s'" % keyname)
+            # "shift" => "none"
+            emodlevel = modlevel & ~(self.MODVAL_SHIFT)  # Effective modifier level.
+            modattr = self.modlevel2modattr(modlevel & ~(self.MODVAL_SHIFT))
+            #crumb("pull binding [%s][%s]" % (modlevel, ksym))
+            #bound = self.bindings[emodlevel][ksym]
+            bound = self.bindings[modlevel][ksym]
+
+          eigenattr = "key"
+          candidates = [ n for n in bindnodes if n.hasAttribute(eigenattr) and n.getAttribute(eigenattr) == keyname ]
+          #crumb("Candidates@1 : %d" % len(candidates))
+          # Filter out just this modlevel.
+          candidates = [ n for n in candidates if (n.hasAttribute("modifier") and n.getAttribute("modifier") == modattr) or ( (not n.hasAttribute("modifier")) and (modattr == "none") ) ]
+          #crumb("Candidates@2 : %d" % len(candidates))
+          if candidates:
+            candidate = candidates[0]
+#            crumb("key/candidates[%s@%s]=%s" % (keyname, modattr, candidate))
+            #crumb("key/candidates[%s@%s]=%s" % (keyname, modattr, candidate.getAttribute("command")))
+          else:
+            candidate = None
+          if candidate:
+            if not bound:
+              # erase node.
+              #crumb("erase node '%s'" % candidate)
+              parentnode = candidate.parentNode
+              parentnode.removeChild(candidate)
+              candidate = None
+            else:
+              # modify node.
+              candidate.setAttribute("command", bound)
+          else:
+            if bound:
+              bindnode = self.cfgdom.createElement("bind")
+              attrs = {
+                "key": keyname,
+                "modifier": modattr,
+                "command": bound
+              }
+              for k,v in attrs.items():
+                bindnode.setAttribute(k, v)
+              txt = "\n" + "".join([" "] * 16)
+              bindingsdom.appendChild(self.cfgdom.createTextNode(txt))
+              bindingsdom.appendChild(bindnode)
+              #crumb("<bindings>.appendChild(%s)" % bindnode)
+            pass
+          pass
+        pass
+#    crumb("dump SyncConfig:")
+#    crumb(self.cfgdom.toprettyxml())
+    pass
+
+  def DumpConfig (self):
+    """Dump current bindings as DOM tree (debug save)."""
+    self.SyncConfig()
+    crumb("DumpConfig:")
+    crumb(self.cfgdom.toxml())
+#    crumb(self.cfgdom.toprettyxml())
+
+  def SaveConfig (self, path=None):
+    """Save DOM tree to file."""
+    if path:
+      crumb("Saving config %s" % path)
+      savefile = open(path, "wt")
+      savefile.write(self.cfgdom.toxml())
+      savefile.close()
+      return True
+    return False
+
+  def LoadConfig (self, path=None):
+    """Load key bindings from file."""
+    if path:
+      crumb("Loading config %s" % path)
+      self.cfgname = path
+      self.cfgdom = xml.dom.minidom.parse(path)
+      try:
+        bindingsnode = self.cfgdom.getElementsByTagName("bindings")[0]
+      except IndexError:
+        crumb("ERROR: 'bindings' section not found (is the config file correct?  Corrupt?).")
+        return
+      for childnode in bindingsnode.childNodes:
+        if childnode.nodeType == childnode.COMMENT_NODE:
+          pass
+        if childnode.nodeType == childnode.ELEMENT_NODE:
+          if childnode.tagName == 'bind':
+            # Digital (boolean) controls.
+            mod = childnode.getAttribute("modifier")
+            cmd = childnode.getAttribute("command")
+
+            modlevel = self.modattr2modlevel(mod)
+
+            if childnode.hasAttribute("key"):
+              # bind to keyboard key
+              key = childnode.getAttribute("key")
+#              modlevel = self.modattr2modlevel(mod, key)
+              self.SetBinding(modlevel, key, cmd)
+              pass
+            elif childnode.hasAttribute("mouse"):
+              # bind to mouse
+              mouse = int(childnode.getAttribute("mouse"))
+              if childnode.hasAttribute("button"):
+                button = childnode.getAttribute("button")
+                key = "mouse-%s.%s" % (mouse, button)
+                self.SetBinding(modlevel, key, cmd)
+              pass
+            elif childnode.hasAttribute("joystick"):
+              joystick = childnode.getAttribute("joystick")
+              if childnode.hasAttribute("button"):
+                # joystick button
+                button = childnode.getAttribute("button")
+                key = "joystick-%s.%s" % (joystick, button)
+                self.SetBinding(modlevel, key, cmd)
+                pass
+              elif childnode.hasAttribute("digital-hatswitch"):
+                # hatswitch.
+                hatswitch = int(childnode.getAttribute("digital-hatswitch"))
+                direction = childnode.getAttribute("direction")
+                key = "digital-hatswitch:%d.%s" % (hatswitch, direction)
+                self.SetBinding(modlevel, key, cmd)
+                pass
+              pass
+            elif childnode.hasAttribute("hatswitch"):
+              pass
+          elif childnode.tagName == 'axis':
+            # Analog controls.
+            mod = childnode.getAttribute("modifier")
+            #cmd = childnode.getAttribute("command")
+            cmd = childnode.getAttribute("name")  # what?
+            modlevel = self.modattr2modlevel(mod)
+            if childnode.hasAttribute("mouse"):
+              if childnode.hasAttribute("axis"):
+                axis = childnode.getAttribute("axis")
+                mouse = childnode.getAttribute("mouse")
+                key = "axis:mouse-%s.%s" % (mouse, axis)
+                self.SetBinding(modlevel, key, cmd)
+            elif childnode.hasAttribute("joystick"):
+              joystick = childnode.getAttribute("joystick")
+              if childnode.hasAttribute("hatswitch"):
+                # analog hatswitch
+                axisnum = int(childnode.getAttribute("axis"))
+                devnum = int(childnode.getAttribute("devnum"))
+                hatnum = int(childnode.getAttribute("hatnum"))
+                margval = float(childnode.getAttribute("margin"))
+                key = "hatswitch:%d" % hatnum
+                self.SetBinding(modlevel, key, cmd)
+                for axischild in childnode.childNodes:
+                  if axischild.nodeType == axischild.ELEMENT_NODE and axischild.tagName == 'hatswitch':
+                    # axis hatswitch
+                    # TODO: what do I do with this?
+                    pass
+                pass
+              elif childnode.hasAttribute("axis"):
+                # regular joystick axis
+                axis = childnode.getAttribute("axis")
+                name = childnode.getAttribute("name")
+                key = "axis:joystick-%s.%s" % (joystick, axis)
+                self.SetBinding(modlevel, key, cmd)
+            pass
+          pass
+        pass
+    # Update all keys to reflect newly loaded keys.
+    self.UseModifierLevel(self.modifier)
+    pass
+
+
+
+
+
+def main (argv=[]):
+  app = VegastrikeKeyBinderApp()
+  #crumb("MainLoop")
+  app.MainLoop()
+
+
+if __name__ == "__main__":
+  main()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/predefs.py	Wed Jun 29 08:32:07 2011 -0700
@@ -0,0 +1,201 @@
+#!/usr/bin/env python
+
+
+# Keybinder pre-defined values.
+
+
+Commands = [
+  # ( Display, Stored )
+  ('(none)', None),
+  ('Netcomm',
+    [
+      "ChangeCommStatus",
+      "UpFreq",
+      "DownFreq",
+      "SwitchWebcam",
+      "SwitchSecured",
+    ] ),
+  ('Communication',
+    [
+      "Cockpit::SkipMusicTrack",
+      "Screenshot",
+      "CommFormup",
+      "CommBreakForm",
+      "CommHelpMeOut",
+      "CommAttackTarget",
+    ] ),
+  ('Chat',
+    [
+      "Comm1Key",
+      "Comm2Key",
+      "Comm3Key",
+      "Comm4Key",
+      "Comm5Key",
+      "Comm6Key",
+      "Comm7Key",
+      "Comm8Key",
+      "Comm9Key",
+    ] ),
+  ('Target Memory - Save',
+    [
+      "SaveTarget1",
+      "SaveTarget2",
+      "SaveTarget3",
+      "SaveTarget4",
+      "SaveTarget5",
+      "SaveTarget6",
+      "SaveTarget7",
+      "SaveTarget8",
+      "SaveTarget9",
+    ] ),
+  ('Target Memory - Recall',
+    [
+      "RestoreTarget1",
+      "RestoreTarget2",
+      "RestoreTarget3",
+      "RestoreTarget4",
+      "RestoreTarget5",
+      "RestoreTarget6",
+      "RestoreTarget7",
+      "RestoreTarget8",
+      "RestoreTarget9",
+    ] ),
+  ('Flight',
+    [
+      "UpKey",
+      "Downkey",
+      "LeftKey",
+      "RightKey",
+      "RollLeftKey",
+      "RollRightKey",
+      "Joystick::Mode::InertialXYToggle",
+      "Joystick::Mode::InertialXZToggle",
+      "Joystick::Mode::RollToggle",
+      "Joystick::Mode::BankToggle",
+    ] ),
+  ('Throttle',
+    [
+      "SwitchCombatMode",
+      "JumpKey",
+      "AccelKey",
+      "DecelKey",
+      "StopKey",
+      "StartKey",
+      "ABKey",
+      "SetVelocityRefKey",
+      "SetVelocityNullKey",
+      "ToggleWarpDrive",
+      "ASAP",
+      #("InertialToggle", "Inertial Slide (Shelton Slide)"),
+      ("SheltonKey", "Inertial Slide (Shelton Slide)"),
+    ] ),
+  ('Mating',
+    [
+      "EjectKey",
+      "EjectCargoKey",
+      "DockKey",
+      "UnDockKey"
+      "RequestClearenceKey",
+    ] ),
+  ('Combat',
+    [
+      "FireKey",
+      "WeapSelKey",
+      "ReverseWeapSelKey",
+      "MissileKey",
+      "MisSelKey",
+      "ReverseMisSelKey",
+      "TurretTargetKey",
+      #"TurretAIKey",
+      "TurretAIOn",
+      "TurretAIOff",
+      "TurretAIFireAtWill",
+      #"SwitchControl",
+      "TurretControl",
+      "CloakKey",
+      "ECMKey",
+    ] ),
+  ('Targeting',
+    [
+      "TargetKey",
+      "ReverseTargetKey",
+      "SigTargetKey",
+      "ReverseSigTargetKey",
+      "NearestTargetKey",
+      "ReverseNearestTargetKey",
+      "SubUnitTargetKey",
+      "UnitTargetKey",
+      "ReverseUnitTargetKey",
+
+      "NearestHostileTargetKey",
+      "NearestDangerousHostileKey",
+      "NearestFriendlyKey",
+      "NearestBaseKey",
+      "NearestPlanetKey",
+      "NearestJumpKey",
+
+      "PickTargetKey",
+      "LockTargetKey",
+    ] ),
+  ('Camera',
+    [
+      "Cockpit::Inside",
+      "Cockpit::InsideLeft",
+      "Cockpit::InsideRight",
+      "Cockpit::InsideBack",
+      "Cockpit::OutsideTarget",
+      "Cockpit::ViewTarget",
+      "Cockpit::PanTarget",
+      "Cockpit::ZoomIn",
+      "Cockpit::Behind",
+      "Cockpit::Pan",
+      "Cockpit::ZoomOut",
+    ] ),
+  ('Cockpit',
+    [
+      "Cockpit::MapKey",
+      "Cockpit::NavScreen",
+      "Cockpit::SwitchLVDU",
+      "Cockpit::SwitchRVDU",
+      "Cockpit::PitchDown",
+      "Cockpit::PitchUp",
+      "Cockpit::YawLeft",
+      "Cockpit::YawRight",
+      "Cockpit::ScrollDown",
+      "Cockpit::ScrollUp",
+      "Cockpit::ScrollDown",
+      "Cockpit::ScrollUp",
+    ] ),
+  ('Sound',
+    [
+      "VolumeInc",
+      "VolumeDec",
+      "MusicVolumeInc",
+      "MusicVolumeDec",
+    ] ),
+  ('analog',
+    [
+      "x",
+      "-x",
+      "y",
+      "-y",
+      "z",
+      "-z",
+      "throttle",
+      "-throttle",
+    ] ),
+  ('Meta',
+    [
+      "Cockpit::BringConsole",
+      "TextMessage",
+      "PauseKey",
+      "TimeReset",
+      "SuicideKey",
+      "Respawn",
+      "Cockpit::Quit",
+      "TimeInc",
+    ] ),
+  ('Custom',
+    [
+    ] ),
+]