Notes · Other Peoples' Talks · FOSDEM 2026 · Building a minimal cross-platform terminal UI library
https://fosdem.org/2026/schedule/event/ZPFNKB-lua-ui/
- Needed a terminal UI library for a luarocks client (luarocket)
- Goals:
- Cross-platform (POSIX/Mac/Windows)
- Non-blocking input, because of Lua's support for coroutines
- Mechanisms over policies (don't enforce event mechanisms)
- No external dependencies (sucks on Windows)
- Non-goals:
- No overlapping windows
- No mouse support
- Architecture: application atop terminal.lua (Lua) atop LuaSystem (C code) atop OSes
- Preferred LuaSystem to be OS-independent, but terminal is vastly different between OSes
- Easy part:
- ANSI in Windows, but not for cmd.exe
- Multi-byte encoding
- Windows does not really have UTF-8 support, and calculating display width is generally a problem across OSes
- UTF-8
- Implemented width functions for UTF-8 chars/strings
string.subreplacements for UTF-8
- Built a line editor:
EditLine- Holds a string
- Manipulated with a cursor
- Tracks position/size in characters and columns
- Used for general string manipulation
- Non-blocking keyboard input
- No non-blocking stdin on Windows
- Fell back to
conio.h(reading directly from the keyboard buffer) - On POSIX: have to read from stdin
- On Windows: have to read from conio, convert to UTF-8, do this in a loop, strip scancodes, etc. Really involved in comparison.
- Initialization:
- POSIX:
- Disable canonical mode
- Disable echo
- Detach FDs
- Set
stdinto non-blocking, but this can setstdoutto non-blocking, especially on Macs. This is because FDs can point to the same file, which can be very confusing.
- POSIX:
- Non-blocking:
- Three functions to read individual bytes: internal C, read single byte with timeout, read UTF8 character with timeout
- Using non-default sleep makes it non-blocking
- Querying is slow
- Terminal takes time to responds to query, which can involve sleeps (2-15ms), which can stack with multiple queries
- Faster to maintain your own declarative state (cursor shape, cursor visibility, scroll region, text attributes) and then query that instead
- Implemented a stack abstraction to maintain this state and to sync to the terminal
- Actually didn't need that many API functions to drive both POSIX and Windows
- Conclusions:
- Results were much better than expected, given the constraints of minimal primitives/no dependencies
- Uniform keyboard reading was uniform and non-blocking
- Display width is painful to deal with
- Querying is slow