diff --git a/linux_configuration/i3-configuration/i3blocks/activitywatch_status.sh b/linux_configuration/i3-configuration/i3blocks/activitywatch_status.sh index 9fa9efe..b4f2bf7 100755 --- a/linux_configuration/i3-configuration/i3blocks/activitywatch_status.sh +++ b/linux_configuration/i3-configuration/i3blocks/activitywatch_status.sh @@ -1,48 +1,36 @@ #!/bin/bash -# ActivityWatch status script for i3blocks -# Shows ActivityWatch installation and running status +# ActivityWatch status script for i3blocks. + +set -euo pipefail -# Check if ActivityWatch is installed check_installed() { - # Check if activitywatch-bin package is installed - if pacman -Qi activitywatch-bin &> /dev/null; then - return 0 - fi - - # Check if aw-qt binary exists - if command -v aw-qt &> /dev/null; then - return 0 - fi - - return 1 + command -v aw-qt > /dev/null 2>&1 || command -v aw-server > /dev/null 2>&1 } -# Check if ActivityWatch is running check_running() { - # Check for aw-qt process - if pgrep -f "aw-qt" > /dev/null 2>&1; then - return 0 - fi - - # Check for aw-server process - if pgrep -f "aw-server" > /dev/null 2>&1; then - return 0 - fi - + local proc_file proc_name + for proc_file in /proc/[0-9]*/comm; do + [[ -r $proc_file ]] || continue + read -r proc_name < "$proc_file" || continue + case $proc_name in + aw-qt | aw-server) + return 0 + ;; + esac + done return 1 } -# Main logic if ! check_installed; then echo "AW uninstalled" echo - echo "#FF0000" # Red + echo "#FF0000" elif check_running; then echo "AW on" echo - echo "#00FF00" # Green + echo "#00FF00" else echo "AW off" echo - echo "#FF0000" # Red + echo "#FF0000" fi diff --git a/linux_configuration/i3-configuration/i3blocks/bluetooth.sh b/linux_configuration/i3-configuration/i3blocks/bluetooth.sh index bdd9bb8..24e64df 100755 --- a/linux_configuration/i3-configuration/i3blocks/bluetooth.sh +++ b/linux_configuration/i3-configuration/i3blocks/bluetooth.sh @@ -1,14 +1,27 @@ #!/bin/bash +# i3blocks bluetooth indicator. -# Get Bluetooth device info -bluetooth_info=$(bluetoothctl info) +set -euo pipefail -# Check if Bluetooth is connected -if echo "$bluetooth_info" | grep -q "Connected: yes"; then - device=$(echo "$bluetooth_info" | grep "Alias" | cut -d ' ' -f2-) - echo " $device" #  is the Bluetooth icon +bluetooth_info=$(bluetoothctl info 2> /dev/null) || bluetooth_info='' + +connected='no' +device='' +while IFS= read -r line; do + case $line in + *'Connected: yes') + connected='yes' + ;; + *'Alias: '*) + device=${line#*Alias: } + ;; + esac +done <<< "$bluetooth_info" + +if [[ $connected == yes && -n $device ]]; then + echo " $device" echo - echo "#50FA7B" # Green for connected + echo "#50FA7B" else echo " Disconnected" fi diff --git a/linux_configuration/i3-configuration/i3blocks/config b/linux_configuration/i3-configuration/i3blocks/config index d4ab053..b311faa 100644 --- a/linux_configuration/i3-configuration/i3blocks/config +++ b/linux_configuration/i3-configuration/i3blocks/config @@ -17,13 +17,13 @@ markup=pango [memory] -command=free -h | awk '/^Mem:/ {print " " $3 "/" $2}' #  for RAM +command=~/.config/i3blocks/memory.sh interval=5 color=#50FA7B [disk] -command=df -h / | awk '/\// {print " " $3 "/" $2}' #  for disk +command=~/.config/i3blocks/disk.sh interval=60 color=#50FA7B @@ -50,7 +50,7 @@ markup=pango [ethernet] -command=ip -o -4 addr show | grep -E 'enp6s0|eth0' | awk '{print " "$4}' || echo " down" #  for Ethernet +command=~/.config/i3blocks/ethernet.sh interval=10 color=#FFFFFF @@ -91,6 +91,6 @@ markup=pango [time] -command=echo " $(date '+%Y-%m-%d %H:%M')" #  for time (Font Awesome icon) -interval=1 +command=~/.config/i3blocks/time.sh +interval=persist color=#50FA7B diff --git a/linux_configuration/i3-configuration/i3blocks/disk.sh b/linux_configuration/i3-configuration/i3blocks/disk.sh new file mode 100755 index 0000000..fe85a71 --- /dev/null +++ b/linux_configuration/i3-configuration/i3blocks/disk.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# i3blocks disk usage indicator with a single df helper. + +set -euo pipefail + +{ + read -r _ + read -r _ size used _ +} < <(df -h / 2> /dev/null) || { + echo " N/A" + exit 0 +} + +echo " ${used}/${size}" diff --git a/linux_configuration/i3-configuration/i3blocks/ethernet.sh b/linux_configuration/i3-configuration/i3blocks/ethernet.sh new file mode 100755 index 0000000..f7ff5ea --- /dev/null +++ b/linux_configuration/i3-configuration/i3blocks/ethernet.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# i3blocks ethernet indicator with one external helper at most. + +set -euo pipefail + +find_ethernet_interface() { + local iface_path iface + for iface_path in /sys/class/net/*; do + iface=${iface_path##*/} + [[ $iface == lo ]] && continue + [[ -d ${iface_path}/wireless ]] && continue + [[ -r ${iface_path}/operstate ]] || continue + printf '%s\n' "$iface" + return 0 + done + return 1 +} + +iface=$(find_ethernet_interface) || { + printf ' down\n' + exit 0 +} + +read -r state < "/sys/class/net/${iface}/operstate" +if [[ $state != up ]]; then + printf ' down\n' + exit 0 +fi + +addr_output=$(ip -o -4 addr show dev "$iface" scope global 2> /dev/null) || addr_output='' +if [[ $addr_output =~ ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+) ]]; then + printf ' %s\n' "${BASH_REMATCH[1]}" +else + printf ' down\n' +fi diff --git a/linux_configuration/i3-configuration/i3blocks/memory.sh b/linux_configuration/i3-configuration/i3blocks/memory.sh new file mode 100755 index 0000000..17e90db --- /dev/null +++ b/linux_configuration/i3-configuration/i3blocks/memory.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# i3blocks memory indicator, zero-fork per invocation via /proc/meminfo. + +set -euo pipefail + +format_mib() { + local mib=$1 + if ((mib >= 1024)); then + printf '%d.%dGiB' "$((mib / 1024))" "$((((mib % 1024) * 10) / 1024))" + else + printf '%dMiB' "$mib" + fi +} + +total_kib=0 +available_kib=0 +while IFS=' :' read -r key value _; do + case $key in + MemTotal) + total_kib=$value + ;; + MemAvailable) + available_kib=$value + ;; + esac +done < /proc/meminfo + +used_mib=$(((total_kib - available_kib) / 1024)) +total_mib=$((total_kib / 1024)) + +printf ' %s/%s\n' "$(format_mib "$used_mib")" "$(format_mib "$total_mib")" diff --git a/linux_configuration/i3-configuration/i3blocks/pc_startup_status.sh b/linux_configuration/i3-configuration/i3blocks/pc_startup_status.sh index 058e683..5bd8903 100755 --- a/linux_configuration/i3-configuration/i3blocks/pc_startup_status.sh +++ b/linux_configuration/i3-configuration/i3blocks/pc_startup_status.sh @@ -1,72 +1,71 @@ #!/bin/bash -# PC Startup Monitor status script for i3blocks -# Shows compact startup compliance status in the status bar +# PC Startup Monitor status script for i3blocks. + +set -euo pipefail + +get_now_epoch() { + if [[ -n ${NOW_EPOCH:-} ]]; then + printf '%s\n' "$NOW_EPOCH" + else + printf '%(%s)T\n' -1 + fi +} + +get_uptime_seconds() { + local uptime_line + if [[ -n ${UPTIME_SECONDS:-} ]]; then + printf '%s\n' "$UPTIME_SECONDS" + return 0 + fi + + read -r uptime_line _ < /proc/uptime || uptime_line='0' + printf '%s\n' "${uptime_line%%.*}" +} -# Function to check if today is a monitored day is_monitored_day() { - local day_of_week - day_of_week=$(date +%u) - if [[ $day_of_week == "1" ]] || [[ $day_of_week == "5" ]] || [[ $day_of_week == "6" ]] || [[ $day_of_week == "7" ]]; then - return 0 - else - return 1 - fi + local epoch=$1 day_of_week + printf -v day_of_week '%(%u)T' "$epoch" + case $day_of_week in + 1 | 5 | 6 | 7) + return 0 + ;; + *) + return 1 + ;; + esac } -# Function to check if current time is in window -is_current_time_in_window() { - local current_hour current_hour_num - current_hour=$(date +%H) - current_hour_num=$((10#$current_hour)) - if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then - return 0 - else - return 1 - fi +hour_in_window() { + local epoch=$1 hour + printf -v hour '%(%H)T' "$epoch" + ((10#$hour >= 5 && 10#$hour < 8)) } -# Function to check if PC was booted in window today was_booted_in_window_today() { - local today uptime_seconds boot_time boot_date - today=$(date +%Y-%m-%d) - uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0") - boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") - boot_date=$(echo "$boot_time" | cut -d' ' -f1) - - if [[ $boot_date != "$today" ]]; then - return 1 - fi - - local boot_hour boot_hour_num - boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) - boot_hour_num=$((10#$boot_hour)) - - if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then - return 0 - else - return 1 - fi + local now_epoch=$1 boot_epoch now_day boot_day + boot_epoch=$((now_epoch - $(get_uptime_seconds))) + printf -v now_day '%(%Y-%m-%d)T' "$now_epoch" + printf -v boot_day '%(%Y-%m-%d)T' "$boot_epoch" + [[ $boot_day == "$now_day" ]] || return 1 + hour_in_window "$boot_epoch" } -# Main logic -if ! is_monitored_day; then - # Not a monitored day +now_epoch=$(get_now_epoch) + +if ! is_monitored_day "$now_epoch"; then echo "PC:skip" echo - echo "#888888" # Gray -elif is_current_time_in_window; then - # Currently in the window - all good + echo "#888888" +elif hour_in_window "$now_epoch"; then echo "PC:live" echo - echo "#00FF00" # Green -elif was_booted_in_window_today; then - # Was booted in window today - compliant + echo "#00FF00" +elif was_booted_in_window_today "$now_epoch"; then echo "PC:ok" echo - echo "#00FF00" # Green + echo "#00FF00" else - # Was NOT booted in window today - non-compliant echo "PC:warn" echo - echo "#FF0000" # Red + echo "#FF0000" fi diff --git a/linux_configuration/i3-configuration/i3blocks/shutdown_countdown.sh b/linux_configuration/i3-configuration/i3blocks/shutdown_countdown.sh index 9744595..fd86fa6 100755 --- a/linux_configuration/i3-configuration/i3blocks/shutdown_countdown.sh +++ b/linux_configuration/i3-configuration/i3blocks/shutdown_countdown.sh @@ -1,9 +1,9 @@ #!/bin/bash -# Shutdown countdown status script for i3blocks -# Shows time remaining until the next shutdown window -# Reads shutdown times from shared config file written by setup_midnight_shutdown.sh +# Shutdown countdown status script for i3blocks. -SHUTDOWN_CONFIG="/etc/shutdown-schedule.conf" +set -euo pipefail + +SHUTDOWN_CONFIG=${SHUTDOWN_CONFIG:-/etc/shutdown-schedule.conf} # Function to show error state in i3blocks and exit show_error() { @@ -14,87 +14,95 @@ show_error() { exit 0 } -# Validate and load config file if [[ ! -f $SHUTDOWN_CONFIG ]]; then show_error "NO CONFIG" fi -# Source the config file to get MON_WED_HOUR and THU_SUN_HOUR -# shellcheck source=/dev/null -if ! source "$SHUTDOWN_CONFIG" 2> /dev/null; then - show_error "BAD CONFIG" -fi +MON_WED_HOUR='' +THU_SUN_HOUR='' +morning_end_hour='5' +while IFS='=' read -r key value; do + value=${value%%[[:space:]]*} + case $key in + MON_WED_HOUR) + MON_WED_HOUR=$value + ;; + THU_SUN_HOUR) + THU_SUN_HOUR=$value + ;; + MORNING_END_HOUR) + morning_end_hour=$value + ;; + esac +done < "$SHUTDOWN_CONFIG" -# Validate that required variables are set -if [[ -z ${MON_WED_HOUR:-} ]] || [[ -z ${THU_SUN_HOUR:-} ]]; then +if [[ -z $MON_WED_HOUR ]] || [[ -z $THU_SUN_HOUR ]]; then show_error "MISSING VARS" fi -# Validate that values are numbers if ! [[ $MON_WED_HOUR =~ ^[0-9]+$ ]] || ! [[ $THU_SUN_HOUR =~ ^[0-9]+$ ]]; then show_error "INVALID HOURS" fi -# Get current time info -current_hour=$(date +%H) -current_minute=$(date +%M) -current_time_minutes=$((10#$current_hour * 60 + 10#$current_minute)) -day_of_week=$(date +%u) # 1=Monday, 7=Sunday +if ! [[ $morning_end_hour =~ ^[0-9]+$ ]]; then + show_error "INVALID HOURS" +fi + +get_now_epoch() { + if [[ -n ${NOW_EPOCH:-} ]]; then + printf '%s\n' "$NOW_EPOCH" + else + printf '%(%s)T\n' -1 + fi +} + +now_epoch=$(get_now_epoch) +printf -v current_hour '%(%H)T' "$now_epoch" +printf -v current_minute '%(%M)T' "$now_epoch" +printf -v day_of_week '%(%u)T' "$now_epoch" + +current_time_minutes=$((10#$current_hour * 60 + 10#$current_minute)) +morning_end_minutes=$((10#$morning_end_hour * 60)) -# Determine shutdown hour based on day of week if [[ $day_of_week -ge 1 ]] && [[ $day_of_week -le 3 ]]; then - # Monday-Wednesday shutdown_hour=$MON_WED_HOUR else - # Thursday-Sunday shutdown_hour=$THU_SUN_HOUR fi shutdown_time_minutes=$((shutdown_hour * 60)) -# Check if we're currently in the shutdown window (after shutdown time or before 05:00) -if [[ $current_time_minutes -ge $shutdown_time_minutes ]] || [[ $current_time_minutes -le 300 ]]; then - # We're in shutdown window - show warning +if [[ $current_time_minutes -ge $shutdown_time_minutes ]] || [[ $current_time_minutes -le $morning_end_minutes ]]; then echo "⏻ SHUTDOWN" echo "⏻" echo "#FF5555" exit 0 fi -# Calculate minutes until shutdown minutes_until_shutdown=$((shutdown_time_minutes - current_time_minutes)) - -# Convert to hours and minutes hours=$((minutes_until_shutdown / 60)) minutes=$((minutes_until_shutdown % 60)) -# Format output if [[ $hours -gt 0 ]]; then time_str="${hours}h ${minutes}m" else time_str="${minutes}m" fi -# Color based on time remaining if [[ $minutes_until_shutdown -le 30 ]]; then - # Less than 30 min - red warning color="#FF5555" icon="⏻" elif [[ $minutes_until_shutdown -le 60 ]]; then - # Less than 1 hour - orange warning color="#FFB86C" icon="⏻" elif [[ $minutes_until_shutdown -le 120 ]]; then - # Less than 2 hours - yellow color="#F1FA8C" icon="⏻" else - # More than 2 hours - normal color="#6272A4" icon="⏻" fi -# Output for i3blocks (full_text, short_text, color) echo "$icon $time_str" echo "$icon" echo "$color" diff --git a/linux_configuration/i3-configuration/i3blocks/time.sh b/linux_configuration/i3-configuration/i3blocks/time.sh new file mode 100755 index 0000000..7dbfb51 --- /dev/null +++ b/linux_configuration/i3-configuration/i3blocks/time.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# i3blocks clock, persist mode with zero external helpers in the hot path. + +set -uo pipefail + +emit() { + printf ' %(%Y-%m-%d %H:%M)T\n' -1 +} + +while :; do + emit + printf -v now '%(%s)T' -1 + delay=$((60 - now % 60)) + IFS= read -r -t "$delay" _ || true +done diff --git a/linux_configuration/i3-configuration/i3blocks/warp_status.sh b/linux_configuration/i3-configuration/i3blocks/warp_status.sh index a4ffa9d..d649822 100755 --- a/linux_configuration/i3-configuration/i3blocks/warp_status.sh +++ b/linux_configuration/i3-configuration/i3blocks/warp_status.sh @@ -1,26 +1,32 @@ #!/bin/bash +# i3blocks WARP indicator with a single helper process. -# Check if warp-cli is installed -if ! command -v warp-cli &> /dev/null; then +set -euo pipefail + +if ! command -v warp-cli > /dev/null 2>&1; then echo " N/A" exit 0 fi -# Get the status from warp-cli -status=$(warp-cli status 2> /dev/null | grep "Status update:" | awk '{print $3}') +status='' +while IFS= read -r line; do + case $line in + 'Status update: '*) + status=${line#Status update: } + ;; + esac +done < <(warp-cli status 2> /dev/null) -# Display the status with an icon -if [ "$status" = "Connected" ]; then +if [[ $status == Connected ]]; then echo "🔒 !!! WARP CONNECTED !!!" echo - echo "#FFFF00" # Yellow -elif [ "$status" = "Disconnected" ]; then + echo "#FFFF00" +elif [[ $status == Disconnected ]]; then echo "WARP disconnected" echo - echo "#00FF00" # Green + echo "#00FF00" else echo "⚠️ ! WARP unknown !" echo - echo "#FF0000" # Red - exit 0 + echo "#FF0000" fi diff --git a/linux_configuration/i3-configuration/i3blocks/wifi_monitor.sh b/linux_configuration/i3-configuration/i3blocks/wifi_monitor.sh index e818c62..65ed724 100755 --- a/linux_configuration/i3-configuration/i3blocks/wifi_monitor.sh +++ b/linux_configuration/i3-configuration/i3blocks/wifi_monitor.sh @@ -1,27 +1,62 @@ #!/bin/bash +# i3blocks Wi-Fi indicator with a small, bounded helper budget. -# Detect the active WiFi interface -wifi_interface=$(iw dev | awk '$1=="Interface"{print $2}') +set -euo pipefail -# If no WiFi interface is found, exit -if [ -z "$wifi_interface" ]; then +find_wifi_interface() { + local line + while IFS= read -r line; do + case $line in + *'Interface '*) + printf '%s\n' "${line##*Interface }" + return 0 + ;; + esac + done < <(iw dev 2> /dev/null) + return 1 +} + +wifi_interface=$(find_wifi_interface) || { echo " down" - exit 1 + exit 0 +} + +ssid='' +signal='' +while IFS= read -r line; do + case $line in + 'SSID: '*) + ssid=${line#SSID: } + ;; + 'signal: '*) + signal=${line#signal: } + signal=${signal% dBm} + ;; + 'Not connected.'*) + ssid='' + ;; + esac +done < <(iw dev "$wifi_interface" link 2> /dev/null) + +if [[ -z $ssid ]]; then + echo " down" + exit 0 fi -# Get the WiFi details -wifi_info=$(iwconfig "$wifi_interface" 2> /dev/null) +ip_address='' +while IFS= read -r line; do + [[ $line == *' inet '* ]] || continue + read -r -a fields <<< "$line" + for ((i = 0; i < ${#fields[@]}; i++)); do + if [[ ${fields[i]} == inet && $((i + 1)) -lt ${#fields[@]} ]]; then + ip_address=${fields[i + 1]%%/*} + break 2 + fi + done +done < <(ip -o -4 addr show dev "$wifi_interface" scope global 2> /dev/null) -# Extract the SSID and signal strength -ssid=$(echo "$wifi_info" | awk -F '"' '/ESSID/ {print $2}') -signal=$(echo "$wifi_info" | awk '/Signal level/ {print $4}' | sed 's/level=//') - -# Get the IP address -ip_address=$(ip addr show "$wifi_interface" | awk '/inet / {print $2}' | cut -d/ -f1) - -# Output the result -if [ -z "$ssid" ]; then - echo " down" -else +if [[ -n $ip_address ]]; then echo " $ssid ($signal dBm) $ip_address" +else + echo " $ssid ($signal dBm)" fi diff --git a/linux_configuration/scripts/digital_wellbeing/focus_mode_daemon.py b/linux_configuration/scripts/digital_wellbeing/focus_mode_daemon.py index 6f7986c..a5eb4ef 100755 --- a/linux_configuration/scripts/digital_wellbeing/focus_mode_daemon.py +++ b/linux_configuration/scripts/digital_wellbeing/focus_mode_daemon.py @@ -162,26 +162,19 @@ def notify(title: str, message: str, urgency: str = "normal") -> None: def get_running_processes() -> set[str]: - """Get set of currently running process names.""" + """Get set of currently running process names by reading /proc directly. + + Reads /proc/*/comm to avoid forking a subprocess on every poll cycle. + """ processes: set[str] = set() - ps_bin = shutil.which("ps") - if ps_bin is None: - return processes try: - result = subprocess.run( - [ps_bin, "-eo", "comm="], - capture_output=True, - text=True, - timeout=10, - check=False, - ) - if result.returncode == 0: - for line in result.stdout.strip().split("\n"): - proc_name = line.strip().lower() + for comm_path in Path("/proc").glob("*/comm"): + with contextlib.suppress(OSError): + proc_name = comm_path.read_text().strip().lower() if proc_name: processes.add(proc_name) - except (OSError, subprocess.SubprocessError) as exc: - log(f"Error getting processes: {exc}") + except OSError as exc: + log(f"Error reading /proc: {exc}") return processes diff --git a/linux_configuration/scripts/lib/common.sh b/linux_configuration/scripts/lib/common.sh index 1443025..a7817bd 100755 --- a/linux_configuration/scripts/lib/common.sh +++ b/linux_configuration/scripts/lib/common.sh @@ -19,7 +19,7 @@ log_message() { local msg="$1" local log_file="${2:-}" local formatted - formatted="$(date '+%Y-%m-%d %H:%M:%S') - $msg" + printf -v formatted '%(%Y-%m-%d %H:%M:%S)T - %s' -1 "$msg" echo "$formatted" >&2 if [[ -n $log_file ]]; then echo "$formatted" >>"$log_file" 2>/dev/null || true @@ -29,7 +29,9 @@ log_message() { # Simple log with timestamp (no file output) # Usage: log "message" log() { - printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" + local _ts + printf -v _ts '%(%Y-%m-%d %H:%M:%S)T' -1 + printf '[%s] %s\n' "$_ts" "$*" } # ============================================================================= @@ -180,24 +182,29 @@ FOCUS_APPS_PROCESSES=( # Returns 0 if focus app found, 1 otherwise # Echoes the name of the found app is_focus_app_running() { - # Check windows first - if command -v xdotool &>/dev/null; then - local app - for app in "${FOCUS_APPS_WINDOWS[@]}"; do - if xdotool search --name "$app" &>/dev/null 2>&1; then - echo "$app" + # One xdotool call with a combined regex instead of N separate calls + if command -v xdotool &>/dev/null && [[ ${#FOCUS_APPS_WINDOWS[@]} -gt 0 ]]; then + local regex wid + printf -v regex '%s|' "${FOCUS_APPS_WINDOWS[@]}" + regex="${regex%|}" # strip trailing | + wid=$(xdotool search --name "$regex" 2>/dev/null | head -1 || true) + if [[ -n $wid ]]; then + xdotool getwindowname "$wid" 2>/dev/null || echo "focus app" + return 0 + fi + fi + + # Check specific processes via /proc (no fork) + local app comm + for app in "${FOCUS_APPS_PROCESSES[@]}"; do + for comm in /proc/[0-9]*/comm; do + [[ -r $comm ]] || continue + read -r _proc_comm < "$comm" 2>/dev/null || continue + if [[ $_proc_comm == *"$app"* ]]; then + echo "$_proc_comm" return 0 fi done - fi - - # Check specific processes - local app - for app in "${FOCUS_APPS_PROCESSES[@]}"; do - if pgrep -f "$app" &>/dev/null; then - echo "$app" - return 0 - fi done return 1 diff --git a/linux_configuration/scripts/system-maintenance/bin/hosts-file-monitor.sh b/linux_configuration/scripts/system-maintenance/bin/hosts-file-monitor.sh index 2caa60a..6f15b39 100755 --- a/linux_configuration/scripts/system-maintenance/bin/hosts-file-monitor.sh +++ b/linux_configuration/scripts/system-maintenance/bin/hosts-file-monitor.sh @@ -11,7 +11,9 @@ HOSTS_INSTALL_SCRIPT="__HOSTS_INSTALL_SCRIPT__" # Log with timestamp (hosts-file-monitor specific) log_message() { - printf '%s [hosts-monitor] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" | tee -a "$LOG_FILE" >&2 + local _ts + printf -v _ts '%(%Y-%m-%d %H:%M:%S)T' -1 + printf '%s [hosts-monitor] %s\n' "$_ts" "$1" | tee -a "$LOG_FILE" >&2 } # Function to check if hosts file needs restoration diff --git a/linux_configuration/scripts/system-maintenance/bin/shutdown-timer-monitor.sh b/linux_configuration/scripts/system-maintenance/bin/shutdown-timer-monitor.sh index d09b25a..027b780 100755 --- a/linux_configuration/scripts/system-maintenance/bin/shutdown-timer-monitor.sh +++ b/linux_configuration/scripts/system-maintenance/bin/shutdown-timer-monitor.sh @@ -12,7 +12,9 @@ CHECK_INTERVAL=30 # Log with timestamp (shutdown-timer-monitor specific) log_message() { - printf '%s [shutdown-monitor] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" | tee -a "$LOG_FILE" >&2 + local _ts + printf -v _ts '%(%Y-%m-%d %H:%M:%S)T' -1 + printf '%s [shutdown-monitor] %s\n' "$_ts" "$1" | tee -a "$LOG_FILE" >&2 } # Function to check if timer needs to be re-enabled diff --git a/linux_configuration/tests/test_i3blocks_efficiency.sh b/linux_configuration/tests/test_i3blocks_efficiency.sh new file mode 100755 index 0000000..95815b0 --- /dev/null +++ b/linux_configuration/tests/test_i3blocks_efficiency.sh @@ -0,0 +1,272 @@ +#!/bin/bash +# Regression tests for i3blocks hot-path efficiency fixes. + +set -euo pipefail + +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd) +I3BLOCKS_DIR="$REPO_DIR/i3-configuration/i3blocks" +CONFIG_FILE="$I3BLOCKS_DIR/config" + +TMP_DIR=$(mktemp -d) +BIN_DIR="$TMP_DIR/bin" +mkdir -p "$BIN_DIR" + +cleanup() { + if [[ -n "${AW_PID:-}" ]]; then + kill "$AW_PID" 2>/dev/null || true + wait "$AW_PID" 2>/dev/null || true + fi + rm -rf "$TMP_DIR" +} +trap cleanup EXIT + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + exit 1 +} + +assert_equals() { + local expected="$1" + local actual="$2" + local context="$3" + if [[ "$expected" != "$actual" ]]; then + fail "$context (expected: '$expected', actual: '$actual')" + fi +} + +assert_le() { + local actual="$1" + local expected_max="$2" + local context="$3" + if (( actual > expected_max )); then + fail "$context (expected <= $expected_max, actual: $actual)" + fi +} + +epoch_utc() { + TZ=UTC date -d "$1" +%s +} + +count_execs() { + local script_path="$1" + local log_file="$TMP_DIR/trace.log" + PATH="$BIN_DIR:$PATH" strace -f -o "$log_file" -e trace=execve bash "$script_path" \ + >/dev/null 2>&1 + grep -c 'execve(' "$log_file" +} + +cat >"$BIN_DIR/bluetoothctl" <<'EOF' +#!/bin/bash +printf '%s\n' \ + 'Device AA:BB:CC:DD:EE:FF' \ + 'Alias: Test Headphones' \ + 'Connected: yes' +EOF +chmod +x "$BIN_DIR/bluetoothctl" + +cat >"$BIN_DIR/pacman" <<'EOF' +#!/bin/bash +exit 1 +EOF +chmod +x "$BIN_DIR/pacman" + +cat >"$BIN_DIR/pgrep" <<'EOF' +#!/bin/bash +exit 1 +EOF +chmod +x "$BIN_DIR/pgrep" + +cat >"$BIN_DIR/iw" <<'EOF' +#!/bin/bash +set -euo pipefail + +if [[ $# -eq 1 && $1 == dev ]]; then + if [[ ${WIFI_HAS_INTERFACE:-1} == 1 ]]; then + printf '%s\n' \ + 'phy#0' \ + ' Interface wlan0' + fi + exit 0 +fi + +if [[ $# -eq 3 && $1 == dev && $3 == link ]]; then + if [[ ${WIFI_CONNECTED:-1} == 1 ]]; then + printf '%s\n' \ + 'Connected to 00:11:22:33:44:55 (on wlan0)' \ + 'SSID: TestWifi' \ + 'signal: -53 dBm' + else + printf '%s\n' 'Not connected.' + fi + exit 0 +fi + +printf 'unexpected iw args: %s\n' "$*" >&2 +exit 1 +EOF +chmod +x "$BIN_DIR/iw" + +cat >"$BIN_DIR/ip" <<'EOF' +#!/bin/bash +set -euo pipefail + +if [[ $# -ge 8 && $1 == -o && $2 == -4 && $3 == addr && $4 == show ]]; then + printf '%s\n' '3: wlan0 inet 192.168.1.44/24 brd 192.168.1.255 scope global dynamic wlan0' + exit 0 +fi + +printf 'unexpected ip args: %s\n' "$*" >&2 +exit 1 +EOF +chmod +x "$BIN_DIR/ip" + +cat >"$BIN_DIR/warp-cli" <<'EOF' +#!/bin/bash +set -euo pipefail + +if [[ $# -eq 1 && $1 == status ]]; then + printf 'Status update: %s\n' "${WARP_STATUS:-Disconnected}" + exit 0 +fi + +printf 'unexpected warp-cli args: %s\n' "$*" >&2 +exit 1 +EOF +chmod +x "$BIN_DIR/warp-cli" + +cat >"$BIN_DIR/df" <<'EOF' +#!/bin/bash +set -euo pipefail + +if [[ $# -eq 2 && $1 == -h && $2 == / ]]; then + printf '%s\n' \ + 'Filesystem Size Used Avail Use% Mounted on' \ + '/dev/nvme0n1p2 100G 15G 80G 16% /' + exit 0 +fi + +printf 'unexpected df args: %s\n' "$*" >&2 +exit 1 +EOF +chmod +x "$BIN_DIR/df" + +ln -s /bin/sleep "$BIN_DIR/aw-qt" + +printf 'Checking config uses dedicated low-fork scripts...\n' +grep -q '^command=~/.config/i3blocks/time.sh$' "$CONFIG_FILE" \ + || fail 'time block should call time.sh' +grep -q '^interval=persist$' "$CONFIG_FILE" \ + || fail 'config should use persist interval for time block' +grep -q '^command=~/.config/i3blocks/memory.sh$' "$CONFIG_FILE" \ + || fail 'memory block should call memory.sh' +grep -q '^command=~/.config/i3blocks/ethernet.sh$' "$CONFIG_FILE" \ + || fail 'ethernet block should call ethernet.sh' +grep -q '^command=~/.config/i3blocks/disk.sh$' "$CONFIG_FILE" \ + || fail 'disk block should call disk.sh' + +printf 'Checking bluetooth block behavior and fork count...\n' +bluetooth_output=$(PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/bluetooth.sh") +assert_equals ' Test Headphones' "$(printf '%s\n' "$bluetooth_output" | sed -n '1p')" \ + 'bluetooth script should show connected alias' +assert_le "$(count_execs "$I3BLOCKS_DIR/bluetooth.sh")" 2 \ + 'bluetooth script should stay at one external helper plus bash' + +printf 'Checking ActivityWatch block behavior and fork count...\n' +activitywatch_output=$(PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/activitywatch_status.sh") +assert_equals 'AW off' "$(printf '%s\n' "$activitywatch_output" | sed -n '1p')" \ + 'activitywatch script should show AW off when not running' +assert_le "$(count_execs "$I3BLOCKS_DIR/activitywatch_status.sh")" 1 \ + 'activitywatch script should avoid pacman/pgrep hot-path forks' + +"$BIN_DIR/aw-qt" 60 >/dev/null 2>&1 & +AW_PID=$! +activitywatch_running_output=$(PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/activitywatch_status.sh") +assert_equals 'AW on' "$(printf '%s\n' "$activitywatch_running_output" | sed -n '1p')" \ + 'activitywatch script should detect running aw-qt process' + +printf 'Checking Wi-Fi block behavior and fork count...\n' +wifi_connected_output=$(PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/wifi_monitor.sh") +assert_equals ' TestWifi (-53 dBm) 192.168.1.44' \ + "$wifi_connected_output" \ + 'wifi script should show ssid, signal, and IP when connected' +assert_le "$(count_execs "$I3BLOCKS_DIR/wifi_monitor.sh")" 4 \ + 'wifi script should stay within bash plus iw/iw/ip exec budget' + +wifi_disconnected_output=$(WIFI_CONNECTED=0 PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/wifi_monitor.sh") +assert_equals ' down' "$wifi_disconnected_output" \ + 'wifi script should show down when the interface is not connected' + +wifi_missing_output=$(WIFI_HAS_INTERFACE=0 PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/wifi_monitor.sh") || true +assert_equals ' down' "$wifi_missing_output" \ + 'wifi script should show down when no Wi-Fi interface exists' + +printf 'Checking WARP block behavior and fork count...\n' +warp_connected_output=$(WARP_STATUS=Connected PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/warp_status.sh") +assert_equals '🔒 !!! WARP CONNECTED !!!' "$(printf '%s\n' "$warp_connected_output" | sed -n '1p')" \ + 'warp script should show the connected warning when WARP is enabled' +assert_equals '#FFFF00' "$(printf '%s\n' "$warp_connected_output" | sed -n '3p')" \ + 'warp script should show yellow when WARP is connected' +assert_le "$(count_execs "$I3BLOCKS_DIR/warp_status.sh")" 2 \ + 'warp script should stay at one external helper plus bash' + +warp_disconnected_output=$(WARP_STATUS=Disconnected PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/warp_status.sh") +assert_equals 'WARP disconnected' "$(printf '%s\n' "$warp_disconnected_output" | sed -n '1p')" \ + 'warp script should show the disconnected state' + +warp_unknown_output=$(WARP_STATUS=Unknown PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/warp_status.sh") +assert_equals '⚠️ ! WARP unknown !' "$(printf '%s\n' "$warp_unknown_output" | sed -n '1p')" \ + 'warp script should show the unknown state when status parsing fails' + +printf 'Checking disk block behavior and fork count...\n' +disk_output=$(PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/disk.sh") +assert_equals ' 15G/100G' "$disk_output" \ + 'disk script should show used and total disk space' +assert_le "$(count_execs "$I3BLOCKS_DIR/disk.sh")" 2 \ + 'disk script should stay at one external helper plus bash' + +printf 'Checking PC startup block behavior and fork count...\n' +pc_live_epoch=$(epoch_utc '2026-05-01 06:30:00') +pc_live_output=$(TZ=UTC NOW_EPOCH="$pc_live_epoch" UPTIME_SECONDS=1800 PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/pc_startup_status.sh") +assert_equals 'PC:live' "$(printf '%s\n' "$pc_live_output" | sed -n '1p')" \ + 'pc startup script should show live during the monitored startup window' +assert_le "$(count_execs "$I3BLOCKS_DIR/pc_startup_status.sh")" 1 \ + 'pc startup script should avoid date and text-processing helpers' + +pc_ok_epoch=$(epoch_utc '2026-05-01 10:00:00') +pc_ok_output=$(TZ=UTC NOW_EPOCH="$pc_ok_epoch" UPTIME_SECONDS=14400 PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/pc_startup_status.sh") +assert_equals 'PC:ok' "$(printf '%s\n' "$pc_ok_output" | sed -n '1p')" \ + 'pc startup script should show ok when boot happened inside the startup window' + +pc_warn_epoch=$(epoch_utc '2026-05-01 10:00:00') +pc_warn_output=$(TZ=UTC NOW_EPOCH="$pc_warn_epoch" UPTIME_SECONDS=1800 PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/pc_startup_status.sh") +assert_equals 'PC:warn' "$(printf '%s\n' "$pc_warn_output" | sed -n '1p')" \ + 'pc startup script should warn when boot happened outside the startup window' + +pc_skip_epoch=$(epoch_utc '2026-04-30 10:00:00') +pc_skip_output=$(TZ=UTC NOW_EPOCH="$pc_skip_epoch" UPTIME_SECONDS=1800 PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/pc_startup_status.sh") +assert_equals 'PC:skip' "$(printf '%s\n' "$pc_skip_output" | sed -n '1p')" \ + 'pc startup script should skip on unmonitored days' + +printf 'Checking shutdown countdown behavior and fork count...\n' +shutdown_config_file="$TMP_DIR/shutdown-schedule.conf" +cat >"$shutdown_config_file" <<'EOF' +MON_WED_HOUR=23 +THU_SUN_HOUR=21 +EOF + +shutdown_countdown_epoch=$(epoch_utc '2026-05-01 19:30:00') +shutdown_countdown_output=$(TZ=UTC NOW_EPOCH="$shutdown_countdown_epoch" SHUTDOWN_CONFIG="$shutdown_config_file" PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/shutdown_countdown.sh") +assert_equals '⏻ 1h 30m' "$(printf '%s\n' "$shutdown_countdown_output" | sed -n '1p')" \ + 'shutdown countdown should show the time remaining until shutdown' +assert_equals '#F1FA8C' "$(printf '%s\n' "$shutdown_countdown_output" | sed -n '3p')" \ + 'shutdown countdown should show yellow for two hours or less remaining' +assert_le "$(count_execs "$I3BLOCKS_DIR/shutdown_countdown.sh")" 1 \ + 'shutdown countdown should avoid date helpers in the hot path' + +shutdown_window_epoch=$(epoch_utc '2026-05-01 21:15:00') +shutdown_window_output=$(TZ=UTC NOW_EPOCH="$shutdown_window_epoch" SHUTDOWN_CONFIG="$shutdown_config_file" PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/shutdown_countdown.sh") +assert_equals '⏻ SHUTDOWN' "$(printf '%s\n' "$shutdown_window_output" | sed -n '1p')" \ + 'shutdown countdown should show SHUTDOWN inside the blocked window' + +printf 'All i3blocks efficiency regression tests passed.\n'