diff --git a/bin/omarchy-launch-about b/bin/omarchy-launch-about index 27fff12c..2d7c9d2c 100755 --- a/bin/omarchy-launch-about +++ b/bin/omarchy-launch-about @@ -19,6 +19,15 @@ LOGO_PAD_LEFT=2 LOGO_PAD_TOP=2 LOGO_PAD_RIGHT=6 +# The content is measured once and then goes on living: an uptime that turns +# minutes into hours and hours into days, a version string that grows, a module +# that shows up on the next boot. A window fitted to exactly what was measured +# has nowhere to put any of it, and the layout clips or scrolls the moment it +# grows — which is also the moment the logo stops being where it was drawn. Keep +# a little in hand rather than measure again every time something ticks over. +FIT_SPARE_COLUMNS=2 +FIT_SPARE_ROWS=1 + POLL_SECONDS=0.5 # fastfetch has no animation of its own, so the sheen is ours. It knows about a @@ -144,9 +153,9 @@ fit_window() { # Mirror the logo block in the fastfetch config: 2 columns of padding left of # the logo, 6 between logo and modules. Then 2 columns of right padding to - # match, and a row for the cursor so the trailing break shows. - local target_c=$(( LOGO_PAD_LEFT + logo_w + LOGO_PAD_RIGHT + module_w + LOGO_PAD_LEFT )) - local target_r=$(( LAYOUT_ROWS + 1 )) + # match, a row for the cursor so the trailing break shows, and the spare above. + local target_c=$(( LOGO_PAD_LEFT + logo_w + LOGO_PAD_RIGHT + module_w + LOGO_PAD_LEFT + FIT_SPARE_COLUMNS )) + local target_r=$(( LAYOUT_ROWS + 1 + FIT_SPARE_ROWS )) local nudges=0 rows cols address width height shift_w shift_h target_w target_h while :; do @@ -190,24 +199,52 @@ fit_window() { measure_layout() { [[ -n ${LAYOUT_ROWS:-} ]] && return 0 - local rendered row + local rendered plain needle offset found row # --pipe false because fastfetch drops its colours when it is not writing to a # terminal, and it is writing to this substitution. rendered=$(fastfetch --pipe false 2>/dev/null; printf X) rendered=${rendered%X} LAYOUT_ROWS=$(printf '%s' "$rendered" | wc -l) - (( LAYOUT_ROWS > LOGO_PAD_TOP )) || return 1 + (( LAYOUT_ROWS > 0 )) || return 1 - # Whatever fastfetch set before the first row of the logo is what the sheen has - # to give those cells back. - row=$(printf '%s' "$rendered" | sed -n "$((LOGO_PAD_TOP + 1))p") + # Find the logo in what fastfetch drew rather than working it out from the + # padding this file was written against. The config that runs is the one in + # /etc, which a checkout does not replace, so the two can disagree — and a logo + # measured two rows above where it was drawn is a logo the sheen moves. Not + # finding it at all is the same answer as finding it somewhere unexpected: + # whatever is on screen is not the text in the file, so leave it alone. + IFS=$'\t' read -r offset needle < <(logo_landmark) || return 1 + [[ -n $needle ]] || return 1 + + plain=$(printf '%s' "$rendered" | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g') + found=$(printf '%s' "$plain" | LC_ALL=C.UTF-8 awk -v needle="$needle" \ + 'index($0, needle) { print NR, index($0, needle); exit }') + [[ -n $found ]] || return 1 + read -r LOGO_ROW LOGO_COLUMN <<<"$found" + LOGO_ROW=$(( LOGO_ROW - offset )) + (( LOGO_ROW >= 1 && LOGO_COLUMN >= 1 )) || return 1 + + # Whatever fastfetch set before that row is what the sheen has to give back. + row=$(printf '%s' "$rendered" | sed -n "$(( LOGO_ROW + offset ))p") LOGO_COLOR="" [[ $row =~ ^(($ESC\[[0-9;]*m)+) ]] && LOGO_COLOR=${BASH_REMATCH[1]} return 0 } +# The longest line of the logo, and how far down the logo it sits — the most +# distinctive thing to look for in the render, and the offset that turns where it +# was found back into where the logo starts. +# Tab-separated, and the offset first, because the line may hold spaces of its +# own and splitting on them would cut the landmark short. +logo_landmark() { + LC_ALL=C.UTF-8 awk ' + { if (length($0) > best) { best = length($0); line = $0; at = NR - 1 } } + END { if (best > 0) printf "%d\t%s\n", at, line } + ' "$LOGO_FILE" 2>/dev/null +} + # The About screen's own reasons the logo might not be where these frames would # draw it. Whether the logo itself can be animated is the sheen's own question. build_sheen() { @@ -226,7 +263,9 @@ build_sheen() { measure_layout || return 1 # The layout needs a row for the cursor past its last line. Without one it has - # scrolled, and the logo is no longer on the rows the frames address. + # scrolled, and the logo is no longer on the rows the frames address. Ask the + # terminal where the cursor actually is rather than trust the arithmetic, and + # keep the arithmetic for a terminal that will not say. local rows cols read -r rows cols <<<"$(stty size)" (( rows > LAYOUT_ROWS )) || return 1 @@ -234,7 +273,7 @@ build_sheen() { # The cell the logo's first row starts on, every attribute fastfetch left on # those cells so a glint that has passed leaves them as it found them, and the # room it has to work in left of the module column. - sheen_build "$LOGO_FILE" "$(( LOGO_PAD_TOP + 1 ))" "$(( LOGO_PAD_LEFT + 1 ))" "${ESC}[0m${LOGO_COLOR}" "$(( cols - LOGO_PAD_LEFT ))" + sheen_build "$LOGO_FILE" "$LOGO_ROW" "$LOGO_COLUMN" "${ESC}[0m${LOGO_COLOR}" "$(( cols - LOGO_COLUMN + 1 ))" } # What the frames were built against. A window that resized, or a logo that was diff --git a/test/shell.d/launch-about-test.sh b/test/shell.d/launch-about-test.sh index 7e8b10d8..6fb92e73 100755 --- a/test/shell.d/launch-about-test.sh +++ b/test/shell.d/launch-about-test.sh @@ -22,14 +22,21 @@ source "$tmp_dir/about.bash" [[ $(type -t sheen_build) == "function" ]] || fail "the launcher finds the sheen it sources" pass "the launcher finds the sheen it sources" +# Kept before the stubs replace it, so the real one can be exercised below. +real_measure_layout=$(declare -f measure_layout) + # Stand in for the terminal, and for the fastfetch run that measures the layout. rows_by_cols="45 140" layout_rows=20 +logo_row=3 +logo_column=3 logo_color=$'\e[1m\e[32m' stty() { printf '%s\n' "$rows_by_cols"; } measure_layout() { LAYOUT_ROWS=$layout_rows LOGO_COLOR=$logo_color + LOGO_ROW=$logo_row + LOGO_COLUMN=$logo_column } # fastfetch resolves the home directory from the passwd database rather than @@ -76,11 +83,11 @@ pass "a roomy window animates" # how much room it has left of the module column. [[ ${handed[0]} == "$HOME/.config/omarchy/branding/about.txt" ]] || fail "the sheen is given the logo About draws" "${handed[0]}" pass "the sheen is given the logo About draws" -[[ ${handed[1]} == "$((config_top + 1))" && ${handed[2]} == "$((config_left + 1))" ]] || fail "the sheen is given the cell the logo starts on" "${handed[1]}/${handed[2]}" +[[ ${handed[1]} == "$logo_row" && ${handed[2]} == "$logo_column" ]] || fail "the sheen is given the cell the logo starts on" "${handed[1]}/${handed[2]}" pass "the sheen is given the cell the logo starts on" [[ ${handed[3]} == $'\e[0m'"$logo_color" ]] || fail "the sheen is given fastfetch's own colour to restore" "$(printf '%q' "${handed[3]}")" pass "the sheen is given fastfetch's own colour to restore" -[[ ${handed[4]} == "$((140 - config_left))" ]] || fail "the sheen is given the columns left of the module column" "${handed[4]}" +[[ ${handed[4]} == "$((140 - logo_column + 1))" ]] || fail "the sheen is given the columns left of the module column" "${handed[4]}" pass "the sheen is given the columns left of the module column" # fastfetch reads the first config it finds across several directories, and any @@ -105,10 +112,12 @@ rm -r "${HOME:?}/searched-later" rows_by_cols="$((layout_rows + 1)) 140" build_sheen || fail "a window with one row past the layout animates" pass "a window with one row past the layout animates" + rows_by_cols="$layout_rows 140" refuses "a window level with the layout's last line leaves it still" rows_by_cols="45 140" + # The loop plays whatever frames are left lying about, so a build that failed has # to leave none of the last one's. SHEEN_FRAMES=(stale frames) @@ -196,12 +205,23 @@ hyprctl() { return 0 } -# logo 10 wide + the config's padding + a 65-column module block. -fit_cols=$(( config_left + 10 + config_right + 65 + config_left )) +# logo 10 wide + the config's padding + a 65-column module block, plus the spare +# the fit keeps in hand for content that grows after it was measured. +fit_cols=$(( config_left + 10 + config_right + 65 + config_left + FIT_SPARE_COLUMNS )) +fit_rows=$(( layout_rows + 1 + FIT_SPARE_ROWS )) -rows_by_cols="$((layout_rows + 1)) $fit_cols" -fit_window || fail "the fit is satisfied by a window with a row past the layout" -pass "the fit is satisfied by a window with a row past the layout" +rows_by_cols="$fit_rows $fit_cols" +fit_window || fail "the fit is satisfied by a window with the spare in it" +pass "the fit is satisfied by a window with the spare in it" + +# Exactly the content and not a cell more is what used to be asked for, and it is +# the size that clips or scrolls as soon as an uptime turns over. +rows_by_cols="$(( layout_rows + 1 )) $(( fit_cols - FIT_SPARE_COLUMNS ))" +if fit_window; then + fail "the fit asks for more than the bare content" +else + pass "the fit asks for more than the bare content" +fi rows_by_cols="$layout_rows $fit_cols" if fit_window; then @@ -209,3 +229,36 @@ if fit_window; then else pass "the fit is not satisfied by a window that scrolls the layout" fi + +# The padding this file was written against is the config in the repo; the config +# that runs is the one in /etc, which a checkout does not replace. Measure the +# logo in what fastfetch drew rather than working it out from an assumption, or a +# logo drawn two rows lower is a logo the sheen picks up and moves. +eval "$real_measure_layout" +LOGO_FILE="$tmp_dir/landmark.txt" +printf '%s\n' '████████' '██ ██' '████████' >"$LOGO_FILE" +render_with_padding() { + local top=$1 left=$2 i line + for (( i = 0; i < top; i++ )); do printf '\n'; done + while IFS= read -r line; do printf '\e[1m\e[32m%*s%s\e[m\n' "$left" '' "$line"; done <"$LOGO_FILE" + for (( i = 0; i < 6; i++ )); do printf 'module line\n'; done +} +for pad in "2 2" "4 5" "0 0" "6 10"; do + set -- $pad + eval "fastfetch() { render_with_padding $1 $2; }" + LAYOUT_ROWS="" + measure_layout || fail "the logo is found wherever fastfetch drew it" "padding $1/$2" + [[ $LOGO_ROW == "$(( $1 + 1 ))" && $LOGO_COLUMN == "$(( $2 + 1 ))" ]] || + fail "the logo is found wherever fastfetch drew it" "padding $1/$2 measured $LOGO_ROW/$LOGO_COLUMN" +done +pass "the logo is found wherever fastfetch drew it" + +# A render that does not contain the file's own text is not this logo, whatever +# the reason — a config that restyled it, a placeholder fastfetch substituted. +fastfetch() { printf 'something else entirely\n'; } +LAYOUT_ROWS="" +if measure_layout; then + fail "a render without the logo in it is not measured" +else + pass "a render without the logo in it is not measured" +fi