From 8ff1b4a8682972f90e30294fe1bfff006dc8b3d4 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sun, 6 Feb 2011 21:52:36 +1100 Subject: [PATCH] Nearly the end of the random stuff... Signed-off-by: NeilBrown --- lib/listselect.py | 2 +- notes/tapboard | 20 ++ petrol/petrol.py | 437 +++++++++++++++++++++++++++++++++ tap2/fakeinput.py | 147 ++++++++++++ tap2/old/tapboard.py | 339 ++++++++++++++++++++++++++ tap2/old/tapinput.py | 150 ++++++++++++ tap2/tapboard.py | 378 +++++++++++++++++++++++++++++ tap2/tapinput-old | 557 +++++++++++++++++++++++++++++++++++++++++++ tap2/tapinput.py | 165 +++++++++++++ 9 files changed, 2194 insertions(+), 1 deletion(-) create mode 100644 notes/tapboard create mode 100644 petrol/petrol.py create mode 100644 tap2/fakeinput.py create mode 100644 tap2/old/tapboard.py create mode 100644 tap2/old/tapinput.py create mode 100644 tap2/tapboard.py create mode 100755 tap2/tapinput-old create mode 100755 tap2/tapinput.py diff --git a/lib/listselect.py b/lib/listselect.py index 6d2d6a1..dac1b8c 100644 --- a/lib/listselect.py +++ b/lib/listselect.py @@ -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 index 0000000..8ad64c1 --- /dev/null +++ b/notes/tapboard @@ -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 index 0000000..4be744f --- /dev/null +++ b/petrol/petrol.py @@ -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 index 0000000..3b96cb0 --- /dev/null +++ b/tap2/fakeinput.py @@ -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 index 0000000..818f6e1 --- /dev/null +++ b/tap2/old/tapboard.py @@ -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 index 0000000..f6d5e4b --- /dev/null +++ b/tap2/old/tapinput.py @@ -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 index 0000000..c69c663 --- /dev/null +++ b/tap2/tapboard.py @@ -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 index 0000000..cd33719 --- /dev/null +++ b/tap2/tapinput-old @@ -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 index 0000000..a189ef0 --- /dev/null +++ b/tap2/tapinput.py @@ -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() + -- 2.39.5