Skip to content

Fix braille symbol resolution on NVDA 2026.1, and make the Punktum app module valid UTF-8 - #8

Open
serrebidev wants to merge 3 commits into
josephsl:mainfrom
serrebidev:braille-package-compat
Open

Fix braille symbol resolution on NVDA 2026.1, and make the Punktum app module valid UTF-8#8
serrebidev wants to merge 3 commits into
josephsl:mainfrom
serrebidev:braille-package-compat

Conversation

@serrebidev

@serrebidev serrebidev commented Aug 19, 2026

Copy link
Copy Markdown

Two independent fixes, one commit each.


1. Resolve braille symbols from their NVDA 2026.1 locations

The problem

NVDA 2026.1 split the monolithic braille and brailleInput modules into the braille and braille.input packages. The old names still resolve, through the module level __getattr__ installed by utils._deprecate — but only for reads, and that has two consequences for this add-on.

1a. The add-on's patches of the property/field functions no longer take effect.

_deprecate.handleDeprecations only intercepts attribute access. Assigning to a moved name binds a brand new attribute on the old module; it does not replace the real one. NVDA core now resolves these three from their new home:

  • textInfos/__init__.py calls braille.regions.properties.getControlFieldBraille(...)
  • NVDAObjects/__init__.py (and NVDAObjects/UIA, NVDAObjects/IAccessible, behaviors, several app modules) call braille.regions.properties.getPropertiesBraille(...)

and two of them are additionally bound by name at import time in the modules that use them:

  • braille/regions/NVDAObject.py: from .properties import getPropertiesBraille
  • braille/regions/textInfo.py: from .properties import getFormatFieldBraille

So braille.getPropertiesBraille = ... in patches.py was writing somewhere nothing reads any more. On NVDA 2026.1 the customizations built on those three functions — role labels, document formatting indicators, undefined character representations — silently stopped applying. Note that patching only the defining module would still not be enough, because of the two import-time bindings above.

1b. Loading the add-on floods the log.

Every deprecated read logs a warning with a full stack trace. On a stock profile that is around fifty warnings and several hundred lines before NVDA has finished starting, which makes the log impractical for diagnosing anything else.

The change

Adds globalPlugins/brailleEssentials/brailleCompat.py, which resolves each symbol the add-on needs from its new location when the package layout is present, and falls back to the pre-2026.1 names otherwise — so addon_minimumNVDAVersion = 2025.3.3 stays supported. Call sites import from it instead of reaching through braille / brailleInput.

Module level functions go through patchBrailleFunction(name, func), which writes to every namespace that actually holds a binding (found via vars(), so the deprecation __getattr__ is never triggered and modules that merely re-export through the shim are not mistaken for holders). getBrailleFunction(name) is its counterpart for capturing the originals in _saveOriginals.

Two details worth flagging:

  • brailleInput.LOUIS_DOTS_IO_START is a RemovedSymbol in 2026.1, not a moved one — it still returns 0x8000 today but raises once NVDAState._allowDeprecatedAPI() goes false. Its value is carried in brailleCompat for _translate.
  • patchBrailleFunction deliberately does not also set the name on braille under the new layout; doing so would shadow the deprecation shim for other add-ons.

Symbols covered: Region, TextRegion, TextInfoRegion, NVDAObjectRegion, NVDAObjectHasUsefulText, BrailleHandler, BrailleBuffer, BrailleInputHandler, getPropertiesBraille, getControlFieldBraille, getFormatFieldBraille, getFocusRegions, getFocusContextRegions, getDisplayList, the four label dicts, TEXT_SEPARATOR, INPUT_START_IND, INPUT_END_IND, SELECTION_SHAPE, brailleInput.handler and LOUIS_DOTS_IO_START.

