maxw = maxw + maxh
self.rows = int(self.height / maxh)
self.cols = int(self.width / maxw)
- if i == 0 or self.rows == 0:
+ if self.rows == 0:
self.rows = 1
if self.cols > int((i + self.rows-1) / self.rows):
self.cols = int((i + self.rows-1) / self.rows)
--- /dev/null
+
+I don't much like my double-tap board.
+
+How can I do qwerty with biggest possible keys?
+
+q w e r t y u i o p
+ a s d f g h j k l
+- z x c v b n m , .
+SF NM SPC ENTR BS
+
+When shifted,
+ - +
+ , ;
+ . :
+
+
+
+1 2 3 4 5 6 7 8 9 0
+ ! @ # $ % ^ & * _
+( ) + - / ? [ ] \ |
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env python
+
+#
+# Freerunner app to track petrol usage in new car.
+# We need to keep a log of entries. Each entry:
+#
+# date kilometers litres whether-full price-paid
+#
+# These are displayed with l/100K number and can get
+# overall l/100K and c/l between two points
+#
+# Must be able to edit old entries.
+#
+# So: 2 pages:
+#
+# 1/
+# summary line: l/100K, c/l from selected to mark
+# list of entries, scrollable and selectable
+# buttons: new, edit, mark/unmark
+#
+# 2/ Entry fields for a new entry
+# date: default to 'today' with plus/minus button on either side
+# kilometer - simple text entry
+# litres + 'fill' indicator
+# c/l
+# $
+# keyboard in number mode
+# Buttons: Fill, not-fill, Discard, Save
+#
+# Should I be able to select between different vehicles? Not now.
+
+import sys, os, time
+import pygtk, gtk, pango
+from listselect import ListSelect
+from tapboard import TapBoard
+
+
+class petrol_list:
+ def __init__(self, list):
+ self.list = list
+ self.mark = None
+ def set_list(self, list):
+ self.list = list
+ def set_mark(self, ind):
+ self.mark = ind
+
+ def __len__(self):
+ return len(self.list)
+ def __getitem__(self, ind):
+ i = self.list[ind]
+ dt = i[0]
+ tm = time.strptime(dt, '%Y-%m-%d')
+ dt = time.strftime('%d/%b/%Y', tm)
+ k = "%06d" % int(i[1])
+ l = "%06.2f" % float(i[2])
+ if len(i) > 3 and i[3] == "full":
+ f = "F"
+ else:
+ f = "-"
+ if len(i) > 4:
+ p = "$%06.2f" % float(i[4])
+ else:
+ p = "----.--"
+ str = "%s %s %s %s %s" % (dt, k, l, p, f)
+ if self.mark == ind:
+ type = 'mark'
+ else:
+ type = 'normal'
+ return (str, type)
+
+
+
+class Petrol(gtk.Window):
+ def __init__(self, file):
+ gtk.Window.__init__(self)
+ self.connect("destroy",self.close_application)
+ self.set_title("Petrol")
+
+ ctx = self.get_pango_context()
+ fd = ctx.get_font_description()
+ fd.set_absolute_size(35*pango.SCALE)
+ vb = gtk.VBox(); self.add(vb); vb.show()
+ self.isize = gtk.icon_size_register("petrol", 40, 40)
+
+ self.listui = self.make_list_ui(fd)
+ self.editui = self.make_edit_ui(fd)
+ vb.add(self.listui); vb.add(self.editui)
+ self.listui.show()
+ self.editui.hide()
+
+ self.active_entry = None
+ self.colours={}
+
+ self.filename = file
+ self.load_file()
+ self.pl.set_list(self.list)
+
+ def close_application(self, ev):
+ gtk.main_quit()
+
+ def make_list_ui(self, fd):
+ ui = gtk.VBox()
+ l = gtk.Label("Petrol Usage")
+ l.modify_font(fd)
+ l.show(); ui.pack_start(l, expand=False)
+
+ h = gtk.HBox(); h.show(); ui.pack_start(h, expand=False)
+ h.set_homogeneous(True)
+ ui.usage_summary = gtk.Label("??.??? l/CKm")
+ ui.usage_summary.show()
+ ui.usage_summary.modify_font(fd)
+ h.add(ui.usage_summary)
+
+ ui.price_summary = gtk.Label("???.? c/l")
+ ui.price_summary.show()
+ ui.price_summary.modify_font(fd)
+ h.add(ui.price_summary)
+
+ ui.list = ListSelect()
+ ui.list.show()
+ ui.pack_start(ui.list, expand=True)
+ ui.list.set_format("normal","black", background="grey", selected="white")
+ ui.list.set_format("mark", "black", bullet=True,
+ background="grey", selected="white")
+ ui.list.set_format("blank", "black", background="lightblue")
+ ui.list.connect('selected', self.selected)
+ self.pl = petrol_list([])
+ ui.list.list = self.pl
+ ui.list.set_zoom(34)
+
+ bbar = gtk.HBox(); bbar.show(); ui.pack_start(bbar, expand=False)
+ self.button(bbar, "New", fd, self.new)
+ self.button(bbar, "Edit", fd, self.edit)
+ self.button(bbar, "Mark", fd, self.mark)
+ return ui
+
+ def make_edit_ui(self, fd):
+ ui = gtk.VBox()
+
+ # title
+ l = gtk.Label("Petrol Event")
+ l.modify_font(fd)
+ l.show(); ui.pack_start(l, fill=False)
+
+ # date - with prev/next buttons.
+ h = gtk.HBox(); h.show(); ui.pack_start(h,fill=False)
+ self.button(h, gtk.STOCK_GO_BACK, fd, self.date_prev, expand=False)
+ l = gtk.Label("Today")
+ l.modify_font(fd)
+ l.show(); h.pack_start(l, expand=True)
+ self.button(h, gtk.STOCK_GO_FORWARD, fd, self.date_next, expand=False)
+ ui.date = l
+
+ # text entry for kilometers
+ h = gtk.HBox(); h.show(); ui.pack_start(h,fill=False)
+ e = self.entry(h, "Km:", fd, self.KM)
+ self.km_entry = e;
+
+ # text entry for litres, with 'fill' indicator
+ h = gtk.HBox(); h.show(); ui.pack_start(h,fill=False)
+ e = self.entry(h, "litres:", fd, self.Litres)
+ self.l_entry = e;
+ l = gtk.Label("(full)")
+ l.modify_font(fd)
+ l.show(); h.pack_start(l, expand=False)
+ self.full_label = l
+
+ # text entry for cents/litre
+ h = gtk.HBox(); h.show(); ui.pack_start(h,fill=False)
+ e = self.entry(h, "cents/l:", fd, self.Cents)
+ self.cl_entry = e;
+
+ # text entry for price paid
+ h = gtk.HBox(); h.show(); ui.pack_start(h,fill=False)
+ e = self.entry(h, "Cost: $", fd, self.Cost)
+ self.cost_entry = e;
+
+ self.entry_priority = [self.l_entry, self.cl_entry]
+
+ # keyboard
+ t = TapBoard(); t.show()
+ ui.pack_start(t, fill=True)
+ t.connect('key', self.use_key)
+
+ # Buttons: fill/non-fill, Discard, Save
+ bbar = gtk.HBox(); bbar.show(); ui.pack_start(bbar, fill=False)
+ bbar.set_homogeneous(True)
+ self.fill_button = self.button(bbar, "non-Fill", fd, self.fill)
+ self.button(bbar, "Discard", fd, self.discard)
+ self.button(bbar, "Save", fd, self.save)
+
+ return ui
+
+ def button(self, bar, label, fd, func, expand = True):
+ btn = gtk.Button()
+ if label[0:3] == "gtk" :
+ img = gtk.image_new_from_stock(label, self.isize)
+ img.show()
+ btn.add(img)
+ else:
+ btn.set_label(label)
+ btn.child.modify_font(fd)
+ btn.show()
+ bar.pack_start(btn, expand = expand)
+ btn.connect("clicked", func)
+ btn.set_focus_on_click(False)
+ return btn
+
+ def entry(self, bar, label, fd, func):
+ l = gtk.Label(label)
+ l.modify_font(fd)
+ l.show(); bar.pack_start(l, expand=False)
+ e = gtk.Entry(); e.show(); bar.pack_start(e, expand=True)
+ e.modify_font(fd)
+ e.set_events(e.get_events()|gtk.gdk.FOCUS_CHANGE_MASK)
+ e.connect('focus-in-event', self.focus)
+ e.connect('changed', func)
+ return e
+ def focus(self, ent, ev):
+ self.active_entry = ent
+ if (len(self.entry_priority) == 0 or
+ self.entry_priority[0] != ent):
+ # Make this entry the most recent
+ self.entry_priority = [ent] + self.entry_priority[:1]
+
+ def calc_other(self):
+ if len(self.entry_priority) != 2:
+ return
+ if self.l_entry not in self.entry_priority:
+ cl = self.check_entry(self.cl_entry)
+ cost = self.check_entry(self.cost_entry)
+ if cl != None and cost != None:
+ self.force_entry(self.l_entry, "%.0f" %(cost * 100.0 / cl))
+ if self.cl_entry not in self.entry_priority:
+ l = self.check_entry(self.l_entry)
+ cost = self.check_entry(self.cost_entry)
+ if l != None and l > 0 and cost != None:
+ self.force_entry(self.cl_entry, "%.1f" %(cost * 100.0 / l))
+ if self.cost_entry not in self.entry_priority:
+ cl = self.check_entry(self.cl_entry)
+ l = self.check_entry(self.l_entry)
+ if cl != None and l != None:
+ self.force_entry(self.cost_entry, "%.2f" %(cl * l / 100.0))
+
+ def force_entry(self, entry, val):
+ entry.set_text(val)
+ entry.modify_base(gtk.STATE_NORMAL, self.get_colour('yellow'))
+
+ def use_key(self, tb, str):
+ if not self.active_entry:
+ return
+ if str == '\b':
+ self.active_entry.emit('backspace')
+ elif str == '\n':
+ self.active_entry.emit('activate')
+ else:
+ self.active_entry.emit('insert-at-cursor', str)
+
+ def set_colour(self, name, col):
+ self.colours[name] = col
+ def get_colour(self, col):
+ # col is either a colour name, or a pre-set colour.
+ # so if it isn't in the list, add it
+ if col == None:
+ return None
+ if col not in self.colours:
+ self.set_colour(col, col)
+ if type(self.colours[col]) == str:
+ self.colours[col] = \
+ self.get_colormap().alloc_color(gtk.gdk.color_parse(self.colours[col]))
+ return self.colours[col]
+
+
+ def check_entry(self, entry):
+ txt = entry.get_text()
+ v = None
+ try:
+ if txt != "":
+ v = eval(txt)
+ colour = None
+ except:
+ colour = 'red'
+ entry.modify_base(gtk.STATE_NORMAL, self.get_colour(colour))
+ return v
+
+ def KM(self,entry):
+ v = self.check_entry(entry)
+ def Litres(self,entry):
+ self.calc_other()
+ def Cents(self,entry):
+ self.calc_other()
+ def Cost(self,entry):
+ self.calc_other()
+
+ def load_file(self):
+ # date:km:litre:full?:price
+ list = []
+ try:
+ f = open(self.filename)
+ l = f.readline()
+ while len(l) > 0:
+ l = l.strip()
+ w = l.split(':')
+ if len(w) >= 3:
+ list.append(w)
+ l = f.readline()
+ except:
+ pass
+
+ list.sort(reverse=True)
+ self.list = list
+ self.curr = 0
+ self.mark = None
+
+ def save_file(self):
+ try:
+ f = open(self.filename + '.tmp', 'w')
+ for l in self.list:
+ f.write(':'.join(l) + '\n')
+ f.close()
+ os.rename(self.filename + '.tmp', self.filename)
+ except:
+ pass
+
+ def selected(self, ev, ind):
+ self.curr = ind
+ l = self.list[ind]
+ ind+= 1
+ ltr = float(l[2])
+ while ind < len(self.list) and \
+ (len(self.list[ind]) <= 3 or self.list[ind][3] != 'full'):
+ ltr += float(self.list[ind][2])
+ ind += 1
+ if ind >= len(self.list) or len(l) <= 3 or l[3] != 'full':
+ lckm = "??.??? l/CKm"
+ else:
+ km = float(l[1]) - float(self.list[ind][1])
+ lckm = "%6.3f l/CKm" % (ltr*100/km)
+ self.listui.usage_summary.set_text(lckm)
+
+ if len(l) >= 5 and float(l[2]) > 0:
+ cl = "%6.2f c/l" % (float(l[4])*100/float(l[2]))
+ else:
+ cl = "???.? c/l"
+ self.listui.price_summary.set_text(cl)
+
+ def new(self, ev):
+ self.curr = None
+ self.editui.date.set_text('Today')
+ self.km_entry.set_text('')
+ self.l_entry.set_text('')
+ self.full_label.set_text('(full)')
+ self.cl_entry.set_text('')
+ self.cost_entry.set_text('')
+ self.listui.hide()
+ self.editui.show()
+
+ def edit(self, ev):
+ if self.curr == None:
+ self.curr = 0
+ l = self.list[self.curr]
+ self.editui.date.set_text(time.strftime('%d/%b/%Y', time.strptime(l[0], "%Y-%m-%d")))
+ self.km_entry.set_text(l[1])
+ self.l_entry.set_text(l[2])
+ self.full_label.set_text('(full)' if l[3]=='full' else '(not full)')
+ self.cost_entry.set_text(l[4])
+ self.entry_priority=[self.l_entry, self.cost_entry];
+ self.calc_other()
+ self.listui.hide()
+ self.editui.show()
+
+ def mark(self, ev):
+ pass
+
+ def date_get(self):
+ x = self.editui.date.get_text()
+ try:
+ then = time.strptime(x, "%d/%b/%Y")
+ except ValueError:
+ then = time.localtime()
+ return then
+ def date_prev(self, ev):
+ tm = time.localtime(time.mktime(self.date_get()) - 12*3600)
+ self.editui.date.set_text(time.strftime("%d/%b/%Y", tm))
+
+ def date_next(self, ev):
+ t = time.mktime(self.date_get()) + 25*3600
+ if t > time.time():
+ t = time.time()
+ tm = time.localtime(t)
+ self.editui.date.set_text(time.strftime("%d/%b/%Y", tm))
+
+ def fill(self, ev):
+ if self.full_label.get_text() == "(full)":
+ self.full_label.set_text("(not full)")
+ self.fill_button.child.set_text("Fill")
+ else:
+ self.full_label.set_text("(full)")
+ self.fill_button.child.set_text("non-Fill")
+
+
+ def discard(self, ev):
+ self.listui.show()
+ self.editui.hide()
+ pass
+
+ def save(self, ev):
+ self.listui.show()
+ self.editui.hide()
+ date = time.strftime('%Y-%m-%d', self.date_get())
+ km = "%d" % self.check_entry(self.km_entry)
+ ltr = "%.1f" % self.check_entry(self.l_entry)
+ full = 'full' if self.full_label.get_text() == "(full)" else 'notfull'
+ price = "%.2f" % self.check_entry(self.cost_entry)
+ if self.curr == None:
+ self.list.append([date,km,ltr,full,price])
+ else:
+ self.list[self.curr] = [date,km,ltr,full,price]
+ self.list.sort(reverse=True)
+ self.pl.set_list(self.list)
+ self.listui.list.list_changed()
+ if self.curr == None:
+ self.listui.list.select(0)
+ self.curr = 0
+ else:
+ self.listui.list.select(self.curr)
+ self.save_file()
+ self.selected(None, self.curr)
+
+
+if __name__ == "__main__":
+
+ p = Petrol("RAV4")
+ p.set_default_size(480, 640)
+ p.show()
+ gtk.main()
+
--- /dev/null
+#!/usr/bin/env python
+
+# fakeinput.py
+# based on pykey, see also "crikey" and http://shallowsky.com/blog/tags/crikey/
+
+import Xlib.display
+import Xlib.X
+import Xlib.XK
+import Xlib.protocol.event
+
+UseXTest = True
+
+try :
+ import Xlib.ext.xtest
+except ImportError:
+ UseXTest = False
+ print "no XTest extension; using XSendEvent"
+
+import sys, time
+
+special_X_keysyms = {
+ '\b': "BackSpace",
+ ' ' : "space",
+ '\t' : "Tab",
+ '\n' : "Return", # for some reason this needs to be cr, not lf
+ '\r' : "Return",
+ '\e' : "Escape",
+ '!' : "exclam",
+ '#' : "numbersign",
+ '%' : "percent",
+ '$' : "dollar",
+ '&' : "ampersand",
+ '"' : "quotedbl",
+ '\'' : "apostrophe",
+ '(' : "parenleft",
+ ')' : "parenright",
+ '*' : "asterisk",
+ '=' : "equal",
+ '+' : "plus",
+ ',' : "comma",
+ '-' : "minus",
+ '.' : "period",
+ '/' : "slash",
+ ':' : "colon",
+ ';' : "semicolon",
+ '<' : "less",
+ '>' : "greater",
+ '?' : "question",
+ '@' : "at",
+ '[' : "bracketleft",
+ ']' : "bracketright",
+ '\\' : "backslash",
+ '^' : "asciicircum",
+ '_' : "underscore",
+ '`' : "grave",
+ '{' : "braceleft",
+ '|' : "bar",
+ '}' : "braceright",
+ '~' : "asciitilde"
+ }
+
+def get_keysym(ch) :
+ keysym = Xlib.XK.string_to_keysym(ch)
+ if keysym == 0 :
+ # Unfortunately, although this works to get the correct keysym
+ # i.e. keysym for '#' is returned as "numbersign"
+ # the subsequent display.keysym_to_keycode("numbersign") is 0.
+ keysym = Xlib.XK.string_to_keysym(special_X_keysyms[ch])
+ return keysym
+
+def is_shifted(ch) :
+ if ch.isupper() :
+ return True
+ if "~!@#$%^&*()_+{}|:\"<>?".find(ch) >= 0 :
+ return True
+ return False
+
+class fakeinput:
+ def __init__(self, display = None):
+ global UseXTest
+ if display:
+ self.display = display
+ else:
+ self.display = Xlib.display.Display()
+
+ self.UseXTest = UseXTest
+
+ if UseXTest and not self.display.query_extension("XTEST") :
+ self.UseXTest = False
+
+ self.new_window()
+
+ def new_window(self):
+ if not self.UseXTest:
+ self.window = self.display.get_input_focus()._data["focus"];
+
+ def char_to_keycode(self, ch):
+ keysym = get_keysym(ch)
+ keycode = self.display.keysym_to_keycode(keysym)
+ if keycode == 0 :
+ print "fakeinput: Sorry, can't map", ch
+
+ if (is_shifted(ch)) :
+ shift_mask = Xlib.X.ShiftMask
+ else :
+ shift_mask = 0
+
+ return keycode, shift_mask
+
+ def send_char(self, ch, dosync = True) :
+ keycode, shift_mask = self.char_to_keycode(ch)
+ if (self.UseXTest) :
+ if shift_mask != 0 :
+ Xlib.ext.xtest.fake_input(self.display, Xlib.X.KeyPress, 50)
+ Xlib.ext.xtest.fake_input(self.display, Xlib.X.KeyPress, keycode)
+ Xlib.ext.xtest.fake_input(self.display, Xlib.X.KeyRelease, keycode)
+ if shift_mask != 0 :
+ Xlib.ext.xtest.fake_input(self.display, Xlib.X.KeyRelease, 50)
+ else :
+ event = Xlib.protocol.event.KeyPress(
+ time = int(time.time()),
+ root = self.display.screen().root,
+ window = self.window,
+ same_screen = 0, child = Xlib.X.NONE,
+ root_x = 0, root_y = 0, event_x = 0, event_y = 0,
+ state = shift_mask,
+ detail = keycode
+ )
+ self.window.send_event(event, propagate = True)
+ event = Xlib.protocol.event.KeyRelease(
+ time = int(time.time()),
+ root = self.display.screen().root,
+ window = self.window,
+ same_screen = 0, child = Xlib.X.NONE,
+ root_x = 0, root_y = 0, event_x = 0, event_y = 0,
+ state = shift_mask,
+ detail = keycode
+ )
+ self.window.send_event(event, propagate = True)
+ if dosync:
+ self.display.sync()
+
+ def send_str(self, str):
+ for ch in str:
+ self.send_char(ch, dosync = False)
+ self.display.sync()
+
--- /dev/null
+
+#
+# a library to draw a widget for tap-input of text
+#
+# Have a 4x10 array of buttons. Some buttons enter a symbol,
+# others switch to a different array of buttons.
+# The 4 rows aren't that same, but vary like a qwerty keyboard.
+# Row1: 10 buttons
+# Row2: 9 buttons offset by half a button
+# Row3: 10 buttons just like Row1
+# Row4: 5 buttons each double-width
+#
+# press/drag is passed to caller as movement.
+# press/hold is passed to caller as 'unmap'.
+#
+# Different configs are:
+# lower-case alpha, with minimal punctuation
+# upper-case alpha, with more punctuation
+# numeric with phone and calculator punctuation
+# all punctuations (there are 32 plus escape, tab,...
+#
+# Bottom row is normally:
+# Shift NUM SPC ENTER BackSpace
+# When 'shift' is pressed, the keyboard flips between
+# upper/lower or numeric/punc
+# and bottom row becomes:
+# lock control alt ... something.
+
+import gtk, pango, gobject
+
+keymap = {}
+
+keymap['lower'] = [
+ ['q','w','e','r','t','y','u','i','o','p'],
+ [ 'a','s','d','f','g','h','j','k','l'],
+ ['-','z','x','c','v','b','n','m',',','.']
+]
+keymap['UPPER'] = [
+ ['Q','W','E','R','T','Y','U','I','O','P'],
+ [ 'A','S','D','F','G','H','J','K','L'],
+ ['+','Z','X','C','V','B','N','M',';',':']
+]
+keymap['number'] = [
+ ['1','2','3','4','5','6','7','8','9','0'],
+ [ '+','*','-','/','#','(',')','[',']'],
+ ['{','}','<','>','?',',','.','=',':',';']
+]
+
+keymap['punctuation'] = [
+ ['!','@','#','$','%','^','&','*','(',')'],
+ [ '~','`','_',',','.','<','>','\'','"'],
+ ['\\','|','+','=','_','-','TAB','ESC','DEL','HOME']
+]
+
+class TapBoard(gtk.VBox):
+ __gsignals__ = {
+ 'key' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_STRING,)),
+ 'move': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_INT, gobject.TYPE_INT)),
+ 'hideme' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ ())
+ }
+ def __init__(self):
+ gtk.VBox.__init__(self)
+ self.keysize = 40
+ self.aspect = 1
+ self.width = int(10*self.keysize)
+ self.height = int(4*self.aspect*self.keysize)
+
+ self.dragx = None
+ self.dragy = None
+ self.moved = False
+
+ self.isize = gtk.icon_size_register("mine", 40, 40)
+
+ self.button_timeout = None
+
+ self.buttons = []
+
+ self.set_homogeneous(True)
+
+ for row in range(3):
+ h = gtk.HBox()
+ h.show()
+ self.add(h)
+ bl = []
+ if row == 1:
+ l = gtk.Label(''); l.show()
+ h.pack_start(l, padding=self.keysize/4)
+ l = gtk.Label(''); l.show()
+ h.pack_end(l, padding=self.keysize/4)
+ else:
+ h.set_homogeneous(True)
+ for col in range(9 + abs(row-1)):
+ b = self.add_button(None, self.tap, (row,col), h)
+ bl.append(b)
+ self.buttons.append(bl)
+
+ h = gtk.HBox()
+ h.show()
+ h.set_homogeneous(True)
+ self.add(h)
+
+ fd = pango.FontDescription('sans 10')
+ fd.set_absolute_size(50 * pango.SCALE * self.keysize / 80)
+ b = self.add_button('Shft', self.nextmode, False, h, fd)
+ self.shftbutton = b
+
+ b = self.add_button('Num', self.nextmode, True, h, fd)
+ b = self.add_button('SPC', self.tap, (-1,' '), h, fd)
+ b = self.add_button('Entr', self.tap, (-1,'\n'), h, fd)
+ b = self.add_button(gtk.STOCK_UNDO, self.tap, (-1,'\b'), h)
+
+ self.mode = 'lower'
+ self.single = False
+ self.size = 0
+ self.connect("size-allocate", self.update_buttons)
+ self.connect("realize", self.update_buttons)
+
+
+ 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()
+ b.set_property('can-focus', False)
+ 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):
+ if self.window == None:
+ return
+
+ 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
+ fdsingle = pango.FontDescription('sans 10')
+ fdsingle.set_absolute_size(size / 1.1 * pango.SCALE)
+ fdword = pango.FontDescription('sans 10')
+ fdword.set_absolute_size(size / 1.5 * pango.SCALE)
+
+ 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] = 30*[None]
+ for row in range(3):
+ for col in range(9+abs(1-row)):
+ if not self.buttons[row][col]:
+ continue
+ sym = keymap[mode][row][col]
+ pm = gtk.gdk.Pixmap(self.window, size, size)
+ pm.draw_rectangle(bg, True, 0, 0, size, size)
+ if len(sym) == 1:
+ self.modify_font(fdsingle)
+ else:
+ self.modify_font(fdword)
+ layout = self.create_pango_layout(sym[0:3])
+ (ink, (ex,ey,ew,eh)) = layout.get_pixel_extents()
+ pm.draw_layout(fg,
+ int(size/2 - ew/2),
+ int(size/2 - eh/2),
+ layout)
+ im = gtk.Image()
+ im.set_from_pixmap(pm, None)
+ base_images[mode][row*10+col] = im
+ self.base_images = base_images
+ fd = pango.FontDescription('sans 10')
+ fd.set_absolute_size(size / 1.5 * pango.SCALE)
+ self.modify_font(fd)
+ self.set_button_images()
+
+
+ def set_button_images(self):
+ for row in range(3):
+ for col in range(9+abs(row-1)):
+ b = self.buttons[row][col]
+ if not b:
+ continue
+ im = self.base_images[self.mode][row*10+col]
+ if im:
+ b.set_image(im)
+
+
+ def tap(self, rc):
+ (row,col) = rc
+ if row < 0 :
+ sym = col
+ else:
+ sym = keymap[self.mode][row][col]
+ self.emit('key', sym)
+
+ def press(self, widget, ev, arg):
+ self.dragx = int(ev.x_root)
+ self.dragy = int(ev.y_root)
+ self.emit('move', 0, 0)
+ if True:
+ # press-and-hold makes us disappear
+ if not self.button_timeout:
+ self.button_timeout = gobject.timeout_add(500, self.disappear)
+
+ def release(self, widget, ev, click, arg):
+ self.dragx = None
+ self.dragy = None
+ if arg == None:
+ if self.button_timeout:
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = None
+ if self.moved:
+ self.moved = False
+ # 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.button_timeout:
+ # quick tap in single tap mode, just enter the symbol
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = None
+ (row,col) = arg
+ sym = keymap[self.mode][row][col]
+ self.emit('key', 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.emit('move', x-self.dragx, 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 disappear(self):
+ self.dragx = None
+ self.dragy = None
+ self.emit('hideme')
+
+ def do_buttons(self):
+ self.set_button_images()
+ self.button_timeout = None
+ return False
+
+
+ def nextmode(self, a):
+ if self.mode == 'lower':
+ self.mode = 'UPPER'
+ self.single = True
+ self.shftbutton.child.set_text('Abc')
+ elif self.mode == 'UPPER' and self.single:
+ self.single = False
+ self.shftbutton.child.set_text('ABC')
+ elif self.mode == 'UPPER' and not self.single:
+ self.mode = 'number'
+ self.shftbutton.child.set_text('123')
+ else:
+ self.mode = 'lower'
+ self.shftbutton.child.set_text('abc')
+ self.set_button_images()
+
+ def noprefix(self):
+
+ 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.shftbutton.child.set_text('abc')
+ self.set_button_images()
+
+if __name__ == "__main__" :
+ w = gtk.Window()
+ w.connect("destroy", lambda w: gtk.main_quit())
+ ti = TapBoard()
+ w.add(ti)
+ w.set_default_size(ti.width, ti.height)
+ root = gtk.gdk.get_default_root_window()
+ (x,y,width,height,depth) = root.get_geometry()
+ x = int((width-ti.width)/2)
+ y = int((height-ti.height)/2)
+ w.move(x,y)
+ def pkey(ti, str):
+ print 'key', str
+ ti.connect('key', pkey)
+ def hideme(ti):
+ print 'hidememe'
+ w.hide()
+ ti.connect('hideme', hideme)
+ ti.show()
+ w.show()
+
+ gtk.main()
+
--- /dev/null
+
+import os, struct, time, gtk
+from fakeinput import fakeinput
+from tapboard import TapBoard
+
+
+
+class EvDev:
+ def __init__(self, path, on_event):
+ self.f = os.open(path, os.O_RDWR|os.O_NONBLOCK);
+ self.ev = gobject.io_add_watch(self.f, gobject.IO_IN, self.read)
+ self.on_event = on_event
+ self.grabbed = False
+ self.down_count = 0
+ def read(self, x, y):
+ try:
+ str = os.read(self.f, 16)
+ except:
+ return True
+
+ if len(str) != 16:
+ return True
+ (sec,usec,typ,code,value) = struct.unpack_from("IIHHI", str)
+ if typ == 0x01:
+ # KEY event
+ if value == 0:
+ self.down_count -= 1
+ else:
+ self.down_count += 1
+ if self.down_count < 0:
+ self.down_count = 0
+ self.on_event(self.down_count, typ, code, value)
+ return True
+ def grab(self):
+ if self.grabbed:
+ return
+ #print "grab"
+ fcntl.ioctl(self.f, EVIOC_GRAB, 1)
+ self.grabbed = True
+ def ungrab(self):
+ if not self.grabbed:
+ return
+ #print "release"
+ fcntl.ioctl(self.f, EVIOC_GRAB, 0)
+ self.grabbed = False
+
+
+
+class KeyboardIcon(gtk.StatusIcon):
+ def __init__(self, win = None):
+ gtk.StatusIcon.__init__(self)
+ self.set_from_file('/usr/local/pixmaps/tapinput.png')
+ if win:
+ self.connect('activate', win.activate)
+ def set_win(self, win):
+ if win:
+ self.connect('activate', win.activate)
+
+power_timer = None
+def power_pressed(cnt, type, code, value):
+ if type != 1:
+ # not a key press
+ return
+ if code != 116:
+ # not the power key
+ return
+ global power_timer
+ if value != 1:
+ # not a down press
+ if power_timer != None:
+ gobject.source_remove(power_timer)
+ power_timer = None
+ return
+ power_timer = gobject.timeout_add(300, power_held)
+
+def power_held():
+ global power_timer
+ power_timer = None
+ open("/sys/class/leds/neo1973:vibrator/trigger","w").write("default-on\n")
+ if win.visible:
+ win.hide()
+ win.visible = False
+ else:
+ win.activate()
+ open("/sys/class/leds/neo1973:vibrator/trigger","w").write("none\n")
+ return False
+
+last_tap = 0
+def tap_pressed(cnt, type, code, value):
+ global last_tap
+ if type != 1:
+ # not a key press
+ return
+ if code != 309:
+ # not BtnZ
+ return
+ if value != 1:
+ # only want dow, not up
+ return
+ now = time.time()
+ print now, last_tap
+ if now - last_tap < 0.2:
+ # two taps
+ if win.visible:
+ win.hide()
+ win.visible = False
+ else:
+ win.activate()
+ last_tap = 0
+ else:
+ last_tap = now
+
+
+ico = KeyboardIcon()
+w = gtk.Window(type=gtk.WINDOW_POPUP)
+w.connect("destroy", lambda w: gtk.main_quit())
+ti = TapBoard()
+w.add(ti)
+ico.set_win(w)
+w.set_default_size(ti.width, ti.height)
+root = gtk.gdk.get_default_root_window()
+(x,y,width,height,depth) = root.get_geometry()
+x = int((width - ti.width)/2)
+y = int((height - ti.height)/2)
+w.move(x,y)
+fi = fakeinput()
+def dokey(ti, str):
+ fi.send_char(str)
+ti.connect('key', dokey)
+def domove(ti, x,y):
+ global startx, starty
+ if x == 0 and y == 0:
+ (startx, starty) = w.get_position()
+ x = 0
+ w.move(startx+x, starty+y)
+
+ti.connect('move', domove)
+def hideme(ti):
+ w.hide()
+ti.connect('hideme', hideme)
+try:
+ pbtn = EvDev("/dev/input/event0", power_pressed)
+ tbtn = EvDev("/dev/input/event3", tap_pressed)
+except:
+ pass
+ti.show()
+w.show()
+fi.new_window()
+gtk.main()
+
--- /dev/null
+
+#
+# a library to draw a widget for tap-input of text
+#
+# Have a 4x10 array of buttons. Some buttons enter a symbol,
+# others switch to a different array of buttons.
+# The 4 rows aren't that same, but vary like a qwerty keyboard.
+# Row1: 10 buttons
+# Row2: 9 buttons offset by half a button
+# Row3: 10 buttons just like Row1
+# Row4: 5 buttons each double-width
+#
+# vertial press/drag is passed to caller as movement.
+# press/hold is passed to caller as 'unmap'.
+# horizontal press/drag modifies the selected button
+# on the first 3 rows, it shifts
+# on NUM it goes straight to punctuation
+#
+# Different configs are:
+# lower-case alpha, with minimal punctuation
+# upper-case alpha, with different punctuation
+# numeric with phone and calculator punctuation
+# Remaining punctuation with some cursor control.
+#
+# Bottom row is normally:
+# Shift NUM SPC ENTER BackSpace
+# When 'shift' is pressed, the keyboard flips between
+# upper/lower or numeric/punc
+# and bottom row becomes:
+# lock control alt ... something.
+
+import gtk, pango, gobject
+
+keymap = {}
+
+keymap['lower'] = [
+ ['q','w','e','r','t','y','u','i','o','p'],
+ [ 'a','s','d','f','g','h','j','k','l'],
+ ['-','z','x','c','v','b','n','m',',','.']
+]
+keymap['lower-shift'] = [
+ ['Q','W','E','R','T','Y','U','I','O','P'],
+ [ 'A','S','D','F','G','H','J','K','L'],
+ ['+','Z','X','C','V','B','N','M',';',':']
+]
+#keymap['number'] = [
+# ['1','2','3','4','5','6','7','8','9','0'],
+# [ '+','*','-','/','#','(',')','[',']'],
+# ['{','}','<','>','?',',','.','=',':',';']
+#]
+keymap['number'] = [
+ ['+','-','*','7','8','9','{','}','<','>'],
+ [ '/','#','4','5','6','(',')','[',']'],
+ ['?','=','0','1','2','3','.',',',':',';']
+]
+
+keymap['number-shift'] = [
+ ['!','@','#','$','%','^','&','*','(',')'],
+ [ '~','`','_',',','.','<','>','\'','"'],
+ ['\\','|','+','=','_','-','Tab','Escape','Delete','Home']
+]
+
+class TapBoard(gtk.VBox):
+ __gsignals__ = {
+ 'key' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_STRING,)),
+ 'move': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_INT, gobject.TYPE_INT)),
+ 'hideme' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ ())
+ }
+ def __init__(self):
+ gtk.VBox.__init__(self)
+ self.keysize = 44
+ self.aspect = 1
+ self.width = int(10*self.keysize)
+ self.height = int(4*self.aspect*self.keysize)
+
+ self.dragx = None
+ self.dragy = None
+ self.moved = False
+ self.xmoved = False
+
+ self.isize = gtk.icon_size_register("mine", 40, 40)
+
+ self.button_timeout = None
+
+ self.buttons = []
+
+ self.set_homogeneous(True)
+
+ for row in range(3):
+ h = gtk.HBox()
+ h.show()
+ self.add(h)
+ bl = []
+ if row == 1:
+ l = gtk.Label(''); l.show()
+ h.pack_start(l, padding=self.keysize/4)
+ l = gtk.Label(''); l.show()
+ h.pack_end(l, padding=self.keysize/4)
+ else:
+ h.set_homogeneous(True)
+ for col in range(9 + abs(row-1)):
+ b = self.add_button(None, self.tap, (row,col), h)
+ bl.append(b)
+ self.buttons.append(bl)
+
+ h = gtk.HBox()
+ h.show()
+ h.set_homogeneous(True)
+ self.add(h)
+
+ fd = pango.FontDescription('sans 10')
+ fd.set_absolute_size(50 * pango.SCALE * self.keysize / 80)
+ b = self.add_button('Shft', self.nextshift, False, h, fd)
+ self.shftbutton = b
+
+ b = self.add_button('Num', self.nextmode, True, h, fd)
+ self.modebutton = b
+ b = self.add_button('SPC', self.tap, (-1,' '), h, fd)
+ b = self.add_button('Entr', self.tap, (-1,'\n'), h, fd)
+ b = self.add_button(gtk.STOCK_UNDO, self.tap, (-1,'\b'), h)
+
+ # mode can be 'lower' or 'number'
+ # shift can be '' or '-shift'
+ # locked can be:
+ # None when shift is ''
+ # False with '-shift' for a single shift
+ # True with '-shift' for a locked shit
+ self.image_mode = ''
+ self.mode = 'lower'
+ self.shift = ''
+ self.locked = None
+ self.size = 0
+ self.connect("size-allocate", self.update_buttons)
+ self.connect("realize", self.update_buttons)
+
+
+ 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()
+ b.set_property('can-focus', False)
+ 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):
+ if self.window == None:
+ return
+
+ 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
+ fdsingle = pango.FontDescription('sans 10')
+ fdsingle.set_absolute_size(size / 1.1 * pango.SCALE)
+ fdword = pango.FontDescription('sans 10')
+ fdword.set_absolute_size(size / 1.5 * pango.SCALE)
+
+ 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] = 30*[None]
+ for row in range(3):
+ for col in range(9+abs(1-row)):
+ if not self.buttons[row][col]:
+ continue
+ sym = keymap[mode][row][col]
+ pm = gtk.gdk.Pixmap(self.window, size, size)
+ pm.draw_rectangle(bg, True, 0, 0, size, size)
+ if len(sym) == 1:
+ self.modify_font(fdsingle)
+ else:
+ self.modify_font(fdword)
+ layout = self.create_pango_layout(sym[0:3])
+ (ink, (ex,ey,ew,eh)) = layout.get_pixel_extents()
+ pm.draw_layout(fg,
+ int(size/2 - ew/2),
+ int(size/2 - eh/2),
+ layout)
+ im = gtk.Image()
+ im.set_from_pixmap(pm, None)
+ base_images[mode][row*10+col] = im
+ self.base_images = base_images
+ fd = pango.FontDescription('sans 10')
+ fd.set_absolute_size(size / 1.5 * pango.SCALE)
+ self.modify_font(fd)
+ self.set_button_images()
+
+ def set_button_images(self):
+ mode = self.mode + self.shift
+ if self.image_mode == mode:
+ return
+ for row in range(3):
+ for col in range(9+abs(row-1)):
+ b = self.buttons[row][col]
+ if not b:
+ continue
+ im = self.base_images[mode][row*10+col]
+ if im:
+ b.set_image(im)
+ self.image_mode = mode
+
+
+ def tap(self, rc, moved):
+ (row,col) = rc
+ m = self.mode + self.shift
+ if moved:
+ m = self.mode + '-shift'
+ if row < 0 :
+ sym = col
+ else:
+ sym = keymap[m][row][col]
+ self.emit('key', sym)
+ if self.shift and not self.locked:
+ self.nextshift(True)
+
+ def press(self, widget, ev, arg):
+ self.dragx = int(ev.x_root)
+ self.dragy = int(ev.y_root)
+ self.moved = False
+ self.xmoved = False
+
+ # press-and-hold makes us disappear
+ if self.button_timeout:
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = None
+ self.button_timeout = gobject.timeout_add(500, self.disappear)
+
+ def release(self, widget, ev, click, arg):
+ self.dragx = None
+ self.dragy = None
+
+ if self.button_timeout:
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = None
+ if self.moved:
+ self.moved = False
+ # 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()
+ else:
+ if self.button_timeout:
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = None
+ click(arg, self.xmoved)
+ self.xmoved = False
+
+ def motion(self, widget, ev):
+ if self.dragx == None:
+ return
+ x = int(ev.x_root)
+ y = int(ev.y_root)
+
+ if (not self.xmoved and abs(y-self.dragy) > 40) or self.moved:
+ if not self.moved:
+ self.emit('move', 0, 0)
+ self.emit('move', x-self.dragx, y-self.dragy)
+ self.moved = True
+ if self.button_timeout:
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = None
+ if (not self.moved and abs(x-self.dragx) > 40) or self.xmoved:
+ self.xmoved = 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 disappear(self):
+ self.dragx = None
+ self.dragy = None
+ self.emit('hideme')
+
+ def do_buttons(self):
+ self.set_button_images()
+ self.button_timeout = None
+ return False
+
+ def set_buttons_soon(self):
+ if self.button_timeout:
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = gobject.timeout_add(500, self.do_buttons)
+
+ def nextshift(self, a, moved=False):
+ if self.shift == '' and not a:
+ self.shift = '-shift'
+ self.locked = False
+ lbl = 'Lock'
+ elif not self.locked and not a:
+ self.locked = True
+ lbl = 'UnLk'
+ else:
+ self.shift = ''
+ lbl = 'Shft'
+
+ self.shftbutton.child.set_text(lbl)
+ if self.shift and not self.locked:
+ self.set_buttons_soon()
+ else:
+ self.set_button_images()
+
+ def nextmode(self, a, moved=False):
+ if self.mode == 'lower':
+ self.mode = 'number'
+ self.modebutton.child.set_text('Abc')
+ else:
+ self.mode = 'lower'
+ self.modebutton.child.set_text('Num')
+ self.nextshift(True)
+ if moved:
+ self.nextshift(False)
+ self.nextshift(False)
+ self.set_button_images()
+
+
+if __name__ == "__main__" :
+ w = gtk.Window()
+ w.connect("destroy", lambda w: gtk.main_quit())
+ ti = TapBoard()
+ w.add(ti)
+ w.set_default_size(ti.width, ti.height)
+ root = gtk.gdk.get_default_root_window()
+ (x,y,width,height,depth) = root.get_geometry()
+ x = int((width-ti.width)/2)
+ y = int((height-ti.height)/2)
+ w.move(x,y)
+ def pkey(ti, str):
+ print 'key', str
+ ti.connect('key', pkey)
+ def hideme(ti):
+ print 'hidememe'
+ w.hide()
+ ti.connect('hideme', hideme)
+ ti.show()
+ w.show()
+
+ gtk.main()
+
--- /dev/null
+#!/usr/bin/env python2.5
+
+#
+# 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, os, struct, time
+from fakeinput import fakeinput
+
+keymap = {}
+
+keymap['lower'] = [
+ ['0','1','Tab','2','3','Delete','?','@','#'],
+ ['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','\'','"','|','(',')'],
+ ['[',']','Escape','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','\'','"','|','(',')'],
+ ['[',']','Escape','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', ' ']
+ ]
+
+class TapInput(gtk.Window):
+ def __init__(self):
+ gtk.Window.__init__(self, type=gtk.WINDOW_POPUP)
+ #self.keysize = 80 Small for opaque
+ #self.keysize = 130 a bit large for transparent
+ self.keysize = 90
+ self.width = int(3*self.keysize)
+ self.height = int(4.2*self.keysize)
+ self.set_default_size(self.width, self.height)
+ root = gtk.gdk.get_default_root_window()
+ (x,y,width,height,depth) = root.get_geometry()
+ x = int((width-self.width)/2)
+ y = int((height-self.width)/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 * self.keysize / 80)
+ 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()
+ self.visible = False
+ self.set_opacity(0.4)
+
+
+ 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()
+ b.set_property('can-focus', False)
+ 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
+ fdsingle = pango.FontDescription('sans 10')
+ fdsingle.set_absolute_size(size / 3.5 * pango.SCALE)
+ fdword = pango.FontDescription('sans 10')
+ fdword.set_absolute_size(size / 4.5 * pango.SCALE)
+
+ bg = self.get_style().bg_gc[gtk.STATE_NORMAL]
+ #fg = self.get_style().fg_gc[gtk.STATE_NORMAL]
+ fg = self.window.new_gc()
+ fg.set_foreground(self.get_colormap().alloc_color(gtk.gdk.color_parse('blue')))
+ 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
+ if len(sym) == 1:
+ self.modify_font(fdsingle)
+ else:
+ self.modify_font(fdword)
+ 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 = pango.FontDescription('sans 10')
+ 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[0:3])
+ (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
+ if not self.button_timeout:
+ self.button_timeout = gobject.timeout_add(300, self.do_buttons)
+ if arg == None:
+ # press-and-hold makes us disappear
+ if not self.button_timeout:
+ self.button_timeout = gobject.timeout_add(300, self.disappear)
+
+ def release(self, widget, ev, click, arg):
+ self.dragx = None
+ self.dragy = None
+ if arg == None:
+ if self.button_timeout:
+ gobject.source_remove(self.button_timeout)
+ self.button_timeout = 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()
+ self.visible = False
+ 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 disappear(self):
+ self.hide()
+ self.visible = False
+ self.dragx = None
+ self.dragy = None
+
+ 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.prefix1:
+ 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('abc')
+ 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()
+ if self.window:
+ (wx,wy,ww,wh,wd) = self.window.get_geometry()
+ else:
+ wx,wy,ww,wh,wd = -1,-1,0,0,0
+ if wx < x or wy < y or wx+ww > width or wy+wh > height:
+ # partly off screen, so recentre
+ x = int((width-self.width)/2)
+ y = int((height-self.height)/2)
+ self.move(x,y)
+ else:
+ x,y = wx,wy
+ self.fi.new_window()
+ self.hide()
+ self.show()
+ self.visible = True
+ self.move(x,y)
+
+
+
+class EvDev:
+ def __init__(self, path, on_event):
+ self.f = os.open(path, os.O_RDWR|os.O_NONBLOCK);
+ self.ev = gobject.io_add_watch(self.f, gobject.IO_IN, self.read)
+ self.on_event = on_event
+ self.grabbed = False
+ self.down_count = 0
+ def read(self, x, y):
+ try:
+ str = os.read(self.f, 16)
+ except:
+ return True
+
+ if len(str) != 16:
+ return True
+ (sec,usec,typ,code,value) = struct.unpack_from("IIHHI", str)
+ if typ == 0x01:
+ # KEY event
+ if value == 0:
+ self.down_count -= 1
+ else:
+ self.down_count += 1
+ if self.down_count < 0:
+ self.down_count = 0
+ self.on_event(self.down_count, typ, code, value)
+ return True
+ def grab(self):
+ if self.grabbed:
+ return
+ #print "grab"
+ fcntl.ioctl(self.f, EVIOC_GRAB, 1)
+ self.grabbed = True
+ def ungrab(self):
+ if not self.grabbed:
+ return
+ #print "release"
+ fcntl.ioctl(self.f, EVIOC_GRAB, 0)
+ self.grabbed = False
+
+
+
+class KeyboardIcon(gtk.StatusIcon):
+ def __init__(self, win = None):
+ gtk.StatusIcon.__init__(self)
+ self.set_from_file('/usr/local/pixmaps/tapinput.png')
+ if win:
+ self.connect('activate', win.activate)
+ def set_win(self, win):
+ if win:
+ self.connect('activate', win.activate)
+
+power_timer = None
+def power_pressed(cnt, type, code, value):
+ if type != 1:
+ # not a key press
+ return
+ if code != 116:
+ # not the power key
+ return
+ global power_timer
+ if value != 1:
+ # not a down press
+ if power_timer != None:
+ gobject.source_remove(power_timer)
+ power_timer = None
+ return
+ power_timer = gobject.timeout_add(300, power_held)
+
+def power_held():
+ global power_timer
+ power_timer = None
+ open("/sys/class/leds/neo1973:vibrator/trigger","w").write("default-on\n")
+ if win.visible:
+ win.hide()
+ win.visible = False
+ else:
+ win.activate()
+ open("/sys/class/leds/neo1973:vibrator/trigger","w").write("none\n")
+ return False
+
+last_tap = 0
+def tap_pressed(cnt, type, code, value):
+ global last_tap
+ if type != 1:
+ # not a key press
+ return
+ if code != 309:
+ # not BtnZ
+ return
+ if value != 1:
+ # only want dow, not up
+ return
+ now = time.time()
+ print now, last_tap
+ if now - last_tap < 0.2:
+ # two taps
+ if win.visible:
+ win.hide()
+ win.visible = False
+ else:
+ win.activate()
+ last_tap = 0
+ else:
+ last_tap = now
+
+ico = KeyboardIcon()
+win = TapInput()
+ico.set_win(win)
+#try:
+pbtn = EvDev("/dev/input/event0", power_pressed)
+tbtn = EvDev("/dev/input/event3", tap_pressed)
+#except:
+# pass
+
+gtk.main()
+
--- /dev/null
+#!/usr/bin/env python2.5
+import os, struct, time, gtk
+from fakeinput import fakeinput
+from tapboard import TapBoard
+import gobject
+
+
+class EvDev:
+ def __init__(self, path, on_event, arg=None):
+ self.f = os.open(path, os.O_RDWR|os.O_NONBLOCK);
+ self.ev = gobject.io_add_watch(self.f, gobject.IO_IN, self.read)
+ self.on_event = on_event
+ self.grabbed = False
+ self.arg = arg
+ self.down_count = 0
+ def read(self, x, y):
+ try:
+ str = os.read(self.f, 16)
+ except:
+ return True
+
+ if len(str) != 16:
+ return True
+ (sec,usec,typ,code,value) = struct.unpack_from("IIHHI", str)
+ if typ == 0x01:
+ # KEY event
+ if value == 0:
+ self.down_count -= 1
+ else:
+ self.down_count += 1
+ if self.down_count < 0:
+ self.down_count = 0
+ self.on_event(self.down_count, typ, code, value, self.arg)
+ return True
+ def grab(self):
+ if self.grabbed:
+ return
+ #print "grab"
+ fcntl.ioctl(self.f, EVIOC_GRAB, 1)
+ self.grabbed = True
+ def ungrab(self):
+ if not self.grabbed:
+ return
+ #print "release"
+ fcntl.ioctl(self.f, EVIOC_GRAB, 0)
+ self.grabbed = False
+
+
+
+class KeyboardIcon(gtk.StatusIcon):
+ def __init__(self, activate = None):
+ gtk.StatusIcon.__init__(self)
+ self.set_from_file('/usr/local/pixmaps/tapinput.png')
+ self.set_activate(activate)
+
+ def set_activate(self, activate):
+ if activate:
+ self.connect('activate', activate)
+
+power_timer = None
+def power_pressed(cnt, type, code, value, win):
+ if type != 1:
+ # not a key press
+ return
+ if code != 116:
+ # not the power key
+ return
+ global power_timer
+ if value != 1:
+ # not a down press
+ if power_timer != None:
+ gobject.source_remove(power_timer)
+ power_timer = None
+ return
+ power_timer = gobject.timeout_add(300, power_held, win)
+
+def power_held(win):
+ global power_timer
+ power_timer = None
+ open("/sys/class/leds/neo1973:vibrator/trigger","w").write("default-on\n")
+ if win.visible:
+ win.hide()
+ win.visible = False
+ else:
+ win.hide()
+ win.show()
+ win.activate()
+ win.visible = True
+ open("/sys/class/leds/neo1973:vibrator/trigger","w").write("none\n")
+ return False
+
+last_tap = 0
+def tap_pressed(cnt, type, code, value, win):
+ global last_tap
+ if type != 1:
+ # not a key press
+ return
+ if code != 309:
+ # not BtnZ
+ return
+ if value != 1:
+ # only want dow, not up
+ return
+ now = time.time()
+ print now, last_tap
+ if now - last_tap < 0.2:
+ # two taps
+ if win.visible:
+ win.hide()
+ win.visible = False
+ else:
+ win.hide()
+ win.show()
+ win.activate()
+ win.visible = True
+ last_tap = 0
+ else:
+ last_tap = now
+
+
+ico = KeyboardIcon()
+w = gtk.Window(type=gtk.WINDOW_POPUP)
+w.visible = True
+w.connect("destroy", lambda w: gtk.main_quit())
+ti = TapBoard()
+w.add(ti)
+w.set_default_size(ti.width, ti.height)
+root = gtk.gdk.get_default_root_window()
+(x,y,width,height,depth) = root.get_geometry()
+x = int((width - ti.width)/2)
+y = int((height - ti.height)/2)
+w.move(x,y)
+def activate(ignore):
+ w.hide()
+ w.show()
+ w.visible = True
+ w.activate()
+ w.move(x,y)
+ico.set_activate(activate)
+fi = fakeinput()
+def dokey(ti, str):
+ fi.send_char(str)
+ti.connect('key', dokey)
+def domove(ti, x,y):
+ global startx, starty
+ if x == 0 and y == 0:
+ (startx, starty) = w.get_position()
+ x = 0
+ w.move(startx+x, starty+y)
+
+ti.connect('move', domove)
+def hideme(ti):
+ w.hide()
+ w.visible = False
+ti.connect('hideme', hideme)
+try:
+ pbtn = EvDev("/dev/input/event0", power_pressed, w)
+ tbtn = EvDev("/dev/input/event3", tap_pressed, w)
+except:
+ pass
+ti.show()
+w.show()
+fi.new_window()
+gtk.main()
+