From f18b6955a99e84b64bbefc601ddd74f72b585ff0 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sat, 7 Nov 2026 15:56:58 -0500 Subject: [PATCH] Hand over reading of i3 stuff to cthulhu when detected. --- I38.md | 6 ++++ README.md | 32 ++++++++++++++++++ i38.sh | 20 +++++------ scripts/announce_i3.py | 63 +++++++++++++++++++++++++++++++++++ scripts/announce_workspace.sh | 6 ++-- 5 files changed, 114 insertions(+), 13 deletions(-) create mode 100755 scripts/announce_i3.py diff --git a/I38.md b/I38.md index 893413f..32bf342 100644 --- a/I38.md +++ b/I38.md @@ -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.
`Control` + `Shift` + `F1` through `F10`: Move the focused window to workspace 1 through 10.
+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. diff --git a/README.md b/README.md index 30351ad..699129a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/i38.sh b/i38.sh index 3262ba4..067a39b 100755 --- a/i38.sh +++ b/i38.sh @@ -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. diff --git a/scripts/announce_i3.py b/scripts/announce_i3.py new file mode 100755 index 0000000..d9524df --- /dev/null +++ b/scripts/announce_i3.py @@ -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()) diff --git a/scripts/announce_workspace.sh b/scripts/announce_workspace.sh index 421ade9..18e0993 100755 --- a/scripts/announce_workspace.sh +++ b/scripts/announce_workspace.sh @@ -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}"