diff --git a/.gitignore b/.gitignore index b170f92..7374a5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +I38_preferences.conf **/__pycache__/ *.pyc *.pyo diff --git a/i38.sh b/i38.sh index 883e0b0..fa1ecc4 100755 --- a/i38.sh +++ b/i38.sh @@ -15,6 +15,7 @@ usingSway=1 # Not by default. i3Path="${XDG_CONFIG_HOME:-$HOME/.config}/i3" i3msg="i3-msg" +configFile="${PWD}/I38_preferences.conf" # Dialog accessibility export DIALOGOPTS='--no-lines --visit-items' @@ -28,7 +29,7 @@ done if ! python3 -c 'import i3ipc' &> /dev/null ; then missing+=("python-i3ipc") fi -if [[ -n "${missing}" ]]; then +if [[ ${#missing[@]} -gt 0 ]]; then echo "Please install the following packages and run this script again:" echo "${missing[*]}" exit 1 @@ -175,7 +176,7 @@ yesno() { # Returns: Yes 0 or No 1 # Args: Question to user. dialog --clear --title "I38" --yesno "$*" -1 -1 --stdout - echo $? + return $? } # Custom application keybinding functions @@ -333,7 +334,80 @@ addCustomApplication() { break done done -} +} + +load_config() { + # Load existing configuration if available + if [[ -f "$configFile" ]]; then + # shellcheck source=/dev/null + source "$configFile" + + # Convert kbd string back to array if present + if [[ -n "$kbd" ]]; then + IFS=' ' read -ra kbd <<< "$kbd" + fi + + # Reconstruct customApps array from numbered entries + customApps=() + local i=0 + local varName + while : ; do + varName="customApp_$i" + if [[ -n "${!varName}" ]]; then + customApps+=("${!varName}") + ((i++)) + else + break + fi + done + + return 0 + fi + return 1 +} + +save_config() { + if yesno "Save this configuration for future runs?"; then + cat > "$configFile" << EOF +# I38 Configuration Preferences +# Generated by i38.sh on $(date) +# Edit this file to change saved preferences or delete to reconfigure from scratch + +# Keyboard configuration +escapeKey="$escapeKey" +mod="$mod" + +# Keyboard layouts (space-separated) +kbd="${kbd[*]}" + +# Volume settings +volumeJump="$volumeJump" + +# Application paths +screenReader="$screenReader" +emailClient="$emailClient" +webBrowser="$webBrowser" +textEditor="$textEditor" +fileBrowser="$fileBrowser" +ircClient="$ircClient" + +# Boolean settings (0=yes, 1=no) +udiskie="$udiskie" +dex="$dex" +batteryAlert="${batteryAlert:-1}" +brlapi="$brlapi" +sounds="$sounds" + +# Custom applications +EOF + # Save custom apps with numbered keys + for i in "${!customApps[@]}"; do + echo "customApp_$i=\"${customApps[$i]}\"" >> "$configFile" + done + + dialog --title "I38" --msgbox "Configuration saved to $configFile\n\nYou can edit this file manually or delete it to reconfigure from scratch." 0 0 + fi +} help() { echo "${0##*/}" @@ -344,6 +418,9 @@ help() { for i in "${!command[@]}" ; do echo "-${i/:/ }: ${command[${i}]}" done | sort + echo "" + echo "Configuration preferences can be saved to I38_preferences.conf in the current directory." + echo "Delete this file to reconfigure from scratch." exit 0 } @@ -378,8 +455,7 @@ chmod +x ~/.xinitrc write_xprofile() { if [[ -f "$HOME/.xprofile" ]]; then -continue="$(yesno "Would you like to add accessibility variables to your $HOME/.xprofile? Without these, accessibility will be limited or may not work at all. Do you want to continue?")" -if [ "$continue" = "no" ]; then +if ! yesno "Would you like to add accessibility variables to your $HOME/.xprofile? Without these, accessibility will be limited or may not work at all. Do you want to continue?"; then exit 0 fi fi @@ -467,6 +543,13 @@ while getopts "${args}" i ; do esac done +# Load saved configuration if available +configLoaded=0 +if load_config; then + configLoaded=1 + dialog --title "I38" --msgbox "Loaded saved preferences from $configFile\n\nMissing or invalid values will be prompted." 0 0 +fi + # Mod1 alt # Mod4 super # Mod2 and Mod3 not usually defined. @@ -475,155 +558,224 @@ done # Ratpoison mode is enabled by default export i3Mode=0 # Prevent setting ratpoison mode key to the same as default mode key -while [[ "$escapeKey" == "$mod" ]] || [[ "$escapeKey" =~ ^Super_ && "$mod" == "Mod4" ]] || [[ "$mod" == "Mod4" && "$escapeKey" =~ ^Super_ ]]; do - escapeKey="$(menulist "Ratpoison mode key:" Control+t Control+z Control+Escape Alt+Escape Control+space "Super Left" "Super Right")" - escapeKey="${escapeKey//Alt/Mod1}" - escapeKey="${escapeKey//Super Left/Super_L}" - escapeKey="${escapeKey//Super Right/Super_R}" - mod="$(menulist "I3 mod key, for top level bindings:" Alt Super)" - mod="${mod//Alt/Mod1}" - mod="${mod//Super/Mod4}" - if [ "$escapeKey" == "$mod" ]; then - dialog --title "I38" --msgbox "Ratpoison and mod key cannot be the same key." -1 -1 - elif [[ "$escapeKey" =~ ^Super_ && "$mod" == "Mod4" ]]; then - dialog --title "I38" --msgbox "Ratpoison mode key cannot be a Super key when mod key is Super." -1 -1 - fi -done -# Multiple keyboard layouts -if [[ $(yesno "Do you want to use multiple keyboard layouts?") -eq 0 ]]; then - unset kbd - while : ; do - kbd+=("$(keyboard_menu)") || break +if [[ -z "$escapeKey" ]] || [[ -z "$mod" ]]; then + while [[ "$escapeKey" == "$mod" ]] || [[ "$escapeKey" =~ ^Super_ && "$mod" == "Mod4" ]] || [[ "$mod" == "Mod4" && "$escapeKey" =~ ^Super_ ]]; do + escapeKey="$(menulist "Ratpoison mode key:" Control+t Control+z Control+Escape Alt+Escape Control+space "Super Left" "Super Right")" + escapeKey="${escapeKey//Alt/Mod1}" + escapeKey="${escapeKey//Super Left/Super_L}" + escapeKey="${escapeKey//Super Right/Super_R}" + mod="$(menulist "I3 mod key, for top level bindings:" Alt Super)" + mod="${mod//Alt/Mod1}" + mod="${mod//Super/Mod4}" + if [ "$escapeKey" == "$mod" ]; then + dialog --title "I38" --msgbox "Ratpoison and mod key cannot be the same key." -1 -1 + elif [[ "$escapeKey" =~ ^Super_ && "$mod" == "Mod4" ]]; then + dialog --title "I38" --msgbox "Ratpoison mode key cannot be a Super key when mod key is Super." -1 -1 + fi done fi +# Multiple keyboard layouts +if [[ ${#kbd[@]} -eq 0 ]]; then + if yesno "Do you want to use multiple keyboard layouts?"; then + unset kbd + while : ; do + kbd+=("$(keyboard_menu)") || break + done + fi +fi # Volume jump -volumeJump=$(rangebox "How much should pressing the volume keys change the volume?" 1 15 5) +if [[ -z "$volumeJump" ]]; then + volumeJump=$(rangebox "How much should pressing the volume keys change the volume?" 1 15 5) +fi # Screen Reader -unset programList -for i in cthulhu orca ; do - if command -v ${i/#-/} &> /dev/null ; then - if [ -n "$programList" ]; then - programList="$programList $i" - else - programList="$i" +if [[ -z "$screenReader" ]] || ! command -v "$screenReader" &> /dev/null; then + unset programList + for i in cthulhu orca ; do + if command -v ${i/#-/} &> /dev/null ; then + if [ -n "$programList" ]; then + programList="$programList $i" + else + programList="$i" + fi fi + done + if [ "$programList" != "${programList// /}" ]; then + screenReader="$(menulist ":Screen Reader" $programList)" + else + screenReader="${programList/#-/}" fi -done -if [ "$programList" != "${programList// /}" ]; then - screenReader="$(menulist ":Screen Reader" $programList)" + export screenReader="$(command -v $screenReader)" else - screenReader="${programList/#-/}" + # Validate and export existing preference + export screenReader fi -export screenReader="$(command -v $screenReader)" # Email client -unset programList -for i in betterbird evolution thunderbird ; do - if command -v ${i/#-/} &> /dev/null ; then - if [ -n "$programList" ]; then - programList="$programList $i" - else - programList="$i" +if [[ -z "$emailClient" ]] || ! command -v "$emailClient" &> /dev/null; then + unset programList + for i in betterbird evolution thunderbird ; do + if command -v ${i/#-/} &> /dev/null ; then + if [ -n "$programList" ]; then + programList="$programList $i" + else + programList="$i" + fi fi + done + if [ "$programList" != "${programList// /}" ]; then + emailClient="$(menulist "Email client:" $programList)" + else + emailClient="${programList/#-/}" fi -done -if [ "$programList" != "${programList// /}" ]; then - emailClient="$(menulist "Email client:" $programList)" + export emailClient="$(command -v $emailClient)" else - emailClient="${programList/#-/}" + # Validate and export existing preference + export emailClient fi -export emailClient="$(command -v $emailClient)" # Web browser -unset programList -for i in brave chromium epiphany firefox google-chrome-stable google-chrome-unstable microsoft-edge-stable microsoft-edge-beta microsoft-edge-dev midori seamonkey vivaldi ; do - if command -v ${i/#-/} &> /dev/null ; then - if [ -n "$programList" ]; then - programList="$programList $i" - else - programList="$i" +if [[ -z "$webBrowser" ]] || ! command -v "$webBrowser" &> /dev/null; then + unset programList + for i in brave chromium epiphany firefox google-chrome-stable google-chrome-unstable microsoft-edge-stable microsoft-edge-beta microsoft-edge-dev midori seamonkey vivaldi ; do + if command -v ${i/#-/} &> /dev/null ; then + if [ -n "$programList" ]; then + programList="$programList $i" + else + programList="$i" + fi fi + done + if [ "$programList" != "${programList// /}" ]; then + webBrowser="$(menulist "Web browser:" $programList)" + else + webBrowser="${programList/#-/}" fi -done -if [ "$programList" != "${programList// /}" ]; then - webBrowser="$(menulist "Web browser:" $programList)" + export webBrowser="$(command -v $webBrowser)" else - webBrowser="${programList/#-/}" + # Validate and export existing preference + export webBrowser fi -export webBrowser="$(command -v $webBrowser)" # Text editor -unset programList -for i in emacs geany gedit kate kwrite l3afpad leafpad libreoffice mousepad pluma ; do -if hash ${i/#-/} &> /dev/null ; then -if [ -n "$programList" ]; then -programList="$programList $i" +if [[ -z "$textEditor" ]] || ! command -v "$textEditor" &> /dev/null; then + unset programList + for i in emacs geany gedit kate kwrite l3afpad leafpad libreoffice mousepad pluma ; do + if hash ${i/#-/} &> /dev/null ; then + if [ -n "$programList" ]; then + programList="$programList $i" + else + programList="$i" + fi + fi + done + if [ "$programList" != "${programList// /}" ]; then + textEditor="$(menulist "Text editor:" $programList)" + else + textEditor="${programList/#-/}" + fi + export textEditor="$(command -v $textEditor)" else -programList="$i" + # Validate and export existing preference + export textEditor fi -fi -done -if [ "$programList" != "${programList// /}" ]; then -textEditor="$(menulist "Text editor:" $programList)" -else -textEditor="${programList/#-/}" -fi -export textEditor="$(command -v $textEditor)" # File browser -# Configure file browser -unset programList -for i in caja nemo nautilus pcmanfm pcmanfm-qt thunar ; do - if hash ${i/#-/} &> /dev/null ; then - if [ -n "$programList" ]; then - programList="$programList $i" - else - programList="$i" +if [[ -z "$fileBrowser" ]] || ! command -v "$fileBrowser" &> /dev/null; then + unset programList + for i in caja nemo nautilus pcmanfm pcmanfm-qt thunar ; do + if hash ${i/#-/} &> /dev/null ; then + if [ -n "$programList" ]; then + programList="$programList $i" + else + programList="$i" + fi fi + done + if [ "$programList" != "${programList// /}" ]; then + fileBrowser="$(menulist "File browser:" $programList)" + else + fileBrowser="${programList/#-/}" fi -done -if [ "$programList" != "${programList// /}" ]; then - fileBrowser="$(menulist "File browser:" $programList)" + export fileBrowser="$(command -v $fileBrowser)" else - fileBrowser="${programList/#-/}" + # Validate and export existing preference + export fileBrowser fi -export fileBrowser="$(command -v $fileBrowser)" # IRC client -unset programList -for i in albikirc Albikirc access-irc ; do - if command -v ${i/#-/} &> /dev/null ; then - if [ -n "$programList" ]; then - programList="$programList $i" +if [[ -z "$ircClient" ]] || ! command -v "$ircClient" &> /dev/null; then + unset programList + for i in albikirc Albikirc access-irc ; do + if command -v ${i/#-/} &> /dev/null ; then + if [ -n "$programList" ]; then + programList="$programList $i" + else + programList="$i" + fi + fi + done + if [ "$programList" != "${programList// /}" ]; then + ircClient="$(menulist "IRC client:" $programList)" + else + ircClient="${programList/#-/}" + fi + export ircClient="$(command -v $ircClient)" +else + # Validate and export existing preference + export ircClient +fi +# Auto mount removable media +if [[ -z "$udiskie" ]]; then + udiskie=1 + if command -v udiskie &> /dev/null ; then + if yesno "Would you like removable drives to automatically mount when plugged in?"; then + export udiskie=0 else - programList="$i" + export udiskie=1 fi fi -done -if [ "$programList" != "${programList// /}" ]; then - ircClient="$(menulist "IRC client:" $programList)" -else - ircClient="${programList/#-/}" -fi -export ircClient="$(command -v $ircClient)" -# Auto mount removable media -udiskie=1 -if command -v udiskie &> /dev/null ; then - export udiskie=$(yesno "Would you like removable drives to automatically mount when plugged in?") fi # Auto start with dex -dex=1 -if command -v dex &> /dev/null ; then - export dex=$(yesno "Would you like to autostart applications with dex?") +if [[ -z "$dex" ]]; then + dex=1 + if command -v dex &> /dev/null ; then + if yesno "Would you like to autostart applications with dex?"; then + export dex=0 + else + export dex=1 + fi + fi fi if [[ $dex -eq 0 ]]; then dex -t "${XDG_CONFIG_HOME:-${HOME}/.config}/autostart" -c $(command -v $screenReader) fi -if command -v acpi &> /dev/null ; then - batteryAlert=1 - batteryAlert=$(yesno "Do you want low battery notifications?") +if [[ -z "$batteryAlert" ]]; then + if command -v acpi &> /dev/null ; then + if yesno "Do you want low battery notifications?"; then + batteryAlert=0 + else + batteryAlert=1 + fi + fi +fi +if [[ -z "$brlapi" ]]; then + if yesno "Do you want to use a braille display with ${screenReader##*/}?"; then + brlapi=0 + else + brlapi=1 + fi +fi +if [[ -z "$sounds" ]]; then + if yesno "Do you want window event sounds?"; then + sounds=0 + else + sounds=1 + fi fi -brlapi=1 -brlapi=$(yesno "Do you want to use a braille display with ${screenReader##*/}?") -sounds=1 -sounds=$(yesno "Do you want window event sounds?") # Custom applications for ratpoison mode -addCustomApplication +if [[ ${#customApps[@]} -eq 0 ]]; then + addCustomApplication +fi + +# Save configuration if requested (only on first run) +if [[ $configLoaded -eq 0 ]]; then + save_config +fi if [[ -d "${i3Path}" ]]; then yesno "This will replace your existing configuration at ${i3Path}. Do you want to continue?" || exit 0 @@ -823,7 +975,7 @@ echo "bindsym ${mod}+Shift+F5 exec spd-say -C" >> ${i3Path}/config # Multiple keyboard layouts if requested. if [[ ${#kbd[@]} -gt 1 ]]; then - echo "bindsym Mod4+space exec ${i3Path}/scripts/keyboard.sh cycle ${kbd[@]}" >> ${i3Path}/config + echo "bindsym Mod4+space exec ${i3Path}/scripts/keyboard.sh cycle ${kbd[*]}" >> ${i3Path}/config fi # Create panel mode @@ -887,7 +1039,7 @@ mode "ratpoison" { # Ratpoison mode keybindings help bound to F1 bindsym F1 exec ${i3Path}/scripts/i38-help-rp.sh, mode "default" # Terminal emulator bound to c -bindsym c exec $sensibleTerminal, mode "default" +bindsym c exec ${i3Path}/scripts/i3-sensible-terminal.sh, mode "default" # Text editor bound to e bindsym e exec $textEditor, mode "default" $(if [[ ${#fileBrowser} -gt 3 ]]; then