summaryrefslogtreecommitdiff
path: root/src/keyboard.py
blob: 99173f402700f4cc1a434ee8665d7e96f90fb88a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# Copyright 2010-2011 Florent Le Coz <louiz@louiz.org>
#
# This file is part of Poezio.
#
# Poezio is free software: you can redistribute it and/or modify
# it under the terms of the zlib license. See the COPYING file.

"""
Functions to interact with the keyboard
Mainly, read keys entered and return a string (most
of the time ONE char, but may be longer if it's a keyboard
shortcut, like ^A, M-a or KEY_RESIZE)
"""

import time

def get_next_byte(s):
    """
    Read the next byte of the utf-8 char
    ncurses seems to return a string of the byte
    encoded in latin-1. So what we get is NOT what we typed
    unless we do the conversion…
    """
    try:
        c = s.getkey()
    except:
        return (None, None)
    if len(c) >= 4:
        return (None, c)
    return (ord(c), c.encode('latin-1')) # returns a number and a bytes object

def read_char(s, timeout=1000):
    """
    Read one utf-8 char
    see http://en.wikipedia.org/wiki/UTF-8#Description
    """
    s.timeout(timeout) # The timeout for timed events to be checked every second
    ret_list = []
    # The list of all chars. For example if you paste a text, the list the chars pasted
    # so that they can be handled at once.
    (first, char) = get_next_byte(s)
    while first is not None or char is not None:
        if not isinstance(first, int): # Keyboard special, like KEY_HOME etc
            return [char]
        if first == 127 or first == 8:
            return ["KEY_BACKSPACE"]
        s.timeout(0)            # we are now getting the missing utf-8 bytes to get a whole char
        if first < 127:  # ASCII char on one byte
            if first <= 26:         # transform Ctrl+* keys
                char = chr(first + 64)
                ret_list.append("^"+char)
                (first, char) = get_next_byte(s)
                continue
            if first == 27:
                second = read_char(s, 0)
                if second is None: # if escape was pressed, a second char
                                   # has to be read. But it timed out.
                    return None
                res = 'M-%s' % (second[0],)
                ret_list.append(res)
                (first, char) = get_next_byte(s)
                continue
        if 194 <= first:
            (code, c) = get_next_byte(s) # 2 bytes char
            char += c
        if 224 <= first:
            (code, c) = get_next_byte(s) # 3 bytes char
            char += c
        if 240 <= first:
            (code, c) = get_next_byte(s) # 4 bytes char
            char += c
        try:
            ret_list.append(char.decode('utf-8')) # return all the concatened byte objets, decoded
        except UnicodeDecodeError:
            return None
        # s.timeout(1)            # timeout to detect a paste of many chars
        (first, char) = get_next_byte(s)
    if not ret_list:
        # nothing at all was read, that’s a timed event timeout
        return None
    if len(ret_list) != 1:
        if ret_list[-1] == '^M':
            ret_list.pop(-1)
        return [char if char != '^M' else '^J' for char in ret_list]
    return ret_list

if __name__ == '__main__':
    import curses
    s = curses.initscr()
    curses.curs_set(1)
    curses.noecho()
    curses.nonl()
    s.keypad(True)
    curses.noecho()
    while True:
        s.addstr('%s\n' % read_char(s))