[10]entry 10 of 29
Brother Eye Terminal
this entry is written twice, in full
- 1.in brief
a desk terminal for live GitHub activity, in no_std Rust
- 2.in general
A miniature phosphor console that watches your commits land.
- 3.in particular
One no_std, heap-free crate renders the entire terminal: it runs in an SDL window today and will run on the RP2040 unmodified.
- 1.in brief
a small desk screen showing live GitHub activity, on a chip with no operating system
- 2.in general
A miniature green-on-black console that watches your commits land.
- 3.in particular
One piece of code, asking the machine for nothing, draws the whole screen: it runs in a desktop window today and will run on the real chip untouched.
context
Brother Eye is already a page on this site, a tour of a 3,139 line zsh config. This is the object that page describes wanting to be: a small always-on terminal sitting on the desk, booting into the same phosphor green on black, showing live GitHub activity instead of a static page. 240x135 pixels, seven rows of log, a blinking cursor, and a command line you can actually type into.
the constraint
The hardware has not been bought. That absence is the reason the workspace is shaped the way it is, rather than an excuse for it.
core/ is no_std2 with no allocator, no clock, and no concrete display type. It knows embedded_graphics::DrawTarget, fixed-capacity heapless4 buffers, and a 32 event ring, and nothing else. sim/ is the only crate permitted std, threads or HTTP, none of which exist on an RP2040, where WiFi and HTTP will come from embassy-net instead.
So the desktop simulator and the future firmware call the identical render(&state, &mut display). Command parsing, the statistics, the boot sequence and the layout all get proven now, against a real keyboard and a visible window, on a feedback loop no board can match.
what i built
- 1.
565 lines of no_std2 core: a heapless4 Deque ring 32 events deep, ten event kinds, a filter, and a full command parser, with no heap allocation anywhere in it
- 2.
A 240x135 renderer in a 6x10 mono font, roughly 39 columns wide: header, seven log rows, input line. Releases and pull requests draw as an inverted tag block, because selection here is inversion rather than a second colour
- 3.
A real command line, accepting lowercase letters, digits, space and hyphen: filter by event kind or by repo prefix, clear, refresh, help, quit. Kinds match exactly rather than by prefix, because push and pr would otherwise be ambiguous
- 4.
Statistics computed over the buffer alone: today counts events sharing the newest date, streak walks consecutive days backwards from it. Both are bounded by the 32 event ring and by GitHub’s own cap on the public feed, so neither claims to be an all-time figure
- 5.
A 281 line simulator that polls on a background thread and hands results to the render loop over a channel, where a manual refresh wakes that thread early instead of opening a second request path
the feed, and the rate limit
Unauthenticated GitHub allows 60 requests an hour. The simulator polls every 90 seconds, which is 40 an hour, so a manual refresh is free rather than borrowed. On a 403 or a 429 it reads X-RateLimit-Reset and sleeps until that moment, with a 30 second floor, instead of retrying into the wall.
The screen says so while it waits. A failed or throttled poll appends NET-ERR or RATE-LIMITED to the header, because the alternative is letting the last good screen sit there looking current. An unrecognised event type lands in a sys row for the same reason: an event nobody wrote a case for is still information, and dropping it would quietly shrink the log.
building it on macOS
Two workarounds live in .cargo/config.toml and look like clutter until you remove them. SDL25 is compiled from source because Homebrew’s sdl2 formula is now an alias for sdl2-compat, an SDL3 shim whose event codes do not match what the sdl2 crate expects, so linking the system library yields a window that ignores the keyboard.
That bundled build then pulls in an MFi joystick backend needing CoreHaptics, which is not linked by default, and ships a CMakeLists.txt older than CMake 4’s minimum-version floor. Hence one link argument and one policy override, a first build measured in minutes, and every build after it measured in seconds.
honest status
The software works. The hardware has not been ordered. The intended target is a Raspberry Pi Pico W stacked with a Pimoroni Pico Display Pack 2.0, a 240x135 ST7789 SPI panel carrying four buttons and two RGB LEDs, chosen because that pairing needs no soldering and no custom board for a first version.
What remains is a firmware/ crate on embassy-rs: an ST7789 driver over SPI, embassy-net for WiFi and HTTP, and the same core linked in untouched. Every line of terminal behaviour is already written, and already running.
The terminal is finished and provable on a desktop today, so the port to hardware is a display driver and a network stack, not a rewrite.
context
Brother Eye is already a page on this site, a tour of a 3,139-line terminal configuration. This is the object that page describes wanting to be: a small always-on screen sitting on the desk, starting up in the same green on black, showing live GitHub activity instead of a static page. A screen 240 by 135 pixels, seven rows of log, a blinking cursor, and a command line you can actually type into.
the constraint
The hardware has not been bought yet. That absence is the reason the project is shaped the way it is, rather than an excuse for the shape it happens to have.
The core of it assumes nothing: no operating system, no memory to borrow, no clock, and no idea what kind of screen it is drawing on. It knows how to draw, it knows how to hold a fixed number of events without ever asking for more room, and it knows nothing else. Only the desktop version is allowed the conveniences, and none of those will exist on the real chip.
So the desktop simulator and the future device call exactly the same drawing function. The commands, the statistics, the start-up sequence and the layout all get proved now, against a real keyboard and a visible window, on a cycle no physical board could match.
what i built
- 1.
565 lines of core that never asks the machine for memory: a ring holding the last 32 events, ten kinds of event, a filter, and a full command parser.
- 2.
A renderer for a 240 by 135 screen in a fixed-width font about 39 characters across: a header, seven rows of log, and an input line. Releases and pull requests are drawn as an inverted block, because on a one-colour screen the only way to highlight something is to invert it.
- 3.
A real command line accepting lowercase letters, digits, space and hyphen: filter by kind of event or by which repository, clear, refresh, help, quit. Kinds have to match exactly rather than by their first letters, because otherwise two of them would be ambiguous.
- 4.
Statistics worked out from the visible buffer alone: today counts the events sharing the newest date, and the streak walks backwards through consecutive days from there. Both are limited by the 32-event ring and by GitHub’s own cap on what it will report, so neither pretends to be an all-time figure.
- 5.
A 281-line simulator that fetches on a background thread and hands results to the drawing loop, where asking for a refresh wakes that thread early rather than opening a second way to make requests.
the feed, and the rate limit
GitHub allows 60 requests an hour without signing in. The simulator asks every 90 seconds, which is 40 an hour, so a manual refresh is spare capacity rather than borrowed. If it is turned away it reads how long it has been told to wait and sleeps until then, with a 30-second floor, instead of hammering at the wall.
And the screen says so while it waits. A failed or throttled fetch writes a short warning into the header, because the alternative is leaving the last good screen sitting there looking current. An event type nobody has written a case for still gets a row of its own, for the same reason: an event you cannot categorise is still information, and dropping it would quietly shrink the log.
building it on macOS
Two workarounds live in the build configuration and look like clutter until you take them out. The windowing library is compiled from source because the version the package manager now installs is a stand-in for a newer one whose key codes do not match, which gives you a window that ignores the keyboard entirely.
Building it from source then drags in a game-controller component that needs a system library nothing links by default, and ships build instructions older than the current build tool will accept. Hence one linker argument and one compatibility override: the first build takes minutes, and every build after it takes seconds.
honest status
The software works. The hardware has not been ordered. The intended target is a small wifi-capable board with a 240 by 135 screen stacked on top of it, carrying four buttons and two lights, chosen because that pairing needs no soldering and no custom board for a first attempt.
What remains is the on-device half: a driver for the screen, wifi and networking for the chip, and the same core linked in untouched. Every line of terminal behaviour is already written, and already running.
The terminal is finished and provable on a desktop today, so moving it to real hardware is a screen driver and a network connection, not a rewrite.