Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
39 changes: 39 additions & 0 deletions source/_magnifier/magnifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@
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.winUser.constants import SystemMetrics
from winBindings import user32
from winAPI._displayTracking import OrientationState, getPrimaryDisplayOrientation
from .utils.types import (
MagnifierParameters,
Expand Down Expand Up @@ -184,6 +188,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 user32.GetSystemMetrics(SystemMetrics.MAXIMUM_TOUCHES) > 0:
# The user has a touchscreen but NVDA cannot intercept inputs; warn them.
if touchHandler.touchSupported():
reason = pgettext(
"magnifier",
# Translators: Warning when magnifier is started on a device with a touch screen but NVDA's touch support is disabled.
"Touch screen input cannot be intercepted because NVDA touch support is disabled. ",
Comment thread
Boumtchack marked this conversation as resolved.
Outdated
)
else:
reason = pgettext(
"magnifier",
# Translators: Warning when the magnifier starts on a portable/source copy with a touchscreen.
"Touch screen input cannot be intercepted because NVDA is not installed. ",
Comment thread
Boumtchack marked this conversation as resolved.
Outdated
)
wx.CallAfter(
gui.messageBox,
reason
+ pgettext(
"magnifier",
# Translators: Suffix appended to touch warning when touch inputs cannot be blocked by the magnifier.
"Touch inputs may not behave as expected while the magnifier is running.",
),
pgettext(
"magnifier",
# Translators: Title of the warning dialog shown when touch cannot be intercepted while the magnifier is running.
"Touch Screen Warning",
),
wx.OK | wx.ICON_WARNING,
)

def _updateMagnifier(self) -> None:
"""
Expand Down Expand Up @@ -254,6 +291,8 @@ def _stopMagnifier(self) -> None:
return
self._stopTimer()
self._isActive = False
if touchHandler.handler is not None:
touchHandler.blockTouchInput = False
# Unregister from display changes
_displayTracking.displayChanged.unregister(self._onDisplayChanged)

Expand Down
5 changes: 5 additions & 0 deletions source/touchHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""

import threading
import winsound
from functools import cached_property
from typing import (
TYPE_CHECKING,
Expand Down Expand Up @@ -189,6 +190,7 @@ class POINTER_TOUCH_INFO(Structure):

touchWindow = None
touchThread = None
blockTouchInput: bool = False


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

def pump(self):
for preheldTracker, tracker in self.trackerManager.emitTrackers():
if blockTouchInput:
winsound.MessageBeep(winsound.MB_ICONASTERISK)
continue
gesture = TouchInputGesture(preheldTracker, tracker, self._curTouchMode.value)
try:
inputCore.manager.executeGesture(gesture)
Expand Down
69 changes: 69 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 @@ -7,6 +7,7 @@
from _magnifier.magnifier import Magnifier
from _magnifier.utils.types import Filter, Direction, Coordinates, MagnifierAction
from comtypes import COMError
import touchHandler
import unittest
from winAPI._displayTracking import getPrimaryDisplayOrientation
from unittest.mock import MagicMock, patch
Expand Down Expand Up @@ -539,3 +540,71 @@ def testClampCoordinatesWithinBounds(self):
validCoords = Coordinates(centerX, centerY)
self.magnifier.currentCoordinates = validCoords
self.assertEqual(self.magnifier.currentCoordinates, validCoords)

def testStartMagnifierBlocksTouchWhenHandlerActive(self):
Comment thread
Boumtchack marked this conversation as resolved.
Outdated
"""blockTouchInput is set when the magnifier starts with the touch handler running."""
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()
self.assertTrue(touchHandler.blockTouchInput)

def testStartMagnifierWarnsWhenTouchSupportDisabled(self):
"""Dialog shown when device has a touchscreen but NVDA touch support is disabled."""
focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2)
self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=focusCoords)

with (
patch("touchHandler.handler", new=None),
patch("winBindings.user32.GetSystemMetrics", return_value=5),
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 testStartMagnifierWarnsOnPortableOrNoUIAccess(self):
"""Dialog shown when device has a touchscreen but NVDA cannot intercept (portable/no UI access)."""
focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2)
self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=focusCoords)

with (
patch("touchHandler.handler", new=None),
patch("winBindings.user32.GetSystemMetrics", return_value=5),
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 testStartMagnifierNoActionWithoutTouchscreen(self):
"""No dialog and no blocking when the device has no touchscreen."""
focusCoords = Coordinates(self.screenWidth // 2, self.screenHeight // 2)
self.magnifier._focusManager.getCurrentFocusCoordinates = MagicMock(return_value=focusCoords)

with (
patch("touchHandler.handler", new=None),
patch("winBindings.user32.GetSystemMetrics", return_value=0),
patch("_magnifier.magnifier.wx.CallAfter") as mock_call_after,
):
self.magnifier._startMagnifier()

mock_call_after.assert_not_called()

def testStopMagnifierUnblocksTouchInput(self):
"""blockTouchInput is reset to False when the magnifier stops with the touch handler active."""
self.magnifier._stopTimer = MagicMock()
self.magnifier._isActive = True
touchHandler.blockTouchInput = True
self.addCleanup(setattr, touchHandler, "blockTouchInput", False)

with patch("touchHandler.handler", new=MagicMock()):
self.magnifier._stopMagnifier()

self.assertFalse(touchHandler.blockTouchInput)
Loading