Skip to content
ogomez92Public

About

NVDA Remote with an optional ssh bridge with Add-on, linux scripting, iOS and mac apps

Resources

Stars

5 stars

Watchers

1 watching

Forks

Latest commit

 

History

10 Commits

Folders and files

Repository files navigation

nvdr

nvdr lets you control a remote NVDA screen reader from a machine that isn't running NVDA — a Linux box over SSH, a Mac, or an iPhone. It speaks the NVDA Remote relay protocol as the master (controller): it hears what the remote NVDA says and forwards your keystrokes back to it.

Disclaimer. This is a hobby project. I built it for personal use to see if the idea would work — nothing more. I'm aware of NVDA's native remote access and of the NVDA Remote controller app currently in TestFlight; this is not a replacement for either. What nvdr adds is (a) an SSH-bridged transport and (b) what I believe is better keyboard handling than that TestFlight app in some situations. Contributions are welcome — especially anything around security, but really anything at all.

What's in this repo

This is a monorepo. Several independently-built components, different languages, all speaking the same wire protocol (client_spec.md is the shared contract):

Path What it is
src/ + Cargo.toml The Rust terminal client. Also exposes nvdr --ipc, a line-oriented IPC bridge used by the apps below.
addon/ An NVDA add-on (nvdrBridge) — lets a Windows NVDA use the SSH bridge.
mac/ A native macOS SwiftUI app.
ios/ An iOS SwiftUI app.
android/ A native Android app (Kotlin + Jetpack Compose).

The terminal client connects directly to the relay over TLS. The Mac app, iOS app, Android app, and NVDA add-on all go through the SSH bridge instead (see below).

The SSH bridge — read this first

The Mac app, the iOS app, the Android app, and the NVDA add-on do not open a TLS connection to the relay themselves. They launch ssh and run nvdr --ipc on a remote bridge box, and that machine dials the NVDA Remote relay:

your device (Mac / iOS / Android / Windows+NVDA)
        │  SSH
        ▼
   bridge box  ──  runs `nvdr --ipc`  ──  TLS  ──▶  relay  ──▶  remote NVDA

So to use the Mac app, iOS app, Android app, or add-on you need a reachable host (Linux, macOS, or Windows) with the nvdr binary built and on its PATH, plus SSH auth set up (the apps support a private key or a password).

This bridge is useful when you want to encrypt the whole remote session over SSH, or when you simply can't reach port 6837 (firewalled networks, captive Wi-Fi, etc.) — your only outbound connection is SSH.

Caveats of the bridge

  • The Mac, iOS, and Android apps use the SSH bridge unconditionally — there is no way to turn it off. They will not connect to a relay directly. If you want a direct connection, use the Rust terminal client. A contribution that lets these apps dial the relay directly (no SSH bridge) would be very welcome — and I'll likely get around to it myself before long if there's demand for it.
  • The bridge only works on the controller (master) side, not the host (slave) side. There is no way to make the machine being controlled run through this bridge — that machine still needs ordinary NVDA Remote.
  • The bridge forwards keystrokes and speech only. Other NVDA audio — beeps, the forms-mode tone, progress-bar ticks, wave/tone effects — is not carried across, for now. You get speech text and that's it.
  • Connecting NVDA's native remote access through this bridge is not implemented. If you'd like to add it, contributions are super appreciated.

The Rust terminal client

A terminal client for the NVDA Remote relay. Designed to run on a VPS over SSH; works from any terminal on Linux, macOS, Windows, or iOS. This is the only component that talks to the relay directly (no bridge).

Build & install

Requires Rust 1.75+.

cargo build --release        # binary at target/release/nvdr

Drop target/release/nvdr somewhere on your $PATH.

Quick start

nvdr                                                  # localhost:6837, prompts for channel
nvdr --host relay.example.com --port 6837 --channel 123456789
nvdr --show-keys                                      # dump the key reference and exit

On first connect nvdr pins the relay's TLS fingerprint (Trust On First Use) into ~/.config/nvdr/known_hosts; later connects verify against it. Relay certs are usually self-signed — that's expected. A cert change drops you into an interactive re-pin prompt.

Using it

Type, and printable characters, arrows, F-keys, Tab/Enter/Esc/Backspace, and any Ctrl+letter are forwarded to the remote NVDA — even Ctrl+C. Speech prints as plain text lines (no ANSI color, screen-reader friendly).

A terminal can't send Insert or the Windows key, so there's a leader key (default Ctrl+G, set with --leader):

