--- a/tuikit/driver/curses.py Sat Mar 15 11:05:12 2014 +0100
+++ b/tuikit/driver/curses.py Sat Mar 15 14:43:47 2014 +0100
@@ -8,17 +8,18 @@
class CursesDriver(Driver):
+
key_codes = (
(0x09, 'tab' ),
(0x0a, 'enter' ),
(0x7f, 'backspace' ),
(0x1b, 'escape' ),
- (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
- (0x1b,0x5b,0x4d, 'mouse' ),
+ (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
+ (0x1b, 0x5b, 0x4d, 'mouse' ),
)
# http://en.wikipedia.org/wiki/ANSI_escape_code
@@ -44,41 +45,41 @@
(0x44, 1, 'left' ),
(0x46, 1, 'end' ), # xterm
(0x48, 1, 'home' ), # xterm
- (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
+ (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 = {
- 'black' : (curses.COLOR_BLACK, 0),
- 'blue' : (curses.COLOR_BLUE, 0),
- 'green' : (curses.COLOR_GREEN, 0),
- 'cyan' : (curses.COLOR_CYAN, 0),
- 'red' : (curses.COLOR_RED, 0),
- 'magenta' : (curses.COLOR_MAGENTA,0),
- 'brown' : (curses.COLOR_YELLOW, 0),
- 'lightgray' : (curses.COLOR_WHITE, 0),
- 'gray' : (curses.COLOR_BLACK, curses.A_BOLD),
- 'lightblue' : (curses.COLOR_BLUE, curses.A_BOLD),
- 'lightgreen' : (curses.COLOR_GREEN, curses.A_BOLD),
- 'lightcyan' : (curses.COLOR_CYAN, curses.A_BOLD),
- 'lightred' : (curses.COLOR_RED, curses.A_BOLD),
- 'lightmagenta' : (curses.COLOR_MAGENTA,curses.A_BOLD),
- 'yellow' : (curses.COLOR_YELLOW, curses.A_BOLD),
- 'white' : (curses.COLOR_WHITE, curses.A_BOLD),
+ 'default': (-1, 0),
+ 'black': (curses.COLOR_BLACK, 0),
+ 'blue': (curses.COLOR_BLUE, 0),
+ 'green': (curses.COLOR_GREEN, 0),
+ 'cyan': (curses.COLOR_CYAN, 0),
+ 'red': (curses.COLOR_RED, 0),
+ 'magenta': (curses.COLOR_MAGENTA, 0),
+ 'brown': (curses.COLOR_YELLOW, 0),
+ 'lightgray': (curses.COLOR_WHITE, 0),
+ 'gray': (curses.COLOR_BLACK, curses.A_BOLD),
+ 'lightblue': (curses.COLOR_BLUE, curses.A_BOLD),
+ 'lightgreen': (curses.COLOR_GREEN, curses.A_BOLD),
+ 'lightcyan': (curses.COLOR_CYAN, curses.A_BOLD),
+ 'lightred': (curses.COLOR_RED, curses.A_BOLD),
+ 'lightmagenta': (curses.COLOR_MAGENTA, curses.A_BOLD),
+ 'yellow': (curses.COLOR_YELLOW, curses.A_BOLD),
+ 'white': (curses.COLOR_WHITE, curses.A_BOLD),
}
attr_map = {
- 'bold' : curses.A_BOLD,
- 'underline' : curses.A_UNDERLINE,
- 'standout' : curses.A_STANDOUT, # inverse bg/fg
- 'blink' : curses.A_BLINK,
+ 'bold': curses.A_BOLD,
+ 'underline': curses.A_UNDERLINE,
+ 'standout': curses.A_STANDOUT, # inverse bg/fg
+ 'blink': curses.A_BLINK,
}
def __init__(self):
- '''Set driver attributes to default values.'''
Driver.__init__(self)
self.log = logging.getLogger('tuikit')
self.stdscr = None
@@ -97,6 +98,7 @@
"""Initialize curses"""
self.stdscr = curses.initscr()
curses.start_color()
+ curses.use_default_colors()
curses.noecho()
curses.cbreak()
self.stdscr.keypad(0)
@@ -114,80 +116,10 @@
curses.nocbreak()
curses.endwin()
- def __enter__(self):
- self.init()
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self.close()
-
## drawing ##
- def draw(self, buffer, x=0, y=0):
- for bufy in range(buffer.size.h):
- for bufx in range(buffer.size.w):
- self.putch(x + bufx, y + bufy,
- buffer.get(bufx, bufy)[0])
-
-
- ## colors, attributes ##
-
- def _parsecolor(self, name):
- name = name.lower().strip()
- return self.color_map[name]
-
- def _getcolorpair(self, fg, bg):
- pair = (fg, bg)
- if pair in self.colorpairs:
- return self.colorpairs[pair]
- num = len(self.colorpairs) + 1
- curses.init_pair(num, fg, bg)
- self.colorpairs[pair] = num
- return num
-
- def _parseattrs(self, attrs):
- res = 0
- for a in attrs:
- a = a.lower().strip()
- res = res | self.attr_map[a]
- return res
-
- def defcolor(self, name, desc):
- """Define color name."""
- parts = desc.split(',')
- fgbg = parts[0].split(' on ', 1)
- fg = fgbg[0]
- bg = fgbg[1:] and fgbg[1] or 'black'
- attrs = parts[1:]
- fg, fgattr = self._parsecolor(fg)
- bg, _bgattr = self._parsecolor(bg)
- col = self._getcolorpair(fg, bg)
- attr = self._parseattrs(attrs)
- self.colors[name] = curses.color_pair(col) | fgattr | attr
-
- def setcolor(self, name):
- """Set defined color. Previous color is forgotten."""
- self.stdscr.attrset(self.colors[name])
-
- def pushcolor(self, name):
- # add prefix if such color is available
- if len(self.colorprefix):
- prefixname = self.colorprefix[-1] + name
- if prefixname in self.colors:
- name = prefixname
- attr = self.colors[name]
- self.stdscr.attrset(attr)
- self.colorstack.append(attr)
-
- def popcolor(self):
- self.colorstack.pop()
- if len(self.colorstack):
- attr = self.colorstack[-1]
- else:
- attr = 0
- self.stdscr.attrset(attr)
-
-
- ## drawing ##
+ def erase(self):
+ self.stdscr.erase()
def putch(self, x, y, c):
if not self.clipstack.test(x, y):
@@ -200,10 +132,14 @@
except curses.error:
pass
- def erase(self):
- self.stdscr.erase()
+ def draw(self, buffer, x=0, y=0):
+ for bufy in range(buffer.size.h):
+ for bufx in range(buffer.size.w):
+ char, attr_desc = buffer.get(bufx, bufy)
+ self.setattr(attr_desc)
+ self.putch(x + bufx, y + bufy, char)
- def commit(self):
+ def flush(self):
if self.cursor:
self.stdscr.move(*self.cursor)
curses.curs_set(True)
@@ -211,6 +147,35 @@
curses.curs_set(False)
self.stdscr.refresh()
+ ## colors, attributes ##
+
+ def setattr(self, attr_desc):
+ """Set attribute to be used for subsequent draw operations."""
+ attr = self.colors.get(attr_desc, None)
+ if attr is None:
+ # first encountered `attr_desc`, initialize
+ fg, bg, attrs = self._parse_attr_desc(attr_desc)
+ fgcol, fgattr = self.color_map[fg]
+ bgcol, _bgattr = self.color_map[bg]
+ colpair = self._getcolorpair(fgcol, bgcol)
+ attr = curses.color_pair(colpair) | self._parseattrs(attrs) | fgattr
+ self.colors[attr_desc] = attr
+ self.stdscr.attrset(attr)
+
+ def _getcolorpair(self, fgcol, bgcol):
+ pair = (fgcol, bgcol)
+ if pair in self.colorpairs:
+ return self.colorpairs[pair]
+ num = len(self.colorpairs) + 1
+ curses.init_pair(num, fgcol, bgcol)
+ self.colorpairs[pair] = num
+ return num
+
+ def _parseattrs(self, attrs):
+ res = 0
+ for a in attrs:
+ res = res | self.attr_map[a]
+ return res
## cursor ##
@@ -224,10 +189,52 @@
curses.curs_set(False)
self.cursor = None
+ ## input, events ##
- ## input ##
+ def getevents(self, timeout=None):
+ """Process input, return list of events.
+
+ timeout -- float, in seconds (None=infinite)
+
+ Returns:
+ [('event', param1, ...), ...]
+
+ """
+ # empty queue -> fill
+ if len(self.inputqueue) == 0:
+ if timeout is not None:
+ timeout = math.ceil(timeout * 10)
+ self._inputqueue_fill(timeout)
+
+ res = []
+ while len(self.inputqueue):
+ c = self._inputqueue_get()
+
+ if c == curses.KEY_MOUSE:
+ res += self._process_mouse()
- def inputqueue_fill(self, timeout=None):
+ elif c == curses.KEY_RESIZE:
+ self.size.h, self.size.w = self.stdscr.getmaxyx()
+ res.append(('resize',))
+
+ elif curses.ascii.isctrl(c):
+ self._inputqueue_unget(c)
+ res += self._process_control_chars()
+
+ elif 192 <= c <= 255:
+ self._inputqueue_unget(c)
+ res += self._process_utf8_chars()
+
+ elif curses.ascii.isprint(c):
+ res += [('keypress', None, str(chr(c)))]
+
+ else:
+ self._inputqueue_unget(c)
+ res += self._process_control_chars()
+
+ return res
+
+ def _inputqueue_fill(self, timeout=None):
"""Wait for curses input, add it to inputqueue.
timeout -- int, tenths of second (None=infinite)
@@ -259,12 +266,7 @@
self.stdscr.nodelay(0)
-
- def inputqueue_top(self, num=0):
- return self.inputqueue[-1-num]
-
-
- def inputqueue_get(self):
+ def _inputqueue_get(self):
c = None
try:
c = self.inputqueue.pop()
@@ -272,64 +274,20 @@
pass
return c
-
- def inputqueue_get_wait(self):
+ def _inputqueue_get_wait(self):
c = None
while c is None:
try:
c = self.inputqueue.pop()
except IndexError:
curses.napms(25)
- self.inputqueue_fill(0)
+ self._inputqueue_fill(0)
return c
-
- def inputqueue_unget(self, c):
+ def _inputqueue_unget(self, c):
self.inputqueue.append(c)
-
- def getevents(self, timeout=None):
- '''Process input, return list of events.
-
- timeout -- float, in seconds
-
- '''
- # empty queue -> fill
- if len(self.inputqueue) == 0:
- if timeout is not None:
- timeout = math.ceil(timeout * 10)
- self.inputqueue_fill(timeout)
-
- res = []
- while len(self.inputqueue):
- c = self.inputqueue_get()
-
- if c == curses.KEY_MOUSE:
- res += self.process_mouse()
-
- elif c == curses.KEY_RESIZE:
- self.size.h, self.size.w = self.stdscr.getmaxyx()
- res.append(('resize',))
-
- elif curses.ascii.isctrl(c):
- self.inputqueue_unget(c)
- res += self.process_control_chars()
-
- elif c >= 192 and c <= 255:
- self.inputqueue_unget(c)
- res += self.process_utf8_chars()
-
- elif curses.ascii.isprint(c):
- res += [('keypress', None, str(chr(c)))]
-
- else:
- self.inputqueue_unget(c)
- res += self.process_control_chars()
-
- return res
-
-
- def process_mouse(self):
+ def _process_mouse(self):
try:
_id, x, y, _z, bstate = curses.getmouse()
except curses.error:
@@ -373,12 +331,11 @@
return out
-
- def process_utf8_chars(self):
+ def _process_utf8_chars(self):
#FIXME read exact number of chars as defined by utf-8
utf = []
while len(utf) <= 6:
- c = self.inputqueue_get_wait()
+ c = self._inputqueue_get_wait()
utf.append(c)
try:
uni = str(bytes(utf), 'utf-8')
@@ -387,15 +344,14 @@
continue
raise Exception('Invalid UTF-8 sequence: %r' % utf)
-
- def process_control_chars(self):
+ def _process_control_chars(self):
codes = self.key_codes
matchingcodes = []
match = None
consumed = []
# consume next char, filter out matching codes
- c = self.inputqueue_get_wait()
+ c = self._inputqueue_get_wait()
consumed.append(c)
while True:
@@ -416,9 +372,9 @@
# match found and some sequencies still match -> continue
if len(matchingcodes) > 0:
if len(self.inputqueue) == 0:
- self.inputqueue_fill(1)
+ self._inputqueue_fill(1)
- c = self.inputqueue_get()
+ c = self._inputqueue_get()
if c:
consumed.append(c)
codes = matchingcodes
@@ -431,28 +387,27 @@
# compare match to consumed, return unused chars
l = len(match) - 1
while len(consumed) > l:
- self.inputqueue_unget(consumed[-1])
+ self._inputqueue_unget(consumed[-1])
del consumed[-1]
keyname = match[-1]
if match is None:
self.log.debug('Unknown control sequence: %s',
- ','.join(['0x%x'%x for x in consumed]))
+ ','.join(['0x%x' % x for x in consumed]))
return [('keypress', 'Unknown', None)]
if keyname == 'mouse':
- return self.process_xterm_mouse()
+ return self._process_xterm_mouse()
if keyname == 'CSI':
- return self.process_control_sequence()
+ return self._process_control_sequence()
return [('keypress', keyname, None)]
-
- def process_xterm_mouse(self):
- t = self.inputqueue_get_wait()
- x = self.inputqueue_get_wait() - 0x21
- y = self.inputqueue_get_wait() - 0x21
+ def _process_xterm_mouse(self):
+ t = self._inputqueue_get_wait()
+ x = self._inputqueue_get_wait() - 0x21
+ y = self._inputqueue_get_wait() - 0x21
out = []
@@ -485,10 +440,10 @@
return out
- def process_control_sequence(self):
+ def _process_control_sequence(self):
codes = self.csi_codes
debug_seq = [0x1b, 0x5b]
- c = self.inputqueue_get_wait()
+ c = self._inputqueue_get_wait()
debug_seq.append(c)
# numeric parameters?
@@ -496,7 +451,7 @@
if chr(c).isdigit():
params.append(chr(c))
while True:
- c = self.inputqueue_get_wait()
+ c = self._inputqueue_get_wait()
debug_seq.append(c)
if chr(c).isdigit():
params[-1] += chr(c)
@@ -530,7 +485,7 @@
break
else:
# more than one matching -> continue loop
- c = self.inputqueue_get_wait()
+ c = self._inputqueue_get_wait()
debug_seq.append(c)
# filter codes using first parameter
@@ -556,4 +511,3 @@
mod = params[1] - 1
return [('keypress', keyname, None, mod)]
-