tkvt100.py 6.9 KB

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