Screen lock and power management now native I38 scripts. Removed customizations and added a personal mode template option instead. This means things added by users should not be removed on updates.

This commit is contained in:
Storm Dragon
2025-12-23 18:47:21 -05:00
parent b4578e0035
commit f1a882b8b1
6 changed files with 584 additions and 268 deletions

View File

@@ -21,7 +21,6 @@ An uppercase I looks like a 1, 3 from i3, and 8 because the song [We Are 138](ht
- i3-wm: The i3 window manager.
- jq: for getting the current workspace
- libnotify: For sending notifications
- lxsession: [optional] For GUI power options like shutdown
- magic-wormhole: [optional] for file sharing with magic-wormhole GUI
- notification-daemon: To handle notifications
- pamixer: for the mute-unmute script

522
i38.sh
View File

@@ -169,7 +169,7 @@ menulist() {
rangebox() {
dialog --title "I38" \
--backtitle "Use the arrow keys to select a number, then press enter." \
--rangebox "$1" -1 -1 $2 $3 $4 --stdout
--rangebox "$1" -1 -1 "$2" "$3" "$4" --stdout
}
yesno() {
@@ -179,161 +179,85 @@ yesno() {
return $?
}
# Custom application keybinding functions
declare -A usedKeys
declare -a customApps
# Personal mode helpers
normalize_ratpoison_key() {
local key="$1"
showKeybindingHelp() {
dialog --title "I38 Keybinding Help" --msgbox \
"Keybinding Notation:
Modifiers: ^ = Ctrl, ! = Alt, # = Super, m = your mod key
Special keys: f1-f12, up/down/left/right, space, tab, return
home/end/insert/delete/pageup/pagedown, print
backspace, escape
key="${key//Alt/Mod1}"
key="${key//Super Left/Super_L}"
key="${key//Super Right/Super_R}"
Examples:
c = just 'c' key
^c = Ctrl+c
!f1 = Alt+F1
mspace = mod+Space
^!up = Ctrl+Alt+Up
#pagedown = Super+Page_Down
Uppercase letters imply Shift (e.g., C = Shift+c)" 0 0
echo "$key"
}
convertKeybinding() {
local input="$1"
local result=""
local baseKey=""
# Handle modifiers
if [[ "$input" == *"^"* ]]; then
result+="Control+"
input="${input//^/}"
fi
if [[ "$input" == *"!"* ]]; then
result+="Mod1+"
input="${input//!/}"
fi
if [[ "$input" == *"#"* ]]; then
result+="Mod4+"
input="${input//#/}"
fi
if [[ "$input" == *"m"* ]]; then
result+="\$mod+"
input="${input//m/}"
fi
# Handle shift for uppercase letters
if [[ "$input" =~ [A-Z] ]]; then
result+="Shift+"
input="${input,,}"
fi
# Convert special keys
case "$input" in
f[1-9]|f1[0-2]) baseKey="F${input#f}" ;;
up|down|left|right) baseKey="$input" ;;
space) baseKey="space" ;;
tab) baseKey="Tab" ;;
return) baseKey="Return" ;;
escape) baseKey="Escape" ;;
backspace) baseKey="BackSpace" ;;
print) baseKey="Print" ;;
home) baseKey="Home" ;;
end) baseKey="End" ;;
insert) baseKey="Insert" ;;
delete) baseKey="Delete" ;;
pageup) baseKey="Page_Up" ;;
pagedown) baseKey="Page_Down" ;;
*) baseKey="$input" ;;
esac
echo "${result}${baseKey}"
}
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
populateUsedKeys() {
# Populate with existing ratpoison mode bindings
usedKeys["Shift+slash"]=1
usedKeys["c"]=1
usedKeys["e"]=1
usedKeys["f"]=1
usedKeys["\$mod+e"]=1
usedKeys["w"]=1
usedKeys["k"]=1
usedKeys["m"]=1
usedKeys["Print"]=1
usedKeys["\$mod+r"]=1
usedKeys["p"]=1
usedKeys["\$mod+s"]=1
usedKeys["Mod1+Shift+0"]=1
usedKeys["Mod1+Shift+9"]=1
usedKeys["Mod1+Shift+equal"]=1
usedKeys["Mod1+Shift+minus"]=1
usedKeys["Mod1+Shift+z"]=1
usedKeys["Mod1+Shift+c"]=1
usedKeys["Mod1+Shift+x"]=1
usedKeys["Mod1+Shift+v"]=1
usedKeys["Mod1+Shift+b"]=1
usedKeys["Mod1+Shift+u"]=1
usedKeys["Mod1+b"]=1
usedKeys["g"]=1
usedKeys["apostrophe"]=1
usedKeys["Shift+c"]=1
usedKeys["Shift+o"]=1
usedKeys["Shift+t"]=1
usedKeys["Control+semicolon"]=1
usedKeys["Control+Shift+semicolon"]=1
usedKeys["Shift+exclam"]=1
usedKeys["\$mod+q"]=1
usedKeys["Control+\$mod+q"]=1
usedKeys["Escape"]=1
usedKeys["Control+g"]=1
}
inputText() {
# Args: prompt text
dialog --title "I38" --inputbox "$1" 0 0 --stdout
}
addCustomApplication() {
local appName appCommand appFlags keybinding convertedKey
populateUsedKeys
while true; do
appName="$(inputText "Custom Applications:\n\nEnter application name (or press enter when finished):")"
[[ -z "$appName" ]] && break
appCommand="$(inputText "Enter execution path/command for $appName:")"
[[ -z "$appCommand" ]] && continue
appFlags="$(inputText "Enter command line flags for $appName (optional):")"
while true; do
keybinding="$(inputText "Enter keybinding for $appName (Examples: c, ^c, !f1, mspace, ^!up) or ? for help:")"
if [[ "$keybinding" == "?" ]]; then
showKeybindingHelp
continue
fi
[[ -z "$keybinding" ]] && break
convertedKey="$(convertKeybinding "$keybinding")"
if [[ -n "${usedKeys[$convertedKey]}" ]]; then
dialog --title "I38" --msgbox "Keybinding '$keybinding' ($convertedKey) is already in use. Please choose another." 0 0
continue
fi
# Add to arrays
customApps+=("$appName|$appCommand|$appFlags|$convertedKey")
usedKeys["$convertedKey"]=1
break
done
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"
}
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"
}
load_config() {
@@ -347,20 +271,6 @@ load_config() {
IFS=' ' read -ra kbd <<< "$kbd"
fi
# Reconstruct customApps array from numbered entries
customApps=()
local i=0
local varName
while : ; do
varName="customApp_$i"
if [[ -n "${!varName}" ]]; then
customApps+=("${!varName}")
((i++))
else
break
fi
done
return 0
fi
return 1
@@ -398,12 +308,13 @@ batteryAlert="${batteryAlert:-1}"
brlapi="$brlapi"
sounds="$sounds"
# Custom applications
# Screen lock
screenlockPinHash="$screenlockPinHash"
# Personal mode
personalModeEnabled="${personalModeEnabled:-1}"
personalModeKey="$personalModeKey"
EOF
# Save custom apps with numbered keys
for i in "${!customApps[@]}"; do
echo "customApp_$i=\"${customApps[$i]}\"" >> "$configFile"
done
dialog --title "I38" --msgbox "Configuration saved to $configFile\n\nYou can edit this file manually or delete it to reconfigure from scratch." 0 0
fi
@@ -429,6 +340,11 @@ 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
#
@@ -436,10 +352,11 @@ cat << 'EOF' > ~/.xinitrc
#
# Executed by startx (run your window manager from here)
[[ -f ~/.Xresources ]] && xrdb -merge -I$HOME ~/.Xresources
[[ -f ~/.Xresources ]] && xrdb -merge -I\$HOME ~/.Xresources
if [ -d /etc/X11/xinit/xinitrc.d ]; then
for f in /etc/X11/xinit/xinitrc.d/*; do
# shellcheck disable=SC2154
[ -x "$f" ] && . "$f"
done
unset f
@@ -447,9 +364,8 @@ fi
[ -f /etc/xprofile ] && . /etc/xprofile
[ -f ~/.xprofile ] && . ~/.xprofile
exec dbus-run-session -- i3
EOF
echo "$sessionCommand" >> ~/.xinitrc
chmod +x ~/.xinitrc
}
@@ -472,8 +388,35 @@ EOF
exit 0
}
apply_screenlock_pin() {
local pinFile="${i3Path}/.screenpin"
local pinValue="$screenlockPinHash"
if [[ -z "$pinValue" ]]; then
rm -f "$pinFile"
return 0
fi
printf "%s\n" "$pinValue" > "$pinFile"
chmod 600 "$pinFile"
}
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
cp -rv scripts/ "${i3Path}/" | dialog --backtitle "I38" --progressbox "Updating scripts..." -1 -1
if [[ -n "$existingPinHash" ]]; then
screenlockPinHash="$existingPinHash"
apply_screenlock_pin
fi
exit 0
}
@@ -589,132 +532,114 @@ if [[ -z "$volumeJump" ]]; then
fi
# Screen Reader
if [[ -z "$screenReader" ]] || ! command -v "$screenReader" &> /dev/null; then
unset programList
programList=()
for i in cthulhu orca ; do
if command -v ${i/#-/} &> /dev/null ; then
if [ -n "$programList" ]; then
programList="$programList $i"
else
programList="$i"
fi
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
if [ "$programList" != "${programList// /}" ]; then
screenReader="$(menulist ":Screen Reader" $programList)"
if [[ ${#programList[@]} -gt 1 ]]; then
screenReader="$(menulist ":Screen Reader" "${programList[@]}")"
else
screenReader="${programList/#-/}"
screenReader="${programList[0]#-}"
fi
export screenReader="$(command -v $screenReader)"
screenReader="$(command -v "$screenReader")"
export screenReader
else
# Validate and export existing preference
export screenReader
fi
# Email client
if [[ -z "$emailClient" ]] || ! command -v "$emailClient" &> /dev/null; then
unset programList
programList=()
for i in betterbird evolution thunderbird ; do
if command -v ${i/#-/} &> /dev/null ; then
if [ -n "$programList" ]; then
programList="$programList $i"
else
programList="$i"
fi
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
if [ "$programList" != "${programList// /}" ]; then
emailClient="$(menulist "Email client:" $programList)"
if [[ ${#programList[@]} -gt 1 ]]; then
emailClient="$(menulist "Email client:" "${programList[@]}")"
else
emailClient="${programList/#-/}"
emailClient="${programList[0]#-}"
fi
export emailClient="$(command -v $emailClient)"
emailClient="$(command -v "$emailClient")"
export emailClient
else
# Validate and export existing preference
export emailClient
fi
# Web browser
if [[ -z "$webBrowser" ]] || ! command -v "$webBrowser" &> /dev/null; then
unset programList
programList=()
for i in brave chromium epiphany firefox google-chrome-stable google-chrome-unstable microsoft-edge-stable microsoft-edge-beta microsoft-edge-dev midori seamonkey vivaldi ; do
if command -v ${i/#-/} &> /dev/null ; then
if [ -n "$programList" ]; then
programList="$programList $i"
else
programList="$i"
fi
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
if [ "$programList" != "${programList// /}" ]; then
webBrowser="$(menulist "Web browser:" $programList)"
if [[ ${#programList[@]} -gt 1 ]]; then
webBrowser="$(menulist "Web browser:" "${programList[@]}")"
else
webBrowser="${programList/#-/}"
webBrowser="${programList[0]#-}"
fi
export webBrowser="$(command -v $webBrowser)"
webBrowser="$(command -v "$webBrowser")"
export webBrowser
else
# Validate and export existing preference
export webBrowser
fi
# Text editor
if [[ -z "$textEditor" ]] || ! command -v "$textEditor" &> /dev/null; then
unset programList
programList=()
for i in emacs geany gedit kate kwrite l3afpad leafpad libreoffice mousepad pluma ; do
if hash ${i/#-/} &> /dev/null ; then
if [ -n "$programList" ]; then
programList="$programList $i"
else
programList="$i"
fi
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
if [ "$programList" != "${programList// /}" ]; then
textEditor="$(menulist "Text editor:" $programList)"
if [[ ${#programList[@]} -gt 1 ]]; then
textEditor="$(menulist "Text editor:" "${programList[@]}")"
else
textEditor="${programList/#-/}"
textEditor="${programList[0]#-}"
fi
export textEditor="$(command -v $textEditor)"
textEditor="$(command -v "$textEditor")"
export textEditor
else
# Validate and export existing preference
export textEditor
fi
# File browser
if [[ -z "$fileBrowser" ]] || ! command -v "$fileBrowser" &> /dev/null; then
unset programList
programList=()
for i in caja nemo nautilus pcmanfm pcmanfm-qt thunar ; do
if hash ${i/#-/} &> /dev/null ; then
if [ -n "$programList" ]; then
programList="$programList $i"
else
programList="$i"
fi
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
if [ "$programList" != "${programList// /}" ]; then
fileBrowser="$(menulist "File browser:" $programList)"
if [[ ${#programList[@]} -gt 1 ]]; then
fileBrowser="$(menulist "File browser:" "${programList[@]}")"
else
fileBrowser="${programList/#-/}"
fileBrowser="${programList[0]#-}"
fi
export fileBrowser="$(command -v $fileBrowser)"
fileBrowser="$(command -v "$fileBrowser")"
export fileBrowser
else
# Validate and export existing preference
export fileBrowser
fi
# IRC client
if [[ -z "$ircClient" ]] || ! command -v "$ircClient" &> /dev/null; then
unset programList
programList=()
for i in albikirc Albikirc access-irc ; do
if command -v ${i/#-/} &> /dev/null ; then
if [ -n "$programList" ]; then
programList="$programList $i"
else
programList="$i"
fi
if command -v "${i/#-/}" &> /dev/null ; then
programList+=("$i")
fi
done
if [ "$programList" != "${programList// /}" ]; then
ircClient="$(menulist "IRC client:" $programList)"
if [[ ${#programList[@]} -gt 1 ]]; then
ircClient="$(menulist "IRC client:" "${programList[@]}")"
else
ircClient="${programList/#-/}"
ircClient="${programList[0]#-}"
fi
export ircClient="$(command -v $ircClient)"
ircClient="$(command -v "$ircClient")"
export ircClient
else
# Validate and export existing preference
export ircClient
@@ -742,7 +667,7 @@ if [[ -z "$dex" ]]; then
fi
fi
if [[ $dex -eq 0 ]]; then
dex -t "${XDG_CONFIG_HOME:-${HOME}/.config}/autostart" -c $(command -v $screenReader)
dex -t "${XDG_CONFIG_HOME:-${HOME}/.config}/autostart" -c "$(command -v "$screenReader")"
fi
if [[ -z "$batteryAlert" ]]; then
if command -v acpi &> /dev/null ; then
@@ -767,9 +692,65 @@ if [[ -z "$sounds" ]]; then
sounds=1
fi
fi
# Custom applications for ratpoison mode
if [[ ${#customApps[@]} -eq 0 ]]; then
addCustomApplication
if [[ -z "$screenlockPinHash" ]]; then
screenlockPinFile="${i3Path}/.screenpin"
if [[ -f "$screenlockPinFile" ]]; then
read -r screenlockPinHash < "$screenlockPinFile"
fi
fi
if [[ -z "$screenlockPinHash" ]]; then
if yesno "Do you want to enable the I38 screen lock (privacy screen only, not a secure system lock)?"; 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=""
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
fi
# Personal mode
personalModeExists=1
if personal_mode_exists; then
personalModeExists=0
personalModeEnabled=0
fi
if [[ $personalModeExists -ne 0 ]]; then
if yesno "Would you like a Personal mode?"; then
personalModeEnabled=0
else
personalModeEnabled=1
fi
if [[ "$personalModeEnabled" -eq 0 ]]; then
if [[ -z "$personalModeKey" ]] || [[ "$personalModeKey" == "$escapeKey" ]]; then
personalModeKey="$(select_personal_mode_key)"
fi
if [[ -z "$personalModeKey" ]]; then
personalModeEnabled=1
fi
fi
fi
# Save configuration if requested (only on first run)
@@ -788,8 +769,10 @@ write_waytray_config
mkdir -p "${i3Path}"
# Move scripts into place
cp -rv scripts/ "${i3Path}/" | dialog --backtitle "I38" --progressbox "Moving scripts into place and writing config..." -1 -1
apply_screenlock_pin
update_personal_customizations
cat << EOF > ${i3Path}/config
cat << EOF > "${i3Path}/config"
# Generated by I38 (${0##*/}) https://git.stormux.org/storm/I38
# $(date '+%A, %B %d, %Y at %I:%M%p')
EOF
@@ -797,13 +780,13 @@ EOF
# If we are using Sway, we need to load in the system configuration
# Usually, this is for system specific dBus things that the distro knows how to manage; we should trust their judgment with that
if [[ $usingSway ]] && [[ -d "${swaySystemIncludesPath}" ]]; then
cat << EOF >> ${i3Path}/config
cat << EOF >> "${i3Path}/config"
# Include your distribution Sway configuration files.
include ${swaySystemIncludesPath}/*
EOF
fi
cat << EOF >> ${i3Path}/config
cat << EOF >> "${i3Path}/config"
# i3 config file (v4)
#
# Please see https://i3wm.org/docs/userguide.html for a complete reference!
@@ -972,18 +955,29 @@ bindsym $mod+Shift+BackSpace mode "default"
EOF
if [[ -n "$screenlockPinHash" ]]; 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
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
echo "bindsym ${mod}+Shift+F5 exec spd-say -C" >> "${i3Path}/config"
# Multiple keyboard layouts if requested.
if [[ ${#kbd[@]} -gt 1 ]]; then
echo "bindsym Mod4+space exec ${i3Path}/scripts/keyboard.sh cycle ${kbd[*]}" >> ${i3Path}/config
echo "bindsym Mod4+space exec ${i3Path}/scripts/keyboard.sh cycle ${kbd[*]}" >> "${i3Path}/config"
fi
# Create panel mode
cat << EOF >> ${i3Path}/config
cat << EOF >> "${i3Path}/config"
# Panel mode configuration
bindsym Control+Mod1+Tab mode "panel"
mode "panel" {
@@ -1023,9 +1017,13 @@ fi)
# Detailed battery information bound to Shift+b
bindsym Shift+b exec --no-startup-id ${i3Path}/scripts/battery_status.sh --detailed, mode "default"
$(if command -v lxsession-logout &> /dev/null ; then
echo "# Power options bound to p"
echo "bindsym p exec --no-startup-id lxsession-logout, mode \"default\""
# Power options bound to p
bindsym p exec --no-startup-id ${i3Path}/scripts/power.sh, mode "default"
$(if [[ -n "$screenlockPinHash" ]]; then
echo " # Screen lock (privacy screen only)"
echo " bindsym Control+\$mod+l exec --no-startup-id ${i3Path}/scripts/screenlock.sh, mode \"default\""
echo " "
fi)
# Exit panel mode without any action
@@ -1036,7 +1034,7 @@ EOF
# Create ratpoison mode if requested.
if [[ -n "${escapeKey}" ]]; then
cat << EOF >> ${i3Path}/config
cat << EOF >> "${i3Path}/config"
# Enter ratpoison mode
bindsym $escapeKey mode "ratpoison"
mode "ratpoison" {
@@ -1107,24 +1105,14 @@ bindsym Shift+o exec $(command -v orca) --replace, mode "default"
bindsym Shift+t exec ${i3Path}/scripts/toggle_screenreader.sh, mode "default"
$(if [[ $usingSway -eq 0 ]]; then
echo "# reload the configuration file"
echo "bindsym Control+semicolon exec bash -c '$i3msg -t command reload && spd-say -P important -Cw "I38 Configuration reloaded."', mode "default""
echo "bindsym Control+semicolon exec bash -c '$i3msg -t command reload && spd-say -P important -Cw \"I38 Configuration reloaded.\"', mode \"default\""
else
echo "# reload the configuration file"
echo "bindsym Control+semicolon exec bash -c '$i3msg -t run_command reload && spd-say -P important -Cw "I38 Configuration reloaded."', mode "default""
echo "bindsym Control+semicolon exec bash -c '$i3msg -t run_command reload && spd-say -P important -Cw \"I38 Configuration reloaded.\"', mode \"default\""
echo "# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)"
echo "bindsym Control+Shift+semicolon exec $i3msg -t run_command restart && spd-say -P important -Cw "I3 restarted.", mode "default""
echo "bindsym Control+Shift+semicolon exec $i3msg -t run_command restart && spd-say -P important -Cw \"I3 restarted.\", mode \"default\""
fi)
# Custom applications
$(for app in "${customApps[@]}"; do
IFS='|' read -r appName appCommand appFlags appKey <<< "$app"
echo "# $appName bound to $appKey"
if [[ -n "$appFlags" ]]; then
echo "bindsym $appKey exec $appCommand $appFlags, mode \"default\""
else
echo "bindsym $appKey exec $appCommand, mode \"default\""
fi
done)
# Run dialog with exclamation
bindsym Shift+exclam exec ${i3Path}/scripts/run_dialog.sh, mode "default"
# exit i3 (logs you out of your X session)
@@ -1140,7 +1128,7 @@ EOF
fi
cat << EOF >> ${i3Path}/config
cat << EOF >> "${i3Path}/config"
# Auto start section
$(if [[ $sounds -eq 0 ]]; then
if [[ $usingSway -eq 0 ]]; then

65
scripts/i38-help-personal.sh Executable file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# This file is part of I38.
# I38 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
# I38 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with I38. If not, see <https://www.gnu.org/licenses/>.
configPath="$(readlink -f "$0")"
configPath="${configPath%/*/*}"
customizationsPath="${configPath}/customizations"
if [[ -f "${configPath}/config" ]]; then
mod="$(grep "set \$mod " "${configPath}/config" | cut -d ' ' -f3)"
mod="${mod//Mod1/Alt}"
mod="${mod//Mod4/Super}"
else
exit 1
fi
if [[ ! -f "$customizationsPath" ]] || ! grep -q '^mode "personal"' "$customizationsPath"; then
message="Personal mode bindings were not found. Check ${customizationsPath}."
echo -e "$message" | yad --text-info --show-cursor --title "I38 - Personal Mode Help" --button "Close:0" --listen
exit 0
fi
personalModeKey="$(grep -m1 -E '^bindsym[[:space:]]+.*mode "personal"' "$customizationsPath" | \
sed -e 's/^bindsym[[:space:]]*//' -e 's/[[:space:]]*mode "personal".*$//')"
if [[ -n "$personalModeKey" ]]; then
personalModeKey="${personalModeKey//\$mod/$mod}"
personalModeKey="${personalModeKey//Mod1/Alt}"
personalModeKey="${personalModeKey//Mod4/Super}"
fi
mapfile helpText < <(sed -n '/^mode "personal"/,/^}$/p' "$customizationsPath" | \
sed -e '/^mode "personal"/d' \
-e '/^}$/d' \
-e 's/bindsym/Key:/g' \
-e 's/Mod1/Alt/g' \
-e 's/, mode "default"//g' \
-e 's/--no-startup-id //g' \
-e "s/\$mod/$mod/g")
for i in "${!helpText[@]}" ; do
helpText[i]="${helpText[i]//${configPath}\/scripts\//}"
helpText[i]="${helpText[i]/.sh/}"
helpText[i]="${helpText[i]/.py/}"
done
header="Personal Mode Keybindings\n\n"
if [[ -n "$personalModeKey" ]]; then
header+="Press ${personalModeKey} to enter personal mode, then use these shortcuts:\n\n"
else
header+="Use these shortcuts while in personal mode:\n\n"
fi
helpText=("$header" "${helpText[@]}" "End of personal mode help. Press Control+Home to jump to the beginning.")
echo -e "${helpText[@]}" | yad --text-info --show-cursor --title "I38 - Personal Mode Help" --button "Close:0" --listen
exit 0