Testing

  • NVDA alpha-57493 (2026.1), Windows 11 25H2: add-on loads with 0 deprecation warnings, down from ~50, and no other new log entries. _saveOriginals resolving all three functions through getBrailleFunction on the real 2026.1 module tree also confirms the holder lookup finds them.
  • Exercised brailleCompat against stub module trees shaped like both layouts, asserting that a patch reaches both braille.regions.properties and the import-time binding in braille.regions.NVDAObject, that it does not shadow the shim on braille, that the flat-layout path still patches braille, and that no code path triggers the deprecation __getattr__.

No behaviour change is intended on NVDA versions before 2026.1.


2. Make the Punktum app module valid UTF-8

addon/appModules/punktum.py is cp1252 encoded — the German comment on line 1 uses 0xFC for the u umlaut — and carries no PEP 263 encoding declaration.

I originally filed this as "the module never loads". That was wrong, and testing against real Punktum corrected it. Python 3.13 does not decode bytes inside comments, so a non-UTF-8 sequence passes if it sits in a comment and fails anywhere else. Python 3.14 rejects them in comments too. Measured on this exact file:

bad byte in a comment bad byte in a string literal
CPython 3.13.13 (NVDA 2026.1) compiles SyntaxError
CPython 3.14.7 SyntaxError SyntaxError

So the module imports fine today, and stops importing the moment NVDA moves to Python 3.14. Separately, ruff cannot read the file at all (E902 stream did not contain valid UTF-8), which quietly excludes it from both ruff check and ruff format --check.

The fix re-encodes as UTF-8 and adds the # coding: utf-8 line the rest of the add-on's modules carry. Nothing else changes except ruff format, which could not reach the file before — two blank lines and a trailing comma.

Testing

Installed Punktum V1.10 (PunktumSoftware.zip, DBSV, August 2026) and ran it under NVDA alpha-57493, probing both candidate app modules with a module level log.info to see which one Python actually imported.

3. Two findings from that test, for your call — no code change here

3a. The bundled copy is shadowed whenever the official Punktum add-on is installed.

Punktum ships its own NVDA add-on inside the download (NVDA/punktum_0.3.0.nvda-addon, author Christian Punz, lastTestedNVDAVersion 2026.1.1), and Punktum's own introduction text tells users to install it. It provides appModules/punktum.py as well.

addonHandler.Addon.addToPackagePath does package.__path__.insert(0, converted_path), and add-ons are processed alphabetically — so punktum is inserted after brailleEssentials and therefore lands earlier on __path__. Confirmed by probe:

PUNKTUM-PROBE: OFFICIAL-addon app module imported from
  ...\nvda\addons\punktum\appModules\punktum.py

With the official add-on moved aside, the same probe shows this add-on's copy importing instead. So the bundled module only ever runs for users who have Punktum but not its add-on.

3b. The bundled copy is an older revision, and touches the braille handler off the main thread.

# brailleEssentials, appModules/punktum.py
Timer = threading.Timer(0.050, setCursorAndClick, args=(routingIndex,))
Timer.start()          # calls braille.handler.routeTo from a Timer thread
# official punktum 0.3.0
core.callLater(50, punktumSecondRouteTo, routingIndex)   # queued onto NVDA's main thread

0.3.0 also returns True/False so its global plugin can tell whether the gesture was handled, and guards each step with try/except.

Given both points, you may prefer to drop appModules/punktum.py from this add-on entirely and let the official add-on own it, or to resync it with 0.3.0. I have deliberately done neither here — this PR only makes the file valid UTF-8. Happy to follow up with whichever you would rather have.

Lint

Measured against upstream main, not a stashed tree.

ruff check: the only difference is that punktum.py: E902 stream did not contain valid UTF-8 is gone, because ruff can now read the file. No new findings.

ruff format --check: the set of flagged files is byte for byte identical before and after. Both files this PR adds or rewrites — brailleCompat.py and punktum.py — are on the clean side.

NVDA 2026.1 split the monolithic braille and brailleInput modules into the
braille and braille.input packages. The old names still resolve through the
module level __getattr__ installed by utils._deprecate, but only for reads:

* Each read logs a warning plus a full stack trace. Loading the add-on writes
  around fifty of those before NVDA has finished starting.
* Assigning to a moved name binds a new attribute on the old module instead of
  replacing the real one. NVDA core resolves getPropertiesBraille,
  getControlFieldBraille and getFormatFieldBraille from
  braille.regions.properties, and braille.regions.NVDAObject and
  braille.regions.textInfo bind two of them by name at import time, so the
  add-on's replacements were no longer reached at all. Role labels, document
  formatting indicators and undefined character representations silently
  stopped applying on 2026.1.

Add brailleCompat, which resolves each symbol the add-on needs from its new
home when the package layout is present and falls back to the pre-2026.1 names
otherwise, keeping addon_minimumNVDAVersion 2025.3.3 supported. Module level
functions are patched through patchBrailleFunction, which writes to every
namespace that holds a binding rather than to braille alone.

brailleInput.LOUIS_DOTS_IO_START was removed outright in 2026.1 with no public
replacement, so its value is carried in brailleCompat for _translate.

Tested on NVDA alpha-57493 (2026.1) and against stubs shaped like both module
layouts: the add-on loads with no deprecation warnings and no behaviour change.
@serrebidev serrebidev changed the title Resolve braille symbols from their NVDA 2026.1 locations Fix braille symbol resolution on NVDA 2026.1, and the Punktum app module never loading Aug 19, 2026
appModules/punktum.py is cp1252 encoded — the German comment on line 1 uses
0xFC for the u umlaut — and carries no PEP 263 encoding declaration.

This is survivable on NVDA 2026.1 only because Python 3.13 does not decode
comment bytes: non-UTF-8 sequences pass if they sit inside a comment, and fail
anywhere else. Python 3.14 tightened that and rejects them in comments too:

	SyntaxError: Non-UTF-8 code starting with '\xfc' in file punktum.py on
	line 1, but no encoding declared

So the module imports today and stops importing the moment NVDA moves to 3.14.
It also means ruff cannot read the file at all (E902), which quietly excludes it
from both `ruff check` and `ruff format --check`.

Re-encode as UTF-8 and add the "# coding: utf-8" line the rest of the add-on's
modules carry. The file is otherwise unchanged apart from ruff format, which
could not reach it before.
@serrebidev
serrebidev force-pushed the braille-package-compat branch from a61f7c3 to 265a45a Compare August 19, 2026 01:39
@serrebidev serrebidev changed the title Fix braille symbol resolution on NVDA 2026.1, and the Punktum app module never loading Fix braille symbol resolution on NVDA 2026.1, and make the Punktum app module valid UTF-8 Aug 19, 2026
`TextInfoRegion.update` still did a function-local `import brailleInput` before
reading `handler.untranslatedBraille`. That binds `brailleInput` as a local for
the whole function, so it shadowed the module-level
`from .brailleCompat import brailleInput` added by this branch and went back to
the deprecated flat module -- meaning `brailleInput.handler` logged a
deprecation warning on every braille update, the one path where it hurts most.

The late import is also no longer needed: brailleCompat is imported at module
scope here and resolves braille.input itself, so the circular import the
comment guarded against cannot occur.

Only shows up with a braille display attached; with noBraille the region never
updates, which is why it survived the first pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUFZd6deUGuMmJYnLAvXxL
@serrebidev

Copy link
Copy Markdown
Author

Pushed one more commit to this branch (6ee2cb9).

TextInfoRegion.update still carried a function-local import brailleInput before reading handler.untranslatedBraille. That binds brailleInput as a local for the whole function, so it shadowed the module-level from .brailleCompat import brailleInput this branch adds and fell back to the deprecated flat module — brailleInput.handler then logged a deprecation warning on every braille update, which is the hottest path there is for this.

The late import is also no longer necessary: brailleCompat is imported at module scope in patches.py and resolves braille.input itself, so the circular import the comment guarded against cannot happen.

It only shows up with a braille display attached — with noBraille the region never updates, which is why it survived the first pass.

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.

1 participant