Skip to content

Add optional screen-reader accessibility (speech) support#391

Open
dannyboy1996 wants to merge 1 commit into
X16Community:masterfrom
dannyboy1996:master
Open

Add optional screen-reader accessibility (speech) support#391
dannyboy1996 wants to merge 1 commit into
X16Community:masterfrom
dannyboy1996:master

Conversation

@dannyboy1996
Copy link
Copy Markdown

Title:
Add optional screen-reader accessibility (speech) support


Summary

This adds optional speech output for blind / low-vision users on Windows. When a
screen reader (NVDA, JAWS, System Access, SuperNova, Window-Eyes, ZoomText) is
running, the emulator detects it at startup and speaks the X16's text-screen
output. When no screen reader is present, the emulator behaves exactly as before
(silent) -- there is no change for sighted users and no robot-voice/SAPI
fallback.

The feature is self-contained: it lives almost entirely in a new src/speech.c /
src/speech.h module, with small taps in main.c and video.c. It compiles and runs
unchanged on all platforms; on non-Windows builds the speech module is a set of
no-op stubs.


What it does

  • Output speech: characters sent to KERNAL CHROUT ($FFD2) are collected and
    spoken as a block once output goes briefly quiet (~150 ms). This covers the
    BASIC prompt, LIST, PRINT, and other text-mode output, and avoids individual
    lines interrupting one another.

  • Key echo: each typed key is spoken as it is pressed.

  • Edit navigation: Left/Right arrows speak the character moved onto, and
    Backspace speaks the character deleted. These are read from the VERA text
    layer at the KERNAL cursor position (RAM $0380/$0383), so they reflect what is
    actually on screen.

  • History review: every printed line is kept in a scrollback ring. Ctrl+Up /
    Ctrl+Down step through it line by line; Ctrl+Home / Ctrl+End jump to the
    oldest / newest line; Ctrl+Shift+Up repeats the current line. These keys are
    consumed on the host and never reach the X16.


How speech is produced

Speech goes through the Tolk library (https://github.com/dkager/tolk, LGPLv3),
which abstracts the various Windows screen readers. Tolk.dll is loaded
DYNAMICALLY at runtime:

  • If Tolk.dll is not present, or no screen reader is running, speech simply
    stays off and the emulator runs normally. There is no build-time or link-time
    dependency on Tolk.
  • SAPI auto-detection is intentionally NOT enabled, so the emulator only speaks
    when a real screen reader is active.

Command-line options

-nospeech            Skip screen-reader detection entirely (force silent).
-speech-interrupt    Make new output interrupt current speech instead of
                     queueing it.

(Speech requires no flag to enable -- it is automatic when a reader is detected.)


Files changed

New:       src/speech.h
           src/speech.c
Modified:  src/main.c       (include; CHROUT tap; options; init/tick/shutdown; usage)
           src/video.c      (key echo; arrow/Backspace char reading; Ctrl+arrow review)
           CMakeLists.txt   (add src/speech.c to the build)

+103 lines in existing files, plus the new module.

Open question for maintainers

The source has no hard dependency on Tolk, but for the feature to work for end
users, Tolk.dll (and its nvdaControllerClient64.dll helper) must sit next to the
executable. Options I'm happy to implement based on your preference:

  1. Document that users drop Tolk.dll in themselves (no build changes).
  2. Add a CMake/CI step to fetch or build Tolk and stage the DLLs in the
    Windows build output.

Tolk is LGPLv3, so redistribution alongside the emulator is fine with
attribution.


Testing

Built with MinGW-w64 (GCC 15.2) + SDL2 2.32 + zlib 1.3.1 on Windows 11.
Verified with NVDA running:

  • Boot banner and READY. prompt are spoken.
  • PRINT "HI" speaks the HI result.
  • Typing echoes keys; Left/Right read the character moved onto; Backspace
    reads the deleted character.
  • Ctrl+Up/Down/Home/End review the scrollback.
  • With no screen reader running, behaviour is unchanged (silent).
  • -nospeech forces silence; -speech-interrupt changes queueing behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant