Keep failed cursor trials and native recapture anchored to the original document, and restore the requested cached root before LRU eviction. Base braille retry resets on established connection time rather than stalled startup. Poll private AT-SPI daemon ownership without triggering service activation and clean up partial startup failures. Add focused regressions and update browser testing notes.
Cthulhu
Cthulhu is a Linux screen reader written in Go.
- Repository Guidelines: layout, commands, and working safeguards.
- Contributing: development, accessibility verification, licensing, and the AI-assisted contribution policy.
- LibreOffice support: integration boundaries, initial Calc behavior, remaining work, and verification.
- Progress reporting: web progress bars, Steam shader processing, and verification.
- Steam keyboard focus: Xorg workspace focus recovery and verification.
- XTerm handoff: terminal reader keys, lock state, speech interruption, and verification.
- Messaging support: Pidgin message review/copy shortcuts, application boundaries, and verification.
- Flatpak foreground accessibility: process identity and proxy matching, tests, and live acceptance.
- Background event filtering: early foreground checks, notification exceptions, and performance verification.
- MATE window switcher: Marco selection announcements, isolated checks, and live verification.
- MATE desktop: Orca comparison, panel menus and icon labels, regression checks, and live verification.
- Qt control states: menu checkability, native regressions, and the Qt 5 provider limitation.
- Audacious GUI: isolated Qt/GTK menu and playlist checks, row-focus fixes, and live verification.
- Tentacle updates: package recovery, version comparison, and update verification.
AI-assisted contributions are welcome. Contributors remain responsible for human review and testing before submission; see the full policy.
Start with the laptop keyboard layout
Run cthulhu -l to use the laptop layout with Caps Lock as the Cthulhu
modifier for this session. No number pad is needed. The selection stays
active when switching applications and does not change the saved profile.
Starting without -l uses the saved keyboard settings again.
Press Caps Lock+Space to open Preferences. Applying preferences replaces the
temporary selection; save the laptop layout there to make it permanent.
Use cthulhu -l -s to open Preferences at startup.
Login greeter settings
Copy your saved active profile into the LightDM account's default Cthulhu settings location, without starting or replacing a reader:
cthulhu --greeter lightdm
Run this as your normal user. Cthulhu requests sudo authentication to run the
copy as the lightdm account. It discovers that account's home directory and
writes .local/share/cthulhu/user-settings-go.toml there, creating directories
as needed. The destination belongs to that account and has mode 0600.
Cthulhu must be installed where the greeter account can execute it.
LightDM's default greeter account is lightdm; custom greeter accounts are
not currently supported.
Use cthulhu --greeter lightdm --config FILE to copy from an alternative
configuration. Your normal configuration remains unchanged. The copy contains
one complete active profile, including speech, Braille and keyboard preferences;
application profiles and Tentacles are not copied. Save preferences first:
unsaved changes are not included. Repeating the command updates the installed
settings. A missing source file selects built-in defaults, as at normal startup.
Configure the LightDM GTK greeter's reader command as
cthulhu --no-tentacles so it uses that account's default settings. Remove any
explicit --config path from an existing reader command to use the new location.
Changes take effect when the greeter reader next starts.
The greeter account needs its own accessibility bus and working audio setup. Selected voices, Braille tables and custom sound assets must also be installed and readable by that account. The command copies settings; it does not configure LightDM or audio, restart a reader, or create a restricted login mode.
Native X11 grab-window regression:
python3 tests/native_test_session.py env CTHULHU_NATIVE_GRAB_FOREGROUND=1 go test ./internal/platform/xorg -run '^TestNativeInputOnlyGrabForeground$' -count=1.
Speech preferences
Use Cthulhu+Left/Right to select a quick speech setting and Cthulhu+Up/Down to adjust it. These changes follow you across windows, including application profiles, for the current session. Cthulhu+Control+Shift+S saves the choices to the global profile. Switching windows does not save or discard them. Applying preferences replaces the temporary quick speech choices; restarting discards unsaved changes. Saved application overrides still apply on the next startup.
Speech preferences let you adjust rate, pitch and volume directly, without first disabling a default checkbox. Unconfigured values display Speech Dispatcher's defaults when available, or rate 50, pitch 5 and volume 5. Adjusting a slider creates an explicit setting; untouched values retain their inheritance, including automatic Uppercase pitch. Apply saves the changes; Cancel restores the last applied voice. See speech preferences verification.
Speech interruption
Incoming announcements queue in order. Focus, selection, state changes, notifications, tooltips and terminal output let earlier speech finish. Passive announcements also queue behind Say All. By default, a new key press or reader command stops earlier speech and clears its pending announcements. Delayed copies of the key that started a command preserve that command's response.
The Speech tab in Preferences offers All keys interrupt, Control, Enter, Space, and Escape, and Define interruption keys. The second choice includes both Control keys and the keypad Enter key. These choices apply immediately after Apply and are saved with the profile. Key echo remains independent; reader commands and explicit stop requests still interrupt speech in every mode.
For custom keys, select Define interruption keys, then activate Record interruption keys. Press the individual keys to record, including Tab, Enter, Space or Escape. Press and release a modifier separately to record it; left and right modifiers can be selected independently. Control+Enter finishes and Control+Escape cancels. Neither finishing nor cancelling adds its Control key to the list. There is no timer. Recording replaces the previous list when finished and requires at least one key. Duplicate keys are ignored. Focus returns to the recording button, and the read-only Defined interruption keys field displays the result. Tab and Shift+Tab work normally outside recording.
Screen lock, shutdown, speech configuration changes and explicit stop requests retain their lifecycle behavior. Existing Tentacle requests marked uninterruptible retain their bounded protection. The speech queue remains bounded against excessive output. See speech queue verification for automated checks and the listening procedure.
Automatic focus mode during structural navigation
In Preferences → General, enable Automatic focus mode during structural navigation to enter focus mode when a structural command reaches a form field or another widget that needs keyboard interaction. For example, press E to move to a text entry, then type directly. Ordinary headings and links keep browse mode. Like Orca, this option is off by default.
The option is available in global and application preferences. Apply saves it; application choices override the global value. Sticky browse mode still keeps browse mode, and sticky focus mode still keeps focus mode. Use Cthulhu+A to switch back to browse mode when finished interacting.
Emoji menu
On Xorg, press Cthulhu+Control+E in either keyboard layout to open the Emoji Menu. Choose a category with arrows, then Tab to the searchable grid. Type a name prefix, navigate with arrows, and press Enter to select.
Copy selected emoji to clipboard is unchecked by default. Unchecked types the emoji at the current input focus, including terminals and browser fields, without using the clipboard. Browse mode blocks typing. Checked copies the emoji without typing it. The choice is remembered for the current reader session. Escape cancels.
Key echo
The Key Echo preferences page offers three radio choices: Disable key echo,
Enable key echo, and Key echo in edit fields. The last choice limits typing
and key-name echo to writable edit controls. It uses the existing key categories
and custom echo voice settings, and Apply takes effect immediately. Existing
configurations keep their previous enabled or disabled behavior. The new setting
is edit-fields-only in the profile's key-echo table.
Enable Use custom echo voice to choose the echo synthesizer, voice, rate, pitch and volume. The synthesizer starts with the main speech synthesizer unless a separate echo synthesizer was previously configured. No additional synthesizer checkbox is needed. Under Use the custom voice for:, choose Key names (such as Enter, Shift and arrows), Typed characters, Words or Sentences. These choices select the voice; the corresponding echo settings still control what is spoken. Turn off custom echo voice to use the main speech settings.
Debug speech delivery records include custom_echo: true for the custom echo
path and false for ordinary speech. See key echo verification
for automated mode/voice tests, the native Enter regression and listening steps.
Key names finish before the resulting navigation or focus announcement starts. For example, Right speaks "right" in the selected key echo voice, followed by the character at the caret in the normal reading voice. A configured interruption key still interrupts the previous key's speech and discards its pending feedback.
Changing the custom echo synthesizer, person, rate, pitch or volume speaks the selected value using the echo voice and its draft settings, before Apply. Tab and Shift+Tab announce the field label in the main voice, followed by its current value in the echo voice. Other controls use the main voice. Apply saves the changes; Cancel restores the previous settings.
For the combined reader, GTK and recorded-speech regression check, build a test
binary and run CTHULHU_BINARY=/path/to/test-binary CTHULHU_PREFS_ECHO_RUNTIME_ONLY=1 /usr/bin/python3 tests/native_preferences.py.
This uses private Xvfb, D-Bus and SSIP services. Live listening still needs a
separate check: Tab and Shift+Tab across all five fields, change synthesizer
and person with arrows and their popups, and confirm labels use the main voice
while values finish in the selected echo voice.
Native controls must expose editable accessibility metadata. This includes standard entries, text editors and editable browser controls. Read-only fields, password contents, buttons, menus and terminals do not enable this mode. Controls that omit editable metadata stay quiet. Pasted text, terminal output containing multiple characters, live regions, navigation and deletion feedback retain their existing presentation rules; those announcements are distinct from key echo.
Wine Access supplies edit-control focus and typing feedback, including standard Edit controls and its supported Scintilla editors. Key-name echo also requires matching current X11 foreground metadata; without that evidence it stays quiet. Wine insertion feedback uses the custom key voice when character echo is off, and the custom character voice when character echo is on.
Capital letters
Speech preferences group three independent checkboxes under Capital letters: Raise pitch for capital letters (on by default), Beep for capital letters, and Say “cap” before capital letters (both off by default). Any combination, including all off, is supported. These settings apply to character navigation, spelling, and typing feedback, including a custom key echo voice. Word and line reading keep their normal presentation.
The beep matches NVDA's generated 2000 Hz, 50 ms capital-letter tone. It uses the Sounds volume and audio backend, independently of role sounds and themes. The pitch checkbox enables the existing Uppercase voice pitch. That voice uses automatic adjustment until an explicit pitch is set with its slider. Apply changes the running reader, and saved choices survive restart and can be set per application. Older capitalization styles are imported into the corresponding beep or “cap” checkbox.
Reading settings descriptions
Under Speech, the Reading settings table has three columns: Enabled, Setting and Description. Each description explains what the checkbox changes, with examples where useful. Use Left and Right to read individual cells and Space to toggle the selected setting from any column. With full-row reading enabled, moving between rows reads the state, title and description together. Further input interrupts speech as usual.
See reading settings verification for automated checks and live listening steps.
Flat review spelling and phonetic hints
Press Numpad 2 twice to hear the current character phonetically. Press Numpad 5 once to read the current word, twice to spell it, or three times to spell it phonetically. Numpad 8 provides the same three choices for the current line. Successive presses must be within 500 milliseconds. Single presses speak immediately. Laptop equivalents are Cthulhu+Comma, Cthulhu+K and Cthulhu+I.
Under Speech → Reading settings, Phonetic character hints is off by default. Enable it to hear a phonetic letter about 300 milliseconds after a character spoken by plain Left or Right, for example “a … alpha”. Further input cancels the pending hint. Selection, word navigation, Home/End and typing echo do not add hints. Spaces, punctuation and letters without a phonetic name keep their normal announcements. This applies to native text controls, browser character navigation and supported Wine editor character navigation. The delayed hint uses the normal voice without repeating capital beeps, pitch changes or “cap”. Explicit phonetic review commands retain capital indicators.
See phonetic review verification for checks and live listening steps.
Read the current line
Press Cthulhu+Shift+Up in either keyboard layout to read the line at the application caret in native AT-SPI controls, or at the browse cursor on a webpage in browse mode. Press twice within 500 milliseconds to spell the line, or three times for phonetics. The command leaves the caret and flat-review position in place. Empty lines say “blank”; password contents are never read. Controls without a text interface read their accessible name. Cthulhu+Up still increases the selected speech setting.
This follows NVDA's desktop NVDA+Up current-line command, rather than replaying the last spoken message. Numpad 8 (laptop: Cthulhu+I) continues to read the independent flat-review line. See current-line verification for checks.
Read the current window title
Press Cthulhu+Numpad Enter in the desktop keyboard layout, or Cthulhu+Slash in the laptop layout, to read the current window title. In a browser this normally includes the active page title. The command also flashes the title on Braille when flash messages are enabled. It works with automatic title announcements disabled and can repeat the same title without moving focus or the browser reading position.
These bindings match Orca's title command. Plain Numpad Enter on desktop and Cthulhu+Enter on laptop retain their Where Am I behavior. Repeated title commands read the title again; a double press does not read the status bar. See window title checks for verification.
Browser button updates
Web buttons announce their current label when focused. Automatic label changes wait for 350 milliseconds without another change before speaking, so a rapidly updating counter does not continually interrupt reading. Braille receives each update immediately. Moving focus, navigating, changing tabs or windows, and locking the session discard pending announcements. Live regions retain their existing announcement behavior.
Debug logging with --debug-file PATH replaces any existing log at that path.
Add --append to preserve earlier sessions, for example
cthulhu --debug-file /tmp/cthulhu.log --append.
--append requires --debug-file. A log can still grow during a long session.
For browser bug reports, start Cthulhu with --debug-file /tmp/cthulhu-browser.log
and reproduce the affected navigation. The startup record includes the release
version, embedded Git revision and modified-build status, Go and AT-SPI versions,
and session information. Builds without Go VCS stamps report unknown; a
modified build identifies its base commit, not the exact uncommitted changes.
Display availability does not establish whether the browser uses native Wayland
or XWayland. Include the browser version and its actual display backend separately.
Navigation records include focus and document identities, the cursor before and
after focus synchronization and movement, recovery decisions, capture attempts,
errors and structural-navigation wrapping. For example, preserved retains the
saved position, replaced-text-child recovers a replacement under its surviving
parent, and saved-object-missing falls back to native focus. These records help
distinguish a cursor reset from an intentional wrap without extra native queries.
Review debug logs before sharing: they also contain spoken page text.
When the browser confirms that a navigation target no longer exists, Cthulhu retires the cached object and refreshes its parent's child list. It retries caret placement only if the same target survives the refresh. Otherwise the move reports a location failure; repeat the navigation command to find a live target from the retained browser focus. An ordinary native caret refusal still allows a successful virtual reading move.
Wine Access
Install the separate wine-access helper for Wine application accessibility.
See Steam/Proton and controller-only verification
for loader support, container staging, hook exclusions, and live regression checks.
Cthulhu checks CTHULHU_WINE_ACCESS_HELPER, then
~/.local/libexec/wine-access/wine-access.exe,
/usr/local/libexec/wine-access/wine-access.exe,
/usr/libexec/wine-access/wine-access.exe, and wine-access.exe on PATH.
The existing cthulhu-wine-access.exe lookup remains supported: the old user
location follows the new user location, the old /usr location follows the new
system locations, and the old name on PATH follows the new name.
Restart Cthulhu after changing the selected helper. Keep the launcher, its
.exe.so sidecar, and the native hooks together in the installed directory.
With an updated Wine Access helper, the log records the running helper's
embedded release version, Git revision and modified-build flag, followed by
initialization status for its workers, hooks, controller and control poll.
The process-start message alone does not establish successful initialization.
Debug logs also identify the selected executable, Wine loader and prefix;
check those paths for an older local copy when machines behave differently.
Helpers built before identity reporting do not provide these identity/status
records. The installed helper's --version option can be used for a manual
check; it does not require a running reader.
cthulhu -v, cthulhu --version, and Cthulhu+Shift+V append
wine-access YYYY.MM.DD when the selected installed helper contains release
metadata. This reads its .exe.so sidecar directly without starting Wine.
Rebuild Wine Access to supply the metadata; older helpers are omitted.
The reported release describes the installed files, which can differ from a
helper still running after an upgrade.
Helper-prefixed stderr diagnostics are captured with a 4096-byte line limit, a 64-record queue and up to 32 distinct records per ten-second window. Repeated, oversized and excess records are counted in a suppression notice. Unrelated Wine/application stderr is ignored. Capture does not log accessible control contents or change speech delivery; output already present in debug speech logs can still contain private text. Review debug logs before sharing them.
The standalone helper currently installs under the compatible name
~/.local/libexec/cthulhu/wine/cthulhu-wine-access.exe. Its complete installation
contains the launcher, cthulhu-wine-access.exe.so, wine-access-menu.dll,
wine-access-menu32.dll, and wine-access-menu-host32.exe. The 32-bit host
supplies native menu and editor feedback for applications such as MUSHclient.
Older Python Cthulhu builds can overwrite the helper launcher and sidecar in
that same directory while leaving the standalone hook files behind. The old
helper can still run and connect to Cthulhu without providing the standalone
menu and editor hooks. If Wine reading disappears after installing an older
Cthulhu build, rebuild and reinstall the separate wine-access project, then
reload Cthulhu from the same desktop session. If a Wine application already
loaded a previous hook DLL, save its work and restart it to load the replacement.
See local installation and reload
for the Go reader commands.
Wine typing feedback in standard Edit controls follows the current key echo settings, including the master switch, character echo, and alphabetic, numeric, punctuation, space and diacritical categories. Tabs and line breaks follow key echo's action-key setting. Applying preferences takes effect without restarting the helper. Navigation, selection and deletion feedback remain available when typing echo is disabled. Word and sentence typing echo are not currently supplied by the Wine helper.
For a live check, type repeated letters, numbers, punctuation and spaces in Wine Notepad, change the corresponding echo settings and apply them, then continue typing. Disable key echo and verify that typing stays quiet while arrows, selection and Backspace still report editing feedback. Re-enable it and check capital/lowercase voices and password exclusion. Automated tests cover queued speech and Braille; audible speech and a physical Braille display still require live verification.
Tentacle process supervision
Installed Tentacles run as the current user and require no administrator setup or particular init system. Each session has a private Linux subreaper helper. The helper watches a reader-owned lifetime pipe and terminates and reaps its child and detached descendants when the session ends, including abrupt reader exit. Exited orphaned descendants are also reaped while the Tentacle remains active. Installation and enabling report success only after registration, keyboard shortcut setup and activation succeed. A failed start leaves the installed package disabled, with an error in Preferences; enabling it again retries. Enabling an already enabled Tentacle restarts it if its session has failed. Cancelling startup during shortcut setup restores the other Tentacles' shortcuts.
Only install Tentacles from publishers you trust. They inherit your account's
file and network access. Protocol sizes, request deadlines and host admission
limits always apply. CPU, memory and task limits require an explicitly configured,
writable cgroup v2 parent with the cpu, memory and pids controllers enabled.
Set CTHULHU_TENTACLE_CGROUP_PARENT or pass
--tentacle-cgroup-parent /absolute/delegated/parent to opt in. Provisioning that
parent is an administrator or session-launcher responsibility, independent of
any init system. An invalid explicit parent fails startup rather than silently
ignoring the requested limits. Without that configuration, supervision works
without kernel resource quotas; it does not provide a security sandbox.
Browser navigation
Browser element lists use Alt+Shift plus the structural navigation key: K for links, A for clickables, H for headings, and 1–6 for headings at a specific level. Use arrows or type a name to select an item, Enter to move to it, and Escape to cancel. See browser element lists for every list shortcut and verification steps.
Heading and browse-caret commands retain the virtual reading position while the browser reports delayed native caret events. Each reader command takes ownership before its result arrives. Native keyboard input, text selection, pointer presses, document loads and leaving the document release that ownership. An unsuccessful native caret placement does not undo a successful virtual move.
When a page replaces a plain text child in a paragraph, heading, section or
ordinary link, navigation can retain its offset through the same surviving
parent and document generation. With multiple children, the replacement must
remain between the same generation-checked adjacent siblings (or the same
start/end boundary).
This prevents repeatedly restarting a paragraph when an inline text object is
recreated. No text-label matching is used to guess a replacement.
This includes CSS-generated link prefixes such as ::before, which browsers
can recreate when placing the native caret. Explicitly named links retain their
separate label-navigation behavior. Recovery never transfers an activation to
a replacement link.
Read All uses navigation recovery too, and stops if the document generation
changes. Text that disappears during a native read requires a refresh rather
than silently losing that part of the line.
When a larger subtree disappears, navigation can restart within its nearest
surviving noninteractive container. Ambiguous children, editable controls and
replaced parents do not inherit the old offset. Removing a native focus anchor
does not reset a still-valid virtual reading position elsewhere in that document.
Verify repeated native replacements, forward/reverse lines and subsequent
Read All with
CTHULHU_NATIVE_BROWSER_TESTS=replacement,generatedlinks python3 tests/native_browser.py.
Explicitly named links retain their complete accessible label when reading by line. Left/Right, Ctrl+Left/Right, and Home/End review that label by character, word, or boundary, including links with no native text or only a numeric badge. The label's review position remains separate from the browser's text caret.
Browse arrows, word navigation and Read All skip non-focusable, non-editable
tooltips and their contents. This avoids repeatedly entering a tooltip that
disappears when native caret placement moves focus away from its owner.
Focusable or editable tooltip contents remain navigable. Verify the native
focus-triggered case with
CTHULHU_NATIVE_BROWSER_TESTS=tooltips python3 tests/native_browser.py.
Enter activates the current link or clickable, including explicitly named custom controls and clickable ancestors of ordinary text. Activation itself is silent whether it succeeds or fails; the page's own focus changes and live regions still speak. Ordinary text does not cause an unnecessary document recapture. A removed target or replaced document cannot silently substitute a different action.
Successful virtual navigation presents the requested content even if the browser refuses native caret placement; synchronization failures remain in diagnostic logs. Stale native placement gets one refresh and retry at the same target and offset. Braille routing also recognizes the temporary caret produced by clearing a selection, so it cannot reset the routed reading position.
Reading a final ellipsis avoids the provider's native line-boundary request:
Chromium can calculate a boundary past truncated text and abort the browser.
At that character, Cthulhu reads a bounded logical line using explicit line
breaks; visual wrapping may be combined. Ordinary line, character and word
navigation retain their existing boundaries. The regression checks are
go test ./internal/atspi -run TestTextBoundaryAvoidsProviderEllipsisAbort
and CTHULHU_NATIVE_BROWSER_TESTS=lines python3 tests/native_browser.py.
Browse-mode arrows and Read All include text inside embedded editors. Reading continues through the editor's paragraphs before reaching the following page content.
Native menu rows can contain several controls. Up and Down announce those
controls individually, including Brave's Sidebar choices and Zoom controls.
Check this workflow with CTHULHU_NATIVE_BROWSER_TESTS=menu python3 tests/native_browser.py.
See browser release review for findings, automated checks and remaining live acceptance.
The generic changing-document regression starts a private reader, browser profile, X display, accessibility bus and speech protocol recorder:
go build -o /tmp/cthulhu-browser-review ./cmd/cthulhu
CTHULHU_BINARY=/tmp/cthulhu-browser-review python3 tests/native_browser_navigation.py
CTHULHU_NATIVE_BROWSER_TESTS=pointer,positions,dynamic,linkedheadings,document,editing,selection,tabs,modes python3 tests/native_browser.py
These checks leave the desktop reader unchanged. They verify synthetic X11 input and recorded speech requests; real-page listening, physical keyboard and Braille checks remain necessary. On an updating page, traverse headings in both directions, pause and continue, read changing lines with arrows and word commands, then verify Tab, selection, editable fields and browser chrome.