1914 lines
65 KiB
Bash
Executable File
1914 lines
65 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Configures the i3 window manager to make it screen reader accessible
|
|
# Written by Storm Dragon, Jeremiah, and contributers.
|
|
# Copyright Stormux, 2022
|
|
|
|
# This program 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.
|
|
|
|
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
|
i3Path="${XDG_CONFIG_HOME:-$HOME/.config}/i3"
|
|
i3msg="i3-msg"
|
|
configFile="${PWD}/I38_preferences.conf"
|
|
i38VersionFile="${i3Path}/i38-version"
|
|
i38CheckoutPath="${PWD}"
|
|
defaultConfig=0
|
|
defaultGenerateX=0
|
|
|
|
# Dialog accessibility
|
|
export DIALOGOPTS='--no-lines --visit-items'
|
|
|
|
# Check to make sure minimum requirements are installed.
|
|
declare -a missing=()
|
|
for i in dialog jq python3 rsync yad xdotool ; do
|
|
if ! command -v "$i" &> /dev/null ; then
|
|
missing+=("$i")
|
|
fi
|
|
done
|
|
if ! command -v xdg-open &> /dev/null ; then
|
|
missing+=("xdg-utils")
|
|
fi
|
|
if command -v python3 &> /dev/null ; then
|
|
if ! python3 -c 'import i3ipc' &> /dev/null ; then
|
|
missing+=("python-i3ipc")
|
|
fi
|
|
if ! python3 -c 'import gi; gi.require_version("Atk", "1.0"); gi.require_version("Gdk", "3.0"); gi.require_version("Gio", "2.0"); gi.require_version("GLib", "2.0"); gi.require_version("Gtk", "3.0"); from gi.repository import Atk, Gdk, Gio, GLib, Gtk' &> /dev/null ; then
|
|
missing+=("python-gobject")
|
|
fi
|
|
fi
|
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
echo "Please install the following packages and run this script again:"
|
|
echo "${missing[*]}"
|
|
exit 1
|
|
fi
|
|
if ! command -v pandoc &> /dev/null && ! command -v markdown &> /dev/null ; then
|
|
echo "Please install either pandoc or markdown."
|
|
echo "The markdown command may be provided by the package discount."
|
|
exit 1
|
|
fi
|
|
|
|
keyboard_menu() {
|
|
keyboardMenu=("us" "English (US)"
|
|
"af" "Dari"
|
|
"al" "Albanian"
|
|
"et" "Amharic"
|
|
"ara" "Arabic"
|
|
"ma" "Arabic (Morocco)"
|
|
"sy" "Arabic (Syria)"
|
|
"am" "Armenian"
|
|
"az" "Azerbaijani"
|
|
"ml" "Bambara"
|
|
"bd" "Bangla"
|
|
"by" "Belarusian"
|
|
"be" "Belgian"
|
|
"dz" "Berber (Algeria, Latin)"
|
|
"ba" "Bosnian"
|
|
"brai" "Braille"
|
|
"bg" "Bulgarian"
|
|
"mm" "Burmese"
|
|
"cn" "Chinese"
|
|
"hr" "Croatian"
|
|
"cz" "Czech"
|
|
"dk" "Danish"
|
|
"mv" "Dhivehi"
|
|
"nl" "Dutch"
|
|
"bt" "Dzongkha"
|
|
"au" "English (Australia)"
|
|
"gh" "English (Ghana)"
|
|
"ng" "English (Nigeria)"
|
|
"za" "English (South Africa)"
|
|
"gb" "English (UK)"
|
|
"epo" "Esperanto"
|
|
"ee" "Estonian"
|
|
"fo" "Faroese"
|
|
"ph" "Filipino"
|
|
"fi" "Finnish"
|
|
"fr" "French"
|
|
"ca" "French (Canada)"
|
|
"cd" "French (Democratic Republic of the Congo)"
|
|
"ge" "Georgian"
|
|
"de" "German"
|
|
"ch" "German (Switzerland)"
|
|
"gr" "Greek"
|
|
"il" "Hebrew"
|
|
"hu" "Hungarian"
|
|
"is" "Icelandic"
|
|
"in" "Indian"
|
|
"jv" "Indonesian (Javanese)"
|
|
"iq" "Iraqi"
|
|
"ie" "Irish"
|
|
"it" "Italian"
|
|
"jp" "Japanese"
|
|
"nec_vndr/jp" "Japanese (PC-98)"
|
|
"kz" "Kazakh"
|
|
"kh" "Khmer (Cambodia)"
|
|
"kr" "Korean"
|
|
"kg" "Kyrgyz"
|
|
"la" "Lao"
|
|
"lv" "Latvian"
|
|
"lt" "Lithuanian"
|
|
"mk" "Macedonian"
|
|
"mv" "Malay (Jawi, Arabic Keyboard)"
|
|
"mt" "Maltese"
|
|
"mao" "Maori"
|
|
"mn" "Mongolian"
|
|
"me" "Montenegrin"
|
|
"gn" "N'Ko (AZERTY)"
|
|
"np" "Nepali"
|
|
"no" "Norwegian"
|
|
"ir" "Persian"
|
|
"pl" "Polish"
|
|
"pt" "Portuguese"
|
|
"br" "Portuguese (Brazil)"
|
|
"ro" "Romanian"
|
|
"ru" "Russian"
|
|
"rs" "Serbian"
|
|
"lk" "Sinhala (phonetic)"
|
|
"sk" "Slovak"
|
|
"si" "Slovenian"
|
|
"es" "Spanish"
|
|
"latam" "Spanish (Latin American)"
|
|
"tz" "Swahili (Tanzania)"
|
|
"se" "Swedish"
|
|
"tw" "Taiwanese"
|
|
"tj" "Tajik"
|
|
"th" "Thai"
|
|
"tr" "Turkish"
|
|
"tm" "Turkmen"
|
|
"bw" "Tswana"
|
|
"ua" "Ukrainian"
|
|
"pk" "Urdu (Pakistan)"
|
|
"uz" "Uzbek (Afghanistan)"
|
|
"vn" "Vietnamese"
|
|
"sn" "Wolof"
|
|
)
|
|
dialog --title "I38" \
|
|
--backtitle "Use the arrow keys to find the option you want, and enter to select it. When you are finished selecting layouts, use right arrow to find \"Done\" and press enter." \
|
|
--clear \
|
|
--cancel-label "Done" \
|
|
--no-tags \
|
|
--menu "Select Keyboard Layout" 0 0 0 "${keyboardMenu[@]}" --stdout
|
|
return $?
|
|
}
|
|
|
|
menulist() {
|
|
# Args: List of items for menu.
|
|
# returns: selected tag
|
|
local menuText="$1"
|
|
shift
|
|
local menuList
|
|
for i in "${@}" ; do
|
|
menuList+=("$i" "$i")
|
|
done
|
|
dialog --title "I38" \
|
|
--backtitle "Use the arrow keys to find the option you want, and enter to select it." \
|
|
--clear \
|
|
--no-tags \
|
|
--menu "$menuText" 0 0 0 "${menuList[@]}" --stdout
|
|
return $?
|
|
}
|
|
|
|
# rangebox
|
|
# $1 text to show
|
|
# $2 minimum value
|
|
# $3 maximum value
|
|
# $4 Default value
|
|
rangebox() {
|
|
dialog --title "I38" \
|
|
--backtitle "Use the arrow keys to select a number, then press enter." \
|
|
--rangebox "$1" -1 -1 "$2" "$3" "$4" --stdout
|
|
}
|
|
|
|
yesno() {
|
|
# Returns: Yes 0 or No 1
|
|
# Args: Question to user.
|
|
if [[ $defaultConfig -eq 1 ]]; then
|
|
return 0
|
|
fi
|
|
dialog --clear --title "I38" --yesno "$*" -1 -1 --stdout
|
|
return $?
|
|
}
|
|
|
|
first_available_program() {
|
|
local program
|
|
|
|
for program in "$@"; do
|
|
if command -v "${program#-}" &> /dev/null; then
|
|
printf '%s\n' "${program#-}"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
first_available_path() {
|
|
local program
|
|
|
|
if program="$(first_available_program "$@")"; then
|
|
command -v "$program"
|
|
fi
|
|
}
|
|
|
|
set_default_configuration() {
|
|
escapeKey="Mod1+Escape"
|
|
mod="Mod1"
|
|
kbd=()
|
|
volumeJump=5
|
|
i3FontSize=12
|
|
screenReader="$(first_available_path cthulhu orca || true)"
|
|
screenReader="${screenReader:-none}"
|
|
emailClient="$(first_available_path betterbird evolution thunderbird || true)"
|
|
webBrowser="$(first_available_path brave chromium epiphany firefox google-chrome-stable google-chrome-unstable microsoft-edge-stable microsoft-edge-beta microsoft-edge-dev midori seamonkey vivaldi || true)"
|
|
textEditor="$(first_available_path emacs geany gedit kate kwrite l3afpad leafpad libreoffice mousepad pluma || true)"
|
|
fileBrowser="$(first_available_path caja nemo nautilus pcmanfm pcmanfm-qt thunar || true)"
|
|
ircClient="$(first_available_path albikirc Albikirc access-irc || true)"
|
|
terminalEmulator="$(first_available_path mate-terminal lxterminal gnome-terminal terminator xfce4-terminal tilix ptyxis kgx sakura roxterm termit guake tilda qterminal konsole || true)"
|
|
if [[ -z "$terminalEmulator" ]] && command -v xterm &> /dev/null && command -v fenrir &> /dev/null; then
|
|
terminalEmulator="$(command -v xterm)"
|
|
fi
|
|
udiskie=1
|
|
if command -v udiskie &> /dev/null; then
|
|
udiskie=0
|
|
fi
|
|
dex=1
|
|
if command -v dex &> /dev/null; then
|
|
dex=0
|
|
fi
|
|
batteryAlert=1
|
|
if command -v acpi &> /dev/null; then
|
|
batteryAlert=0
|
|
fi
|
|
brlapi=1
|
|
if [[ "$screenReader" != "none" ]] && [[ -n "$screenReader" ]]; then
|
|
brlapi=0
|
|
fi
|
|
sounds=0
|
|
screenlockMethod="none"
|
|
screenlockPinHash=""
|
|
screenlockAutolockSeconds=0
|
|
screenlockOnStartup=1
|
|
if i38lock_is_supported; then
|
|
screenlockMethod="i38lock"
|
|
screenlockAutolockSeconds=300
|
|
screenlockOnStartup=0
|
|
fi
|
|
personalModeEnabled=0
|
|
personalModeKey="Control+Escape"
|
|
waytrayUseI38Config=0
|
|
}
|
|
|
|
# Personal mode helpers
|
|
normalize_ratpoison_key() {
|
|
local key="$1"
|
|
|
|
key="${key//Alt/Mod1}"
|
|
key="${key//Super Left/Super_L}"
|
|
key="${key//Super Right/Super_R}"
|
|
|
|
echo "$key"
|
|
}
|
|
|
|
select_personal_mode_key() {
|
|
local ratpoisonModeKeys=(
|
|
"Control+t"
|
|
"Control+z"
|
|
"Control+Escape"
|
|
"Alt+Escape"
|
|
"Control+space"
|
|
"Super Left"
|
|
"Super Right"
|
|
)
|
|
local personalModeKeyOptions=()
|
|
local option normalizedOption
|
|
|
|
for option in "${ratpoisonModeKeys[@]}"; do
|
|
normalizedOption="$(normalize_ratpoison_key "$option")"
|
|
if [[ "$normalizedOption" != "$escapeKey" ]]; then
|
|
personalModeKeyOptions+=("$option")
|
|
fi
|
|
done
|
|
|
|
if [[ ${#personalModeKeyOptions[@]} -eq 0 ]]; then
|
|
echo ""
|
|
return
|
|
fi
|
|
|
|
local selectedKey
|
|
selectedKey="$(menulist "Personal mode key:" "${personalModeKeyOptions[@]}")"
|
|
normalize_ratpoison_key "$selectedKey"
|
|
}
|
|
|
|
select_screenlock_autolock_seconds() {
|
|
local selectedAutolock
|
|
|
|
if ! selectedAutolock="$(dialog --title "I38" \
|
|
--backtitle "Use the arrow keys to choose how long I38 should wait before locking the screen automatically." \
|
|
--clear \
|
|
--default-item "0" \
|
|
--no-tags \
|
|
--menu "Autolock screen after:" 0 0 0 \
|
|
"0" "Never" \
|
|
"180" "3 minutes" \
|
|
"300" "5 minutes" \
|
|
"600" "10 minutes" \
|
|
"1800" "30 minutes" \
|
|
"3600" "1 hour" \
|
|
--stdout)"; then
|
|
selectedAutolock="0"
|
|
fi
|
|
|
|
echo "$selectedAutolock"
|
|
}
|
|
|
|
select_screenlock_method() {
|
|
local defaultMethod="$1"
|
|
local -a lockOptions=(
|
|
"pin" "I38 PIN privacy screen"
|
|
"none" "Do not configure screen lock"
|
|
)
|
|
|
|
if i38lock_is_supported; then
|
|
lockOptions=(
|
|
"i38lock" "I38Lock secure PAM locker"
|
|
"${lockOptions[@]}"
|
|
)
|
|
fi
|
|
|
|
if [[ "$defaultMethod" != "pin" && "$defaultMethod" != "i38lock" && "$defaultMethod" != "none" ]]; then
|
|
if i38lock_is_supported; then
|
|
defaultMethod="i38lock"
|
|
else
|
|
defaultMethod="pin"
|
|
fi
|
|
fi
|
|
|
|
dialog --title "I38" \
|
|
--backtitle "Use the arrow keys to choose the screen lock I38 should configure." \
|
|
--clear \
|
|
--default-item "$defaultMethod" \
|
|
--menu "Screen lock method:" 0 0 0 \
|
|
"${lockOptions[@]}" \
|
|
--stdout
|
|
}
|
|
|
|
screen_reader_name() {
|
|
if [[ "$screenReader" == "none" || -z "$screenReader" ]]; then
|
|
echo "none"
|
|
return
|
|
fi
|
|
|
|
echo "${screenReader##*/}"
|
|
}
|
|
|
|
orca_major_version() {
|
|
local orcaPath="$1"
|
|
local versionLine
|
|
|
|
versionLine="$("$orcaPath" --version 2>&1 | awk '/^Orca version / { split($3, parts, "."); print parts[1]; exit }')"
|
|
if [[ "$versionLine" =~ ^[0-9]+$ ]]; then
|
|
echo "$versionLine"
|
|
fi
|
|
}
|
|
|
|
orca_is_i38lock_supported() {
|
|
local orcaPath="$1"
|
|
local majorVersion
|
|
|
|
if [[ -z "$orcaPath" ]] || ! command -v "$orcaPath" &> /dev/null; then
|
|
return 1
|
|
fi
|
|
|
|
majorVersion="$(orca_major_version "$orcaPath")"
|
|
[[ "$majorVersion" =~ ^[0-9]+$ ]] && [[ "$majorVersion" -ge 50 ]]
|
|
}
|
|
|
|
i38lock_is_supported() {
|
|
local readerName orcaPath
|
|
|
|
if ! command -v i38lock &> /dev/null; then
|
|
return 1
|
|
fi
|
|
|
|
readerName="$(screen_reader_name)"
|
|
if [[ "$readerName" == "cthulhu" ]] || command -v cthulhu &> /dev/null; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$readerName" == "orca" ]]; then
|
|
orcaPath="$screenReader"
|
|
else
|
|
orcaPath="$(command -v orca 2> /dev/null || true)"
|
|
fi
|
|
|
|
orca_is_i38lock_supported "$orcaPath"
|
|
}
|
|
|
|
screenlock_is_configured() {
|
|
[[ "$screenlockMethod" == "pin" || "$screenlockMethod" == "i38lock" ]]
|
|
}
|
|
|
|
screenlock_command() {
|
|
if [[ "$screenlockMethod" == "i38lock" ]]; then
|
|
command -v i38lock
|
|
else
|
|
printf '%s/scripts/screenlock.sh\n' "$i3Path"
|
|
fi
|
|
}
|
|
|
|
update_personal_customizations() {
|
|
local customizationsPath="${i3Path}/customizations"
|
|
local startMarker="# I38 Personal mode start"
|
|
local endMarker="# I38 Personal mode end"
|
|
local personalModeBlock
|
|
|
|
if personal_mode_exists; then
|
|
return
|
|
fi
|
|
|
|
if [[ "${personalModeEnabled:-1}" -ne 0 ]] || [[ -z "$personalModeKey" ]]; then
|
|
return
|
|
fi
|
|
|
|
personalModeBlock=$(cat << EOF
|
|
${startMarker}
|
|
bindsym ${personalModeKey} mode "personal"
|
|
# A template mode where you can bind items that will not be overwritten during updates
|
|
mode "personal" {
|
|
bindsym F1 exec ${i3Path}/scripts/i38-help-personal.sh, mode "default"
|
|
bindsym Escape mode "default"
|
|
bindsym Control+g mode "default"
|
|
}
|
|
${endMarker}
|
|
EOF
|
|
)
|
|
|
|
if [[ -f "$customizationsPath" ]] && [[ -s "$customizationsPath" ]]; then
|
|
printf "\n%s\n" "$personalModeBlock" >> "$customizationsPath"
|
|
else
|
|
printf "%s\n" "$personalModeBlock" > "$customizationsPath"
|
|
fi
|
|
}
|
|
|
|
personal_mode_exists() {
|
|
local customizationsPath="${i3Path}/customizations"
|
|
|
|
[[ -f "$customizationsPath" ]] && grep -q 'mode "personal"' "$customizationsPath"
|
|
}
|
|
|
|
terminal_command() {
|
|
local terminalPath="$1"
|
|
local terminalName="${terminalPath##*/}"
|
|
|
|
if [[ "$terminalName" == "xterm" ]]; then
|
|
printf '%s -T "I38 terminal" -e "env ORC_CODE=backup fenrir -x"' "$terminalPath"
|
|
else
|
|
printf '%s' "$terminalPath"
|
|
fi
|
|
}
|
|
|
|
screen_reader_command() {
|
|
local screenReaderPath="$1"
|
|
|
|
if [[ "${screenReaderPath##*/}" == "cthulhu" ]]; then
|
|
printf 'env ORC_CODE=backup %s' "$screenReaderPath"
|
|
else
|
|
printf '%s' "$screenReaderPath"
|
|
fi
|
|
}
|
|
|
|
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
|
|
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
save_config() {
|
|
if [[ $defaultConfig -eq 1 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
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"
|
|
i3FontSize="${i3FontSize:-12}"
|
|
|
|
# Application paths
|
|
screenReader="$screenReader"
|
|
emailClient="$emailClient"
|
|
webBrowser="$webBrowser"
|
|
textEditor="$textEditor"
|
|
fileBrowser="$fileBrowser"
|
|
ircClient="$ircClient"
|
|
terminalEmulator="$terminalEmulator"
|
|
|
|
# Boolean settings (0=yes, 1=no)
|
|
udiskie="$udiskie"
|
|
dex="$dex"
|
|
batteryAlert="${batteryAlert:-1}"
|
|
brlapi="$brlapi"
|
|
sounds="$sounds"
|
|
|
|
# Screen lock
|
|
screenlockMethod="${screenlockMethod:-none}"
|
|
screenlockPinHash="$screenlockPinHash"
|
|
screenlockAutolockSeconds="${screenlockAutolockSeconds:-0}"
|
|
screenlockOnStartup="${screenlockOnStartup:-1}"
|
|
|
|
# Personal mode
|
|
personalModeEnabled="${personalModeEnabled:-0}"
|
|
personalModeKey="$personalModeKey"
|
|
|
|
# WayTray configuration (0=use I38 config, 1=keep user config)
|
|
waytrayUseI38Config="${waytrayUseI38Config:-0}"
|
|
EOF
|
|
|
|
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##*/}"
|
|
echo "Released under the terms of the GPL V3 License: https://www.gnu.org/licenses/"
|
|
echo -e "This is a Stormux project: https://stormux.org\n"
|
|
echo -e "Usage:\n"
|
|
echo "With no arguments, create the i3 configuration."
|
|
for i in "${!command[@]}" ; do
|
|
echo "-${i/:/ <parameter>}: ${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 1 && -z "$terminalEmulator" ]]; then
|
|
echo "No supported terminal emulator was found. Please install mate-terminal, lxterminal, gnome-terminal, terminator, another supported accessible terminal, or install both xterm and fenrir."
|
|
exit 1
|
|
fi
|
|
if [[ $defaultConfig -eq 0 ]] && { [[ -z "$terminalEmulator" ]] || { [[ ! -x "$terminalEmulator" ]] && ! command -v "$terminalEmulator" &> /dev/null; }; }; then
|
|
configChanged=1
|
|
programList=()
|
|
for i in mate-terminal lxterminal gnome-terminal terminator xfce4-terminal tilix ptyxis kgx sakura roxterm termit guake tilda qterminal konsole ; do
|
|
if command -v "$i" &> /dev/null ; then
|
|
programList+=("$i")
|
|
fi
|
|
done
|
|
if command -v xterm &> /dev/null && command -v fenrir &> /dev/null ; then
|
|
programList+=("xterm")
|
|
fi
|
|
if [[ ${#programList[@]} -eq 0 ]]; then
|
|
dialog --title "I38" --msgbox "No supported terminal emulator was found. Please install mate-terminal, lxterminal, gnome-terminal, terminator, another supported accessible terminal, or install both xterm and fenrir." -1 -1
|
|
exit 1
|
|
elif [[ ${#programList[@]} -gt 1 ]]; then
|
|
terminalEmulator="$(menulist "Terminal emulator:" "${programList[@]}")"
|
|
else
|
|
terminalEmulator="${programList[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 "$(screen_reader_command "$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 [[ $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")"
|
|
screenReaderExec="$(screen_reader_command "$screenReader")"
|
|
|
|
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 icons
|
|
bindsym \$mod+Control+d exec --no-startup-id ${i3Path}/scripts/desktop.sh
|
|
|
|
# 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 spd-say -P important -Cw "moved to workspace 1"
|
|
# Move window to workspace 2
|
|
bindsym Control+Shift+F2 move container to workspace number \$ws2, exec spd-say -P important -Cw "moved to workspace 2"
|
|
# Move window to workspace 3
|
|
bindsym Control+Shift+F3 move container to workspace number \$ws3, exec spd-say -P important -Cw "moved to workspace 3"
|
|
# Move window to workspace 4
|
|
bindsym Control+Shift+F4 move container to workspace number \$ws4, exec spd-say -P important -Cw "moved to workspace 4"
|
|
# Move window to workspace 5
|
|
bindsym Control+Shift+F5 move container to workspace number \$ws5, exec spd-say -P important -Cw "moved to workspace 5"
|
|
# Move window to workspace 6
|
|
bindsym Control+Shift+F6 move container to workspace number \$ws6, exec spd-say -P important -Cw "moved to workspace 6"
|
|
# Move window to workspace 7
|
|
bindsym Control+Shift+F7 move container to workspace number \$ws7, exec spd-say -P important -Cw "moved to workspace 7"
|
|
# Move window to workspace 8
|
|
bindsym Control+Shift+F8 move container to workspace number \$ws8, exec spd-say -P important -Cw "moved to workspace 8"
|
|
# Move window to workspace 9
|
|
bindsym Control+Shift+F9 move container to workspace number \$ws9, exec spd-say -P important -Cw "moved to workspace 9"
|
|
# Move window to workspace 10
|
|
bindsym Control+Shift+F10 move container to workspace number \$ws10, exec spd-say -P important -Cw "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"
|
|
|
|
# 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"
|
|
# 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 env ORC_CODE=backup $(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 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.sh"
|
|
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 $screenReaderExec"
|
|
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"
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Welcome to I38</title>
|
|
<meta charset="utf-8">
|
|
</head>
|
|
<body>
|
|
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"
|
|
</body>
|
|
</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 <code>f</code> 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="<code>${personalModeKeyDisplay}</code>: Enter Personal Mode.<br>"
|
|
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 '<p><strong>%s:</strong><br>\n' "$groupTitle"
|
|
for item in "${rows[@]}"; do
|
|
IFS='|' read -r key desc <<< "$item"
|
|
printf '<code>%s</code>: %s<br>\n' "$key" "$desc"
|
|
done
|
|
printf '</p>\n'
|
|
}
|
|
|
|
{
|
|
echo "<h4>Default Mode Keybindings</h4>"
|
|
|
|
emit_keybinding_group "Applications" "${applications[@]}"
|
|
|
|
# Workspace Navigation
|
|
if [[ ${#workspaceSwitch[@]} -gt 0 ]] || [[ ${#workspaceMove[@]} -gt 0 ]]; then
|
|
echo "<p><strong>Workspace Navigation:</strong><br>"
|
|
echo "<code>Control+F1</code> through <code>F10</code>: Switch to workspace 1-10<br>"
|
|
echo "<code>Control+Shift+F1</code> through <code>F10</code>: Move window to workspace 1-10<br>"
|
|
echo "</p>"
|
|
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
|