Shortcut Sends Meaning
<leader> c Alt+F4 close window
<leader> w NVDA+T read window title
<leader> d Win+D show desktop
<leader> t Alt+Tab switch window

<leader> <leader> toggles a sticky NVDA modifier; <leader> : opens a command prompt (:k win+r, :caps, :say <text>, :help, :quit, …).

There's also a one-shot scripting mode (-k / -s) — see scripting.md.

What a terminal can't do

CapsLock as a key, the Windows key alone, left/right modifier distinction, a modifier pressed alone, most numpad keys. Use :k win+... for the Windows key.

The NVDA add-on (addon/)

nvdrBridge lets a Windows machine running NVDA reach a remote NVDA through the SSH bridge instead of NVDA Remote's normal direct connection — again, for SSH encryption or when port 6837 is blocked.

  • Install: zip the contents of addon/ (so manifest.ini is at the archive root), rename to nvdrBridge.nvda-addon, install via NVDA's Add-on Store ("Install from external source"), restart NVDA.
  • Configure under NVDA → Preferences → Settings → nvdr Bridge (SSH host / user / key, relay host, channel key).
  • NVDA+F11 toggles key forwarding between local and remote. While forwarding is on, every keystroke (except NVDA+F11 itself) goes to the remote slave and the remote NVDA's speech is spoken on your local synth.

This is the controller side only — see the bridge caveats above. Full setup and troubleshooting: addon/README.md.

The macOS app (mac/)

A native SwiftUI app that bridges your Mac to a remote NVDA through the SSH bridge. It installs a system-wide low-level keyboard hook (CGEventTap + IOHIDManager).

  • NVDA+F11 (e.g. CapsLock+F11) toggles forwarding on and off.
  • While forwarding is on, the app eats all keystrokes — including Cmd+Q, Cmd+Tab, and other system shortcuts. They are sent to the remote machine, not handled by macOS. Turn forwarding off (NVDA+F11) to get your keyboard back.
  • The app requires Accessibility and Input Monitoring permissions to run the keyboard hook. It detects when they're missing and shows a banner with buttons that jump straight to the relevant System Settings pane.
  • It is a Developer ID / non-sandboxed app — the keyboard hook can't work in the App Store sandbox.

The iOS app (ios/)

A SwiftUI app that bridges an iPhone/iPad to a remote NVDA through the SSH bridge.

  • NVDA+F11 toggles forwarding on and off, same as the Mac app.
  • While forwarding is on, the app captures all hardware-keyboard input — system combos included — and sends it to the remote machine.

The Android app (android/)

A native Kotlin / Jetpack Compose app that bridges an Android device with a hardware (Bluetooth/USB) keyboard to a remote NVDA through the SSH bridge. Speech from the slave is spoken on the phone via Android Text-to-Speech.

  • NVDA+F11 (e.g. CapsLock+F11) toggles forwarding on and off, same as the Mac and iOS apps.
  • Capture is focused-only by default — like the iOS app, it intercepts keys while nvdr is in the foreground, and releases any held keys on the slave when the app is backgrounded.
  • Optionally, a system-wide capture path via an AccessibilityService grabs combos Android would otherwise intercept first (Alt+Tab, the Meta/Windows key). It's the Android analog of the Mac app's system-wide hook — opt-in, and without it the app still does focused-only capture.
  • Needs Android 8.0 (API 26) or newer.

Full setup and configuration: android/README.md.

Building the apps

The two Swift apps use xcodegen-generated Xcode projects:

cd mac        # or: cd ios
xcodegen generate
xcodebuild

The Android app is a Gradle project — needs JDK 17 and the Android SDK (compileSdk 36):

cd android
./gradlew assembleDebug      # → app/build/outputs/apk/debug/app-debug.apk
./gradlew installDebug       # build + install on a connected device (needs adb)

None of these apps will be published on the App Store / Play Store (the Mac one can't be sandboxed; the others aren't headed there either). Build them yourself from source.

Contributing

This started as a personal experiment, so there's plenty of room to improve it. Some things to look at— security review and hardening especially, but also better protocol coverage (carrying NVDA's non-speech audio, wiring NVDA's native remote access through the bridge), more keyboard handling, docs, anything. Open an issue or a PR.

Files

  • client_spec.md — the wire protocol this project implements.
  • scripting.md — the -k / -s one-shot scripting grammar.
  • ~/.config/nvdr/known_hosts — TLS fingerprint cache for the terminal client.

License

Same as the NVDA Remote project.

About

NVDA Remote with an optional ssh bridge with Add-on, linux scripting, iOS and mac apps

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages