Modern line-oriented terminal emulator without support for TUIs
git clone https://git.8pit.net/saneterm.git
1# Design 2 3This document describe internal implementation details of `saneterm`. 4 5## Input Handling 6 7Input is handled line-wise, i.e. data is not written to the PTY until 8the user types a newline character. This allows implementing more 9advanced line editing features (e.g. readline-like keybindings) in10`saneterm`. This line-based design is inspired by [`9term`][9term man page].11Similar to `9term`, it also requires `saneterm` to disable local echo12via `termios(3)`. Applications forcing local echo (e.g. `ssh(1)`) are13currently not supported.1415The line-based input forms a contiguous document collected in a16[Gtk TextBuffer][gtk textbuffer]. Text can be edited anywhere in the17buffer. In order to determine which changes should be send to the PTY,18`saneterm` records the last output point of the child process. Only text19entered beyond this output point is written to the PTY.2021## Control Codes2223In the Unix world, terminal emulators are usually [character-orientated][char terms].24That is, each typed character is written directly to the PTY. That25includes control characters like backspace, ctrl+z, ctrl+c, et cetera.26The current line discipline settings determine how these characters are27supposed to be interpreted. For example, `^Z` (ctrl+z) causes the line28discipline to send a `SIGTSTP` signal by default. Details of the TTY29subsystem are also further described in an [article by Linus Ã…kesson][tty demystified].3031In the line-based context there are two possible approaches regarding32the handling of these [control characters][wikipedia c0 and c1]:33341. The corresponding ASCII code for the control character can be35 *buffered* in the line buffer. Essentially, it is treated as a normal36 character and send to the program when the user enters a newline.37 This is the approach employed by 9term.382. Special handling for control characters could be added to the39 terminal emulator itself by *intercepting* key bindings directly. For40 instance, ctrl+c could be hardcoded to always send the interrupt41 control code. This would allow bypassing the line-based buffer and42 sending control codes to the PTY directly.4344Presently, `saneterm` implements the latter approach. That is, custom45[Gtk signals][gtk signals] are defined for control commands, e.g.46`interrupt` for ctrl+c. These signals are then bound to pre-defined key47combinations, i.e. the `interrupt` signal is bound to `ctrl+c`. The48signal handler for the `interrupt` signal then determines the current49ASCII control character for `VINTR` using `termios(3)` and sends this50character to the PTY. At the moment, these key bindings are ignored if51the cursor is not at the buffer position where the next character would52be entered.5354### Buffering5556Line-based buffer of control characters, as done by `9term`, is also not57trivial to implement. It requires translating Gtk key events to ASCII58control characters and a printable representation of each control59character for the [Gtk TextView][gtk textview] used by `saneterm`.60Special care also needs to be taken to ensure that this printable61representation behaves like a single character. For instance, if the62printable representation for ctrl+z (`0x1a`) is `^Z`, a standard Gtk63`backspace` signal must remove the entire thing (i.e. the `^` and the64`Z` character) and not just the `Z` character.6566### Intercepting6768This approach seems more intuitiv in the Unix world. For instance, to69send a `SIGTSTP` signal one just has to press ctrl+z (as one would in a70character-orientated terminal) instead of pressing ctrl+z and then71enter. It does also have some caveats as keycodes are normally72configured using `termios(3)`. As an example, it possible to bind73`SIGINT` to a different keycode using `stty intr <keycode>` but since74`saneterm` keybindings are defined separately it would not respect that75setting. For the same reason, it is also difficult to support76noncanoical mode as defined in `termios(3)`.7778The `saneterm` handlers also need to query the `termios(3)` setting on79each Gtk signal to determine the current control character, which should80be send to the PTY, using `termios(3)`. Additionally, the line buffer81is bypassed on these signals and any data presently stored in it is82never received by the application. In this regarding `VEOF` (ctrl+d) is83handled in a special way as it also causes the current line buffer to be84written to the PTY.8586[9term man page]: https://9fans.github.io/plan9port/man/man1/9term.html87[gtk textbuffer]: https://developer.gnome.org/gtk3/stable/GtkTextBuffer.html88[tty demystified]: https://www.linusakesson.net/programming/tty/89[wikipedia c0 and c1]: https://en.wikipedia.org/wiki/C0_and_C1_control_codes90[gtk signals]: https://developer.gnome.org/gtk-tutorial/stable/x159.html91[gtk textview]: https://developer.gnome.org/gtk3/stable/GtkTextView.html92[char terms]: https://en.wikipedia.org/wiki/Computer_terminal#Character-oriented_terminal