DriverCurses: Add support for key modifiers.
authorRadek Brich <radek.brich@devl.cz>
Wed, 23 Jan 2013 00:50:23 +0100
changeset 65 5f0697950f15
parent 64 03f591f5fe5c
child 66 824a9837bbb3
DriverCurses: Add support for key modifiers.
tests/curses_keycodes.py
tuikit/driver_curses.py
tuikit/events.py
tuikit/layout.py
--- a/tests/curses_keycodes.py	Sun Jan 20 00:49:19 2013 +0100
+++ b/tests/curses_keycodes.py	Wed Jan 23 00:50:23 2013 +0100
@@ -14,14 +14,19 @@
     curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION)
     while True:
         c = screen.getch()
+        s = ''
 
         screen.nodelay(1)
         while c != -1:
+            try:
+                s += chr(c)
+            except ValueError:
+                s += '?'
             screen.addstr('0x%02x,' % c)
             c = screen.getch()
         screen.nodelay(0)
 
-        screen.addstr('\n')
+        screen.addstr(' %r\n' % s)
 
         screen.refresh()
 
--- a/tuikit/driver_curses.py	Sun Jan 20 00:49:19 2013 +0100
+++ b/tuikit/driver_curses.py	Wed Jan 23 00:50:23 2013 +0100
@@ -9,41 +9,47 @@
 
 
 class DriverCurses(Driver):
