Files
face-unlock/src/lib/common.sh
T

200 lines
6.1 KiB
Bash

# shellcheck shell=bash
#
# Paths, translations and output helpers.
#
# The shell side never touches the camera or the face data. Those belong to
# the daemon, and everything here that needs them asks it through
# face-unlock-ctl. What this side does write is the user's own settings
# file, and (through sudo, and only when asked) the system settings and the
# PAM files of sudo and polkit.
FU_VERSION="@VERSION@"
FU_NAME="face-unlock"
FU_PRETTY="Face Unlock"
FU_LIBDIR="${FU_LIBDIR:-@LIBDIR@}"
FU_LIBEXECDIR="${FU_LIBEXECDIR:-@LIBEXECDIR@}"
FU_LOCALEDIR="${FU_LOCALEDIR:-@LOCALEDIR@}"
FU_PAMDIR="${FU_PAMDIR:-@PAMDIR@}"
FU_CTL="${FU_CTL:-$FU_LIBEXECDIR/face-unlock-ctl}"
FU_AGENT="${FU_AGENT:-$FU_LIBEXECDIR/face-unlock-agent}"
FU_PAM_MODULE="${FU_PAM_MODULE:-$FU_PAMDIR/pam_face_unlock.so}"
FU_XDG_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}"
FU_CONFDIR="${FU_CONFDIR:-${FU_XDG_CONFIG}/${FU_NAME}}"
FU_CONFIG="${FU_CONFIG:-${FU_CONFDIR}/config}"
# The system settings. Only root writes them; see system.sh.
FU_SYSCONFIG="${FU_SYSCONFIG:-/etc/${FU_NAME}/config}"
FU_UNIT_SOCKET="face-unlockd.socket"
FU_UNIT_AGENT="face-unlock-agent.service"
# The desktop this runs in: plasma, gnome, hyprland, niri or other. All but
# the first two have a lock screen that is a program of its own, and there
# face-unlock's own one (`face-unlock lock`) is on offer.
case ":${XDG_CURRENT_DESKTOP:-}:" in
*:KDE:*) FU_DESKTOP=plasma ;;
*:GNOME:*) FU_DESKTOP=gnome ;;
*:Hyprland:*) FU_DESKTOP=hyprland ;;
*:niri:*) FU_DESKTOP=niri ;;
*) FU_DESKTOP=other ;;
esac
# ---------------------------------------------------------------------------
# Translations
# ---------------------------------------------------------------------------
export TEXTDOMAIN="face-unlock"
export TEXTDOMAINDIR="${FU_LOCALEDIR}"
fu_ui_locale() {
local l="${FU_UI_LOCALE:-}"
if [[ -z $l ]]; then
l="${LC_ALL:-}"
[[ -z $l ]] && l="${LC_MESSAGES:-}"
[[ -z $l ]] && l="${LANG:-}"
fi
# systemd writes /etc/locale.conf and most distributions use it; Debian and
# Ubuntu keep the same LANG= line in /etc/default/locale instead.
if [[ -z $l ]]; then
local f
for f in /etc/locale.conf /etc/default/locale; do
[[ -r $f ]] || continue
l="$(sed -n 's/^LANG=//p' "$f" | tr -d '"' | head -n1)"
[[ -n $l ]] && break
done
fi
printf '%s\n' "${l:-C}"
}
# Every gettext lookup is a fork and the settings screen redraws a screenful of
# labels per keypress, so results are memoized.
declare -A FU_MSG_CACHE=()
FU_MSG_RESULT=''
# fu_msg_into <locale> <msgid>
# Plain lookup with the result in FU_MSG_RESULT and no printf formatting, for
# callers that would otherwise pay a fork per label per frame.
fu_msg_into() {
local locale="$1" msgid="$2" cachekey
cachekey="${locale}"$'\x1f'"${msgid}"
if [[ -n ${FU_MSG_CACHE[$cachekey]+set} ]]; then
FU_MSG_RESULT="${FU_MSG_CACHE[$cachekey]}"
return 0
fi
FU_MSG_RESULT="$(LC_ALL="$locale" LANGUAGE="${locale%%.*}" gettext -- "$msgid" 2>/dev/null)"
[[ -n $FU_MSG_RESULT ]] || FU_MSG_RESULT="$msgid"
FU_MSG_CACHE[$cachekey]="$FU_MSG_RESULT"
return 0
}
# fu_msg_in <locale> <msgid> [printf args...]
fu_msg_in() {
local locale="$1" msgid="$2"
shift 2
fu_msg_into "$locale" "$msgid"
# With no arguments the message is plain text, not a format string. Feeding
# it to printf anyway would turn a literal percent sign in a translation
# into an invalid conversion.
if (( $# == 0 )); then
printf '%s' "$FU_MSG_RESULT"
return
fi
# shellcheck disable=SC2059 # the format string is the translated message
printf -- "$FU_MSG_RESULT" "$@"
}
FU_LOCALE_CACHED=''
# fu_msg <msgid> [printf args...]
fu_msg() {
[[ -n $FU_LOCALE_CACHED ]] || FU_LOCALE_CACHED="$(fu_ui_locale)"
fu_msg_in "$FU_LOCALE_CACHED" "$@"
}
# ---------------------------------------------------------------------------
# Output
# ---------------------------------------------------------------------------
# Decided once, while stdout is still whatever the process was started with:
# testing -t 1 at the point of use is wrong for anything called through $(...),
# which sees a pipe and would conclude nobody is watching.
FU_INTERACTIVE=''
[[ -t 1 ]] && FU_INTERACTIVE=1
if [[ -n $FU_INTERACTIVE && -z ${NO_COLOR:-} ]]; then
FU_C_RESET=$'\033[0m'
FU_C_BOLD=$'\033[1m'
FU_C_DIM=$'\033[2m'
FU_C_BLUE=$'\033[38;2;52;153;255m'
FU_C_GREEN=$'\033[32m'
FU_C_YELLOW=$'\033[33m'
FU_C_RED=$'\033[31m'
else
FU_C_RESET='' FU_C_BOLD='' FU_C_DIM='' FU_C_BLUE=''
FU_C_GREEN='' FU_C_YELLOW='' FU_C_RED=''
fi
# What fu_ok, fu_bad and fu_note printed. The menu redraws straight after an
# action, which wipes the screen, so it shows these again under the new frame
# rather than holding everything up for a key press.
FU_UI_NOTICES=()
fu_say() { printf '%s\n' "$*"; }
fu_head() { printf '\n%s%s%s\n\n' "$FU_C_BOLD$FU_C_BLUE" "$*" "$FU_C_RESET"; }
fu_ok() { FU_UI_NOTICES+=("$FU_C_GREEN✔$FU_C_RESET $*"); printf '%s✔%s %s\n' "$FU_C_GREEN" "$FU_C_RESET" "$*"; }
fu_bad() { FU_UI_NOTICES+=("$FU_C_RED✘$FU_C_RESET $*"); printf '%s✘%s %s\n' "$FU_C_RED" "$FU_C_RESET" "$*" >&2; }
fu_note() { FU_UI_NOTICES+=("$FU_C_DIM•$FU_C_RESET $*"); printf '%s•%s %s\n' "$FU_C_DIM" "$FU_C_RESET" "$*"; }
fu_have() { command -v "$1" > /dev/null 2>&1; }
# Human-readable "x minutes ago" for a unix timestamp. 0 or empty yields the
# translated "never".
#
# Written with [[ ]] and a variable for each number on purpose: xgettext reads
# the < of an (( )) as a redirection and loses every string after it.
fu_time_ago() {
local ts="$1" now delta n
if [[ ! $ts =~ ^[0-9]+$ || $ts -eq 0 ]]; then
fu_msg "never"
printf '\n'
return
fi
now="$(date +%s)"
delta=$(( now - ts ))
[[ $delta -lt 0 ]] && delta=0
if [[ $delta -lt 60 ]]; then
fu_msg "just now"
elif [[ $delta -lt 120 ]]; then
fu_msg "1 minute ago"
elif [[ $delta -lt 3600 ]]; then
n=$(( delta / 60 ))
fu_msg "%d minutes ago" "$n"
elif [[ $delta -lt 7200 ]]; then
fu_msg "1 hour ago"
elif [[ $delta -lt 86400 ]]; then
n=$(( delta / 3600 ))
fu_msg "%d hours ago" "$n"
elif [[ $delta -lt 172800 ]]; then
fu_msg "1 day ago"
else
n=$(( delta / 86400 ))
fu_msg "%d days ago" "$n"
fi
printf '\n'
}