diff --git a/I38.md b/I38.md index 9e370ea..0591a20 100644 --- a/I38.md +++ b/I38.md @@ -95,6 +95,7 @@ In Panel Mode, single keypresses launch different information panels: | `F1` | Show panel mode keybindings | | `w` | Display weather information | | `Shift` + `w` | Open Magic Wormhole file transfer GUI | +| `t` | System Tray - Access minimized applications, status indicators, and background services *(requires waytray)* | | `s` | Show system information | | `r` | Open reminder panel | | `n` | Launch notes application | @@ -134,7 +135,28 @@ The notes panel (`n` key in Panel Mode) provides a simple application for creati The reminder panel (`r` key in Panel Mode) offers the same reminder functionality described in the Reminders and Notifications section, It was previously in ratpoison mode but has been moved to panel mode because it is a better fit. -*Note:* Because Panel Mode uses a custom implementation rather than a traditional system tray, applications that require a system tray to run may not work with I38. +### System Tray (Optional) + +If you have waytray installed, I38 provides keyboard-accessible system tray functionality through the StatusNotifierItem (SNI) protocol. This allows you to interact with applications that use the system tray for status indicators or minimize to tray. + +**Features:** +- Access minimized applications (Discord, Slack, Telegram, etc.) +- View and interact with status indicators (NetworkManager, volume, VPN, etc.) +- Control background services through their tray context menus +- Full keyboard navigation without mouse required + +**Usage:** +1. Press `Control` + `Alt` + `Tab` to enter Panel Mode +2. Press `t` to open the system tray window +3. Use arrow keys to navigate between tray items +4. Press `Enter` or `Space` to activate the selected item +5. Press `Menu` key or `Shift` + `F10` to open context menus +6. Press `Escape` to close the tray window + +**Installation:** +waytray is an optional dependency. If installed, I38 will automatically configure it to provide only system tray functionality (battery, weather, and system information are disabled to avoid duplication with I38's panel utilities). + +*Note:* Without waytray installed, applications that require a system tray to run may not work with I38. However, many applications offer alternative ways to run without a tray. ## Accessibility Features diff --git a/i38.sh b/i38.sh index a1b5c4e..0862bf0 100755 --- a/i38.sh +++ b/i38.sh @@ -401,6 +401,44 @@ update_scripts() { exit 0 } +write_waytray_config() { + # Only create config if waytray binaries are detected + if ! command -v waytray-daemon &> /dev/null || ! command -v waytray &> /dev/null ; then + return 1 + fi + + local waytrayConfigDir="${XDG_CONFIG_HOME:-$HOME/.config}/waytray" + local waytrayConfig="${waytrayConfigDir}/config.toml" + + mkdir -p "${waytrayConfigDir}" + + # Ask user if config already exists + if [[ -f "${waytrayConfig}" ]]; then + if ! yesno "Existing waytray configuration detected. Replace with I38's minimal tray-only config?\n\n(Select 'No' to keep your existing waytray configuration with all modules enabled)"; then + return 0 # User wants to keep existing config + fi + fi + + # Create I38's minimal tray-only config + cat << 'EOF' > "${waytrayConfig}" +# WayTray Configuration - Generated by I38 +# I38 provides its own battery, weather, and system info utilities +# This config enables ONLY the system tray (SNI) functionality + +[modules] +order = ["tray"] + +[modules.tray] +enabled = true + +[notifications] +enabled = true +timeout_ms = 5000 +EOF + + return 0 +} + # Array of command line arguments declare -A command=( @@ -597,6 +635,9 @@ mkdir -p "${i3Path}" # Move scripts into place cp -rv scripts/ "${i3Path}/" | dialog --backtitle "I38" --progressbox "Moving scripts into place and writing config..." -1 -1 +# Configure waytray if available +write_waytray_config + cat << EOF > ${i3Path}/config # Generated by I38 (${0##*/}) https://git.stormux.org/storm/I38 # $(date '+%A, %B %d, %Y at %I:%M%p') @@ -799,10 +840,15 @@ mode "panel" { # Magic wormhole bound to shift+W bindsym Shift+w exec --no-startup-id ${i3Path}/scripts/wormhole.py, mode "default" - + +$(if command -v waytray &> /dev/null ; then + echo " # System tray bound to t" + echo " bindsym t exec --no-startup-id waytray, mode \"default\"" + echo " " +fi) # System information bound to s bindsym s exec --no-startup-id ${i3Path}/scripts/sysinfo.sh, mode "default" - + $(if command -v remind &> /dev/null ; then echo "# Reminders bound to r" echo "bindsym r exec --no-startup-id ${i3Path}/scripts/reminder.sh, mode \"default\"" @@ -816,6 +862,9 @@ $(if command -v blueman-manager &> /dev/null ; then echo "bindsym b exec --no-startup-id blueman-manager, mode \"default\"" fi) + # Detailed battery information bound to Shift+b + bindsym Shift+b exec --no-startup-id ${i3Path}/scripts/battery_status.sh --detailed, mode "default" + $(if command -v lxsession-logout &> /dev/null ; then echo "# Power options bound to p" echo "bindsym p exec --no-startup-id lxsession-logout, mode \"default\"" @@ -980,6 +1029,10 @@ fi if [[ $batteryAlert -eq 0 ]]; then echo "exec_always --no-startup-id ${i3Path}/scripts/battery_alert.sh" fi +# WayTray system tray daemon +if command -v waytray-daemon &> /dev/null ; then + echo 'exec_always --no-startup-id bash -c "pgrep -x waytray-daemon > /dev/null || waytray-daemon"' +fi if [[ $dex -eq 0 ]]; then echo '# Start XDG autostart .desktop files using dex. See also' echo '# https://wiki.archlinux.org/index.php/XDG_Autostart' diff --git a/scripts/battery_status.sh b/scripts/battery_status.sh index ed9f7fb..bd7beb4 100755 --- a/scripts/battery_status.sh +++ b/scripts/battery_status.sh @@ -1,29 +1,217 @@ #!/usr/bin/env bash # This file is part of I38. - + # I38 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, # either version 3 of the License, or (at your option) any later version. - + # I38 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. - + # You should have received a copy of the GNU General Public License along with I38. If not, see . - - -#check for acpi -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 - ' _ {} \; + +# Parse command line arguments +detailedMode=0 +if [[ "$1" == "-d" ]] || [[ "$1" == "--detailed" ]]; then + detailedMode=1 +fi + +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 output="" + + # Get model name if available + if [[ -f "${batteryPath}/model_name" ]]; then + modelName="$(cat "${batteryPath}/model_name" 2>/dev/null | tr -d '\n')" + fi + + # Get manufacturer if available + if [[ -f "${batteryPath}/manufacturer" ]]; then + manufacturer="$(cat "${batteryPath}/manufacturer" 2>/dev/null | tr -d '\n')" + 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 + fi + + # Build readable device name + local deviceName="$batteryName" + if [[ -n "$manufacturer" ]] && [[ -n "$modelName" ]]; then + deviceName="$manufacturer $modelName" + elif [[ -n "$modelName" ]]; then + deviceName="$modelName" + 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 + + echo "$output" +} + +# Simple battery status (default mode) +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" +} + +# 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 + 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 + fi + done < <(find /sys/class/power_supply -type l 2>/dev/null) + + if [[ $batteryCount -eq 0 ]]; then + spd-say -P important -Cw "No battery information available" + else + spd-say -P important -Cw "$batteryOutput" + 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 + ' _ {} \; + fi fi