Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions source/_magnifier/magnifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
from comtypes import COMError
from logHandler import log
import wx
import gui
import ui
import speech
import screenCurtain
import touchHandler
from winAPI import _displayTracking
from winAPI._displayTracking import OrientationState, getPrimaryDisplayOrientation
from .utils.types import (
Expand Down Expand Up @@ -184,6 +186,39 @@ def _startMagnifier(self) -> None:

self._isActive = True
self.currentCoordinates = self._focusManager.getCurrentFocusCoordinates()
if touchHandler.handler is not None:
# Touch events are already intercepted by NVDA; block gesture execution
# to prevent incorrect coordinates from the magnified view reaching the system.
touchHandler.blockTouchInput = True
elif touchHandler.touchSupported():
# Touch is supported but the handler is not running (user disabled it).
# We can't intercept inputs, so warn the user.
wx.CallAfter(
gui.messageBox,
pgettext(
"magnifier",
# Translators: Warning shown when the magnifier starts and touch input cannot be intercepted
"Touch screen input cannot be intercepted because NVDA touch support is disabled. "
"Touch inputs may not behave as expected while the magnifier is running.",
),
# Translators: Title of the warning dialog shown when touch cannot be intercepted
pgettext("magnifier", "Magnifier — Touch screen warning"),
wx.OK | wx.ICON_WARNING,
)
else:
# Portable copy, running from source, or no UI access: touch cannot be intercepted.
wx.CallAfter(
gui.messageBox,
pgettext(
"magnifier",
# Translators: Warning shown when the magnifier starts on a portable/source copy with a touchscreen
"Touch screen input cannot be intercepted on portable copies of NVDA or without UI Access. "
"Touch inputs may not behave as expected while the magnifier is running.",
),
# Translators: Title of the warning dialog shown when touch cannot be intercepted
pgettext("magnifier", "Magnifier — Touch screen warning"),
wx.OK | wx.ICON_WARNING,
)
Comment thread
Boumtchack marked this conversation as resolved.
Outdated

def _updateMagnifier(self) -> None:
"""
Expand Down Expand Up @@ -254,6 +289,7 @@ def _stopMagnifier(self) -> None:
return
self._stopTimer()
self._isActive = False
touchHandler.blockTouchInput = False
Comment thread
Boumtchack marked this conversation as resolved.
Outdated
# Unregister from display changes
_displayTracking.displayChanged.unregister(self._onDisplayChanged)

Expand Down
6 changes: 6 additions & 0 deletions source/touchHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ class POINTER_TOUCH_INFO(Structure):

touchWindow = None
touchThread = None
blockTouchInput: bool = False


class TouchInputGesture(inputCore.InputGesture):
Expand Down Expand Up @@ -391,6 +392,11 @@ def setMode(self, mode):

def pump(self):
for preheldTracker, tracker in self.trackerManager.emitTrackers():
if blockTouchInput:
import tones

tones.beep(200, 50)
continue
Comment thread
Boumtchack marked this conversation as resolved.
Outdated
gesture = TouchInputGesture(preheldTracker, tracker, self._curTouchMode.value)
try:
inputCore.manager.executeGesture(gesture)
Expand Down
54 changes: 54 additions & 0 deletions tests/unit/test_magnifier/test_magnifier.py
Comment thread
Boumtchack marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -539,3 +539,57 @@ def testClampCoordinatesWithinBounds(self):
validCoords = Coordinates(centerX, centerY)
self.magnifier.currentCoordinates = validCoords
self.assertEqual(self.magnifier.currentCoordinates, validCoords)

def testStartMagnifierBlocksTouchOnInstalledCopy(self):
"""blockTouchInput is set to True when the magnifier starts with the touch handler active."""
focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2)
self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=focusCoords)

with (
patch("touchHandler.handler", new=MagicMock()),
patch("touchHandler.blockTouchInput", False),
):
self.magnifier._startMagnifier()
import touchHandler

self.assertTrue(touchHandler.blockTouchInput)

def testStartMagnifierWarnsWhenTouchSupportedButHandlerInactive(self):
"""A warning dialog is shown when touch is supported but the handler is not running."""
focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2)
self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=focusCoords)

with (
patch("touchHandler.handler", new=None),
patch("touchHandler.touchSupported", return_value=True),
patch("_magnifier.magnifier.wx.CallAfter") as mock_call_after,
):
self.magnifier._startMagnifier()

mock_call_after.assert_called_once()

def testStartMagnifierWarnsOnPortableCopy(self):
"""A warning dialog is shown on portable copies where touch cannot be intercepted."""
focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2)
self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=focusCoords)

with (
patch("touchHandler.handler", new=None),
patch("touchHandler.touchSupported", return_value=False),
patch("_magnifier.magnifier.wx.CallAfter") as mock_call_after,
):
self.magnifier._startMagnifier()

mock_call_after.assert_called_once()

def testStopMagnifierUnblocksTouchInput(self):
"""blockTouchInput is reset to False unconditionally when the magnifier stops."""
import touchHandler

self.magnifier._stopTimer = MagicMock()
self.magnifier._isActive = True
touchHandler.blockTouchInput = True

self.magnifier._stopMagnifier()

self.assertFalse(touchHandler.blockTouchInput)
Loading