160
scripts/power.sh Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env bash
# This file is part of I38.
# I38 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
# I38 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with I38. If not, see <https://www.gnu.org/licenses/>.
if ! command -v yad &> /dev/null; then
echo "yad is required for power options."
exit 1
fi
action="$(yad --title "I38" --text "Power options" --list \
--column "Action" --column "Description" \
"Power off" "Shut down the system" \
"Reboot" "Restart the system" \
"Log out" "Exit the current session" \
--print-column=1 --separator="" --button "Select:0" --button "Cancel:1")"
yadResult=$?
if [[ $yadResult -ne 0 ]]; then
exit 0
fi
case "$action" in
"Power off")
powerAction="poweroff"
login1Method="PowerOff"
consolekitMethod="Stop"
;;
"Reboot")
powerAction="reboot"
login1Method="Reboot"
consolekitMethod="Restart"
;;
"Log out")
powerAction="logout"
;;
*)
exit 0
;;
esac
confirmAction() {
local promptText="$1"
yad --title "I38" --text "$promptText" --button "Yes:0" --button "No:1"
return $?
}
case "$powerAction" in
"poweroff")
confirmAction "Power off the system now?" || exit 0
;;
"reboot")
confirmAction "Reboot the system now?" || exit 0
;;
"logout")
confirmAction "Log out of the current session now?" || exit 0
;;
esac
try_logout() {
if [[ -n "${SWAYSOCK:-}" ]] && command -v swaymsg &> /dev/null; then
swaymsg -t command exit &> /dev/null
return $?
fi
if [[ -n "${I3SOCK:-}" ]] && command -v i3-msg &> /dev/null; then
i3-msg -t command exit &> /dev/null
return $?
fi
if command -v swaymsg &> /dev/null; then
swaymsg -t command exit &> /dev/null
return $?
fi
if command -v i3-msg &> /dev/null; then
i3-msg -t command exit &> /dev/null
return $?
fi
return 1
}
try_loginctl() {
command -v loginctl &> /dev/null || return 1
loginctl "$powerAction" --no-ask-password &> /dev/null
}
try_systemctl() {
command -v systemctl &> /dev/null || return 1
systemctl "$powerAction" --no-ask-password &> /dev/null
}
try_gdbus_login1() {
command -v gdbus &> /dev/null || return 1
gdbus call --system \
--dest org.freedesktop.login1 \
--object-path /org/freedesktop/login1 \
--method "org.freedesktop.login1.Manager.${login1Method}" false \
&> /dev/null
}
try_gdbus_consolekit() {
command -v gdbus &> /dev/null || return 1
gdbus call --system \
--dest org.freedesktop.ConsoleKit \
--object-path /org/freedesktop/ConsoleKit/Manager \
--method "org.freedesktop.ConsoleKit.Manager.${consolekitMethod}" \
&> /dev/null
}
try_dbus_send_login1() {
command -v dbus-send &> /dev/null || return 1
dbus-send --system --print-reply \
--dest=org.freedesktop.login1 \
/org/freedesktop/login1 \
"org.freedesktop.login1.Manager.${login1Method}" \
boolean:false \
&> /dev/null
}
try_dbus_send_consolekit() {
command -v dbus-send &> /dev/null || return 1
dbus-send --system --print-reply \
--dest=org.freedesktop.ConsoleKit \
/org/freedesktop/ConsoleKit/Manager \
"org.freedesktop.ConsoleKit.Manager.${consolekitMethod}" \
&> /dev/null
}
try_shutdown() {
command -v shutdown &> /dev/null || return 1
if [[ "$powerAction" == "poweroff" ]]; then
shutdown -h now &> /dev/null
else
shutdown -r now &> /dev/null
fi
}
try_direct() {
command -v "$powerAction" &> /dev/null || return 1
"$powerAction" &> /dev/null
}
if [[ "$powerAction" == "logout" ]]; then
if try_logout; then
exit 0
fi
else
if try_loginctl || try_systemctl || try_gdbus_login1 || try_dbus_send_login1 || \
try_gdbus_consolekit || try_dbus_send_consolekit || try_shutdown || try_direct; then
exit 0
fi
fi
yad --title "I38" --text "Power action failed. You may need permission or a polkit agent to continue." --button "Close:0"
exit 1

