}: ${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
}
write_xinitrc()
{
if [[ -f "$HOME/.xinitrc" ]]; then
yesno "This will overwrite your existing $HOME/.xinitrc file. Do you want to continue?" || exit 0
fi
if yesno "Do you want to launch i3 without an isolated D-Bus session? Selecting No will keep the dbus-session-launch wrapper."; then
sessionCommand="exec -- i3"
else
sessionCommand="exec dbus-session-launch -- i3"
fi
cat << 'EOF' > ~/.xinitrc
#!/bin/sh
#
# ~/.xinitrc
#
# Executed by startx (run your window manager from here)
[ -f "$HOME/.Xresources" ] && xrdb -merge -I"$HOME" "$HOME/.Xresources"
if [ -d /etc/X11/xinit/xinitrc.d ]; then
for f in /etc/X11/xinit/xinitrc.d/?*.sh; do
[ -r "$f" ] && . "$f"
done
unset f
fi
[ -f /etc/xprofile ] && . /etc/xprofile
[ -f "$HOME/.xprofile" ] && . "$HOME/.xprofile"
EOF
echo "$sessionCommand" >> ~/.xinitrc
chmod +x ~/.xinitrc
}
write_xprofile() {
local xprofilePath="$HOME/.xprofile"
if [[ -f "$xprofilePath" ]]; then
if ! yesno "Would you like to add accessibility variables to your $xprofilePath? Without these, accessibility will be limited or may not work at all. Do you want to continue?"; then
exit 0
fi
fi
I38_XPROFILE_PATH="$xprofilePath" python3 - << 'PY'
import os
from pathlib import Path
xprofile_path = Path(os.environ["I38_XPROFILE_PATH"])
start_marker = "# I38 accessibility variables start"
end_marker = "# I38 accessibility variables end"
block = """\
{start}
export XDG_CURRENT_DESKTOP=i3
export XDG_SESSION_DESKTOP=i3
export ACCESSIBILITY_ENABLED=1
export CHROME_FLAGS="--force-renderer-accessibility"
export GTK_MODULES=gail:atk-bridge
export GNOME_ACCESSIBILITY=1
export QT_ACCESSIBILITY=1
export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1
export SAL_USE_VCLPLUGIN=gtk3
{end}
""".format(start=start_marker, end=end_marker)
content = ""
if xprofile_path.exists():
content = xprofile_path.read_text(encoding="utf-8")
lines = content.splitlines()
output = []
in_i38_block = False
for line in lines:
if line == start_marker:
in_i38_block = True
continue
if line == end_marker:
in_i38_block = False
continue
if not in_i38_block:
output.append(line)
new_content = "\n".join(output).rstrip()
if new_content:
new_content += "\n\n"
new_content += block
xprofile_path.write_text(new_content, encoding="utf-8")
PY
}
apply_screenlock_pin() {
local pinFile="${i3Path}/.screenpin"
local pinValue="$screenlockPinHash"
if [[ "$screenlockMethod" != "pin" ]] || [[ -z "$pinValue" ]]; then
rm -f "$pinFile"
return 0
fi
printf "%s\n" "$pinValue" > "$pinFile"
chmod 600 "$pinFile"
}
copy_scripts() {
mkdir -p "${i3Path}/scripts"
rsync -rv \
--exclude='__pycache__/' \
--exclude='*.pyc' \
--exclude='*.pyo' \
--exclude='*.pyd' \
scripts/ "${i3Path}/scripts/"
find "${i3Path}/scripts" -type f \
\( -name '*.pyc' -o -name '*.pyo' -o -name '*.pyd' \) \
-delete
find "${i3Path}/scripts" -depth -type d -name '__pycache__' \
-exec rm -rf -- {} +
}
update_scripts() {
local existingPinHash=""
local pinFile="${i3Path}/.screenpin"
if [[ -f "$pinFile" ]]; then
read -r existingPinHash < "$pinFile"
fi
if [[ -z "$existingPinHash" ]] && [[ -f "$configFile" ]]; then
# shellcheck source=/dev/null
source "$configFile"
existingPinHash="$screenlockPinHash"
fi
copy_scripts | dialog --backtitle "I38" --progressbox "Updating scripts..." -1 -1
write_i38_version
write_desktop_shortcuts_template
if [[ -n "$existingPinHash" ]]; then
screenlockPinHash="$existingPinHash"
screenlockMethod="${screenlockMethod:-pin}"
apply_screenlock_pin
fi
exit 0
}
write_desktop_shortcuts_template() {
local shortcutsFile="${i3Path}/desktop_shortcuts"
if [[ -f "$shortcutsFile" ]]; then
return 0
fi
cat << 'EOF' > "$shortcutsFile"
# I38 desktop quick-launch shortcuts
# Format: Pretty Name|/path/to/application --flags-if-any|optional workspace
# The workspace field is optional. Leave it empty to launch on the current workspace.
# Lines starting with # and blank lines are ignored.
#
# Examples:
# Firefox|/usr/bin/firefox|9
# Brave|/usr/bin/brave|
EOF
}
write_i38_xresources() {
local xresourcesFile="${i3Path}/I38.Xresources"
cat << 'EOF' > "$xresourcesFile"
! Visual settings generated by I38
Xcursor.size: 48
EOF
}
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 0
fi
local waytrayConfigDir="${XDG_CONFIG_HOME:-$HOME/.config}/waytray"
local waytrayConfig="${waytrayConfigDir}/config.toml"
mkdir -p "${waytrayConfigDir}"
# Ask user if config already exists (unless preference already saved)
if [[ -f "${waytrayConfig}" ]]; then
if [[ -z "$waytrayUseI38Config" ]]; 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
waytrayUseI38Config=0
else
waytrayUseI38Config=1
fi
fi
if [[ $waytrayUseI38Config -ne 0 ]]; 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
}
i38_current_commit() {
if ! command -v git &> /dev/null; then
return 1
fi
git -C "$PWD" rev-parse --verify HEAD 2> /dev/null
}
write_i38_version() {
mkdir -p "${i3Path}"
if i38Commit="$(i38_current_commit)"; then
printf '%s\n' "$i38Commit" > "$i38VersionFile"
else
rm -f "$i38VersionFile"
fi
}
# Array of command line arguments
declare -A command=(
[h]="This help screen."
[d]="Generate the default I38 configuration without prompting."
[D]="Generate the default I38 configuration plus ~/.xinitrc and ~/.xprofile without prompting."
[u]="Copy over the latest version of scripts."
[x]="Generate ~/.xinitrc and ~/.xprofile."
[X]="Generate ~/.xprofile only."
)
# Convert the keys of the associative array to a format usable by getopts
args="${!command[*]}"
args="${args//[[:space:]]/}"
while getopts "${args}" i ; do
case "$i" in
h) help;;
d) defaultConfig=1;;
D) defaultConfig=1; defaultGenerateX=1;;
u) update_scripts;;
x) write_xinitrc; write_xprofile; exit 0;;
X) write_xprofile; exit 0;;
esac
done
if [[ $defaultConfig -eq 1 ]]; then
set_default_configuration
fi
if [[ $defaultGenerateX -eq 1 ]]; then
write_xinitrc
write_xprofile
fi
# Load saved configuration if available
configLoaded=0
configChanged=0
if [[ $defaultConfig -eq 0 ]] && 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.
# Configuration questions
# Ratpoison mode is enabled by default
export i3Mode=0
# Prevent setting ratpoison mode key to the same as default mode key
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 [[ $defaultConfig -eq 0 && ${#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
if [[ -z "$volumeJump" ]]; then
volumeJump=$(rangebox "How much should pressing the volume keys change the volume?" 1 15 5)
fi
if [[ -z "$i3FontSize" ]] || [[ ! "$i3FontSize" =~ ^[0-9]+$ ]]; then
configChanged=1
i3FontSize=$(rangebox "How large should I38 window title text be?" 8 24 12)
if [[ ! "$i3FontSize" =~ ^[0-9]+$ ]]; then
i3FontSize=12
fi
fi
# Screen Reader
if [[ $defaultConfig -eq 0 ]] && { [[ -z "$screenReader" ]] || { [[ "$screenReader" != "none" ]] && ! command -v "$screenReader" &> /dev/null; }; }; then
programList=()
for i in cthulhu orca ; do
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
programList+=("None")
if [[ ${#programList[@]} -gt 1 ]]; then
screenReader="$(menulist ":Screen Reader" "${programList[@]}")"
else
screenReader="${programList[0]#-}"
fi
if [[ "$screenReader" == "None" ]]; then
screenReader="none"
else
screenReader="$(command -v "$screenReader")"
fi
export screenReader
else
# Validate and export existing preference
export screenReader
fi
# Email client
if [[ $defaultConfig -eq 0 ]] && { [[ -z "$emailClient" ]] || ! command -v "$emailClient" &> /dev/null; }; then
programList=()
for i in betterbird evolution thunderbird ; do
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
if [[ ${#programList[@]} -gt 1 ]]; then
emailClient="$(menulist "Email client:" "${programList[@]}")"
else
emailClient="${programList[0]#-}"
fi
emailClient="$(command -v "$emailClient")"
export emailClient
else
# Validate and export existing preference
export emailClient
fi
# Web browser
if [[ $defaultConfig -eq 0 ]] && { [[ -z "$webBrowser" ]] || ! command -v "$webBrowser" &> /dev/null; }; then
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
programList+=("$i")
fi
done
if [[ ${#programList[@]} -gt 1 ]]; then
webBrowser="$(menulist "Web browser:" "${programList[@]}")"
else
webBrowser="${programList[0]#-}"
fi
webBrowser="$(command -v "$webBrowser")"
export webBrowser
else
# Validate and export existing preference
export webBrowser
fi
# Text editor
if [[ $defaultConfig -eq 0 ]] && { [[ -z "$textEditor" ]] || ! command -v "$textEditor" &> /dev/null; }; then
programList=()
for i in emacs geany gedit kate kwrite l3afpad leafpad libreoffice mousepad pluma ; do
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
if [[ ${#programList[@]} -gt 1 ]]; then
textEditor="$(menulist "Text editor:" "${programList[@]}")"
else
textEditor="${programList[0]#-}"
fi
textEditor="$(command -v "$textEditor")"
export textEditor
else
# Validate and export existing preference
export textEditor
fi
# File browser
if [[ $defaultConfig -eq 0 ]] && { [[ -z "$fileBrowser" ]] || ! command -v "$fileBrowser" &> /dev/null; }; then
programList=()
for i in caja nemo nautilus pcmanfm pcmanfm-qt thunar ; do
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
if [[ ${#programList[@]} -gt 1 ]]; then
fileBrowser="$(menulist "File browser:" "${programList[@]}")"
else
fileBrowser="${programList[0]#-}"
fi
fileBrowser="$(command -v "$fileBrowser")"
export fileBrowser
else
# Validate and export existing preference
export fileBrowser
fi
# IRC client
if [[ $defaultConfig -eq 0 ]] && { [[ -z "$ircClient" ]] || ! command -v "$ircClient" &> /dev/null; }; then
programList=()
for i in albikirc Albikirc access-irc ; do
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
if [[ ${#programList[@]} -gt 1 ]]; then
ircClient="$(menulist "IRC client:" "${programList[@]}")"
else
ircClient="${programList[0]#-}"
fi
ircClient="$(command -v "$ircClient")"
export ircClient
else
# Validate and export existing preference
export ircClient
fi
# Terminal emulator
if [[ $defaultConfig -eq 0 ]] && { [[ -z "$terminalEmulator" ]] || { [[ ! -x "$terminalEmulator" ]] && ! command -v "$terminalEmulator" &> /dev/null; }; }; then
configChanged=1
if [[ ${#availableTerminals[@]} -gt 1 ]]; then
terminalEmulator="$(menulist "Terminal emulator:" "${availableTerminals[@]}")"
else
terminalEmulator="${availableTerminals[0]}"
fi
terminalEmulator="$(command -v "$terminalEmulator")"
export terminalEmulator
else
# Validate and export existing preference
export terminalEmulator
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
export udiskie=1
fi
fi
fi
# Auto start 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 ]] && [[ "$screenReader" != "none" ]] && [[ -n "$screenReader" ]]; then
dex -t "${XDG_CONFIG_HOME:-${HOME}/.config}/autostart" -c "$screenReader"
fi
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 [[ "$screenReader" == "none" ]] || [[ -z "$screenReader" ]]; then
brlapi=1
elif [[ -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
if [[ -z "$usbNotifications" ]]; then
configChanged=1
if yesno "Do you want USB connection sounds and spoken device names? Requires python-pyudev, sox for sounds, and speech-dispatcher for announcements."; then
usbNotifications=0
else
usbNotifications=1
fi
fi
if [[ $defaultConfig -eq 0 && -z "$screenlockPinHash" ]]; then
screenlockPinFile="${i3Path}/.screenpin"
if [[ -f "$screenlockPinFile" ]]; then
read -r screenlockPinHash < "$screenlockPinFile"
fi
fi
screenlockNeedsSelection=0
if [[ -z "${screenlockMethod+x}" ]]; then
screenlockNeedsSelection=1
fi
if [[ -z "$screenlockMethod" || "$screenlockMethod" == "none" ]]; then
if [[ $defaultConfig -eq 0 && -n "$screenlockPinHash" ]]; then
screenlockMethod="pin"
else
screenlockMethod="none"
fi
fi
if [[ "$screenlockMethod" != "pin" && "$screenlockMethod" != "i38lock" && "$screenlockMethod" != "none" ]]; then
screenlockMethod="none"
fi
if [[ "$screenlockMethod" == "i38lock" ]] && ! i38lock_is_supported; then
dialog --title "I38" --msgbox "I38Lock is not available with the current screen reader setup. I38Lock requires the i38lock command and either Cthulhu or Orca 50 or newer. Please choose another screen lock method." -1 -1
screenlockMethod="none"
screenlockNeedsSelection=1
configChanged=1
fi
if [[ $screenlockNeedsSelection -eq 1 ]]; then
selectedScreenlockMethod="$(select_screenlock_method "$screenlockMethod")"
dialogResult=$?
if [[ $dialogResult -eq 0 ]]; then
screenlockMethod="$selectedScreenlockMethod"
configChanged=1
fi
fi
if [[ "$screenlockMethod" == "pin" ]]; then
if [[ -z "$screenlockPinHash" ]]; then
while : ; do
screenlockPin="$(dialog --title "I38" --clear --passwordbox "Enter a 4-digit PIN to enable screen lock." -1 -1 --stdout)"
dialogResult=$?
if [[ $dialogResult -ne 0 ]]; then
screenlockPinHash=""
screenlockMethod="none"
break
fi
if [[ ! "$screenlockPin" =~ ^[0-9]{4}$ ]]; then
dialog --title "I38" --msgbox "PIN must be exactly 4 digits." -1 -1
continue
fi
screenlockPinConfirm="$(dialog --title "I38" --clear --passwordbox "Re-enter the 4-digit PIN to confirm." -1 -1 --stdout)"
dialogResult=$?
if [[ $dialogResult -ne 0 ]]; then
screenlockPinHash=""
break
fi
if [[ "$screenlockPin" != "$screenlockPinConfirm" ]]; then
dialog --title "I38" --msgbox "PINs do not match. Please try again." -1 -1
continue
fi
screenlockPinHash="$(printf "%s" "$screenlockPin" | sha512sum | awk '{print $1}')"
unset screenlockPin screenlockPinConfirm
break
done
fi
else
screenlockPinHash=""
fi
if screenlock_is_configured; then
if [[ -z "${screenlockOnStartup+x}" ]]; then
configChanged=1
if yesno "Lock the screen automatically when I38 starts? If your screen reader or display setup fails, you may need to unlock without feedback."; then
screenlockOnStartup=0
else
screenlockOnStartup=1
fi
fi
if command -v xprintidle &> /dev/null; then
if [[ -z "${screenlockAutolockSeconds+x}" ]] || [[ ! "$screenlockAutolockSeconds" =~ ^[0-9]+$ ]]; then
configChanged=1
screenlockAutolockSeconds="$(select_screenlock_autolock_seconds)"
fi
elif [[ -z "${screenlockAutolockSeconds+x}" ]] || [[ ! "$screenlockAutolockSeconds" =~ ^[0-9]+$ ]]; then
screenlockAutolockSeconds=0
fi
else
screenlockAutolockSeconds=0
screenlockOnStartup=1
fi
# Personal mode
personalModeExists=1
if personal_mode_exists; then
personalModeExists=0
personalModeEnabled=0
fi
if [[ $personalModeExists -ne 0 ]]; then
personalModeEnabled=0
if [[ -z "$personalModeKey" ]] || [[ "$personalModeKey" == "$escapeKey" ]]; then
personalModeKey="$(select_personal_mode_key)"
fi
if [[ -z "$personalModeKey" ]]; then
personalModeEnabled=1
fi
fi
# Save configuration if requested on first run or after filling missing saved values.
if [[ $configLoaded -eq 0 || $configChanged -eq 1 ]]; then
save_config
fi
terminalExec="$(terminal_command "$terminalEmulator")"
if [[ -d "${i3Path}" ]]; then
yesno "This will replace your existing configuration at ${i3Path}. Do you want to continue?" || exit 0
fi
# Configure waytray if available
write_waytray_config
# Create the i3 configuration directory.
mkdir -p "${i3Path}"
write_i38_version
write_desktop_shortcuts_template
write_i38_xresources
# Move scripts into place
if [[ $defaultConfig -eq 1 ]]; then
copy_scripts > /dev/null
else
copy_scripts | dialog --backtitle "I38" --progressbox "Moving scripts into place and writing config..." -1 -1
fi
apply_screenlock_pin
update_personal_customizations
cat << EOF > "${i3Path}/config"
# Generated by I38 (${0##*/}) https://git.stormux.org/storm/I38
# $(date '+%A, %B %d, %Y at %I:%M%p')
EOF
cat << EOF >> "${i3Path}/config"
# i3 config file (v4)
#
# Please see https://i3wm.org/docs/userguide.html for a complete reference!
#
# This config file uses keycodes (bindsym) and was written for the QWERTY
# layout.
# set mod key
set \$mod $mod
# set workspace layout to tabbed so apps use most of the screen
workspace_layout tabbed
# set the mouse so it is trapped in focused window
# this fixes some issues in some games that require focus and pause when focus is moved via mouse accidentally
focus_follows_mouse no
# Font for window titles and tab labels.
font pango:monospace ${i3FontSize}
# Window rules
# Fix Wine window issues - improves stability and prevents lockups
for_window [class="Wine"] floating enable
# I38 help - Open comprehensive HTML guide
bindsym \$mod+Shift+F1 exec $webBrowser ${i3Path}/I38.html
# Run dialog
bindsym \$mod+F2 exec ${i3Path}/scripts/run_dialog.py
# Bookmarks dialog
bindsym \$mod+Control+b exec ${i3Path}/scripts/bookmarks.sh
# Clipboard manager
bindsym \$mod+Control+c exec ${i3Path}/scripts/i38-clipboard.py --show
# Move the mouse pointer to the center of the focused window
bindsym \$mod+Control+m exec --no-startup-id ${i3Path}/scripts/find_mouse.sh
$(if command -v xzoom &> /dev/null; then
echo "# Magnify part of the screen with xzoom"
echo "bindsym \$mod+Control+z exec --no-startup-id xzoom"
echo
fi)
# gtk bar
bindsym \$mod+Control+Delete exec --no-startup-id sgtk-bar
# Use pactl to adjust volume in PulseAudio.
# Increase system volume
bindsym \$mod+XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +${volumeJump}% & play -qnG synth 0.03 sin 440
# Decrease system volume
bindsym \$mod+XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -${volumeJump}% & play -qnG synth 0.03 sin 440
# Mute/unmute system volume
bindsym \$mod+XF86AudioMute exec --no-startup-id ${i3Path}/scripts/mute-unmute.sh
# Music player controls
# Increase music volume
bindsym XF86AudioRaiseVolume exec --no-startup-id ${i3Path}/scripts/music_controler.sh incvol $volumeJump
# Decrease music volume
bindsym XF86AudioLowerVolume exec --no-startup-id ${i3Path}/scripts/music_controler.sh decvol $volumeJump
# Previous track
bindsym XF86AudioPrev exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh prev
# Pause music playback
bindsym XF86AudioMute exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh pause
# Play music
bindsym XF86AudioPlay exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh play
# Get music player information
bindsym \$mod+XF86AudioPlay exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh info
# Stop music playback
bindsym XF86AudioStop exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh stop
# Next track
bindsym XF86AudioNext exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh next
# start a terminal
bindsym \$mod+Return exec $terminalExec
# Close focused window
bindsym \$mod+F4 kill
# Applications menu
bindsym \$mod+F1 exec --no-startup-id "${i3Path}/scripts/menu.py"
# Desktop dialog
bindsym \$mod+Control+d exec --no-startup-id ${i3Path}/scripts/desktop.py
# change focus
# Focus previous window (alt+shift+tab)
bindsym Mod1+Shift+Tab focus left
# Focus next window (alt+tab)
bindsym Mod1+Tab focus right
# Toggle fullscreen for the focused window
bindsym \$mod+BackSpace fullscreen toggle
# Move the currently focused window to the scratchpad
bindsym \$mod+Shift+minus move scratchpad
# Bind the focused window to the scratchpad so it always opens there
bindsym \$mod+Shift+equal exec --no-startup-id ${i3Path}/scripts/bind_to_scratchpad.sh
# Show or cycle scratchpad windows
bindsym \$mod+minus scratchpad show
# Define names for default workspaces for which we configure key bindings later on.
# We use variables to avoid repeating the names in multiple places.
set \$ws1 "1"
set \$ws2 "2"
set \$ws3 "3"
set \$ws4 "4"
set \$ws5 "5"
set \$ws6 "6"
set \$ws7 "7"
set \$ws8 "8"
set \$ws9 "9"
set \$ws10 "10"
# switch to workspace
# Switch to workspace 1
bindsym Control+F1 workspace number \$ws1, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# Switch to workspace 2
bindsym Control+F2 workspace number \$ws2, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# Switch to workspace 3
bindsym Control+F3 workspace number \$ws3, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# Switch to workspace 4
bindsym Control+F4 workspace number \$ws4, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# Switch to workspace 5
bindsym Control+F5 workspace number \$ws5, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# Switch to workspace 6
bindsym Control+F6 workspace number \$ws6, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# Switch to workspace 7
bindsym Control+F7 workspace number \$ws7, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# Switch to workspace 8
bindsym Control+F8 workspace number \$ws8, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# Switch to workspace 9
bindsym Control+F9 workspace number \$ws9, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# Switch to workspace 10
bindsym Control+F10 workspace number \$ws10, exec --no-startup-id ${i3Path}/scripts/announce_workspace.sh
# move focused container to workspace
# Move window to workspace 1
bindsym Control+Shift+F1 move container to workspace number \$ws1, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 1"
# Move window to workspace 2
bindsym Control+Shift+F2 move container to workspace number \$ws2, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 2"
# Move window to workspace 3
bindsym Control+Shift+F3 move container to workspace number \$ws3, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 3"
# Move window to workspace 4
bindsym Control+Shift+F4 move container to workspace number \$ws4, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 4"
# Move window to workspace 5
bindsym Control+Shift+F5 move container to workspace number \$ws5, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 5"
# Move window to workspace 6
bindsym Control+Shift+F6 move container to workspace number \$ws6, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 6"
# Move window to workspace 7
bindsym Control+Shift+F7 move container to workspace number \$ws7, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 7"
# Move window to workspace 8
bindsym Control+Shift+F8 move container to workspace number \$ws8, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 8"
# Move window to workspace 9
bindsym Control+Shift+F9 move container to workspace number \$ws9, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 9"
# Move window to workspace 10
bindsym Control+Shift+F10 move container to workspace number \$ws10, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 10"
# A mode that will pass all keys except $mod+shift+backspace to the current application.
# Enter bypass mode
bindsym $mod+shift+BackSpace mode "bypass"
mode "bypass" {
# Exit bypass mode.
bindsym $mod+Shift+BackSpace mode "default"
}
EOF
if [[ "$screenlockMethod" == "pin" ]]; then
cat << EOF >> "${i3Path}/config"
# Screen lock mode (managed by screenlock.sh)
mode "screenlock" {
bindsym Escape nop
bindsym Control+g nop
}
EOF
fi
# Perform OCR on screen
echo "bindsym ${mod}+F5 exec ${i3Path}/scripts/ocr.py" >> "${i3Path}/config"
# Interrupt speech output
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"
fi
# Create panel mode
cat << EOF >> "${i3Path}/config"
# Enter Panel Mode
bindsym Control+Mod1+Tab mode "panel"
mode "panel" {
# Panel mode keybindings help bound to F1
bindsym F1 exec ${i3Path}/scripts/i38-help-panel.sh, mode "default"
# Weather information bound to w
bindsym w exec --no-startup-id ${i3Path}/scripts/weather.sh, mode "default"
# 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\""
fi)
# Simple notes system bound to n
bindsym n exec --no-startup-id ${i3Path}/scripts/notes.py, mode "default"
# Network connections bound to Shift+n
bindsym Shift+n exec --no-startup-id python3 ${i3Path}/scripts/network.py, mode "default"
# Password manager bound to m
bindsym m exec --no-startup-id ${i3Path}/scripts/passmanager.py, mode "default"
$(if command -v blueman-manager &> /dev/null ; then
echo "# Bluetooth bound to b"
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"
# Power options bound to p
bindsym p exec --no-startup-id ${i3Path}/scripts/power.sh, mode "default"
$(if screenlock_is_configured; then
if [[ "$screenlockMethod" == "i38lock" ]]; then
echo " # Screen lock (I38Lock secure PAM locker)"
else
echo " # Screen lock (I38 PIN privacy screen)"
fi
echo " bindsym Control+\$mod+l exec --no-startup-id $(screenlock_command), mode \"default\""
echo " "
fi)
# Exit panel mode without any action
bindsym Escape mode "default"
bindsym Control+g mode "default"
}
EOF
# Create ratpoison mode if requested.
if [[ -n "${escapeKey}" ]]; then
cat << EOF >> "${i3Path}/config"
# Enter ratpoison mode
bindsym $escapeKey mode "ratpoison"
mode "ratpoison" {
# Ratpoison mode keybindings help bound to F1
bindsym F1 exec ${i3Path}/scripts/i38-help-rp.sh, mode "default"
# Create a desktop shortcut bound to Control+Alt+d
bindsym Control+Mod1+d exec --no-startup-id python3 ${i3Path}/scripts/shortcut_creator.py --file-manager '${fileBrowser}', mode "default"
# Terminal emulator bound to c
bindsym c exec $terminalExec, mode "default"
# Text editor bound to e
bindsym e exec $textEditor, mode "default"
$(if [[ ${#fileBrowser} -gt 3 ]]; then
echo "# File browser bound to f"
echo "bindsym f exec $fileBrowser, mode \"default\""
fi)
# Email client bound to \$mod+e
bindsym \$mod+e exec $emailClient, mode "default"
# Web browser bound to w
bindsym w exec $webBrowser, mode "default"
$(if command -v steam &> /dev/null ; then
echo "# Steam bound to s"
echo "bindsym s exec --no-startup-id command steam -bigpicture, mode \"default\""
fi)
# Kill window bound to k
bindsym k kill, mode "default"
$(if command -v mumble &> /dev/null ; then
echo "# Mumble bound to m"
echo "bindsym m exec $(command -v mumble), mode \"default\""
fi)
$(if command -v ocrdesktop &> /dev/null ; then
echo "# OCR desktop bound to print screen alternative \$mod+r"
echo "bindsym Print exec $(command -v ocrdesktop) -b, mode \"default\""
echo "bindsym \$mod+r exec $(command -v ocrdesktop) -b, mode \"default\""
fi)
$(if command -v pidgin &> /dev/null ; then
echo "# p bound to pidgin"
echo "bindsym p exec $(command -v pidgin), mode \"default\""
fi)
$(if [[ ${#ircClient} -gt 3 ]]; then
echo "# IRC client bound to i"
echo "bindsym i exec $ircClient, mode \"default\""
fi)
$(if command -v xrandr &> /dev/null ; then
echo "# alt+s bound to brightness control"
echo "bindsym \$mod+s exec --no-startup-id ${i3Path}/scripts/screen_controller.sh, mode \"default\""
fi)
#Keyboard based volume Controls with pulseaudio
bindsym Mod1+Shift+0 exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +${volumeJump}% & play -qnG synth 0.03 sin 440
bindsym Mod1+Shift+9 exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -${volumeJump}% & play -qnG synth 0.03 sin 440
# Music player controls
# Requires playerctl.
bindsym Mod1+Shift+equal exec --no-startup-id ${i3Path}/scripts/music_controler.sh incvol $volumeJump, mode "default"
bindsym Mod1+Shift+minus exec --no-startup-id ${i3Path}/scripts/music_controler.sh decvol $volumeJump, mode "default"
bindsym Mod1+Shift+z exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh prev, mode "default"
bindsym Mod1+Shift+c exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh pause, mode "default"
bindsym Mod1+Shift+x exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh play, mode "default"
bindsym Mod1+Shift+v exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh stop, mode "default"
bindsym Mod1+Shift+b exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh next, mode "default"
bindsym Mod1+Shift+u exec --no-startup-id play -qV0 "| sox -np synth 0.03 sin 2000 pad 0 .02" "| sox -np synth 0.03 sin 2000" norm 1.0 vol 0.4 & ${i3Path}/scripts/music_controler.sh info, mode "default"
#Check battery status
bindsym Mod1+b exec --no-startup-id ${i3Path}/scripts/battery_status.sh, mode "default"
#Check controller battery status
bindsym g exec ${i3Path}/scripts/game_controller.sh -s, mode "default"
# AI Assistant bound to a
bindsym a exec ${i3Path}/scripts/ai.py, mode "default"
# Toggle desktop audio recording and slideshow video conversion
bindsym Mod1+Shift+r exec --no-startup-id ${i3Path}/scripts/slideshow_record_toggle.sh, mode "default"
# Get a list of windows in the current workspace
bindsym apostrophe exec --no-startup-id ${i3Path}/scripts/window_list.sh, mode "default"
$(if command -v cthulhu &> /dev/null; then
echo "# Restart Cthulhu"
echo "bindsym Shift+c exec $(command -v cthulhu) --replace, mode \"default\""
fi)
$(if command -v orca &> /dev/null; then
echo "# Restart Orca"
echo "bindsym Shift+o exec $(command -v orca) --replace, mode \"default\""
fi)
$(if [[ "$screenReader" != "none" ]] && [[ -n "$screenReader" ]]; then
echo "# Toggle screen reader"
echo "bindsym Shift+t exec ${i3Path}/scripts/toggle_screenreader.sh, mode \"default\""
fi)
# reload the configuration file
bindsym Control+semicolon exec bash -c '$i3msg -t run_command reload && spd-say -P important -Cw "I38 Configuration reloaded."', mode "default"
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
bindsym Control+Shift+semicolon exec $i3msg -t run_command restart && spd-say -P important -Cw "I3 restarted.", mode "default"
# Run dialog with exclamation
bindsym Shift+exclam exec ${i3Path}/scripts/run_dialog.py, mode "default"
# exit i3 (logs you out of your X session)
bindsym \$mod+q exec bash -c 'yad --image "dialog-question" --title "I38" --button=yes:0 --button=no:1 --text "You pressed the exit shortcut. Do you really want to exit i3? This will end your X session." && $i3msg -t run_command exit'
bindsym Control+\$mod+q exec bash -c 'yad --image "dialog-question" --title "I38" --button=yes:0 --button=no:1 --text "You pressed the exit shortcut. Do you really want to exit i3? This will end your X session." && $i3msg -t run_command exit'
# Exit ratpoison mode without any action escape or Control+g
bindsym Escape mode "default"
bindsym Control+g mode "default"
}
EOF
fi
cat << EOF >> "${i3Path}/config"
# Auto start section
$(if [[ $sounds -eq 0 ]]; then
echo "exec_always --no-startup-id ${i3Path}/scripts/sound.py"
fi
if [[ ${usbNotifications:-1} -eq 0 ]]; then
echo "exec_always --no-startup-id python3 \"${i3Path}/scripts/usb_notifications.py\""
fi
if command -v xrdb &> /dev/null; then
echo "exec --no-startup-id xrdb -merge ${i3Path}/I38.Xresources"
fi
# Steam game focus handler - focuses games when they open behind Big Picture
if command -v steam &> /dev/null; then
echo "exec --no-startup-id ${i3Path}/scripts/steam_games.py"
fi
# i3 watchdog - monitors i3 responsiveness and auto-recovers from freezes
echo "exec_always --no-startup-id ${i3Path}/scripts/i3_watchdog.sh"
if [[ $brlapi -eq 0 ]]; then
echo 'exec --no-startup-id xbrlapi --quiet'
fi
if [[ $udiskie -eq 0 ]]; then
echo 'exec --no-startup-id udiskie'
fi
# Notification daemon
if [[ -x "/usr/lib/xfce4/notifyd/xfce4-notifyd" ]]; then
echo 'exec_always --no-startup-id /usr/lib/xfce4/notifyd/xfce4-notifyd'
elif [[ -x "/usr/lib/notification-daemon-1.0/notification-daemon" ]]; then
echo 'exec_always --no-startup-id /usr/lib/notification-daemon-1.0/notification-daemon -r'
elif [[ -x "/usr/libexec/notification-daemon" ]]; then
# Work around for weird Void Linux stuff
echo 'exec_always --no-startup-id /usr/libexec/notification-daemon -r'
fi
if command -v remind &> /dev/null && command -v notify-send &> /dev/null ; then
echo "exec_always --no-startup-id ${i3Path}/scripts/launch_remind.sh"
touch ~/.reminders
fi
if [[ $batteryAlert -eq 0 ]]; then
echo "exec_always --no-startup-id ${i3Path}/scripts/battery_alert.sh"
fi
if screenlock_is_configured && [[ "${screenlockAutolockSeconds:-0}" -gt 0 ]] && command -v xprintidle &> /dev/null; then
if [[ "$screenlockMethod" == "i38lock" ]]; then
echo "exec_always --no-startup-id ${i3Path}/scripts/screenlock_autolock.sh ${screenlockAutolockSeconds} $(screenlock_command) --nofork"
else
echo "exec_always --no-startup-id ${i3Path}/scripts/screenlock_autolock.sh ${screenlockAutolockSeconds} $(screenlock_command)"
fi
fi
if screenlock_is_configured && [[ "${screenlockOnStartup:-1}" -eq 0 ]]; then
echo "exec --no-startup-id bash -c 'sleep 2; $(screenlock_command)'"
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
case "${fileBrowser##*/}" in
thunar|pcmanfm|pcmanfm-qt)
echo "exec_always --no-startup-id ${i3Path}/scripts/filemanager.sh ${fileBrowser}"
;;
esac
echo "exec_always --no-startup-id ${i3Path}/scripts/i38-clipboard.py --daemon"
echo "exec_always --no-startup-id ${i3Path}/scripts/desktop.py"
echo "exec_always --no-startup-id ${i3Path}/scripts/i38-update-check.sh"
if [[ $dex -eq 0 ]]; then
echo '# Start XDG autostart .desktop files using dex. See also'
echo '# https://wiki.archlinux.org/index.php/XDG_Autostart'
echo 'exec --no-startup-id dex --autostart --environment i3'
else
echo '# Startup applications'
if command -v x11bell &> /dev/null; then
echo 'exec --no-startup-id x11bell play -nqV0 synth .1 sq norm -12'
fi
if [[ "$screenReader" != "none" ]] && [[ -n "$screenReader" ]]; then
echo "exec $screenReader"
fi
fi)
# First run help documentation
exec --no-startup-id bash -c 'if [[ -f "${i3Path}/firstrun" ]]; then ${webBrowser} "${i3Path}/I38.html" & sleep 1 && rm "${i3Path}/firstrun"; fi'
# If you want to add personal customizations to i3, add them in ${i3Path}/customizations
# It is not overwritten when the config file is recreated.
include "${i3Path}/customizations"
EOF
touch "${i3Path}/customizations"
touch "${i3Path}/scratchpad"
# Check for markdown or pandoc for converting the welcome document
if command -v pandoc &> /dev/null ; then
pandoc -f markdown-citations -t html "I38.md" -so "${i3Path}/I38.html" --metadata pagetitle="Welcome to I38"
elif command -v markdown &> /dev/null ; then
cat << EOF > "${i3Path}/I38.html"
Welcome to I38
EOF
# Convert markdown to html and append to the file
markdown "I38.md" >> "${i3Path}/I38.html"
# Close the HTML tags using heredoc
cat << EOF >> "${i3Path}/I38.html"
EOF
fi
# More readable version of variables.
escapeKey="${escapeKey//Mod1/Alt}"
escapeKey="${escapeKey//Super_L/Super Left}"
escapeKey="${escapeKey//Super_R/Super Right}"
mod="${mod//Mod1/Alt}"
mod="${mod//Mod4/Super}"
webBrowser="${webBrowser##*/}"
screenReader="${screenReader##*/}"
textEditor="${textEditor##*/}"
fileBrowser="${fileBrowser##*/}"
ircClient="${ircClient##*/}"
webBrowserHelp="${webBrowser:-no web browser was configured}"
if [[ "$screenReader" == "none" ]] || [[ -z "$screenReader" ]]; then
screenReaderHelp="no screen reader was configured"
else
screenReaderHelp="$screenReader"
fi
textEditorHelp="${textEditor:-no text editor was configured}"
if [[ -n "$fileBrowser" ]]; then
fileBrowserHelp="I38 uses ${fileBrowser} for file management. Launch it in Ratpoison mode with the f key."
else
fileBrowserHelp="No file manager was configured when this help file was generated."
fi
personalModeKeyHelp=""
personalModeKeyForHelp=""
if [[ "${personalModeEnabled:-1}" -eq 0 && -n "$personalModeKey" ]]; then
personalModeKeyForHelp="$personalModeKey"
fi
if [[ -z "$personalModeKeyForHelp" ]] && [[ -f "${i3Path}/customizations" ]]; then
personalModeKeyForHelp="$(awk '/^[[:space:]]*bindsym[[:space:]].*[[:space:]]mode "personal"/ { print $2; exit }' "${i3Path}/customizations")"
fi
if [[ -n "$personalModeKeyForHelp" ]]; then
personalModeKeyDisplay="${personalModeKeyForHelp//Mod1/Alt}"
personalModeKeyDisplay="${personalModeKeyDisplay//Mod4/Super}"
personalModeKeyDisplay="${personalModeKeyDisplay//Super_L/Super Left}"
personalModeKeyDisplay="${personalModeKeyDisplay//Super_R/Super Right}"
personalModeKeyHelp="${personalModeKeyDisplay}: Enter Personal Mode.
"
fi
# Extract and categorize top-level keybindings for documentation
# Generate HTML directly since we're inserting into an already-converted HTML file
# Extract comment+bindsym pairs from config (excluding mode blocks)
declare -a applications workspaceSwitch workspaceMove windowMgmt utilities modes media
# Read the config and extract top-level bindings with their comments
while IFS= read -r line; do
if [[ "$line" =~ ^#.*$ ]]; then
# This is a comment line - save it
lastComment="${line#\# }"
elif [[ "$line" =~ ^bindsym.*$ ]]; then
# This is a bindsym line - pair it with the last comment
keybinding="${line#bindsym }"
keybinding="${keybinding//\\$/}"
# Extract just the key combination, which is the first word after bindsym.
keyCombo="${keybinding%% *}"
# Clean up key combo
keyCombo="${keyCombo//\$mod/$mod}"
keyCombo="${keyCombo//Mod1/Alt}"
keyCombo="${keyCombo//Mod4/Super}"
keyCombo="${keyCombo//+shift+/+Shift+}"
keyCombo="${keyCombo//apostrophe/apostrophe}"
keyCombo="${keyCombo//semicolon/semicolon}"
keyCombo="${keyCombo//exclam/exclamation mark}"
keyCombo="${keyCombo//minus/dash}"
keyCombo="${keyCombo//equal/equals sign}"
# Categorize based on the comment or the action
if [[ "$keybinding" == Control+F*workspace*number* ]]; then
# Switch to workspace
workspaceSwitch+=("$keyCombo|$lastComment")
elif [[ "$keybinding" == Control+Shift+F*move*container*workspace* ]]; then
# Move to workspace
workspaceMove+=("$keyCombo|$lastComment")
elif [[ "$lastComment" =~ (menu|terminal|editor|browser|Run dialog) ]] || [[ "$keybinding" =~ (menu.py|run_dialog) ]]; then
applications+=("$keyCombo|$lastComment")
elif [[ "$lastComment" =~ (focus|window|fullscreen|scratchpad|kill|close) ]] || [[ "$keybinding" =~ (focus|kill|fullscreen|scratchpad) ]]; then
windowMgmt+=("$keyCombo|$lastComment")
elif [[ "$lastComment" =~ (mode|ratpoison|panel|bypass) ]] || [[ "$keybinding" =~ mode ]]; then
modes+=("$keyCombo|$lastComment")
elif [[ "$keybinding" =~ (Audio|music_controler|pactl) ]]; then
media+=("$keyCombo|$lastComment")
else
utilities+=("$keyCombo|$lastComment")
fi
lastComment=""
fi
done < <(
awk '
/^mode "/ {
inMode = 1
previousComment = ""
next
}
inMode && /^}$/ {
inMode = 0
next
}
inMode {
next
}
/^#/ {
previousComment = $0
next
}
/^bindsym/ {
if (previousComment != "") {
print previousComment
previousComment = ""
}
print
next
}
{
previousComment = ""
}
' "${i3Path}/config"
)
# Build HTML reference sections without tables, so generated help stays easy to
# navigate with basic screen-reader commands.
emit_keybinding_group() {
local groupTitle="$1"
shift
local item key desc
local -a rows=()
if [[ $# -eq 0 ]]; then
return
fi
for item in "$@"; do
IFS='|' read -r key desc <<< "$item"
if [[ -n "$desc" ]]; then
rows+=("$key|$desc")
fi
done
if [[ ${#rows[@]} -eq 0 ]]; then
return
fi
printf '%s:
\n' "$groupTitle"
for item in "${rows[@]}"; do
IFS='|' read -r key desc <<< "$item"
printf '%s: %s
\n' "$key" "$desc"
done
printf '
\n'
}
{
echo "Default Mode Keybindings
"
emit_keybinding_group "Applications" "${applications[@]}"
# Workspace Navigation
if [[ ${#workspaceSwitch[@]} -gt 0 ]] || [[ ${#workspaceMove[@]} -gt 0 ]]; then
echo "Workspace Navigation:
"
echo "Control+F1 through F10: Switch to workspace 1-10
"
echo "Control+Shift+F1 through F10: Move window to workspace 1-10
"
echo "
"
fi
emit_keybinding_group "Window Management" "${windowMgmt[@]}"
emit_keybinding_group "Utilities" "${utilities[@]}"
emit_keybinding_group "Modes" "${modes[@]}"
emit_keybinding_group "Media Controls" "${media[@]}"
} > "${i3Path}/toplevel_temp.txt"
# Replace TOPLEVELKEYBINDINGS placeholder with the generated HTML content
awk '
/TOPLEVELKEYBINDINGS/ {
system("cat '"${i3Path}"'/toplevel_temp.txt")
next
}
{ print }
' "${i3Path}/I38.html" > "${i3Path}/I38.html.tmp" && mv "${i3Path}/I38.html.tmp" "${i3Path}/I38.html"
rm -f "${i3Path}/toplevel_temp.txt"
# Customize the html file to the user's choices.
I38_HELP_WEBBROWSER="$webBrowserHelp" \
I38_HELP_MODKEY="$mod" \
I38_HELP_SCREENREADER="$screenReaderHelp" \
I38_HELP_RATPOISONKEY="$escapeKey" \
I38_HELP_PERSONALMODEKEY="$personalModeKeyHelp" \
I38_HELP_TEXTEDITOR="$textEditorHelp" \
I38_HELP_FILEBROWSERHELP="$fileBrowserHelp" \
I38_HELP_CHECKOUTPATH="$i38CheckoutPath" \
python3 - "${i3Path}/I38.html" << 'PY'
import os
import sys
from pathlib import Path
html_path = Path(sys.argv[1])
replacements = {
"@WEBBROWSER@": os.environ["I38_HELP_WEBBROWSER"],
"@MODKEY@": os.environ["I38_HELP_MODKEY"],
"@SCREENREADER@": os.environ["I38_HELP_SCREENREADER"],
"@RATPOISONKEY@": os.environ["I38_HELP_RATPOISONKEY"],
"@PERSONALMODEKEY@": os.environ["I38_HELP_PERSONALMODEKEY"],
"@TEXTEDITOR@": os.environ["I38_HELP_TEXTEDITOR"],
"@FILEBROWSERHELP@": os.environ["I38_HELP_FILEBROWSERHELP"],
"@I38CHECKOUTPATH@": os.environ["I38_HELP_CHECKOUTPATH"],
}
content = html_path.read_text(encoding="utf-8")
for placeholder, value in replacements.items():
content = content.replace(placeholder, value)
html_path.write_text(content, encoding="utf-8")
PY
# Create the firstrun file
touch "${i3Path}/firstrun"
if [[ $defaultConfig -eq 1 ]]; then
echo "Default I38 configuration generated."
fi