Skip to content

Commit 797a881

Browse files
beta to master (#20143)
Co-authored-by: GitHub Actions <actions@github.com>
2 parents f8b8e1c + 7bf0d9a commit 797a881

10 files changed

Lines changed: 5368 additions & 5118 deletions

File tree

source/_magnifier/__init__.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
"""
1010

1111
from typing import TYPE_CHECKING
12-
from .config import getMagnifiedView
12+
13+
from logHandler import log
14+
15+
from .config import getMagnifiedView, getEnabled, setEnabled
1316
from .utils.types import MagnifiedView
1417

1518
if TYPE_CHECKING:
@@ -72,9 +75,44 @@ def initialize() -> None:
7275
"""
7376
Initialize the magnifier module with the default magnifier view from config.
7477
"""
78+
log.debug("Initializing magnifier")
7579
magnifiedView = getMagnifiedView()
7680
_setMagnifiedView(magnifiedView)
81+
if getEnabled():
82+
start()
83+
84+
85+
def terminate() -> None:
86+
"""
87+
Terminate the magnifier module.
88+
Called when NVDA shuts down.
89+
"""
90+
global _magnifier
91+
92+
log.debug("Terminating magnifier")
93+
stop(persist=False)
94+
_magnifier = None
95+
96+
97+
def start() -> None:
98+
if _magnifier is None:
99+
log.error("Attempted to start magnifier, but it is not initialized.")
100+
return
77101
_magnifier._startMagnifier()
102+
setEnabled(True)
103+
104+
105+
def stop(persist: bool = True) -> None:
106+
"""Stop the magnifier if it is active.
107+
108+
:param persist: Whether to persist the magnifier state
109+
"""
110+
if isActive():
111+
_magnifier._stopMagnifier()
112+
if persist:
113+
setEnabled(False)
114+
else:
115+
log.debug("Attempted to stop magnifier, but it is not active.")
78116

79117

80118
def isActive() -> bool:
@@ -111,14 +149,3 @@ def getMagnifier() -> "Magnifier | None":
111149
"""
112150
global _magnifier
113151
return _magnifier
114-
115-
116-
def terminate() -> None:
117-
"""
118-
Terminate the magnifier module.
119-
Called when NVDA shuts down.
120-
"""
121-
global _magnifier
122-
if _magnifier and _magnifier._isActive:
123-
_magnifier._stopMagnifier()
124-
_magnifier = None

source/_magnifier/commands.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@
1010

1111
from typing import Literal
1212
import ui
13-
from . import getMagnifier, initialize, terminate, changeMagnifiedView
13+
from . import changeMagnifiedView, getMagnifier, start, stop
1414
from .config import (
1515
getMagnifiedView,
1616
setMagnifiedView,
1717
getZoomLevelString,
1818
getFilter,
19-
getFullscreenMode,
20-
ZoomLevel,
2119
getFollowState,
20+
getFullscreenMode,
2221
setFollowState,
2322
toggleAllFollowStates,
23+
ZoomLevel,
2424
)
2525
from .magnifier import Magnifier
2626
from .fullscreenMagnifier import FullScreenMagnifier
@@ -89,7 +89,7 @@ def toggleMagnifier() -> None:
8989
magnifier: Magnifier | None = getMagnifier()
9090
if magnifier and magnifier._isActive:
9191
# Stop magnifier
92-
terminate()
92+
stop()
9393
ui.message(
9494
pgettext(
9595
"magnifier",
@@ -107,7 +107,7 @@ def toggleMagnifier() -> None:
107107
),
108108
)
109109
else:
110-
initialize()
110+
start()
111111
currentFilter = getFilter()
112112
magnifiedView = getMagnifiedView()
113113
zoomLevel = getZoomLevelString()

source/_magnifier/config.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# A part of NonVisual Desktop Access (NVDA)
2-
# Copyright (C) 2025 NV Access Limited, Antoine Haffreingue
2+
# Copyright (C) 2025-2026 NV Access Limited, Antoine Haffreingue
33
# This file may be used under the terms of the GNU General Public License, version 2 or later, as modified by the NVDA license.
44
# For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt
55

@@ -13,6 +13,24 @@
1313
from .utils.types import Filter, FullScreenMode, MagnifierFollowFocusType, MagnifiedView
1414

1515

16+
def setEnabled(enable: bool) -> None:
17+
"""
18+
Set the config for the magnifier state (enable or disabled).
19+
20+
:param enable: True if the magnifier is enabled, False if it is disabled.
21+
"""
22+
config.conf["magnifier"]["enabled"] = enable
23+
24+
25+
def getEnabled() -> bool:
26+
"""
27+
Check if the magnifier is enabled in config.
28+
29+
:return: True if the magnifier is enabled, False otherwise.
30+
"""
31+
return config.conf["magnifier"]["enabled"]
32+
33+
1634
class ZoomLevel:
1735
"""
1836
Constants and utilities for zoom level management.

source/_magnifier/fullscreenMagnifier.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ def __init__(self):
4141
self.currentCoordinates = Coordinates(0, 0)
4242
self._spotlightManager = SpotlightManager(self)
4343
self._displaySize = Size(self._displayOrientation.width, self._displayOrientation.height)
44-
self._startMagnifier()
4544

4645
@Magnifier.filterType.setter
4746
def filterType(self, value: Filter) -> None:

source/config/configSpec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
117117
# Magnifier settings
118118
[magnifier]
119+
enabled = boolean(default=false)
119120
magnifiedView = string(default="fullscreen")
120121
zoomLevel = float(min=1.0, max=10.0, default=2.0)
121122
isTrueCentered = boolean(default=False)

source/core.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# A part of NonVisual Desktop Access (NVDA)
2-
# Copyright (C) 2006-2025 NV Access Limited, Aleksey Sadovoy, Christopher Toth, Joseph Lee, Peter Vágner,
2+
# Copyright (C) 2006-2026 NV Access Limited, Aleksey Sadovoy, Christopher Toth, Joseph Lee, Peter Vágner,
33
# Derek Riemer, Babbage B.V., Zahari Yurukov, Łukasz Golonka, Cyrille Bougot, Julien Cochuyt
44
# This file is covered by the GNU General Public License.
55
# See the file COPYING for more details.
@@ -10,7 +10,6 @@
1010
from typing import (
1111
TYPE_CHECKING,
1212
Any,
13-
List,
1413
Optional,
1514
)
1615
import comtypes
@@ -135,7 +134,7 @@ def handleReplaceCLIArg(cliArgument: str) -> bool:
135134
return cliArgument in ("-r", "--replace")
136135

137136
addonHandler.isCLIParamKnown.register(handleReplaceCLIArg)
138-
unknownCLIParams: List[str] = list()
137+
unknownCLIParams: list[str] = list()
139138
for param in globalVars.unknownAppArgs:
140139
isParamKnown = addonHandler.isCLIParamKnown.decide(cliArgument=param)
141140
if not isParamKnown:
@@ -324,7 +323,9 @@ def resetConfiguration(factoryDefaults=False):
324323
import audio
325324
import screenCurtain
326325
import mathPres
326+
import _magnifier as magnifier
327327

328+
magnifier.terminate()
328329
log.debug("Terminating vision")
329330
vision.terminate()
330331
log.debug("Terminating Screen Curtain")
@@ -399,6 +400,7 @@ def resetConfiguration(factoryDefaults=False):
399400
vision.initialize()
400401
log.debug("initializing Screen Curtain")
401402
screenCurtain.initialize()
403+
magnifier.initialize()
402404
log.debug("Reloading user and locale input gesture maps")
403405
inputCore.manager.loadUserGestureMap()
404406
inputCore.manager.loadLocaleGestureMap()
@@ -1058,6 +1060,10 @@ def Notify(self):
10581060

10591061
sessionTracking.initialize()
10601062

1063+
import _magnifier as magnifier
1064+
1065+
magnifier.initialize()
1066+
10611067
NVDAState._TrackNVDAInitialization.markInitializationComplete()
10621068

10631069
log.info("NVDA initialized")
@@ -1084,6 +1090,7 @@ def _doPostNvdaStartupAction():
10841090
)
10851091
queueHandler.pumpAll()
10861092
_terminate(gui)
1093+
_terminate(magnifier)
10871094
config.saveOnExit()
10881095

10891096
_doLoseFocus()

source/gui/settingsDialogs.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import keyboardHandler
4141
import languageHandler
4242
import logHandler
43+
from _magnifier.commands import toggleMagnifier
4344
import _magnifier.config as magnifierConfig
4445
from _magnifier.utils.types import Filter, FullScreenMode, MagnifierFollowFocusType
4546
import queueHandler
@@ -6049,6 +6050,18 @@ def makeSettings(
60496050
sizer=settingsSizer,
60506051
)
60516052

6053+
# Enable the magnifier
6054+
# Translators: The label for a setting in magnifier settings to enable or disable the magnifier.
6055+
enableMagnifierText = _("&Enable magnifier (immediate effect)")
6056+
self._magnifierEnabledInitially = magnifierConfig.getEnabled()
6057+
self.enableMagnifierCheckBox = sHelper.addItem(wx.CheckBox(self, label=enableMagnifierText))
6058+
self.bindHelpEvent(
6059+
"MagnifierEnable",
6060+
self.enableMagnifierCheckBox,
6061+
)
6062+
self.enableMagnifierCheckBox.Bind(wx.EVT_CHECKBOX, self.onEnableMagnifierChange)
6063+
self.enableMagnifierCheckBox.SetValue(self._magnifierEnabledInitially)
6064+
60526065
# ZOOM SETTINGS
60536066
# Translators: The label for a setting in magnifier settings to select the zoom level.
60546067
zoomLabelText = _("&Zoom level:")
@@ -6066,7 +6079,7 @@ def makeSettings(
60666079
self.zoomList,
60676080
)
60686081

6069-
# Set value from config
6082+
# Set value from config
60706083
zoomLevel = magnifierConfig.getZoomLevel()
60716084
zoomIndex = bisect.bisect_left(zoomValues, zoomLevel)
60726085
# Find the closest value
@@ -6099,7 +6112,7 @@ def makeSettings(
60996112

61006113
# FILTER SETTINGS
61016114
# Translators: The label for a setting in magnifier settings to select the default filter
6102-
filterLabelText = _("&filter:")
6115+
filterLabelText = _("F&ilter:")
61036116
filterChoices = [f.displayString for f in Filter]
61046117
self.filterList = sHelper.addLabeledControl(
61056118
filterLabelText,
@@ -6114,7 +6127,7 @@ def makeSettings(
61146127

61156128
# FULLSCREEN MODE SETTINGS
61166129
# Translators: The label for a setting in magnifier settings to select the full-screen mode
6117-
fullscreenModeLabelText = _("&fullscreen mode:")
6130+
fullscreenModeLabelText = _("&Fullscreen mode:")
61186131
fullscreenModeChoices = [mode.displayString for mode in FullScreenMode] if FullScreenMode else []
61196132
self.fullscreenModeList = sHelper.addLabeledControl(
61206133
fullscreenModeLabelText,
@@ -6170,7 +6183,7 @@ def makeSettings(
61706183

61716184
# KEEP MOUSE CENTERED
61726185
# Translators: The label for a checkbox to keep the mouse pointer centered in the magnifier view
6173-
keepMouseCenteredText = _("Keep &mouse pointer centered in magnifier view")
6186+
keepMouseCenteredText = _("Keep mouse pointer &centered in magnifier view")
61746187
self.keepMouseCenteredCheckBox = sHelper.addItem(wx.CheckBox(self, label=keepMouseCenteredText))
61756188
self.bindHelpEvent(
61766189
"MagnifierKeepMouseCentered",
@@ -6180,6 +6193,8 @@ def makeSettings(
61806193

61816194
def onSave(self):
61826195
"""Save the current selections to config."""
6196+
magnifierConfig.setEnabled(self.enableMagnifierCheckBox.GetValue())
6197+
61836198
selectedZoom = self.zoomList.GetSelection()
61846199
magnifierConfig.setZoomLevel(magnifierConfig.ZoomLevel.zoom_range()[selectedZoom])
61856200

@@ -6196,6 +6211,20 @@ def onSave(self):
61966211
magnifierConfig.setFollowState(focusType, checkBox.GetValue())
61976212
config.conf["magnifier"]["keepMouseCentered"] = self.keepMouseCenteredCheckBox.GetValue()
61986213

6214+
def onDiscard(self):
6215+
"""Restore magnifier state from original settings from config."""
6216+
if self._magnifierEnabledInitially != magnifierConfig.getEnabled():
6217+
toggleMagnifier()
6218+
self.enableMagnifierCheckBox.SetValue(self._magnifierEnabledInitially)
6219+
6220+
def onEnableMagnifierChange(self, evt: wx.CommandEvent):
6221+
"""Enable magnifier immediately when the checkbox is toggled, and update the checkbox state if there is an error enabling the magnifier."""
6222+
requestedEnabled = evt.IsChecked()
6223+
currentEnabled = magnifierConfig.getEnabled()
6224+
if requestedEnabled != currentEnabled:
6225+
toggleMagnifier()
6226+
self.enableMagnifierCheckBox.SetValue(magnifierConfig.getEnabled())
6227+
61996228

62006229
class PrivacyAndSecuritySettingsPanel(SettingsPanel):
62016230
# Translators: The title of the privacy and security category in NVDA's settings.

tests/unit/test_magnifier/test_fullscreenMagnifier.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ def testCannotStartWhenWindowsMagnifierRunning(self):
298298

299299
with patch("_magnifier.fullscreenMagnifier.ui.message") as mock_message:
300300
magnifier = FullScreenMagnifier()
301+
magnifier._startMagnifier()
301302

302303
self.assertFalse(magnifier._isActive)
303304
mock_message.assert_called_once()
@@ -311,6 +312,7 @@ def testCannotStartWhenMagInitializeFails(self):
311312

312313
with patch("_magnifier.fullscreenMagnifier.ui.message") as mock_message:
313314
magnifier = FullScreenMagnifier()
315+
magnifier._startMagnifier()
314316

315317
self.assertFalse(magnifier._isActive)
316318
mock_message.assert_called_once()

0 commit comments

Comments
 (0)