The Touch Interface

The appliance boots into a fullscreen Qt Quick application on its 5 “ DSI panel, with no desktop, no login, and no keyboard. This chapter covers the surface: how the screens are arranged, how a keyboard-less kiosk does things a kiosk normally cannot, and the two settings subsystems with non-obvious behaviour — LED colours and display sleep.

The shell

Main.qml is the shell: a NavRail (Home, Review, Puzzles, Settings, and Broadcast when it applies) alongside a StackView that holds the current screen. Sub-screens map back to the rail entry that pushed them — the Wi-Fi and board-lighting screens both highlight Settings — so the rail shows the section you are in rather than the top of the stack.

The rail is hidden while the first-run wizard owns the screen (Chapter The First-Run Wizard), which is what stops setup being side-stepped before it completes.

Rotation

The panel is natively portrait (720$\times$<!-- -->{=html}1280); the Kirin UI is landscape. eglfs/KMS on the Pi does not honour QT_QPA_EGLFS_ROTATION — it rotates the reported geometry but not the scanout — so the rotation is done inside the scene graph instead: everything is authored against a fixed 1280$\times$<!-- -->{=html}720 stage, which is rotated a quarter turn when the physical panel is portrait. Qt Quick maps touch input through item transforms, so hit-testing stays correct with no extra work, and on a landscape desktop screen nothing is rotated at all.

Screens

Screen

Purpose

HomeScreen

Status, and the entry point to a new game.

VersionSelectScreen

The opponent ladder, colour, odds and time control (Ch. Playing a Game). Also the destination of review’s “play from here”, so the choices are identical rather than a second picker to maintain.

GameScreen

The live game: board rendered from session.fen, move list, captured trays, clocks, thinking banner, and the transport (Stop/Restart/Rescan/Takeback/Home). Also hosts the mismatch scrim and the Auto-vs-Hand restart prompt.

ReviewScreen

Games and Puzzles tabs (Ch. Match Review).

PuzzleScreen

Solving (Ch. Puzzles).

PlayFriendScreen

Host or join a networked game by code (Ch. Networked Play).

BroadcastScreen

Mirroring a tournament game (Ch. Broadcast Mirroring).

SettingsScreen

Wi-Fi, account link, Lichess, board lighting, updates, sleep, power.

SetupWizardScreen

First run (Ch. The First-Run Wizard).

ChessBoard.qml is the one board renderer, used live (two-tap origin $\rightarrow$ destination input, auto-queen, selection and last-move highlights, orientation flip) and non-interactively in review and broadcast.

Typing without a keyboard

A Wi-Fi password has to be typeable on a board with no keyboard, before any network exists. KeyboardPanel.qml is a plain-QML on-screen keyboard driving the focused text field directly — no input-method module, so there is nothing to configure on the Pi and no Qt Virtual Keyboard package to install. A physical keyboard still works for desktop development.

System administration that a kiosk normally delegates to a desktop lives in NetworkBridge: Wi-Fi through nmcli, power through systemctl. Every external command runs asynchronously through QProcess with arguments passed as a list — the shell is never involved, so an SSID or password cannot inject a command.

Warning

A cold Pi rfkill-blocks its Wi-Fi until the regulatory domain is set, and scans return nothing until then. The host provisioning script sets the country domain, installs the PolicyKit rule that lets the operator manage Wi-Fi without a password prompt, and puts the operator account in netdev. Without those three, the Wi-Fi screen looks broken rather than empty.

Board lighting settings

The LED subsystem (Chapter The LED Highlight Subsystem) is configurable from Settings: the categories (legal moves, recommendation, last move, check, errors), brightness, a preset, and a colour per category. Colours persist in QSettings — and how they persist is the part worth documenting, because the obvious implementation is wrong in a way that only shows up after a release.

D-36 — Persist only the colours that deviate from the factory palette, and stamp a schema version

Decided 2026-07 · Origin: kirin-gui/src/session_bridge.cpp · Status: in force

Context The first implementation saved the whole colour set whenever any LED setting was touched.

Decision Save a colour only when it differs from the factory default, and remove the key when it matches. A kLedColorsSchema constant is stamped alongside; on load, a board written by an older schema has its stored colour keys dropped once.

Rejected Saving everything. It bakes the current defaults into every board’s settings file, so a later change to a factory colour can never reach any board that has touched any LED setting — it reads its own saved copy of the old default back and shadows the new one. The bug is invisible on the bench, because a fresh board has nothing saved.

Consequences Changing a default LED colour requires bumping kLedColorsSchema, otherwise the change does not reach boards in the field. Categories the user never touched always follow the struct defaults, so improving the palette is a code change rather than a migration.

Display sleep

D-64 — Idle sleep blanks the panel backlight and wakes on touch — it is not system suspend

Decided 2026-07 · Origin: kirin-gui/src/display_bridge.h · Status: in force

Context A board sitting in a living room should not glow all night, but it is an appliance with a game, a gantry, sensors and network connections in flight.

Decision DisplayBridge writes the panel’s brightness sysfs node to 0 after an idle period and restores it on the next touch. The OS, engine, gantry, sensors, OLED and network all stay fully up.

Rejected System suspend. The Pi 5 has no usable suspend-to-RAM, and an appliance must not drop a game, a gantry position or a live connection to save a few watts.

Consequences The wake tap always arrives, because the Goodix touch controller sits on its own I^2^C bus and keeps delivering events whether the backlight is on or off — “kill the display, keep touch” is the panel’s natural behaviour rather than a trick. brightness is group-video writable on the appliance image and the operator account is in that group, so no root, polkit rule or udev rule is needed. On a dev box with no panel the path is absent and every method is a safe no-op.

Auto-sleep is inhibited while a live game is in progress — lifecycle Preparing, EngineThinking or HumanToMove — because a game legitimately sits idle for many minutes while the human thinks. Puzzles leave the session lifecycle Idle and therefore do not inhibit sleep, which is intended. A manual Sleep from the power menu is always allowed.

Both timings are environment-overridable (KIRIN_BACKLIGHT_DIR, KIRIN_SLEEP_MS) so the whole behaviour is testable with no panel and no hardware.

Power, and never pulling the plug

Shutdown is a first-class action, reachable from a power dialog on the shell and a confirmed row in Settings, routed through NetworkBridge::shutdown() to systemctl poweroff. The appliance is never powered off by pulling the plug mid-write — an SD card interrupted during a settings or archive write is how a board comes back without its games.