Compare commits

..
2 Commits
Author SHA1 Message Date
Felitendo a17aed4b3d Lower the battery threshold to 30% and default the cachy-update prompt to yes
The prompt offering to silence cachy-update's own update notification now
defaults to yes: once updates install themselves that notification is nothing
but noise, so the common answer should be the one you get by pressing Enter.

It also accepts "j" now. The prompt is translated, so a German user reads
"[J/n]" and types the German letter - which the old check for "y" alone
silently read as a refusal.
2026-08-08 16:30:20 +02:00
Felitendo 2fa830284e Make the settings screen redraw 50x faster
Arrow-key navigation took 435 ms per keypress, which reads as the whole console
reloading on every press - because it effectively was. The cost was forks: each
frame ran a command substitution per label for the value, the translation and
the rendered state, 54 subshells for eighteen rows.

The frame is now assembled in memory and written once. Translations, the split
specs and the terminfo clear string are resolved before the loop; values are
re-read only after something actually changes, not on cursor movement. Helpers
on that path assign to a variable instead of printing, since printing is what
forced the substitution.

Measured on the same machine: 435 ms -> 7.5 ms per keypress.
2026-08-08 16:25:09 +02:00
9 changed files with 128 additions and 52 deletions

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@
# Overridable so a packager can pass the version it is actually building
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
# straight from a checkout, and is what a release tag has to carry.
VERSION ?= 1.1.0
VERSION ?= 1.1.2
PREFIX ?= /usr
DESTDIR ?=
+1 -1
View File
@@ -74,7 +74,7 @@ deliberately.
A run is postponed — and retried an hour later — when:
- the battery is below 40 % (ignored on mains power; desktops without a battery
- the battery is below 30 % (ignored on mains power; desktops without a battery
are never affected),
- a game is running: GameMode, a known game process, or anything holding a
blocking idle inhibitor,
+1 -1
View File
@@ -168,7 +168,7 @@ msgstr ""
msgid "base-devel is missing - AUR packages cannot be built without it."
msgstr ""
msgid "cachy-update also notifies about available updates. Turn its notifications off? [y/N]"
msgid "cachy-update also notifies about available updates. Turn its notifications off? [Y/n]"
msgstr ""
msgid "cachy-update's update check has been disabled."
+2 -2
View File
@@ -169,8 +169,8 @@ msgstr "Kein AUR-Helper gefunden – für AUR-Updates paru oder yay installieren
msgid "base-devel is missing - AUR packages cannot be built without it."
msgstr "base-devel fehlt – ohne das lassen sich AUR-Pakete nicht bauen."
msgid "cachy-update also notifies about available updates. Turn its notifications off? [y/N]"
msgstr "cachy-update benachrichtigt ebenfalls über verfügbare Updates. Dessen Benachrichtigungen abschalten? [j/N]"
msgid "cachy-update also notifies about available updates. Turn its notifications off? [Y/n]"
msgstr "cachy-update benachrichtigt ebenfalls über verfügbare Updates. Dessen Benachrichtigungen abschalten? [J/n]"
msgid "cachy-update's update check has been disabled."
msgstr "Die Update-Prüfung von cachy-update wurde abgeschaltet."
+1 -1
View File
@@ -31,7 +31,7 @@ UpdateInterval=1d
# Never update while the battery is below this percentage. Ignored on mains
# power and on machines without a battery.
MinBatteryPercent=40
MinBatteryPercent=30
# Update only while plugged in. Stricter than MinBatteryPercent.
RequireAC=no
+10 -2
View File
@@ -117,9 +117,17 @@ cau_offer_disable_cachy_update() {
(( found )) || return 0
printf '\n %s\n ' \
"$(cau_msg "cachy-update also notifies about available updates. Turn its notifications off? [y/N]")"
"$(cau_msg "cachy-update also notifies about available updates. Turn its notifications off? [Y/n]")"
read -r answer || return 0
[[ ${answer,,} == y ]] || return 0
# Defaults to yes: once updates install themselves, cachy-update's "N
# updates available" is purely noise. "j" is accepted too - the prompt is
# translated, so a German user types the German letter and used to have
# that silently read as "no".
case "${answer,,}" in
''|y|yes|j|ja) ;;
*) return 0 ;;
esac
while read -r user uid; do
[[ -n $user ]] || continue
+21
View File
@@ -75,6 +75,27 @@ cau_msg() {
cau_msg_in "$(cau_ui_locale)" "$@"
}
# cau_msg_into <locale> <msgid>
# Plain lookup with the result in CAU_MSG_RESULT and no printf formatting.
# For callers that redraw many labels per keypress, where wrapping cau_msg in a
# command substitution would cost a fork per label.
CAU_MSG_RESULT=''
cau_msg_into() {
local locale="$1" msgid="$2" cachekey
cachekey="${locale}"$'\x1f'"${msgid}"
if [[ -n ${CAU_MSG_CACHE[$cachekey]+set} ]]; then
CAU_MSG_RESULT="${CAU_MSG_CACHE[$cachekey]}"
return 0
fi
CAU_MSG_RESULT="$(LC_ALL="$locale" LANGUAGE="${locale%%.*}" gettext -- "$msgid" 2>/dev/null)"
[[ -n $CAU_MSG_RESULT ]] || CAU_MSG_RESULT="$msgid"
CAU_MSG_CACHE[$cachekey]="$CAU_MSG_RESULT"
return 0
}
# Translations are memoized. Every gettext lookup is a fork, and the settings
# screen redraws forty-odd labels per keypress; without this the redraw takes
# long enough that a keystroke arriving during it is lost when the terminal
+18 -9
View File
@@ -20,11 +20,17 @@ _cau_config_slurp() {
return 0
}
# cau_config_get <Key> [default]
cau_config_get() {
# _cau_config_lookup <Key> [default]
# Result in CAU_CONFIG_VALUE. Assigning rather than printing matters on the
# settings screen, which reads every key on every frame: a command substitution
# there is a fork, and forks were the entire cost of a redraw.
CAU_CONFIG_VALUE=''
_cau_config_lookup() {
local key="$1" default="${2:-}" val='' line
[[ -r $CAU_CONFIG ]] || { printf '%s\n' "$default"; return; }
CAU_CONFIG_VALUE="$default"
[[ -r $CAU_CONFIG ]] || return 0
_cau_config_slurp
# last assignment wins, matching the previous sed|tail behaviour
@@ -41,11 +47,14 @@ cau_config_get() {
val="${val%\"}"
val="${val#\"}"
if [[ -n $val ]]; then
printf '%s\n' "$val"
else
printf '%s\n' "$default"
fi
[[ -n $val ]] && CAU_CONFIG_VALUE="$val"
return 0
}
# cau_config_get <Key> [default]
cau_config_get() {
_cau_config_lookup "$@"
printf '%s\n' "$CAU_CONFIG_VALUE"
}
# cau_config_bool <Key> <default: yes|no>
@@ -125,7 +134,7 @@ cau_config_load() {
CFG_INTERVAL="$(cau_config_get UpdateInterval 1d)"
CFG_INTERVAL_SECONDS="$(cau_duration_to_seconds "$CFG_INTERVAL" 86400)"
CFG_MIN_BATTERY="$(cau_config_int MinBatteryPercent 40)"
CFG_MIN_BATTERY="$(cau_config_int MinBatteryPercent 30)"
CFG_KEEP_OLD="$(cau_config_int KeepOldPackages 3)"
CFG_AUR_HELPER="$(cau_config_get AURHelper auto)"
CFG_IGNORE_PKG="$(cau_config_get IgnorePkg '')"
+73 -35
View File
@@ -135,7 +135,7 @@ CAU_SETTINGS=(
"UpdateInterval|choice:6h 12h 1d 2d 1w|1d|Time between update runs"
"SkipWhenGaming|bool|yes|Postpone while a game is running"
"RequireAC|bool|no|Only update on mains power"
"MinBatteryPercent|choice:0 20 30 40 50 60 70 80|40|Minimum battery level (%)"
"MinBatteryPercent|choice:0 20 30 40 50 60 70 80|30|Minimum battery level (%)"
"UpdateAUR|bool|yes|Update AUR packages"
"UpdateFlatpak|bool|yes|Update Flatpaks"
"UpdateAppImages|bool|yes|Update AppImages"
@@ -156,25 +156,33 @@ _cau_is_true() {
}
# _cau_setting_display <type> <value>
# Result in CAU_SETTING_SHOWN. The three constant strings are resolved once by
# the caller into CAU_LBL_*; looking them up here would put a translation call
# on the per-line path.
CAU_SETTING_SHOWN=''
CAU_LBL_ON=''
CAU_LBL_OFF=''
CAU_LBL_NONE=''
_cau_setting_display() {
local type="$1" value="$2"
case "$type" in
bool)
if _cau_is_true "$value"; then
printf '%s%s%s' "$CAU_C_GREEN" "$(cau_msg "ON")" "$CAU_C_RESET"
CAU_SETTING_SHOWN="${CAU_C_GREEN}${CAU_LBL_ON}${CAU_C_RESET}"
else
printf '%s%s%s' "$CAU_C_DIM" "$(cau_msg "OFF")" "$CAU_C_RESET"
CAU_SETTING_SHOWN="${CAU_C_DIM}${CAU_LBL_OFF}${CAU_C_RESET}"
fi
;;
text)
if [[ -n $value ]]; then
printf '%s' "$value"
CAU_SETTING_SHOWN="$value"
else
printf '%s%s%s' "$CAU_C_DIM" "$(cau_msg "(none)")" "$CAU_C_RESET"
CAU_SETTING_SHOWN="${CAU_C_DIM}${CAU_LBL_NONE}${CAU_C_RESET}"
fi
;;
*) printf '%s' "$value" ;;
*) CAU_SETTING_SHOWN="$value" ;;
esac
}
@@ -205,58 +213,88 @@ _cau_setting_cycle() {
# cau_ui_settings
# A cursor list rather than a numbered menu: there are eighteen settings, and
# numbering them would run out of digits and force paging.
#
# The frame is assembled in memory and written once. Everything constant - the
# specs, the translated labels, the clear sequence - is resolved before the
# loop, and the values are re-read only after something actually changes.
# Drawing the naive way cost a command substitution per label per frame, which
# measured 435 ms per keypress: arrow keys felt like the console was reloading,
# because in effect it was.
cau_ui_settings() {
local cursor=0 key spec name type default label value line pad
local count=${#CAU_SETTINGS[@]}
local -a names=() types=() defaults=() labels=()
local -a values=()
local spec name type default label locale i key frame row pad dirty=1 cursor=0
while true; do
clear 2>/dev/null || true
cau_head " $(cau_msg "Settings")"
locale="$(cau_ui_locale)"
cau_msg_into "$locale" "ON"; CAU_LBL_ON="$CAU_MSG_RESULT"
cau_msg_into "$locale" "OFF"; CAU_LBL_OFF="$CAU_MSG_RESULT"
cau_msg_into "$locale" "(none)"; CAU_LBL_NONE="$CAU_MSG_RESULT"
local i=0
for spec in "${CAU_SETTINGS[@]}"; do
IFS='|' read -r name type default label <<< "$spec"
value="$(cau_config_get "$name" "$default")"
if (( i == cursor )); then
line="${CAU_C_BLUE}▸${CAU_C_RESET} "
else
line=" "
fi
local text
text="$(cau_msg "$label")"
pad=$(( 42 - ${#text} ))
(( pad < 0 )) && pad=0
printf ' %s%s%*s %s\n' "$line" "$text" "$pad" '' \
"$(_cau_setting_display "$type" "$value")"
i=$(( i + 1 ))
names+=("$name"); types+=("$type"); defaults+=("$default")
cau_msg_into "$locale" "$label"
labels+=("$CAU_MSG_RESULT")
done
printf '\n %s%s%s\n' "$CAU_C_DIM" \
"$(cau_msg "Up/Down select - Space or Right changes - q goes back")" "$CAU_C_RESET"
local title hint
cau_msg_into "$locale" "Settings"; title="$CAU_MSG_RESULT"
cau_msg_into "$locale" "Up/Down select - Space or Right changes - q goes back"
hint="$CAU_MSG_RESULT"
# the terminfo clear string, fetched once instead of forking per frame
local clearseq
clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J'
while true; do
if (( dirty )); then
for i in "${!names[@]}"; do
_cau_config_lookup "${names[i]}" "${defaults[i]}"
values[i]="$CAU_CONFIG_VALUE"
done
dirty=0
fi
frame="$clearseq"$'\n'"${CAU_C_BOLD}${CAU_C_BLUE} ${title}${CAU_C_RESET}"$'\n\n'
local marker selected="${CAU_C_BLUE}▸${CAU_C_RESET} "
for i in "${!names[@]}"; do
_cau_setting_display "${types[i]}" "${values[i]}"
pad=$(( 42 - ${#labels[i]} ))
(( pad < 0 )) && pad=0
if (( i == cursor )); then marker="$selected"; else marker=' '; fi
printf -v row ' %s%s%*s %s' \
"$marker" "${labels[i]}" "$pad" '' "$CAU_SETTING_SHOWN"
frame+="$row"$'\n'
done
frame+=$'\n'" ${CAU_C_DIM}${hint}${CAU_C_RESET}"$'\n'
printf '%s' "$frame"
key="$(cau_read_key)" || return 0
IFS='|' read -r name type default label <<< "${CAU_SETTINGS[cursor]}"
value="$(cau_config_get "$name" "$default")"
type="${types[cursor]}"
name="${names[cursor]}"
case "$key" in
up|k) cursor=$(( (cursor - 1 + count) % count )) ;;
down|j) cursor=$(( (cursor + 1) % count )) ;;
space|enter|right|l)
if [[ $type == text ]]; then
cau_ui_edit_text "$name" "$value"
cau_ui_edit_text "$name" "${values[cursor]}"
else
cau_config_set "$name" "$(_cau_setting_cycle "$type" "$value" 1)" \
cau_config_set "$name" "$(_cau_setting_cycle "$type" "${values[cursor]}" 1)" \
|| { cau_bad "$(cau_msg "Could not write the configuration file.")"; cau_pause; }
fi
dirty=1
;;
left|h)
[[ $type == text ]] || cau_config_set "$name" \
"$(_cau_setting_cycle "$type" "$value" -1)" \
if [[ $type != text ]]; then
cau_config_set "$name" "$(_cau_setting_cycle "$type" "${values[cursor]}" -1)" \
|| { cau_bad "$(cau_msg "Could not write the configuration file.")"; cau_pause; }
dirty=1
fi
;;
q|Q|escape) return 0 ;;
*) ;;