Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/keyboard.py
diff options
context:
space:
mode:
authorWade Brainerd <wadetb@gmail.com>2008-11-11 22:20:53 (GMT)
committer Wade Brainerd <wadetb@gmail.com>2008-11-11 22:20:53 (GMT)
commit777fceb60edd521dc4e1c976d77ec60f9f48a18c (patch)
tree34663278fc01728037707727fe970d52db2d1723 /keyboard.py
parent61535bcc8d8a73e527a3ff1ff2aac3eae8b56ae9 (diff)
Keyboard improvements.
Diffstat (limited to 'keyboard.py')
-rw-r--r--keyboard.py216
1 files changed, 138 insertions, 78 deletions
diff --git a/keyboard.py b/keyboard.py
index a7250ca..5683a30 100644
--- a/keyboard.py
+++ b/keyboard.py
@@ -4,11 +4,11 @@ import pygtk
pygtk.require('2.0')
import gtk
-# List of all possible properties in the keyboard layout description.
+# List of all key properties in the keyboard layout description.
#
# Keyboard Layouts use a property inheritance scheme similar to CSS (cascading style sheets):
# - Keys inherit properties from their groups, if not explicitly set.
-# - Groups similarly inherit properties from the layout.
+# - Groups inherit properties from the layout.
# - The layout inherits properties from defaults values defined below.
#
# Therefore it is possible to set any property once in the Layout, and have
@@ -46,6 +46,9 @@ KEY_PROPS = [
# Gap between keys. Used by 'horizontal' and 'verical' layout algorithms.
{ 'name': 'key-gap', 'default': 0 },
+ # Keyboard scan code for this key.
+ { 'name': 'key-scan', 'default': 0 },
+
# Text label to be displayed on keys which do not generate ASCII keys.
{ 'name': 'key-label', 'default': '' },
@@ -102,20 +105,20 @@ DEFAULT_LAYOUT = {
'group-y': 50,
'keys': [
- {'key-normal':"`",'key-shift':"~",'key-width':35},
- {'key-normal':"1",'key-shift':"!"},
- {'key-normal':"2",'key-shift':"@"},
- {'key-normal':"3",'key-shift':"#"},
- {'key-normal':"4",'key-shift':"$"},
- {'key-normal':"5",'key-shift':"%"},
- {'key-normal':"6",'key-shift':"^"},
- {'key-normal':"7",'key-shift':"&"},
- {'key-normal':"8",'key-shift':"*"},
- {'key-normal':"9",'key-shift':"("},
- {'key-normal':"0",'key-shift':")"},
- {'key-normal':"-",'key-shift':"_"},
- {'key-normal':"=",'key-shift':"+",'key-width':65},
- {'key-label':"erase",'key-width':95}
+ {'key-scan':0x31,'key-normal':"`",'key-shift':"~",'key-width':35},
+ {'key-scan':0x0a,'key-normal':"1",'key-shift':"!"},
+ {'key-scan':0x0b,'key-normal':"2",'key-shift':"@"},
+ {'key-scan':0x0c,'key-normal':"3",'key-shift':"#"},
+ {'key-scan':0x0d,'key-normal':"4",'key-shift':"$"},
+ {'key-scan':0x0e,'key-normal':"5",'key-shift':"%"},
+ {'key-scan':0x0f,'key-normal':"6",'key-shift':"^"},
+ {'key-scan':0x10,'key-normal':"7",'key-shift':"&"},
+ {'key-scan':0x11,'key-normal':"8",'key-shift':"*"},
+ {'key-scan':0x12,'key-normal':"9",'key-shift':"("},
+ {'key-scan':0x13,'key-normal':"0",'key-shift':")"},
+ {'key-scan':0x14,'key-normal':"-",'key-shift':"_"},
+ {'key-scan':0x15,'key-normal':"=",'key-shift':"+",'key-width':65},
+ {'key-scan':0x16,'key-label':"erase",'key-width':95}
]
},
{
@@ -124,20 +127,20 @@ DEFAULT_LAYOUT = {
'group-y': 100,
'keys': [
- {'key-label':"tab"},
- {'key-normal':"q",'key-shift':"Q"},
- {'key-normal':"w",'key-shift':"W"},
- {'key-normal':"e",'key-shift':"E"},
- {'key-normal':"r",'key-shift':"R"},
- {'key-normal':"t",'key-shift':"T"},
- {'key-normal':"y",'key-shift':"Y"},
- {'key-normal':"u",'key-shift':"U"},
- {'key-normal':"i",'key-shift':"I"},
- {'key-normal':"o",'key-shift':"O"},
- {'key-normal':"p",'key-shift':"P"},
- {'key-normal':"[",'key-shift':"{"},
- {'key-normal':"]",'key-shift':"}",'key-width':55},
- {'key-label':"enter",'key-width':95,'key-height':95}
+ {'key-scan':0x17,'key-label':"tab"},
+ {'key-scan':0x18,'key-normal':"q",'key-shift':"Q"},
+ {'key-scan':0x19,'key-normal':"w",'key-shift':"W"},
+ {'key-scan':0x1a,'key-normal':"e",'key-shift':"E"},
+ {'key-scan':0x1b,'key-normal':"r",'key-shift':"R"},
+ {'key-scan':0x1c,'key-normal':"t",'key-shift':"T"},
+ {'key-scan':0x1d,'key-normal':"y",'key-shift':"Y"},
+ {'key-scan':0x1e,'key-normal':"u",'key-shift':"U"},
+ {'key-scan':0x1f,'key-normal':"i",'key-shift':"I"},
+ {'key-scan':0x20,'key-normal':"o",'key-shift':"O"},
+ {'key-scan':0x21,'key-normal':"p",'key-shift':"P"},
+ {'key-scan':0x22,'key-normal':"[",'key-shift':"{"},
+ {'key-scan':0x23,'key-normal':"]",'key-shift':"}",'key-width':55},
+ {'key-scan':0x24,'key-label':"enter",'key-width':95,'key-height':95}
]
},
{
@@ -146,19 +149,19 @@ DEFAULT_LAYOUT = {
'group-y': 150,
'keys': [
- {'key-label':"ctrl",'key-width':55},
- {'key-normal':"a",'key-shift':"A"},
- {'key-normal':"s",'key-shift':"S"},
- {'key-normal':"d",'key-shift':"D"},
- {'key-normal':"f",'key-shift':"F"},
- {'key-normal':"g",'key-shift':"G"},
- {'key-normal':"h",'key-shift':"H"},
- {'key-normal':"j",'key-shift':"J"},
- {'key-normal':"k",'key-shift':"K"},
- {'key-normal':"l",'key-shift':"L"},
- {'key-normal':";",'key-shift':":"},
- {'key-normal':"'",'key-shift':")"},
- {'key-normal':"\"",'key-shift':"|"}
+ {'key-scan':0x25,'key-label':"ctrl",'key-width':55},
+ {'key-scan':0x26,'key-normal':"a",'key-shift':"A"},
+ {'key-scan':0x27,'key-normal':"s",'key-shift':"S"},
+ {'key-scan':0x28,'key-normal':"d",'key-shift':"D"},
+ {'key-scan':0x29,'key-normal':"f",'key-shift':"F"},
+ {'key-scan':0x2a,'key-normal':"g",'key-shift':"G"},
+ {'key-scan':0x2b,'key-normal':"h",'key-shift':"H"},
+ {'key-scan':0x2c,'key-normal':"j",'key-shift':"J"},
+ {'key-scan':0x2d,'key-normal':"k",'key-shift':"K"},
+ {'key-scan':0x2e,'key-normal':"l",'key-shift':"L"},
+ {'key-scan':0x2f,'key-normal':";",'key-shift':":"},
+ {'key-scan':0x30,'key-normal':"'",'key-shift':")"},
+ {'key-scan':0x33,'key-normal':"\\",'key-shift':"|"}
]
},
{
@@ -167,19 +170,19 @@ DEFAULT_LAYOUT = {
'group-y': 200,
'keys': [
- {'key-label':"shift",'key-width':75},
- {'key-normal':"z",'key-shift':"Z"},
- {'key-normal':"x",'key-shift':"X"},
- {'key-normal':"c",'key-shift':"C"},
- {'key-normal':"v",'key-shift':"V"},
- {'key-normal':"b",'key-shift':"B"},
- {'key-normal':"n",'key-shift':"N"},
- {'key-normal':"m",'key-shift':"M"},
- {'key-normal':",",'key-shift':"<"},
- {'key-normal':".",'key-shift':">"},
- {'key-normal':"/",'key-shift':"?"},
- {'key-label':"shift",'key-width':75},
- {'key-label':"",'key-shift':""}, # Up
+ {'key-scan':0x32,'key-label':"shift",'key-width':75},
+ {'key-scan':0x34,'key-normal':"z",'key-shift':"Z"},
+ {'key-scan':0x35,'key-normal':"x",'key-shift':"X"},
+ {'key-scan':0x36,'key-normal':"c",'key-shift':"C"},
+ {'key-scan':0x37,'key-normal':"v",'key-shift':"V"},
+ {'key-scan':0x38,'key-normal':"b",'key-shift':"B"},
+ {'key-scan':0x39,'key-normal':"n",'key-shift':"N"},
+ {'key-scan':0x3a,'key-normal':"m",'key-shift':"M"},
+ {'key-scan':0x3b,'key-normal':",",'key-shift':"<"},
+ {'key-scan':0x3c,'key-normal':".",'key-shift':">"},
+ {'key-scan':0x3d,'key-normal':"/",'key-shift':"?"},
+ {'key-scan':0x3e,'key-label':"shift",'key-width':75},
+ {'key-scan':0x6f,'key-label':"",'key-shift':""}, # Up
{'key-label':"",'key-shift':""}, # Multiply
]
},
@@ -191,13 +194,13 @@ DEFAULT_LAYOUT = {
'keys': [
{'key-label':"fn",'key-width':35},
{'key-label':"",'key-width':55}, # LHand
- {'key-label':"alt",'key-width':55}, # LAlt
- {'key-normal':" ",'key-shift':" ", 'key-width':325},
- {'key-label':"alt",'key-width':55}, # RAlt
+ {'key-scan':0x40,'key-label':"alt",'key-width':55}, # LAlt
+ {'key-scan':0x41,'key-normal':" ",'key-shift':" ", 'key-width':325},
+ {'key-scan':0x6c,'key-label':"alt",'key-width':55}, # RAlt
{'key-label':"",'key-width':55}, # RHand
- {'key-label':""}, # Left
- {'key-label':""}, # Down
- {'key-label':""}, # Right
+ {'key-scan':0x71,'key-label':""}, # Left
+ {'key-scan':0x74,'key-label':""}, # Down
+ {'key-scan':0x72,'key-label':""}, # Right
]
}
]
@@ -217,28 +220,42 @@ class Key:
self.screen_width = 0
self.screen_height = 0
-class Keyboard(gtk.DrawingArea):
- """A GTK widget which provides an interactive visual keyboard, with support
+ self.pressed = False
+ self.hilite = False
+
+ def set_hilite(self, enabled):
+ self.hilite = enabled
+
+class Keyboard(gtk.EventBox):
+ """A GTK widget which implements an interactive visual keyboard, with support
for custom data driven layouts."""
- def __init__(self):
- gtk.DrawingArea.__init__(self)
+ def __init__(self, root_window):
+ gtk.EventBox.__init__(self)
- # This array contains the current keyboard layout.
- self.keys = []
+ self.root_window = root_window
- self.pangolayout = self.create_pango_layout("")
+ self.area = gtk.DrawingArea()
+ self.area.connect("expose-event", self._expose_cb)
+ self.add(self.area)
- self.set_events(gtk.gdk.POINTER_MOTION_MASK |
- gtk.gdk.POINTER_MOTION_HINT_MASK )
+ # This array contains the current keyboard layout.
+ self.keys = None
+ self.key_map = None
+
+ self.shift_down = False
- self.connect("expose-event", self.expose_cb)
+ # Setup keyboard event snooping in the root window.
+ root_window.add_events(gtk.gdk.KEY_PRESS_MASK | gtk.gdk.KEY_RELEASE_MASK)
+ root_window.connect('key-press-event', self._key_press_cb)
+ root_window.connect('key-release-event', self._key_release_cb)
def _build_key_list(self, layout):
"""Builds a list of Keys objects from a layout description.
Also fills in derived and inherited key properties.
The layout description can be discarded afterwards."""
self.keys = []
+ self.key_map = {}
group_count = 0
for g in layout['groups']:
@@ -266,8 +283,13 @@ class Keyboard(gtk.DrawingArea):
props[pname] = p['default']
# Add to internal list.
- self.keys.append(Key(props))
+ key = Key(props)
+ self.keys.append(key)
key_count += 1
+
+ # Add to scan code mapping table.
+ if props['key-scan']:
+ self.key_map[props['key-scan']] = key
group_count += 1
@@ -340,17 +362,15 @@ class Keyboard(gtk.DrawingArea):
k.screen_width = int(k.width * ratio_x / 100)
k.screen_height = int(k.height * ratio_y / 100)
- def expose_cb(self, area, event):
+ def _expose_cb(self, area, event):
# Update layout given screen size.
self._update_screen_layout()
# Draw the keys.
- cr = self.window.cairo_create()
+ cr = self.area.window.cairo_create()
cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
cr.clip()
- bounds = self.get_allocation()
-
for k in self.keys:
cr.save()
@@ -371,7 +391,12 @@ class Keyboard(gtk.DrawingArea):
cr.line_to(x1, y1 + corner)
cr.close_path()
- cr.set_source_rgb(1.0, 1.0, 1.0)
+ if k.pressed:
+ cr.set_source_rgb(1.0, 0.6, 0.6)
+ elif k.hilite:
+ cr.set_source_rgb(0.6, 1.0, 0.6)
+ else:
+ cr.set_source_rgb(1.0, 1.0, 1.0)
cr.fill_preserve()
cr.set_source_rgb(0.1, 0.1, 0.1)
@@ -382,6 +407,8 @@ class Keyboard(gtk.DrawingArea):
# Inner text.
if k.props['key-label']:
text = k.props['key-label']
+ elif self.shift_down:
+ text = k.props['key-shift']
else:
text = k.props['key-normal']
@@ -395,6 +422,39 @@ class Keyboard(gtk.DrawingArea):
return True
+ def _key_press_cb(self, widget, event):
+ # Useful line for determining what scan code matches what key.
+ #print "HW: %x" % event.hardware_keycode
+
+ if self.key_map.has_key(event.hardware_keycode):
+ key = self.key_map[event.hardware_keycode]
+ key.pressed = True
+ if key.props['key-label'] == 'shift':
+ self.shift_down = True
+
+ self.queue_draw()
+ return False
+
+ def _key_release_cb(self, widget, event):
+ if self.key_map.has_key(event.hardware_keycode):
+ key = self.key_map[event.hardware_keycode]
+ key.pressed = False
+ if key.props['key-label'] == 'shift':
+ self.shift_down = False
+
+ self.queue_draw()
+ return False
+
+ def clear_hilite(self):
+ for k in self.keys:
+ k.hilite = False
+
+ def find_key_by_letter(self, letter):
+ for k in self.keys:
+ if k.props['key-normal'] == letter or k.props['key-shift'] == letter:
+ return k
+ return None
+
if __name__ == "__main__":
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.set_title("keyboard widget")