Skip to content

Commit 1ba7192

Browse files
authored
Merge a0b563e into 2faead2
2 parents 2faead2 + a0b563e commit 1ba7192

5 files changed

Lines changed: 333 additions & 25 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from ctypes import WinError
2+
from ctypes.wintypes import RECT
3+
4+
from locationHelper import RectLTRB, RectLTWH
5+
from logHandler import log
6+
from vision import (
7+
_isDebug,
8+
)
9+
from .screenCurtain import MAGTRANSFORM, Magnification
10+
from winAPI import _displayTracking
11+
from windowUtils import CustomWindow
12+
import winUser
13+
14+
WindowClassName = "MagnifierWindow"
15+
WindowTitle = "Screen Magnifier Sample"
16+
WC_MAGNIFIER = "Magnifier"
17+
RESTOREDWINDOWSTYLES = (
18+
winUser.WS_SIZEBOX
19+
| winUser.WS_SYSMENU
20+
| winUser.WS_CLIPCHILDREN
21+
| winUser.WS_CAPTION
22+
| winUser.WS_MAXIMIZEBOX
23+
)
24+
25+
26+
class HostWindow(CustomWindow):
27+
className = WindowClassName
28+
windowName = WindowTitle
29+
windowsStyle = RESTOREDWINDOWSTYLES
30+
extendedWindowStyle = (
31+
# Ensure that the window is on top of all other windows
32+
winUser.WS_EX_TOPMOST
33+
# A layered window ensures that L{transparentColor} will be considered transparent, when painted
34+
| winUser.WS_EX_LAYERED
35+
)
36+
37+
def __init__(self, magnificationFactor: int = 2):
38+
super().__init__(
39+
windowName=self.windowName,
40+
windowStyle=self.windowsStyle,
41+
extendedWindowStyle=self.extendedWindowStyle,
42+
parent=None,
43+
)
44+
winUser.SetLayeredWindowAttributes(
45+
self.handle,
46+
0x00,
47+
0xFF,
48+
winUser.LWA_ALPHA,
49+
)
50+
if not winUser.user32.SetWindowPos(
51+
self.handle,
52+
winUser.HWND_TOPMOST,
53+
self.targetRect.left,
54+
self.targetRect.top,
55+
self.targetRect.width,
56+
int(self.targetRect.height),
57+
winUser.SWP_NOACTIVATE | winUser.SWP_NOMOVE | winUser.SWP_NOSIZE,
58+
):
59+
raise WinError()
60+
if not winUser.user32.UpdateWindow(self.handle):
61+
raise WinError()
62+
self.magnifierWindow = MagnifierWindow(self, magnificationFactor)
63+
64+
@property
65+
def targetRect(self) -> RectLTRB:
66+
# Top quarter of screen
67+
return RectLTRB(
68+
0, 0, _displayTracking._orientationState.width, _displayTracking._orientationState.height / 4
69+
)
70+
71+
def windowProc(self, hwnd: int, msg: int, wParam: int, lParam: int):
72+
log.debug(f"received window proc message: {msg}")
73+
74+
75+
class MagnifierWindow(CustomWindow):
76+
className = WC_MAGNIFIER
77+
windowName = "MagnifierWindow"
78+
windowStyle = winUser.WS_CHILD | winUser.MS_SHOWMAGNIFIEDCURSOR | winUser.WS_VISIBLE
79+
80+
def __init__(self, hostWindow: HostWindow, magnificationFactor: int = 2):
81+
self.hostWindow = hostWindow
82+
self.magnificationFactor = magnificationFactor
83+
if _isDebug():
84+
log.debug("initializing NVDA Magnifier window")
85+
super().__init__(
86+
windowName=self.windowName,
87+
windowStyle=self.windowStyle,
88+
parent=hostWindow.handle,
89+
)
90+
91+
magWindowRect = self.magWindowRect
92+
if not winUser.user32.SetWindowPos(
93+
self.handle,
94+
winUser.HWND_TOPMOST,
95+
magWindowRect.left,
96+
magWindowRect.top,
97+
magWindowRect.width,
98+
magWindowRect.height,
99+
winUser.SWP_NOACTIVATE | winUser.SWP_NOMOVE | winUser.SWP_NOSIZE,
100+
):
101+
raise WinError()
102+
if not winUser.user32.UpdateWindow(self.handle):
103+
raise WinError()
104+
105+
Magnification.MagSetWindowSource(self.handle, RECT(200, 200, 700, 700))
106+
Magnification.MagSetWindowTransform(self.handle, MAGTRANSFORM(self.magnificationFactor))
107+
108+
@property
109+
def magWindowRect(self) -> RectLTWH:
110+
r = winUser.getClientRect(self.hostWindow.handle)
111+
return RectLTRB(
112+
r.left,
113+
r.top,
114+
r.right,
115+
r.bottom,
116+
).toLTWH()
117+
118+
def windowProc(self, hwnd: int, msg: int, wParam: int, lParam: int):
119+
log.debug(f"received window proc message: {msg}")

