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