ucurses: Implementation of curses subset.

This implements only curses windows, with unbuffered output directly to
terminal. Curses pads (offscreen windows) are thus not supported. Only
subset of APIs are implemented, too.
pull/26/head
Paul Sokolovsky 2015-05-19 00:59:16 +03:00
rodzic 631ebfd8ba
commit 6984f17acc
1 zmienionych plików z 303 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,303 @@
import os
import tty, termios
COLOR_BLACK = 0
COLOR_RED = 1
COLOR_GREEN = 2
COLOR_YELLOW = 3
COLOR_BLUE = 4
COLOR_MAGENTA = 5
COLOR_CYAN = 6
COLOR_WHITE = 7
A_NORMAL = 0
A_BOLD = 1
A_UNDERLINE = 2
A_REVERSE = 4
A_STANDOUT = A_REVERSE
ATTRMAP = {
A_NORMAL: b"\x1b[0m",
A_BOLD: b"\x1b[1m", # Some terminal emulators don't render bold by default, then use 4m for underline
A_REVERSE: b"\x1b[7m",
}
# Use http://www.utf8-chartable.de/unicode-utf8-table.pl
# for utf-8 pseudographic reference
# "─"
ACS_HLINE = b"\xe2\x94\x80"
# "│"
ACS_VLINE = b"\xe2\x94\x82"
# "┌"
ACS_ULCORNER = b"\xe2\x94\x8c"
# "┐"
ACS_URCORNER = b"\xe2\x94\x90"
# "└"
ACS_LLCORNER = b"\xe2\x94\x94"
# "┘"
ACS_LRCORNER = b"\xe2\x94\x98"
KEY_F1 = 1031
KEY_RESIZE = 1100
KEY_MOUSE = 1101
KEY_BTAB = 1090
KEY_UP = 1001
KEY_DOWN = 1002
KEY_LEFT = 1003
KEY_RIGHT = 1004
KEY_HOME = 1005
KEY_END = 1006
KEY_PGUP = 1007
KEY_PGDN = 1008
KEY_QUIT = 1009
KEY_ENTER = 1010
KEY_BACKSPACE = 1011
KEY_DELETE = 1012
KEY_ESC = 1020
KEY_DC = KEY_DELETE
KEY_PPAGE = KEY_PGUP
KEY_NPAGE = KEY_PGDN
KEYMAP = {
b"\x1b[A": KEY_UP,
b"\x1b[B": KEY_DOWN,
b"\x1b[D": KEY_LEFT,
b"\x1b[C": KEY_RIGHT,
b"\x1bOH": KEY_HOME,
b"\x1bOF": KEY_END,
b"\x1b[1~": KEY_HOME,
b"\x1b[4~": KEY_END,
b"\x1b[5~": KEY_PGUP,
b"\x1b[6~": KEY_PGDN,
b"\x03": KEY_QUIT,
b"\r": KEY_ENTER,
b"\x7f": KEY_BACKSPACE,
b"\x1b[3~": KEY_DELETE,
b"\x1bOA": KEY_UP,
b"\x1bOB": KEY_DOWN,
b"\x1bOD": KEY_LEFT,
b"\x1bOC": KEY_RIGHT,
b"\x1bOP": KEY_F1,
b"\x1b": KEY_ESC,
b"\x1b[Z": KEY_BTAB,
}
ALL_MOUSE_EVENTS = 0xff
def _wr(s):
# TODO: When Python is 3.5, update this to use only bytes
if isinstance(s, str):
s = bytes(s, "utf-8")
os.write(1, s)
def _move(row, col):
# TODO: When Python is 3.5, update this to use bytes
_wr("\x1b[%d;%dH" % (row + 1, col + 1))
# Clear specified number of positions
def _clear_num_pos(num):
if num > 0:
_wr("\x1b[%dX" % num)
def _draw_box(left, top, width, height):
bottom = top + height - 1
_move(top, left)
_wr(ACS_ULCORNER)
hor = ACS_HLINE * (width - 2)
_wr(hor)
_wr(ACS_URCORNER)
_move(bottom, left)
_wr(ACS_LLCORNER)
_wr(hor)
_wr(ACS_LRCORNER)
top += 1
while top < bottom:
_move(top, left)
_wr(ACS_VLINE)
_move(top, left + width - 1)
_wr(ACS_VLINE)
top += 1
class error(Exception):
pass
class Window:
def __init__(self, lines, cols, y, x):
self.lines = lines
self.cols = cols
self.y = y
self.x = x
self.bkgattr = A_NORMAL
self.keybuf = None
self.keyi = 0
def _goto(self, row, col):
_move(self.y + row, self.x + col)
def move(self, y, x):
# Maybe need to cache coords?
self._goto(y, x)
def getmaxyx(self):
return (self.lines, self.cols)
def addstr(self, y, x, str, attr=A_NORMAL):
self._goto(y, x)
# TODO: Should be "ORed"
if attr == A_NORMAL:
attr = self.bkgattr
if attr != A_NORMAL:
_wr(ATTRMAP[attr])
_wr(str)
_wr(ATTRMAP[A_NORMAL])
else:
_wr(str)
def addnstr(self, y, x, str, n, attr=A_NORMAL):
self.addstr(y, x, str[:n], attr)
def addch(self, y, x, ch, attr=A_NORMAL):
if isinstance(ch, int):
ch = chr(ch)
self.addstr(y, x, ch, attr)
def attron(self, attr):
pass
def attroff(self, attr):
pass
def attrset(self, attr):
pass
def bkgdset(self, ch, attr=A_NORMAL):
self.bkgattr = attr
def erase(self):
for i in range(self.lines):
self._goto(i, 0)
_clear_num_pos(self.cols)
def border(self):
_draw_box(self.x, self.y, self.cols, self.lines)
def hline(self, y, x, ch, n):
self.move(y, x)
_wr(ch * n)
def vline(self, y, x, ch, n):
for i in range(n):
self.move(y + i, x)
_wr(ch)
def refresh(self):
pass
def redrawwin(self):
pass
def keypad(self, yes):
pass
def timeout(self, delay):
assert delay < 0
def getch(self):
if self.keybuf and self.keyi < len(self.keybuf):
c = self.keybuf[self.keyi]
self.keyi += 1
return c
key = os.read(0, 32)
if key[0] != 0x1b:
self.keybuf = key
self.keyi = 1
key = key[0]
else:
if key in KEYMAP:
key = KEYMAP[key]
else:
assert False, repr(key)
return key
SCREEN = Window(24, 80, 0, 0)
org_termios = None
def wrapper(func):
global org_termios
org_termios = termios.tcgetattr(0)
res = func()
endwin()
return res
def initscr():
global org_termios
org_termios = termios.tcgetattr(0)
return SCREEN
def doupdate():
pass
def endwin():
global org_termios
_wr(b"\r")
termios.tcsetattr(0, termios.TCSANOW, org_termios)
def raw():
tty.setraw(0)
def cbreak():
#TODO
pass
def nocbreak():
#TODO
pass
def echo():
#TODO
pass
def noecho():
#TODO
pass
def meta(yes):
#TODO
pass
def mousemask(mask):
# Mouse reporting - X10 compatbility mode
_wr(b"\x1b[?9h")
def has_colors():
return False
def can_change_color():
return False
def curs_set(visibility):
if visibility > 0:
_wr(b"\x1b[?25h")
else:
_wr(b"\x1b[?25l")
def beep():
_wr(b"\x07")
def newwin(lines, cols, y, x):
#print("newwin(%d, %d, %d, %d)" % (lines, cols, y, x))
return Window(lines, cols, y, x)