From d3fb549fd1d35737827fe559e5f181f263497ff6 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sun, 21 Jun 2026 23:28:49 -0400 Subject: [PATCH] Detailed battery status is now presented in a dialogue. Quick battery status should actually work now. --- I38.md | 8 +- README.md | 13 ++ i38.sh | 3 + scripts/battery_status.sh | 295 ++++++++++++++++++-------------------- 4 files changed, 160 insertions(+), 159 deletions(-) diff --git a/I38.md b/I38.md index e3ecd28..b471904 100644 --- a/I38.md +++ b/I38.md @@ -132,7 +132,8 @@ In Panel Mode, single keypresses launch different information panels: | `s` | Show system information | | `r` | Open reminder panel | | `n` | Launch notes application | -| `b` | Open bluetooth. *requires blueman be installed at the time of your i3 config generation* | +| `b` | Open Bluetooth *(requires blueman during i3 config generation)* | +| `@MODKEY@` + `b` | Speak a quick battery status | | `Shift` + `b` | Show detailed battery information | | `m` | Launch password manager | | `p` | Show power options | @@ -150,7 +151,10 @@ The system information panel (`s` key in Panel Mode) displays vital system stati - Memory usage - Disk space - Network status -- Battery level (if applicable) + +### Battery Information + +The battery report has two modes. Press `@MODKEY@` + `b` in Panel Mode for a concise spoken announcement of charge level and charging state. Press `Shift` + `b` in Panel Mode for the detailed report, shown in a reviewable text dialog instead of spoken aloud, so the full details can be navigated with the arrow keys: charge, status, health, chemistry, voltage, current, power draw, and cycle count when the hardware exposes them. The quick report is also available with `Alt` + `b` in Ratpoison Mode. If `acpi` is installed it is used for the report, otherwise I38 reads `/sys/class/power_supply` directly. In the detailed dialog, press `Control` + `Home` to jump back to the top of the text. ### Weather Panel diff --git a/README.md b/README.md index 4f4b616..3cef8e0 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,19 @@ You can apply the same configuration to GTK2 apps. Create or edit ~/.gtkrc-2.0 - -x: Generate ~/.xinitrc and ~/.xprofile. - -X: Generate ~/.xprofile only. +## Dependency Helpers + +Distro-specific dependency helpers live in `extra/`. On Arch Linux, run: + +```bash +extra/arch-linux.sh +``` + +The helper checks I38's required packages, asks which missing provider +applications to install, adds lightweight optional feature packages, then +installs the selected package list at the end. Optional packages that need +manual choice, such as Tesseract language data, are skipped. + ## Development Enable the tracked pre-commit hooks once after cloning: diff --git a/i38.sh b/i38.sh index 3e4f0c3..ca7331c 100755 --- a/i38.sh +++ b/i38.sh @@ -1360,6 +1360,9 @@ $(if command -v blueman-manager &> /dev/null ; then echo "bindsym b exec --no-startup-id blueman-manager, mode \"default\"" fi) + # Quick battery status bound to the configured mod key+b + bindsym \$mod+b exec --no-startup-id ${i3Path}/scripts/battery_status.sh, mode "default" + # Detailed battery information bound to Shift+b bindsym Shift+b exec --no-startup-id ${i3Path}/scripts/battery_status.sh --detailed, mode "default" diff --git a/scripts/battery_status.sh b/scripts/battery_status.sh index bd7beb4..120348c 100755 --- a/scripts/battery_status.sh +++ b/scripts/battery_status.sh @@ -10,91 +10,71 @@ # You should have received a copy of the GNU General Public License along with I38. If not, see . +# Battery status reporting. +# Simple mode (default, Panel Mode mod+b): speaks a concise status as a quick +# announcement. Detailed mode (-d/--detailed, Panel Mode Shift+b): shows +# the full report in a screen reader accessible yad text-info dialog so it can +# be reviewed with the arrow keys. + # Parse command line arguments detailedMode=0 if [[ "$1" == "-d" ]] || [[ "$1" == "--detailed" ]]; then detailedMode=1 fi +# Read a sysfs attribute safely, stripping trailing newlines. Returns empty if +# the attribute file does not exist or cannot be read. +read_attribute() { + local path="$1" + [[ -f "$path" ]] && cat "$path" 2>/dev/null | tr -d '\n' +} + get_battery_details() { local batteryPath="$1" local batteryName="${batteryPath##*/}" - local modelName="" - local manufacturer="" - local capacity="" - local status="" - local health="" - local cycleCount="" - local technology="" - local voltage="" - local current="" - local power="" + local modelName + local manufacturer + local capacity + local status + local health + local cycleCount + local technology + local voltage + local current + local power + local deviceName local output="" - # Get model name if available - if [[ -f "${batteryPath}/model_name" ]]; then - modelName="$(cat "${batteryPath}/model_name" 2>/dev/null | tr -d '\n')" + modelName="$(read_attribute "${batteryPath}/model_name")" + manufacturer="$(read_attribute "${batteryPath}/manufacturer")" + capacity="$(read_attribute "${batteryPath}/capacity")" + status="$(read_attribute "${batteryPath}/status")" + health="$(read_attribute "${batteryPath}/capacity_level")" + cycleCount="$(read_attribute "${batteryPath}/cycle_count")" + technology="$(read_attribute "${batteryPath}/technology")" + + # Voltage (microvolts -> volts) + local voltageUv + voltageUv="$(read_attribute "${batteryPath}/voltage_now")" + if [[ -n "$voltageUv" ]] && [[ "$voltageUv" =~ ^[0-9]+$ ]]; then + voltage="$(awk "BEGIN {printf \"%.2f\", $voltageUv/1000000}")" fi - # Get manufacturer if available - if [[ -f "${batteryPath}/manufacturer" ]]; then - manufacturer="$(cat "${batteryPath}/manufacturer" 2>/dev/null | tr -d '\n')" + # Current (microamps -> amps) + local currentUa + currentUa="$(read_attribute "${batteryPath}/current_now")" + if [[ -n "$currentUa" ]] && [[ "$currentUa" =~ ^[0-9]+$ ]]; then + current="$(awk "BEGIN {printf \"%.2f\", $currentUa/1000000}")" fi - # Get capacity (charge percentage) - if [[ -f "${batteryPath}/capacity" ]]; then - capacity="$(cat "${batteryPath}/capacity" 2>/dev/null | tr -d '\n')" - fi - - # Get status (Charging, Discharging, Full, etc.) - if [[ -f "${batteryPath}/status" ]]; then - status="$(cat "${batteryPath}/status" 2>/dev/null | tr -d '\n')" - fi - - # Get health/capacity level if available - if [[ -f "${batteryPath}/capacity_level" ]]; then - health="$(cat "${batteryPath}/capacity_level" 2>/dev/null | tr -d '\n')" - fi - - # Get cycle count if available - if [[ -f "${batteryPath}/cycle_count" ]]; then - cycleCount="$(cat "${batteryPath}/cycle_count" 2>/dev/null | tr -d '\n')" - fi - - # Get technology (Li-ion, Li-poly, etc.) - if [[ -f "${batteryPath}/technology" ]]; then - technology="$(cat "${batteryPath}/technology" 2>/dev/null | tr -d '\n')" - fi - - # Get voltage (in microvolts, convert to volts) - if [[ -f "${batteryPath}/voltage_now" ]]; then - local voltageUv - voltageUv="$(cat "${batteryPath}/voltage_now" 2>/dev/null | tr -d '\n')" - if [[ -n "$voltageUv" ]] && [[ "$voltageUv" =~ ^[0-9]+$ ]]; then - voltage="$(awk "BEGIN {printf \"%.2f\", $voltageUv/1000000}")" - fi - fi - - # Get current (in microamps, convert to amps) - if [[ -f "${batteryPath}/current_now" ]]; then - local currentUa - currentUa="$(cat "${batteryPath}/current_now" 2>/dev/null | tr -d '\n')" - if [[ -n "$currentUa" ]] && [[ "$currentUa" =~ ^[0-9]+$ ]]; then - current="$(awk "BEGIN {printf \"%.2f\", $currentUa/1000000}")" - fi - fi - - # Get power (in microwatts, convert to watts) - if [[ -f "${batteryPath}/power_now" ]]; then - local powerUw - powerUw="$(cat "${batteryPath}/power_now" 2>/dev/null | tr -d '\n')" - if [[ -n "$powerUw" ]] && [[ "$powerUw" =~ ^[0-9]+$ ]]; then - power="$(awk "BEGIN {printf \"%.2f\", $powerUw/1000000}")" - fi + # Power (microwatts -> watts) + local powerUw + powerUw="$(read_attribute "${batteryPath}/power_now")" + if [[ -n "$powerUw" ]] && [[ "$powerUw" =~ ^[0-9]+$ ]]; then + power="$(awk "BEGIN {printf \"%.2f\", $powerUw/1000000}")" fi # Build readable device name - local deviceName="$batteryName" if [[ -n "$manufacturer" ]] && [[ -n "$modelName" ]]; then deviceName="$manufacturer $modelName" elif [[ -n "$modelName" ]]; then @@ -102,116 +82,117 @@ get_battery_details() { elif [[ -n "$manufacturer" ]]; then deviceName="$manufacturer Battery" else - # Try to make the battery name more readable deviceName="${batteryName//BAT/Battery }" deviceName="${deviceName//hid-/}" deviceName="${deviceName//_/ }" fi - # Build the output string output="$deviceName" - - if [[ -n "$capacity" ]]; then - output="$output, ${capacity}%" - fi - - if [[ -n "$status" ]]; then - output="$output, ${status,,}" - fi - - if [[ -n "$health" ]]; then - output="$output, health: ${health,,}" - fi - - if [[ -n "$technology" ]]; then - output="$output, type: $technology" - fi - - if [[ -n "$voltage" ]]; then - output="$output, voltage: ${voltage}V" - fi - - if [[ -n "$current" ]]; then - output="$output, current: ${current}A" - fi - - if [[ -n "$power" ]]; then - output="$output, power: ${power}W" - fi - - if [[ -n "$cycleCount" ]]; then - output="$output, cycles: $cycleCount" - fi + [[ -n "$capacity" ]] && output="${output}"$'\n'"Charge: ${capacity}%" + [[ -n "$status" ]] && output="${output}"$'\n'"Status: ${status,,}" + [[ -n "$health" ]] && output="${output}"$'\n'"Health: ${health,,}" + [[ -n "$technology" ]] && output="${output}"$'\n'"Type: $technology" + [[ -n "$voltage" ]] && output="${output}"$'\n'"Voltage: ${voltage} V" + [[ -n "$current" ]] && output="${output}"$'\n'"Current: ${current} A" + [[ -n "$power" ]] && output="${output}"$'\n'"Power: ${power} W" + [[ -n "$cycleCount" ]] && output="${output}"$'\n'"Cycles: $cycleCount" echo "$output" } -# Simple battery status (default mode) +# Concise one-line report for the simple spoken announcement. get_battery_simple() { local batteryPath="$1" - local bat="${batteryPath##*/}" - bat="${bat//BAT/Battery }" - bat="${bat}: $( { cat "${batteryPath}/status";echo -n ", "; cat "${batteryPath}/capacity"; } | tr -d \\n) percent." - echo "$bat" + local batteryName="${batteryPath##*/}" + local status + local capacity + local deviceName + + status="$(read_attribute "${batteryPath}/status")" + capacity="$(read_attribute "${batteryPath}/capacity")" + deviceName="${batteryName//BAT/Battery }" + + if [[ -n "$status" ]] && [[ -n "$capacity" ]]; then + echo "${deviceName}: ${status,,}, ${capacity}%" + elif [[ -n "$capacity" ]]; then + echo "${deviceName}: ${capacity}%" + fi } -# Main script if [[ $detailedMode -eq 1 ]]; then - # Detailed mode - if command -v acpi &> /dev/null; then - # Try acpi first for more detailed info - batteryInfo="" - while IFS= read -r line; do - if [[ -n "$batteryInfo" ]]; then - batteryInfo="${batteryInfo}. " - fi - batteryInfo="${batteryInfo}${line}" - done < <(acpi -V 2>/dev/null | grep -i battery) - - if [[ -n "$batteryInfo" ]]; then - spd-say -P important -Cw "$batteryInfo" - exit 0 - fi - fi - - # Fallback to /sys/class/power_supply for detailed info + # Detailed mode: show the full report in a reviewable yad text-info dialog. + batteryInfoText="" batteryCount=0 - batteryOutput="" - while IFS= read -r batteryPath; do - if [[ -e "${batteryPath}/capacity" ]] || [[ -e "${batteryPath}/status" ]]; then - batteryDetails="$(get_battery_details "$batteryPath")" - - if [[ -n "$batteryDetails" ]]; then - ((batteryCount++)) - if [[ -n "$batteryOutput" ]]; then - batteryOutput="${batteryOutput}. " - fi - batteryOutput="${batteryOutput}${batteryDetails}" - fi + # Append a non-empty battery block, separating batteries with a blank line. + add_battery_block() { + local block="$1" + [[ -z "$block" ]] && return + batteryCount=$((batteryCount + 1)) + if [[ -n "$batteryInfoText" ]]; then + batteryInfoText="${batteryInfoText}"$'\n\n' fi - done < <(find /sys/class/power_supply -type l 2>/dev/null) + batteryInfoText="${batteryInfoText}${block}" + } + + # Prefer acpi -V, fall back to /sys/class/power_supply. + if command -v acpi &> /dev/null; then + acpiInfo="" + while IFS= read -r line; do + [[ -z "$line" ]] && continue + if [[ -n "$acpiInfo" ]]; then + acpiInfo="${acpiInfo}"$'\n' + fi + acpiInfo="${acpiInfo}${line}" + done < <(acpi -V 2>/dev/null | grep -i battery) + add_battery_block "$acpiInfo" + fi if [[ $batteryCount -eq 0 ]]; then - spd-say -P important -Cw "No battery information available" - else - spd-say -P important -Cw "$batteryOutput" + while IFS= read -r batteryPath; do + if [[ -e "${batteryPath}/capacity" ]] || [[ -e "${batteryPath}/status" ]]; then + add_battery_block "$(get_battery_details "$batteryPath")" + fi + done < <(find /sys/class/power_supply -type l 2>/dev/null) fi -else - # Simple mode (default) - if command -v acpi &> /dev/null; then - bat="$(acpi -b)" - spd-say -P important -Cw "$bat" - else - find /sys/class/power_supply -type l -exec bash -c ' - for i ; do - if [[ -e "$i/capacity" ]]; then - bat="${i##*/}" - bat="${bat//BAT/Battery }" - bat="${bat}: $( { cat "${i}/status";echo -n ", "; cat "${i}/capacity"; } | tr -d \\n) percent." - spd-say -P important -Cw "$bat" - fi - done - ' _ {} \; + + if [[ $batteryCount -eq 0 ]]; then + batteryInfoText="No battery information available." fi + + displayText="Battery Information (Detailed) + +${batteryInfoText} + +End of text. Press Control+Home to return to the beginning." + + echo "$displayText" | yad --pname=I38Battery \ + --title="I38 Battery Information" \ + --text-info \ + --show-cursor \ + --width=450 \ + --height=400 \ + --center \ + --button="Close:0" + exit $? fi + +# Simple mode: speak a concise status as a quick announcement. +announcement="" +if command -v acpi &> /dev/null; then + announcement="$(acpi -b 2>/dev/null)" +else + while IFS= read -r batteryPath; do + if [[ -e "${batteryPath}/capacity" ]]; then + block="$(get_battery_simple "$batteryPath")" + [[ -n "$block" ]] && announcement="${announcement:+$announcement. }$block" + fi + done < <(find /sys/class/power_supply -type l 2>/dev/null) +fi + +if [[ -z "$announcement" ]]; then + announcement="No battery information available." +fi + +spd-say -P important -Cw "$announcement" +exit $?