Hand over reading of i3 stuff to cthulhu when detected.

This commit is contained in:
Storm Dragon committed 2026-11-07 15:56:58 -05:00
1 parent c4e572b857
commit f18b6955a9
5 files changed
+114 -13

No files matched your search

+6
View File
@@ -44,6 +44,12 @@ Workspaces are separate work areas for organizing applications. I38 provides 10
`Control` + `F1` through `F10`: Switch to workspace 1 through 10.<br>
`Control` + `Shift` + `F1` through `F10`: Move the focused window to workspace 1 through 10.<br>
When Cthulhu is running on this X display, I38 leaves workspace and window-move
speech to Cthulhu. Keep its Reading options for i3 workspaces and workspace
moves enabled. Switching to Orca restores I38's announcements automatically;
workspace sounds and other I38-specific speech continue as configured. Mode
sounds also remain available alongside Cthulhu's native mode announcements.
### Window Management
I38 uses a tabbed layout by default. In this layout, the focused window takes the screen, and you switch between windows much like switching between tabs.
+32
View File
@@ -78,6 +78,38 @@ During setup, the screen reader picker includes Cthulhu, Orca, and None. If no s
During setup, I38 can configure one screen lock method. The legacy I38 PIN lock is a privacy screen only. I38Lock is a true PAM-backed screen locker, available at https://git.stormux.org/storm/i38lock and packaged in the Stormux repository. I38 offers I38Lock only when `i38lock` is installed and the screen-reader setup can support it: Cthulhu, or Orca 50 or newer. I38 does not enable the PIN lock and I38Lock at the same time.
## Screen reader announcements
I38 leaves workspace-switch and window-workspace-move speech to Cthulhu when
its native session-bus service is running on the current X display. Detection
runs for each announcement, so switching to Orca or stopping Cthulhu restores
I38's speech immediately. Installation or saved screen-reader preferences alone
do not suppress speech; an unavailable bus or unreadable process also leaves
I38 speech enabled.
Workspace and mode sounds remain enabled as configured. I38's mode feedback is
sound only; Cthulhu supplies its own mode speech. Battery, USB, OCR, brightness,
keyboard-layout, and other I38-specific announcements continue as before.
Keep Cthulhu's Reading choices “Announce i3 workspaces” and “Announce i3 window
workspace moves” enabled for its replacement speech. Turning those choices off
leaves those events silent while Cthulhu is active.
For an existing installation, run `./i38.sh -u` to copy the updated workspace
script and shared helper. Regenerate the I38 configuration and run
`i3-msg reload` to update window-move bindings too; copying scripts alone does
not change those bindings. Custom bindings which invoke `spd-say` directly need
to use `scripts/announce_i3.py` for overlapping i3 speech.
Run `python3 -B -m unittest discover -s tests -v` for automated checks. After
reviewing the diff and updating the installation, use Control+F1/F2 to switch
workspaces and Control+Shift+F1/F2 to move an expendable window. With Cthulhu,
expect its native speech once per real change and the usual I38 sounds. Switch
to Orca and repeat: expect I38 workspace and move speech. Switch back without
regenerating and confirm duplicate speech stops. Enter/leave Ratpoison, Panel,
Personal (if configured), and Bypass modes; check existing mode sounds and
Cthulhu's native mode speech. Also check battery status, OCR, and USB alerts if
enabled. Automated checks do not prove audible output or physical key behavior.
## USB Connection Alerts (Optional)
Setup offers USB connection sounds and spoken device names separately from window
+10 -10
View File
@@ -1360,25 +1360,25 @@ bindsym Control+F10 workspace number \$ws10, exec --no-startup-id ${i3Path}/scri
# 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"
bindsym Control+Shift+F1 move container to workspace number \$ws1, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 1"
# Move window to workspace 2
bindsym Control+Shift+F2 move container to workspace number \$ws2, exec spd-say -P important -Cw "moved to workspace 2"
bindsym Control+Shift+F2 move container to workspace number \$ws2, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 2"
# Move window to workspace 3
bindsym Control+Shift+F3 move container to workspace number \$ws3, exec spd-say -P important -Cw "moved to workspace 3"
bindsym Control+Shift+F3 move container to workspace number \$ws3, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 3"
# Move window to workspace 4
bindsym Control+Shift+F4 move container to workspace number \$ws4, exec spd-say -P important -Cw "moved to workspace 4"
bindsym Control+Shift+F4 move container to workspace number \$ws4, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 4"
# Move window to workspace 5
bindsym Control+Shift+F5 move container to workspace number \$ws5, exec spd-say -P important -Cw "moved to workspace 5"
bindsym Control+Shift+F5 move container to workspace number \$ws5, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 5"
# Move window to workspace 6
bindsym Control+Shift+F6 move container to workspace number \$ws6, exec spd-say -P important -Cw "moved to workspace 6"
bindsym Control+Shift+F6 move container to workspace number \$ws6, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 6"
# Move window to workspace 7
bindsym Control+Shift+F7 move container to workspace number \$ws7, exec spd-say -P important -Cw "moved to workspace 7"
bindsym Control+Shift+F7 move container to workspace number \$ws7, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 7"
# Move window to workspace 8
bindsym Control+Shift+F8 move container to workspace number \$ws8, exec spd-say -P important -Cw "moved to workspace 8"
bindsym Control+Shift+F8 move container to workspace number \$ws8, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 8"
# Move window to workspace 9
bindsym Control+Shift+F9 move container to workspace number \$ws9, exec spd-say -P important -Cw "moved to workspace 9"
bindsym Control+Shift+F9 move container to workspace number \$ws9, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 9"
# Move window to workspace 10
bindsym Control+Shift+F10 move container to workspace number \$ws10, exec spd-say -P important -Cw "moved to workspace 10"
bindsym Control+Shift+F10 move container to workspace number \$ws10, exec --no-startup-id ${i3Path}/scripts/announce_i3.py "moved to workspace 10"
# A mode that will pass all keys except $mod+shift+backspace to the current application.
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later
"""Speak i3 feedback only when Cthulhu is not handling it on this display."""
import os
from pathlib import Path
import subprocess
import sys
from gi.repository import Gio, GLib
def display_screen(display):
"""An omitted X screen means screen zero; other screens have their own root."""
host, separator, number = display.rpartition(':')
if '.' not in number:
number += '.0'
return host + separator + number
def cthulhu_is_active():
display = os.environ.get('DISPLAY', '')
if not display:
return False
try:
connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
# Query the bus daemon, never activate a screen reader or call speech.
owner = connection.call_sync(
'org.freedesktop.DBus', '/org/freedesktop/DBus',
'org.freedesktop.DBus', 'GetNameOwner',
GLib.Variant('(s)', ('org.stormux.Cthulhu1.Service',)),
GLib.VariantType.new('(s)'), Gio.DBusCallFlags.NONE, 500, None
).unpack()[0]
processId = connection.call_sync(
'org.freedesktop.DBus', '/org/freedesktop/DBus',
'org.freedesktop.DBus', 'GetConnectionUnixProcessID',
GLib.Variant('(s)', (owner,)), GLib.VariantType.new('(u)'),
Gio.DBusCallFlags.NONE, 500, None
).unpack()[0]
processPath = Path('/proc') / str(processId)
if processPath.stat().st_uid != os.getuid():
return False
for entry in (processPath / 'environ').read_bytes().split(b'\0'):
if entry.startswith(b'DISPLAY='):
readerDisplay = os.fsdecode(entry[len(b'DISPLAY='):])
return display_screen(readerDisplay) == display_screen(display)
except (GLib.Error, OSError):
# Uncertain detection must preserve announcements for other readers.
pass
return False
def main():
if len(sys.argv) < 2:
return 0
if cthulhu_is_active():
return 0
return subprocess.call(['spd-say', '-P', 'important', '-Cw', '--',
' '.join(sys.argv[1:])])
if __name__ == '__main__':
sys.exit(main())
+3 -3
View File
@@ -12,7 +12,7 @@
path="$(readlink -f "$0")"
path="${path%/*/*}"
path="${path%/*}"
workSpace="$(i3-msg -t get_workspaces \
| jq '.[] | select(.focused==true).name' \
| cut -d"\"" -f2)"
@@ -21,7 +21,7 @@ right=0
msg="Workspace ${workSpace}"
if [[ "${workSpace}" == "11" ]]; then
play -qnV0 synth 1.5 pl A4 pl E5 pl C5 delay 0.0 0.1 0.2 remix - fade p 0 1.5 .5
spd-say -P important -Cw "I38 panel"
python3 "${path}/announce_i3.py" "I38 panel"
exit 0
fi
if ! [[ "${workSpace}" =~ ^[0-9]+$ ]]; then
@@ -40,4 +40,4 @@ else
fi
fi
play -nqV0 synth pi fade 0 .25 .15 pad 0 1 reverb overdrive riaa norm -8 speed 1 remix v0.${left} v0.${right} &
spd-say -P important -Cw "${msg}"
python3 "${path}/announce_i3.py" "${msg}"