source/visionEnhancementProviders/screenCurtain.py

Lines changed: 101 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# A part of NonVisual Desktop Access (NVDA)
22
# This file is covered by the GNU General Public License.
33
# See the file COPYING for more details.
4-
# Copyright (C) 2018-2023 NV Access Limited, Babbage B.V., Leonard de Ruijter
4+
# Copyright (C) 2018-2024 NV Access Limited, Babbage B.V., Leonard de Ruijter
55

66
"""Screen curtain implementation based on the windows magnification API.
77
The Magnification API has been marked by MS as unsupported for WOW64 applications such as NVDA. (#12491)
@@ -10,7 +10,7 @@
1010
import os
1111
from vision import providerBase
1212
from ctypes import Structure, windll, c_float, POINTER, WINFUNCTYPE, WinError
13-
from ctypes.wintypes import BOOL
13+
from ctypes.wintypes import BOOL, FLOAT, HWND, RECT, INT
1414
from autoSettingsUtils.driverSetting import BooleanDriverSetting
1515
from autoSettingsUtils.autoSettings import SupportedSettingType
1616
import wx
@@ -30,6 +30,24 @@ class MAGCOLOREFFECT(Structure):
3030
_fields_ = (("transform", c_float * 5 * 5),)
3131

3232

33+
class MAGTRANSFORM(Structure):
34+
_fields_ = (("v", c_float * 3 * 3),)
35+
36+
def __init__(self, magnificationFactor: float = 1.0):
37+
"""
38+
https://learn.microsoft.com/en-us/windows/win32/api/magnification/ns-magnification-magtransform
39+
40+
:param magnificationFactor: defaults to 1.0.
41+
The minimum value of this parameter is 1.0, and the maximum value is 4096.0.
42+
If this value is 1.0, the screen content is not magnified and no offsets are applied.
43+
"""
44+
super().__init__()
45+
assert 1.0 <= magnificationFactor <= 4096.0
46+
self.v[0][0] = magnificationFactor
47+
self.v[1][1] = magnificationFactor
48+
self.v[2][2] = 1.0
49+
50+
3351
# homogeneous matrix for a 4-space transformation (red, green, blue, opacity).
3452
# https://docs.microsoft.com/en-gb/windows/win32/gdiplus/-gdiplus-using-a-color-matrix-to-transform-a-single-color-use
3553
TRANSFORM_BLACK = MAGCOLOREFFECT() # empty transformation
@@ -51,14 +69,29 @@ class Magnification:
5169
# Set full screen color effect
5270
_MagSetFullscreenColorEffectFuncType = WINFUNCTYPE(BOOL, POINTER(MAGCOLOREFFECT))
5371
_MagSetFullscreenColorEffectArgTypes = ((1, "effect"),)
72+
MagSetFullscreenColorEffect = _MagSetFullscreenColorEffectFuncType(
73+
("MagSetFullscreenColorEffect", _magnification),
74+
_MagSetFullscreenColorEffectArgTypes,
75+
)
76+
MagSetFullscreenColorEffect.errcheck = _errCheck
5477

5578
# Get full screen color effect
5679
_MagGetFullscreenColorEffectFuncType = WINFUNCTYPE(BOOL, POINTER(MAGCOLOREFFECT))
5780
_MagGetFullscreenColorEffectArgTypes = ((2, "effect"),)
81+
MagGetFullscreenColorEffect = _MagGetFullscreenColorEffectFuncType(
82+
("MagGetFullscreenColorEffect", _magnification),
83+
_MagGetFullscreenColorEffectArgTypes,
84+
)
85+
MagGetFullscreenColorEffect.errcheck = _errCheck
5886

5987
# show system cursor
6088
_MagShowSystemCursorFuncType = WINFUNCTYPE(BOOL, BOOL)
6189
_MagShowSystemCursorArgTypes = ((1, "showCursor"),)
90+
MagShowSystemCursor = _MagShowSystemCursorFuncType(
91+
("MagShowSystemCursor", _magnification),
92+
_MagShowSystemCursorArgTypes,
93+
)
94+
MagShowSystemCursor.errcheck = _errCheck
6295

6396
# initialize
6497
_MagInitializeFuncType = WINFUNCTYPE(BOOL)
@@ -70,28 +103,72 @@ class Magnification:
70103
MagUninitialize = _MagUninitializeFuncType(("MagUninitialize", _magnification))
71104
MagUninitialize.errcheck = _errCheck
72105

73-
# These magnification functions are not available on versions of Windows prior to Windows 8,
74-
# and therefore looking them up from the magnification library will raise an AttributeError.
75-
try:
76-
MagSetFullscreenColorEffect = _MagSetFullscreenColorEffectFuncType(
77-
("MagSetFullscreenColorEffect", _magnification),
78-
_MagSetFullscreenColorEffectArgTypes,
79-
)
80-
MagSetFullscreenColorEffect.errcheck = _errCheck
81-
MagGetFullscreenColorEffect = _MagGetFullscreenColorEffectFuncType(
82-
("MagGetFullscreenColorEffect", _magnification),
83-
_MagGetFullscreenColorEffectArgTypes,
84-
)
85-
MagGetFullscreenColorEffect.errcheck = _errCheck
86-
MagShowSystemCursor = _MagShowSystemCursorFuncType(
87-
("MagShowSystemCursor", _magnification),
88-
_MagShowSystemCursorArgTypes,
89-
)
90-
MagShowSystemCursor.errcheck = _errCheck
91-
except AttributeError:
92-
MagSetFullscreenColorEffect = None
93-
MagGetFullscreenColorEffect = None
94-
MagShowSystemCursor = None
106+
_MagSetWindowSourceFuncType = WINFUNCTYPE(BOOL, HWND, POINTER(RECT))
107+
_MagSetWindowSourceArgTypes = ((1, "hwnd"), (1, "rect"))
108+
MagSetWindowSource = _MagSetWindowSourceFuncType(
109+
("MagSetWindowSource", _magnification),
110+
_MagSetWindowSourceArgTypes,
111+
)
112+
MagSetWindowSource.errcheck = _errCheck
113+
114+
_MagGetWindowSourceFuncType = WINFUNCTYPE(BOOL, HWND, POINTER(RECT))
115+
_MagGetWindowSourceArgTypes = ((1, "hwnd"), (2, "rect"))
116+
MagGetWindowSource = _MagGetWindowSourceFuncType(
117+
("MagGetWindowSource", _magnification),
118+
_MagGetWindowSourceArgTypes,
119+
)
120+
MagGetWindowSource.errcheck = _errCheck
121+
122+
_MagSetWindowTransformFuncType = WINFUNCTYPE(BOOL, HWND, POINTER(MAGTRANSFORM))
123+
_MagSetWindowTransformArgTypes = ((1, "hwnd"), (1, "transform"))
124+
MagSetWindowTransform = _MagSetWindowTransformFuncType(
125+
("MagSetWindowTransform", _magnification),
126+
_MagSetWindowTransformArgTypes,
127+
)
128+
MagSetWindowTransform.errcheck = _errCheck
129+
130+
# Create transformation window
131+
_MagGetWindowTransformFuncType = WINFUNCTYPE(BOOL, HWND, POINTER(MAGTRANSFORM))
132+
_MagGetWindowTransformArgTypes = ((1, "hwnd"), (2, "transform"))
133+
MagGetWindowTransform = _MagGetWindowTransformFuncType(
134+
("MagGetWindowTransform", _magnification),
135+
_MagGetWindowTransformArgTypes,
136+
)
137+
MagGetWindowTransform.errcheck = _errCheck
138+
139+
_MagSetFullscreenTransformFuncType = WINFUNCTYPE(BOOL, POINTER(FLOAT), POINTER(INT), POINTER(INT))
140+
_MagSetFullscreenTransformArgTypes = ((1, "magLevel"), (1, "offsetX"), (1, "offsetY"))
141+
MagSetFullscreenTransform = _MagSetFullscreenTransformFuncType(
142+
("MagSetFullscreenTransform", _magnification),
143+
_MagSetFullscreenTransformArgTypes,
144+
)
145+
MagSetFullscreenTransform.errcheck = _errCheck
146+
147+
_MagGetFullscreenTransformFuncType = WINFUNCTYPE(BOOL, POINTER(FLOAT), POINTER(INT), POINTER(INT))
148+
_MagGetFullscreenTransformArgTypes = ((2, "magLevel"), (2, "offsetX"), (2, "offsetY"))
149+
MagGetFullscreenTransform = _MagGetFullscreenTransformFuncType(
150+
("MagGetFullscreenTransform", _magnification),
151+
_MagGetFullscreenTransformArgTypes,
152+
)
153+
MagGetFullscreenTransform.errcheck = _errCheck
154+
155+
# # Create transformation window
156+
# _MagGetInputTransformFuncType = WINFUNCTYPE(BOOL, POINTER(BOOL), POINTER(RECT), POINTER(RECT))
157+
# _MagGetInputTransformArgTypes = ((2, "enabled"), (2, "src"), (2, "dest"))
158+
# MagGetInputTransform = _MagGetInputTransformFuncType(
159+
# ("MagGetInputTransform", _magnification),
160+
# _MagGetInputTransformArgTypes,
161+
# )
162+
# MagGetInputTransform.errcheck = _errCheck
163+
164+
# # Create transformation window
165+
# _MagSetInputTransformFuncType = WINFUNCTYPE(BOOL, POINTER(BOOL), POINTER(RECT), POINTER(RECT))
166+
# _MagSetInputTransformArgTypes = ((1, "enabled"), (1, "src"), (1, "dest"))
167+
# MagSetInputTransform = _MagGetInputTransformFuncType(
168+
# ("MagSetInputTransform", _magnification),
169+
# _MagSetInputTransformArgTypes,
170+
# )
171+
# MagSetInputTransform.errcheck = _errCheck
95172

96173

97174
# Translators: Name for a vision enhancement provider that disables output to the screen,

source/winUser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ class GUITHREADINFO(Structure):
142142
WS_VSCROLL = 0x200000
143143
WS_CAPTION = 0xC00000
144144
WS_CLIPCHILDREN = 0x02000000
145+
WS_MAXIMIZEBOX = 0x00010000
146+
WS_CHILD = 0x40000000
147+
MS_SHOWMAGNIFIEDCURSOR = 0x0001
145148
WS_EX_TOPMOST = 0x00000008
146149
WS_EX_LAYERED = 0x80000
147150
WS_EX_TOOLWINDOW = 0x00000080
@@ -533,7 +536,7 @@ def getControlID(hwnd):
533536
return user32.GetWindowLongW(hwnd, GWL_ID)
534537

535538

536-
def getClientRect(hwnd):
539+
def getClientRect(hwnd: HWND) -> RECT:
537540
r = RECT()
538541
if not user32.GetClientRect(hwnd, byref(r)):
539542
raise WinError()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# This file is covered by the GNU General Public License.
3+
# See the file COPYING for more details.
4+
# Copyright (C) 2024 NV Access Limited.
5+
6+
"""Unit tests for the vision enhancement providers submodule."""

0 commit comments

Comments
 (0)