Skip to content

Add multi routing gesture and select with routing command#20028

Draft
LeonarddeR wants to merge 22 commits intonvaccess:masterfrom
LeonarddeR:cellIndexes
Draft

Add multi routing gesture and select with routing command#20028
LeonarddeR wants to merge 22 commits intonvaccess:masterfrom
LeonarddeR:cellIndexes

Conversation

@LeonarddeR
Copy link
Copy Markdown
Collaborator

@LeonarddeR LeonarddeR commented Apr 28, 2026

Link to issue number:

Closes #20001

Summary of the issue:

NVDA's BrailleDisplayGesture only exposed a single routingIndex, so braille displays that report multiple simultaneously pressed routing keys could not bind a meaningful "multi routing" gesture. There was also no way for a user to select a range of text directly from the braille display.

Description of user facing changes:

  • New "multi routing" gesture: pressing multiple routing keys simultaneously on a supported display can now be bound to a single gesture.
  • New command "Selects the text from the first up to the last pressed braille routing key" (script_braille_selectRange). The selection is half-open: it starts at the cell under the first pressed routing key and ends just before the cell under the last pressed routing key. On drivers that emit multiRouting natively, the gesture is bound to this command out of the box; users of other displays can bind it manually if they wish.
  • Drivers shipped with multiRoutingbraille_selectRange bound by default: ALVA, Albatross, Baum (and compatible), HumanWare Brailliant BI/B series, Handy Tech, NLS eReader Zoomax, Seika Notetaker, Standard HID Braille displays.

Description of developer facing changes:

  • braille.BrailleDisplayGesture exposes cellIndexes: list[int] | None. The single-valued routingIndex is now a deprecated property that falls back to max(cellIndexes) for compatibility. When NVDAState._allowDeprecatedAPI() returns False the getter and setter raise AttributeError.
  • New helper BrailleDisplayGesture.idForCellCount(count, baseName="routing") (classmethod) builds the canonical id ("routing" / "multiRouting" / "multiSecondRouting").
  • cellIndexes is not limited to routing keys; touch-sensitive cells (e.g. Handy Tech ATC) can reuse it.
  • _remoteClient propagates cellIndexes while keeping routingIndex populated for older peers.
  • seikantk.InputGestureRouting.__init__ now accepts a list of indexes; a single int is still accepted for backwards compatibility.

Description of development approach:

  • Added cellIndexes and a deprecation shim for routingIndex on BrailleDisplayGesture.
  • Migrated every in-tree driver from self.routingIndex = x to self.cellIndexes = [x] (or to a collected list where the protocol reports several keys at once).
  • Generalised the gesture id construction via idForCellCount so drivers with multiple routing ranges (e.g. ALVA routing + secondRouting, HID routerSet1 collections, Albatross dual-row) yield ids like multiRouting, multiSecondRouting, routerSet1_multiRouterKey.
  • Added script_braille_selectRange in globalCommands using getTextInfoForWindowPos + setEndPoint("endToEnd") + updateSelection(). Guarded against cellIndexes being None/short and against NotImplementedError from the focused control.
  • Wired multiRoutingbraille_selectRange in the gesture map of every driver whose driver-side implementation now reports simultaneous routing keys.

Testing strategy:

Unit tests added in tests/unit/test_braille/test_brailleDisplayDrivers.py covering gesture id construction and cellIndexes population for multi-key presses.

Manual testing performed on the following drivers (all updated to emit multiRouting):

  • ALVA (BC640/BC680)
  • Albatross
  • Baum (and compatible)
  • HumanWare Brailliant BI/B series
  • Handy Tech
  • NLS eReader Zoomax
  • Seika Notetaker
  • Standard HID Braille

Per-driver multi-routing support matrix

