| 1 | # Copyright (c) Twisted Matrix Laboratories. |
|---|
| 2 | # See LICENSE for details. |
|---|
| 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#tkFont.Font(family = 'Courier', size = 10) |
|---|
| 16 | fontWidth, fontHeight = None,None#max(map(ttyFont.measure, string.letters+string.digits)), int(ttyFont.metrics()['linespace']) |
|---|
| 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 | #for (a, b) in colorMap.items(): |
|---|
| 57 | # self.canvas.tag_config(a, foreground=b) |
|---|
| 58 | # self.canvas.tag_config('b'+a, background=b) |
|---|
| 59 | #self.canvas.tag_config('underline', underline=1) |
|---|
| 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 | #print self.x,self.y,repr(data) |
|---|
| 93 | #if len(data)>5: raw_input() |
|---|
| 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: # bell |
|---|
| 109 | self.bell() |
|---|
| 110 | elif b == 8: # BS |
|---|
| 111 | if self.x: |
|---|
| 112 | self.x-=1 |
|---|
| 113 | elif b == 9: # TAB |
|---|
| 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 | #if ';' in cursor and cursor[-1]!='H': |
|---|
| 157 | # end = cursor[-1] |
|---|
| 158 | # parts = cursor[:-1].split(';') |
|---|
| 159 | # [self.parseCursor(x+end) for x in parts] |
|---|
| 160 | # return |
|---|
| 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') |
|---|