Skip to content

HandyTech: Interpret reading position packets from Active Tactile Control#20353

Draft
codeofdusk wants to merge 2 commits into
nvaccess:masterfrom
codeofdusk:t4775_1
Draft

HandyTech: Interpret reading position packets from Active Tactile Control#20353
codeofdusk wants to merge 2 commits into
nvaccess:masterfrom
codeofdusk:t4775_1

Conversation

@codeofdusk

@codeofdusk codeofdusk commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Link to issue number:

Re #4775.

Summary of the issue:

Some HandyTech Braille displays, such as the Activator, can communicate the user's reading position back to the controlling device. This allows, for instance, the display to advance when the user finishes reading a line (instead of on a timer or with a button press), to speak the current word or line (or perform some other action) if the user dwells, etc. NVDA lacks support for interpreting HandyTech control packets containing this information.

Description of how this pull request fixes the issue:

This PR adds support for parsing reading position packets to the HandyTech driver and stores the current reading position in a private field. No user-facing behaviour changes have been made as the API will likely require some discussion (do we want to expose reading position as events, an extension point, something else?) and that work is better done in a follow-up.

Testing strategy:

Tested with an Activator over USB-C. Reported reading positions are accurate, including in text fields with a rapidly refreshing blinking cursor.

Known issues with pull request:

The ATC protocol is undocumented as far as I can tell. I've made a best effort and added logging in case of unexpected packet shapes. NVDA was already receiving (and throwing away) these reading position packets, so these changes have no other impact on the HandyTech driver.

Code Review Checklist:

  • Documentation:
    • Change log entry
    • User Documentation
    • Developer / Technical Documentation
    • Context sensitive help for GUI changes
  • Testing:
    • Unit tests
    • System (end to end) tests
    • Manual testing
  • UX of all users considered:
    • Speech
    • Braille
    • Low Vision
    • Different web browsers
    • Localization in other languages / culture than English
  • API is compatible with existing add-ons.
  • Security precautions taken.

This commit surfaces no user behaviour; to be done in future work.
@codeofdusk codeofdusk requested a review from a team as a code owner June 17, 2026 00:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds Active Tactile Control (ATC) reading-position handling for Handy Tech braille displays to filter transient/invalid touch reports after a refresh.

Changes:

  • Introduces ATC reading position tracking with a post-refresh “settling” suppression window.
  • Hooks HT_EXTPKT_READING_POSITION packets into the input stream handler.
  • Updates file header copyright attribution.

Comment thread source/brailleDisplayDrivers/handyTech.py
Comment thread source/brailleDisplayDrivers/handyTech.py
Comment thread source/brailleDisplayDrivers/handyTech.py Outdated
Comment thread source/brailleDisplayDrivers/handyTech.py Outdated
Comment thread source/brailleDisplayDrivers/handyTech.py
@codeofdusk

Copy link
Copy Markdown
Contributor Author

CC @FelixGruetzmacher, @LeonarddeR.

@LeonarddeR

Copy link
Copy Markdown
Collaborator

Interesting approach. I prototyped something that didn't save the reading position on the mixin, but executed a new AtcGesture that contained the reading position. Of course this is still something that can be done. Also there is no handling of old display reading position handling (Modular Evolution), but that can easily be added later.

First of all thoug, I think we need to know how far we can go with implementing ATC parsing directly in NVDA without violating the ATC patents held by Handy Tech/Helptech. I assume they are only patenting the hardware, not the software that uses it. That said, this pr is doing nothing extra besides what BRLTTY already has.

@codeofdusk

codeofdusk commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

That said, this pr is doing nothing extra besides what BRLTTY already has.

VoiceOver (iOS/MacOS) also implements its own support for this feature, and there's another implementation in #4775 (comment). I feel strongly that we're fine.

@seanbudd seanbudd added the conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review. label Jun 23, 2026
f"Unexpected ATC reading position packet with length {len(packet)}: {packet!r}",
)
return
reading_position = packet[1]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please make sure to use lowerCamelCase for variable names throught these changes


def __init__(self, display):
super().__init__(display)
self._readingPosition: Optional[int] = None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please make sure to use modern type hints such as | None or list throughout these changes


def __init__(self, display):
super().__init__(display)
self._readingPosition: Optional[int] = None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
self._readingPosition: Optional[int] = None
self._readingPosition: int | None = None

def __init__(self, display):
super().__init__(display)
self._readingPosition: Optional[int] = None
self._lastRefreshTime = 0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
self._lastRefreshTime = 0.0
self._lastRefreshTime: float = 0.0

log.debug("Enabling ATC")
self._display.atc = True

def display(self, cells: List[int]):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
def display(self, cells: List[int]):
def display(self, cells: list[int]):

f"Unexpected ATC reading position packet with length {len(packet)}: {packet!r}",
)
return
reading_position = packet[1]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
reading_position = packet[1]
readingPosition = packet[1]

)
return
reading_position = packet[1]
elapsed_since_refresh = time.monotonic() - self._lastRefreshTime

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
elapsed_since_refresh = time.monotonic() - self._lastRefreshTime
elapsedSinceRefresh = time.monotonic() - self._lastRefreshTime

return
reading_position = packet[1]
elapsed_since_refresh = time.monotonic() - self._lastRefreshTime
is_settling_after_refresh = elapsed_since_refresh < self.READ_SUPPRESS_AFTER_REFRESH_SECONDS

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
is_settling_after_refresh = elapsed_since_refresh < self.READ_SUPPRESS_AFTER_REFRESH_SECONDS
isSettlingAfterRefresh = elapsed_since_refresh < self.READ_SUPPRESS_AFTER_REFRESH_SECONDS

@SaschaCowley SaschaCowley marked this pull request as draft June 26, 2026 06:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants