Originally UNIX communicated with it’s users via a “teletype” – that is a typewriter with a (gigantic) computer hooked up between the keys & the head. It read in a text stream & wrote out a text stream. But eventually we moved to monitors. At which point we now have a grid of text (at least) to play with rather than a mere stream.
Sure we avoid taking advantage of this out of sheer habit (to the delight of screenreader users), but sometimes the usability benefits are indisputable.
Textgrid Output “NCurses”
NCurses aids your programs in manipulating the text output grid by efficiently serializing it over the stdout stream. Here I’m starting with the miscallaneous aspects of it’s core library.
There’s a suite of “wcwidth” functions which consults lookuptables generated by the uniset
command to determine how wide each character is. Because monospace is impossible with i18n.
I see an extensive datamodel at this level I’ll revisit later as I describe how it’s used. As well as macros & C functions for converting it into debugging output.
There’s a validator for NCurses databases of different terminal’s control characters, run as a C script.
There’s files full of noops for various functions, presumably normally different files are compiled.
Within it’s “core” sublibrary NCurses implements the core logic including:
wresize
which validates the new size & retrieves the old size. If it changed it allocs memory to store the rerendered lines. Then iterates over each row index to populate the new text for that row under various conditions. If_SUBWIN
flag is set & it’s within the window’s previous height it’ll check whether it needs realloc/populate a new row or can copy the existing one. If it’s outside the subwindow height it needs to generate a new row from the subwindow’s text. Otherwise it’ll consult the mainwindow’s plines defaulting to NULLs. Then it’ll consider inserting lines communicating scrollstate. The old data is freed and the window state updated before recursively iterating over all subwindows to copy it’s text into the rerendered lines.- There’s a
scanf
reimplementation split accross multiple functions. - There’s a trie implementation for use in autocompletion.
- There’s abstractions around
sig[set]mask
&sigvec
. - There’s a length-capped
printf
reimplementation. Which can write directly into the in-local-memory textgrid. - There’s a function for retrieving the
panel_hook
property for the current screen. - There’s AWK files for generating lookuptable functions for decoding control codes & retrieving key names.
- There’s functions for mutating windows, queueing up any text changes.
- There’s a function for retrieving the character from the in-local-memory grid under the window’s in-local-memory cursor.
- Functions for storing aside & removing the active colour or attribute.
- Function for retrieving the character (from the in-local-memory grid) at an x offset for each row.
- Moving a char back onto a FIFO.
- Retrieving dirty flags from text flags.
- Set those dirty flags.
- Validating, numbering, & setting labels.
- Copying “soft label keys” into the textgrid.
- Retrieving the screen’s current soft-label-key.
- Initialize soft-label-keys (SLKs)
- Set & retrieve the SLK “attribute pairs”, colour or general.
- Remove an SLK, including it’s in-grid text.
- Toggle that attribute on or off.
- Set the top & bottom of a window.
- Enable & disable scrolling.
- Various wrappers around it’s
scanf
reimplementation integrating the window’s input reading. - Function for reloading terminal attributes & calling
_nc_update_screensize
to rerender. - Function for triggering an update to a window’s screen.
- Function to rerender offscreen in local memory.
- Function for reassembling a line of text in that grid.
- Wrappers around it’s
printf
reimplementation outputting direct to the window. - Some concept of “pad” windows.
- Functions for handling subwindow overlaps, copying the intersection into the other.
- Functions for setting or unsetting a newline flag & rerendering.
- Updating the position of a window, possibly updating flags.
- Updating the window’s
_leaveok
flag. - Checking if the current attribute, if present, has a
_endwin
property ofewSuspend
. - Functions for reading a line of “wide” (UTF16) characters from a window’s rendered text.
winsdelln
scrolls & syncs a window.initscr
initialize a new window & screen from environment variables.- Inserting characters (lowering tabs to spaces) at the current cursor & rerender.
- Retrieving a line of text from the rendered textgrid stripping attrs.
- Updating the
_immed
flag. - Repeating a certain char a given number of times into a line of the text grid at the current cursor & rerender.
- There’s a function for adding & removing a “key” from the screen’s
keytry
trie. - Retrieve a count from that trie.
- Recursively location key definition from that trie.
- Either (compiletime choice) consult the trie (two conditions) or driver (later topic) to determine whether the given keycode is supported.
- Update the screen’s
_legacy_coding
property returning the old value. - Add text char-by-char to the text grid at the current cursor.
- Tell the “driver” or (compiletime choice) output the “bell” or “flash_screen” characters & flush.
- Render border characters around a window.
- Set & get the background colour attribute of a window.
- Apply a colour attribute to each char in a line of text within a window.
- Call
werase
& if successful set the_clear
window flag. - Update the
_clear
window flag. - Replace all a window’s lines with newline chars.
- Overwrite all chars of a line with spaces.
- Set a colour attribute on a window.
- Move chars to coverup the one at the current cursor in a window.
- Remove a window from the screen, updating appropriate dirty flags.
- Update default colours via the driver or (compiletime-condition) by updating various screen properties.
- Determine whether to initialize a “Win32Console” or “TInfo” driver.
- Set or unset a flag indicating whether to echo stdin to stdout.
- Some more wrappers around driver methods.
- Overwrite a window’s text (again).
- Flash the screen falling back to beeps, same way as the
beep
function. - Destructors.
- Allow stdin to edit a line of text, like Readline does.
- There’s a function which wraps a given character an all the relevant formatting “annotations” stored aside.
- There’s a suite of functions which, after decoding any given control/attribute characters (calling
unctrl
&AttrOf
), considers whether to insert the character at the given cursor location moving subsequent chars over with any necessary text wrapping & formatting attrs, expand tabs to spaces or newlines, whether to scroll in the face of newlines. Then updates cursor location. - There’s a couple wrappers around the above to set the
sync
flag. - There’s a large suite of functions for encoding colours, and outputting them via TInfo-specified chars or the configured driver, possibly via attributes. Uses a selection of lookuptables (possibly dynamically-computed) depending on how colours are encoded. Includes colourspace conversion routines.
- There’s configuration around escape delays.
- There’s functions which queues stdin into a FIFO, and for decoding any e.g. mouse “events” in there. Platform-specific code is in here to defer further parsing whilst the mouse is actively moving, or to effect the event decoding. The keypad also gets special decoding handling. After all of which the decoded character might be echoed to the text grid. This all takes some effort I’m not fully following. Most of the (platform-specific) details are in a seperate file.
- Functions for serializing control characters to human readable text, amongst decoding of mouse events.
- A suite of functions for hittesting mouse coordinates against windows. And otherwise querying mouse state.
- Functions for setting or unsetting a filtered flag on screens.
- An initializer for the screen structure alongside it’s I/O & daemonic pthreads. A seperate function it calls focuses on configuring I/O flags.
- Appears to be a 2nd variant of that, with a more complex callgraph.
- There’s a
scr_restore
function for swapping out a window. scrdump
windows the windows to a given file.- A couple of other other functions like
scr_restore
&scrdump
. - There’s a function which
memcpy
s lines to move them up or down filling in missing lines with NULLs. Thus implementing scrolling. - There’s function for setting the current screen.
- There’s a destructor for screens.
- There’s a partial (platform-independant) initialize for screens referring to envvars.
- There’s a function for saving some validated args to the
safe_ripoff_sp
global. With a further wrapping adding locking. - There’s an initializer for SLK labels state, which in small part calls a function for initializing a table of “format SLKs”.
slk_restore
unsetshidden
flag, setsdirty
flag, & defers toslk_refresh
.- There’s functions for manipulating an ordered “pairs” array of formatting parameters used during output rendering.
- A window resize event handler…
Upon resizing a terminal it performs some validation & stateflag updates, clears SLKs, runs the main logic, updates any ripoff windows, & rerenders SLKs UI. Main logic involves, under a lock, computing the height difference to determine whether to run the increase or decrease routines (topdown or bottomup window search, to reformat the SLKs, lines, & call wresize
on), & if successful updating various properties whilst freeing old hashmap caches.
TInfo
To output it’s grid of text to the terminal emulator (e.g. libvte, Linux has one builtin, & these used to be implemented in hardware) NCurses needs to know the control characters understood by it. Though Windows apparantly uses something other than control codes (I might skip this sublibrary…) hence the concept of “drivers”.
So in it’s “tinfo” sublibrary NCurses implements:
use_screen
which temporarily updates a global under a lock whilst running the given callback.- AWK & Shell scripts for gathering datafiles & (more involved) compiling them to C code to hold the hashmaps.
- A “driver” (large) methodtable is implemented in case we want to compile Windows support in as well. Alongside a function for populating fields from a envvar-loaded “magic cookie”, & one to load a diver by name. Otherwise the “base” sublibrary compiles directly against TInfo.
- There’s a suite of sized string utilities.
-
Functions for processing a “ “-delimited name list. _nc_init_termtype
zeroes out all fields of the givenTERMTYPE2*
mallocing new smallintmaps.- There’s a datafile parser calling out to the various other APIs I’m describing, including a tokenizer. Parsing’s otherwise mostly contained within a single function.
- There’s checks whether the terminal emulator it’s running against is MinTTY, since it’s a special case on Windows. Same file has wrapper around Windows’ current time syscall.
_nc_set_writedir
sets thetic_dir
global & opens the specified directory.tigetflag
wraps, with argument decoding,_nc_find_type_entry
or a manual boolean parallelarray lookup.tigetnum
&tigetstr
works essentially the same way.- There’s a destructor for
TPARM_STATE/DATA
involving in part allocating a “fringe” for tree traversal. - There’s a C script for reading an arrayset of keynames from a given commented file & outputting as C code.
- There’s postprocessing functions once the datafiles are parsed depending on whether it’s a “terminfo” or “termcap” file. Also a type-independant field-lookup function is defined & used in same file as the parser.
- A suite of functions for interacting with Windows terminals.
def_shell_mode
defers to driver or updates the tty mode properties as returned by the kernel.termname
retrieves a property of the current terminal.tgoto
stack-interprets it’s chars lowering to sprintf._nc_trim_sgr0
, with the aid of several helper functions, normalizes the abstract syntax tree it has parsed seperately, then frees any leftover junk.- There’s a whole suite of functions operating basically the same
def_shell_mode
. These may be combined withsaved_tty
which retrieves the given parm’s, or global screen’s,saved_tty
property. tgetent
validates it’s input, frees any attribute which mache the cache, & configures some other properties.raw
consults keyboard status after successful mode setting.cbreak
wraps_nc_setmode
&_nc_set_tty_mode
with updates to buffer, & parm or termp, flags.qiflush
also wraps_nc_set_tty_mode
with flag updates, this term operating on the termp’sNttyb
buffer property.intrflush
works similarly.raw
,cbreak
, &qiflush
all haveno...
variants working similarly.mcprint
wrapswrite
with argument reformatting &, on windows, a yield.idlok
sets a corresponding screen & window property, as doesidcok
.napms
either defers to driver or wrapsnanosleep
syscall.halfdelay
wrapscbreak
& increments the parm_cbreak
property.- There’s a C script which parses a TSV file skipping any
userdef
rows, reformats into a hashmap, & outputs as a C dataheader. - There’s a suite of functions lightly abstracting raw text output, interacting a little.
- More attribute lookup functions.
- Various getters & setters.
- Various functions looks up a control character & outputs it, providing a slightly higherlevel abstraction.
has_ic
checks various flags to compute it’s boolean output, as doeshas_il
.erasechar
checksfpathconf
(via wrapper) before retrieving the termp’sOttyb.sg_erase
property.- APIs for saving the current configuration as binary data into the configured database, utilizing recursive in-memory serializers. With validation.
_nc_read_temcap_entry
opens a specified (falling back to $TERMCAP) file deferring to_nc_tgetent
for serializing the bytes, followed by validation & writing the data out into that file._nc_tgetent
utilizes the sized strings library, & a couple helper functions, to concatenate the textual representation of all it’s fields into the output buffer._nc_read_entry[2]
performs loosely the reverse.- There’s a suite of functions abstracting Windows terminal-related syscalls.
tgetstr
wraps_nc_find_type_entry
to construct a hashset of deduplicated strings (useful optimization).- There’s interrupt handlers for setting the corresponding flag on all windows.
killchar
works basically same aserasechar
.flushinp
wraps platform-appropriate flush syscall._nc_home_terminfo
conditionally returns “HOME” envvar._nc_init_keytry
populates a try with the table of keynames.- There’s
getenv
&setenv
wrappers. - There’s termtype destructors.
_nc_doalloc
wrapsmalloc
/realloc
/free
.- There’s a doubly-linked list of TInfo configurations implemented, focusing on deletion.
_nc_get_screensize
may defer to the driver or read environment variables or IOCTL results, setting those syscalls if appropriate. On UNIX systems this info is rerequested upon a specific interrupt, requiring aresize
callback to run.- There’s special logic for numpad communication.
- There’s a destructor for a NCurses terminal.
baudrate
consults a lookuptable & maybe $BAUDRATE to set some globals.- Functions for looking up character info, e.g. width.
- Functions for retrieving the locale (and charset) from environment variables utilizing locale.h if available. Which it is for GNU LibC.
- A suite of functions for initializing globals, especially regarding pthreads.
- There’s functions implementing global hashmap lookups.
_nc_tic_expand
interprets the given string to yield a more verbose version.- There’s a hashmap of “databases” implementation, where databases are defined via a methodtable.
- There’s a trivial trie implementation.
- There’s a few initializers/allocators for
SCREEN*
structures, one of which is intended for use by the parser to aid memory reuse. Utilizes own freelist. May source configuration from $TERM envvar, and may consult driver and/orsysconf
for further configuration. t[i]parm
stack-interprets a specializedprintf
that can operate on a grid. This is preceded by a cached “setup” phase anlyzing stack depth & how parameters are referenced; & copying variadic args to an array.- There’s a function which copies all strings of a given terminal pointer, into internal storage, presumable for heap compaction optimizations.
- There’s a function to merge two
ENTRY*
s. - There’s a iterator over the database hashmap & directory.
- There’s a stderr debugger.
- There’s same filepath utilities, in part abstracting error handling.
- There’s a sophisticated scanner, & a sophisticated lexer for use by the parsers.
- There’s a sizable C script for reformatting termcap files into terminfo files.
_nc_align_termtype
goes over all the fields of the two givenTERMTYPE2*
s to ensure both are structured comparably, with the aid of several helper functions particularly for merging dynamic field names.copy_termtype
copies all fields from a sourceTERMTYPE2*
to a destinationTERMTYPE2*
.
NCurses-Internal Debugging “trace”
Like all software NCurses needs to be debugged, and as an I/O library NCurses requires special infrastructure for that. So here I’m discussing it’s “trace” sublibrary, optional calls to which are placed throughout the “base” & “tinfo” sublibraries I described above.
In “trace” there’s:
_nc_varargs
reformats strings passed throughsprintf
._nc_trace_xnames
wraps the core_tracef
to debug dynamic field counts & values.- Recursively
tracef
ing tries. _nc_vis[w]buf
& related functions, after validation, populates buffer allocated via_nc_trace_buf
or (compiletime choice)typeRealloc
& fills it with a reformatted version of the input string altered to contain only printable characters._nc_trace_buf[cat]
wrapstypeRealloc
storing results in a global array (to be inspected via GDB?), freeing all values upon negative buffer IDs. Latter adds a_nc_STRCPY
call._tracechar
wraps_nc_SPRINTF
&safe_keyname
orsafe_unctrl
._tracedump
iterates lines & columns to (with a little postprocessing) compute the narrowest possible display width to cap later column iterations, then iterates over the lines again to extract various interesting properties &_tracef
them._[nc_]tracemouse
_nc_SPRINTF
s a header &_nc_STRCAT
s a curly-bracketed body listing all the bitflags currently set._nc_retrace_mmask_t
leaves off the head._nc_tracebits
&_nc_trace_ttymode
uses a helper function to output all bits set in various internal bitmasks_nc_viscbuf[2]
wraps extensive calls to_nc_trace_bufcat
to output textlines followed by their attributes, also calls_traceattr2
traceattr2
& related functions iterates over the names array to concatenate formatted text describing themtracechtype[2]
further concatenates on the named character & charset name_tracecchar_t[2]
concatenates/formats similar info
That central tracef
validates input, uses some sort of macros system to track function entries & returns incrementing & decrementing a “level”, prepends the pthread ID on, adds “+”s to indicate said level, & ultimately wraps vfprintf
.
This various more trivial wrapper functions, a trace
function for openining the line-buffered file it logs to (defaults to ./trace[.log]), and a couple function for updating the locked _nc_tracing
global bitmask.
Uploading the textgrid “tty”
If my first-glance understanding is correct, NCurses’s “tty” sublibrary handles finding the optimal set of operations for altering the text grid in the terminal emulator to reflect that which it has stored in memory. It extensively wraps TInfo.
This optionally incorporates a debugging call to request the current cursor location from the terminal.
vidputs
uploads numerous attributes & modeflags to the terminal emulator via stdout. termattrs
aggregates global flags into a bitmask.
Scrolling (also applies to inserting lines of text) is an extremely common operation in terminal applications like the sort that uses NCurses.
To efficiently apply these operations line# index from the lastfrom is kept for each line of text. From there it applies to initial line iterations: from top to bottom applying a TInfo scrolln
up operation if necessary to realign line numbers, & from bottom to top applying a TInfo scrolln
down operation if necessary.
With trace debug outputs.
There’s a _nc_timedwait
function which wraps system-specific (accross a range of OSs) to wait on an array of filedescriptors or a timeout, whatever comes first.
This “tty” sublibrary (via it’s _nc_signal_handler
function) is where interrupt handlers are registered.
But ultimately the main work involves estimating how much it would cost (minimizing context switching?) to use apply various opcodes to the desired effect.
There’s a hashmap implementation (not worrying about hash collisions, as it’s now only a performance not correctness issue) to generate those updated line numbers where missing.
The routines for estimating how many bytes are required to update the screen to the desired state refers (upon moving the cursor) to a struct initialized per MOV op. Working much like a game AI moving NPCs.
The central code of NCurses’ s “tty” sublibrary” involves, once state is validated & locks held, ensuring signal handlers are registered, screensize is updated, we’re in the correct mode, & all attrs toggle-on/offs are onscreen.
Then it’ll consider clearing the screen to send it the terminal emulator the fill grid it has in memory. Otherwise, deferring whilst input is pending, applies that scroll optimization, considers a clear-to-end-of-screen operation, & process all changed lines.
The central code of NCurses’ s “tty” sublibrary” involves, once state is validated & locks held, ensuring signal handlers are registered, screensize is updated, we’re in the correct mode, & all attrs toggle-on/offs are onscreen.
Then it’ll consider clearing the screen to send it the terminal emulator the fill grid it has in memory. Otherwise, deferring whilst input is pending, applies that scroll optimization, considers a clear-to-end-of-screen operation, & process all changed lines.
If it’s a nontrivial line change it’ll more carefully recompute the last differing char, & maybe insert the new chars at the first index, compiletime-optionally performs some widechar checks& unconditionally inserts chars at the second index OR emits an appropriate delete control code. All move cursor operations utilizes the cost-estimations described previously to choose optimal cursor navigation control chars.
Then performs cleanup including e.g. moving cursor to it’s logical location.
UTF16 NCurses “widechar”
There’s a numerous different ways to encode text, but today we mostly today we deal with UTF8 & maybe UTF16. Benefits between them are very minor, but UTF8 aids the transition from english-centric code (by being very compatible with ASCII) so that tends to be what we use. Here I’m discussing NCurses’s support for UTF16, as C calls it “wide chars”.
wunctrl
wraps unctrl
with format conversion.
wvline_set
wraps a loop over lines setting that col, with _nc_render
& _nc_synchook
.
_nc_init_wacs
wraps a lookuptable & SetChar
with locale & width checks.
Unless otherwise stated the conversion between widechars & chars or vice versa wraps Windows API calls. I’m not yet sure what they do when not compiling against Windows…
slk_wset
wraps slk_set
using StdLibC’s widestring to string conversions.
pecho_wchar
wraps wadd_wch
& prefresh
with some validation.
unget_wch
wraps ungetch
with validation/allocation & it’s own char conversion wrapping StdLibC’s.
key_name
wraps wunctrl
& wcstombs
with zeroed memory & output validation.
win_wchnstr
wraps a manually-copy/validation loop with further input validation.
win[n]wstr
is a manual loop (using Windows APIs?) over lines & columns copying data out to caller. Calling getyx
for a starting point.
term_attrs
optionally adds additional bitflags to it’s response indicating whether widechars are supported.
vid_puts
& vid_attrs
encodes outputs various encodes/outputs attributes.
win_wch
retrieves the wide character at the position under the, as indicated by getyx
, the cursor.
whline_set
iterates over a manually-written loop setting each column of the given line in reverse with input validation, _nc_render
, & _nc_synchook
.
_nc_is_charable
wraps wctob
or _nc_to_char
to return a bool. _nc_to_char
wraps wcto[m]b
. _nc_to_widechar
wraps [m]btowc
. All depending on what the OS provides.
wadd_wch
& wecho_wchar
before calling _nc_synchook
if successful & possibly surrounded by trace-debugging, involves depending on it’s input char position-checked annotated char stored at the appropriate filling in columns where needed, sequences of such spaces until desired tabstop possibly inserting a newline/scroll, clearing to end of line (for “\n”) & possibly scrolling, or outputting a control char rendering.
There’s a border_set
reimplementation for widechars wborder_set
.
setcchar
wraps memset
/memcpy
with annotations, width summing (because, again, there’s no such thing as truly monospace international text), input validation, & possibly debug tracing.
getcchar
similarly wraps wmemchr
/wmemcpy
with input validation, annotations, & maybe debug tracing.
erasewchar
wraps erasechar
with output casting. As per killwchar
.
wget_wch
wraps repeated nc_wgetch
with multibyte decoding within a buffer & maybe using safe_ungetch
.
wgetn_wstr
involves temporarily updating various properties/flags, get the cursor location via get_yx
, possibly refreshing the window rendering, & repeatedly calling wget_wch
interpreting it’s result. wmov
ing over any deleted chars, beeping on errors, and otherwise manually echoing the typed/editted text.
wins_wch
wraps _nc_insert_wch
restoring the old cursor location & flagging for sync. _nc_insert_wch
inturn wraps winsch
or _nc_render
. wins_nwstr
does similar in a loop.
NCurses Forms
NCurses provides a “forms” library, implementing an API first introduced in System V Linux, which renders a low-res GUI to the terminal. Given we actually have (prettier) GUIs now that are in fact accessible & fully support international text, I discourage using this library. Thankfully I mostly only see this used in Debian installers & upgrade prompts, whilst BIOSes/UEFI appear to have their own equivalent.
Please go even more nostalgic and stick to more teletype-style UIs for your commandline programs instead!
P.S. This library probably inspired the web’s concept of forms…
This defines a methodtable for widgets, with some bitflags (including, seperately, mutability) & methods for:
- both per-char & on-blur validation methods, as well as bitflags,
- memory management,
- & up/down navigation.
Upon this it implements several standard field types including ones for:
- IPv4 addresses
- Alphanumeric text
- ASCII letters
- Sorted enum/text selection
- Numbers (why doesn’t this bind the up/down keys?) - integer & floating point
- Regex-validated text
- Custom types
There’s accessors for a form’s window, subwindow, userdata, size, & cursor pos & it’s window.
Fields have accessors for:
- Their fieldtype (as listed above), recursively traversing a given iterator tree for it’s value.
- Other less-sophisticated fieldtype setter
- Fieldtype’s callback methods
- Userdata
- Status
- Whether it’s a new page.
- Padding char.
- Position
- maxwidth
- Bitflags
- Justification
The form tracks a doubly-linkedlist of these fields, & another of fieldtypes, rendering a NCurses window hierarchy. There’s getters & setters for a form’s page & options, & selected field. There’s getters for the selected index, whether there’s offscreen data.
post_form
is a wrapper around _nc_Set_Form_Page
with state management/validation & triggering of events. unpost_form
closes the window hierarchy.
There’s a conversion between FORM_COMMAND
enums & corresponding strings.
NCurses Menus “menu”
If you wish to be reductive you could view basically all UIs (especially on TVs, like I’m targetting Haphaestus) as being a menu to some degree of fanciness or other. Certainly menus are an extremely common UI construct!
And NCurses provides a library for rendering them to the commandline, which I’m studying today!
Its concept of menus has getters/setters for:
- NCurses window & subwindow they render to
- Userdata
- Spacing
- Padding
- Match pattern
- Bitflags
- Available items array
- Toprow menuitem
- Selected menuitem
- Char to mark selection
- Maxsize/”format”
NCurses menus also has getters for
- Min size
- (Sync’d) cursor & it’s position
Their menu items have getters/setters for:
- Value
- Userdata
- Bitflags
And getters for:
- Visibility (via box hittest)
- Name
- Description
Both menus & menuitems have constructors/destructors, & default property values.
All these setters trigger the menu to rerender.
There’s a function menu_driver
which, after validating it’s state, interprets the input character (which might represent mouse events) into a change of the menu’s state. Notably the toprow & current item properties. Possibly selected via a substring match.
There’s converters (via lookup table) between MENU_COMMAND
enum & their textual representation, or vice versa.
post_menu
, after state validation & getting layout props, renders the menu & all it’s menuitems with background colours.
Upon rendering the menuitems it may render the selection char with it’s own background, if the menuitem is the selected one. And possibly shows item descriptions similarly.
Rendering the menu itself uses wmov
to scroll the menu the specified toprow is at the top (offset computed elsewhere).
unpost_menu
removes the menu’s window.
There’s size computation functions to support some of those getters. And to precompute where arrow keys jump to.
Events are triggered throughout.
NCurses Panels “panel”
NCurses “panel” library aids overlapping NCurses windows by tracking additional state in “panels”. Though it strikes me this should be a non-problem not even nostalgia should bring on. And certainly would interfere with accessibility!
For roughly 3 decades similar logic was found in most UI toolkits, browser engines, & even window managers like X11. However now that basically all computers have GPUs in them (even inside the CPU) we have pretty much finished the refactor to simpler, less-glitchy hardware-accelerated compositing. Not that NCurses can use that!
NCurses panels have getters/setters for it’s window, it’s window’s position, & any attached userdata. But mostly this library serves to query/mutate a bottom-to-top doubly-linked list of panels (head/bottom & tail/top pointers), in rendering order.
The stack mutation functions include:
- Set/get whether the panel is “hidden” (i.e. in the stack).
- Deleting a panel (hiding then freeing it).
- Retrieving the bottom & top panels.
- Moving a panel there.
- Getting previous/below & next/above panel.
Panels ofcourse have constructors & destructors.
Rendering is split into update (determining bbox intersections, flagging dirty) & refresh (on their windows) passes iterating over panels from bottom to top.
LibVTE
Having covered Bash & NCurses I now wish to describe what’s on the other end of stdout, etc!
Because while you may find it relatively trivial to input & output text in commandline programs, it isn’t for the computer. At least if you want to include those outside the US & UK! Which the hardware these renderers claim to emulate didn’t.
Usually your terminal application will be a thin wrapper around LibVTE, which I’ll tackle one layer at a time! Though Linux also implements it’s own to be available during early boot, and early home computers implemented it in hardware.
GTK Terminal Widgets
I’ll start with the GTK widget which gets embedded into applications. Another layer above this converts the GObject method calls into C++ method calls whilst validating the arguments.
Upon realization this widget loads text selection, invisible, mousing, & (possibly debug) hyperlink cursors, if on GTK3 initializes a window for receiving events, initializes a “input method” for typing non-English text with callbacks deferring to next layer, grabs clipboard references, & defers to next layer.
In it’s constructor, this widget sets it’s default alignment, focusability, & maybe GTK3 redraw behaviour before initializing it’s wrappee. In it’s destructor all eventhandlers are cleared & wrappee deallocated.
There’s cursor-setting methods I don’t immediately see a caller for.
There’s clipboard event handlers which do little more than defer to wrappee, or have GTK ring the auditory errorbell.
It tells GTK4 there’s no sizing constraints.
Once constructed LibVTE’s widget adds a CSS class indicating a monospace font should be used & registers various event handlers. Which amongst more raw event handlers includes responding to reconfiguring cursing blinking & aspect-ratio. Before dirtying the style (for GTK3) or padding (for GTK4). This sending said cursor config with it’s initial values into the wrappee.
Upon dispose it clears accessibility data & SIGKILLs the shell it’s rendering. Upon which it logs & emits a GObject signal.
There’s GObject signal emitting (and logging) methods for charsize, (via GTK “adjustments” in part) scrollbounds, & scrollvalue.
There’s methods used to do the data conversion in the event handlers.
Sizing methods defer to the wrappee & possibly logged, as is rendering. Desired padding config is forwarded to it.
Tooltips are disabled.
There’s setter methods for configuring scrolling & it’s wrappee’s properties.
And there’s cleanup.
Core Terminal Widget
Once LibVTE has transitioned almost entirely, with the exception of GLib’s “signals” (a.k.a. events; none of which are discussed below, though I will state they’re aggregated as flags) & mainloop, to C++ land, it implements the bulk of it’s logic. Seemingly just because they prefer, at least traditionally, C++.
Throughout everything else it implements methods resetting and/or clearing state (in turn wrapped by the constructor, destructor, & aggregate methods), as well as configuring render, scrolling (which is wrapped with methods up/down a number of lines, etc), selection, find-in-scrollback, etc parameters (including the processing being rendered) whilst invalidating appropriate state. A global list of active terminals to aid with cleanup, applied during processing incoming data or updating dirty state.
Parsing
The text for LibVTE to display is serialized from the shell & its commands over a “pipe” filedescriptor. LibVTE needs to insert each of these char into it’s appropriate place, which can get non-trivial if, say, NCurses (described above) or ReadLine is used. Then there’s the issue of text encodings! Or you could even send it images to display, never seen that used!
Event handlers are registered to read the shell’s stdout (and write to stdin) to/from buffers, and for flagging when those pipes close. There’s several methods, and wrappers around them, to enqueue binary or textual data to be written to stdin. The stdout buffer is split into roughly consistant “chunksizes” (whilst decoding “packets” in a choice of syntaxes to control scroll-lock, terminal I/O, etc) & registers a timeout to throttle the actual parsing of those. Which in turn is wrapped with preprocessing, error handling, & maybe profiling.
To actually process that queued data it’ll dequeue each lexed “chunk”, maybe log it, & branch over the current “syntax” to determine the actual logic to use. Then it’ll postprocess to update insertion changes, maybe scroll to bottom, maybe clear text selection, maybe invalidate the ring view & flag itself as dirty, trigger any pending GLib signals, maybe invalidate it’s own rendering or just the cursor rendering, determines whether to render the text cursor according to the blink period, updates the Input Method cursor (deferring to it’s GTK wrapper after layout computation), & collects offscreen hyperlink highlights.
- The UTF8 syntax, after sending each byte through the UTF8 decoder, checks whether it’s instead some command (like GRAPHIC) it should dispatch to the appropriate method. Or upon successful UTF8 decoding it instead inserts the new char into the scrollback buffer.
- PCTerm syntax, works essentially the same way but with a configurable text decoder.
- And DecSixel syntax defers to a seperate parser before inserting the parsed image by computing the bounding box, appending it to the row, and surrounded by
ProcessingContext
methods erases any chars behind it. ThatProcessingContext
might update it’s bbox top property, afterwords it checks if it needs to dirty any linewrapping before unconditionally updating the bbox bottom & updating it’s bitflags.
These operations are managed by a ProcessingContext
tracking bbox top & bottom, bitflags (was modified, is bottom, text invalidated, in scroll region, cursor visibility), cursorstyle, saved cursor position, & saved screen. Which’ll perform scrollregion invalidation checks after most commands.
But ultimately they all serve to mutate a “scrollback ringbuffer” (wrapped with several terminal emulator methods), usually inserting an individual char whilst making room for it. Or perhaps it’s constructor will insert warning text directly into this scrollback ringbuffer via a special wrapper method. To insert each char it first rewrites it into proper Unicode if in “linedrawing” mode, computes the char’s monospaced width, applies softwrapping if enabled with recomputed bidi data, logs, validates, & ultimately defers to the ringbuffer before updating cursor & cleaning up.
Layout
Before we need to know we need to know where to place each individual char as determined by the font. Where to split lines of text, where to place the text cursor. Monospaced text makes this easier, though still non-trivial thanks to kanji, etc.
Several layout properties are extracted from the configured font (this method has several wrappers directly integrating GTK/Pango which in turn integrates FontConfig), which may require notifying the rendered process of a resize & definitely involves invalidating all rendering. This font is loaded upon sizing, late-initializing, or configuring. And there’s methods converting between pixel coordinates & grid coordinates, with a grid offset stored for smoother scrolling.
To compute desired width or height it converts grid width (and constant min-value) into pixels & adds padding. Once GTK gets back to this widget with it’s assigned size based on those preferences it subtracts off the padding & converts back into gridsize, computes extra padding based on configured alignment, saves this size, updates gridsize, notifies of change, & maybe invalidates all rendering.
Upon setting the gridsize (several callers) it considers forwarding this event to the shell retrieves it’s gridsize OR saves the configured values before, if it changed, configuring the underlying scrollback buffer & screens, updates scrollback linecount & whether the cursor is onscreen, & triggers scroll, resize, & text modified events. To set a screen’s size it might clear all selections, recomputes text wrapping for the textcursor’s current row, updates markers & selections to match, defers to the underlying scrollback ringbuffer to rewrap text, considers whether we need scrollbars, restores text selection, computes new insert & scroll deltas if required, restores textcursor, & triggers scroll event.
There’s a heavily-used method which, after numerous checks, integrates Cairo & GTK to invalidate the rendering of the given altered text lines. A wrapper methods tweaks those inputs to handle text wrapping, or allows specifying only a single row to dirty. Or use a vte::grid::span
. Or for the sake of text selection, the symmetrical difference between two spans. Or alternatively you could dirty everything! Another method clears these dirty rects.
The text cursor blinks to catch your attention, which requires several methods (some of which decides whether the cursor should be blinking) & a periodic timeout to implement. At which point the textcursor’s rendering has to be invalidated. An invariant’s enforced that the textcursor’s not above the viewport, & occasionally it has cause to check whether it’s below.
Upon updating the scroll value it’ll clamp it to the available range, compute diff, save new state, & considers invalidating state for rerendering. Upon unmap (remove from display) it’ll pause the underlying ringview.
Rendering
The primary purpose of LibVTE is to allow you to see the text written to stdout via commandline programs! Because text requires some significant processing (especially if we want to support non-English text & emojis, & especially now hardware doesn’t do this for us) requires some processing to turn it into an image the monitor can display. Then for text entry we also need to render a textcursor &, for international text entry, Input Method pre-editting.
To draw the widget itself it might perform some GTK3 adjustments before retrieving allocated size & configuring it’s C++ vector graphics abstraction with a Cairo surface, renders a background box, temporarily configures a clip/translation, draws the SIXEL image if compile-/run-time enabled, updates blink state, draws all visible lines of text, draws the preedit string, restores Cairo state, with temporarily reconfigured clip & translation repaints the cursor, unconfigures C++ vector graphics abstraction, reschedules cursor blink, & unsets any invalidation.
To draw the Input Method’s preedit string it’ll validate there is one, update the underlying ringview, compute bidi for the row, cap the column, if there’s text render it’s background followed by the Pango text converted into it’s own datamodel for rendering, then renders the cursored char in reverse colours. To draw the textcursor it performs various validation checks before updating the ringview, locating the first char under the cursor, choosing colours & computing bbox; if it’s configured to render I-beam cursors it chooses which side of the char to render on & draws that rect(s) (2 if mixed directions…), if underlines it’ll retrieve that edge from the cell & fill it, & if blocks it’ll fill or stroke (depending on focus state) a box around the char handling space/tab chars specially.
To draw the rows of text it updates the underlying ringview, allocs some temp memory & retrieves assigned width, computes bbox, & iterates over the rows skipping ones not needing rerendering; for each of those lines it retrieves each cell’s selection, bidi data, & colours before reiterating over the lines needing rerendering & the contained cells in visual order retrieving the colour/style/selection & gathering into runs for rendering via the cells rendering method. And calling it again for any trailing text. To draw these retrieved cells it retrieves appropriate palette colours (by looking up the cell’s colours in the palette, considering swapping default foreground & background, considering switching to the bold or dim colours, considering reversing the foreground & background, & decides which colours if not a swap to use for indicating text selection and/or the text cursor), considers whether it wants to break runs & draw the background rect, updates blink state, considers drawing lines over the text if requested, & renders the text itself.
There’s a method for concatenating all this text to be rendered (or selected) from the underlying scrollback ringbuffer (via some more of it’s wrapper methods) between two rows with or without text wrapping with the aid of a new ringview to resolve bidi & text formatting. Formatting parameters are returned in a seperate array. There’s several wrappers around this method focusing on displayed or selected text. Or computing checksums. Or it can retrieve an individual char at a given row/column.
Or there’s a special char which should be rendered, via it’s GTK wrapper & in turn a GTK method, as an audible beep.
Selection (& Find)
Text selection is a vital feature for any desktop (or phone) app handling text, because not only does it help us combine the functionality of multiple applications using copy/paste (or middle click) but also because it’s how those who can’t see well instruct their screenreaders of which text they want read out to them.
- Upon cursor down keypress it’ll update scroll data & text wrapping, invalidating the rendering of the relevant rows. Upon initial mouse motion, doubleclick, or tripleclick it’ll start a selection updating the underlying ringview, convert into grid coordinates, update state with appropriate postprocessing (described above), & temporarily stops reading from stdout.
- Upon mouse updates it updates underlying ringview, recomputes layout info from last mouse position & bidirectional text annotations, checks whether it’s possible we’re hovering over the match & if we are, recomputes match highlighting state, & updates the mouse cursor via another method.
- Upon mouse release or unfocus it’ll save the selection, if present, to primary clipboard, trigger a GLib signal, stop autoscrolling, & resume reading from stdout.
- Upon Select All commands it’ll clear any existing selection replacing it with one covering the fulling scrollback ringbuffer, updating primary clipboard, triggering the GLib event, & invalidating all rendering.
- Upon mouse motion it may also start a low priority periodic timeout for autoscrolling.
- Upon mouse motion or that periodic timeout it may also update the scrolling state based on the position of the mouse relative to the widget before extending selection, so you can reach more of the text during mousedrag.
These are implemented using methods for extending selections to valid/configurable breakpoints (& in turn a method for classifying char types), deferring to the underlying ringview for taking bidirectional text into consideration, normalizing start/end order.
Upon updating selection it’ll also update the “primary” (middleclick) clipboard with the selected text, or the GTK wrapper can tell it to populate the “secondary” (ctrl+c, ctrl+v) clipboard. In which it can render the selected text with it’s attributes into a (deprecated) HTML string. This clipboards can be cleared internally or by the wrapper.
There’s methods for cycling through & selecting each substring match of a configured regular expression or plain text. Because that’s a useful feature for understanding the output we’re seeing!
Input Methods
Computer keyboards were specifically designed for typing English text, which doesn’t suite everyone. So operating systems provide “Input Methods” which hooks into the UI toolkit to alter the text being typed to be more suitable for the user’s language, or to provide an alternate means of typing that text. Smartphone software keyboards are an example of an Input Method.
For the sake of these Input Methods (and in turn both internationalization & accessibility), this core widget has methods for:
- “committing” the text entered via it by sending it to it’s child process & maybe scrolling to bottom,
- setting whether Input Methods are enabled,
- resetting related state, saving new preedit text flagged with appropriate dirty flags whilst updating the Input Method as to cursor location,
- noops in place of retrieving surrounding context,
- computing & informing it’s GTK wrapper of text cursor location,
- computing preedit text width or length,
- & signaling that preedit text has been committed.
Upon mousepress or input enable it might call a method to reset both it’s widget’s Input Method & it’s own preedit text. Because keyboards are specifically designed for English which doesn’t suit everyone.
Hyperlinks
Since the web took off it’s now become common for commandline programs to output URLs, which LibVTE streamlines use of by autodetecting them so you can click them to open in your browser.
For this there’s a method for checking whether the text at a row/column or x,y coordinates is a hyperlink, deferring to the ringbuffer for the actual logic. And one for for computing the bounding box of the hyperlink at the given index, used for hyperlink highlighting in response to mouse updates. Highlighting hyperlinks in turn involves updating the ringview, determines whether it’s possible we’re hovering over a link & consults the row data to determine which one it was invalidating the old hover, computes bounding box, invalidates Find match if present, updates mouse cursor via another method, & triggers the corresponding GLib signal.
Input
Not only does LibVTE handle rendering commandline output to the screen, but it also handles converting relatively raw (but nowhere as raw as Linux’s internal one handles!) input events into bytes enqueued to be written to stdin for the shell or whatever to handle. Or to have these events trigger other operations like notably text selection.
Upon focus it has its GTK wrapper grab focus, & if it’s been realized invalidates the cursor blinking (enabling it), possibly the rendering, the Input Method, & the text cursor before possibly sending the event through stdin via a special encoding method for e.g. NCurses to handle. Upon unfocus unsets it’s has_focus
flag & text cursor blinking, & if it’s been realized considers sending the event through stdin (via a special encoding method), closes the text selection, maybe invalidates it’s rendering, unfocuses the Input Method, invalidates the textcursor, & unsets mouse state.
Upon key press it may reregister the blink timeout, asks the internal keymap whether this is a modifier key & if not hides the mousecursor, if the Input Method determines whether it takes precedance over the key possibly (regardless of whether it’s a press, release, etc) calling into its GTK wrapper to defer to the Input Method, figures how it wants to encode backspace/insert/nav keys (or adjusts textsize) or might consult the internal keymap, hands ctrl-modified keys to its GTK wrapper, if this key event wasn’t otherwise handled it’ll get converted into UTF-8 possibly modified by the control key and if that was a normal char it’s conditionally sent through the keymap to add modifiers & written to stdin. Before maybe scrolling to bottom. Key releases are sent through the input method. Modifier keys are saved.
Upon paste the text is buffered, with little preprocessing, to be written to stdin.
Upon mouse motion it updates the underlying ringview, computes coordinate conversions, & determines whether to start text selection, modify that selection, autoscroll, or (via a seperate encoding method) sends mouse drag events to stdin. Before saving mouse position & updating highlighting/autohiding. Upon mouse enter it converts & saves the coordinate space, flags that the mousecursor’s over this widget, updates autohiding/highlighting, & determines which mousecursor should be rendered. Upon mouseleave it does similar. Upon mouse scroll it updates the underlying ringview, updates smooth scroll delta, considers sending through stdin, otherwise computes scrollspeed, considers faking arrow keys, or considers registering a timeout to update the scroll state.
Upon mouse press it updates the underlying ringview, resets the Input Method upon single click, converts coordinates, checks clickcount & button for which action it should perform (below), in GTK4 has a stub for showing the menu, updates mouse state properties, & updates highlighting/autohiding state.
- Upon single leftclick has it’s GTK wrapper grab focus, & considers updating, clearing, and/or normalizing selection state.
- Upon single middleclick requests the contents of the primary clipboard.
- Upon double leftclick starts char- or word-wise selection.
- Upon triple leftclick starts line-wise selection.
- Upon mouse release updates the underlying ringview, converts coordinates, stops autoscrolling, probably checks which button was pressed, saves mousestate, & updates highlighting/autohiding.
- Upon left release ends the current selection, if present, as described above.
- Ignores middle releases.
- Otherwise considers sending through stdin via a serialization method.
Ringbuffer
The next layer of LibVTE is the scrollback ringbuffer (and ringviews), which stores the logical position of all output text (including sidetable of hyperlink locations & images, which it can garbage collect via hittesting or temporary presence bitmask) in the terminal.
Internally it’s modelled as a resizable (mostly growing, e.g. when “thawing” or inserting rows) power-of-two number of “rows”, overriding old rows (implemented via bitmask on index) when the array fills up. There’s several wrappers for retrieving row properties at an index. Data overflowing the ringbuffer is saved encrypted to disk (a warning’s printed if LibTLS(?) is unavailable). There’s several well-used wrapper methods, including for discarding saved rows.
The start index of the hyperlink in any row is saved in-memory to it, for the method wrapping that to dynamically compute it’s length. Or if not in ringbuffer it’ll retrieve some text both before & after hypertext garbage collection.
Has reset methods.
Inserting a row considers validating it’s state, flushing a row to disk, & ensuring there’s enough room to insert it before moving all the lines up (there’s a refactor FIXME comment) & reinitializing the bottom row, & considers freezing a row & validating, & returns that row.
Removing works similarly, moving rows down with validation & mutability checks.
Appending a row inserts into the last index.
To rewrap text after possible validation & with a new stream over the overflow data, it freezes all mutable rows compute a markers count, allocs some temp memory, convert markers into frozen text offsets, attempts to read a record, extracts attrs, hypertext length, & end offsets from each frozen record, uses that info to iterate over paragraphs to locate their boundary & repeatedlies consults attributes & hyperlinks to find an appropriate splitting point to stop the line from overflowing the allocated width. There’s a fastpath for ASCII chars, and handles images specially configuring it’s y position & postprocessing after applying each paragraph split.
Yields a new hard-wrapped ring.
It can drop the on-disk overflow data after adjusting ringbuffer region.
There’s a property for the number of visible rows.
There’s a method to concatenate & save all the text from the ringbuffer’s rows.
Rows
Underlying LibVTE’s ringbuffer’s logic for logical text layout are datastructures for individual textrows. Which primarily serves to wrap an array of “cells”, providing all the typical arraylist methods.
Though there’s also linewrapping & bidi bitflags.
Each cell in turn tracks, alongside their individual chars, some bitflags (cols, fragment, style, lines, reverse, blink, dim, invisible,), colours, & an index into the hyperlink table.
Some of these bitflag fields are actually tiny ints (multibit). The colours in a single 64bit word as bytes for foreground & background red, green, & blue; and (roughly) nybbles for decor red, green, & blue.
Overflowing Rows
In LibVTE rows can overflow the “ringbuffer” to disk via 4 layers of abstraction.
The lowermost layer wraps the LibC syscalls with error checking & seeking. Around that it implements “snakes” for determining where in the file to seek to. And around that it adds “boa” compression & encryption to prevent any other program from reading LibVTE’s memory. And finally it has a highlevel abstraction hiding all this, & a test suite.
LibVTE’s concept of “snakes” is essentually a truer ringbuffer (as opposed to what’s in RAM where lines are shifted around in), such that it yields 4 possible states.
There may be one (state 1) or two (state2) contiguous regions (2nd region preceding first), but once the head catches up to the tail it might append start appending to the end of the file again. At which point you have three contiguous regions (state 3) until enough the tail is deleted we’re back to two contiguous sections (state 4).
A “ringbuffer” refers to a datastructure which has finite pre-allocated storage such that when you fill it up you wrap around back to start overriding old entries.
A LibVTE “snake” tracks the theoretical & on-disk read & write indexes for each of it’s max 3 segments (which can be consulted as virtual memory table for random-access read/write), it’s state, underlying file, & redundant head/tail. It’s implemented in GObject rather than C++ for some reason.
LibVTE’s concept of “boas” wraps GNU LibTLS & ZLib to produce sized, overwrite-counted, & error-checked compressed then encrypted segments to store in the “snake”. These dependencies are technical optional, but a warning will be printed directly to this scrollback ring if LibTLS is missing.
The highlevel Stream GObject class adds read/write buffers, abstracts away any abstraction leakages, & adds some useful utils.
RingViews
Thanks to differing writing directions, etc the visual position of text onscreen in your terminal emulator may not fully line up with where they’re positioned by the commandline programs within the scrollback buffer. So after linewrapping (which does happen within the scrollback buffer) the text is copied into a “ringview” for layout.
This RingView
can be paused (freeing it’s fields) & automatically resumed (realloc’ing them) to save memory when the widget’s offscreen.
There’s setters (& getters) for it’s underlying scrollback buffer, width, bidi enable, & shaping enable which also sets dirty flag if they change. The rows setter further performs validation & allocs corresponding bidirows.
But mostly when the widget calls update
& the dirty flag is set it’ll resume the ringview if needed, locates the start of the top logical line, repeatedly reallocs & populates the corresponding bidirows within limits, before deferring each row to the bidirunner
.
The underlying BidiRows
consists of tables mapping between the “logical” & “visual” orders (which are consulted both for input & output), which glyphs are right-to-left, & the glyphs corresponding to the text allocated with enough space to fill the terminal’s gridwidth. These tables have wrapper methods.
The BidiRunner
wraps FriBiDi and to a lesser extent GLib unicode strings to populate those tables. It has seperate codepaths for implicit & explicit paragraphs, textlines, & Arabic.
For explicit paragraphs it iterates over each of it’s rows treating the row starting starting at that point as a line. Lines initializes initializes incrementing (implicitly) or decrementing tables before considering applying Arabic shaping using FriBiDi & surrounding logic to handle allocations & iteration.
For implicit paragraphs with mixed text directions it calls into different FriBiDi logic surrounded by allocations, iterations, & initialization.
Miscellaneous LibVTE Components
For buffering input text’s sake, and presumably historical reasons, it aliases GLib’s bytearray implementation under it’s namespace.
It declares C++ wrapper types around Cairo, GLib (including exception bridging), GObject, Graphene, GTK, LibICU (with a 2nd-layer abstraction), LibC’s file descriptors & errors, Pango, & LibPCRE2 (also with a 2nd-layer abstraction) to integrate them into C++’s garbage collection.
It defines some magic numbers to e.g. visually represent escape codes.
The “chunks” the stdout buffer is split into are (true) ringbuffers with a dedicated freelist; the wrapping is for when they overflow.
There’s a wrapper abstracting away GDK/GTK’s clipboard with their MIMEtypes.
Some trivial C++ utilities.
There’s heavily used debugging utilities for serializing internal data to it’s own stdout, to view in the parent terminal emulator.
There’s an abstraction around Cairo which aids rendering common geometries to LibVTE (mostly text & rectangles), whilst traversing the C/C++ language boundary. Rendered ASCII text for a font is cached via a seperate C++ class. Or uses manual rendering (seperate class) for special graphics chars (like tree
uses) in case the normal font doesn’t support them to render lines, etc.
There’s a class representing images (provided via escape/SIXEL codes on stdout) which can be rendered via Cairo.
There’s lookup tables for decoding GDK keys into text, actions, & stdin encodings.
It reimplements some LibC APIs in the event those aren’t available.
There’s wrappers around mode bitflags.
There’s a scanner, lexer, statemachine & macro-generated parser, linter, & text serializer for ANSI codes. And an alternative somewhat halfhearted one for XTerm control codes, which defers to a bunch of new methods on the widget.
The code to preprocessing copied text to reformat control codes is implemented in a seperate file.
There’s a couple refcounted PTY wrappers around the pthread/exec’d process, along with it’s semi-kernel-specific stdin/stdout/stderr (using posix_openpt
), it’s rendering an interface to. Triggering the TIOCSWINSZ & TIOCGWINSZ as specified by the widget. There’s a seperate class, and some utility functions, for spawning this process. Which includes a DBus call into logind (/org/freedesktop/systemd1)’s StartTransientUnit
method, if available.
There’s a GObject “Reaper” class used by the main widget implementation to cleanup any killed child processes.
There’s a couple classes to decode SIXEL images from stdout into a Cairo surface, with a dedicated fuzzer & test script.
There’s a class for tracking tabstops, to render tab chars.
There’s a custom UTF8 decoder.
There’s functions for comparing, hashing, & mutating Unicode strings.
There’s a utility function for generating tempfiles.
There’s a GObject ATK subclass which hooks the widget up to the Orca screenreader via DBus for the sake of accessible output, implementing conversions from text offsets to/from grid coordinates. There’s a few central methods gathering data & determining to emit the GObject signals notifying Orca, which are called directly or indirectly by the main widget implementation - possibly via it’s GObject signals.
Then there’s all the DBus methods for it to implement…
There’s various magic numbers relating to how stdout is encoded & how the terminal’s rendered.
There’s a public GObject wrapper around the C++ class representing the process being rendered, & it’s LibPCRE2 abstraction.
There’s an alternate, somewhat halfhearted, XTerm control codes wrapper. Adds additional methods to the widget representing the different XTerm control codes.
There’s code for parsing & serializing various internal types to/from strings.
There’s numerous testscripts for helping the LibVTE developers reach all the different codepaths.
And it bundles it’s own minimal app which can be used as a more accessible & international alternative to Linux’s builtin terminal, once the system has booted up enough!