terminal.py - A Pure Python Terminal Emulator

About This Module

This crux of this module is the Terminal class which is a pure-Python implementation of a quintessential Unix-style terminal emulator. It actually does its best to emulate an xterm. This means it supports the majority of the relevant portions of ECMA-48. This includes support for emulating varous VT-* terminal types as well as the "linux" terminal type.

The Terminal class's emulation support is not complete but it should suffice for most terminal emulation needs. If additional support for certain escape sequences or modes are required please feel free to provide a patch or to simply ask for something to be added.

Note that Terminal was written from scratch in order to be as fast as possible. Comments have been placed where different implementations/development patterns have been tried and ultimately failed to provide speed improvements. Any and all suggestions or patches to improve speed (or emulation support) are welcome!

Supported Emulation Types

Without any special mode settings or parameters, Terminal should be able to support most applications under the following terminal types (e.g. "export TERM=<terminal type>"):

  • xterm (the most important one)
  • ECMA-48/ANSI X3.64
  • Nearly all the VT-* types: VT-52, VT-100, VT-220, VT-320, VT-420, and VT-520
  • Linux console ("linux")

What Terminal Doesn't Do

The Terminal class is meant to emulate the display portion of a given terminal. It does not translate keystrokes into escape sequences or special control codes--you'll have to take care of that in your application (or at the client-side like Gate One). It does, however, keep track of many keystroke-specific modes of operation such as Application Cursor Keys and the G0 and G1 charset modes with callbacks that can be used to notify your application when something changes.

Special Considerations

Many methods inside Terminal start with an underscore. This was done to indicate that such methods shouldn't be called directly (from a program that imported the module). If it was thought that a situation might arise where a method could be used externally by a controlling program, the underscore was omitted.

Asynchronous Use

To support asynchronous usage (and make everything faster), Terminal was written to support extensive callbacks that are called when certain events are encountered. Here are the events and their callbacks:

Callback Called when...
Terminal.CALLBACK_SCROLL_UP The terminal is scrolled up (back).
Terminal.CALLBACK_CHANGED The screen is changed/updated.
Terminal.CALLBACK_CURSOR_POS The cursor position changes.
Terminal.CALLBACK_DSR A Device Status Report (DSR) is requested (via the DSR escape sequence).
Terminal.CALLBACK_TITLE The terminal title changes (xterm-style)
Terminal.CALLBACK_BELL The bell character (^G) is encountered.
Terminal.CALLBACK_OPT The special optional escape sequence is encountered.
Terminal.CALLBACK_MODE The terminal mode setting changes (e.g. use alternate screen buffer).

Note that Terminal.CALLBACK_DSR is special in that it in most cases it will be called with arguments. See the code for examples of how and when this happens.

Also, in most cases it is unwise to override Terminal.CALLBACK_MODE since this method is primarily meant for internal use within the Terminal class.

Using Terminal

Gate One makes extensive use of the Terminal class and its callbacks. So that's a great place to look for specific examples (gateone.py and termio.py, specifically). Having said that, implementing Terminal is pretty straightforward:

>>> import terminal
>>> term = terminal.Terminal(24, 80)
>>> term.write("This text will be written to the terminal screen.")
>>> term.dump()
[u'This text will be written to the terminal screen.                               ',
<snip>
u'                                                                                ']

Here's an example with some basic callbacks:

>>> def mycallback():
...     "This will be called whenever the screen changes."
...     print("Screen update! Perfect time to dump the terminal screen.")
...     print(term.dump()[0]) # Only need to see the top line for this demo =)
...     print("Just dumped the screen.")
>>> import terminal
>>> term = terminal.Terminal(24, 80)
>>> term.callbacks[term.CALLBACK_CHANGED] = mycallback
>>> term.write("This should result in mycallback() being called")
Screen update! Perfect time to dump the terminal screen.
This should result in mycallback() being called
Just dumped the screen.

Note

In testing Gate One it was determined that it is faster to perform the conversion of a terminal screen to HTML on the server side than it is on the client side (via JavaScript anyway).

