# Fenrir Fenrir is a console screen reader for Linux, written in Go. It reads the Linux text console and can also run inside an X11 terminal without root privileges. **Current maintainer:** Storm Dragon **Original creator:** Chrys (see [CREDITS](CREDITS)) See the [user manual](docs/user.md) for detailed usage documentation. ## Features - Screen review by line, word, and character, including spelling and phonetic reading. - Cursor, table, and highlight tracking. - Desktop and laptop keyboard layouts, with an interactive tutorial. - A keyboard-operated settings menu and quick settings shortcuts. - Temporary built-in application overrides for Ninjam, NetworkManager's nmtui, classic Rogue, and Codex CLI (with an optional event bridge). - Speech Dispatcher and hardware speech synthesizer support. - Sound icons, progress tones, and indentation and text attribute feedback. - Multiple clipboards, optional persistent history, and X clipboard synchronization. - Spell checking, spelling suggestions, and an emoji picker. - Script shortcuts and Unix socket remote control. - Translated announcements and help. ## Build and run Build requirements: - Linux. The build script obtains the Go version required by [go.mod](go.mod) if a suitable version is not installed. - A C compiler and `pkg-config`. - Development headers and libraries for libevdev, X11, libvterm, and ALSA. Their pkg-config names are `libevdev`, `x11`, `vterm`, and `alsa`. Runtime requirements depend on the features you use: - Speech Dispatcher with a configured synthesizer for the default speech driver. - PulseAudio or PipeWire's PulseAudio service for the default sound driver; ALSA output is also available. - Enchant 2 and a dictionary for your language for spell checking. - `xclip` for X clipboard synchronization. - libusb 1.0 for native DECtalk USB speech, or access to the configured serial device for a serial synthesizer. From the checkout root: ```bash ./build-fenrir.sh ./fenrir --config config/settings/settings.toml --check-config ``` The script uses Go's automatic toolchain selection with an installed Go 1.21 or newer. With older Go or no Go installed, it downloads the required official Go archive using `curl` and `tar` and caches it under `${XDG_CACHE_HOME:-$HOME/.cache}/fenrir/toolchains/`. Downloads require internet access. It does not replace the system Go or install native dependencies. By default, the resulting binary is `fenrir` in the checkout root, even when the script is invoked from another directory. Use `--output PATH` (or `-o PATH`) to select a different filename; relative paths are resolved from the caller's working directory and missing parent directories are created. To build manually with a suitable Go installation, use `go build -o fenrir ./cmd/fenrir`. Keep the binary beside the `config/` directory when running from the checkout; keyboard layouts, punctuation profiles, and sound themes are runtime resources. ### Building for packages The script can help build on distributions with older system Go versions, including Debian and Slint, provided the required Go toolchain supports the system and compatible native development libraries are available. Build in the target distribution's environment so the binary links against its libraries; a binary built on a newer distribution may require newer libraries at runtime. Compatibility with a particular distribution release still needs testing. Choose an output file and pass Go build flags after `--`: ```bash ./build-fenrir.sh --output build/fenrir -- -trimpath -mod=readonly ``` The script builds from its own checkout. It inherits `GOFLAGS`, `GOCACHE`, `GOMODCACHE`, `GOTMPDIR`, and CGO compiler/linker settings. Output selection controls the final binary; use those Go variables to relocate caches and temporary compilation files. `GOTMPDIR` must already exist. For example, an Arch recipe can build under `$srcdir` and stage the binary under `$pkgdir` in its separate packaging step: ```bash # In build(), from the extracted source checkout: GOMODCACHE="$srcdir/go-mod" GOCACHE="$srcdir/go-build" \ ./build-fenrir.sh --output "$srcdir/build/fenrir" -- \ -trimpath -mod=readonly -modcacherw -buildmode=pie # In package(): install -Dm755 "$srcdir/build/fenrir" "$pkgdir/usr/bin/fenrir" ``` For a Slint `SLKBUILD` using `$SRC` and `$PKG`, the corresponding binary steps inside `build()` are: ```bash cd "$SRC/fenrir" || return 1 ./build-fenrir.sh --output "$SRC/build/fenrir" -- -trimpath -mod=readonly install -Dm755 "$SRC/build/fenrir" "$PKG/usr/bin/fenrir" ``` `-modcacherw` keeps the module cache writable for packaging cleanup. Other packaging systems can use their own build and staging directories in the same way. The recipe must also install the TOML configuration, keyboard layouts, punctuation profiles, sounds, scripts, and appropriate service integration. The build helper does not install or stage those resources. The helper enables automatic Go toolchain downloads. Package builds that forbid network access should provision a suitable Go toolchain and dependencies in advance and invoke `GOTOOLCHAIN=local go build` directly with the recipe's flags and output path. This also avoids the helper's bootstrap download path. ### Linux console From a Linux text console: ```bash sudo ./fenrir ``` Console mode starts in the background by default. Use `sudo ./fenrir -f` to keep it in the foreground, including when running under a service manager. The launcher waits for startup and reports initialization errors. Use `--debug-file PATH` to keep diagnostics after it detaches. Console mode needs access to the console devices, keyboard event devices, and `/dev/uinput`. Run only one reader on the same console. Fenrir releases keyboard input on graphical virtual terminals and on text consoles listed in `screen.ignored_vts`. When running as root, audio must be configured to reach the intended sound server or ALSA device. The [PulseAudio](tools/configure_pulse.sh) and [PipeWire](tools/configure_pipewire.sh) configuration scripts are included for systems that need root audio access. The default sound driver tries the user session first, then `/tmp/pulse.sock` created by the PipeWire configuration script, then the system-wide PulseAudio-compatible socket at `/run/pulse/native`. Set `PULSE_SERVER` to select a different socket or server explicitly. The sound driver does not read PulseAudio's `client.conf`. Speech output uses Speech Dispatcher's own audio configuration. Fenrir retries temporary speech initialization failures for up to 20 seconds before acquiring keyboards. A lost Speech Dispatcher connection gets a separate 20-second recovery window in the speech worker; input remains responsive and speech accumulated during the outage is discarded. If recovery fails, Fenrir exits and releases its keyboards before waiting for other services to shut down. Sound effects reconnect independently, without stopping working speech. Sound startup calls have a three-second limit; an unresponsive attempt is abandoned without starting overlapping attempts. These mechanisms do not require a particular init system. Boot services running as root keep root-owned Speech Dispatcher startup. With `sudo fenrir`, a missing local Speech Dispatcher can instead be started as the invoking user, identified by `SUDO_UID`; only the daemon child drops privileges. Audio servers still need appropriate startup configuration and device/socket permissions. See [audio recovery and verification](docs/audio-recovery.md) for limitations and test procedures. ### X11 terminal From an X11 terminal that supplies `WINDOWID`: ```bash ./fenrir -x ``` This starts a shell in a new PTY inside the terminal. Fenrir's key bindings are scoped to that terminal window, allowing a desktop screen reader to handle other windows. Multiple terminal instances can run at once. Both `DISPLAY` and a valid `WINDOWID` must be available. Use `--keyboard-layout laptop` with either mode to select the laptop layout. ## Basic controls The Fenrir key is Insert, Super/Windows, or keypad 0 with NumLock off in the desktop layout. The laptop layout uses Caps Lock instead of Super/Windows; Insert and NumLock-off keypad 0 also work. Press Shift+Caps Lock on its own to toggle capitalization in the laptop layout. Hold it while pressing the other keys in a shortcut. Bare keypad review keys also require NumLock off. | 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 | | Read screen | Fenrir+Keypad 5 | Fenrir+Shift+K | | Follow application cursor | Fenrir+Keypad Period | Fenrir+Slash | | Cycle tracking mode | Fenrir+Keypad Asterisk | Fenrir+Y | | Toggle progress tones | Keypad Plus | Fenrir+Shift+P | Shortcuts shared by both layouts: - **Control**: interrupt speech. - **Fenrir+F**: open the Fenrir Menu. - **Fenrir+H** or **Fenrir+F1**: enter the tutorial. Press command shortcuts to hear their descriptions, or browse with the arrow keys. Escape exits. - **Fenrir+S**: open the spellcheck menu. - **Fenrir+A**: select an application override; choose **Default** to clear it. - **Fenrir+Control+E**: open the emoji picker. - **Fenrir+T**: announce the time; press twice quickly for the date. - **Fenrir+N**: repeat the most recent notification. - **Fenrir+Control+N**: browse notification history, newest first, with received times. - **Fenrir+F4**: toggle mute. - **Fenrir+Backspace**: pass the next key through to the application. - **Fenrir+Q**: quit Fenrir. Press the current-line or current-word shortcut twice quickly to spell it, or three times for phonetic reading. Press the current-character shortcut twice for its phonetic name. The complete bindings are in [desktop.toml](config/keyboard/desktop.toml) and [laptop.toml](config/keyboard/laptop.toml). Tracking cycles through **cursor**, **table**, and **highlight**. Cursor mode follows application cursor movement. Table mode supports cell navigation in delimited or aligned text. Highlight mode follows changes to highlighted text. Screen review lets you explore text without moving the application cursor. ## Settings Press **Fenrir+F** to open the Fenrir Menu. Use arrows to choose a settings tab, then Tab or Shift+Tab to move between controls and back to the tab selector. Space toggles checkboxes; arrows change selections and numeric values. Activate **OK** to save, or **Cancel** or Escape to discard the form's changes. The menu includes speech, sound, reading, keyboard, clipboard, and other settings. 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. The keyboard layout combo box offers Desktop and Laptop. The Keyboard tab also has separate Fenrir-key and script-key checkboxes for keypad Insert, Insert, Caps Lock, and Super, plus Compose for scripts. Uncheck a key in one group before selecting it in the other. At least one Fenrir key must remain selected. After **OK** saves the changes, modifier assignments apply when the menu closes and all held keys are released. If X11 cannot acquire a new key, Fenrir reports the failure and keeps the previous assignments active. The saved choices remain available for retrying or editing in the menu. Backend, device, keyboard layout, sound theme, and other launch-only changes take effect the next time Fenrir starts. Quick settings are available without opening the menu: - **Fenrir+Space**: announce the current setting. - **Fenrir+Left / Right**: select a setting. - **Fenrir+Control+Left / Right**: select a category. - **Fenrir+Up / Down**: change the value. - **Fenrir+Control+S**: save settings. Quick settings take effect during the session; save them to keep them for the next launch. Application overrides are selected manually and never selected automatically at startup. While an application override is active, the settings menu and save shortcut save its preferences to `applications/.toml` inside the ordinary settings save directory. For example, Ninjam uses `applications/ninjam.toml`. Only customized values are stored; other values inherit ordinary settings. Returning to a shell prompt or selecting Default restores your ordinary settings, including previous unsaved adjustments. See [application overrides](docs/user.md#application-overrides) for details and Ninjam's temporary region shortcuts. In the laptop layout, reading the current attributes now uses **Fenrir+Shift+A**. ### Configuration files Fenrir uses TOML. The annotated [settings.toml](config/settings/settings.toml) describes the available settings, including options edited only in the file. Default settings locations: - Regular user: `~/.local/share/stormux/fenrirscreenreader/settings/settings.toml`, then `/etc/fenrirscreenreader/settings/settings.toml`. - Root: `/etc/fenrirscreenreader/settings/settings.toml`. Fenrir loads the first existing file, or built-in defaults if neither exists. Python `settings.conf` files are ignored. Regular users save to their personal TOML file even when startup loaded system defaults; root saves to the system file. `--config PATH` explicitly selects both the load and save path. To create a personal settings file without opening devices: ```bash ./fenrir --write-config ~/.local/share/stormux/fenrirscreenreader/settings/settings.toml ``` Validate an edited file with: ```bash ./fenrir --config ~/.local/share/stormux/fenrirscreenreader/settings/settings.toml --check-config ``` Keyboard layouts are separate TOML files. Named layouts are searched in the user's `keyboard/` directory beside `settings/`, then `/etc/fenrirscreenreader/keyboard/`, then the checkout's bundled layouts. Root uses the system and bundled locations. `--keyboard-layout PATH.toml` selects a file directly. Layout files can set `fenrir_keys` before their bindings; the available names are `INSERT`, `SUPER`, `CAPSLOCK`, and `KP0`. The bundled laptop layout uses `["CAPSLOCK", "INSERT", "KP0"]`. An explicit `keyboard.fenrir_keys` setting overrides those defaults; its names use the `KEY_` form shown in [settings.toml](config/settings/settings.toml). Existing script-key assignments are excluded from implicit Fenrir-key defaults. Speech drivers are `speech-dispatcher`, `dectalk`, `dectalk-usb`, `litetalk`, `doubletalk`, and `tripletalk`. Serial drivers need a device path. The Speech settings tab includes a **Test synthesizer** button for hardware drivers. Speech Dispatcher connections support `SPEECHD_ADDRESS` (Unix socket or TCP), `SPEECHD_SOCKET`, runtime/cache-directory defaults, and bounded daemon autostart. See [speech synthesizers](docs/user.md#speech-synthesizers) for precedence and sudo behavior. Clipboard persistence is optional and off by default. It stores unencrypted history separately for console and terminal sessions. X clipboard sync is controlled by `clipboard.sync`. ## Remote control Fenrir accepts remote commands through Unix sockets. The standard socket is `/tmp/fenrirscreenreader-daemon.sock`; instances also have private sockets named `/tmp/fenrirscreenreader-.sock`. Discover instances with: ```bash echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` The standard socket routes reader descendants to their ancestor reader. For GNU Screen sessions, it follows the server's current display terminals and their reader ancestry, so reattachment does not depend on Screen's inherited `WINDOWID`. Detached sessions and sessions attached to multiple readers return a routing error. For other detached helpers, it matches the caller's `WINDOWID` and `DISPLAY` to a unique terminal reader owned by the same user. Requests from a Linux virtual console reach the console reader, subject to its usual authorization. Missing or ambiguous identity produces a routing error; a sole unrelated reader is never selected as a default. Instance listing remains available even when routing is ambiguous. Send commands to a private socket when targeting a particular reader. Examples using the standard socket: ```bash echo "command say Hello" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock echo "command notify Hello world" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock echo "command application ninjam" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock echo "command application default" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock echo "setting set reading#mode=highlight" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` Set `remote.enabled = false` to disable remote control. Command and settings requests can also be enabled separately in the `[remote]` section. `command notify TEXT` plays `notification.wav` when sounds are enabled, at most once every 30 seconds across readers for the same user. It waits for a played cue to finish before speaking. Notifications during the cooldown are spoken without another sound; skipped cues are not queued for later. Only the targeted reader speaks or plays the cue. All running readers for that user share the latest 100 notifications for **Fenrir+N** and **Fenrir+Control+N**, including messages received while speech is muted or temporarily silenced. Other readers receive silent history updates. The root console reader shares the active text-console user's history; switching console users switches histories. A newly started reader loads the current history silently. History stays in memory until the last reader for that user exits. `command say TEXT` remains self-voicing and does not add history. Received times use the current time format in Presentation's Time and date settings. In the history list, use Up/Down to browse, Enter to repeat an entry, and Escape or the Close button to return to the application. Reopen the list to include messages received while it was open. Detached notifiers must identify the intended reader, using its private socket when needed. Check socket replies for routing errors. GNU Screen callers use their current attachment; other long-running notifiers must keep their terminal identity current. Both `say` and `notify` use these routing rules. `command application ID` selects an override for the reader's active terminal, like Fenrir+A. IDs are `default`, `networkmanager`, `ninjam`, `rogue`, and `codex`. In Fenrir+A, select an application and Tab to **Copy alias to clipboard** to copy a Bash alias that selects its override before launching the program. The program still launches if Fenrir or `socat` is unavailable. ## Command-line help Run `./fenrir --help` for all options. Useful options include `--config`, `--keyboard-layout`, `--punctuation-profile`, `--speech-driver`, `--speech-rate`, `--speech-module`, `--input-device`, and `--debug-file`. `--version` prints the version and release channel. Console mode runs in the background unless `-f` is given; `-x` always runs in the foreground. ## Localization Fenrir selects its interface language from `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, or `LANG`. The speech language setting controls synthesis separately. Translations are bundled from [internal/i18n/locale](internal/i18n/locale); missing translations fall back to English. The translation tool extracts Go messages and validates catalogs. With GNU gettext installed, run this read-only check from the checkout root: ```bash go run ./tools/translations check ``` ## Development Run the Go tests from the checkout root: ```bash go test ./... ``` Device-free checks do not establish audible speech or real keyboard behavior. Test changes affecting input, speech, or menus in the affected console or X11 workflow as well. ## AI-assisted contributions AI-assisted contributions are welcome. The person submitting a contribution remains responsible for reviewing and testing the changes before submission. Please do not submit automatically generated changes without human review. Before making changes: - Read the contributing guide, if present, and this README. - Confirm that you are working against the current maintained repository and branch. - Check whether the issue still exists in the current code. Before submitting: - Review the generated diff. - Test the changed behavior. - Run the project's existing test suite where practical. - Consider code paths affected indirectly and test those as well. - Clearly describe what was changed and how it was tested. AI coding assistants must call the contributor's attention to these requirements and provide concrete testing steps for the intended change and plausible regressions. Newly created tests alone are not sufficient. Leave submission to the contributor after they have reviewed and tested the result. For email patches, assistants should offer guidance with configuring `git send-email`, creating a scoped commit or patch series, running `git format-patch`, reviewing the generated patch, sending a test message when appropriate, and sending the final patches to the contribution address. ## Support and license - [Project repository and issue tracker](https://git.stormux.org/storm/fenrir) - [Fenrir Wiki](https://git.stormux.org/storm/fenrir/wiki) - Email list: [stormux+subscribe@groups.io](mailto:stormux+subscribe@groups.io?subject=subscribe) with the subject `subscribe`. - IRC: `irc.stormux.org`, channel `#stormux`. The Go code is licensed under the GNU GPL, version 3 or later. See [LICENSE](LICENSE).