100
scripts/screenlock.sh Executable file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# This file is part of I38.
# I38 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
# I38 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with I38. If not, see <https://www.gnu.org/licenses/>.
screenlockPinHash=""
scriptPath="$(readlink -f "$0")"
scriptDir="${scriptPath%/*}"
i3Path="${scriptDir%/scripts}"
pinFile="${i3Path}/.screenpin"
if [[ -f "$pinFile" ]]; then
read -r screenlockPinHash < "$pinFile"
fi
if [[ -z "$screenlockPinHash" ]]; then
yad --title "I38" --text "Screen lock is not configured. Run i38.sh and set a 4-digit PIN to enable it." --button "Close:0"
exit 1
fi
if ! command -v yad &> /dev/null; then
exit 1
fi
if ! command -v jq &> /dev/null; then
yad --title "I38" --text "Screen lock requires jq to determine the current workspace." --button "Close:0"
exit 1
fi
if ! command -v sha512sum &> /dev/null; then
yad --title "I38" --text "Screen lock requires sha512sum to validate the PIN." --button "Close:0"
exit 1
fi
wmMsg="i3-msg"
if [[ -n "${SWAYSOCK:-}" ]] && command -v swaymsg &> /dev/null; then
wmMsg="swaymsg"
elif [[ -n "${I3SOCK:-}" ]] && command -v i3-msg &> /dev/null; then
wmMsg="i3-msg"
elif command -v swaymsg &> /dev/null; then
wmMsg="swaymsg"
elif command -v i3-msg &> /dev/null; then
wmMsg="i3-msg"
else
yad --title "I38" --text "No i3 or sway command interface was found for screen lock." --button "Close:0"
exit 1
fi
currentWorkspace="$($wmMsg -t get_workspaces | jq -r '.[] | select(.focused==true) | .name')"
lockWorkspace="i38-lock"
if $wmMsg -t get_workspaces | jq -e --arg name "$lockWorkspace" '.[] | select(.name==$name)' &> /dev/null; then
lockWorkspace="i38-lock-$$"
fi
$wmMsg -t command "workspace --no-auto-back-and-forth \"$lockWorkspace\"" &> /dev/null
$wmMsg -t command "mode screenlock" &> /dev/null
attemptCount=0
while : ; do
if [[ $attemptCount -eq 0 ]]; then
promptText="Screen lock is enabled. Enter your 4-digit PIN to unlock."
else
promptText="Incorrect PIN. Enter your 4-digit PIN to unlock."
fi
pinInput="$(yad --entry --hide-text --title "I38" --text "$promptText" --entry-label "Screen lock PIN" --button "Unlock:0" --on-top --sticky --skip-taskbar --fixed --center --undecorated --fullscreen --no-escape)"
yadResult=$?
if [[ $yadResult -ne 0 ]]; then
attemptCount=$((attemptCount + 1))
continue
fi
if [[ ! "$pinInput" =~ ^[0-9]{4}$ ]]; then
attemptCount=$((attemptCount + 1))
continue
fi
pinHash="$(printf "%s" "$pinInput" | sha512sum | awk '{print $1}')"
unset pinInput
if [[ "$pinHash" == "$screenlockPinHash" ]]; then
break
fi
attemptCount=$((attemptCount + 1))
done
$wmMsg -t command "mode default" &> /dev/null
if [[ -n "$currentWorkspace" ]]; then
$wmMsg -t command "workspace \"$currentWorkspace\"" &> /dev/null
fi
exit 0

View File

@@ -71,12 +71,16 @@ def on_mode(self,event):
mode = event.change
if mode == 'ratpoison':
play_sound_async('play -qV0 "|sox -np synth .07 sq 400" "|sox -np synth .5 sq 800" fade h 0 .5 .5 norm -20')
elif mode == 'personal':
play_sound_async('play -nqV0 synth pl E3 pl B3 remix - fade h 0 .25 .2 overdrive riaa norm -12')
elif mode == 'bypass':
play_sound_async('play -nqV0 synth .1 saw 700 saw 1200 delay 0 .04 remix - norm -6')
elif mode == 'default':
# Play different sounds based on which mode we're exiting
if currentMode == 'ratpoison':
play_sound_async('play -qV0 "|sox -np synth .07 sq 400" "|sox -np synth .5 sq 800" fade h 0 .5 .5 norm -20 reverse')
elif currentMode == 'personal':
play_sound_async('play -nqV0 synth pl E3 pl B3 remix - fade h 0 .25 .2 overdrive riaa norm -12 reverse')
elif currentMode == 'panel':
play_sound_async('play -nqV0 synth 0.05 pluck C5 norm -8 : synth 0.05 pluck F4 norm -8 : synth 0.05 pluck C4 norm -8 : synth 0.05 pluck F3 norm -8')
elif currentMode == 'bypass':