-    xterm_codes = (
+    key_codes = (
         (0x09,                      'tab'           ),
         (0x0a,                      'enter'         ),
         (0x7f,                      'backspace'     ),
         (0x1b,                      'escape'        ),
-        (0x1b,0x4f,0x50,            'f1'            ),
-        (0x1b,0x4f,0x51,            'f2'            ),
-        (0x1b,0x4f,0x52,            'f3'            ),
-        (0x1b,0x4f,0x53,            'f4'            ),
-        (0x1b,0x5b,0x31,0x35,0x7e,  'f5'            ),
-        (0x1b,0x5b,0x31,0x37,0x7e,  'f6'            ),
-        (0x1b,0x5b,0x31,0x38,0x7e,  'f7'            ),
-        (0x1b,0x5b,0x31,0x39,0x7e,  'f8'            ),
-        (0x1b,0x5b,0x31,0x7e,       'home'          ),  # linux
-        (0x1b,0x5b,0x32,0x30,0x7e,  'f9'            ),
-        (0x1b,0x5b,0x32,0x31,0x7e,  'f10'           ),
-        (0x1b,0x5b,0x32,0x33,0x7e,  'f11'           ),
-        (0x1b,0x5b,0x32,0x34,0x7e,  'f12'           ),
-        (0x1b,0x5b,0x32,0x7e,       'insert'        ),
-        (0x1b,0x5b,0x33,0x7e,       'delete'        ),
-        (0x1b,0x5b,0x34,0x7e,       'end'           ),  # linux
-        (0x1b,0x5b,0x35,0x7e,       'pageup'        ),
-        (0x1b,0x5b,0x36,0x7e,       'pagedown'      ),
-        (0x1b,0x5b,0x41,            'up'            ),
-        (0x1b,0x5b,0x42,            'down'          ),
-        (0x1b,0x5b,0x43,            'right'         ),
-        (0x1b,0x5b,0x44,            'left'          ),
-        (0x1b,0x5b,0x46,            'end'           ),
-        (0x1b,0x5b,0x48,            'home'          ),
-        (0x1b,0x5b,0x4d,            'mouse'         ),
-        (0x1b,0x5b,0x5b,0x41,       'f1'            ),  # linux
-        (0x1b,0x5b,0x5b,0x42,       'f2'            ),  # linux
-        (0x1b,0x5b,0x5b,0x43,       'f3'            ),  # linux
-        (0x1b,0x5b,0x5b,0x44,       'f4'            ),  # linux
-        (0x1b,0x5b,0x5b,0x45,       'f5'            ),  # linux
+        (0x1b,0x4f,0x50,            'f1'            ),  # xterm
+        (0x1b,0x4f,0x51,            'f2'            ),  # xterm
+        (0x1b,0x4f,0x52,            'f3'            ),  # xterm
+        (0x1b,0x4f,0x53,            'f4'            ),  # xterm
+        (0x1b,0x5b,                 'CSI'           ),  # see csi_codes
+    )
+
+    # http://en.wikipedia.org/wiki/ANSI_escape_code
+    csi_codes = (
+        # code            param     key name
+        (0x7e,              1,      'home'          ),  # linux
+        (0x7e,              2,      'insert'        ),
+        (0x7e,              3,      'delete'        ),
+        (0x7e,              4,      'end'           ),  # linux
+        (0x7e,              5,      'pageup'        ),
+        (0x7e,              6,      'pagedown'      ),
+        (0x7e,              15,     'f5'            ),
+        (0x7e,              17,     'f6'            ),
+        (0x7e,              18,     'f7'            ),
+        (0x7e,              19,     'f8'            ),
+        (0x7e,              20,     'f9'            ),
+        (0x7e,              21,     'f10'           ),
+        (0x7e,              23,     'f11'           ),
+        (0x7e,              24,     'f12'           ),
+        (0x41,              1,      'up'            ),
+        (0x42,              1,      'down'          ),
+        (0x43,              1,      'right'         ),
+        (0x44,              1,      'left'          ),
+        (0x46,              1,      'end'           ),  # xterm
+        (0x48,              1,      'home'          ),  # xterm
+        (0x4d,              None,   'mouse'         ),
+        (0x5b,0x41,         1,      'f1'            ),  # linux
+        (0x5b,0x42,         1,      'f2'            ),  # linux
+        (0x5b,0x43,         1,      'f3'            ),  # linux
+        (0x5b,0x44,         1,      'f4'            ),  # linux
+        (0x5b,0x45,         1,      'f5'            ),  # linux
     )
 
     color_map = {
@@ -280,7 +286,6 @@
                 res += [('keypress', None, str(chr(c)))]
 
             else:
-                #self.top.keypress(None, unicode(chr(c)))
                 self.inputqueue_unget(c)
                 res += self.process_control_chars()
 
@@ -328,7 +333,7 @@
 
 
     def process_control_chars(self):
-        codes = self.xterm_codes
+        codes = self.key_codes
         matchingcodes = []
         match = None
         consumed = []
@@ -379,8 +384,8 @@
                 ','.join(['0x%x'%x for x in consumed]))
             return [('keypress', 'Unknown', None)]
 
-        if keyname == 'mouse':
-            return self.process_xterm_mouse()
+        if keyname == 'CSI':
+            return self.process_control_sequence()
 
         return [('keypress', keyname, None)]
 
@@ -413,6 +418,81 @@
 
         return out
 
+    def process_control_sequence(self):
+        codes = self.csi_codes
+        debug_seq = [0x1b, 0x5b]
+        c = self.inputqueue_get_wait()
+        debug_seq.append(c)
+
+        # numeric parameters?
+        params = []
+        if chr(c).isdigit():
+            params.append(chr(c))
+            while True:
+                c = self.inputqueue_get_wait()
+                debug_seq.append(c)
+                if chr(c).isdigit():
+                    params[-1] += chr(c)
+                elif chr(c) == ';':
+                    params.append('')
+                else:
+                    break
+        params = [int(x) for x in params]
+        if len(params) == 0:
+            params = [1]
+
+        # filter codes using
+        while True:
+            matching_codes = []
+            for code in codes:
+                if len(code) > 2 and code[0] == c:
+                    matching_codes.append(code[1:])
+            codes = matching_codes
+
+            if len(codes) == 0:
+                # no match -> unknown code
+                seq = ','.join(['0x%x' % x for x in debug_seq])
+                self.log.debug('Unknown control sequence: %s', seq)
+                return [('keypress', 'Unknown:' + seq, None)]
+            elif len(codes) == 1:
+                # one match -> we got the winner
+                break
+            elif len(codes[0]) == 2:
+                # more than one matching, but no more chars to check
+                # will be sorted out using parameters
+                break
+            else:
+                # more than one matching -> continue loop
+                c = self.inputqueue_get_wait()
+                debug_seq.append(c)
+
+        # filter codes using first parameter
+        matching_codes = []
+        for code in codes:
+            if params[0] == code[0] or params[0] is None:
+                matching_codes.append(code)
+
+        if len(matching_codes) == 0:
+            # no match -> unknown code
+            seq = ','.join(['0x%x' % x for x in debug_seq])
+            self.log.debug('Unknown control sequence: %s', seq)
+            return [('keypress', 'Unknown:' + seq, None)]
+
+        if len(matching_codes) > 1:
+            raise Exception('Internal error: invalid csi_codes, more than one matching')
+
+        keyname = matching_codes[0][1]
+
+        if keyname == 'mouse':
+            return self.process_xterm_mouse()
+
+        # modifiers
+        mod = 0
+        if len(params) > 1:
+            mod = params[1] - 1
+
+        return [('keypress', keyname, None, mod)]
+
 
 driverclass = DriverCurses
 
--- a/tuikit/events.py	Sun Jan 20 00:49:19 2013 +0100
+++ b/tuikit/events.py	Wed Jan 23 00:50:23 2013 +0100
@@ -48,13 +48,20 @@
 
 
 class KeyboardEvent(Event):
-    def __init__(self, keyname, char):
+
+    MOD_SHIFT = 1<<0
+    MOD_ALT   = 1<<1
+    MOD_CTRL  = 1<<2
+    MOD_META  = 1<<3
+
+    def __init__(self, keyname, char, mod=0):
         Event.__init__(self)
         self.keyname = keyname
         self.char = char
+        self.mod = mod
 
     def __repr__(self):
-        return 'KeyboardEvent(keyname={0.keyname},char={0.char})'.format(self)
+        return 'KeyboardEvent(keyname={0.keyname},char={0.char},mod={0.mod})'.format(self)
 
 
 class MouseEvent(Event):
@@ -66,7 +73,7 @@
         self.wy = y
         self.px = 0  # parent coordinates
         self.py = 0
-        #: Mouse button: left=1, middle=2, right=3, wheelup=4 wheeldown=5
+        #: Mouse button: left=1, middle=2, right=3, wheelup=4, wheeldown=5
         self.button = button
 
     def make_child_event(self, container, child):
--- a/tuikit/layout.py	Sun Jan 20 00:49:19 2013 +0100
+++ b/tuikit/layout.py	Wed Jan 23 00:50:23 2013 +0100
@@ -227,7 +227,7 @@
         self._grid = []
         rown = 0
         coln = 0
-        for child in self._getchildren():
+        for child in self._get_children():
             if coln == 0:
                 row = []
                 for _i in range(self.numcols):