diff --git a/builder/build-iso.sh b/builder/build-iso.sh index 6f919d0..78e0b4a 100755 --- a/builder/build-iso.sh +++ b/builder/build-iso.sh @@ -117,7 +117,7 @@ cp "/tmp/$NODE_FILENAME" "$build_cache_dir/airootfs/opt/packages/" # The selected omarchy-settings package is needed here so its post_install hook # drops Omarchy's plymouthd.conf into /etc/plymouth before mkarchiso builds the # live initramfs. -arch_packages=(linux-t2 git gum jq openssl plymouth python-terminaltexteffects tzupdate omarchy-keyring "$OMARCHY_SETTINGS_PACKAGE" lvm2 cryptsetup parted) +arch_packages=(linux-t2 git gum jq openssl plymouth ttfx tzupdate omarchy-keyring "$OMARCHY_SETTINGS_PACKAGE" lvm2 cryptsetup parted) printf '%s\n' "${arch_packages[@]}" >> "$build_cache_dir/packages.x86_64" # The live ISO boots linux-t2 (see airootfs/etc/mkinitcpio.d/linux-t2.preset), so diff --git a/configs/airootfs/root/configurator b/configs/airootfs/root/configurator index 44e428b..6a53d0a 100644 --- a/configs/airootfs/root/configurator +++ b/configs/airootfs/root/configurator @@ -121,36 +121,42 @@ greeter() { # ColorShift the logo: a green base (indexed color 2) with a cyan accent (6) # drifting through, settling on green. Indexed ANSI colors, not hex — the - # framebuffer console can't render tte's truecolor faithfully (it crushes the + # framebuffer console can't render truecolor faithfully (it crushes the # palette to a muddy lavender), but the 16 indexed colors map to the Tokyo - # Night palette and render true. One long-running invocation (many cycles) so - # it never restarts — a restart is what flashed. The effect reads from - # /dev/null so it never swallows the Return; --reuse-canvas paints upward from - # the saved cursor, anchored one row below the logo. - if command -v tte >/dev/null 2>&1; then - printf '\033[%d;1H\0337' "$((logo_row + logo_h))" - # Run tte directly (not inside a `while` subshell) so $anim is tte's own - # PID: killing a wrapping subshell would orphan tte, which then keeps - # painting the logo over the keyboard step. --cycles is high enough that it - # never ends on its own before Return. - tte -i "$LOGO_PATH" \ - --canvas-width "$cols" \ - --anchor-text c \ - --frame-rate 60 \ - --reuse-canvas \ - colorshift \ - --gradient-stops 2 10 6 10 \ - --gradient-frames 3 \ - --cycles 1000 \ - --final-gradient-stops 2 \ - /dev/tty 2>/dev/null & - anim=$! - fi + # Night palette and render true. --xterm-colors is what keeps them indexed: + # ttfx resolves even indexed stops to truecolor otherwise, which would put the + # settle color out of step with the `gum style --foreground 2` logo underneath. + # One long-running invocation (many cycles) so it never restarts — a restart is + # what flashed. The effect reads from /dev/null so it never swallows the + # Return; --reuse-canvas paints upward from the saved cursor, anchored one row + # below the logo. + printf '\033[%d;1H\0337' "$((logo_row + logo_h))" + # Run ttfx directly (not inside a `while` subshell) so $anim is ttfx's own + # PID: killing a wrapping subshell would orphan ttfx, which then keeps + # painting the logo over the keyboard step. --cycles is high enough that it + # never ends on its own before Return. + # Two columns narrower than the console: ttfx centers its text two columns + # right of plain centering, so a full-width canvas would sit the animated + # logo a column off the gum-drawn one underneath it. + ttfx -i "$LOGO_PATH" \ + --canvas-width "$((cols > 2 ? cols - 2 : cols))" \ + --anchor-text c \ + --frame-rate 60 \ + --reuse-canvas \ + --xterm-colors \ + colorshift \ + --gradient-stops 2 10 6 10 \ + --gradient-frames 3 \ + --cycles 1000 \ + --final-gradient-stops 2 \ + /dev/tty 2>/dev/null & + anim=$! IFS= read -r _ /dev/null; wait "$anim" 2>/dev/null || true; } - # tte leaves the tty in raw/no-echo mode when killed; restore it or the gum + # Tolerate ttfx's kill signal in `wait` so a future `set -e` here can't abort. + kill "$anim" 2>/dev/null + wait "$anim" 2>/dev/null || true + # ttfx leaves the tty in raw/no-echo mode when killed; restore it or the gum # prompts in the keyboard step that follows silently die. stty sane /dev/null || true # Reset the screen so the leftover animation frame doesn't linger under the diff --git a/configs/airootfs/usr/local/bin/omarchy-install-dashboard b/configs/airootfs/usr/local/bin/omarchy-install-dashboard index bf6da9d..82e1f34 100755 --- a/configs/airootfs/usr/local/bin/omarchy-install-dashboard +++ b/configs/airootfs/usr/local/bin/omarchy-install-dashboard @@ -142,8 +142,18 @@ LOGO_HEIGHT="$(logo_height)" (( LOGO_WIDTH > 0 )) || LOGO_WIDTH=81 (( LOGO_HEIGHT > 0 )) || LOGO_HEIGHT=1 CONTENT_WIDTH="$LOGO_WIDTH" +# Rows the progress screen occupies: logo, blank, title, blank, bar, blank, tip. +PROGRESS_HEIGHT=$((LOGO_HEIGHT + 6)) +TOP_ROW=1 DYNAMIC_ROW=$((LOGO_HEIGHT + 3)) +LAST_SIZE="" LAST_PM=10 +# The finish screen mirrors the greeter's frame — logo, blank, one line, gap, +# one button — so the last thing the installer shows matches the first. It runs +# one row taller: `gum confirm` always paints prompt/blank/button and clears all +# three, so the button cannot sit closer than two rows under a title we drew. +FINISH_HEIGHT=$((LOGO_HEIGHT + 5)) +FINISH_TOP_ROW=2 # ── Install progress ───────────────────────────────────────────────────────── # @@ -389,15 +399,40 @@ current_tip() { printf '%s' "${tips[$(( (NOW - DASH_T0) / 8 % ${#tips[@]} ))]}" } +# Vertically center a block of the given height, echoing the row it starts on. +# Both screens here center the way the configurator's greeter centers its +# welcome, so the install opens, runs, and closes on the same composition +# rather than stacking each frame from row one. +centered_top_row() { + local height="$1" rows top + rows="$(term_size)" + rows="${rows%% *}" + [[ $rows =~ ^[0-9]+$ ]] || rows=24 + top=$(( (rows - height) / 2 )) + (( top < 0 )) && top=0 + printf '%d' "$(( top + 1 ))" +} + +measure_progress_layout() { + LAST_SIZE="$(term_size)" + TOP_ROW="$(centered_top_row "$PROGRESS_HEIGHT")" + DYNAMIC_ROW=$(( TOP_ROW + LOGO_HEIGHT + 1 )) +} + render_static() { - printf '%s' "$CLEAR" - blank_line + measure_progress_layout + printf '%s%s%d;1H' "$CLEAR" "$CSI" "$TOP_ROW" render_logo blank_line } render_dynamic() { local pm + # The console can still resize under us (the framebuffer settles late in VMs). + # Horizontal padding is recomputed every frame anyway, but the logo is drawn + # once, so redraw the static half at the new center rather than leave the + # centered layout stranded against a stale geometry. + [[ "$(term_size)" == "$LAST_SIZE" ]] || render_static set_now install_progress pm=$PROGRESS_PM @@ -452,30 +487,40 @@ render_finish() { local duration effect_canvas_width duration="$(install_duration || true)" duration="${duration:-Complete}" + # ttfx centers its text two columns right of plain centering, so hand it a + # canvas two columns narrower and the animated logo lands exactly on the + # static one left_padding already drew. Otherwise the logo jumps a column + # sideways when the effect starts and again when it ends. effect_canvas_width="$(term_cols)" - (( effect_canvas_width > 1 )) && effect_canvas_width=$((effect_canvas_width - 1)) + (( effect_canvas_width > 2 )) && effect_canvas_width=$((effect_canvas_width - 2)) + + # Set before anything that can fail, so reboot_prompt still lands on the + # button row even if the logo or the effect below never renders. + FINISH_TOP_ROW="$(centered_top_row "$FINISH_HEIGHT")" { - printf '%s%s' "$SHOW_CURSOR" "$CLEAR" - blank_line + printf '%s%s%s%d;1H' "$SHOW_CURSOR" "$CLEAR" "$CSI" "$FINISH_TOP_ROW" render_logo blank_line center "Installed Omarchy in ${duration}" "$CONTENT_WIDTH" blank_line } >"$TTY_PATH" - if command -v tte >/dev/null 2>&1 && command -v timeout >/dev/null 2>&1 && [[ -f $LOGO_PATH ]]; then - # TTE reuse mode restores a saved cursor before moving up over its input. - # Save this anchor so the effect redraws the same logo rows shown above. - printf '%s%d;1H\0337' "$CSI" "$((LOGO_HEIGHT + 2))" >"$TTY_PATH" - timeout 8s tte -i "$LOGO_PATH" \ + if [[ -f $LOGO_PATH ]]; then + # Reuse mode restores a saved cursor before moving up over its input. Save + # this anchor so the effect redraws the same logo rows shown above. + printf '%s%d;1H\0337' "$CSI" "$((FINISH_TOP_ROW + LOGO_HEIGHT))" >"$TTY_PATH" + # Smoke runs ~4s at 60fps, so the 8s cap is a stuck-process guard rather than + # a trim. The rate is the effect's pacing: the 260 this carried for laseretch + # finishes smoke in under a second, which reads as a flicker, not a send-off. + timeout 8s ttfx -i "$LOGO_PATH" \ --canvas-width "$effect_canvas_width" \ --anchor-text c \ - --frame-rate 260 \ + --frame-rate 60 \ --reuse-canvas \ - laseretch \ + smoke \ >"$TTY_PATH" 2>/dev/null || true - printf '%s%d;1H' "$CSI" "$((LOGO_HEIGHT + 8))" >"$TTY_PATH" + printf '%s%d;1H' "$CSI" "$((FINISH_TOP_ROW + LOGO_HEIGHT + 5))" >"$TTY_PATH" fi } @@ -486,8 +531,10 @@ reboot_prompt() { # applies, so setting both stops on the finish screen without a keyboard. interactive || return 0 - # Position the cursor one blank line below the title so the button sits right there. - printf '%s%d;1H%s' "$CSI" "$((LOGO_HEIGHT + 5))" "$CLEAR_LINE" >"$TTY_PATH" + # gum paints its prompt row at the cursor and the button two rows under it, so + # anchor on the row right below the title to put the button as close to it as + # gum allows. FINISH_HEIGHT budgets for the same three rows. + printf '%s%d;1H%s' "$CSI" "$((FINISH_TOP_ROW + LOGO_HEIGHT + 2))" "$CLEAR_LINE" >"$TTY_PATH" prompt_pad=$(( ( $(term_cols) - 18 ) / 2 )) (( prompt_pad < 0 )) && prompt_pad=0