diff --git a/source/_magnifier/__init__.py b/source/_magnifier/__init__.py index f742dd6f1ea..b4cbed790f2 100644 --- a/source/_magnifier/__init__.py +++ b/source/_magnifier/__init__.py @@ -11,6 +11,7 @@ from typing import TYPE_CHECKING from .config import getMagnifiedView from .utils.types import MagnifiedView +from .fullscreenMagnifier import FullScreenMagnifier if TYPE_CHECKING: from .magnifier import Magnifier @@ -119,6 +120,9 @@ def terminate() -> None: Called when NVDA shuts down. """ global _magnifier - if _magnifier and _magnifier._isActive: - _magnifier._stopMagnifier() + if _magnifier: + if _magnifier._isActive: + _magnifier._stopMagnifier() + if isinstance(_magnifier, FullScreenMagnifier): + _magnifier._finalUninitializeNativeMagnification() _magnifier = None diff --git a/source/_magnifier/fullscreenMagnifier.py b/source/_magnifier/fullscreenMagnifier.py index b9d1cd697d3..eb9615fbf70 100644 --- a/source/_magnifier/fullscreenMagnifier.py +++ b/source/_magnifier/fullscreenMagnifier.py @@ -40,6 +40,7 @@ def __init__(self): self._currentCoordinates = Coordinates(0, 0) self._spotlightManager = SpotlightManager(self) self._displaySize = Size(self._displayOrientation.width, self._displayOrientation.height) + self._nativeApiInitialized: bool = False self._startMagnifier() @Magnifier.filterType.setter @@ -89,26 +90,25 @@ def _startMagnifier(self) -> None: def _initializeNativeMagnification(self) -> None: """ - Initialize the Magnification API and verify it is fully usable. - - Raises OSError if MagInitialize fails or if the initial fullscreen - transform fails (e.g. Windows Magnifier already holds the API). If - MagSetFullscreenTransform fails after a successful MagInitialize, this - method uninitializes the native magnification API before re-raising. - Failures from MagInitialize are propagated to the caller. - """ - magnification.MagInitialize() - log.debug("Magnification API initialized") + Initialize the Magnification API and apply the initial fullscreen transform. + MagInitialize is called only once per instance lifetime: the Initialize → + Uninitialize → Initialize cycle within the same process causes + MagSetFullscreenTransform to fail with WinError 0. To avoid this, the API + stays initialized between magnifier toggles and is only uninitialized on + NVDA shutdown via _finalUninitializeNativeMagnification. + Raises OSError if MagInitialize fails or if MagSetFullscreenTransform fails + (e.g. Windows Magnifier already holds the API). + """ + if not self._nativeApiInitialized: + magnification.MagInitialize() + self._nativeApiInitialized = True + log.debug("Magnification API initialized") # Applying the first real update verifies the API is usable without # briefly jumping the magnified view to the top-left corner. - try: - coordinates = self._getCoordinatesForMode(self._currentCoordinates) - # Save screen position for mode continuity, matching _doUpdate. - self._lastScreenPosition = coordinates - self._fullscreenMagnifier(coordinates) - except OSError: - self._uninitializeNativeMagnification() - raise + coordinates = self._getCoordinatesForMode(self._currentCoordinates) + # Save screen position for mode continuity, matching _doUpdate. + self._lastScreenPosition = coordinates + self._fullscreenMagnifier(coordinates) def _doUpdate(self): """ @@ -123,11 +123,20 @@ def _doUpdate(self): def _stopMagnifier(self) -> None: """ - Stop the Full-screen magnifier using windows DLL + Stop the Full-screen magnifier using windows DLL. + The native API is intentionally kept initialized to allow clean restarts; + it is only uninitialized on NVDA shutdown via _finalUninitializeNativeMagnification. """ super()._stopMagnifier() self._resetMagnification() + + def _finalUninitializeNativeMagnification(self) -> None: + """ + Uninitialize the Magnification API. + Must be called after _stopMagnifier, which already resets the transform. + """ self._uninitializeNativeMagnification() + self._nativeApiInitialized = False @trackNativeMagnifierErrors def _resetMagnification(self) -> None: @@ -172,6 +181,7 @@ def _attemptRecovery(self) -> None: ) self._uninitializeNativeMagnification() + self._nativeApiInitialized = False try: # _initializeNativeMagnification also probes MagSetFullscreenTransform,