tinybox
Owner: IIIlllIIIllI URL: git@github.com:nyangkosense/tinybox.git
README
# Tinybox
I needed a simple way to build terminal interfaces in Go. Everything out there was either massive (tcell), abandoned (termbox), or tried to force some architecture on me (bubbletea with its Elm thing). I just wanted to draw stuff on the screen and read keyboard input without pulling in half the internet.
So I wrote Tinybox. It's one Go file, about 1000 lines. You can read the whole thing in an afternoon. Copy it into your project and modify it however you want. No dependencies, no build systems, no package managers.
https://github.com/user-attachments/assets/7a647922-77c9-458e-8052-3f4166ee9cdc
## What It Does
Tinybox gives you raw terminal access which means you get a grid of cells, you put characters in them, you call Present() to update the screen. That's basically the core of it.
It handles the annoying parts like entering raw mode, parsing escape sequences, tracking what changed so you're not redrawing everything constantly. Mouse events work. Colors work. You can catch Ctrl-Z properly. The stuff you'd expect.
The API is deliberately small. Init() to start, Close() to cleanup, SetCell() to draw, PollEvent() to read input. Maybe 30 functions total. If you need something that's not there, the code is right there - so you can simply add it yourself.
## How It Works
The terminal is just a 2D grid of cells. Each cell has a character, foreground color, background color, and some attributes like bold or underline. Tinybox maintains two buffers - what you're drawing to, and what's currently on screen. When you call Present(), it figures out what changed and sends only those updates to the terminal.
Input comes through as events - keyboard, mouse, resize. The event loop is yours to write. Tinybox just gives you the events, you decide what to do with them. No callbacks, no handlers, no framework nonsense.
Here's the simplest possible program:
```go
tb.Init()
defer tb.Close()
tb.PrintAt(0, 0, "some string")
tb.Present()
tb.PollEvent() // wait for key
```
Look at example.go if you want to see something more complex. It's a basic system monitor that shows how to handle resize, use colors, and create a simple table layout (screenshot). That sample leans on Linux's statfs fields; tweak the disk bits if you're building it on OpenBSD or the other BSDs.
The API won't change because there's no version to track. You have the code. If you need it to work differently, change it.
There’s also a tiny demo app under `demo/` if you want to poke at the API without writing boilerplate. It uses the same primitives (draw cells, poll events, toggle mouse) and nothing more.
## Example
```
make
```
```
./example
```
## Design Choices
The code reads top to bottom - constants, types, low-level terminal stuff, then the public API.
It sticks to the plain POSIX termios/ioctl calls. The `termios_*.go` pair only map native ioctl constants, `select_*.go` hides the syscall differences, and `fdset_posix.go` flips the right bits so `select` works the same everywhere.
Two background goroutines handle signals; one for terminal resize (SIGWINCH) and one for resume from suspension (SIGCONT). These run in the background but don't do any terminal drawing, just update internal state and queue events. The main program loop is single-threaded.
No Unicode normalization or grapheme clustering or any of that. The terminal handles displaying Unicode, we just pass it through.
### Colors
Colors use the 256-color palette because that's what every modern terminal supports.
## What's Included
The basics are all there, which means you can draw text anywhere on screen with colors and attributes. Tinybox drawing characters work for making borders and tables, mouse support includes clicks, drags, and scroll wheels. Terminal resize is handled automatically.
The dirty cell tracking means it's fast even over SSH. Only the cells that changed get updated. You can build surprisingly complex interfaces and they'll still feel snappy.
Suspend/resume works properly. Hit Ctrl-Z, do something in the shell, fg back, and your program continues where it left off. The terminal state is saved and restored correctly.
There's a buffer save/restore mechanism if you need to draw temporary overlays like menus or dialogs. Save the buffer, draw your popup, then restore when done.
## What's Not Included
No widget library. No buttons, text fields, or scroll views. Those are your problem. Tinybox gives you a canvas and input events - what you build is up to you.
No configuration files. No themes. No plugins. If you want different defaults, change the source.
No layout managers. You calculate where things go. It's not that hard.
No documentation beyond this README and the code itself. The function names are clear and the implementation is right there if you need details.
## Why Bother?
Sometimes you need a TUI and don't want to deal with ncurses or massive Go libraries. Sometimes you want code you can actually understand. Sometimes smaller is better.
Tinybox does exactly what it needs to do and nothing more. It's not trying to be everything for everyone.
If you need more, there are plenty of feature-rich alternatives. If you want less, you probably don't want a TUI at all.
LICENSE
Copyright (c) Sebastian M. 2025
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Commits
| Hash | Date | Author | Subject |
| 50aaee2 | 2025-11-01 17:01:40 +0100 | Sebastian | Merge pull request #1 from xplshn/proper |
| 537523f | 2025-10-21 00:30:25 -0300 | xplshn | add .gitignore |
| 5ec091c | 2025-10-21 00:23:30 -0300 | xplshn | switch to using github.com/nyangkosense/tinybox/pkg |
| 63708c1 | 2025-10-21 00:22:40 -0300 | xplshn | re-structure |
| e210eaa | 2025-09-28 10:10:56 +0200 | Sebastian | Update README.md |
| f8231b0 | 2025-09-27 09:23:55 +0200 | SM | mouse demo code |
| 8648b32 | 2025-09-24 11:15:51 +0200 | SM | rm showcase |
| 5c5a293 | 2025-09-24 11:09:36 +0200 | SM | tinyfm |
| 12b1f76 | 2025-09-24 10:47:25 +0200 | SM | readme |
| 6f8a049 | 2025-09-24 10:41:05 +0200 | SM | POSIX |
| 138cea3 | 2025-09-24 10:29:33 +0200 | SM | testing posix compliant |
| 5cc090d | 2025-09-24 10:13:10 +0200 | SM | syscall.Select now returns only the count |
| fff7665 | 2025-09-24 10:09:25 +0200 | SM | using posix compliant consts and ops |
| 610dc36 | 2025-09-20 12:05:18 +0200 | SM | readme |
| 82d818c | 2025-09-20 11:52:31 +0200 | SM | stream escape codes without Sprinf, keeps track of terminal state, and emits runes via utf8.EncodeRune, cutting allocations and redundant attribute resets |
| 6f20053 | 2025-09-06 15:12:31 +0200 | Sebastian | Update README.md |
| ad970e7 | 2025-09-06 15:08:59 +0200 | Sebastian | Update README.md |
| e75f2f3 | 2025-09-04 09:07:05 +0200 | Sebastian | Update README.md |
| efb99c1 | 2025-09-03 21:02:38 +0200 | Sebastian | Update README.md |
| d8147fc | 2025-09-03 20:52:39 +0200 | SM | SGR mouse format sends separate events for press and release |
| f7f6af0 | 2025-09-03 18:53:18 +0200 | SM | rm bin |
| e37e6a7 | 2025-09-02 16:02:59 +0200 | SM | savedbuffer - check if realloc needed |
| 28ea851 | 2025-09-02 15:28:35 +0200 | Sebastian | Update README.md |
| 891fa38 | 2025-09-02 13:21:34 +0200 | Sebastian | Update README.md |
| d8b24a7 | 2025-09-02 13:19:13 +0200 | Sebastian | Update README.md |
| bf53083 | 2025-09-02 13:05:40 +0200 | SM | readme |
| d580ce0 | 2025-09-02 12:50:41 +0200 | Sebastian | Update README.md |
| 4bc546b | 2025-09-02 12:22:50 +0200 | Sebastian | Update README.md |
| 91f3851 | 2025-09-02 12:22:28 +0200 | Sebastian | Update README.md |
| dee9ec4 | 2025-09-02 12:14:21 +0200 | Sebastian | markdown again... |
| 3a6c873 | 2025-09-02 12:13:59 +0200 | Sebastian | Update README.md |
| 939e65d | 2025-09-02 12:06:25 +0200 | Sebastian | Update README.md |
| 7f12499 | 2025-09-02 12:02:03 +0200 | SM | commit |