Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flipctld

A C11 reference implementation of the FlipCTL menu-UI core: declarative wrappers that turn CLI tools into D-pad-navigable menus on a tiny LCD. It is the production follow-up to the Python concept in flipctl-proto and consumes the same JSON manifest schema unchanged — the proto's demo-apps/*.json run here as-is.

libc only. No third-party dependencies, nothing to compile but the standard toolchain. make produces a single flipctld binary.

What this is

The proto validated the interaction model and manifest format using curses as a deliberate stand-in for a real renderer (see its DESIGN.md). This implementation does properly what the proto faked:

Proto faked flipctld does
curses renderer (can't do pixels) a render abstraction with two real backends: ANSI terminal and a Linux framebuffer that mmaps /dev/fb0 at 32bpp and blits an 8x16 bitmap font
terminal escape codes for input, hand-decoded a button-input abstraction with two sources: terminal keys and the kernel input layer (/dev/input/eventN, the way an embedded app reads its own front-panel buttons)
json stdlib module a small strict JSON reader written here (~510 lines incl. full string unescaping)
reader thread + queue non-blocking capture: a single poll() loop multiplexes the input source against the child's output pipe — no threads

The navigation model is identical to the proto: Up/Down move, Right = OK/open, with a screen stack where OK pushes and back pops.

Architecture

manifest.json ─▶ json.c (strict reader) ─▶ manifest.c (typed model + validation)
                                                  │
   surface.h ◀── viewport.c ──┐            screen.c: menu / form / output / result
   (render seam)              │            (logic objects behind a small vtable)
        ├─ surface_ansi.c     │                  │ build_command(): {param} expansion,
        └─ surface_fb.c   draw through            │ toggle→flag, empty tokens dropped
                          the viewport            ▼
   input.h ◀── main.c poll() loop ─────▶ runner.c: fork + execvp (no shell),
   (input seam)   multiplexes input fd     stdout+stderr → pipe → ring buffer
        ├─ input_term.c    and pipe fd          │
        └─ input_evdev.c                        ▼ feeds the output screen,
                                            then the result summary

Every screen is a struct behind a vtable (handle_key, draw, plus optional poll hooks). The render and input seams are the only things a new frontend replaces — screen logic never names a backend.

Modules

  • json.[ch] — strict recursive-descent JSON DOM (objects, arrays, strings with \uXXXX/surrogates, numbers, booleans, null; trailing data rejected).
  • manifest.[ch] — typed manifest model, form defaults, and build_command() (token-list templates, {param} substitution, toggle→flag, drop-if-empty).
  • surface.[ch], surface_ansi.c, surface_fb.c — the render abstraction and its two backends.
  • viewport.[ch] — the 40x20 drawing surface (the proto's Viewport): clipped text, rules, centered title/status bars.
  • input.[ch], input_term.c, input_evdev.c — the button-input abstraction and its two sources.
  • screen.[ch] — the four screens and the navigation verbs.
  • runner.[ch] — process launch + non-blocking capture into a line ring.
  • headless.c, main.c — CI pipeline and the wiring + poll() event loop.

Fonts

src/font8x16.h embeds the IBM PC/VGA BIOS 8x16 ROM font (code page 437 glyph order). Bitmap typefaces are not copyrightable in the US and this font has been redistributed freely for decades; provenance is documented in the header.

Build

make            # -Wall -Wextra -Werror, clean
make clean

Run

# Interactive, ANSI terminal backend + terminal keys (default):
./flipctld manifests/ping.json

# Linux framebuffer backend (renders to /dev/fb0) + physical buttons:
./flipctld manifests/nmap.json --backend fb --input buttons --input-dev /dev/input/event3

# Headless: build argv, run, stream captured output (no TTY needed) — for CI:
./flipctld manifests/ping.json --headless custom --set host=127.0.0.1 --set count=1
./flipctld manifests/ping.json --headless localhost

# Just print the constructed argv:
./flipctld manifests/nmap.json --dry-run scan --set services=on

Keys (terminal): arrows (Up/Down move, Right = OK/open, Left = back), Enter = OK, q quits. On the device the same five events come from the front-panel buttons via evdev.

If --backend fb is selected and /dev/fb0 is absent or not 32bpp, the program prints a clear message and exits non-zero — it never crashes.

Capturing screens

tools/capture.py drives flipctld in a pty and prints the rendered viewport as ASCII (mirrors the proto's tools/capture.py):

python3 tools/capture.py manifests/ping.json                 # menu
python3 tools/capture.py manifests/nmap.json right           # parameter form
python3 tools/capture.py manifests/ping.json down down right down right wait:3 right   # run → result

Manifest schema

Unchanged from the proto. A menu tree of leaves ({"label","action"}) and submenus ({"label","items":[...]}); actions carry an optional form and a command token list. Four field types — toggle (expands to its flag when on), enum (cycle options), int (step within min/max), text. A token that substitutes to empty is dropped from argv, so commands are always built as a clean argument vector — there is no shell at any point.

Real vs. still missing

Real now (was faked in the proto): pixel rendering via framebuffer, evdev button input, a hand-written JSON parser, thread-free non-blocking capture, and a clean render/input seam.

Still future work (matches the proto's DESIGN.md roadmap):

  • Daemon + socket protocol. This is still a single process that owns the screen and runs one app. Real FlipCTL is a system daemon that owns the input devices and renderer, multiplexes several apps (the App-Switcher button), and speaks a small Unix-socket protocol to plugins. The manifest format here is the static half of that protocol; the dynamic half (custom screens, progress, partial updates) needs message types.
  • Plugin process isolation. Plugins should run as separate processes with per-action capability grants (cap_net_raw for nmap -sS) rather than the current in-process exec. Long-running jobs should outlive a screen switch (a job table the UI re-attaches to), not be tied to the output screen.
  • Output post-processing (regex extractors so a 40-char screen shows "3 hosts up", not truncated lines), richer validation, on-screen keyboard widget, and apt-style packaging with a manifest directory scan as the root menu.

About

FlipCTL menu-UI core for Flipper One - JSON manifests, D-pad nav, ANSI + framebuffer

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages