Initial low vision features added. xzoom needs to be installed for magnification. This first implementation is probably terrible, feedback welcome for improvement. Screen lock can now happen when I38 starts. As a reminder, it's not as secure as one of the system lockers, but it is screen reader accessible and should keep casual snoopers thwarted.

This commit is contained in:
Storm Dragon
2026-05-20 03:25:28 -04:00
parent 7ef49290ea
commit 1a2252d8de
5 changed files with 158 additions and 22 deletions
+22 -7
View File
@@ -1,10 +1,10 @@
# Welcome to I38 - Accessible i3 Window Manager
> **Note:** This help guide has been tailored to your specific configuration. You've chosen **@WEBBROWSER@** as your web browser, **@MODKEY@** as your mod key, and you're using the **@SCREENREADER@** screen reader.
> **Note:** This help guide has been tailored to your specific configuration. You've chosen **@WEBBROWSER@** as your web browser, **@MODKEY@** as your mod key, and your screen reader setting is **@SCREENREADER@**.
## Introduction to I38
I38 is a configuration for the i3 window manager that makes it more accessible for blind people. It features audio feedback, screen reader integration, and keyboard shortcuts designed for non-visual navigation.
I38 is a configuration for the i3 window manager that makes it more accessible for blind and low-vision people. It features audio feedback, optional screen reader integration, low-vision-friendly visual settings, and keyboard shortcuts designed for efficient navigation.
Unlike traditional desktop environments like GNOME or MATE, i3 is a tiling window manager, which means windows are arranged in a non-overlapping layout. This can be more efficient to navigate by keyboard, as windows are organized in a predictable structure.
@@ -67,9 +67,9 @@ Common Ratpoison mode commands:
| `k` | Kill (close) the current window |
| `F1` | Show ratpoison mode keybindings |
| `Escape` or `Control` + `g` | Exit Ratpoison mode without taking action |
| `Shift` + `c` | Restart Cthulhu screen reader |
| `Shift` + `o` | Restart Orca screen reader |
| `Shift` + `t` | Toggle screen reader |
| `Shift` + `c` | Restart Cthulhu screen reader, if installed |
| `Shift` + `o` | Restart Orca screen reader, if installed |
| `Shift` + `t` | Toggle screen reader, if one was configured |
| `Control` + `;` | Reload I38 configuration |
| `Control` + `q` | Exit i3 (log out) |
| `!` | Open run dialog |
@@ -200,10 +200,10 @@ waytray is an optional dependency. If installed, I38 will automatically configur
### Screen Reader
I38 is configured to work with your screen reader (@SCREENREADER@). The screen reader will provide spoken feedback about what's happening on screen so long as there is a window. If you don't have a window open and need to change something @SCREENREADER@ related, press Control+Alt+d to bring up the desktop, then screen reader keys should work.
If you configured a screen reader, I38 uses @SCREENREADER@. The screen reader will provide spoken feedback about what's happening on screen so long as there is a window. If you don't have a window open and need to change something screen-reader related, press Control+Alt+d to bring up the desktop, then screen reader keys should work.
- Toggle screen reader: `@RATPOISONKEY@` then `Shift` + `t`
- Restart screen reader: `@RATPOISONKEY@` then `Shift` + `o` (for Orca) or `Shift` + `c` (for Cthulhu)
- Restart screen reader: `@RATPOISONKEY@` then `Shift` + `o` (for Orca, if installed) or `Shift` + `c` (for Cthulhu, if installed)
- Interrupt speech: `@MODKEY@` + `Shift` + `F5`
*GNOME/MATE comparison:* GNOME uses Orca by default with its own keyboard shortcuts. I38 integrates screen readers more deeply with the window manager.
@@ -212,6 +212,17 @@ I38 is configured to work with your screen reader (@SCREENREADER@). The screen r
If you've enabled braille display support during setup, I38 will start XBrlAPI automatically to provide braille output from your screen reader.
### Low-Vision Support
During setup, I38 asks for the window title font size used by i3. The default is larger than standard i3 so window titles and tab labels are easier to see.
- I38 applies a larger mouse cursor size at startup when xrdb is available.
- Find the mouse pointer: `@MODKEY@` + `Control` + `m`
- Start xzoom magnifier, if installed when your i3 config was generated: `@MODKEY@` + `Control` + `z`
- Adjust screen brightness or enable the screen curtain, if xrandr is available: In Ratpoison mode, `Alt` + `s`
I38 keeps windows in a tabbed layout by default. This gives the focused app most of the screen and reduces visual clutter, but it also means switching windows is usually done with `Alt` + `Tab`, the window list, or workspace keys rather than by clicking overlapping windows.
### OCR (Optical Character Recognition)
If required dependencies are installed, you can use OCR to read text from images or inaccessible applications:
@@ -227,6 +238,8 @@ If you enabled the I38 privacy screen lock during setup, press `Control` + `Alt`
When a screen lock PIN is configured, I38 can also autolock after an idle timeout. The default is never. Autolock is X11/i3-only and requires `xprintidle`; if `playerctl` is installed, I38 will avoid autolocking during likely video playback.
If you chose to lock the screen when I38 starts, I38 launches the privacy screen shortly after i3 startup. Startup lock is meant to prevent casual snooping, not to secure the system against a determined user.
### Sound Effects
I38 provides audio feedback for many actions:
@@ -316,6 +329,8 @@ In Ratpoison mode, these are also available with Alt+Shift combinations:
- Check battery status: In Ratpoison mode, `Alt` + `b`
- Check game controller status: In Ratpoison mode, `g`
- Adjust screen brightness (if xrandr is available): In Ratpoison mode, `Alt` + `s`
- Find the mouse pointer: `@MODKEY@` + `Control` + `m`
- Start xzoom magnifier, if installed: `@MODKEY@` + `Control` + `z`
*GNOME/MATE comparison:* These functions are typically available through system menus or indicators in GNOME/MATE.
+3
View File
@@ -50,6 +50,7 @@ Optional features use these packages when installed:
- xclip: Clipboard support
- xfce4-notifyd: For sending notifications. Replaces notification-daemon
- xorg-setxkbmap: [optional] for multiple keyboard layouts
- xzoom: [optional] lightweight X11 magnifier bound by I38 when installed
### AI Assistant (Optional)
- python-requests: For Ollama integration
@@ -66,6 +67,8 @@ I38 will try to detect your browser, file manager, and text editor and present y
Ratpoison mode is now enabled by default for better accessibility and ease of use.
During setup, the screen reader picker includes Cthulhu, Orca, and None. If no supported screen reader is installed, I38 assumes None and skips screen-reader startup. Setup also asks for the generated i3 window-title font size, defaulting to 12. I38 applies a larger mouse cursor size at startup when xrdb is available. If xzoom is installed when the i3 config is generated, I38 binds it as a lightweight magnifier. I38 also includes a keybinding to move the mouse pointer to the center of the focused window.
## GTK Application Sound Themes (Optional)
This section is completely optional and separate from I38's window manager sounds. If you want GTK-based applications (like file managers, terminal emulators, text editors, etc.) to play sounds for their own events (button presses, menu navigation, dialog boxes, etc.), you can configure a GTK sound theme.
+76 -15
View File
@@ -324,6 +324,7 @@ kbd="${kbd[*]}"
# Volume settings
volumeJump="$volumeJump"
i3FontSize="${i3FontSize:-12}"
# Application paths
screenReader="$screenReader"
@@ -344,6 +345,7 @@ sounds="$sounds"
# Screen lock
screenlockPinHash="$screenlockPinHash"
screenlockAutolockSeconds="${screenlockAutolockSeconds:-0}"
screenlockOnStartup="${screenlockOnStartup:-1}"
# Personal mode
personalModeEnabled="${personalModeEnabled:-0}"
@@ -514,6 +516,15 @@ write_desktop_shortcuts_template() {
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
@@ -641,20 +652,32 @@ fi
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 [[ -z "$screenReader" ]] || ! command -v "$screenReader" &> /dev/null; then
if [[ -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
screenReader="$(command -v "$screenReader")"
if [[ "$screenReader" == "None" ]]; then
screenReader="none"
else
screenReader="$(command -v "$screenReader")"
fi
export screenReader
else
# Validate and export existing preference
@@ -803,7 +826,7 @@ if [[ -z "$dex" ]]; then
fi
fi
fi
if [[ $dex -eq 0 ]]; then
if [[ $dex -eq 0 ]] && [[ "$screenReader" != "none" ]] && [[ -n "$screenReader" ]]; then
dex -t "${XDG_CONFIG_HOME:-${HOME}/.config}/autostart" -c "$(command -v "$screenReader")"
fi
if [[ -z "$batteryAlert" ]]; then
@@ -815,7 +838,9 @@ if [[ -z "$batteryAlert" ]]; then
fi
fi
fi
if [[ -z "$brlapi" ]]; then
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
@@ -867,6 +892,14 @@ if [[ -z "$screenlockPinHash" ]]; then
fi
fi
if [[ -n "$screenlockPinHash" ]]; then
if [[ -z "${screenlockOnStartup+x}" ]]; then
configChanged=1
if yesno "Lock the screen automatically when I38 starts? This is useful for privacy, but if your screen reader or display setup fails you may need to enter the PIN 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
@@ -877,6 +910,7 @@ if [[ -n "$screenlockPinHash" ]]; then
fi
else
screenlockAutolockSeconds=0
screenlockOnStartup=1
fi
# Personal mode
personalModeExists=1
@@ -915,6 +949,7 @@ write_waytray_config
mkdir -p "${i3Path}"
write_i38_version
write_desktop_shortcuts_template
write_i38_xresources
# Move scripts into place
cp -rv scripts/ "${i3Path}/" | dialog --backtitle "I38" --progressbox "Moving scripts into place and writing config..." -1 -1
apply_screenlock_pin
@@ -943,9 +978,8 @@ workspace_layout tabbed
# 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. Will also be used by the bar unless a different font
# is used in the bar {} block below.
font pango:monospace 8
# Font for window titles and tab labels.
font pango:monospace ${i3FontSize}
# Window rules
# Fix Wine window issues - improves stability and prevents lockups
@@ -963,6 +997,15 @@ 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
@@ -1242,12 +1285,18 @@ bindsym a exec ${i3Path}/scripts/ai.py, mode "default"
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"
# Restart Cthulhu
bindsym Shift+c exec $(command -v cthulhu) --replace, mode "default"
# Restart Orca
bindsym Shift+o exec $(command -v orca) --replace, mode "default"
# Toggle screen reader
bindsym Shift+t exec ${i3Path}/scripts/toggle_screenreader.sh, mode "default"
$(if command -v cthulhu &> /dev/null; then
echo "# Restart Cthulhu"
echo "bindsym Shift+c exec $(command -v cthulhu) --replace, mode \"default\""
fi)
$(if command -v orca &> /dev/null; then
echo "# Restart Orca"
echo "bindsym Shift+o exec $(command -v orca) --replace, mode \"default\""
fi)
$(if [[ "$screenReader" != "none" ]] && [[ -n "$screenReader" ]]; then
echo "# Toggle screen reader"
echo "bindsym Shift+t exec ${i3Path}/scripts/toggle_screenreader.sh, mode \"default\""
fi)
# reload the configuration file
bindsym Control+semicolon exec bash -c '$i3msg -t run_command reload && spd-say -P important -Cw "I38 Configuration reloaded."', mode "default"
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
@@ -1272,6 +1321,9 @@ cat << EOF >> "${i3Path}/config"
$(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"
@@ -1303,6 +1355,9 @@ fi
if [[ -n "$screenlockPinHash" ]] && [[ "${screenlockAutolockSeconds:-0}" -gt 0 ]] && command -v xprintidle &> /dev/null; then
echo "exec_always --no-startup-id ${i3Path}/scripts/screenlock_autolock.sh ${screenlockAutolockSeconds}"
fi
if [[ -n "$screenlockPinHash" ]] && [[ "${screenlockOnStartup:-1}" -eq 0 ]]; then
echo "exec --no-startup-id bash -c 'sleep 2; ${i3Path}/scripts/screenlock.sh'"
fi
# WayTray system tray daemon
if command -v waytray-daemon &> /dev/null ; then
echo 'exec_always --no-startup-id bash -c "pgrep -x waytray-daemon > /dev/null || waytray-daemon"'
@@ -1319,7 +1374,9 @@ else
if command -v x11bell &> /dev/null; then
echo 'exec --no-startup-id x11bell play -nqV0 synth .1 sq norm -12'
fi
echo "exec $screenReader"
if [[ "$screenReader" != "none" ]] && [[ -n "$screenReader" ]]; then
echo "exec $screenReader"
fi
fi)
# First run help documentation
@@ -1367,7 +1424,11 @@ textEditor="${textEditor##*/}"
fileBrowser="${fileBrowser##*/}"
ircClient="${ircClient##*/}"
webBrowserHelp="${webBrowser:-no web browser was configured}"
screenReaderHelp="${screenReader:-no screen reader was configured}"
if [[ "$screenReader" == "none" ]] || [[ -z "$screenReader" ]]; then
screenReaderHelp="no screen reader was configured"
else
screenReaderHelp="$screenReader"
fi
textEditorHelp="${textEditor:-no text editor was configured}"
if [[ -n "$fileBrowser" ]]; then
fileBrowserHelp="I38 uses ${fileBrowser} for file management. Launch it in Ratpoison mode with the \`f\` key."
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Move the mouse pointer to the center of the focused window.
if ! command -v xdotool &> /dev/null; then
if command -v notify-send &> /dev/null; then
notify-send "I38" "xdotool is required to move the mouse pointer."
fi
exit 1
fi
activeWindow="$(xdotool getactivewindow 2> /dev/null || true)"
if [[ "$activeWindow" =~ ^[0-9]+$ ]]; then
while IFS='=' read -r key value; do
case "$key" in
X|Y|WIDTH|HEIGHT)
if [[ "$value" =~ ^-?[0-9]+$ ]]; then
printf -v "$key" '%s' "$value"
fi
;;
esac
done < <(xdotool getwindowgeometry --shell "$activeWindow" 2> /dev/null || true)
fi
if [[ "${X:-}" =~ ^-?[0-9]+$ ]] &&
[[ "${Y:-}" =~ ^-?[0-9]+$ ]] &&
[[ "${WIDTH:-}" =~ ^[0-9]+$ ]] &&
[[ "${HEIGHT:-}" =~ ^[0-9]+$ ]] &&
[[ "$WIDTH" -gt 0 ]] &&
[[ "$HEIGHT" -gt 0 ]]; then
pointerX=$((X + WIDTH / 2))
pointerY=$((Y + HEIGHT / 2))
else
read -r displayWidth displayHeight < <(xdotool getdisplaygeometry)
pointerX=$((displayWidth / 2))
pointerY=$((displayHeight / 2))
fi
xdotool mousemove "$pointerX" "$pointerY"
+18
View File
@@ -15,6 +15,24 @@ scriptPath="$(readlink -f "$0")"
scriptDir="${scriptPath%/*}"
i3Path="${scriptDir%/scripts}"
pinFile="${i3Path}/.screenpin"
runtimeDir="${XDG_RUNTIME_DIR:-/tmp}"
lockDir="${runtimeDir}/i38-screenlock"
if [[ -d "$lockDir" ]]; then
if [[ -f "${lockDir}/pid" ]]; then
read -r existingPid < "${lockDir}/pid"
if [[ "$existingPid" =~ ^[0-9]+$ ]] && kill -0 "$existingPid" 2> /dev/null; then
exit 0
fi
fi
rm -rf "$lockDir"
fi
if ! mkdir "$lockDir" 2> /dev/null; then
exit 0
fi
printf "%s\n" "$$" > "${lockDir}/pid"
trap 'rm -rf "$lockDir"' EXIT
if [[ -f "$pinFile" ]]; then
read -r screenlockPinHash < "$pinFile"