Slint implementation of the Flipper One UI: the shared component library plus a demo binary that drives the 256x144 SPI panel, its buttons, and a browser view.
This is Phase 0 and Phase 1 of the flipctl UI plan. The library is intended to be
shared by flipperos-installer, flipper-boot-menu and flipctl; the first two of
those consume it today, the installer still draws its own components.
Our source is MIT. The distributed binary is GPL-3.0-only, because it links
Slint and we elect the GPL-3.0-only arm of its tri-license; sources stay publicly
buildable either way. THIRD-PARTY-LICENSES.md collects the license text of every
crate the panel binary links and is generated by
scripts/gen-third-party-licenses.sh from Cargo.lock, so it cannot drift.
The three pixel fonts in third_party/flipctl-fonts carry their own terms, and one
is not MIT: HaxrCorp 4090 is an adaptation of a CC BY-SA 3.0 font, so it and the
glyph table generated from it stay ShareAlike. REUSE.toml records that per file,
and reuse lint checks it.
The same applies to an app under apps/ that draws with the framework: it links
flipctl-app, and so Slint, so its binary is GPL-3.0-only too while its source
stays MIT. Each app has a THIRD-PARTY-LICENSES.md of its own, generated by the
same script, which travels inside its bundle.
So, in the terms the rest of the org states this in:
- GPL-3.0-only: the whole binary, flipctl's and any framework app's, because it statically links Slint
- MIT: our source, and the Busy9px font
- CC-BY-SA-3.0: the HaxrCorp 4090 font, and the glyph table generated from it
- Unlicense: the Born2bSportyV2 font
| Path | What |
|---|---|
crates/flipper-ui/tokens.toml |
Every colour, offset, radius, timing and count. The single source of truth. |
crates/flipper-ui/build.rs |
Generates the Rust and Slint themes from it, and compiles the components. |
crates/flipper-ui/ui/ |
root.slint (the one window), idle.slint, list.slint, statusbar.slint, frame.slint. |
crates/flipper-ui/src/app.rs |
An app's manifest, what it needs installed, and how it is launched. |
crates/flipper-ui/src/bundle.rs |
The ~/Apps walk, and the AppImages in it: read without being run, remembered by stamp. |
crates/flipper-ui/src/script.rs |
A script as an app: the manifest in its head, and the runtime it is run through. |
crates/flipper-ui/src/ipc.rs |
The socket a desktop hands a bundle to, behind flipctl open. |
crates/flipper-ui/src/status.rs |
Live battery, temperatures, power, hostname, profile and addresses, from sysfs and procfs. |
crates/flipper-ui/src/kms.rs |
DRM/KMS panel sink. |
crates/flipper-ui/src/evdev.rs |
Raw evdev button reader, no libinput. |
crates/flipper-ui/src/remote/ |
Browser view: frame stream, input endpoint, comparison page. |
crates/flipper-ui/src/tui/ |
The same view models in a character grid, for a serial console. Cursive over crossterm, behind the tui feature. |
tools/appimage/ |
The bundler: an app under apps/ to an aarch64 AppImage, on the host. ci/bundle.Dockerfile is its container. |
tools/ |
Converters for the prototype's packed fonts and 6-bit greyscale sprites. |
crates/flipper-tokens/ |
The theme generators, as a library both flipctl's build script and an app's can call. |
crates/flipctl-app/ |
The framework a hosted app draws itself with: the window, the keys, the status global, and the widget library. |
docs/apps.md |
How to write an app. |
docs/inventory.md |
The design system as measured, and where fake-flipctl2's own docs have drifted from its code. |
docs/dependencies.md |
What the binary links, and why, with measurements. |
The host toolchain usually has no aarch64 std, so this is built on the device or in the dev container, never on the local host.
export CARGO_PROFILE_RELEASE_LTO=false CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16
cargo build --release -p flipctl --features device,slint,remote
LTO off and 16 codegen units are for build speed while iterating; drop both for a size-measured build.
What runs on the machine is /usr/bin/flipctl under flipctl.service, the same
one the image boots. A deploy installs over that path and restarts the unit; the
build tree is where the build happens and nothing more. From the host:
./build_deploy.sh --panel # the panel and its buttons
./build_deploy.sh # browser only, leaving the panel alone
./build_deploy.sh --cross # build here for aarch64 in docker, not on the device
./build_deploy.sh --apps # also push target/appimage/*.AppImage to ~/Apps
The build happens on the device by default, because that is where the toolchain for
it is: about 25s once its target directory is warm. --cross builds here instead,
against ci/cross.Dockerfile, and pushes the binary. Nothing in flipctl links a C
library except libc, so a cross gcc is the whole of what a host needs, and a cold
build measures 3m against 20m on the device. Warm, the device wins; cold, this does.
or on the device, in the tree the deploy copied over:
./run_me.sh
A deploy brings the binary and the browser view's assets. Apps are AppImages in
~/Apps on the device, built by tools/appimage/build.sh and pushed with --apps
(docs/apps.md); a deploy without it leaves them alone. It also installs what a
bundle needs from the machine until the image ships it: fuse3, and the udev rule,
group and modules listed in systemd/README.md.
The mode, the port and the peer live in a deploy drop-in,
/etc/systemd/system/flipctl.service.d/50-deploy.conf, rewritten by every deploy.
Delete it and the machine runs exactly what the image ships: --panel on 8899
with the installed assets.
systemd-run --unit=flipctl is not an option, and the error is worth knowing:
the image installs /etc/systemd/system/flipctl.service, and a transient unit
cannot take a name that already has a fragment file on disk, stopped or not
(Unit flipctl.service was already loaded or has a fragment file).
A one-off out of the build tree is still the way to measure, since it takes flags the unit does not carry, and it needs the panel's owner stopped first:
sudo systemctl stop cog-seat1.service flipctl.service
sudo ./target/release/flipctl --panel
Then open http://<device>:8899/: our screen, an overlay that counts differing
pixels against a peer if one is set, and a control pad laid out like the hardware.
--frames N exits after N commits and reports timings; --bench measures the
ceiling.
8899 is the port the fake-flipctl2 prototype's own server used to hold, and cog
still loads http://localhost:8899, so flipctl now answers the URL that kiosk
opens. There is no default peer for the same reason: comparing against 8899 would
be comparing against ourselves. Point --peer at a prototype wherever one is
actually running:
PEER=127.0.0.1:8900 ./build_deploy.sh # a prototype moved off 8899
The two cannot share a machine on one port, and the prototype is what has to move, since its server binds 8899 and its browser asks for it.
cargo test -p flipper-ui --features slint,remote
FLIPPER_UI_BLESS=1 regenerates the goldens. Most goldens come from our own
output, so they only prove self-consistency; tests/reference/soft_button_bar.png
is derived from the design export instead, and is the one fixture that can catch
the implementation being confidently wrong.