| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
"""Module to emulate a VT100 terminal in Tkinter. |
|---|
| 7 |
|
|---|
| 8 |
Maintainer: Paul Swartz |
|---|
| 9 |
""" |
|---|
| 10 |
|
|---|
| 11 |
import Tkinter, tkFont |
|---|
| 12 |
import ansi |
|---|
| 13 |
import string |
|---|
| 14 |
|
|---|
| 15 |
ttyFont = None |
|---|
| 16 |
fontWidth, fontHeight = None,None |
|---|
| 17 |
|
|---|
| 18 |
colorKeys = ( |
|---|
| 19 |
'b', 'r', 'g', 'y', 'l', 'm', 'c', 'w', |
|---|
| 20 |
'B', 'R', 'G', 'Y', 'L', 'M', 'C', 'W' |
|---|
| 21 |
) |
|---|
| 22 |
|
|---|
| 23 |
colorMap = { |
|---|
| 24 |
'b': '#000000', 'r': '#c40000', 'g': '#00c400', 'y': '#c4c400', |
|---|
| 25 |
'l': '#000080', 'm': '#c400c4', 'c': '#00c4c4', 'w': '#c4c4c4', |
|---|
| 26 |
'B': '#626262', 'R': '#ff0000', 'G': '#00ff00', 'Y': '#ffff00', |
|---|
| 27 |
'L': '#0000ff', 'M': '#ff00ff', 'C': '#00ffff', 'W': '#ffffff', |
|---|
| 28 |
} |
|---|
| 29 |
|
|---|
| 30 |
class VT100Frame(Tkinter.Frame): |
|---|
| 31 |
def __init__(self, *args, **kw): |
|---|
| 32 |
global ttyFont, fontHeight, fontWidth |
|---|
| 33 |
ttyFont = tkFont.Font(family = 'Courier', size = 10) |
|---|
| 34 |
fontWidth, fontHeight = max(map(ttyFont.measure, string.letters+string.digits)), int(ttyFont.metrics()['linespace']) |
|---|
| 35 |
self.width = kw.get('width', 80) |
|---|
| 36 |
self.height = kw.get('height', 25) |
|---|
| 37 |
self.callback = kw['callback'] |
|---|
| 38 |
del kw['callback'] |
|---|
| 39 |
kw['width'] = w = fontWidth * self.width |
|---|
| 40 |
kw['height'] = h = fontHeight * self.height |
|---|
| 41 |
Tkinter.Frame.__init__(self, *args, **kw) |
|---|
| 42 |
self.canvas = Tkinter.Canvas(bg='#000000', width=w, height=h) |
|---|
| 43 |
self.canvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1) |
|---|
| 44 |
self.canvas.bind('<Key>', self.keyPressed) |
|---|
| 45 |
self.canvas.bind('<1>', lambda x: 'break') |
|---|
| 46 |
self.canvas.bind('<Up>', self.upPressed) |
|---|
| 47 |
self.canvas.bind('<Down>', self.downPressed) |
|---|
| 48 |
self.canvas.bind('<Left>', self.leftPressed) |
|---|
| 49 |
self.canvas.bind('<Right>', self.rightPressed) |
|---|
| 50 |
self.canvas.focus() |
|---|
| 51 |
|
|---|
| 52 |
self.ansiParser = ansi.AnsiParser(ansi.ColorText.WHITE, ansi.ColorText.BLACK) |
|---|
| 53 |
self.ansiParser.writeString = self.writeString |
|---|
| 54 |
self.ansiParser.parseCursor = self.parseCursor |
|---|
| 55 |
self.ansiParser.parseErase = self.parseErase |
|---|
| 56 |
|
|---|
| 57 |
|
|---|
| 58 |
|
|---|
| 59 |
|
|---|
| 60 |
|
|---|
| 61 |
self.x = 0 |
|---|
| 62 |
self.y = 0 |
|---|
| 63 |
self.cursor = self.canvas.create_rectangle(0,0,fontWidth-1,fontHeight-1,fill='green',outline='green') |
|---|
| 64 |
|
|---|
| 65 |
def _delete(self, sx, sy, ex, ey): |
|---|
| 66 |
csx = sx*fontWidth + 1 |
|---|
| 67 |
csy = sy*fontHeight + 1 |
|---|
| 68 |
cex = ex*fontWidth + 3 |
|---|
| 69 |
cey = ey*fontHeight + 3 |
|---|
| 70 |
items = self.canvas.find_overlapping(csx,csy, cex,cey) |
|---|
| 71 |
for item in items: |
|---|
| 72 |
self.canvas.delete(item) |
|---|
| 73 |
|
|---|
| 74 |
def _write(self, ch, fg, bg): |
|---|
| 75 |
if self.x == self.width: |
|---|
| 76 |
self.x = 0 |
|---|
| 77 |
self.y+=1 |
|---|
| 78 |
if self.y == self.height: |
|---|
| 79 |
[self.canvas.move(x,0,-fontHeight) for x in self.canvas.find_all()] |
|---|
| 80 |
self.y-=1 |
|---|
| 81 |
canvasX = self.x*fontWidth + 1 |
|---|
| 82 |
canvasY = self.y*fontHeight + 1 |
|---|
| 83 |
items = self.canvas.find_overlapping(canvasX, canvasY, canvasX+2, canvasY+2) |
|---|
| 84 |
if items: |
|---|
| 85 |
[self.canvas.delete(item) for item in items] |
|---|
| 86 |
if bg: |
|---|
| 87 |
self.canvas.create_rectangle(canvasX, canvasY, canvasX+fontWidth-1, canvasY+fontHeight-1, fill=bg, outline=bg) |
|---|
| 88 |
self.canvas.create_text(canvasX, canvasY, anchor=Tkinter.NW, font=ttyFont, text=ch, fill=fg) |
|---|
| 89 |
self.x+=1 |
|---|
| 90 |
|
|---|
| 91 |
def write(self, data): |
|---|
| 92 |
|
|---|
| 93 |
|
|---|
| 94 |
self.ansiParser.parseString(data) |
|---|
| 95 |
self.canvas.delete(self.cursor) |
|---|
| 96 |
canvasX = self.x*fontWidth + 1 |
|---|
| 97 |
canvasY = self.y*fontHeight + 1 |
|---|
| 98 |
self.cursor = self.canvas.create_rectangle(canvasX,canvasY,canvasX+fontWidth-1,canvasY+fontHeight-1, fill='green', outline='green') |
|---|
| 99 |
self.canvas.lower(self.cursor) |
|---|
| 100 |
|
|---|
| 101 |
def writeString(self, i): |
|---|
| 102 |
if not i.display: |
|---|
| 103 |
return |
|---|
| 104 |
fg = colorMap[i.fg] |
|---|
| 105 |
bg = i.bg != 'b' and colorMap[i.bg] |
|---|
| 106 |
for ch in i.text: |
|---|
| 107 |
b = ord(ch) |
|---|
| 108 |
if b == 7: |
|---|
| 109 |
self.bell() |
|---|
| 110 |
elif b == 8: |
|---|
| 111 |
if self.x: |
|---|
| 112 |
self.x-=1 |
|---|
| 113 |
elif b == 9: |
|---|
| 114 |
[self._write(' ',fg,bg) for i in range(8)] |
|---|
| 115 |
elif b == 10: |
|---|
| 116 |
if self.y == self.height-1: |
|---|
| 117 |
self._delete(0,0,self.width,0) |
|---|
| 118 |
[self.canvas.move(x,0,-fontHeight) for x in self.canvas.find_all()] |
|---|
| 119 |
else: |
|---|
| 120 |
self.y+=1 |
|---|
| 121 |
elif b == 13: |
|---|
| 122 |
self.x = 0 |
|---|
| 123 |
elif 32 <= b < 127: |
|---|
| 124 |
self._write(ch, fg, bg) |
|---|
| 125 |
|
|---|
| 126 |
def parseErase(self, erase): |
|---|
| 127 |
if ';' in erase: |
|---|
| 128 |
end = erase[-1] |
|---|
| 129 |
parts = erase[:-1].split(';') |
|---|
| 130 |
[self.parseErase(x+end) for x in parts] |
|---|
| 131 |
return |
|---|
| 132 |
start = 0 |
|---|
| 133 |
x,y = self.x, self.y |
|---|
| 134 |
if len(erase) > 1: |
|---|
| 135 |
start = int(erase[:-1]) |
|---|
| 136 |
if erase[-1] == 'J': |
|---|
| 137 |
if start == 0: |
|---|
| 138 |
self._delete(x,y,self.width,self.height) |
|---|
| 139 |
else: |
|---|
| 140 |
self._delete(0,0,self.width,self.height) |
|---|
| 141 |
self.x = 0 |
|---|
| 142 |
self.y = 0 |
|---|
| 143 |
elif erase[-1] == 'K': |
|---|
| 144 |
if start == 0: |
|---|
| 145 |
self._delete(x,y,self.width,y) |
|---|
| 146 |
elif start == 1: |
|---|
| 147 |
self._delete(0,y,x,y) |
|---|
| 148 |
self.x = 0 |
|---|
| 149 |
else: |
|---|
| 150 |
self._delete(0,y,self.width,y) |
|---|
| 151 |
self.x = 0 |
|---|
| 152 |
elif erase[-1] == 'P': |
|---|
| 153 |
self._delete(x,y,x+start,y) |
|---|
| 154 |
|
|---|
| 155 |
def parseCursor(self, cursor): |
|---|
| 156 |
|
|---|
| 157 |
|
|---|
| 158 |
|
|---|
| 159 |
|
|---|
| 160 |
|
|---|
| 161 |
start = 1 |
|---|
| 162 |
if len(cursor) > 1 and cursor[-1]!='H': |
|---|
| 163 |
start = int(cursor[:-1]) |
|---|
| 164 |
if cursor[-1] == 'C': |
|---|
| 165 |
self.x+=start |
|---|
| 166 |
elif cursor[-1] == 'D': |
|---|
| 167 |
self.x-=start |
|---|
| 168 |
elif cursor[-1]=='d': |
|---|
| 169 |
self.y=start-1 |
|---|
| 170 |
elif cursor[-1]=='G': |
|---|
| 171 |
self.x=start-1 |
|---|
| 172 |
elif cursor[-1]=='H': |
|---|
| 173 |
if len(cursor)>1: |
|---|
| 174 |
y,x = map(int, cursor[:-1].split(';')) |
|---|
| 175 |
y-=1 |
|---|
| 176 |
x-=1 |
|---|
| 177 |
else: |
|---|
| 178 |
x,y=0,0 |
|---|
| 179 |
self.x = x |
|---|
| 180 |
self.y = y |
|---|
| 181 |
|
|---|
| 182 |
def keyPressed(self, event): |
|---|
| 183 |
if self.callback and event.char: |
|---|
| 184 |
self.callback(event.char) |
|---|
| 185 |
return 'break' |
|---|
| 186 |
|
|---|
| 187 |
def upPressed(self, event): |
|---|
| 188 |
self.callback('\x1bOA') |
|---|
| 189 |
|
|---|
| 190 |
def downPressed(self, event): |
|---|
| 191 |
self.callback('\x1bOB') |
|---|
| 192 |
|
|---|
| 193 |
def rightPressed(self, event): |
|---|
| 194 |
self.callback('\x1bOC') |
|---|
| 195 |
|
|---|
| 196 |
def leftPressed(self, event): |
|---|
| 197 |
self.callback('\x1bOD') |
|---|