About The Scrollback Bufffer

The Terminal class implements a scrollback buffer. Here's how it works: Whenever a scroll_up() event occurs, the line (or lines) that will be removed from the top of the screen will be placed into Terminal.scrollback_buf. Then, whenever dump_html() is called, the scrollback buffer will be returned along with the screen output and reset to an empty state.

Why do this? In the event that a very large write() occurs (e.g. 'ps aux'), it gives the controlling program the ability to capture what went past the screen without some fancy tracking logic surrounding Terminal.write().

More information about how this works can be had by looking at the dump_html() function itself.

Note

There's more than one function that empties Terminal.scrollback_buf when called. You'll just have to have a look around =)

Class Docstrings

class terminal.Terminal(rows=24, cols=80)[source]

Terminal controller class.

init_screen()[source]

Fills self.screen with empty lines of (unicode) spaces using self.cols and self.rows for the dimensions.

NOTE: Just because each line starts out with a uniform length does not mean it will stay that way. Processing of escape sequences is handled when an output function is called.

init_renditions()[source]

Fills self.renditions with lists of None using self.cols and self.rows for the dimenions.

init_scrollback()[source]

Empties out the scrollback buffers

terminal_reset(*args, **kwargs)[source]

Resets the terminal back to an empty screen with all defaults.

resize(rows, cols)[source]

Resizes the terminal window, adding or removing rows or columns as needed.

get_cursor_position()[source]

Returns the current cursor positition as a tuple, (row, col)

set_title(title)[source]

Sets self.title to title and executes self.callbacks[self.CALLBACK_TITLE]()

save_cursor_position()[source]

Saves the cursor position and current rendition settings to self.saved_cursorX, self.saved_cursorY, and self.saved_rendition

restore_cursor_position()[source]

