TERMKEY(7)

πŸŽ€ TERMKEY(7) πŸŽ€

Hiii bestie, welcome to TERMKEY(7) from the Miscellaneous Information Manual β€” it's the manual page for termkey, and no cap, this one is a whole journey. Grab a snack, we're doing this in two parts because there is so much tea to spill. β˜•βœ¨


πŸ’– NAME πŸ’–

termkey β€” terminal keypress reading library


πŸ¦‹ DESCRIPTION πŸ¦‹

Okay so, termkey</p> is a library that lets your program read and interpret keypress and other events from a terminal. The way that it understands the encoding schemes terminals use to encode keypresses, and UTF-8 too, so it can hand you back actual events that represent key events? Iconic. Obsessed. πŸ’…

termkey operates in a pseudo object-oriented fashion. It gives you exactly one function, termkey_new(3), that returns a pointer to a newly-allocated structure. Every other function takes that pointer as its first argument β€” that's the whole bit, that pointer is your instance, she's the main character. 🌟

A typical use of this library would be: call termkey_new() to construct a new instance representing the stdin stream, then use termkey_waitkey(3) to wait for and interpret key press events. And when your program is done with her, termkey_destroy(3) deallocates all the resources the instance was using. Clean exit, slay. ✨


🌈 Reading Events 🌈

Each instance of a termkey structure can be used by the program in one of three ways:

  • Synchronously β€” blocking to wait for keypresses from a filehandle.
  • Asynchronously β€” returning keypresses if they're available, while co-operating with a non-blocking program.
  • Abstractly β€” interpreting key press bytes fed to it directly by the containing program.

To get the next key event synchronously, a program may call termkey_waitkey(3). She'll either return an event from the internal buffer, or block until a key is available, returning it the moment it's ready. It behaves similarly to getc(3), fgetc(3), or similar β€” except it understands and returns entire key press events rather than single bytes. Fr fr, glow-up of the century. πŸ’…

To work with an asynchronous program, you use two other functions:

  1. termkey_advisereadable(3) β€” tells a termkey instance that more bytes of input may be available from its file handle, so it should call read(2) to go get them.
  2. termkey_getkey(3) β€” the program can then call this to extract key press events out of the internal buffer, in a way similar to termkey_waitkey().

And finally, bytes of input can be fed directly into the termkey instance by calling termkey_push_bytes(3). This may be useful if the bytes have already been read from the terminal by the application, or even in situations that don't directly involve a terminal filehandle at all. Because of these situations, it's possible to construct a termkey instance not associated with a file handle, by passing -1 as the file descriptor. Yes, -1. She's unbothered, she's free. πŸ¦‹

A termkey instance contains a buffer of pending bytes that have been read but not yet consumed by termkey_getkey(3). Here's the buffer squad:


πŸ”‘ Key Events πŸ”‘

Key events are stored in structures. Each structure holds details of one key event. This structure is defined as follows:

typedef struct {
    TermKeyType type;
    union {
        long       codepoint; /* TERMKEY_TYPE_UNICODE  */
        int        number;    /* TERMKEY_TYPE_FUNCTION */
        TermKeySym sym;       /* TERMKEY_TYPE_KEYSYM   */
    } code;
    int modifiers;
    char utf8[7];
} TermKeyKey;

The type field indicates the type of event, and it's the one that decides which member of the code union is valid. It'll be one of these constants, and every single one of them matters, so here's the full roster:

  • TERMKEY_TYPE_UNICODE β€” a Unicode codepoint. This value indicates that code.codepoint is valid, and will contain the codepoint number of the keypress. In Unicode mode (if the TERMKEY_FLAG_UTF8 bit is set) this will be its Unicode character number. In raw byte mode, this will contain a single 8-bit byte.
  • TERMKEY_TYPE_FUNCTION β€” a numbered function key. This value indicates that code.number is valid, and contains the number of the numbered function key.
  • TERMKEY_TYPE_KEYSYM β€” a symbolic key. This value indicates that code.sym is valid, and contains the symbolic key value.
  • TERMKEY_TYPE_MOUSE β€” a mouse button press, release, or movement. The code structure should be considered opaque; termkey_interpret_mouse(3) may be used to interpret it.
  • TERMKEY_TYPE_POSITION β€” a cursor position report. The code structure should be considered opaque; termkey_interpret_position(3) may be used to interpret it.
  • TERMKEY_TYPE_MODEREPORT β€” an ANSI or DEC mode value report. The code structure should be considered opaque; termkey_interpret_modereport(3) may be used to interpret it.
  • TERMKEY_TYPE_DCS β€” a DCS sequence including its terminator. The code structure should be considered opaque; termkey_interpret_string(3) may be used to interpret it.
  • TERMKEY_TYPE_OSC β€” a OSC sequence including its terminator. The code structure should be considered opaque; termkey_interpret_string(3) may be used to interpret it.
  • TERMKEY_TYPE_UNKNOWN_CSI β€” an unrecognised CSI sequence. The code structure should be considered opaque; termkey_interpret_csi(3) may be used to interpret it.

