Terminal

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:

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:

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:

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. wmoving 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:

Upon this it implements several standard field types including ones for:

There’s accessors for a form’s window, subwindow, userdata, size, & cursor pos & it’s window.

Fields have accessors for:


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 menus also has getters for

Their menu items have getters/setters for:

And getters for:

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:

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.

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.

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:

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.

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.

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!