blob: 69de6f60268bf94a47d76cf8dd23cbf9125ae94c [file] [log] [blame]
#!/bin/sh
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Converts and displays UTF-8 based text message file from
# /usr/share/chromeos-assets/text/boot_messages/${locale}/${message}.txt .
# Uses pango-view to convert from txt to png, then frecon to render png on
# frame buffer.
# Default conversion parameters (can be overridden by environment variables)
# Note pango-view and frecon uses different color code format (#rrggbb vs
# 0xrrggbb), so we use format 'rrggbb' in IMAGE_BACKGROUND_RGB and not
# allowing common names.
: ${IMAGE_BACKGROUND_RGB:=202124}
: ${IMAGE_FONT_NAME:=sans-serif}
: ${IMAGE_FONT_SIZE:=22}
: ${IMAGE_MARGIN_SIZE:=5}
: ${IMAGE_TEXT_COLOR:=White}
: ${MESSAGE_BASE_PATH:=/usr/share/chromeos-assets/text/boot_messages}
: ${ASSETS_IMAGE_PATH:=/usr/share/chromeos-assets/images}
: ${SPINNER_IMAGE_BASE:=/usr/share/chromeos-assets/images/spinner/48x48}
: ${SPINNER_INTERVAL:=100}
: ${PROGRESS_BAR_WIDTH:=2}
: ${PROGRESS_BAR_RGB:=d0d0d0}
: ${DISPLAY_INDICATOR_FILE:=/run/boot_message_displayed}
: ${LOGGER_TAG:=display_boot_message}
: ${MESSAGE_OPTIONS:=""}
isatty() {
local path="$1"
[ -t 0 ] <"${path}"
}
create_temp_output() {
# File extension (.png) is required for render engine (pango-view) to decide
# output format.
mktemp --suffix .png
}
# Prints width of given PNG file.
get_png_width() {
local input="$1"
local width
# Get image width (the 17~20 bytes of PNG file in big-endian).
width="$(dd if="${input}" bs=1 count=4 skip=16 2>/dev/null | od -A n -t x1)"
echo "$(( 0x$(echo "${width}" | sed 's/ //g') ))"
}
# Converts a text file to PNG file by pango-view without further postprocessing.
txt_to_png() {
local input="$1"
local output="$2"
local locale="$3"
local option="$4"
pango-view -q --output="${output}" \
--dpi=72 --align=left --hinting=full \
--margin="${IMAGE_MARGIN_SIZE}"\
--font="${IMAGE_FONT_NAME} ${IMAGE_FONT_SIZE}" \
--foreground="${IMAGE_TEXT_COLOR}" \
--background="#${IMAGE_BACKGROUND_RGB}" \
--language="${locale}" \
${option} "${input}"
}
# Converts a message file to PNG format to fit into given size.
message_to_png() {
local input="$1"
local output="$2"
local locale="$3"
local max_size_file="$4"
local extra_options="$5"
txt_to_png "${input}" "${output}" "${locale}" "${extra_options}"
# We prefer a "left-aligned text image on center of screen, with text
# wrapped by margin of its background image (max_size_file)". However if a
# --width is assigned to pango-view, it will always pad (by text align
# direction) to specified width, even if the image is smaller. That creates an
# image which is always aligned to left of background, not on center. To fix
# that, we first create the file, compare the width, and assign --width only
# if we need wrapping.
if [ -f "${max_size_file}" ]; then
local max_width="$(get_png_width "${max_size_file}")"
local width="$(get_png_width "${output}")"
if [ "${max_width}" -gt 0 -a "${width}" -gt "${max_width}" ]; then
extra_options="${extra_options} --width=${max_width}"
txt_to_png "${input}" "${output}" "${locale}" "${extra_options}"
fi
fi
}
# Returns if given message needs spinner animation.
need_spinner() {
local message="$1"
case "${message}" in
enter_dev2 | leave_dev | self_repair | update_firmware | power_wash | \
show_spinner | wipe | update_detachable_base_firmware | \
update_touchpad_firmware | update_touchscreen_firmware | \
update_firmware_slow_extra_warning | update_fwupd_firmware)
return 0
;;
*)
# Default: don't show spinner for unknown messages
return 1
esac
}
# Returns true if given message requires frecon to be restarted.
need_frecon_restart() {
local message="$1"
# Restart frecon if needs spinner.
if need_spinner "${message}"; then
return 0
fi
# Restart frecon if not running.
if ! pgrep frecon > /dev/null; then
return 0
fi
# Restart frecon if /run/frecon/vt0 does not exist.
if ! isatty /run/frecon/vt0; then
return 0
fi
# Do not restart frecon for the next messages:
case "${message}" in update_arc_data_snapshot)
return 1
;;
*)
return 0
esac
}
# Renders given images to screen.
render_images() {
local rc=0
local message="$1"
local file="$2"
local spiner_offset=""
local spinner_file="${SPINNER_IMAGE_BASE}"*01.png
# Keep a copy of file in temp folder and never delete so it won't be removed
# before the renderer (frecon running in background) is ready.
# This is required for factory wiping that file lives on stateful partition.
# It will be removed only when system reboots.
local image_file="$(create_temp_output)"
cp -f "${file}" "${image_file}"
# Put spinner in left of centered message file, with a padding of 1/2 spinner
# image width (offset is calculated from the default location, which puts
# spinner in center).
local spinner_offset_x=-"$(( $(get_png_width "${image_file}") / 2 +
$(get_png_width ${spinner_file}) ))"
# TODO(hungte) Figure out a way to render using OSC in frecon if there is
# already a frecon instance running.
# Stop any running frecon instance.
touch "${DISPLAY_INDICATOR_FILE}"
# By default frecon would exit after all images are displayed unless there
# is --loop-start or --enable-vts. Most boot-time messages need to keep the
# display until reboot so we want to always add --enable-vts.
local params="--enable-vts \
--clear 0x${IMAGE_BACKGROUND_RGB} \
--frame-interval 0 \
--scale=0 \
--enable-vt1 \
--no-login \
--enable-osc \
--pre-create-vts \
${ASSETS_IMAGE_PATH}/boot_message.png \
${image_file}"
# Frecon requires all options specified before image files.
if need_spinner "${message}"; then
params="\
--loop-offset ${spinner_offset_x},0 \
--loop-interval ${SPINNER_INTERVAL} \
--loop-start 2 \
${params} \
${SPINNER_IMAGE_BASE}*.png"
fi
if need_frecon_restart "${message}"; then
logger -t "${LOGGER_TAG}" "Kill frecon and display message: ${message}"
pkill -9 frecon
frecon --daemon ${params}
simulate_tty
else
logger -t "${LOGGER_TAG}" "Display message: ${message}"
update_image "${ASSETS_IMAGE_PATH}/boot_message.png"
update_image "${image_file}"
fi
return ${rc}
}
# Shows an arbitrary text file.
show_text_file() {
local message="$1"
local locale="$2"
local file="$3"
local options="$4"
local rc
# Some systems run the rendering in background (for example, frecon) and we
# don't know when the file will be accessed; so keep the file undeleted until
# next boot.
local output="$(create_temp_output)"
message_to_png "${file}" "${output}" "${locale}" \
"${ASSETS_IMAGE_PATH}/boot_message.png" "${options}" &&
render_images "${message}" "${output}" || rc=$?
rm -f "${output}"
return ${rc}
}
# Shows a predefined and localized message from ${MESSAGE_BASE_PATH}.
show_message() {
local message="$1"
local locales="$2"
local options="${MESSAGE_OPTIONS}"
local rc=0
local locale file
for locale in ${locales}; do
file="${MESSAGE_BASE_PATH}/${locale}/${message}.txt"
[ -f "${file}" ] || continue
show_text_file "${message}" "${locale}" "${file}" "${options}" || rc=$?
return ${rc}
done
# If none of given locales have this message, return as failure.
logger -t "${LOGGER_TAG}" \
"Couldn't find message ${message} in '${locales}'; not displaying anything."
return 1
}
# Shows a PNG image file or text markup file.
show_file() {
local message="$1"
local file="$2"
case "${file}" in
*.png)
render_images "${message}" "${file}"
;;
*)
show_text_file "${message}" "" "${file}" "--markup"
;;
esac
}
# Draw a box using frecon box drawing escape sequences.
draw_box() {
local width="$1"
local height="$2"
local color="$3"
local offset_x="$4"
local offset_y="$5"
# Bail out if the frecon terminal doesn't exist.
isatty /run/frecon/vt0 || return 1
printf '\033]box:color=0x%s;size=%u,%u;offset=%d,%d;scale=0\033\\' \
"${color}" "${width}" "${height}" "${offset_x}" "${offset_y}" \
> /run/frecon/vt0
}
# Show image using frecon image showing escape sequences.
update_image() {
local image_file="$1"
isatty /run/frecon/vt0 || return 1
printf '\033]image:file=%s;color=%s;scale=0\033\\' "${image_file}" \
"0x${IMAGE_BACKGROUND_RGB}" > /run/frecon/vt0
}
# Updates the progress bar. This requires frecon to already be set up, e.g. by
# previously invoking display_boot_message to show a message.
update_progress() {
local progress="$1"
# Draw the frame. The calculations below size the bar to be half the width of
# the maximum message width, and 20 times as wide as high. The bar should show
# below the message, so it's offset by 5 times its height to the bottom.
local width="$(get_png_width "${ASSETS_IMAGE_PATH}/boot_message.png")"
local bar_width="$(( width / ${PROGRESS_BAR_WIDTH} ))"
local bar_height="$(( width / 40 ))"
local bar_offset_y="$(( bar_height * 5 ))"
# The frame is created by a solid rectangle of the full bar dimension,
# overlayed with a slightly smaller rectangle in background color (2 pixels on
# each edge) to punch out the interior.
draw_box "${bar_width}" "${bar_height}" \
"${PROGRESS_BAR_RGB}" "0" "${bar_offset_y}"
draw_box "$(( bar_width - 4 ))" "$(( bar_height - 4 ))" \
"${IMAGE_BACKGROUND_RGB}" "0" "${bar_offset_y}"
# Draw the progress bar.
if [ "$((progress))" -lt 0 ]; then
progress=0
fi
if [ "$((progress))" -gt 100 ]; then
progress=100
fi
# The progress rectangle itself is slightly smaller than the frame (2 pixels
# on each edge), and needs to be offset to the left so it doesn't show at the
# center of the bar, but aligned with the left edge.
local progress_width="$(( ((bar_width - 8) * progress) / 100 ))"
local progress_offset=-"$(( (bar_width - 8 - progress_width) / 2 ))"
draw_box "${progress_width}" "$(( bar_height - 8 ))" \
"${PROGRESS_BAR_RGB}" "${progress_offset}" "${bar_offset_y}"
}
# Simulate the TTY1 for text output (for example, progress bar).
# Some legacy scripts may create a broken TTY1 so we want to delete that if
# it is not a character device. Note the vtN in /run/frecon starts from 0.
simulate_tty() {
local i="" frecon_vt="/run/frecon/vt" tty=""
for i in 1 2 3; do
tty="/dev/tty$((i + 1))"
if isatty "${frecon_vt}${i}" && ! isatty "${tty}"; then
rm -f "${tty}"
ln -sf "${frecon_vt}${i}" "${tty}"
fi
done
}
action() {
local action="$1"
case "${action}" in
# Restores frecon when the boot message should be cleared.
restore_frecon)
if [ -f "${DISPLAY_INDICATOR_FILE}" ]; then
logger -t "${LOGGER_TAG}" "Kill and restore frecon."
rm "${DISPLAY_INDICATOR_FILE}"
pkill -9 frecon || true
if is_developer_end_user; then
# Restore frecon with the same arguments as boot-splash.conf
frecon --daemon --enable-osc --enable-vts --pre-create-vts
fi
fi
;;
*)
return 1
;;
esac
}
usage() {
cat <<EOL >&2
Usage: $0 [MESSAGE LOCALES | show_file FILE | show_spinner FILE |
update_progress PERCENTAGE |
action [clear_indicator | restore_frecon] ]"
EOL
exit 1
}
main() {
[ "$#" = "2" ] || usage
case "$1" in
show_file|show_spinner)
show_file "$1" "$2"
;;
update_progress)
update_progress "$2"
;;
action)
action "$2"
;;
*)
show_message "$1" "$2"
;;
esac
}
set -e
main "$@"