Skip to main content

gui walkthrough

Every image below is rendered in CI from the real QML β€” the same src/qml/Wizard.qml and the same six step modules under modules/ that the shipped installer loads. Only src/main.cpp is swapped, for tests/capture.cpp. Nothing about the UI is mocked or redrawn.

The capture runs on the Qt offscreen platform plugin with the software scenegraph, inside a Fedora container that carries the KF6 runtime (Kirigami, Kirigami Addons, org.kde.desktop Qt Quick Controls style). See .github/workflows/screenshots.yml.

The TunaOS KDE installer, step by step

The steps​

WelcomeWelcome β€” what the wizard is about to do.
Target diskTarget disk β€” lsblk -J, filtered to whole disks, one FormCard radio per device. Re-read every time the step becomes current, so hotplugged media appear.
Disk encryptionDisk encryption β€” none, luks-passphrase, and (only when /sys/class/tpm/tpm0 exists) tpm2-luks and tpm2-luks-passphrase. The passphrase fields appear only for the options that take one.
ConfirmConfirm β€” the recipe as it will be written, and the only button in the app that starts an install.
InstallingInstalling β€” live fisherman output. No Back, no Next: the wizard advances itself when the process exits.
FinishedFinished β€” success or failure, with the exit code.

What the capture checks​

Existence checks are not enough. A sibling repository published a blank page while its "the PNGs exist and are non-empty" assertion passed. So tests/capture.cpp reads its own pixels back and fails the build when a screen did not really render:

  • distinct colours β€” a screen that never painted collapses to a handful.
  • ink β€” the fraction of sampled pixels far enough (Manhattan distance > 40) from the image's own dominant colour to be drawn content. Measuring against the image's own background rather than a fixed luma keeps this correct under a dark colour scheme.
  • row and column spread β€” the fraction of sampled rows, and of sampled columns, that contain any ink. This is what actually discriminates. Density alone does not: the welcome and finished steps are hero pages β€” one icon, a heading, a line of prose β€” and legitimately score around 1% ink, while a form step with a FormCard behind it scores 30%. A blank screen scores zero on all four.

Measured on the six real screens at 1000Γ—700 (CI run 31136541525), and the thresholds sit below these with margin (see the table at the top of tests/capture.cpp):

stepcoloursinkrowscols
01-welcome2341.26%25.1%44.9%
02-disk31522.03%41.2%53.9%
03-encryption36726.89%53.8%53.9%
04-confirm28830.20%56.8%53.9%
05-progress33415.77%100.0%100.0%
06-done1630.59%17.6%38.0%

Every run prints these, so a drift is visible in the log before it is a mystery. The capture also clears the output directory before rendering: a run that dies on the first screen must not leave the previous run's images there for the artifact upload to publish as its own.

The screenshot artifact is uploaded with if: always() β€” it is needed precisely when the capture fails.

Why this cannot start an install​

The harness drives navigation through Wizard.goToStep(), which moves the visible step and calls the target module's onPageActivated(). The only onPageActivated() in the tree is the disk step's, and it re-reads lsblk and nothing else. InstallerController.startInstall() β€” the sole path to a privileged fisherman process β€” has exactly one caller, the Install button's onClicked in Wizard.qml, which the harness never presses.

Worth stating explicitly: the sibling XFCE installer does kick off an install from its page-enter hook, so "drive the steps" is not automatically harmless across this family of frontends.