The modifiers bitmask is composed of a bitwise-or of the constants TERMKEY_KEYMOD_SHIFT, TERMKEY_KEYMOD_CTRL and TERMKEY_KEYMOD_ALT. 🫢

The utf8 field is only set on events whose type is TERMKEY_TYPE_UNICODE. Do not read it for other events, bestie, I'm begging. πŸ™ˆ

Key events that represent special keys (type is TERMKEY_TYPE_KEYSYM) have with them a symbolic value that identifies the special key, in code.sym. Two helpers for that:

A pair of functions is also provided to convert between key events and strings:

And key events may be compared for equality or ordering by using termkey_keycmp(3). 🌸


πŸŽ›οΈ Control Flags πŸŽ›οΈ

Details of the behaviour of a termkey instance are controlled by two bitmasks of flags. Here's who does what:

The following control flags are recognised. All of them. Every single one. πŸ’…

  • TERMKEY_FLAG_NOINTERPRET β€” Do not attempt to interpret C0 codes into keysyms. Instead report them as plain Ctrl-letter events.
  • TERMKEY_FLAG_CONVERTKP β€” Convert xterm's alternative keypad symbols into the plain ASCII codes they would represent.
  • TERMKEY_FLAG_RAW β€” Ignore locale settings; do not attempt to recombine UTF-8 sequences. Instead report only raw values.
  • TERMKEY_FLAG_UTF8 β€” Ignore locale settings; force UTF-8 recombining on. This flag overrides TERMKEY_FLAG_RAW.
  • TERMKEY_FLAG_NOTERMIOS β€” Even if the terminal file descriptor fd represents a TTY device, do not call the tcsetattr(3) termios function on it to set it to canonical input mode.
  • TERMKEY_FLAG_SPACESYMBOL β€” Report space as being a symbolic key rather than a Unicode codepoint. Setting or clearing this flag in fact sets or clears the TERMKEY_CANON_SPACESYMBOL canonicalisation flag.
  • TERMKEY_FLAG_CTRLC β€” Disable the SIGINT behaviour of Ctrl-C. If this flag is provided, then Ctrl-C will be available as a normal keypress, rather than sending the process group a SIGINT. This flag only takes effect without TERMKEY_FLAG_NOTERMIOS; with it, none of the signal keys are disabled anyway.
  • TERMKEY_FLAG_EINTR β€” Without this flag, IO operations are retried when interrupted by a signal (EINTR). With this flag the TERMKEY_RES_ERROR result is returned instead.
  • TERMKEY_FLAG_NOSTART β€” This flag is only meaningful to the constructor functions termkey_new(3) and termkey_new_abstract(3). If set, the constructor will not call termkey_start(3) as part of the construction process. The user must call that at some future time before the instance will be usable.

The following canonicalisation flags are recognised:

  • TERMKEY_CANON_SPACESYMBOL β€” If this flag is set then a Unicode space character is represented using the TERMKEY_SYM_SPACE symbol. If this flag is not set, it is represented by the U+0020 Unicode codepoint.
  • TERMKEY_CANON_DELBS β€” If this flag is set then an ASCII DEL character is represented by the TERMKEY_SYM_BACKSPACE symbol. If not, it is represented by TERMKEY_SYM_DEL. An ASCII BS character is always represented by TERMKEY_SYM_BACKSPACE, regardless of this flag. No cap. 🫢

🧚 Multi-byte Events 🧚

Special keys, mouse events, and UTF-8 encoded Unicode text are all represented by more than one byte. So if the start of a multi-byte sequence is seen by termkey_waitkey(), it will wait a short time to see if the remainder of the sequence arrives. And if the sequence remains unfinished after this timeout, it will be returned in its incomplete state. Here's how the leftovers show up:

  • Partial escape sequences are returned as an Escape key (TERMKEY_SYM_ESCAPE) followed by the text contained in the sequence.
  • Partial UTF-8 sequences are returned as the Unicode replacement character, U+FFFD.

