1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Urwid escape sequences common to curses_display and raw_display
5#    Copyright (C) 2004-2011  Ian Ward
6#
7#    This library is free software; you can redistribute it and/or
8#    modify it under the terms of the GNU Lesser General Public
9#    License as published by the Free Software Foundation; either
10#    version 2.1 of the License, or (at your option) any later version.
11#
12#    This library is distributed in the hope that it will be useful,
13#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15#    Lesser General Public License for more details.
16#
17#    You should have received a copy of the GNU Lesser General Public
18#    License along with this library; if not, write to the Free Software
19#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20#
21# Urwid web site: http://excess.org/urwid/
22
23from __future__ import division, print_function
24
25"""
26Terminal Escape Sequences for input and display
27"""
28
29import re
30
31try:
32    from urwid import str_util
33except ImportError:
34    from urwid import old_str_util as str_util
35
36from urwid.compat import bytes, bytes3
37
38# NOTE: because of circular imports (urwid.util -> urwid.escape -> urwid.util)
39# from urwid.util import is_mouse_event -- will not work here
40import urwid.util
41
42within_double_byte = str_util.within_double_byte
43
44SO = "\x0e"
45SI = "\x0f"
46IBMPC_ON = "\x1b[11m"
47IBMPC_OFF = "\x1b[10m"
48
49DEC_TAG = "0"
50DEC_SPECIAL_CHARS = u'▮◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·'
51ALT_DEC_SPECIAL_CHARS = u"_`abcdefghijklmnopqrstuvwxyz{|}~"
52
53DEC_SPECIAL_CHARMAP = {}
54assert len(DEC_SPECIAL_CHARS) == len(ALT_DEC_SPECIAL_CHARS), repr((DEC_SPECIAL_CHARS, ALT_DEC_SPECIAL_CHARS))
55for c, alt in zip(DEC_SPECIAL_CHARS, ALT_DEC_SPECIAL_CHARS):
56    DEC_SPECIAL_CHARMAP[ord(c)] = SO + alt + SI
57
58SAFE_ASCII_DEC_SPECIAL_RE = re.compile(u"^[ -~%s]*$" % DEC_SPECIAL_CHARS)
59DEC_SPECIAL_RE = re.compile(u"[%s]" % DEC_SPECIAL_CHARS)
60
61
62###################
63## Input sequences
64###################
65
66class MoreInputRequired(Exception):
67    pass
68
69def escape_modifier( digit ):
70    mode = ord(digit) - ord("1")
71    return "shift "*(mode&1) + "meta "*((mode&2)//2) + "ctrl "*((mode&4)//4)
72
73
74input_sequences = [
75    ('[A','up'),('[B','down'),('[C','right'),('[D','left'),
76    ('[E','5'),('[F','end'),('[G','5'),('[H','home'),
77
78    ('[1~','home'),('[2~','insert'),('[3~','delete'),('[4~','end'),
79    ('[5~','page up'),('[6~','page down'),
80    ('[7~','home'),('[8~','end'),
81
82    ('[[A','f1'),('[[B','f2'),('[[C','f3'),('[[D','f4'),('[[E','f5'),
83
84    ('[11~','f1'),('[12~','f2'),('[13~','f3'),('[14~','f4'),
85    ('[15~','f5'),('[17~','f6'),('[18~','f7'),('[19~','f8'),
86    ('[20~','f9'),('[21~','f10'),('[23~','f11'),('[24~','f12'),
87    ('[25~','f13'),('[26~','f14'),('[28~','f15'),('[29~','f16'),
88    ('[31~','f17'),('[32~','f18'),('[33~','f19'),('[34~','f20'),
89
90    ('OA','up'),('OB','down'),('OC','right'),('OD','left'),
91    ('OH','home'),('OF','end'),
92    ('OP','f1'),('OQ','f2'),('OR','f3'),('OS','f4'),
93    ('Oo','/'),('Oj','*'),('Om','-'),('Ok','+'),
94
95    ('[Z','shift tab'),
96    ('On', '.'),
97
98    ('[200~', 'begin paste'), ('[201~', 'end paste'),
99] + [
100    (prefix + letter, modifier + key)
101    for prefix, modifier in zip('O[', ('meta ', 'shift '))
102    for letter, key in zip('abcd', ('up', 'down', 'right', 'left'))
103] + [
104    ("[" + digit + symbol, modifier + key)
105    for modifier, symbol in zip(('shift ', 'meta '), '$^')
106    for digit, key in zip('235678',
107        ('insert', 'delete', 'page up', 'page down', 'home', 'end'))
108] + [
109    ('O' + chr(ord('p')+n), str(n)) for n in range(10)
110] + [
111    # modified cursor keys + home, end, 5 -- [#X and [1;#X forms
112    (prefix+digit+letter, escape_modifier(digit) + key)
113    for prefix in ("[", "[1;")
114    for digit in "12345678"
115    for letter,key in zip("ABCDEFGH",
116        ('up','down','right','left','5','end','5','home'))
117] + [
118    # modified F1-F4 keys -- O#X form
119    ("O"+digit+letter, escape_modifier(digit) + key)
120    for digit in "12345678"
121    for letter,key in zip("PQRS",('f1','f2','f3','f4'))
122] + [
123    # modified F1-F13 keys -- [XX;#~ form
124    ("["+str(num)+";"+digit+"~", escape_modifier(digit) + key)
125    for digit in "12345678"
126    for num,key in zip(
127        (3,5,6,11,12,13,14,15,17,18,19,20,21,23,24,25,26,28,29,31,32,33,34),
128        ('delete', 'page up', 'page down',
129        'f1','f2','f3','f4','f5','f6','f7','f8','f9','f10','f11',
130        'f12','f13','f14','f15','f16','f17','f18','f19','f20'))
131] + [
132    # mouse reporting (special handling done in KeyqueueTrie)
133    ('[M', 'mouse'),
134    # report status response
135    ('[0n', 'status ok')
136]
137
138class KeyqueueTrie(object):
139    def __init__( self, sequences ):
140        self.data = {}
141        for s, result in sequences:
142            assert type(result) != dict
143            self.add(self.data, s, result)
144
145    def add(self, root, s, result):
146        assert type(root) == dict, "trie conflict detected"
147        assert len(s) > 0, "trie conflict detected"
148
149        if ord(s[0]) in root:
150            return self.add(root[ord(s[0])], s[1:], result)
151        if len(s)>1:
152            d = {}
153            root[ord(s[0])] = d
154            return self.add(d, s[1:], result)
155        root[ord(s)] = result
156
157    def get(self, keys, more_available):
158        result = self.get_recurse(self.data, keys, more_available)
159        if not result:
160            result = self.read_cursor_position(keys, more_available)
161        return result
162
163    def get_recurse(self, root, keys, more_available):
164        if type(root) != dict:
165            if root == "mouse":
166                return self.read_mouse_info(keys,
167                    more_available)
168            return (root, keys)
169        if not keys:
170            # get more keys
171            if more_available:
172                raise MoreInputRequired()
173            return None
174        if keys[0] not in root:
175            return None
176        return self.get_recurse(root[keys[0]], keys[1:], more_available)
177
178    def read_mouse_info(self, keys, more_available):
179        if len(keys) < 3:
180            if more_available:
181                raise MoreInputRequired()
182            return None
183
184        b = keys[0] - 32
185        x, y = (keys[1] - 33)%256, (keys[2] - 33)%256  # supports 0-255
186
187        prefix = ""
188        if b & 4:    prefix = prefix + "shift "
189        if b & 8:    prefix = prefix + "meta "
190        if b & 16:    prefix = prefix + "ctrl "
191        if (b & MOUSE_MULTIPLE_CLICK_MASK)>>9 == 1:    prefix = prefix + "double "
192        if (b & MOUSE_MULTIPLE_CLICK_MASK)>>9 == 2:    prefix = prefix + "triple "
193
194        # 0->1, 1->2, 2->3, 64->4, 65->5
195        button = ((b&64)/64*3) + (b & 3) + 1
196
197        if b & 3 == 3:
198            action = "release"
199            button = 0
200        elif b & MOUSE_RELEASE_FLAG:
201            action = "release"
202        elif b & MOUSE_DRAG_FLAG:
203            action = "drag"
204        elif b & MOUSE_MULTIPLE_CLICK_MASK:
205            action = "click"
206        else:
207            action = "press"
208
209        return ( (prefix + "mouse " + action, button, x, y), keys[3:] )
210
211    def read_cursor_position(self, keys, more_available):
212        """
213        Interpret cursor position information being sent by the
214        user's terminal.  Returned as ('cursor position', x, y)
215        where (x, y) == (0, 0) is the top left of the screen.
216        """
217        if not keys:
218            if more_available:
219                raise MoreInputRequired()
220            return None
221        if keys[0] != ord('['):
222            return None
223        # read y value
224        y = 0
225        i = 1
226        for k in keys[i:]:
227            i += 1
228            if k == ord(';'):
229                if not y:
230                    return None
231                break
232            if k < ord('0') or k > ord('9'):
233                return None
234            if not y and k == ord('0'):
235                return None
236            y = y * 10 + k - ord('0')
237        if not keys[i:]:
238            if more_available:
239                raise MoreInputRequired()
240            return None
241        # read x value
242        x = 0
243        for k in keys[i:]:
244            i += 1
245            if k == ord('R'):
246                if not x:
247                    return None
248                return (("cursor position", x-1, y-1), keys[i:])
249            if k < ord('0') or k > ord('9'):
250                return None
251            if not x and k == ord('0'):
252                return None
253            x = x * 10 + k - ord('0')
254        if not keys[i:]:
255            if more_available:
256                raise MoreInputRequired()
257        return None
258
259
260
261
262# This is added to button value to signal mouse release by curses_display
263# and raw_display when we know which button was released.  NON-STANDARD
264MOUSE_RELEASE_FLAG = 2048
265
266# This 2-bit mask is used to check if the mouse release from curses or gpm
267# is a double or triple release. 00 means single click, 01 double,
268# 10 triple. NON-STANDARD
269MOUSE_MULTIPLE_CLICK_MASK = 1536
270
271# This is added to button value at mouse release to differentiate between
272# single, double and triple press. Double release adds this times one,
273# triple release adds this times two.  NON-STANDARD
274MOUSE_MULTIPLE_CLICK_FLAG = 512
275
276# xterm adds this to the button value to signal a mouse drag event
277MOUSE_DRAG_FLAG = 32
278
279
280#################################################
281# Build the input trie from input_sequences list
282input_trie = KeyqueueTrie(input_sequences)
283#################################################
284
285_keyconv = {
286    -1:None,
287    8:'backspace',
288    9:'tab',
289    10:'enter',
290    13:'enter',
291    127:'backspace',
292    # curses-only keycodes follow..  (XXX: are these used anymore?)
293    258:'down',
294    259:'up',
295    260:'left',
296    261:'right',
297    262:'home',
298    263:'backspace',
299    265:'f1', 266:'f2', 267:'f3', 268:'f4',
300    269:'f5', 270:'f6', 271:'f7', 272:'f8',
301    273:'f9', 274:'f10', 275:'f11', 276:'f12',
302    277:'shift f1', 278:'shift f2', 279:'shift f3', 280:'shift f4',
303    281:'shift f5', 282:'shift f6', 283:'shift f7', 284:'shift f8',
304    285:'shift f9', 286:'shift f10', 287:'shift f11', 288:'shift f12',
305    330:'delete',
306    331:'insert',
307    338:'page down',
308    339:'page up',
309    343:'enter',    # on numpad
310    350:'5',        # on numpad
311    360:'end',
312}
313
314
315
316def process_keyqueue(codes, more_available):
317    """
318    codes -- list of key codes
319    more_available -- if True then raise MoreInputRequired when in the
320        middle of a character sequence (escape/utf8/wide) and caller
321        will attempt to send more key codes on the next call.
322
323    returns (list of input, list of remaining key codes).
324    """
325    code = codes[0]
326    if code >= 32 and code <= 126:
327        key = chr(code)
328        return [key], codes[1:]
329    if code in _keyconv:
330        return [_keyconv[code]], codes[1:]
331    if code >0 and code <27:
332        return ["ctrl %s" % chr(ord('a')+code-1)], codes[1:]
333    if code >27 and code <32:
334        return ["ctrl %s" % chr(ord('A')+code-1)], codes[1:]
335
336    em = str_util.get_byte_encoding()
337
338    if (em == 'wide' and code < 256 and
339        within_double_byte(chr(code),0,0)):
340        if not codes[1:]:
341            if more_available:
342                raise MoreInputRequired()
343        if codes[1:] and codes[1] < 256:
344            db = chr(code)+chr(codes[1])
345            if within_double_byte(db, 0, 1):
346                return [db], codes[2:]
347    if em == 'utf8' and code>127 and code<256:
348        if code & 0xe0 == 0xc0: # 2-byte form
349            need_more = 1
350        elif code & 0xf0 == 0xe0: # 3-byte form
351            need_more = 2
352        elif code & 0xf8 == 0xf0: # 4-byte form
353            need_more = 3
354        else:
355            return ["<%d>"%code], codes[1:]
356
357        for i in range(need_more):
358            if len(codes)-1 <= i:
359                if more_available:
360                    raise MoreInputRequired()
361                else:
362                    return ["<%d>"%code], codes[1:]
363            k = codes[i+1]
364            if k>256 or k&0xc0 != 0x80:
365                return ["<%d>"%code], codes[1:]
366
367        s = bytes3(codes[:need_more+1])
368
369        assert isinstance(s, bytes)
370        try:
371            return [s.decode("utf-8")], codes[need_more+1:]
372        except UnicodeDecodeError:
373            return ["<%d>"%code], codes[1:]
374
375    if code >127 and code <256:
376        key = chr(code)
377        return [key], codes[1:]
378    if code != 27:
379        return ["<%d>"%code], codes[1:]
380
381    result = input_trie.get(codes[1:], more_available)
382
383    if result is not None:
384        result, remaining_codes = result
385        return [result], remaining_codes
386
387    if codes[1:]:
388        # Meta keys -- ESC+Key form
389        run, remaining_codes = process_keyqueue(codes[1:],
390            more_available)
391        if urwid.util.is_mouse_event(run[0]):
392            return ['esc'] + run, remaining_codes
393        if run[0] == "esc" or run[0].find("meta ") >= 0:
394            return ['esc']+run, remaining_codes
395        return ['meta '+run[0]]+run[1:], remaining_codes
396
397    return ['esc'], codes[1:]
398
399
400####################
401## Output sequences
402####################
403
404ESC = "\x1b"
405
406CURSOR_HOME = ESC+"[H"
407CURSOR_HOME_COL = "\r"
408
409APP_KEYPAD_MODE = ESC+"="
410NUM_KEYPAD_MODE = ESC+">"
411
412SWITCH_TO_ALTERNATE_BUFFER = ESC+"7"+ESC+"[?47h"
413RESTORE_NORMAL_BUFFER = ESC+"[?47l"+ESC+"8"
414
415#RESET_SCROLL_REGION = ESC+"[;r"
416#RESET = ESC+"c"
417
418REPORT_STATUS = ESC + "[5n"
419REPORT_CURSOR_POSITION = ESC+"[6n"
420
421INSERT_ON = ESC + "[4h"
422INSERT_OFF = ESC + "[4l"
423
424def set_cursor_position( x, y ):
425    assert type(x) == int
426    assert type(y) == int
427    return ESC+"[%d;%dH" %(y+1, x+1)
428
429def move_cursor_right(x):
430    if x < 1: return ""
431    return ESC+"[%dC" % x
432
433def move_cursor_up(x):
434    if x < 1: return ""
435    return ESC+"[%dA" % x
436
437def move_cursor_down(x):
438    if x < 1: return ""
439    return ESC+"[%dB" % x
440
441HIDE_CURSOR = ESC+"[?25l"
442SHOW_CURSOR = ESC+"[?25h"
443
444MOUSE_TRACKING_ON = ESC+"[?1000h"+ESC+"[?1002h"
445MOUSE_TRACKING_OFF = ESC+"[?1002l"+ESC+"[?1000l"
446
447DESIGNATE_G1_SPECIAL = ESC+")0"
448
449ERASE_IN_LINE_RIGHT = ESC+"[K"
450