--- /dev/null
+#!/usr/bin/env python
+
+#
+# experiment with tap input.
+# Have a 3x4 array of buttons.
+# Enter any symbol by tapping two buttons from the top 3x3,
+# or bottom-middle
+# Other Bottom buttons are: mode and cancel/delete
+# mode cycles : lower, caps, upper, number (abc Abc ABC 123)
+# cancel is effective after a single tap, delete when no pending tap
+#
+# The 3x3 keys normally show a 3x3 matrix of what they enable
+# When one is tapped, all keys change to show a single symbol (after a pause).
+#
+# "123" works in 'single tap' mode. A single tap makes the symbol in the center
+# of the key appear. To get other symbols (*, #), hold the relevant key until
+# other symbols appear. Then tap.
+#
+# The window can be dragged by touch-and-drag anywhere.
+# If the window is dragged more than 1/2 off the screen, it disappears.
+
+import gtk, pango, gobject
+from fakeinput import fakeinput
+
+keymap = {}
+
+keymap['lower'] = [
+ ['0','1','Tab','2','3',' ','?','@','#'],
+ ['b','c','d','f','g','h',' ','Down',' '],
+ ['<','4','5','>','6','7','Return','{','}'],
+ ['j','k','~','l','m','Right','n','p','`'],
+ ['a','e','i','o',' ','u','r','s','t'],
+ ['\\',';',':','Left','\'','"','|','(',')'],
+ ['[',']',' ','8','9','=','+','-','_'],
+ [' ','Up',' ','q','v','w','x','y','z'],
+ ['!','$','%','^','*','/','&',',','.'],
+ None,
+ [' ',' ',' ',' ',' ',' ','Left','Up','Right',' ','Down',' ']
+
+ ]
+keymap['UPPER'] = [
+ ['0','1','Tab','2','3',' ','?','@','#'],
+ ['B','C','D','F','G','H',' ','Down',' '],
+ ['<','4','5','>','6','7','Return','{','}'],
+ ['J','K','~','L','M','Right','N','P','`'],
+ ['A','E','I','O',' ','U','R','S','T'],
+ ['\\',';',':','Left','\'','"','|','(',')'],
+ ['[',']',' ','8','9','=','+','-','_'],
+ [' ','Up',' ','Q','V','W','X','Y','Z'],
+ ['!','$','%','^','*','/','&',',','.'],
+ None,
+ [' ',' ',' ',' ',' ',' ','Left','Up','Right',' ','Down',' ']
+ ]
+keymap['number'] = [
+ ['1',' ',' ',' ',' ',' ',' ',' ',' '],
+ [' ','2',' ',' ',' ',' ',' ',' ',' '],
+ [' ',' ','3',' ',' ',' ',' ',' ',' '],
+ [' ',' ',' ','4',' ',' ',' ',' ',' '],
+ [' ',' ',' ',' ','5',' ',' ',' ',' '],
+ [' ',' ',' ',' ',' ','6',' ',' ',' '],
+ [' ',' ',' ',' ',' ',' ','7',' ',' '],
+ [' ',' ',' ',' ',' ',' ',' ','8',' '],
+ [' ',' ',' ',' ',' ',' ',' ',' ','9'],
+ None,
+ [' ',' ',' ',' ',' ',' ','*',' ','#', ' ', '0', ' ']
+ ]
+04034
+
+class TapInput(gtk.Window):
+ def __init__(self):
+ gtk.Window.__init__(self, type=gtk.WINDOW_POPUP)
+ self.set_default_size(320, 420)
+ root = gtk.gdk.get_default_root_window()
+ (x,y,width,height,depth) = root.get_geometry()
+ x = int((width-320)/2)
+ y = int((height-420)/2)
+ self.move(x,y)
+
+ self.fi = fakeinput()
+ self.dragx = None
+ self.dragy = None
+ self.moved = False
+
+ self.isize = gtk.icon_size_register("mine", 40, 40)
+
+ self.button_timeout = None
+
+ self.buttons = []
+ v1 = gtk.VBox()
+ v1.show()
+ self.add(v1)
+
+ v = gtk.VBox()
+ v.show()
+ v1.add(v)
+ v.set_homogeneous(True)
+
+ for row in range(3):
+ h = gtk.HBox()
+ h.show()
+ h.set_homogeneous(True)
+ v.add(h)
+ bl = []
+ for col in range(3):
+ b = self.add_button(None, self.tap, row*3+col,
+ h)
+ bl.append(b)
+ self.buttons.append(bl)
+
+
+ h = gtk.HBox()
+ h.show()
+ h.set_homogeneous(True)
+ v.add(h)
+
+ fd = pango.FontDescription('sans 10')
+ fd.set_absolute_size(30 * pango.SCALE)
+ b = self.add_button('abc', self.nextmode, None, h, fd)
+ self.modebutton = b
+
+ b = self.add_button(None, self.tap, 10, h)
+ self.buttons.append([None, b, None])
+
+ b = self.add_button(gtk.STOCK_UNDO, self.delete, None, h)
+
+ self.mode = 'lower'
+ self.taps = 2
+ self.single = False
+ self.prefix = None
+ self.prefix1 = None
+ self.size = 0
+ self.update_buttons()
+ self.connect("configure-event", self.update_buttons)
+ self.hide()
+
+ def add_button(self, label, click, arg, box, font = None):
+ if not label:
+ b = gtk.Button()
+ elif label[0:4] == 'gtk-':
+ img = gtk.image_new_from_stock(label, self.isize)
+ img.show()
+ b = gtk.Button()
+ b.add(img)
+ else:
+ b = gtk.Button(label)
+ b.show()
+ if font:
+ b.child.modify_font(font)
+ b.connect('button_press_event', self.press, arg)
+ b.connect('button_release_event', self.release, click, arg)
+ b.connect('motion_notify_event', self.motion)
+ b.add_events(gtk.gdk.POINTER_MOTION_MASK|
+ gtk.gdk.POINTER_MOTION_HINT_MASK)
+
+ box.add(b)
+ return b
+
+ def update_buttons(self, *a):
+ alloc = self.buttons[0][0].get_allocation()
+ w = alloc.width; h = alloc.height
+ if w > h:
+ size = h
+ else:
+ size = w
+ size -= 12
+ if size <= 10 or size == self.size:
+ return
+ print "update buttons", size
+ self.size = size
+
+ # For each button in 3x3 we need 10 images,
+ # one for initial state, and one for each of the new states
+ # So there are two fonts we want.
+ # First we make the initial images
+ fd = pango.FontDescription('sans 10')
+ fd.set_absolute_size(size / 4.5 * pango.SCALE)
+ self.modify_font(fd)
+
+ bg = self.get_style().bg_gc[gtk.STATE_NORMAL]
+ fg = self.get_style().fg_gc[gtk.STATE_NORMAL]
+ red = self.window.new_gc()
+ red.set_foreground(self.get_colormap().alloc_color(gtk.gdk.color_parse('red')))
+ base_images = {}
+ for mode in keymap.keys():
+ base_images[mode] = 12*[None]
+ for row in range(4):
+ for col in range(3):
+ if not self.buttons[row][col]:
+ continue
+ if row*3+col >= len(keymap[mode]):
+ continue
+ syms = keymap[mode][row*3+col]
+ pm = gtk.gdk.Pixmap(self.window, size, size)
+ pm.draw_rectangle(bg, True, 0, 0, size, size)
+ for r in range(4):
+ for c in range(3):
+ if r*3+c >= len(syms):
+ continue
+ sym = syms[r*3+c]
+ if sym == ' ':
+ continue
+ xpos = ((c-col+1)*2+1)
+ ypos = ((r-row+1)*2+1)
+ colour = fg
+ if xpos != xpos%6:
+ xpos = xpos%6
+ colour = red
+ if ypos != ypos%6:
+ ypos = ypos%6
+ colour = red
+ layout = self.create_pango_layout(sym[0:3])
+ (ink, (ex,ey,ew,eh)) = layout.get_pixel_extents()
+ pm.draw_layout(colour,
+ int(xpos*size/6 - ew/2),
+ int(ypos*size/6 - eh/2),
+ layout)
+ im = gtk.Image()
+ im.set_from_pixmap(pm, None)
+ base_images[mode][row*3+col] = im
+ self.base_images = base_images
+ fd.set_absolute_size(size / 1.5 * pango.SCALE)
+ self.modify_font(fd)
+ sup_images = {}
+ sup_by_sym = {}
+ for mode in keymap.keys():
+ sup_images[mode] = 12*[None]
+ for row in range(4):
+ for col in range(3):
+ ilist = 12 * [None]
+ for r in range(4):
+ for c in range(3):
+ if r*3+c >= len(keymap[mode]):
+ continue
+ if keymap[mode][r*3+c] == None:
+ continue
+ if row*3+col >= len(keymap[mode][r*3+c]):
+ continue
+ sym = keymap[mode][r*3+c][row*3+col]
+ if sym == ' ':
+ continue
+ if sym in sup_by_sym:
+ im = sup_by_sym[sym]
+ else:
+ pm = gtk.gdk.Pixmap(self.window, size, size)
+ pm.draw_rectangle(bg, True, 0, 0, size, size)
+ layout = self.create_pango_layout(sym)
+ (ink, (ex,ey,ew,eh)) = layout.get_pixel_extents()
+ pm.draw_layout(fg,
+ int((size - ew)/2), int((size - eh)/2),
+ layout)
+ im = gtk.Image()
+ im.set_from_pixmap(pm, None)
+ sup_by_sym[sym] = im
+ ilist[r*3+c] = im
+ sup_images[mode][row*3+col] = ilist
+ self.sup_images = sup_images
+ self.set_button_images()
+
+
+ def set_button_images(self):
+ for row in range(4):
+ for col in range(3):
+ b = self.buttons[row][col]
+ if not b:
+ continue
+ p = self.prefix
+ if p == None:
+ p = self.prefix1
+ if p == None:
+ im = self.base_images[self.mode][row*3+col]
+ else:
+ im = self.sup_images[self.mode][row*3+col][p]
+ if im:
+ b.set_image(im)
+
+
+ def tap(self, rc):
+ if self.prefix == None:
+ self.prefix = rc
+ if self.taps == 2:
+ self.button_timeout = gobject.timeout_add(500, self.do_buttons)
+ else:
+ sym = keymap[self.mode][self.prefix][rc]
+ self.fi.send_char(sym)
+ self.noprefix()
+
+ def press(self, widget, ev, arg):
+ self.dragx = int(ev.x_root)
+ self.dragy = int(ev.y_root)
+ self.startx, self.starty = self.get_position()
+ if arg != None and self.taps == 1 and self.button_timeout == None and self.prefix == None:
+ self.prefix1 = arg
+ self.button_timeout = gobject.timeout_add(500, self.do_buttons)
+
+ def release(self, widget, ev, click, arg):
+ self.dragx = None
+ self.dragy = None
+ if self.moved:
+ self.moved = False
+ self.noprefix()
+ # If we are half way off the screen, hide
+ root = gtk.gdk.get_default_root_window()
+ (rx,ry,rwidth,rheight,depth) = root.get_geometry()
+ (x,y,width,height,depth) = self.window.get_geometry()
+ xmid = int(x + width / 2)
+ ymid = int(y + height / 2)
+ if xmid < 0 or xmid > rwidth or \
+ ymid < 0 or ymid > rheight:
+ self.hide()
+ elif arg != None and self.taps == 1 and self.button_timeout:
+ # quick tap in single tap mode, just enter the symbol
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = None
+ num = arg
+ sym = keymap[self.mode][num][num]
+ self.fi.send_char(sym)
+ else:
+ click(arg)
+ def motion(self, widget, ev):
+ if self.dragx == None:
+ return
+ x = int(ev.x_root)
+ y = int(ev.y_root)
+
+ if abs(x-self.dragx)+abs(y-self.dragy) > 40 or self.moved:
+ self.move(self.startx+x-self.dragx,
+ self.starty+y-self.dragy);
+ self.moved = True
+ if self.button_timeout:
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = None
+ if ev.is_hint:
+ gtk.gdk.flush()
+ ev.window.get_pointer()
+
+
+ def do_buttons(self):
+ self.set_button_images()
+ self.button_timeout = None
+ return False
+
+
+ def nextmode(self, a):
+ if self.prefix:
+ return self.noprefix()
+ if self.mode == 'lower':
+ self.mode = 'UPPER'
+ self.single = True
+ self.modebutton.child.set_text('Abc')
+ elif self.mode == 'UPPER' and self.single:
+ self.single = False
+ self.modebutton.child.set_text('ABC')
+ elif self.mode == 'UPPER' and not self.single:
+ self.mode = 'number'
+ self.taps = 1
+ self.modebutton.child.set_text('123')
+ else:
+ self.mode = 'lower'
+ self.taps = 2
+ self.modebutton.child.set_text('mode')
+ self.set_button_images()
+
+ def delete(self, a):
+ if self.prefix == None:
+ self.fi.send_char("\b")
+ else:
+ self.noprefix()
+
+ def noprefix(self):
+ self.prefix = None
+ self.prefix1 = None
+
+ if self.button_timeout:
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = None
+ else:
+ self.set_button_images()
+
+ if self.single:
+ self.mode = 'lower'
+ self.single = False
+ self.modebutton.child.set_text('abc')
+ self.set_button_images()
+
+
+ def activate(self, *a):
+ root = gtk.gdk.get_default_root_window()
+ (x,y,width,height,depth) = root.get_geometry()
+ x = int((width-320)/2)
+ y = int((height-420)/2)
+ self.move(x,y)
+ self.fi.new_window()
+ self.show()
+ self.move(x,y)
+
+
+class KeyboardIcon(gtk.StatusIcon):
+ def __init__(self, x):
+ gtk.StatusIcon.__init__(self)
+ self.set_from_file('/usr/local/pixmaps/tapinput.png')
+ self.connect('activate', x.activate)
+
+win = TapInput()
+ico = KeyboardIcon(win)
+gtk.main()
+