Restores the cursor position and rendition settings from self.saved_cursorX, self.saved_cursorY, and self.saved_rendition (if they're set).

set_G0_charset(char)[source]

Sets the terminal's G0 (default) charset to the type specified by char

Here's the possibilities:
0 DEC Special Character and Line Drawing Set A United Kingdom (UK) B United States (USASCII) 4 Dutch C Finnish 5 Finnish R French Q French Canadian K German Y Italian E Norwegian/Danish 6 Norwegian/Danish Z Spanish H Swedish 7 Swedish = Swiss

NOTE: Doesn't actually do anything other than set the variable.

set_G1_charset(char)[source]

Sets the terminal's G1 (alt) charset to the type specified by char

Here's the possibilities:
0 DEC Special Character and Line Drawing Set A United Kingdom (UK) B United States (USASCII) 4 Dutch C Finnish 5 Finnish R French Q French Canadian K German Y Italian E Norwegian/Danish 6 Norwegian/Danish Z Spanish H Swedish 7 Swedish = Swiss

NOTE: Doesn't actually do anything other than set the variable.

write(chars)[source]

Write chars to the terminal at the current cursor position advancing the cursor as it does so. If chars is not unicode, it will be converted to unicode before being stored in self.screen.

flush()[source]

Only here to make Terminal compatible with programs that want to use file-like methods.

scroll_up(n=1)[source]

Scrolls up the terminal screen by n lines (default: 1). The callbacks CALLBACK_CHANGED and CALLBACK_SCROLL_UP are called after scrolling the screen.

NOTE: This will only scroll up the region within self.top_margin and self.bottom_margin (if set).

scroll_down(n=1)[source]

Scrolls down the terminal screen by n lines (default: 1). The callbacks CALLBACK_CHANGED and CALLBACK_SCROLL_DOWN are called after scrolling the screen.

insert_line(n)[source]

Inserts a line at the current cursor position.

application_mode(boolean)[source]

self.application_keys = boolean

alternate_screen_buffer(alt)[source]

If alt is True, copy the current screen and renditions to self.alt_screen and self.alt_renditions then re-init self.screen and self.renditions. If alt is False, restore the saved screen buffer and renditions then nullify self.alt_screen and self.alt_renditions.

alternate_screen_buffer_cursor(alt)[source]

Same as self.alternate_screen_buffer but saves/restores the cursor location.

show_hide_cursor(boolean)[source]

self.show_cursor = boolean

send_receive_mode(onoff)[source]

Turns on or off local echo dependong on the value of onoff

self.local_echo = onoff

insert_characters(n=1)[source]

Inserts the specified number of characters at the cursor position

delete_characters(n=1)[source]

DCH - Deletes (to the left) the specified number of characters at the cursor position. As characters are deleted, the remaining characters between the cursor and right margin move to the left. Character attributes (renditions) move with the characters. The terminal adds blank spaces with no visual character attributes at the right margin. DCH has no effect outside the scrolling margins.

NOTE: Deletes renditions too.

cursor_left(n=1)[source]

ESCnD CUB (Cursor Back)

cursor_right(n=1)[source]

ESCnC CUF (Cursor Forward)

cursor_up(n=1)[source]

ESCnA CUU (Cursor Up)

cursor_down(n=1)[source]

ESCnB CUD (Cursor Down)

cursor_next_line(n)[source]

ESCnE CNL (Cursor Next Line)

cursor_previous_line(n)[source]

ESCnF CPL (Cursor Previous Line)

cursor_horizontal_absolute(n)[source]

ESCnG CHA (Cursor Horizontal Absolute)

cursor_position(coordinates)[source]

ESCnH CUP (Cursor Position). Move the cursor to the given coordinates.

coordinates: Should be something like, 'row;col' (1-based) but, 'row', 'row;', and ';col' are also valid (assumes 1 on missing value).

If coordinates is '', the cursor will be moved to the top left (1;1).

cursor_position_vertical(n)[source]

Vertical Line Position Absolute (VPA) - Moves the cursor to given line.

clear_screen()[source]

Clears the screen. Also used to emulate a terminal reset.

clear_screen_from_cursor_down()[source]

Clears the screen from the cursor down (Esc[J or Esc[0J).

clear_screen_from_cursor_up()[source]

Clears the screen from the cursor up (Esc[1J).

clear_screen_from_cursor(n)[source]

CSI*n*J ED (Erase Data). This escape sequence uses the following rules:

Esc[J   Clear screen from cursor down   ED0
Esc[0J  Clear screen from cursor down   ED0
Esc[1J  Clear screen from cursor up     ED1
Esc[2J  Clear entire screen             ED2
clear_line_from_cursor_right()[source]

Clears the screen from the cursor right (Esc[K or Esc[0K).

clear_line_from_cursor_left()[source]

Clears the screen from the cursor left (Esc[1K).

clear_line()[source]

Clears the entire line (Esc[2K).

clear_line_from_cursor(n)[source]

CSI*n*K EL (Erase in Line). This escape sequence uses the following rules:

Esc[K   Clear screen from cursor right  EL0
Esc[0K  Clear screen from cursor right  EL0
Esc[1K  Clear screen from cursor left   EL1
Esc[2K  Clear entire line               ED2
set_led_state(n)[source]

Sets the values the dict, self.leds depending on n using the following rules:

Esc[0q Turn off all four leds DECLL0 Esc[1q Turn on LED #1 DECLL1 Esc[2q Turn on LED #2 DECLL2 Esc[3q Turn on LED #3 DECLL3 Esc[4q Turn on LED #4 DECLL4
dump_html()[source]

Dumps the terminal screen as a list of HTML-formatted lines.

Note: This places <span class="cursor">(current character)</span> around the cursor location.

dump_plain()[source]

Dumps the screen and the scrollback buffer as-is then empties the scrollback buffer.

dump_components()[source]

Dumps the screen and renditions as-is, the scrollback buffer as HTML, and the current cursor coordinates. Also, empties the scrollback buffer

NOTE: Was used in some performance-related experiments but might be useful for other patterns in the future so I've left it here.

dump()[source]

Returns self.screen as a list of strings with no formatting. No scrollback buffer. No renditions.

NOTE: Does not empty the scrollback buffer. Primarily used to get a quick glance of what is being displayed (when debugging).