The amount of time the termkey instance will wait is set by termkey_set_waittime(3), and is returned by termkey_get_waittime(3). Initially it will be set to 50 miliseconds. (Yes, that's exactly how the page spells it, and I'm keeping it character for character because that's the rule πŸ’€)


🐭 Mouse Events 🐭

The TERMKEY_TYPE_MOUSE event type indicates a mouse event. The code field of the event structure should be considered opaque, though modifiers will be valid. In order to obtain the details of the mouse event, call termkey_interpret_mouse(3) passing the event structure and pointers to integers to store the result in. 🫢

termkey recognises three mouse protocols:

  • the original X10 protocol (CSI M followed by three bytes),
  • SGR encoding (CSI < ... M, as requested by CSI ? 1006 h),
  • and rxvt encoding (CSI ... M, as requested by CSI ? 1015 h).

Which encoding is in use is inferred automatically by termkey, and does not need to be specified explicitly. She just knows. πŸ’…βœ¨


okay bestie that's part 1 β€” part 2 is coming, don't go anywhere 🌈

πŸŽ€ Position Events πŸŽ€

okay so when you get a TERMKEY_TYPE_POSITION event, that's your terminal telling you where the cursor is at. slay ✨ it's basically the terminal answering back β€” usually because you asked it with the Report Cursor Position command, which is CSI ? 6 n.

the event bytes themselves are opaque (mysterious, iconic, we don't peek πŸ™ˆ), but you can get the actual goods by calling termkey_interpret_position(3) β€” just hand it the event structure plus pointers to integers where you want the result stashed. easy πŸ’…

real talk though bestie: only a DEC CPR sequence (CSI ? R) gets recognised here. the non-DEC prefixed CSI R does not count, and that's on purpose β€” because that one could literally be read as the F3 function key instead. so like, careful 🫢

🌸 Mode Reports 🌸

TERMKEY_TYPE_MODEREPORT is the vibe when your terminal sends back an ANSI or DEC mode report. it's typically what you get in response to the Request Mode command β€” either CSI $p or CSI ? $p, depending on which flavour we're talking.

same deal as before: the event bytes are opaque, no cap πŸ«₯, but you can pull the info out by calling termkey_interpret_modereport(3) and passing the event structure along with pointers to integers to store the result in. yes bestie, pointers to integers, you got this πŸ’ͺ


πŸ¦‹ Control Strings πŸ¦‹

now TERMKEY_TYPE_DCS and TERMKEY_TYPE_OSC are the two event types for a DCS or an OSC control string. these usually show up when your terminal is responding to similar kinds of strings that the application sent out as queries β€” like, you asked, it answered, we love a two-way conversation πŸ’¬

the event bytes are opaque again (as per usual, the drama 😭), but the body of the string itself? that you can grab by calling termkey_interpret_string(3) immediately after this event lands.

⚠️ and this part is so important: the underlying termkey instance can only hold one pending string at a time. so you gotta call that function in a timely manner β€” soon after the event arrives. at the very least, before you call any other functions that will insert bytes into or remove key events from the instance. don't let it get overwritten, queen πŸ‘‘πŸ’€

πŸ’œ Unrecognised CSIs πŸ’œ

TERMKEY_TYPE_UNKNOWN_CSI means the termkey ran into a CSI sequence it just... does not recognise. rude of it, honestly. 😀 it has been extracted from the stream though, so it's still available for you to inspect by calling termkey_interpret_csi(3).

but listen: if you wanna look at that sequence, you do it immediately β€” before any other IO operations on the termkey instance. and yes, specifically before calling termkey_waitkey() or termkey_getkey() again. otherwise the buffer space that sequence is chilling in gets overwritten and it's gone forever, no cap 😭

the good news? other types of key event don't have this problem at all πŸŽ‰ because the TermKeyKey structure is enough to hold all the information required. so it's just this one diva of an event type that needs the special treatment ✨


⭐ SEE ALSO ⭐


and that's the whole page bestie!! 🫢 this was the TERMKEY(7) manual page, section 7, and that little footer down at the bottom is just its name sitting pretty β€” TERMKEY(7). go forth and handle your key events, main character πŸŒˆπŸ’–πŸ§š