From 969d1d393e75a229523c234203059fb570d28ed1 Mon Sep 17 00:00:00 2001 From: Lars-Dominik Braun Date: Tue, 17 Sep 2019 18:31:24 +0200 Subject: Initial import --- lulua/writer.py | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 lulua/writer.py (limited to 'lulua/writer.py') diff --git a/lulua/writer.py b/lulua/writer.py new file mode 100644 index 0000000..38dc01c --- /dev/null +++ b/lulua/writer.py @@ -0,0 +1,202 @@ +# Copyright (c) 2019 lulua contributors +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import json +from operator import itemgetter + +from .layout import * + +# XXX: dynamically index this by Button()? +defaultFingermap = { + # fingers: hand (L/R), finger (counting from left to right on left hand and right to left on right hand) + # B: number row + # number keys left side + 'Bl1': (LEFT, LITTLE), + 'Bl2': (LEFT, LITTLE), + 'Bl3': (LEFT, LITTLE), + 'Bl4': (LEFT, RING), + 'Bl5': (LEFT, MIDDLE), + 'Bl6': (LEFT, INDEX), + 'Bl7': (LEFT, INDEX), + # number keys right side + 'Br6': (RIGHT, INDEX), + 'Br5': (RIGHT, INDEX), + 'Br4': (RIGHT, MIDDLE), + 'Br3': (RIGHT, RING), + 'Br2': (RIGHT, LITTLE), + 'Br1': (RIGHT, LITTLE), + 'Br_bs': (RIGHT, LITTLE), + # C: top row + 'Cl_tab': (LEFT, LITTLE), + # letter keys left side + 'Cl1': (LEFT, LITTLE), + 'Cl2': (LEFT, RING), + 'Cl3': (LEFT, MIDDLE), + 'Cl4': (LEFT, INDEX), + 'Cl5': (LEFT, INDEX), + # letter keys right side + 'Cr7': (RIGHT, INDEX), + 'Cr6': (RIGHT, INDEX), + 'Cr5': (RIGHT, MIDDLE), + 'Cr4': (RIGHT, RING), + 'Cr3': (RIGHT, LITTLE), + 'Cr2': (RIGHT, LITTLE), + 'Cr1': (RIGHT, LITTLE), + # return key + 'CD_ret': (RIGHT, LITTLE), + # D: middle row + 'Dl_caps': (LEFT, LITTLE), + # letter keys left side + 'Dl1': (LEFT, LITTLE), + 'Dl2': (LEFT, RING), + 'Dl3': (LEFT, MIDDLE), + 'Dl4': (LEFT, INDEX), + 'Dl5': (LEFT, INDEX), + # letter keys right side + 'Dr7': (RIGHT, INDEX), + 'Dr6': (RIGHT, INDEX), + 'Dr5': (RIGHT, MIDDLE), + 'Dr4': (RIGHT, RING), + 'Dr3': (RIGHT, LITTLE), + 'Dr2': (RIGHT, LITTLE), + 'Dr1': (RIGHT, LITTLE), + # E: bottom row + 'El_shift': (LEFT, LITTLE), + # letter keys left side + 'El1': (LEFT, LITTLE), + 'El2': (LEFT, LITTLE), + 'El3': (LEFT, RING), + 'El4': (LEFT, MIDDLE), + 'El5': (LEFT, INDEX), + 'El6': (LEFT, INDEX), + # letter keys right side + 'Er5': (RIGHT, INDEX), + 'Er4': (RIGHT, INDEX), + 'Er3': (RIGHT, MIDDLE), + 'Er2': (RIGHT, RING), + 'Er1': (RIGHT, LITTLE), + 'Er_shift': (RIGHT, LITTLE), + # F: bottom control row + 'Fl_ctrl': (LEFT, LITTLE), + 'Fl_fn': (LEFT, LITTLE), + 'Fl_win': (LEFT, THUMB), + 'Fl_alt': (LEFT, THUMB), + 'Fl_space': (LEFT, THUMB), + 'Fr_space': (RIGHT, THUMB), + 'Fr_altgr': (RIGHT, THUMB), + 'Fr_win': (RIGHT, THUMB), + 'Fr_menu': (RIGHT, THUMB), + 'Fr_ctrl': (RIGHT, LITTLE), + } + +class SkipEvent: + __slots__ = ('char', ) + + def __init__ (self, char): + self.char = char + + def __eq__ (self, other): + if not isinstance (other, SkipEvent): + return NotImplemented + return self.char == other.char + + def __repr__ (self): + return f'SkipEvent({self.char!r})' + +class Writer: + """ The magical being whose commands the machine obeys """ + + __slots__ = ('hands', 'lastCombination', 'layout') + + def __init__ (self, layout: KeyboardLayout): + self.layout = layout + # assuming 10 finger typing + self.hands = { + LEFT: Hand (LEFT, [Finger (x) for x in FingerType]), + RIGHT: Hand (RIGHT, [Finger (x) for x in reversed (FingerType)]), + } + self.lastCombination = None + + def __getitem__ (self, k): + return self.hands[k] + + def getHandFinger (self, button: Button): + return defaultFingermap[button.name] + + def chooseCombination (self, combinations): + """ + Choose the best button combination from the ones given. + + Return the actual button combination used. + + For instance: + - A key on the right is usually combined with the shift button on the + left and vice versa. + - The spacebar is usually hit by the thumb of the previously unused + hand or the one on the left if two buttons were pressed at the same + time. + - The combination with the minimum amount of fingers required is chosen + if multiple options are available + """ + dirToScore = {LEFT: 1, RIGHT: -1} + def calcEffort (comb): + prev = self.lastCombination + if prev is None: + e = 0 + elif len (prev) > 1: + # prefer left side + e = dirToScore[RIGHT] + else: + assert len (prev.buttons) == 1 + e = dirToScore[self.getHandFinger (first (prev.buttons))[0]] + for b in comb: + pos = self.getHandFinger (b)[0] + e += dirToScore[pos] + #print ('score for', buttons, abs (e)) + return abs (e) + len (comb) + + return min (zip (map (calcEffort, combinations), combinations), key=itemgetter (0))[1] + + def press (self, comb): + self.lastCombination = comb + + def type (self, fd): + buf = '' + while True: + buf += fd.read (self.layout.bufferLen-len (buf)) + if not buf: + break + + try: + match, combinations = self.layout (buf) + assert len (match) > 0, match + + comb = self.chooseCombination (combinations) + + yield match, comb + + self.press (comb) + buf = buf[len (match):] + except KeyError: + # ignore unknown characters + yield None, SkipEvent (buf[0]) + buf = buf[1:] + continue + -- cgit v1.2.3