diff --git a/bugs b/bugs deleted file mode 100644 index e142b533..00000000 --- a/bugs +++ /dev/null @@ -1,15 +0,0 @@ -Please report bugs and feature requests to: -https://git.stormux.org/storm/fenrir/issues - -For bugs, please provide a debug file that shows the issue. -How to create a debug file: -1. start fenrir in debug mode -sudo fenrir -d - -2. -stop fenrir (fenrirKey + q) -the debug file is in /tmp/fenrir.log -if another Fenrir debug instance is already using it, check /tmp/fenrir2.log, -/tmp/fenrir3.log, etc. - -please be as precise as possible to make it easy to solve the problem. diff --git a/config/settings/settings.toml b/config/settings/settings.toml index 2ef413ed..0687036d 100644 --- a/config/settings/settings.toml +++ b/config/settings/settings.toml @@ -75,7 +75,8 @@ punctuation_profile = "default" punctuation_level = "some" # Whole whitespace-delimited emoticons, matching Python's names/boundaries. emoticons = true -# Optional summaries for whole repeated-symbol tokens, e.g. ##### -> 5 number. +# Optional summaries for repeated-symbol tokens, e.g. ##### -> 5 number. +# Matching brackets around a run are allowed, e.g. [#####]. # Only symbols named at the selected punctuation level are summarized. repeated_symbols = false # Minimum run length, 2–10000; default 4 means more than three repeats. diff --git a/docs/Howto Configure Pulse Systemwide.txt b/docs/Howto Configure Pulse Systemwide.txt deleted file mode 100644 index 492aca15..00000000 --- a/docs/Howto Configure Pulse Systemwide.txt +++ /dev/null @@ -1,28 +0,0 @@ -Fenrir console audio - -Fenrir's pulse sound driver uses PulseAudio or PipeWire's PulseAudio service. -The alsa driver opens the configured ALSA PCM device directly. Speech Dispatcher -has its own synthesis and audio configuration, separate from Fenrir's sound cues. - -For an X11 terminal session, start Fenrir as the regular user with ./fenrir -x. -For console mode started through sudo, Fenrir uses SUDO_UID to find the invoking -user's PulseAudio and Speech Dispatcher sockets. An explicit PULSE_SERVER takes -precedence for sound cues; SPEECHD_ADDRESS with a unix_socket: address takes -precedence for Speech Dispatcher. The relevant services must already be running -and accessible to the reader process. - -If root sound still needs separate configuration, the checkout includes - tools/configure_pulse.sh for PulseAudio and - tools/configure_pipewire.sh for PipeWire. -Read the appropriate script before running it. Each has a regular-user part -and a root part, and writes audio configuration files. The PulseAudio script -replaces the user's default.pa and configures an anonymously accessible socket -at /tmp/pulse.sock. The PipeWire script also changes WirePlumber settings. -These are system audio changes, not prerequisites for every Fenrir session. - -For ALSA, select driver = "alsa" under [presentation.sound] in settings.toml -and set device to the desired PCM name, such as "default". Driver and device -changes apply at the next launch. - -See README.md for build and startup instructions and docs/user.md for the Sound -settings and troubleshooting sections. diff --git a/docs/create_manpage.sh b/docs/create_manpage.sh index 13d3620a..6a0d3d01 100755 --- a/docs/create_manpage.sh +++ b/docs/create_manpage.sh @@ -14,15 +14,13 @@ trap 'rm -rf -- "$tempDir"' EXIT # Generate every format successfully before replacing any existing output. pandoc "$docsDir/user.md" --from markdown --to plain --wrap=auto \ | sed 's/[[:blank:]]*$//' > "$tempDir/user.txt" -pandoc "$docsDir/user.md" --from markdown --to asciidoc --wrap=auto \ - --output "$tempDir/fenrir.adoc" pandoc "$docsDir/user.md" --from markdown --to man --standalone \ --lua-filter "$docsDir/man_tables.lua" \ --metadata title=fenrir --metadata section=1 \ --metadata header="Fenrir User Manual" \ --output "$tempDir/fenrir.1" -for manualFile in user.txt fenrir.adoc fenrir.1; do +for manualFile in user.txt fenrir.1; do mv -- "$tempDir/$manualFile" "$docsDir/$manualFile" done diff --git a/docs/fenrir.1 b/docs/fenrir.1 index 1231ce91..31df56fd 100644 --- a/docs/fenrir.1 +++ b/docs/fenrir.1 @@ -86,90 +86,68 @@ review. .IP \(bu 2 \f[B]Previous / current / next line\f[R] .RS 2 -.PP -Desktop: Keypad 7 / 8 / 9 -.PP -Laptop: Fenrir+U / I / O +\f[B]Desktop:\f[R] Keypad 7 / 8 / 9 +\f[B]Laptop:\f[R] Fenrir+U / I / O .RE .IP \(bu 2 \f[B]Previous / current / next word\f[R] .RS 2 -.PP -Desktop: Keypad 4 / 5 / 6 -.PP -Laptop: Fenrir+J / K / L +\f[B]Desktop:\f[R] Keypad 4 / 5 / 6 +\f[B]Laptop:\f[R] Fenrir+J / K / L .RE .IP \(bu 2 \f[B]Previous / current / next character\f[R] .RS 2 -.PP -Desktop: Keypad 1 / 2 / 3 -.PP -Laptop: Fenrir+M / Comma / Period +\f[B]Desktop:\f[R] Keypad 1 / 2 / 3 +\f[B]Laptop:\f[R] Fenrir+M / Comma / Period .RE .IP \(bu 2 \f[B]Top / bottom of screen\f[R] .RS 2 -.PP -Desktop: Fenrir+Keypad 7 / 9 -.PP -Laptop: Fenrir+Shift+U / O +\f[B]Desktop:\f[R] Fenrir+Keypad 7 / 9 +\f[B]Laptop:\f[R] Fenrir+Shift+U / O .RE .IP \(bu 2 \f[B]Line start / end\f[R] .RS 2 -.PP -Desktop: Fenrir+Keypad 4 / 6 -.PP -Laptop: Fenrir+Shift+J / L +\f[B]Desktop:\f[R] Fenrir+Keypad 4 / 6 +\f[B]Laptop:\f[R] Fenrir+Shift+J / L .RE .IP \(bu 2 \f[B]First / last text on line\f[R] .RS 2 -.PP -Desktop: Fenrir+Keypad 1 / 3 -.PP -Laptop: Fenrir+Control+J / L +\f[B]Desktop:\f[R] Fenrir+Keypad 1 / 3 +\f[B]Laptop:\f[R] Fenrir+Control+J / L .RE .IP \(bu 2 \f[B]Read screen\f[R] .RS 2 -.PP -Desktop: Fenrir+Keypad 5 -.PP -Laptop: Fenrir+Shift+K +\f[B]Desktop:\f[R] Fenrir+Keypad 5 +\f[B]Laptop:\f[R] Fenrir+Shift+K .RE .IP \(bu 2 \f[B]Screen before / after cursor\f[R] .RS 2 -.PP -Desktop: Fenrir+Keypad 8 / 2 -.PP -Laptop: Fenrir+Shift+I / Comma +\f[B]Desktop:\f[R] Fenrir+Keypad 8 / 2 +\f[B]Laptop:\f[R] Fenrir+Shift+I / Comma .RE .IP \(bu 2 \f[B]Character above / below\f[R] .RS 2 -.PP -Desktop: Fenrir+Control+Keypad 8 / 2 -.PP -Laptop: Fenrir+Control+I / Comma +\f[B]Desktop:\f[R] Fenrir+Control+Keypad 8 / 2 +\f[B]Laptop:\f[R] Fenrir+Control+I / Comma .RE .IP \(bu 2 \f[B]Follow cursor\f[R] .RS 2 -.PP -Desktop: Fenrir+Keypad Period -.PP -Laptop: Fenrir+Slash +\f[B]Desktop:\f[R] Fenrir+Keypad Period +\f[B]Laptop:\f[R] Fenrir+Slash .RE .IP \(bu 2 \f[B]Review position\f[R] .RS 2 -.PP -Desktop: Keypad Period -.PP -Laptop: Fenrir+Shift+Period +\f[B]Desktop:\f[R] Keypad Period +\f[B]Laptop:\f[R] Fenrir+Shift+Period .RE .PP Press the current\-line or current\-word shortcut twice quickly to spell @@ -215,7 +193,13 @@ application. Character echo, word echo, and cursor echo can be configured separately. Punctuation levels are \f[CR]none\f[R], \f[CR]some\f[R], \f[CR]most\f[R], and \f[CR]all\f[R]. -Character review still names punctuation. +At \f[CR]some\f[R] and \f[CR]most\f[R], punctuation inside words, URLs, +email addresses, and filenames is also named. +Apostrophes within words and single numeric separators such as those in +\f[CR]3.14\f[R], \f[CR]1,000\f[R], and \f[CR]12:30\f[R] follow the +selected level. +Sentence punctuation follows the selected level too. +Character review always names punctuation. Capital indicators, emoticons, repeated\-symbol summaries, indentation feedback, and attribute feedback are configurable in Reading. .PP @@ -239,7 +223,8 @@ Activate \f[B]OK\f[R] to save, or \f[B]Cancel\f[R] or Escape to discard the form\(cqs changes. .PP Speech rate, pitch, and volume use a scale from \-100 to 100. -For speech rate, Up and Down change the value by 10; Left and Right change it by 1. +For speech rate, Up and Down change the value by 10; Left and Right +change it by 1. Sound volume is between 0.0 and 1.0. Backend, device, keyboard layout, sound theme, and other launch\-only settings take effect on the next start. @@ -267,38 +252,374 @@ Quick settings work without opening the form: .IP \(bu 2 \f[B]Fenrir+Space\f[R] .RS 2 -.PP -Action: Announce current setting +\f[B]Action:\f[R] Announce current setting .RE .IP \(bu 2 \f[B]Fenrir+Left / Right\f[R] .RS 2 -.PP -Action: Previous / next setting +\f[B]Action:\f[R] Previous / next setting .RE .IP \(bu 2 \f[B]Fenrir+Control+Left / Right\f[R] .RS 2 -.PP -Action: Previous / next category +\f[B]Action:\f[R] Previous / next category .RE .IP \(bu 2 \f[B]Fenrir+Up / Down\f[R] .RS 2 -.PP -Action: Change value +\f[B]Action:\f[R] Change value .RE .IP \(bu 2 \f[B]Fenrir+Control+S\f[R] .RS 2 -.PP -Action: Save settings +\f[B]Action:\f[R] Save settings .RE .PP Quick changes apply to the current session. Save to retain them for future launches. Session controls such as mute and temporary silence are separate from saved startup preferences. +.SS Application overrides +Press \f[B]Fenrir+A\f[R] to open \f[B]Applications\f[R]. +Up and Down select an item; Enter activates it and closes the menu. +Escape or the Cancel button closes the menu without changing the +selection. +Tab and Shift+Tab move between the list, Copy alias to clipboard, +Cancel, and the tab selector. +The active override is initially selected. +.PP +Type the start of a name to jump to it: \f[B]n\f[R] selects +NetworkManager, then \f[B]i\f[R] selects Ninjam. +Searching ignores letter case and does not activate the item. +Backspace removes a search character; Control+Backspace clears the +search. +Up, Down, or moving focus away from the list also clears the search. +Unmatched characters announce \f[B]No matches\f[R] and leave the last +matching search intact. +.PP +Start the application normally, then select its override. +This menu does not launch programs. +Choose \f[B]Default\f[R] to restore the settings and reading regions +that were in effect before the override. +Overrides are built into Fenrir and are never selected automatically or +remembered across restarts. +.PP +To launch a program with its override, select its name in this list, +then Tab to \f[B]Copy alias to clipboard\f[R] and press Enter. +This copies a Bash alias into Fenrir\(cqs clipboard without selecting +the override or closing the menu. +Paste it into your Bash configuration, such as \f[CR]\(ti/.bashrc\f[R], +and load that configuration in a new shell. +The aliases use the executable names \f[CR]cninjam\f[R], +\f[CR]nmtui\f[R], \f[CR]rogue\f[R], and \f[CR]codex\f[R]; Default has no +alias. +Existing aliases with those names are replaced when you load the +definition. +.PP +The alias requests the override through \f[CR]socat\f[R], then launches +the real program with the arguments you supplied. +Errors remain visible, and the program still launches if the request +fails, remote control is disabled, or Fenrir or \f[CR]socat\f[R] is +missing. +Socket inactivity is limited to two seconds. +It uses the standard socket, or your configured +\f[CR]remote.socket_file\f[R], rather than a process\-specific socket +that changes at restart. +Paste with Fenrir\(cqs clipboard command; desktop clipboard +synchronization follows your normal clipboard settings. +.PP +While an override is active, \f[B]Fenrir+F\f[R] opens its application +settings, with the application name in the title. +\f[B]OK\f[R], \f[B]Fenrir+Control+S\f[R], and remote +\f[CR]SETTING SAVE\f[R] save that application\(cqs preferences. +Quick and remote changes remain temporary until saved. +Default settings are kept separately, including any unsaved adjustments +made before activating the override. +.PP +Profiles live in an \f[CR]applications/\f[R] subdirectory of the +ordinary settings save directory. +For example: +.IP \(bu 2 +System settings: +\f[CR]/etc/fenrirscreenreader/settings/applications/ninjam.toml\f[R]. +.IP \(bu 2 +Personal settings: +\f[CR]\(ti/.local/share/stormux/fenrirscreenreader/settings/applications/ninjam.toml\f[R]. +.IP \(bu 2 +With \f[CR]\-\-config /path/settings.toml\f[R]: +\f[CR]/path/applications/ninjam.toml\f[R]. +.PP +NetworkManager uses \f[CR]networkmanager.toml\f[R]; Rogue uses +\f[CR]rogue.toml\f[R]; Codex uses \f[CR]codex.toml\f[R]. +The directory and file are created on the first successful save. +Files contain only customized settings; omitted values inherit ordinary +settings followed by the built\-in application defaults. +A customized voice selection saves module, language, and voice together +so a later change to the ordinary voice does not break that selection. +Profiles are loaded when their override first recognizes the +application; restarting Fenrir still starts with Default. +Reselect the application to reload an edited file. +.PP +Application settings include voice, rate, pitch, volume, reading +behavior, sound enablement/volume, and typing echo. +Startup settings such as speech driver, serial device, keyboard layout, +and sound theme remain in the default menu; clipboard storage and +console routing also remain global. +Session mute and the temporary automatic\-reading toggle are not +implicitly saved. +Remote \f[CR]SAVEAS\f[R] and \f[CR]screen#ignore_screen\f[R] are +unavailable while an application is active. +Saving is blocked during context transitions or failed voice +restoration. +A malformed profile or unavailable voice leaves ordinary settings in +effect and reports an error. +Select the application again after correcting the file. +.PP +For example, a rate\-only profile contains current TOML setting names, +with no top\-level \f[CR]version\f[R] field: +.IP +.EX +\f[B][presentation.speech]\f[R] +rate = 40 +.EE +.PP +To make a saved value inherit again, remove that setting from the +application file and reselect the override. +Removing the file restores complete inheritance. +.PP +An override belongs to its terminal. +Switching VTs or leaving the terminal suspends its effects; returning +resumes it when the application screen is recognized. +Unrecognized screens use ordinary reading. +A fresh shell prompt on the cursor row, with the application interface +absent, clears the selection and announces \f[B]Default settings +restored\f[R]. +This works from screen contents, so it does not require access to remote +processes over SSH or inside GNU Screen. +Unusual prompts may require selecting Default manually. +Password prompts, confirmation questions, and old shell prompts in +scrollback do not clear it. +Split windows, heavily customized application colors, and localized or +unfamiliar layouts may prevent recognition; Default is always available. +.PP +\f[B]NetworkManager\f[R] targets \f[CR]nmtui\f[R] and temporarily +selects highlight tracking. +.PP +\f[B]Ninjam\f[R] targets the \f[CR]cninjam\f[R] curses client. +It announces selected controls with master, metronome, or channel +context; mute, transmit, and receive controls are spoken as states. +While adjusting a volume/pan control, Left/Right announces only the pan +percentage and direction, or center; Up/Down announces only the volume, +with minus and plus spoken explicitly. +Peak meters and the interval bar remain quiet. +Navigating the application\(cqs controls after manual review resumes +tracking; passive updates leave review in place. +Incoming chat is announced independently of control changes. +The following shortcuts temporarily replace numbered region reads: +.IP \(bu 2 +\f[B]Fenrir+1\f[R] +.RS 2 +\f[B]Ninjam information:\f[R] Latest new chat message received since +activation +.RE +.IP \(bu 2 +\f[B]Fenrir+2\f[R] +.RS 2 +\f[B]Ninjam information:\f[R] Selected channel or control +.RE +.IP \(bu 2 +\f[B]Fenrir+3\f[R] +.RS 2 +\f[B]Ninjam information:\f[R] Connection, tempo, and interval position +.RE +.PP +Fenrir+Shift+B lists these application assignments. +Existing user regions are preserved and restored when the override ends. +Review, Read Screen, and copying continue to expose the actual terminal +text, including its literal \f[CR]X\f[R] marks. +Chat history is baselined on activation, resize, buffer changes, and +history paging to avoid announcing old messages as new. +Messages that appear and disappear between screen captures cannot be +recovered reliably. +.PP +The laptop shortcut for reading attributes is \f[B]Fenrir+Shift+A\f[R]; +Fenrir+A opens Applications in both layouts. +.PP +\f[B]Rogue\f[R] targets the classic English Rogue 5.4 terminal game, +including the 5.4.4 package. +Start \f[CR]rogue\f[R], then select \f[B]Rogue\f[R] with +\f[B]Fenrir+A\f[R] (type \f[B]r\f[R], then Enter). +The override announces messages and prompts, including \f[B]More; press +Space\f[R], changes in health, hunger, dungeon level, strength, armor, +and experience level, and newly visible monsters, objects, doors, +stairs, and traps. +Ordinary map redraws stay quiet. +Moving monsters are compared by letter and count to reduce repeated +announcements. +Their current positions remain available on request. +Appearing and disappearing messages between captures, or one monster +replacing another of the same letter without a count change, cannot +reliably be distinguished from the visible display alone. +.IP \(bu 2 +\f[B]Fenrir+1\f[R] +.RS 2 +\f[B]Rogue information:\f[R] Health, for example \(lqHealth: 12 of +12\(rq +.RE +.IP \(bu 2 +\f[B]Fenrir+2\f[R] +.RS 2 +\f[B]Rogue information:\f[R] Full status line +.RE +.IP \(bu 2 +\f[B]Fenrir+3\f[R] +.RS 2 +\f[B]Rogue information:\f[R] Latest message or prompt +.RE +.IP \(bu 2 +\f[B]Fenrir+4\f[R] +.RS 2 +\f[B]Rogue information:\f[R] Eight neighboring cells, clockwise from +north +.RE +.IP \(bu 2 +\f[B]Fenrir+5\f[R] +.RS 2 +\f[B]Rogue information:\f[R] Visible monsters and their positions +relative to you +.RE +.IP \(bu 2 +\f[B]Fenrir+6\f[R] +.RS 2 +\f[B]Rogue information:\f[R] Visible objects and landmarks, with +relative positions +.RE +.PP +These explicit reads include their region names. +Names are supported by the shared application\-region API, so other +overrides can use them as needed. +\f[B]Fenrir+Shift+B\f[R] lists the assignments. +Slots 7 through 10 remain available for manual regions. +Existing user regions are preserved while the override is active and +restored when it ends, as with Ninjam. +.PP +Directions give cell offsets, for example \(lqbat, north 2, east 3.\(rq +Descriptions use only displayed symbols; they do not reveal hidden +monsters, secret doors, or unidentified items. +Blank cells are described as unseen, and hallucination or disguised +monsters can make the displayed symbols misleading. +The playfield uses the first 24 rows and 80 columns even in a larger +terminal, with status on row 24. +Inventory, help, and options pages use ordinary reading and review; map +regions report information unavailable on those pages. +Slow inventory uses the ordinary message region. +Read Screen, review, and copying retain the actual terminal text. +Unrecognized screens return to ordinary reading. +.PP +Developers can exercise an installed classic Rogue in private PTYs +without touching the active reader: +.IP +.EX +FENRIR_ROGUE_TEST_BINARY=/usr/bin/rogue go test ./internal/terminal \-run TestRogueLiveOverride \-v +.EE +.SS Codex CLI +The \f[B]Codex\f[R] override replaces automatic terminal diffs with +completed assistant messages. +It suppresses tool output, streaming fragments, and redraws. +Manual screen review remains available, and typing echo is limited to +the composer. +Messages are retained in memory even while speech is muted or automatic +reading is disabled. +Existing lifecycle alerts remain separate from reply reading. +Left/Right navigation retains character feedback inside the composer. +While Codex is working, the existing activity tone sounds once every +four seconds without requiring redraws. +It stops for approval/question waits and completion, respects the sound +and progress toggles, and remains independent of speech mute. +.IP \(bu 2 +\f[B]Fenrir+1 through Fenrir+9\f[R] +.RS 2 +\f[B]Codex information:\f[R] Recent completed assistant messages, newest +first +.RE +.IP \(bu 2 +\f[B]Fenrir+0\f[R] +.RS 2 +\f[B]Codex information:\f[R] Working, ready, waiting for approval or an +answer, finished, or interrupted +.RE +.PP +Select \f[B]Codex\f[R] with \f[B]Fenrir+A\f[R], type \f[B]c\f[R], then +Enter. +Select \f[B]Default\f[R] to leave the override. +Without its bridge, the override suppresses automatic screen changes and +the status shortcut says \f[B]Waiting for Codex bridge\f[R]. +.PP +The optional bridge requires Python 3 and Fenrir remote command control +enabled. +Merge a command hook invoking +\f[CR]python3 /absolute/path/to/fenrir/tools/codex_bridge.py\f[R] into +each of these events in \f[CR]\(ti/.codex/hooks.json\f[R]: +\f[CR]SessionStart\f[R], \f[CR]UserPromptSubmit\f[R], +\f[CR]PermissionRequest\f[R], \f[CR]PostToolUse\f[R], \f[CR]Stop\f[R], +\f[CR]Interrupt\f[R], and \f[CR]SessionEnd\f[R]. +For example, one event entry is: +.IP +.EX +{ + \(dqhooks\(dq: { + \(dqSessionStart\(dq: [{\(dqhooks\(dq: [{\(dqtype\(dq: \(dqcommand\(dq, \(dqcommand\(dq: \(dqpython3 /absolute/path/to/fenrir/tools/codex_bridge.py\(dq, \(dqtimeout\(dq: 3}]}] + } +} +.EE +.PP +Preserve existing hooks. +Review and trust the added hooks through Codex\(cqs \f[CR]/hooks\f[R] +interface; restart or resume Codex if required by the installed version. +Starting or resuming a conversation starts a detached bridge follower; +submitting a prompt also restarts it if necessary. +The bridge never speaks or answers approvals. +.PP +The follower reads only the transcript path supplied by that +conversation\(cqs hook and verifies its session identifier. +It accepts completed assistant events, never tool results, reasoning, or +streamed deltas. +It keeps the nine latest replies in memory and writes only status, +conversation identifiers, and lock files to a private runtime directory. +Switching conversations replaces the terminal binding; packets from an +older conversation cannot overwrite the new history. +Existing history is loaded silently on attachment or bridge restart. +Long replies are transported in chunks and announced only after +assembly; the current per\-message limit is 1 MiB. +The worker stops when Codex exits or a \f[CR]SessionEnd\f[R] hook runs. +.PP +X11 routing requires the same \f[CR]WINDOWID\f[R] as the Fenrir +instance; Linux console routing uses Codex\(cqs controlling VT and the +reader\(cqs existing authorization. +Other windows and consoles do not receive the replies. +This local bridge does not forward SSH audio or attach a remote Codex +server to a local terminal. +.PP +The transcript parser was checked against Codex CLI 0.165.0. +It uses Codex\(cqs local completed\-event records, rather than a public +subscription API; a changed record format may require an update. +Status comes from hooks and recorded turn events, not the terminal\(cqs +animated activity label. +Check \f[B]Fenrir+0\f[R] after resuming. +.PP +For a private protocol and keyboard test without live speech or Codex +requests: +.IP +.EX +go build \-o fenrir ./cmd/fenrir +python3 \-m unittest discover \-s tests \-p test_codex_bridge.py +xvfb\-run \-a env FENRIR_PRIVATE_X11=1 python3 tests/native_codex.py +.EE +.PP +Real acceptance still needs a Codex reply spoken once after completion, +working status on demand, history review, composer typing, +approval/question handling, and returning to ordinary reading after +choosing Default or exiting Codex. .SS Clipboard Use review navigation to place two marks around text with \f[B]Fenrir+X\f[R], then press \f[B]Fenrir+C\f[R] to copy it. @@ -306,68 +627,57 @@ In table mode Fenrir+X sets headers instead. .IP \(bu 2 \f[B]Fenrir+Shift+X\f[R] .RS 2 -.PP -Action: Read marked text +\f[B]Action:\f[R] Read marked text .RE .IP \(bu 2 \f[B]Fenrir+Control+Shift+X\f[R] .RS 2 -.PP -Action: Remove marks +\f[B]Action:\f[R] Remove marks .RE .IP \(bu 2 \f[B]Fenrir+C\f[R] .RS 2 -.PP -Action: Copy marked text +\f[B]Action:\f[R] Copy marked text .RE .IP \(bu 2 \f[B]Fenrir+Control+C\f[R] .RS 2 -.PP -Action: Copy last presented text +\f[B]Action:\f[R] Copy last presented text .RE .IP \(bu 2 \f[B]Fenrir+Shift+C\f[R] .RS 2 -.PP -Action: Read selected clipboard +\f[B]Action:\f[R] Read selected clipboard .RE .IP \(bu 2 \f[B]Fenrir+Control+Shift+C\f[R] .RS 2 -.PP -Action: Clear selected clipboard +\f[B]Action:\f[R] Clear selected clipboard .RE .IP \(bu 2 \f[B]Fenrir+Home / End\f[R] .RS 2 -.PP -Action: First / last clipboard +\f[B]Action:\f[R] First / last clipboard .RE .IP \(bu 2 \f[B]Fenrir+PageUp / PageDown\f[R] .RS 2 -.PP -Action: Previous / next clipboard +\f[B]Action:\f[R] Previous / next clipboard .RE .IP \(bu 2 \f[B]Fenrir+V\f[R] .RS 2 -.PP -Action: Paste selected clipboard +\f[B]Action:\f[R] Paste selected clipboard .RE .IP \(bu 2 \f[B]Fenrir+F5 / F6\f[R] .RS 2 -.PP -Action: Import from / export to file +\f[B]Action:\f[R] Import from / export to file .RE .IP \(bu 2 \f[B]Fenrir+F7 / F8\f[R] .RS 2 -.PP -Action: Import from / export to X clipboard +\f[B]Action:\f[R] Import from / export to X clipboard .RE .PP \f[CR]clipboard.number_of_clipboards\f[R] limits history; the default is @@ -442,6 +752,39 @@ Select the driver and theme in Sound settings. The ALSA device name is set with \f[CR]presentation.sound.device\f[R] in the file. .PP +Speech Dispatcher has its own synthesis and audio configuration, +separate from Fenrir\(cqs sound cues. +For an X11 terminal session, run \f[CR]./fenrir \-x\f[R] as your regular +user. +With sudo, Fenrir uses \f[CR]SUDO_UID\f[R] to locate the invoking +user\(cqs PulseAudio socket. +The default sound driver tries the user session, then +\f[CR]/tmp/pulse.sock\f[R], then \f[CR]/run/pulse/native\f[R]. +Set \f[CR]PULSE_SERVER\f[R] to select an explicit server; the sound +driver does not read PulseAudio\(cqs \f[CR]client.conf\f[R]. +The sound service must already be running and accessible to the reader. +See Speech synthesizers for Speech Dispatcher addresses and autostart +behavior. +.PP +If root sound needs additional configuration, the checkout includes +configure_pulse.sh for PulseAudio and configure_pipewire.sh for +PipeWire. +Read the appropriate script before running it. +Each has a regular\-user part and a root part, and writes audio +configuration files. +The PulseAudio script replaces the user\(cqs \f[CR]default.pa\f[R] and +configures an anonymously accessible socket at +\f[CR]/tmp/pulse.sock\f[R]. +The PipeWire script also changes WirePlumber settings. +These scripts change system audio configuration and are only needed when +the existing audio setup does not provide the required access. +.PP +For ALSA, select \f[CR]driver = \(dqalsa\(dq\f[R] under +\f[CR][presentation.sound]\f[R] in \f[CR]settings.toml\f[R] and set +\f[CR]device\f[R] to the desired PCM name, such as +\f[CR]\(dqdefault\(dq\f[R]. +Driver and device changes apply at the next launch. +.PP Personal themes are searched under \f[CR]\(ti/.local/stormux/fenrir/sounds/\f[R] before bundled and system themes. @@ -471,8 +814,8 @@ If default files are absent, Fenrir uses built\-in defaults. Python \f[CR]settings.conf\f[R] files are ignored. Regular users save to their personal TOML file, including when startup loaded system defaults; root saves to the system file. -\f[CR]\-\-config PATH\f[R] explicitly selects both the load and save path, -and that file must exist at startup. +\f[CR]\-\-config PATH\f[R] explicitly selects both the load and save +path, and that file must exist at startup. .PP Create a personal settings file and validate it without opening devices: .IP @@ -494,6 +837,32 @@ The default driver is \f[CR]speech\-dispatcher\f[R]. Select its module, language, and voice in Speech settings; empty values use server defaults. .PP +Fenrir uses \f[CR]SPEECHD_ADDRESS\f[R] when set: +\f[CR]unix_socket:/absolute/path\f[R] selects a Unix socket, and +\f[CR]inet_socket:HOST:PORT\f[R] selects TCP (default host +\f[CR]127.0.0.1\f[R], port \f[CR]6560\f[R]). +Invalid addresses report an error. +Otherwise, \f[CR]SPEECHD_SOCKET\f[R] selects a Unix socket. +Without either variable, Fenrir uses +\f[CR]$XDG_RUNTIME_DIR/speech\-dispatcher/speechd.sock\f[R], falling +back to \f[CR]$XDG_CACHE_HOME/speech\-dispatcher/speechd.sock\f[R] or +\f[CR]\(ti/.cache/speech\-dispatcher/speechd.sock\f[R] when the runtime +directory is unset. +With sudo, the default remains +\f[CR]/run/user/$SUDO_UID/speech\-dispatcher/speechd.sock\f[R]. +Sudo may remove address variables; preserve them explicitly if needed. +.PP +When opening speech, Fenrir first tries the selected server. +If its socket is missing or refuses connections, Fenrir requests +\f[CR]speech\-dispatcher \-\-spawn\f[R] and briefly retries. +The daemon\(cqs configuration controls whether spawning is allowed. +\f[CR]SPEECHD_CMD\f[R] can select another executable. +Autostart is limited to Unix sockets and local loopback TCP servers. +When sudo changes the account, start Speech Dispatcher as the invoking +user first; Fenrir will not launch a root\-owned daemon for that user. +Settings discovery never starts a daemon or interrupts the active +speaking client. +.PP Hardware driver IDs are \f[CR]dectalk\f[R], \f[CR]dectalk\-usb\f[R], \f[CR]litetalk\f[R], \f[CR]doubletalk\f[R], and \f[CR]tripletalk\f[R]. Serial drivers require \f[CR]presentation.speech.hardware_device\f[R], @@ -553,6 +922,17 @@ Other supported operations include: .IP \(bu 2 \f[CR]command exportclipboard\f[R]: export the selected clipboard. .IP \(bu 2 +\f[CR]command application ID\f[R]: select \f[CR]default\f[R], +\f[CR]networkmanager\f[R], \f[CR]ninjam\f[R], \f[CR]rogue\f[R], or +\f[CR]codex\f[R] for this reader\(cqs active terminal, like Fenrir+A. +This requires command remote control. +An empty reply means selection succeeded; invalid IDs, busy settings, +and restoration failures return an error. +Application recognition and profile loading happen later, when the +application appears; this command does not launch it. +\f[CR]default\f[R] restores ordinary settings. +Use a private socket when targeting a particular reader. +.IP \(bu 2 \f[CR]command window X1 Y1 X2 Y2\f[R]: set a reading window, using zero\-based, inclusive coordinates. .IP \(bu 2 @@ -574,89 +954,77 @@ self\-voicing also allows an account logged into the active console. Fenrir runs as \f[CR]fenrir [options]\f[R]. Use \f[CR]\-\-help\f[R] for the built\-in reference. .IP \(bu 2 -\f[B]\-\-help\f[R] +\f[B]\f[CB]\-\-help\f[B]\f[R] .RS 2 -.PP -Purpose: Show usage and options +\f[B]Purpose:\f[R] Show usage and options .RE .IP \(bu 2 -\f[B]\-\-version\f[R] +\f[B]\f[CB]\-\-version\f[B]\f[R] .RS 2 -.PP -Purpose: Print version and release channel +\f[B]Purpose:\f[R] Print version and release channel .RE .IP \(bu 2 -\f[B]\-f\f[R] +\f[B]\f[CB]\-f\f[B]\f[R] .RS 2 -.PP -Purpose: Keep console mode in the foreground (always enabled with -\f[CR]\-x\f[R]) +\f[B]Purpose:\f[R] Keep console mode in the foreground (always enabled +with \f[CR]\-x\f[R]) .RE .IP \(bu 2 -\f[B]\-x\f[R] +\f[B]\f[CB]\-x\f[B]\f[R] .RS 2 -.PP -Purpose: Start a shell inside this X11 terminal +\f[B]Purpose:\f[R] Start a shell inside this X11 terminal .RE .IP \(bu 2 -\f[B]\-\-config PATH\f[R] +\f[B]\f[CB]\-\-config PATH\f[B]\f[R] .RS 2 -.PP -Purpose: Load a specific TOML settings file +\f[B]Purpose:\f[R] Load a specific TOML settings file .RE .IP \(bu 2 -\f[B]\-\-keyboard\-layout NAME_OR_PATH\f[R] +\f[B]\f[CB]\-\-keyboard\-layout NAME_OR_PATH\f[B]\f[R] .RS 2 -.PP -Purpose: Select a named layout or TOML file +\f[B]Purpose:\f[R] Select a named layout or TOML file .RE .IP \(bu 2 -\f[B]\-\-punctuation\-profile NAME_OR_PATH\f[R] +\f[B]\f[CB]\-\-punctuation\-profile NAME_OR_PATH\f[B]\f[R] .RS 2 -.PP -Purpose: Select a punctuation profile +\f[B]Purpose:\f[R] Select a punctuation profile .RE .IP \(bu 2 -\f[B]\-\-check\-config\f[R] +\f[B]\f[CB]\-\-check\-config\f[B]\f[R] .RS 2 -.PP -Purpose: Validate settings, layout, and punctuation without opening devices +\f[B]Purpose:\f[R] Validate settings, layout, and punctuation without +opening devices .RE .IP \(bu 2 -\f[B]\-\-write\-config PATH\f[R] +\f[B]\f[CB]\-\-write\-config PATH\f[B]\f[R] .RS 2 -.PP -Purpose: Write effective settings to a file and exit +\f[B]Purpose:\f[R] Write effective settings to a file and exit .RE .IP \(bu 2 -\f[B]\-\-speech\-driver ID\f[R] +\f[B]\f[CB]\-\-speech\-driver ID\f[B]\f[R] .RS 2 -.PP -Purpose: Override the configured speech driver +\f[B]Purpose:\f[R] Override the configured speech driver .RE .IP \(bu 2 -\f[B]\-\-speech\-rate NUMBER\f[R] +\f[B]\f[CB]\-\-speech\-rate NUMBER\f[B]\f[R] .RS 2 -.PP -Purpose: Override rate, from \-100 to 100 +\f[B]Purpose:\f[R] Override rate, from \-100 to 100 .RE .IP \(bu 2 -\f[B]\-\-speech\-module NAME\f[R] +\f[B]\f[CB]\-\-speech\-module NAME\f[B]\f[R] .RS 2 -.PP -Purpose: Select a Speech Dispatcher module +\f[B]Purpose:\f[R] Select a Speech Dispatcher module .RE .IP \(bu 2 -\f[B]\-\-input\-device PATH\f[R] +\f[B]\f[CB]\-\-input\-device PATH\f[B]\f[R] .RS 2 -.PP -Purpose: Select a console keyboard event device; may be repeated +\f[B]Purpose:\f[R] Select a console keyboard event device; may be +repeated .RE .IP \(bu 2 -\f[B]\-\-debug\-file PATH\f[R] +\f[B]\f[CB]\-\-debug\-file PATH\f[B]\f[R] .RS 2 -.PP -Purpose: Append timing and reader\-decision diagnostics +\f[B]Purpose:\f[R] Append timing and reader\-decision diagnostics .RE .SS Troubleshooting .SS Speech or sound is missing @@ -693,9 +1061,11 @@ problem. .SS Documentation maintenance and support \f[CR]docs/user.md\f[R] is the shared manual source. With Pandoc installed, run \f[CR]./docs/create_manpage.sh\f[R] to -regenerate \f[CR]docs/user.txt\f[R], \f[CR]docs/fenrir.adoc\f[R], and -\f[CR]docs/fenrir.1\f[R]. +regenerate \f[CR]docs/user.txt\f[R] and \f[CR]docs/fenrir.1\f[R]. The script can run from any working directory. +Edit the Markdown source rather than the generated files. +The bundled \f[CR]docs/man_tables.lua\f[R] filter renders man\-page +tables as labeled lists for narrow terminals. .PP Please review documentation and test instructions for the affected workflow before submitting a change, as described in the README\(cqs diff --git a/docs/fenrir.adoc b/docs/fenrir.adoc deleted file mode 100644 index 92982b58..00000000 --- a/docs/fenrir.adoc +++ /dev/null @@ -1,491 +0,0 @@ -== Fenrir User Manual - -Fenrir is a Linux console screen reader written in Go. It reads Linux -text consoles and can run in an X11 terminal as a regular user. - -*Current maintainer:* Storm Dragon + -*Original creator:* Chrys (see link:../CREDITS[CREDITS]) - -For build requirements and setup, see the link:../README.md[README]. - -=== Starting and stopping - -Run these commands from the checkout root after building `fenrir`: - -[source,bash] ----- -# Linux text console, with console and keyboard device access -sudo ./fenrir - -# Linux console in the foreground (also use -f with service managers) -sudo ./fenrir -f - -# X11 terminal, as your regular user -./fenrir -x - -# Either mode can select the laptop layout -./fenrir -x --keyboard-layout laptop ----- - -Console mode starts in the background by default. Add `-f` to keep it in -the foreground, including under a service manager. The launcher returns -after the reader initializes and reports startup failures in the -launching terminal. Use `--debug-file PATH` to retain diagnostics after -detaching. Help, version, and configuration-only commands always run in -the foreground. - -X11 mode (`-x`) always stays in the foreground because it owns the -terminal and its child shell. Press *Fenrir{plus}Q* to quit either mode. -Run only one reader on a given Linux console. Separate X11 terminal -instances can run at once. - -X11 mode starts a shell in a new PTY. The launching terminal must supply -valid `DISPLAY` and `WINDOWID` environment variables. Keyboard -interception is scoped to that terminal window; a desktop screen reader -can handle other windows. - -Console mode needs access to console devices, keyboard event devices, -and `/dev/uinput`. It releases keyboard input on graphical virtual -terminals and on text consoles selected in `screen.ignored++_++vts`. - -=== Keyboard layouts and help - -The *Fenrir key* is Insert, Super/Windows, or keypad 0 with NumLock off. -Hold it while pressing the other keys in a shortcut. Bare keypad review -keys also require NumLock off. - -The desktop layout uses the numeric keypad. The laptop layout uses -Fenrir chords on the main keyboard. Select a layout with -`--keyboard-layout desktop` or `--keyboard-layout laptop`, or set -`keyboard.layout` in the settings file. - -Press *Fenrir{plus}H* or *Fenrir{plus}F1* to enter the tutorial. Command -shortcuts are announced and described instead of executed. Up and Down -browse commands; Left and Right switch between active actions, unbound -actions, and scripts. Space repeats the current item. Escape or -Fenrir{plus}F1 exits. - -The complete layouts are -link:../config/keyboard/desktop.toml[desktop.toml] and -link:../config/keyboard/laptop.toml[laptop.toml]. - -=== Screen review - -Review moves Fenrir’s reading position without moving the application’s -cursor. Return to the application cursor with the Follow cursor -shortcut. By default, application cursor movement or a screen change -also leaves review. - -[width="100%",cols="34%,33%,33%",options="header",] -|=== -|Action |Desktop |Laptop -|Previous / current / next line |Keypad 7 / 8 / 9 |Fenrir{plus}U / I / O - -|Previous / current / next word |Keypad 4 / 5 / 6 |Fenrir{plus}J / K / L - -|Previous / current / next character |Keypad 1 / 2 / 3 |Fenrir{plus}M / -Comma / Period - -|Top / bottom of screen |Fenrir{plus}Keypad 7 / 9 -|Fenrir{plus}Shift{plus}U / O - -|Line start / end |Fenrir{plus}Keypad 4 / 6 |Fenrir{plus}Shift{plus}J / -L - -|First / last text on line |Fenrir{plus}Keypad 1 / 3 -|Fenrir{plus}Control{plus}J / L - -|Read screen |Fenrir{plus}Keypad 5 |Fenrir{plus}Shift{plus}K - -|Screen before / after cursor |Fenrir{plus}Keypad 8 / 2 -|Fenrir{plus}Shift{plus}I / Comma - -|Character above / below |Fenrir{plus}Control{plus}Keypad 8 / 2 -|Fenrir{plus}Control{plus}I / Comma - -|Follow cursor |Fenrir{plus}Keypad Period |Fenrir{plus}Slash - -|Review position |Keypad Period |Fenrir{plus}Shift{plus}Period -|=== - -Press the current-line or current-word shortcut twice quickly to spell -the text, or three times for phonetic reading. Press the -current-character shortcut twice for its phonetic name. The default -interval between presses is 200 ms; -`keyboard.multi++_++press++_++timeout++_++ms` changes it at launch. - -=== Tracking modes - -Cycle through *cursor*, *table*, and *highlight* with -*Fenrir{plus}Keypad Asterisk* on desktop or *Fenrir{plus}Y* on laptop. -The startup selection is `reading.mode`. - -* *Cursor*: follows application cursor movement. -* *Table*: treats delimited or space-aligned text as cells. -* *Highlight*: follows changes to highlighted text, such as menu -selections. - -In table mode, word navigation moves between cells, line start/end moves -to the first/last cell, and first/last text moves to the first/last -character in the current cell. Place review on a header row and press -*Fenrir{plus}X* to use its cells as column headers. Outside table mode, -Fenrir{plus}X places a text-selection mark. - -=== Speech and automatic reading - -* *Control* interrupts speech. -* *Fenrir{plus}F4* toggles mute. -* *Keypad Enter* on desktop temporarily silences speech. Speech can -return on the next keypress or a newly recognized prompt or completion. -* *Fenrir{plus}Keypad Enter* on desktop, or *Fenrir{plus}Enter* on -laptop, toggles automatic reading of new text. -* *Fenrir{plus}Backspace* passes the next key through to the -application. - -Character echo, word echo, and cursor echo can be configured separately. -Punctuation levels are `none`, `some`, `most`, and `all`. Character -review still names punctuation. Capital indicators, emoticons, -repeated-symbol summaries, indentation feedback, and attribute feedback -are configurable in Reading. - -*Fenrir{plus}T* announces the time; press twice quickly for the date. -*Fenrir{plus}Shift{plus}V* announces the Fenrir version. - -=== Fenrir Menu and quick settings - -Press *Fenrir{plus}F* to open the Fenrir Menu. It contains settings tabs -for presentation, speech, sound, reading, keyboard, internal UI, clipboard, and other -options. The form captures its navigation keys until it closes. - -* At the tab selector, use arrows to choose a tab. -* Tab and Shift{plus}Tab move between controls and the tab selector. -* Space or Enter toggles a checkbox. -* Arrows change list selections and numeric values. -* Activate *OK* to save, or *Cancel* or Escape to discard the form’s -changes. - -Speech rate, pitch, and volume use a scale from -100 to 100. For speech -rate, Up and Down change the value by 10; Left and Right change it by 1. Sound -volume is between 0.0 and 1.0. Backend, device, keyboard layout, sound -theme, and other launch-only settings take effect on the next start. - -The *Internal UI* tab controls speech in Fenrir’s own menus and dialogs. -*Announce grid row and column* and *Announce item position* are -independent checkboxes, both enabled by default. Disable coordinates to -omit “row 1, column 2”; disable item position to omit “2 of 20”. Item -names and grid navigation remain available with both disabled. Saved -changes apply immediately to Emoji and Spellcheck grids and persist -across launches. These are global settings, so this tab is omitted from -application settings. The TOML keys are -`internal++_++ui.announce++_++grid++_++coordinates` and -`internal++_++ui.announce++_++item++_++position`. - -Settings saves use mode `644` for the standard system settings file at -`/etc/fenrirscreenreader/settings/settings.toml`, so other users can -read it as a startup fallback. Personal settings and application -profiles use mode `600`. - -Quick settings work without opening the form: - -[cols=",",options="header",] -|=== -|Shortcut |Action -|Fenrir{plus}Space |Announce current setting -|Fenrir{plus}Left / Right |Previous / next setting -|Fenrir{plus}Control{plus}Left / Right |Previous / next category -|Fenrir{plus}Up / Down |Change value -|Fenrir{plus}Control{plus}S |Save settings -|=== - -Quick changes apply to the current session. Save to retain them for -future launches. Session controls such as mute and temporary silence are -separate from saved startup preferences. - -=== Clipboard - -Use review navigation to place two marks around text with -*Fenrir{plus}X*, then press *Fenrir{plus}C* to copy it. In table mode -Fenrir{plus}X sets headers instead. - -[cols=",",options="header",] -|=== -|Shortcut |Action -|Fenrir{plus}Shift{plus}X |Read marked text -|Fenrir{plus}Control{plus}Shift{plus}X |Remove marks -|Fenrir{plus}C |Copy marked text -|Fenrir{plus}Control{plus}C |Copy last presented text -|Fenrir{plus}Shift{plus}C |Read selected clipboard -|Fenrir{plus}Control{plus}Shift{plus}C |Clear selected clipboard -|Fenrir{plus}Home / End |First / last clipboard -|Fenrir{plus}PageUp / PageDown |Previous / next clipboard -|Fenrir{plus}V |Paste selected clipboard -|Fenrir{plus}F5 / F6 |Import from / export to file -|Fenrir{plus}F7 / F8 |Import from / export to X clipboard -|=== - -`clipboard.number++_++of++_++clipboards` limits history; the default is -200. `clipboard.export++_++path` selects the import/export file, -initially `/tmp/fenrirClipboard`. `clipboard.sync` enables automatic X -clipboard synchronization, which requires `xclip` and access to the X -display. - -Persistent history is optional and off by default. Set -`clipboard.persistent` to retain unencrypted history across launches. -Terminal sessions use `~/.local/state/fenrir/clipboard/terminal.json`; -console sessions use `/var/lib/fenrir/clipboard/tty.json`. Same-user -readers of the same mode share persistent history. - -=== Spell checking and emoji - -Automatic spell checking uses Enchant 2 and a dictionary matching the -session language. Reading settings control word repetition, spoken -misspelling feedback, and misspelling sounds. - -Press *Fenrir{plus}S* to open the Spellcheck Menu for the current word. -The Suggestions tab lets you edit the word and choose a suggested -spelling. The Exceptions tab lets you add words, remove selected entries -with Delete, and undo a removal. Selecting a suggestion copies it to -Fenrir’s clipboard; paste it with Fenrir{plus}V. - -Press *Fenrir{plus}Control{plus}E* to open the Emoji Menu. Choose a -category at the tab selector, then Tab to the emoji grid. Arrows -navigate, typing filters by name prefix, and Control{plus}Backspace -clears the filter. Enter copies the selected emoji to Fenrir’s -clipboard. Escape closes the menu. - -=== Reading windows and live regions - -Reading regions belong to the current terminal and last for the reader -session. A reading window restricts automatic reading to a rectangle. -Live regions exclude changing areas from automatic speech while allowing -explicit reading and progress monitoring. - -Outside table mode, place two marks with *Fenrir{plus}X* to define -opposite corners. Press *Fenrir{plus}Keypad Slash* on desktop or -*Fenrir{plus}Control{plus}8* on laptop to set a reading window. Press -the shortcut twice quickly to clear it. - -To create a live region, place one mark for the rest of that line, or -two marks for a rectangle, then press *Fenrir{plus}B*. Fenrir assigns -the first free slot, up to ten. Region creation clears the marks. - -* *Fenrir{plus}1* through *Fenrir{plus}0* read live regions 1 through -10. -* *Fenrir{plus}Shift{plus}1* through *Fenrir{plus}Shift{plus}0* clear -individual live regions. -* *Fenrir{plus}Shift{plus}B* lists the current reading regions. -* *Fenrir{plus}Control{plus}B* clears all live regions. -* *Fenrir{plus}Control{plus}Shift{plus}B* clears both the reading window -and live regions. - -=== Sound and progress - -Sound output uses `pulse` for PulseAudio or PipeWire’s PulseAudio -service, or `alsa` for an ALSA PCM device. Select the driver and theme -in Sound settings. The ALSA device name is set with -`presentation.sound.device` in the file. - -Personal themes are searched under `~/.local/stormux/fenrir/sounds/` -before bundled and system themes. A theme contains WAV files and -`soundicons.toml`. - -Progress tones are controlled separately from speech and automatic -reading. Press *Keypad Plus* on desktop or *Fenrir{plus}Shift{plus}P* on -laptop to toggle them. Fenrir recognizes percentages, fractions, -progress bars, and activity indicators; tones rise with progress. -Startup behavior is set by `presentation.progress.enabled`. - -=== Configuration files - -Settings are TOML. The annotated -link:../config/settings/settings.toml[settings.toml] lists all available -options. Fenrir loads one settings file rather than merging files. - -* Regular users: first -`~/.local/share/stormux/fenrirscreenreader/settings/settings.toml`, then -`/etc/fenrirscreenreader/settings/settings.toml`. -* Root: `/etc/fenrirscreenreader/settings/settings.toml`. -* `--config PATH`: use that file explicitly. - -If default files are absent, Fenrir uses built-in defaults. An -explicitly selected file must exist. Saving targets the selected file; -if startup used built-in defaults, saving creates the first default path -for that account. - -Create a personal settings file and validate it without opening devices: - -[source,bash] ----- -./fenrir --write-config ~/.local/share/stormux/fenrirscreenreader/settings/settings.toml -./fenrir --config ~/.local/share/stormux/fenrirscreenreader/settings/settings.toml --check-config ----- - -Named keyboard layouts are searched in the regular user’s -`~/.local/share/stormux/fenrirscreenreader/keyboard/`, then -`/etc/fenrirscreenreader/keyboard/`, then `config/keyboard/` beside the -binary. Root searches the system and bundled locations. An explicit TOML -path selects one file. Custom layouts replace the complete layout; they -are not overlays. See the link:../config/keyboard/Readme.md[keyboard -guide] for an example. - -=== Speech synthesizers - -The default driver is `speech-dispatcher`. Select its module, language, -and voice in Speech settings; empty values use server defaults. - -Hardware driver IDs are `dectalk`, `dectalk-usb`, `litetalk`, -`doubletalk`, and `tripletalk`. Serial drivers require -`presentation.speech.hardware++_++device`, for example `/dev/ttyUSB0`, -and default to 9600 baud when `hardware++_++baud++_++rate = 0`. -DoubleTalk support targets the external LT device. TripleTalk USB -devices need to expose a serial tty. - -Native `dectalk-usb` uses libusb 1.0 and ignores serial settings. The -process needs access to the device. The Speech form’s *Test synthesizer* -button previews hardware choices before saving. Driver and port changes -apply at launch. - -=== Script shortcuts - -Fenrir searches `~/.local/stormux/fenrir/`, the configured -`general.script++_++path` (default -`/usr/share/fenrirscreenreader/scripts`), and the bundled -`config/scripts/` beside the binary. Earlier directories take priority. - -A filename such as `example++__++-++__++key++_++y.sh` assigns the script -key plus Y. The script key defaults to Compose and is configured by -`general.script++_++keys`. Scripts must be executable to run. Fenrir -invokes them directly and passes the current account name as the first -argument. Commands appear in the tutorial’s script list. - -=== Remote control - -Unix socket remote control is enabled by default. The standard socket is -`/tmp/fenrirscreenreader-daemon.sock`, and instances also have private -sockets `/tmp/fenrirscreenreader-++<++pid++>++.sock`. Set -`remote.socket++_++file` for a custom socket. - -Use `ls` to discover instances. Use a private socket to address one -reader: - -[source,bash] ----- -echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock -echo "command say Hello" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock -echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock -echo "setting set reading#mode=highlight" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ----- - -Remote requests use `section++#++setting=value` syntax independently of -TOML. Remote speech rate, pitch, and volume use *0.0 to 1.0*, mapped to -the -100 to 100 scale used in settings files and menus. For example, -`speech++#++rate=0.8` selects rate 60. - -Other supported operations include: - -* `command tempdisablespeech`: temporarily silence speech. -* `command clipboard TEXT`: add text to the clipboard. -* `command exportclipboard`: export the selected clipboard. -* `command window X1 Y1 X2 Y2`: set a reading window, using zero-based, -inclusive coordinates. -* `command resetwindow`: clear the window. -* `setting save`: save settings. -* `setting reset`: clear quick and remote overrides back to the last -loaded or successfully saved settings. -* `command quitapplication`: quit Fenrir. - -Set `remote.enabled = false` to disable the service. Command and setting -requests have separate switches, `enable++_++command++_++remote` and -`enable++_++settings++_++remote`. Requests are authorized against the -caller’s account; console self-voicing also allows an account logged -into the active console. - -=== Command-line options - -Fenrir runs as `fenrir ++[++options++]++`. Use `--help` for the built-in -reference. - -[width="100%",cols="50%,50%",options="header",] -|=== -|Option |Purpose -|`--help` |Show usage and options - -|`--version` |Print version and release channel - -|`-f` |Keep console mode in the foreground (always enabled with `-x`) - -|`-x` |Start a shell inside this X11 terminal - -|`--config PATH` |Load a specific TOML settings file - -|`--keyboard-layout NAME++_++OR++_++PATH` |Select a named layout or TOML -file - -|`--punctuation-profile NAME++_++OR++_++PATH` |Select a punctuation -profile - -|`--check-config` |Validate settings, layout, and punctuation without -opening devices - -|`--write-config PATH` |Write effective settings to a file and exit - -|`--speech-driver ID` |Override the configured speech driver - -|`--speech-rate NUMBER` |Override rate, from -100 to 100 - -|`--speech-module NAME` |Select a Speech Dispatcher module - -|`--input-device PATH` |Select a console keyboard event device; may be -repeated - -|`--debug-file PATH` |Append timing and reader-decision diagnostics -|=== - -=== Troubleshooting - -==== Speech or sound is missing - -Check that speech and sound are enabled, and that the selected backend -is available. Speech Dispatcher needs a configured synthesizer. Pulse -output needs access to a running PulseAudio service, including -PipeWire’s PulseAudio service; ALSA needs access to its selected PCM -device. Root console sessions may need separate audio configuration; see -the README’s setup section. - -For hardware speech, check the driver, device path, device permissions, -and serial baud rate. Use *Test synthesizer* before saving a new -hardware selection. - -==== Keyboard input or X11 mode fails - -In console mode, check access to keyboard event devices and -`/dev/uinput`. In X11 mode, check `DISPLAY`, `WINDOWID`, and competing -grabs on the terminal. Use NumLock off for bare keypad review. A -configured `keyboard.devices` list or `--input-device` applies to -console mode and cannot be used with `-x`. - -==== Check configuration and collect diagnostics - -Validate an edited settings file with `--config PATH --check-config`. -For reader diagnostics, add `--debug-file /tmp/fenrir-debug.log` to the -launch command. This appends timing and reader decisions without screen -text or key values. `./fenrir --version` identifies the build when -reporting a problem. - -=== Documentation maintenance and support - -`docs/user.md` is the shared manual source. With Pandoc installed, run -`./docs/create++_++manpage.sh` to regenerate `docs/user.txt`, -`docs/fenrir.adoc`, and `docs/fenrir.1`. The script can run from any -working directory. - -Please review documentation and test instructions for the affected -workflow before submitting a change, as described in the README’s -contribution policy. - -* https://git.stormux.org/storm/fenrir[Project repository and issue -tracker] -* https://git.stormux.org/storm/fenrir/wiki[Fenrir Wiki] -* Email list: `stormux{plus}subscribe@groups.io`, with the subject -`subscribe`. -* IRC: `irc.stormux.org`, channel `++#++stormux`. diff --git a/docs/man_tables.lua b/docs/man_tables.lua new file mode 100644 index 00000000..33626c13 --- /dev/null +++ b/docs/man_tables.lua @@ -0,0 +1,53 @@ +-- Render manual tables as labeled lists so shortcuts remain readable at narrow +-- terminal widths. Keep cell contents as Pandoc elements to preserve formatting. +function Table(manualTable) + local columnNames = {} + local headerRow = manualTable.head.rows[1] + if headerRow then + for columnIndex, cell in ipairs(headerRow.cells) do + columnNames[columnIndex] = pandoc.utils.stringify(cell.contents) + end + end + + local listItems = {} + local function append_row(row) + local itemBlocks = pandoc.List() + for columnIndex, cell in ipairs(row.cells) do + local cellBlocks = cell.contents:clone() + if columnIndex == 1 then + cellBlocks = cellBlocks:walk({ + Plain = function(block) + return pandoc.Para({pandoc.Strong(block.content)}) + end, + Para = function(block) + return pandoc.Para({pandoc.Strong(block.content)}) + end, + }) + elseif columnNames[columnIndex] then + local label = pandoc.Strong({pandoc.Str(columnNames[columnIndex] .. ":")}) + local firstBlock = cellBlocks[1] + if firstBlock and (firstBlock.t == "Plain" or firstBlock.t == "Para") then + firstBlock.content:insert(1, pandoc.Space()) + firstBlock.content:insert(1, label) + else + cellBlocks:insert(1, pandoc.Para({label})) + end + end + itemBlocks:extend(cellBlocks) + end + listItems[#listItems + 1] = itemBlocks + end + + for _, body in ipairs(manualTable.bodies) do + for _, row in ipairs(body.head) do + append_row(row) + end + for _, row in ipairs(body.body) do + append_row(row) + end + end + for _, row in ipairs(manualTable.foot.rows) do + append_row(row) + end + return pandoc.BulletList(listItems) +end diff --git a/docs/user.md b/docs/user.md index a2b80b63..90087fc9 100644 --- a/docs/user.md +++ b/docs/user.md @@ -8,6 +8,168 @@ consoles and can run in an X11 terminal as a regular user. For build requirements and setup, see the [README](../README.md). +## Starting and stopping + +Run these commands from the checkout root after building `fenrir`: + +```bash +# Linux text console, with console and keyboard device access +sudo ./fenrir + +# Linux console in the foreground (also use -f with service managers) +sudo ./fenrir -f + +# X11 terminal, as your regular user +./fenrir -x + +# Either mode can select the laptop layout +./fenrir -x --keyboard-layout laptop +``` + +Console mode starts in the background by default. Add `-f` to keep it in the +foreground, including under a service manager. The launcher returns after the +reader initializes and reports startup failures in the launching terminal. +Use `--debug-file PATH` to retain diagnostics after detaching. Help, version, +and configuration-only commands always run in the foreground. + +X11 mode (`-x`) always stays in the foreground because it owns the terminal +and its child shell. Press **Fenrir+Q** to quit either mode. Run only one +reader on a given Linux console. Separate X11 terminal instances can run at +once. + +X11 mode starts a shell in a new PTY. The launching terminal must supply valid +`DISPLAY` and `WINDOWID` environment variables. Keyboard interception is scoped +to that terminal window; a desktop screen reader can handle other windows. + +Console mode needs access to console devices, keyboard event devices, and +`/dev/uinput`. It releases keyboard input on graphical virtual terminals and +on text consoles selected in `screen.ignored_vts`. + +## Keyboard layouts and help + +The **Fenrir key** is Insert, Super/Windows, or keypad 0 with NumLock off. +Hold it while pressing the other keys in a shortcut. Bare keypad review keys +also require NumLock off. + +The desktop layout uses the numeric keypad. The laptop layout uses Fenrir +chords on the main keyboard. Select a layout with `--keyboard-layout desktop` +or `--keyboard-layout laptop`, or set `keyboard.layout` in the settings file. + +Press **Fenrir+H** or **Fenrir+F1** to enter the tutorial. Command shortcuts are +announced and described instead of executed. Up and Down browse commands; +Left and Right switch between active actions, unbound actions, and scripts. +Space repeats the current item. Escape or Fenrir+F1 exits. + +The complete layouts are [desktop.toml](../config/keyboard/desktop.toml) and +[laptop.toml](../config/keyboard/laptop.toml). + +## Screen review + +Review moves Fenrir's reading position without moving the application's cursor. +Return to the application cursor with the Follow cursor shortcut. By default, +application cursor movement or a screen change also leaves review. + +| Action | Desktop | Laptop | +| --- | --- | --- | +| Previous / current / next line | Keypad 7 / 8 / 9 | Fenrir+U / I / O | +| Previous / current / next word | Keypad 4 / 5 / 6 | Fenrir+J / K / L | +| Previous / current / next character | Keypad 1 / 2 / 3 | Fenrir+M / Comma / Period | +| Top / bottom of screen | Fenrir+Keypad 7 / 9 | Fenrir+Shift+U / O | +| Line start / end | Fenrir+Keypad 4 / 6 | Fenrir+Shift+J / L | +| First / last text on line | Fenrir+Keypad 1 / 3 | Fenrir+Control+J / L | +| Read screen | Fenrir+Keypad 5 | Fenrir+Shift+K | +| Screen before / after cursor | Fenrir+Keypad 8 / 2 | Fenrir+Shift+I / Comma | +| Character above / below | Fenrir+Control+Keypad 8 / 2 | Fenrir+Control+I / Comma | +| Follow cursor | Fenrir+Keypad Period | Fenrir+Slash | +| Review position | Keypad Period | Fenrir+Shift+Period | + +Press the current-line or current-word shortcut twice quickly to spell the +text, or three times for phonetic reading. Press the current-character shortcut +twice for its phonetic name. The default interval between presses is 200 ms; +`keyboard.multi_press_timeout_ms` changes it at launch. + +## Tracking modes + +Cycle through **cursor**, **table**, and **highlight** with +**Fenrir+Keypad Asterisk** on desktop or **Fenrir+Y** on laptop. +The startup selection is `reading.mode`. + +- **Cursor**: follows application cursor movement. +- **Table**: treats delimited or space-aligned text as cells. +- **Highlight**: follows changes to highlighted text, such as menu selections. + +In table mode, word navigation moves between cells, line start/end moves to the +first/last cell, and first/last text moves to the first/last character in the +current cell. Place review on a header row and press **Fenrir+X** to use its +cells as column headers. Outside table mode, Fenrir+X places a text-selection +mark. + +## Speech and automatic reading + +- **Control** interrupts speech. +- **Fenrir+F4** toggles mute. +- **Keypad Enter** on desktop temporarily silences speech. Speech can return on + the next keypress or a newly recognized prompt or completion. +- **Fenrir+Keypad Enter** on desktop, or **Fenrir+Enter** on laptop, toggles + automatic reading of new text. +- **Fenrir+Backspace** passes the next key through to the application. + +Character echo, word echo, and cursor echo can be configured separately. +Punctuation levels are `none`, `some`, `most`, and `all`. At `some` and `most`, +punctuation inside words, URLs, email addresses, and filenames is also named. +Apostrophes within words and single numeric separators such as those in `3.14`, +`1,000`, and `12:30` follow the selected level. Sentence punctuation follows the +selected level too. Character review always names punctuation. +Capital indicators, emoticons, repeated-symbol summaries, +indentation feedback, and attribute feedback are configurable in Reading. + +**Fenrir+T** announces the time; press twice quickly for the date. +**Fenrir+Shift+V** announces the Fenrir version. + +## Fenrir Menu and quick settings + +Press **Fenrir+F** to open the Fenrir Menu. It contains settings tabs for +presentation, speech, sound, reading, keyboard, internal UI, clipboard, and other options. +The form captures its navigation keys until it closes. + +- At the tab selector, use arrows to choose a tab. +- Tab and Shift+Tab move between controls and the tab selector. +- Space or Enter toggles a checkbox. +- Arrows change list selections and numeric values. +- Activate **OK** to save, or **Cancel** or Escape to discard the form's changes. + +Speech rate, pitch, and volume use a scale from -100 to 100. For speech rate, +Up and Down change the value by 10; Left and Right change it by 1. Sound volume is +between 0.0 and 1.0. Backend, device, keyboard layout, sound theme, and other +launch-only settings take effect on the next start. + +The **Internal UI** tab controls speech in Fenrir's own menus and dialogs. +**Announce grid row and column** and **Announce item position** are independent +checkboxes, both enabled by default. Disable coordinates to omit “row 1, column 2”; +disable item position to omit “2 of 20”. Item names and grid navigation remain +available with both disabled. Saved changes apply immediately to Emoji and +Spellcheck grids and persist across launches. These are global settings, so this +tab is omitted from application settings. The TOML keys are +`internal_ui.announce_grid_coordinates` and `internal_ui.announce_item_position`. + +Settings saves use mode `644` for the standard system settings file at +`/etc/fenrirscreenreader/settings/settings.toml`, so other users can read it as a +startup fallback. Personal settings and application profiles use mode `600`. + +Quick settings work without opening the form: + +| Shortcut | Action | +| --- | --- | +| Fenrir+Space | Announce current setting | +| Fenrir+Left / Right | Previous / next setting | +| Fenrir+Control+Left / Right | Previous / next category | +| Fenrir+Up / Down | Change value | +| Fenrir+Control+S | Save settings | + +Quick changes apply to the current session. Save to retain them for future +launches. Session controls such as mute and temporary silence are separate +from saved startup preferences. + ## Application overrides Press **Fenrir+A** to open **Applications**. Up and Down select an item; Enter @@ -241,168 +403,6 @@ Real acceptance still needs a Codex reply spoken once after completion, working status on demand, history review, composer typing, approval/question handling, and returning to ordinary reading after choosing Default or exiting Codex. -## Starting and stopping - -Run these commands from the checkout root after building `fenrir`: - -```bash -# Linux text console, with console and keyboard device access -sudo ./fenrir - -# Linux console in the foreground (also use -f with service managers) -sudo ./fenrir -f - -# X11 terminal, as your regular user -./fenrir -x - -# Either mode can select the laptop layout -./fenrir -x --keyboard-layout laptop -``` - -Console mode starts in the background by default. Add `-f` to keep it in the -foreground, including under a service manager. The launcher returns after the -reader initializes and reports startup failures in the launching terminal. -Use `--debug-file PATH` to retain diagnostics after detaching. Help, version, -and configuration-only commands always run in the foreground. - -X11 mode (`-x`) always stays in the foreground because it owns the terminal -and its child shell. Press **Fenrir+Q** to quit either mode. Run only one -reader on a given Linux console. Separate X11 terminal instances can run at -once. - -X11 mode starts a shell in a new PTY. The launching terminal must supply valid -`DISPLAY` and `WINDOWID` environment variables. Keyboard interception is scoped -to that terminal window; a desktop screen reader can handle other windows. - -Console mode needs access to console devices, keyboard event devices, and -`/dev/uinput`. It releases keyboard input on graphical virtual terminals and -on text consoles selected in `screen.ignored_vts`. - -## Keyboard layouts and help - -The **Fenrir key** is Insert, Super/Windows, or keypad 0 with NumLock off. -Hold it while pressing the other keys in a shortcut. Bare keypad review keys -also require NumLock off. - -The desktop layout uses the numeric keypad. The laptop layout uses Fenrir -chords on the main keyboard. Select a layout with `--keyboard-layout desktop` -or `--keyboard-layout laptop`, or set `keyboard.layout` in the settings file. - -Press **Fenrir+H** or **Fenrir+F1** to enter the tutorial. Command shortcuts are -announced and described instead of executed. Up and Down browse commands; -Left and Right switch between active actions, unbound actions, and scripts. -Space repeats the current item. Escape or Fenrir+F1 exits. - -The complete layouts are [desktop.toml](../config/keyboard/desktop.toml) and -[laptop.toml](../config/keyboard/laptop.toml). - -## Screen review - -Review moves Fenrir's reading position without moving the application's cursor. -Return to the application cursor with the Follow cursor shortcut. By default, -application cursor movement or a screen change also leaves review. - -| Action | Desktop | Laptop | -| --- | --- | --- | -| Previous / current / next line | Keypad 7 / 8 / 9 | Fenrir+U / I / O | -| Previous / current / next word | Keypad 4 / 5 / 6 | Fenrir+J / K / L | -| Previous / current / next character | Keypad 1 / 2 / 3 | Fenrir+M / Comma / Period | -| Top / bottom of screen | Fenrir+Keypad 7 / 9 | Fenrir+Shift+U / O | -| Line start / end | Fenrir+Keypad 4 / 6 | Fenrir+Shift+J / L | -| First / last text on line | Fenrir+Keypad 1 / 3 | Fenrir+Control+J / L | -| Read screen | Fenrir+Keypad 5 | Fenrir+Shift+K | -| Screen before / after cursor | Fenrir+Keypad 8 / 2 | Fenrir+Shift+I / Comma | -| Character above / below | Fenrir+Control+Keypad 8 / 2 | Fenrir+Control+I / Comma | -| Follow cursor | Fenrir+Keypad Period | Fenrir+Slash | -| Review position | Keypad Period | Fenrir+Shift+Period | - -Press the current-line or current-word shortcut twice quickly to spell the -text, or three times for phonetic reading. Press the current-character shortcut -twice for its phonetic name. The default interval between presses is 200 ms; -`keyboard.multi_press_timeout_ms` changes it at launch. - -## Tracking modes - -Cycle through **cursor**, **table**, and **highlight** with -**Fenrir+Keypad Asterisk** on desktop or **Fenrir+Y** on laptop. -The startup selection is `reading.mode`. - -- **Cursor**: follows application cursor movement. -- **Table**: treats delimited or space-aligned text as cells. -- **Highlight**: follows changes to highlighted text, such as menu selections. - -In table mode, word navigation moves between cells, line start/end moves to the -first/last cell, and first/last text moves to the first/last character in the -current cell. Place review on a header row and press **Fenrir+X** to use its -cells as column headers. Outside table mode, Fenrir+X places a text-selection -mark. - -## Speech and automatic reading - -- **Control** interrupts speech. -- **Fenrir+F4** toggles mute. -- **Keypad Enter** on desktop temporarily silences speech. Speech can return on - the next keypress or a newly recognized prompt or completion. -- **Fenrir+Keypad Enter** on desktop, or **Fenrir+Enter** on laptop, toggles - automatic reading of new text. -- **Fenrir+Backspace** passes the next key through to the application. - -Character echo, word echo, and cursor echo can be configured separately. -Punctuation levels are `none`, `some`, `most`, and `all`. At `some` and `most`, -punctuation inside words, URLs, email addresses, and filenames is also named. -Apostrophes within words and single numeric separators such as those in `3.14`, -`1,000`, and `12:30` follow the selected level. Sentence punctuation follows the -selected level too. Character review always names punctuation. -Capital indicators, emoticons, repeated-symbol summaries, -indentation feedback, and attribute feedback are configurable in Reading. - -**Fenrir+T** announces the time; press twice quickly for the date. -**Fenrir+Shift+V** announces the Fenrir version. - -## Fenrir Menu and quick settings - -Press **Fenrir+F** to open the Fenrir Menu. It contains settings tabs for -presentation, speech, sound, reading, keyboard, internal UI, clipboard, and other options. -The form captures its navigation keys until it closes. - -- At the tab selector, use arrows to choose a tab. -- Tab and Shift+Tab move between controls and the tab selector. -- Space or Enter toggles a checkbox. -- Arrows change list selections and numeric values. -- Activate **OK** to save, or **Cancel** or Escape to discard the form's changes. - -Speech rate, pitch, and volume use a scale from -100 to 100. For speech rate, -Up and Down change the value by 10; Left and Right change it by 1. Sound volume is -between 0.0 and 1.0. Backend, device, keyboard layout, sound theme, and other -launch-only settings take effect on the next start. - -The **Internal UI** tab controls speech in Fenrir's own menus and dialogs. -**Announce grid row and column** and **Announce item position** are independent -checkboxes, both enabled by default. Disable coordinates to omit “row 1, column 2”; -disable item position to omit “2 of 20”. Item names and grid navigation remain -available with both disabled. Saved changes apply immediately to Emoji and -Spellcheck grids and persist across launches. These are global settings, so this -tab is omitted from application settings. The TOML keys are -`internal_ui.announce_grid_coordinates` and `internal_ui.announce_item_position`. - -Settings saves use mode `644` for the standard system settings file at -`/etc/fenrirscreenreader/settings/settings.toml`, so other users can read it as a -startup fallback. Personal settings and application profiles use mode `600`. - -Quick settings work without opening the form: - -| Shortcut | Action | -| --- | --- | -| Fenrir+Space | Announce current setting | -| Fenrir+Left / Right | Previous / next setting | -| Fenrir+Control+Left / Right | Previous / next category | -| Fenrir+Up / Down | Change value | -| Fenrir+Control+S | Save settings | - -Quick changes apply to the current session. Save to retain them for future -launches. Session controls such as mute and temporary silence are separate -from saved startup preferences. - ## Clipboard Use review navigation to place two marks around text with **Fenrir+X**, then @@ -477,6 +477,30 @@ Sound output uses `pulse` for PulseAudio or PipeWire's PulseAudio service, or `alsa` for an ALSA PCM device. Select the driver and theme in Sound settings. The ALSA device name is set with `presentation.sound.device` in the file. +Speech Dispatcher has its own synthesis and audio configuration, separate +from Fenrir's sound cues. For an X11 terminal session, run `./fenrir -x` as +your regular user. With sudo, Fenrir uses `SUDO_UID` to locate the invoking +user's PulseAudio socket. The default sound driver tries the user session, +then `/tmp/pulse.sock`, then `/run/pulse/native`. Set `PULSE_SERVER` to select +an explicit server; the sound driver does not read PulseAudio's `client.conf`. +The sound service must already be running and accessible to the reader. +See [Speech synthesizers](#speech-synthesizers) for Speech Dispatcher addresses +and autostart behavior. + +If root sound needs additional configuration, the checkout includes +[configure_pulse.sh](../tools/configure_pulse.sh) for PulseAudio and +[configure_pipewire.sh](../tools/configure_pipewire.sh) for PipeWire. +Read the appropriate script before running it. Each has a regular-user part +and a root part, and writes audio configuration files. The PulseAudio script +replaces the user's `default.pa` and configures an anonymously accessible socket +at `/tmp/pulse.sock`. The PipeWire script also changes WirePlumber settings. +These scripts change system audio configuration and are only needed when the +existing audio setup does not provide the required access. + +For ALSA, select `driver = "alsa"` under `[presentation.sound]` in +`settings.toml` and set `device` to the desired PCM name, such as `"default"`. +Driver and device changes apply at the next launch. + Personal themes are searched under `~/.local/stormux/fenrir/sounds/` before bundled and system themes. A theme contains WAV files and `soundicons.toml`. @@ -660,8 +684,10 @@ values. `./fenrir --version` identifies the build when reporting a problem. ## Documentation maintenance and support `docs/user.md` is the shared manual source. With Pandoc installed, run -`./docs/create_manpage.sh` to regenerate `docs/user.txt`, `docs/fenrir.adoc`, and -`docs/fenrir.1`. The script can run from any working directory. +`./docs/create_manpage.sh` to regenerate `docs/user.txt` and `docs/fenrir.1`. +The script can run from any working directory. Edit the Markdown source rather +than the generated files. The bundled `docs/man_tables.lua` filter renders +man-page tables as labeled lists for narrow terminals. Please review documentation and test instructions for the affected workflow before submitting a change, as described in the README's contribution policy. diff --git a/docs/user.txt b/docs/user.txt index a097b957..23856aca 100644 --- a/docs/user.txt +++ b/docs/user.txt @@ -135,7 +135,11 @@ Speech and automatic reading - Fenrir+Backspace passes the next key through to the application. Character echo, word echo, and cursor echo can be configured separately. -Punctuation levels are none, some, most, and all. Character review still +Punctuation levels are none, some, most, and all. At some and most, +punctuation inside words, URLs, email addresses, and filenames is also +named. Apostrophes within words and single numeric separators such as +those in 3.14, 1,000, and 12:30 follow the selected level. Sentence +punctuation follows the selected level too. Character review always names punctuation. Capital indicators, emoticons, repeated-symbol summaries, indentation feedback, and attribute feedback are configurable in Reading. @@ -146,8 +150,9 @@ Fenrir+Shift+V announces the Fenrir version. Fenrir Menu and quick settings Press Fenrir+F to open the Fenrir Menu. It contains settings tabs for -presentation, speech, sound, reading, keyboard, internal UI, clipboard, and other -options. The form captures its navigation keys until it closes. +presentation, speech, sound, reading, keyboard, internal UI, clipboard, +and other options. The form captures its navigation keys until it +closes. - At the tab selector, use arrows to choose a tab. - Tab and Shift+Tab move between controls and the tab selector. @@ -157,9 +162,10 @@ options. The form captures its navigation keys until it closes. changes. Speech rate, pitch, and volume use a scale from -100 to 100. For speech -rate, Up and Down change the value by 10; Left and Right change it by 1. Sound -volume is between 0.0 and 1.0. Backend, device, keyboard layout, sound -theme, and other launch-only settings take effect on the next start. +rate, Up and Down change the value by 10; Left and Right change it by 1. +Sound volume is between 0.0 and 1.0. Backend, device, keyboard layout, +sound theme, and other launch-only settings take effect on the next +start. The Internal UI tab controls speech in Fenrir’s own menus and dialogs. Announce grid row and column and Announce item position are independent @@ -190,6 +196,259 @@ Quick changes apply to the current session. Save to retain them for future launches. Session controls such as mute and temporary silence are separate from saved startup preferences. +Application overrides + +Press Fenrir+A to open Applications. Up and Down select an item; Enter +activates it and closes the menu. Escape or the Cancel button closes the +menu without changing the selection. Tab and Shift+Tab move between the +list, Copy alias to clipboard, Cancel, and the tab selector. The active +override is initially selected. + +Type the start of a name to jump to it: n selects NetworkManager, then i +selects Ninjam. Searching ignores letter case and does not activate the +item. Backspace removes a search character; Control+Backspace clears the +search. Up, Down, or moving focus away from the list also clears the +search. Unmatched characters announce No matches and leave the last +matching search intact. + +Start the application normally, then select its override. This menu does +not launch programs. Choose Default to restore the settings and reading +regions that were in effect before the override. Overrides are built +into Fenrir and are never selected automatically or remembered across +restarts. + +To launch a program with its override, select its name in this list, +then Tab to Copy alias to clipboard and press Enter. This copies a Bash +alias into Fenrir’s clipboard without selecting the override or closing +the menu. Paste it into your Bash configuration, such as ~/.bashrc, and +load that configuration in a new shell. The aliases use the executable +names cninjam, nmtui, rogue, and codex; Default has no alias. Existing +aliases with those names are replaced when you load the definition. + +The alias requests the override through socat, then launches the real +program with the arguments you supplied. Errors remain visible, and the +program still launches if the request fails, remote control is disabled, +or Fenrir or socat is missing. Socket inactivity is limited to two +seconds. It uses the standard socket, or your configured +remote.socket_file, rather than a process-specific socket that changes +at restart. Paste with Fenrir’s clipboard command; desktop clipboard +synchronization follows your normal clipboard settings. + +While an override is active, Fenrir+F opens its application settings, +with the application name in the title. OK, Fenrir+Control+S, and remote +SETTING SAVE save that application’s preferences. Quick and remote +changes remain temporary until saved. Default settings are kept +separately, including any unsaved adjustments made before activating the +override. + +Profiles live in an applications/ subdirectory of the ordinary settings +save directory. For example: + +- System settings: + /etc/fenrirscreenreader/settings/applications/ninjam.toml. +- Personal settings: + ~/.local/share/stormux/fenrirscreenreader/settings/applications/ninjam.toml. +- With --config /path/settings.toml: /path/applications/ninjam.toml. + +NetworkManager uses networkmanager.toml; Rogue uses rogue.toml; Codex +uses codex.toml. The directory and file are created on the first +successful save. Files contain only customized settings; omitted values +inherit ordinary settings followed by the built-in application defaults. +A customized voice selection saves module, language, and voice together +so a later change to the ordinary voice does not break that selection. +Profiles are loaded when their override first recognizes the +application; restarting Fenrir still starts with Default. Reselect the +application to reload an edited file. + +Application settings include voice, rate, pitch, volume, reading +behavior, sound enablement/volume, and typing echo. Startup settings +such as speech driver, serial device, keyboard layout, and sound theme +remain in the default menu; clipboard storage and console routing also +remain global. Session mute and the temporary automatic-reading toggle +are not implicitly saved. Remote SAVEAS and screen#ignore_screen are +unavailable while an application is active. Saving is blocked during +context transitions or failed voice restoration. A malformed profile or +unavailable voice leaves ordinary settings in effect and reports an +error. Select the application again after correcting the file. + +For example, a rate-only profile contains current TOML setting names, +with no top-level version field: + + [presentation.speech] + rate = 40 + +To make a saved value inherit again, remove that setting from the +application file and reselect the override. Removing the file restores +complete inheritance. + +An override belongs to its terminal. Switching VTs or leaving the +terminal suspends its effects; returning resumes it when the application +screen is recognized. Unrecognized screens use ordinary reading. A fresh +shell prompt on the cursor row, with the application interface absent, +clears the selection and announces Default settings restored. This works +from screen contents, so it does not require access to remote processes +over SSH or inside GNU Screen. Unusual prompts may require selecting +Default manually. Password prompts, confirmation questions, and old +shell prompts in scrollback do not clear it. Split windows, heavily +customized application colors, and localized or unfamiliar layouts may +prevent recognition; Default is always available. + +NetworkManager targets nmtui and temporarily selects highlight tracking. + +Ninjam targets the cninjam curses client. It announces selected controls +with master, metronome, or channel context; mute, transmit, and receive +controls are spoken as states. While adjusting a volume/pan control, +Left/Right announces only the pan percentage and direction, or center; +Up/Down announces only the volume, with minus and plus spoken +explicitly. Peak meters and the interval bar remain quiet. Navigating +the application’s controls after manual review resumes tracking; passive +updates leave review in place. Incoming chat is announced independently +of control changes. The following shortcuts temporarily replace numbered +region reads: + + Shortcut Ninjam information + ---------- --------------------------------------------------- + Fenrir+1 Latest new chat message received since activation + Fenrir+2 Selected channel or control + Fenrir+3 Connection, tempo, and interval position + +Fenrir+Shift+B lists these application assignments. Existing user +regions are preserved and restored when the override ends. Review, Read +Screen, and copying continue to expose the actual terminal text, +including its literal X marks. Chat history is baselined on activation, +resize, buffer changes, and history paging to avoid announcing old +messages as new. Messages that appear and disappear between screen +captures cannot be recovered reliably. + +The laptop shortcut for reading attributes is Fenrir+Shift+A; Fenrir+A +opens Applications in both layouts. + +Rogue targets the classic English Rogue 5.4 terminal game, including the +5.4.4 package. Start rogue, then select Rogue with Fenrir+A (type r, +then Enter). The override announces messages and prompts, including +More; press Space, changes in health, hunger, dungeon level, strength, +armor, and experience level, and newly visible monsters, objects, doors, +stairs, and traps. Ordinary map redraws stay quiet. Moving monsters are +compared by letter and count to reduce repeated announcements. Their +current positions remain available on request. Appearing and +disappearing messages between captures, or one monster replacing another +of the same letter without a count change, cannot reliably be +distinguished from the visible display alone. + + Shortcut Rogue information + ---------- -------------------------------------------------------- + Fenrir+1 Health, for example “Health: 12 of 12” + Fenrir+2 Full status line + Fenrir+3 Latest message or prompt + Fenrir+4 Eight neighboring cells, clockwise from north + Fenrir+5 Visible monsters and their positions relative to you + Fenrir+6 Visible objects and landmarks, with relative positions + +These explicit reads include their region names. Names are supported by +the shared application-region API, so other overrides can use them as +needed. Fenrir+Shift+B lists the assignments. Slots 7 through 10 remain +available for manual regions. Existing user regions are preserved while +the override is active and restored when it ends, as with Ninjam. + +Directions give cell offsets, for example “bat, north 2, east 3.” +Descriptions use only displayed symbols; they do not reveal hidden +monsters, secret doors, or unidentified items. Blank cells are described +as unseen, and hallucination or disguised monsters can make the +displayed symbols misleading. The playfield uses the first 24 rows and +80 columns even in a larger terminal, with status on row 24. Inventory, +help, and options pages use ordinary reading and review; map regions +report information unavailable on those pages. Slow inventory uses the +ordinary message region. Read Screen, review, and copying retain the +actual terminal text. Unrecognized screens return to ordinary reading. + +Developers can exercise an installed classic Rogue in private PTYs +without touching the active reader: + + FENRIR_ROGUE_TEST_BINARY=/usr/bin/rogue go test ./internal/terminal -run TestRogueLiveOverride -v + +Codex CLI + +The Codex override replaces automatic terminal diffs with completed +assistant messages. It suppresses tool output, streaming fragments, and +redraws. Manual screen review remains available, and typing echo is +limited to the composer. Messages are retained in memory even while +speech is muted or automatic reading is disabled. Existing lifecycle +alerts remain separate from reply reading. Left/Right navigation retains +character feedback inside the composer. While Codex is working, the +existing activity tone sounds once every four seconds without requiring +redraws. It stops for approval/question waits and completion, respects +the sound and progress toggles, and remains independent of speech mute. + + ----------------------------------------------------------------------- + Shortcut Codex information + ----------------------------------- ----------------------------------- + Fenrir+1 through Fenrir+9 Recent completed assistant + messages, newest first + + Fenrir+0 Working, ready, waiting for + approval or an answer, finished, or + interrupted + ----------------------------------------------------------------------- + +Select Codex with Fenrir+A, type c, then Enter. Select Default to leave +the override. Without its bridge, the override suppresses automatic +screen changes and the status shortcut says Waiting for Codex bridge. + +The optional bridge requires Python 3 and Fenrir remote command control +enabled. Merge a command hook invoking +python3 /absolute/path/to/fenrir/tools/codex_bridge.py into each of +these events in ~/.codex/hooks.json: SessionStart, UserPromptSubmit, +PermissionRequest, PostToolUse, Stop, Interrupt, and SessionEnd. For +example, one event entry is: + + { + "hooks": { + "SessionStart": [{"hooks": [{"type": "command", "command": "python3 /absolute/path/to/fenrir/tools/codex_bridge.py", "timeout": 3}]}] + } + } + +Preserve existing hooks. Review and trust the added hooks through +Codex’s /hooks interface; restart or resume Codex if required by the +installed version. Starting or resuming a conversation starts a detached +bridge follower; submitting a prompt also restarts it if necessary. The +bridge never speaks or answers approvals. + +The follower reads only the transcript path supplied by that +conversation’s hook and verifies its session identifier. It accepts +completed assistant events, never tool results, reasoning, or streamed +deltas. It keeps the nine latest replies in memory and writes only +status, conversation identifiers, and lock files to a private runtime +directory. Switching conversations replaces the terminal binding; +packets from an older conversation cannot overwrite the new history. +Existing history is loaded silently on attachment or bridge restart. +Long replies are transported in chunks and announced only after +assembly; the current per-message limit is 1 MiB. The worker stops when +Codex exits or a SessionEnd hook runs. + +X11 routing requires the same WINDOWID as the Fenrir instance; Linux +console routing uses Codex’s controlling VT and the reader’s existing +authorization. Other windows and consoles do not receive the replies. +This local bridge does not forward SSH audio or attach a remote Codex +server to a local terminal. + +The transcript parser was checked against Codex CLI 0.165.0. It uses +Codex’s local completed-event records, rather than a public subscription +API; a changed record format may require an update. Status comes from +hooks and recorded turn events, not the terminal’s animated activity +label. Check Fenrir+0 after resuming. + +For a private protocol and keyboard test without live speech or Codex +requests: + + go build -o fenrir ./cmd/fenrir + python3 -m unittest discover -s tests -p test_codex_bridge.py + xvfb-run -a env FENRIR_PRIVATE_X11=1 python3 tests/native_codex.py + +Real acceptance still needs a Codex reply spoken once after completion, +working status on demand, history review, composer typing, +approval/question handling, and returning to ordinary reading after +choosing Default or exiting Codex. + Clipboard Use review navigation to place two marks around text with Fenrir+X, then @@ -267,6 +526,30 @@ or alsa for an ALSA PCM device. Select the driver and theme in Sound settings. The ALSA device name is set with presentation.sound.device in the file. +Speech Dispatcher has its own synthesis and audio configuration, +separate from Fenrir’s sound cues. For an X11 terminal session, run +./fenrir -x as your regular user. With sudo, Fenrir uses SUDO_UID to +locate the invoking user’s PulseAudio socket. The default sound driver +tries the user session, then /tmp/pulse.sock, then /run/pulse/native. +Set PULSE_SERVER to select an explicit server; the sound driver does not +read PulseAudio’s client.conf. The sound service must already be running +and accessible to the reader. See Speech synthesizers for Speech +Dispatcher addresses and autostart behavior. + +If root sound needs additional configuration, the checkout includes +configure_pulse.sh for PulseAudio and configure_pipewire.sh for +PipeWire. Read the appropriate script before running it. Each has a +regular-user part and a root part, and writes audio configuration files. +The PulseAudio script replaces the user’s default.pa and configures an +anonymously accessible socket at /tmp/pulse.sock. The PipeWire script +also changes WirePlumber settings. These scripts change system audio +configuration and are only needed when the existing audio setup does not +provide the required access. + +For ALSA, select driver = "alsa" under [presentation.sound] in +settings.toml and set device to the desired PCM name, such as "default". +Driver and device changes apply at the next launch. + Personal themes are searched under ~/.local/stormux/fenrir/sounds/ before bundled and system themes. A theme contains WAV files and soundicons.toml. @@ -289,10 +572,10 @@ options. Fenrir loads one settings file rather than merging files. - --config PATH: use that file explicitly. If default files are absent, Fenrir uses built-in defaults. Python -settings.conf files are ignored. Regular users save to their personal TOML -file, including when startup loaded system defaults; root saves to the system -file. --config PATH explicitly selects both the load and save path, and that -file must exist at startup. +settings.conf files are ignored. Regular users save to their personal +TOML file, including when startup loaded system defaults; root saves to +the system file. --config PATH explicitly selects both the load and save +path, and that file must exist at startup. Create a personal settings file and validate it without opening devices: @@ -311,6 +594,27 @@ Speech synthesizers The default driver is speech-dispatcher. Select its module, language, and voice in Speech settings; empty values use server defaults. +Fenrir uses SPEECHD_ADDRESS when set: unix_socket:/absolute/path selects +a Unix socket, and inet_socket:HOST:PORT selects TCP (default host +127.0.0.1, port 6560). Invalid addresses report an error. Otherwise, +SPEECHD_SOCKET selects a Unix socket. Without either variable, Fenrir +uses $XDG_RUNTIME_DIR/speech-dispatcher/speechd.sock, falling back to +$XDG_CACHE_HOME/speech-dispatcher/speechd.sock or +~/.cache/speech-dispatcher/speechd.sock when the runtime directory is +unset. With sudo, the default remains +/run/user/$SUDO_UID/speech-dispatcher/speechd.sock. Sudo may remove +address variables; preserve them explicitly if needed. + +When opening speech, Fenrir first tries the selected server. If its +socket is missing or refuses connections, Fenrir requests +speech-dispatcher --spawn and briefly retries. The daemon’s +configuration controls whether spawning is allowed. SPEECHD_CMD can +select another executable. Autostart is limited to Unix sockets and +local loopback TCP servers. When sudo changes the account, start Speech +Dispatcher as the invoking user first; Fenrir will not launch a +root-owned daemon for that user. Settings discovery never starts a +daemon or interrupts the active speaking client. + Hardware driver IDs are dectalk, dectalk-usb, litetalk, doubletalk, and tripletalk. Serial drivers require presentation.speech.hardware_device, for example /dev/ttyUSB0, and default to 9600 baud when @@ -360,6 +664,14 @@ Other supported operations include: - command tempdisablespeech: temporarily silence speech. - command clipboard TEXT: add text to the clipboard. - command exportclipboard: export the selected clipboard. +- command application ID: select default, networkmanager, ninjam, rogue, + or codex for this reader’s active terminal, like Fenrir+A. This + requires command remote control. An empty reply means selection + succeeded; invalid IDs, busy settings, and restoration failures return + an error. Application recognition and profile loading happen later, + when the application appears; this command does not launch it. default + restores ordinary settings. Use a private socket when targeting a + particular reader. - command window X1 Y1 X2 Y2: set a reading window, using zero-based, inclusive coordinates. - command resetwindow: clear the window. @@ -451,8 +763,10 @@ problem. Documentation maintenance and support docs/user.md is the shared manual source. With Pandoc installed, run -./docs/create_manpage.sh to regenerate docs/user.txt, docs/fenrir.adoc, -and docs/fenrir.1. The script can run from any working directory. +./docs/create_manpage.sh to regenerate docs/user.txt and docs/fenrir.1. +The script can run from any working directory. Edit the Markdown source +rather than the generated files. The bundled docs/man_tables.lua filter +renders man-page tables as labeled lists for narrow terminals. Please review documentation and test instructions for the affected workflow before submitting a change, as described in the README’s diff --git a/internal/app/punctuation_test.go b/internal/app/punctuation_test.go index 8cbd6cab..0846c3d2 100644 --- a/internal/app/punctuation_test.go +++ b/internal/app/punctuation_test.go @@ -66,6 +66,28 @@ func TestURLSpeechKeepsEmbeddedPunctuationAndOriginalClipboardText(t *testing.T) } } +func TestPackageProgressBarSpeechKeepsOriginalClipboardText(t *testing.T) { + processor, _, err := punctuation.Load([]string{"../../config/punctuation/default.toml"}) + if err != nil { + t.Fatal(err) + } + settings := config.Defaults() + settings.Reading.RepeatedSymbols = true + voice := &fakeVoice{} + echo := &echoSpeaker{Speaker: voice, punctuation: processor, punctuationLevel: "some", settings: &settings} + text := "(123/189) upgrading nodejs-lts-jod" + strings.Repeat(" ", 157) + "[" + strings.Repeat("#", 120) + "] 100%" + echo.Say(text) + if len(voice.messages) != 1 { + t.Fatal("missing progress speech", voice.messages) + } + if got := strings.Join(strings.Fields(voice.messages[0]), " "); got != "123 slash 189 upgrading nodejs dash lts dash jod 120 number 100" { + t.Fatal("progress bar did not summarize", got) + } + if echo.last != text { + t.Fatal("clipboard echo lost original progress bar", echo.last) + } +} + func TestReviewCharactersUseProfileRegardlessOfLevelAndKeepBoundaries(t *testing.T) { processor, _, err := punctuation.Load([]string{"../../config/punctuation/fr.toml"}) if err != nil { diff --git a/internal/punctuation/text_processing.go b/internal/punctuation/text_processing.go index e09df9f7..27354e76 100644 --- a/internal/punctuation/text_processing.go +++ b/internal/punctuation/text_processing.go @@ -47,10 +47,24 @@ func (p *Processor) process_token(token, level string, options TextOptions) stri } } if options.RepeatedSymbols && options.RepeatedSymbolsMin >= 2 && token != "" { - char, _ := utf8.DecodeRuneInString(token) + repeated := token + prefix, suffix := "", "" + // Progress bars commonly enclose a repeated run in brackets. Keep + // those delimiters under ordinary punctuation handling, and require + // the entire interior to repeat so paths and mixed tokens stay literal. + if len(token) >= 2 { + for _, pair := range []string{"[]", "()", "{}", "<>"} { + if token[0] == pair[0] && token[len(token)-1] == pair[1] { + prefix, suffix = token[:1], token[len(token)-1:] + repeated = token[1 : len(token)-1] + break + } + } + } + char, _ := utf8.DecodeRuneInString(repeated) if p.levels[level][char] && (unicode.IsPunct(char) || unicode.IsSymbol(char)) { count := 0 - for _, next := range token { + for _, next := range repeated { if next != char { return p.Text(token, level) } @@ -66,7 +80,7 @@ func (p *Processor) process_token(token, level string, options TextOptions) stri name = i18n.T(name) } } - return i18n.Format("{0} {1}", strconv.Itoa(count), name) + return p.Text(prefix, level) + i18n.Format("{0} {1}", strconv.Itoa(count), name) + p.Text(suffix, level) } } } diff --git a/internal/punctuation/text_processing_test.go b/internal/punctuation/text_processing_test.go index b7a554e4..5ffa955f 100644 --- a/internal/punctuation/text_processing_test.go +++ b/internal/punctuation/text_processing_test.go @@ -28,13 +28,17 @@ func TestTextSubstitutionsAndBoundaries(t *testing.T) { {"#### ####", "4 number 4 number"}, {"##### title #####", "5 number title 5 number"}, {"////", "4 slash"}, + {"[#####]", " 5 number "}, + {"(#####)", " 5 number "}, + {"{#####}", " 5 number "}, + {"<#####>", " 5 number "}, {"Oo", "WTF?"}, } { if got := processor.Process_text(trial.text, "some", options); got != trial.want { t.Errorf("%q: %q, want %q", trial.text, got, trial.want) } } - for _, text := range []string{"hello :)!", "path/to:)file", "https://example.invalid/:)", "https://example.invalid/:-/", "(o_O)", "###", "##--##", "a####b", "../....", "https://example.invalid/####", "10000", "aaaa", "🙂🙂🙂🙂", ""} { + for _, text := range []string{"hello :)!", "path/to:)file", "https://example.invalid/:)", "https://example.invalid/:-/", "(o_O)", "###", "##--##", "a####b", "../....", "https://example.invalid/####", "10000", "aaaa", "🙂🙂🙂🙂", "", "[]", "[###]", "[####)", "[##--##]", "[a####b]", "[../....]", "[https://example.invalid/####]"} { for _, level := range Levels() { if got, want := processor.Process_text(text, level, options), processor.Text(text, level); got != want { t.Errorf("%q, %s changed ordinary processing: %q, want %q", text, level, got, want) @@ -101,6 +105,30 @@ func TestSubstitutionOptionsAndProfileNames(t *testing.T) { } } +func TestBracketedRepeatedSymbolsRespectOptionsAndLevels(t *testing.T) { + processor, _, err := Load([]string{"../../config/punctuation/default.toml"}) + if err != nil { + t.Fatal(err) + } + for _, level := range Levels() { + for _, options := range []TextOptions{ + {}, + {Emoticons: true}, + {RepeatedSymbols: true, RepeatedSymbolsMin: 1}, + {RepeatedSymbols: true, RepeatedSymbolsMin: 6}, + {RepeatedSymbols: true, RepeatedSymbolsMin: 4}, + } { + want := processor.Text("[#####]", level) + if options.RepeatedSymbols && options.RepeatedSymbolsMin == 4 && level != "none" { + want = processor.Text("[", level) + "5 number" + processor.Text("]", level) + } + if got := processor.Process_text("[#####]", level, options); got != want { + t.Errorf("level %s, options %+v: got %q, want %q", level, options, got, want) + } + } + } +} + func TestTextSubstitutionTranslationsAndFieldOrder(t *testing.T) { root := t.TempDir() directory := filepath.Join(root, "xx", "LC_MESSAGES")