Cthulhu is no longer a fork. Now it is a screen reader written in Golang.

This commit is contained in:
Storm Dragon committed 2026-10-14 00:16:34 -04:00
1 parent a998fe67de
commit 069fc674dc
708 files changed
+112482 -532

No files matched your search

+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Firefox AT-SPI baseline on a private X server, bus, and browser profile."""
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import time
ROOT = Path(__file__).resolve().parents[1]
PAGE = Path(__file__).with_name("firefox-baseline.html")
def run_inside():
linked = os.environ.get("CTHULHU_FIREFOX_LINKED") == "1"
page = Path(__file__).with_name("browser-linked-headings.html") if linked else PAGE
browser = os.environ.get("CTHULHU_FIREFOX") or shutil.which("firefox")
if not browser:
raise RuntimeError("Firefox executable not found")
with tempfile.TemporaryDirectory(prefix="cthulhu-firefox-profile-") as profile:
Path(profile, "user.js").write_text(
'user_pref("datareporting.policy.firstRunURL", "");\n'
'user_pref("browser.startup.homepage_override.mstone", "ignore");\n',
encoding="utf-8",
)
with tempfile.TemporaryFile() as log:
environment = dict(os.environ, MOZ_ACCESSIBILITY_ENABLED="1")
process = subprocess.Popen(
[browser, "--no-remote", "--profile", profile, "--new-window", page.resolve().as_uri()],
env=environment, stdout=log, stderr=log,
)
try:
deadline = time.monotonic() + 25
while time.monotonic() < deadline:
if process.poll() is not None:
log.seek(0)
raise RuntimeError("Firefox exited before the probe: " + log.read().decode(errors="replace"))
result = subprocess.run(
["xdotool", "search", "--onlyvisible", "--name", "Cthulhu Linked Headings Probe" if linked else "Cthulhu Firefox Baseline"],
capture_output=True, text=True,
)
if result.stdout.strip():
window = result.stdout.splitlines()[0]
subprocess.run(["xdotool", "windowfocus", window], check=True)
break
time.sleep(0.1)
else:
raise RuntimeError("Firefox fixture window missing")
environment["CTHULHU_NATIVE_FIREFOX"] = "1"
environment.setdefault("GOCACHE", "/tmp/cthulhu-cache")
if linked:
environment["CTHULHU_NATIVE_LINKED_HEADINGS"] = "1"
subprocess.run(
["go", "test", "./internal/app", "-run", "^TestNativeLinkedHeadings$",
"-count=1", "-v", "-timeout=45s"],
cwd=ROOT, env=environment, check=True, timeout=50,
)
return
subprocess.run(
["go", "test", "./internal/atspi", "-run", "^TestNativeFirefoxBaseline$",
"-count=1", "-v", "-timeout=45s"],
cwd=ROOT, env=environment, check=True, timeout=55,
)
subprocess.run(
["go", "test", "./internal/app", "-run", "^TestNativeBrowserTabs$",
"-count=1", "-v", "-timeout=30s"],
cwd=ROOT, env=environment, check=True, timeout=35,
)
finally:
process.terminate()
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.wait(timeout=5)
def main():
if len(sys.argv) == 2 and sys.argv[1] == "--inside":
run_inside()
return
read_fd, write_fd = os.pipe()
server = subprocess.Popen(
["Xvfb", "-displayfd", str(write_fd), "-screen", "0", "800x600x24", "-nolisten", "tcp"],
pass_fds=(write_fd,), stderr=subprocess.DEVNULL,
)
os.close(write_fd)
try:
with os.fdopen(read_fd) as reader:
display = reader.readline().strip()
if not display:
raise RuntimeError("Xvfb failed to start")
environment = dict(os.environ, DISPLAY=":" + display, XDG_SESSION_TYPE="x11")
environment.pop("DBUS_SESSION_BUS_ADDRESS", None)
environment.pop("AT_SPI_BUS_ADDRESS", None)
environment.pop("WAYLAND_DISPLAY", None)
subprocess.run(
["dbus-run-session", "--", sys.executable, str(Path(__file__).resolve()), "--inside"],
env=environment, check=True, timeout=90,
)
finally:
server.terminate()
server.wait(timeout=3)
if __name__ == "__main__":
main()