# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. # """Module to emulate a VT100 terminal in Tkinter. Maintainer: Paul Swartz """ import string import tkinter as Tkinter import tkinter.font as tkFont from . import ansi ttyFont = None # tkFont.Font(family = 'Courier', size = 10) fontWidth, fontHeight = ( None, None, ) # max(map(ttyFont.measure, string.letters+string.digits)), int(ttyFont.metrics()['linespace']) colorKeys = ( "b", "r", "g", "y", "l", "m", "c", "w", "B", "R", "G", "Y", "L", "M", "C", "W", ) colorMap = { "b": "#000000", "r": "#c40000", "g": "#00c400", "y": "#c4c400", "l": "#000080", "m": "#c400c4", "c": "#00c4c4", "w": "#c4c4c4", "B": "#626262", "R": "#ff0000", "G": "#00ff00", "Y": "#ffff00", "L": "#0000ff", "M": "#ff00ff", "C": "#00ffff", "W": "#ffffff", } class VT100Frame(Tkinter.Frame): def __init__(self, *args, **kw): global ttyFont, fontHeight, fontWidth ttyFont = tkFont.Font(family="Courier", size=10) fontWidth = max(map(ttyFont.measure, string.ascii_letters + string.digits)) fontHeight = int(ttyFont.metrics()["linespace"]) self.width = kw.get("width", 80) self.height = kw.get("height", 25) self.callback = kw["callback"] del kw["callback"] kw["width"] = w = fontWidth * self.width kw["height"] = h = fontHeight * self.height Tkinter.Frame.__init__(self, *args, **kw) self.canvas = Tkinter.Canvas(bg="#000000", width=w, height=h) self.canvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1) self.canvas.bind("", self.keyPressed) self.canvas.bind("<1>", lambda x: "break") self.canvas.bind("", self.upPressed) self.canvas.bind("", self.downPressed) self.canvas.bind("", self.leftPressed) self.canvas.bind("", self.rightPressed) self.canvas.focus() self.ansiParser = ansi.AnsiParser(ansi.ColorText.WHITE, ansi.ColorText.BLACK) self.ansiParser.writeString = self.writeString self.ansiParser.parseCursor = self.parseCursor self.ansiParser.parseErase = self.parseErase # for (a, b) in colorMap.items(): # self.canvas.tag_config(a, foreground=b) # self.canvas.tag_config('b'+a, background=b) # self.canvas.tag_config('underline', underline=1) self.x = 0 self.y = 0 self.cursor = self.canvas.create_rectangle( 0, 0, fontWidth - 1, fontHeight - 1, fill="green", outline="green" ) def _delete(self, sx, sy, ex, ey): csx = sx * fontWidth + 1 csy = sy * fontHeight + 1 cex = ex * fontWidth + 3 cey = ey * fontHeight + 3 items = self.canvas.find_overlapping(csx, csy, cex, cey) for item in items: self.canvas.delete(item) def _write(self, ch, fg, bg): if self.x == self.width: self.x = 0 self.y += 1 if self.y == self.height: [self.canvas.move(x, 0, -fontHeight) for x in self.canvas.find_all()] self.y -= 1 canvasX = self.x * fontWidth + 1 canvasY = self.y * fontHeight + 1 items = self.canvas.find_overlapping(canvasX, canvasY, canvasX + 2, canvasY + 2) if items: [self.canvas.delete(item) for item in items] if bg: self.canvas.create_rectangle( canvasX, canvasY, canvasX + fontWidth - 1, canvasY + fontHeight - 1, fill=bg, outline=bg, ) self.canvas.create_text( canvasX, canvasY, anchor=Tkinter.NW, font=ttyFont, text=ch, fill=fg ) self.x += 1 def write(self, data): self.ansiParser.parseString(data) self.canvas.delete(self.cursor) canvasX = self.x * fontWidth + 1 canvasY = self.y * fontHeight + 1 self.cursor = self.canvas.create_rectangle( canvasX, canvasY, canvasX + fontWidth - 1, canvasY + fontHeight - 1, fill="green", outline="green", ) self.canvas.lower(self.cursor) def writeString(self, i): if not i.display: return fg = colorMap[i.fg] bg = i.bg != "b" and colorMap[i.bg] for ch in i.text: b = ord(ch) if b == 7: # bell self.bell() elif b == 8: # BS if self.x: self.x -= 1 elif b == 9: # TAB [self._write(" ", fg, bg) for index in range(8)] elif b == 10: if self.y == self.height - 1: self._delete(0, 0, self.width, 0) [ self.canvas.move(x, 0, -fontHeight) for x in self.canvas.find_all() ] else: self.y += 1 elif b == 13: self.x = 0 elif 32 <= b < 127: self._write(ch, fg, bg) def parseErase(self, erase): if ";" in erase: end = erase[-1] parts = erase[:-1].split(";") [self.parseErase(x + end) for x in parts] return start = 0 x, y = self.x, self.y if len(erase) > 1: start = int(erase[:-1]) if erase[-1] == "J": if start == 0: self._delete(x, y, self.width, self.height) else: self._delete(0, 0, self.width, self.height) self.x = 0 self.y = 0 elif erase[-1] == "K": if start == 0: self._delete(x, y, self.width, y) elif start == 1: self._delete(0, y, x, y) self.x = 0 else: self._delete(0, y, self.width, y) self.x = 0 elif erase[-1] == "P": self._delete(x, y, x + start, y) def parseCursor(self, cursor): # if ';' in cursor and cursor[-1]!='H': # end = cursor[-1] # parts = cursor[:-1].split(';') # [self.parseCursor(x+end) for x in parts] # return start = 1 if len(cursor) > 1 and cursor[-1] != "H": start = int(cursor[:-1]) if cursor[-1] == "C": self.x += start elif cursor[-1] == "D": self.x -= start elif cursor[-1] == "d": self.y = start - 1 elif cursor[-1] == "G": self.x = start - 1 elif cursor[-1] == "H": if len(cursor) > 1: y, x = map(int, cursor[:-1].split(";")) y -= 1 x -= 1 else: x, y = 0, 0 self.x = x self.y = y def keyPressed(self, event): if self.callback and event.char: self.callback(event.char) return "break" def upPressed(self, event): self.callback("\x1bOA") def downPressed(self, event): self.callback("\x1bOB") def rightPressed(self, event): self.callback("\x1bOC") def leftPressed(self, event): self.callback("\x1bOD")