Driver Multi routing emitted multiRoutingbraille_selectRange bound
ALVA Yes (incl. multiSecondRouting) Yes
Albatross Yes Yes
Baum Yes Yes
Brailliant B (HumanWare) Yes Yes
Handy Tech Yes Yes
HID Braille (standard) Yes (routerSet1_multiRouterKey) Yes
NLS eReader Zoomax Yes Yes
Seika Notetaker Yes Yes
BrailleNote (HumanWare) No n/a
brltty No n/a
EcoBraille No n/a
Eurobraille No n/a
Freedom Scientific No n/a
hedoMobilLine No n/a
hedoProfiLine No n/a
HIMS No n/a
Lilli No n/a
Nattiq No n/a
Papenmeier (USB + serial) No n/a
Seika (non-Notetaker) No n/a
Braille Viewer (built-in) No n/a

Known issues with pull request:

  • Drivers in the "n/a" rows above do not emit multiRouting because their current driver-side implementation does not aggregate simultaneous routing key presses — not necessarily a hard protocol limitation. Adding support for any of them is a follow-up that only requires collecting indexes into cellIndexes and using idForCellCount.
  • script_braille_selectRange relies on TextInfo.updateSelection(); controls that raise NotImplementedError (some Win32 console-style targets) report a localized "Selection not supported here" message.

Code Review Checklist:

  • Documentation:
    • Change log entry
    • User Documentation
    • Developer / Technical Documentation (cellIndexes, idForCellCount, deprecation entry)
    • Context sensitive help for GUI changes — n/a
  • Testing:
    • Unit tests
    • System (end to end) tests — n/a
    • Manual testing
  • UX of all users considered:
    • Speech
    • Braille
    • Low Vision
    • Different web browsers
    • Localization in other languages / culture than English (translator comments added)
  • API is compatible with existing add-ons. (routingIndex getter/setter preserved with deprecation warning under _allowDeprecatedAPI(); seikantk.InputGestureRouting accepts legacy int.)
  • Security precautions taken.

LeonarddeR and others added 11 commits April 28, 2026 12:18
…ccess#20001)

`braille.BrailleDisplayGesture.routingIndex` (single int) was too narrow:
"routing" excluded non-routing cell-addressed gestures (e.g. Handy Tech
Active Tactile Control), and simultaneous routing key presses had
unspecified behavior (last index won).

Core API (source/braille.py):
- New `cellIndexes: list[int]` canonical attribute for any cell-addressed
  gesture (routing, touch, ATC, ...).
- `BrailleDisplayGesture.idForCellCount(n)` helper returning
  `"routing"` for <=1 cell, `"multiRouting"` for >1.
- `_get_routingIndex`/`_set_routingIndex` deprecation shim using the
  `AutoPropertyObject` pattern: getter returns first cell, setter wraps
  into a single-element list. Warning gated on
  `NVDAState._allowDeprecatedAPI()`.

Scripts (source/globalCommands.py):
- `script_braille_routeTo` and `script_braille_reportFormatting` now
  read `cellIndexes[0]`.
- New `script_braille_selectToCell` (unassigned by default) selects text
  between first and last pressed routing cells when bound to a
  `multiRouting` gesture.

