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