]> git.neil.brown.name Git - freerunner.git/commitdiff
Nearly the end of the random stuff...
authorNeilBrown <neilb@suse.de>
Sun, 6 Feb 2011 10:52:36 +0000 (21:52 +1100)
committerNeilBrown <neilb@suse.de>
Sun, 6 Feb 2011 10:52:36 +0000 (21:52 +1100)
Signed-off-by: NeilBrown <neilb@suse.de>
lib/listselect.py
notes/tapboard [new file with mode: 0644]
petrol/petrol.py [new file with mode: 0644]
tap2/fakeinput.py [new file with mode: 0644]
tap2/old/tapboard.py [new file with mode: 0644]
tap2/old/tapinput.py [new file with mode: 0644]
tap2/tapboard.py [new file with mode: 0644]
tap2/tapinput-old [new file with mode: 0755]
tap2/tapinput.py [new file with mode: 0755]

index 6d2d6a1245dca26ecc18dac56eec35e10ec1e74c..dac1b8cbe58fba13c94d706b7bea30d84476bf59 100644 (file)
@@ -262,7 +262,7 @@ class ListSelect(gtk.DrawingArea):
             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)
diff --git a/notes/tapboard b/notes/tapboard
new file mode 100644 (file)
index 0000000..8ad64c1
--- /dev/null
@@ -0,0 +1,20 @@
+
+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
diff --git a/petrol/petrol.py b/petrol/petrol.py
new file mode 100644 (file)
index 0000000..4be744f
--- /dev/null
@@ -0,0 +1,437 @@
+#!/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()
+
diff --git a/tap2/fakeinput.py b/tap2/fakeinput.py
new file mode 100644 (file)
index 0000000..3b96cb0
--- /dev/null
@@ -0,0 +1,147 @@
+#!/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()
+
diff --git a/tap2/old/tapboard.py b/tap2/old/tapboard.py
new file mode 100644 (file)
index 0000000..818f6e1
--- /dev/null
@@ -0,0 +1,339 @@
+
+#
+# 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()
+
diff --git a/tap2/old/tapinput.py b/tap2/old/tapinput.py
new file mode 100644 (file)
index 0000000..f6d5e4b
--- /dev/null
@@ -0,0 +1,150 @@
+
+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()
+
diff --git a/tap2/tapboard.py b/tap2/tapboard.py
new file mode 100644 (file)
index 0000000..c69c663
--- /dev/null
@@ -0,0 +1,378 @@
+
+#
+# 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()
+
diff --git a/tap2/tapinput-old b/tap2/tapinput-old
new file mode 100755 (executable)
index 0000000..cd33719
--- /dev/null
@@ -0,0 +1,557 @@
+#!/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()
+
diff --git a/tap2/tapinput.py b/tap2/tapinput.py
new file mode 100755 (executable)
index 0000000..a189ef0
--- /dev/null
@@ -0,0 +1,165 @@
+#!/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()
+