Driver migration (source/brailleDisplayDrivers/*): all 22 drivers
migrated from `self.routingIndex = X` to `self.cellIndexes = [X]`.
Multi-routing emission added where the protocol already reports
simultaneous presses:
- baum: iterate bitmap of routing keys
- handyTech: aggregate routing key codes from pressed-key set
- albatross: aggregate primary routing range indexes
- nlseReaderZoomax: iterate routing keys bitmap
- seikantk: emit single gesture with sorted indexes instead of one
  gesture per key

Remote compat (source/_remoteClient/*):
- Sender serializes `cellIndexes` plus legacy `routingIndex` (first
  cell) for old peers.
- Receiver normalizes legacy `routingIndex` into `cellIndexes` before
  attribute assignment, avoiding a deprecation warning.

brailleViewer: `brailleViewerInputGesture.py` sets `cellIndexes`.

Tests (tests/unit/test_braille/test_brailleDisplayDrivers.py): added
coverage for default `cellIndexes`, `idForCellCount`, deprecated
`routingIndex` getter/setter round-trip, and `multiRouting` identifier
regex validity.

Docs (user_docs/en/changes.md): new-feature entry for multi-routing
selection, developer change for `cellIndexes`/`multiRouting`/helper,
deprecation entry for `routingIndex`.
- cellIndexes default is now None instead of empty list, matching
  the previous routingIndex = None convention.
- Replace #: style comments with docstrings for ID_ROUTING,
  ID_MULTI_ROUTING, and cellIndexes.
- Update deprecated routingIndex setter to return None (not []).
- Update tests to expect None default and None on clear.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
- idForCellCount now accepts an optional baseName parameter (default
  "routing"). For count > 1, prepends "multi" with the first character
  uppercased: "routing" → "multiRouting",
  "secondRouting" → "multiSecondRouting", "route" → "multiRoute".
- Albatross driver: collect all routing ranges into a dict keyed by
  range name, then merge into a flat cellIndexes list preserving
  duplicates for cross-row simultaneous presses.
- Alva driver: same aggregation pattern for "routing" and
  "secondRouting" ranges, replacing the previous overwrite behavior.
- Added test_idForCellCount_custom_baseName covering secondRouting,
  route, and upperRouting base names.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
These class constants on BrailleDisplayGesture are superseded by
the idForCellCount helper which dynamically builds the gesture id
for any routing range name.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
…orCellCount

- brailliantB: aggregate routing keys into list, support multi-press
- hidBrailleStandard: collect routing keys with prefix, support multi-press
  producing routerSet1_multiRouterKey for simultaneous presses
- Switch all drivers from braille.BrailleDisplayGesture.idForCellCount to
  self.idForCellCount for consistency

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
The end position of the selection no longer includes the character
at the last routing key, making the selection range exclusive.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Make the deprecated routingIndex getter and the remote client legacy
field use max(cellIndexes) consistently, so single-cell behavior is
preserved while multi-cell gestures resolve to the highest index.
Also guard script_braille_selectToCell against cellIndexes being None.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restore backwards compatibility for callers that pass a single int
to InputGestureRouting, normalizing it to a one-element list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Caller (_handleRouting) already passes a fresh sorted list, and the
single-int compat path constructs a new list. No need to copy again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds default bindings for the new braille_selectToCell command on
drivers that emit a multiRouting gesture: ALVA, Albatross, Baum,
Brailliant B, Handy Tech, HID Braille, NLS eReader Zoomax, and
Seika Notetaker. Updates the user-facing changelog to list these
drivers explicitly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace 'braille cells' with 'braille routing keys' in the script
description, error message and changelog entry to match the actual
input concept.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 28, 2026 11:10
@LeonarddeR LeonarddeR requested a review from a team as a code owner April 28, 2026 11:10
@LeonarddeR LeonarddeR requested a review from SaschaCowley April 28, 2026 11:10
LeonarddeR and others added 2 commits April 28, 2026 13:11
Allows subclasses to override the helper if they need a different
naming convention while still letting drivers call self.idForCellCount.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, when NVDAState._allowDeprecatedAPI() returned False, the
routingIndex getter and setter still returned/applied the value and
only suppressed the log message. That defeated the purpose of the
flag, which is meant to prove a code path is free of deprecated API
usage. Now raise AttributeError instead, mirroring the pattern used
elsewhere (e.g. addonHandler._pickleToJsonMigration).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances NVDA’s braille input model to support gestures that address multiple braille cells at once (e.g., multiple routing keys pressed simultaneously), and adds a new built-in command to select text directly from the braille display using multi-routing.

Changes:

  • Added cellIndexes (list of addressed cell indexes) to braille.BrailleDisplayGesture, deprecated routingIndex, and introduced BrailleDisplayGesture.idForCellCount(...) for consistent gesture ids.
  • Updated in-tree braille display drivers (and Braille Viewer) to populate cellIndexes and emit multiRouting (and related) ids where appropriate; bound multiRouting to the new selection command on supported drivers.
  • Added script_braille_selectToCell and propagated cellIndexes across NVDA Remote while retaining legacy routingIndex compatibility.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
user_docs/en/changes.md Documents the new multi-routing gesture, selection command, and the cellIndexes API/deprecation.
tests/unit/test_braille/test_brailleDisplayDrivers.py Adds unit tests for cellIndexes, idForCellCount, and the routingIndex compatibility shim.
source/globalCommands.py Updates routing/formatting scripts to use cellIndexes; adds braille_selectToCell command.
source/brailleViewer/brailleViewerInputGesture.py Migrates Braille Viewer routing gesture to cellIndexes.
source/brailleDisplayDrivers/seikantk.py Aggregates routing indexes into cellIndexes, emits multi ids, and binds multiRouting to selection.
source/brailleDisplayDrivers/seika.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/papenmeier_serial.py Migrates routing handling to cellIndexes while preserving existing ids.
source/brailleDisplayDrivers/papenmeier.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/nlseReaderZoomax.py Collects multiple routing keys into cellIndexes, uses idForCellCount, binds selection.
source/brailleDisplayDrivers/nattiqbraille.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/lilli.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/hims.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/hidBrailleStandard.py Aggregates routing keys into cellIndexes, emits routerSet1_multiRouterKey, binds selection.
source/brailleDisplayDrivers/hedoProfiLine.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/hedoMobilLine.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/handyTech.py Aggregates routing indexes, uses idForCellCount, binds selection.
source/brailleDisplayDrivers/freedomScientific.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/eurobraille/gestures.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/ecoBraille.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/brltty.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/brailliantB.py Aggregates routing indexes, uses idForCellCount, binds selection.
source/brailleDisplayDrivers/brailleNote.py Migrates routing gesture to cellIndexes.
source/brailleDisplayDrivers/baum.py Aggregates routing indexes, uses idForCellCount, binds selection.
source/brailleDisplayDrivers/alva.py Supports multiple routing ranges via per-range aggregation and idForCellCount; binds selection.
source/brailleDisplayDrivers/albatross/gestures.py Supports multiple routing ranges via aggregation and idForCellCount; binds selection.
source/braille.py Introduces cellIndexes, adds idForCellCount, and implements deprecated routingIndex shim.
source/_remoteClient/session.py Propagates cellIndexes over Remote while providing legacy routingIndex.
source/_remoteClient/input.py Normalizes legacy routingIndex into cellIndexes to avoid triggering deprecation warnings.

Comment thread user_docs/en/changes.md Outdated
Comment thread source/brailleDisplayDrivers/hidBrailleStandard.py
Comment thread tests/unit/test_braille/test_brailleDisplayDrivers.py Outdated
Comment thread source/globalCommands.py Outdated
LeonarddeR and others added 8 commits April 28, 2026 13:18
The previous name suggested a single destination cell, while the
command actually selects a range between two routing keys. Update
all driver gesture maps and the changelog to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the redundant 'Requires a display that reports simultaneous
routing key presses' sentence. The input gestures dialog already
shows the bound gesture, and the wording is now consistent with
sibling braille script descriptions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The selection is half-open: it includes the cell at the first
pressed routing key but stops at (does not include) the cell at
the last pressed routing key. 'Between' was misleading. Use
'from the first up to the last' instead, both in the script
description and in the changelog entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@LeonarddeR LeonarddeR marked this pull request as draft April 28, 2026 11:46
@LeonarddeR LeonarddeR changed the title Add multi routing gesture and select-to-routing-key command Add multi routing gesture and select with routing command Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Change routingIndex to cellIndexes on BrailleDisplayGesture and use multiRouting

2 participants