Skip to content

Commit dfe9108

Browse files
authored
Merge a3c3537 into 4d384fa
2 parents 4d384fa + a3c3537 commit dfe9108

4 files changed

Lines changed: 145 additions & 6 deletions

File tree

source/globalCommands.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import core
4444
import winVersion
4545
from base64 import b16encode
46+
import vision
4647

4748
#: Script category for text review commands.
4849
# Translators: The name of a category of NVDA commands.
@@ -68,6 +69,9 @@
6869
#: Script category for Braille commands.
6970
# Translators: The name of a category of NVDA commands.
7071
SCRCAT_BRAILLE = _("Braille")
72+
#: Script category for Vision commands.
73+
# Translators: The name of a category of NVDA commands.
74+
SCRCAT_VISION = _("Vision")
7175
#: Script category for tools commands.
7276
# Translators: The name of a category of NVDA commands.
7377
SCRCAT_TOOLS = pgettext('script category', 'Tools')
@@ -2278,6 +2282,49 @@ def script_recognizeWithUwpOcr(self, gesture):
22782282
# Translators: Describes a command.
22792283
script_recognizeWithUwpOcr.__doc__ = _("Recognizes the content of the current navigator object with Windows 10 OCR")
22802284

2285+
@script(
2286+
# Translators: Describes a command.
2287+
description=_(
2288+
"Toggles the state of the screen curtain, "
2289+
"either hiding or SHOWING the contents of the screen. "
2290+
"If pressed to enable once, the screen curtain is enabled until you restart NVDA. "
2291+
"If pressed tree times, it is enabled until you disable it"
2292+
),
2293+
category=SCRCAT_VISION
2294+
)
2295+
def script_toggleScreenCurtain(self, gesture):
2296+
message = None
2297+
try:
2298+
if not winVersion.isFullScreenMagnificationAvailable():
2299+
return
2300+
scriptCount = scriptHandler.getLastScriptRepeatCount()
2301+
screenCurtainName = "screenCurtain"
2302+
if scriptCount == 0 and screenCurtainName in vision.handler.providers:
2303+
vision.handler.terminateProvider(screenCurtainName)
2304+
# Translators: Reported when the screen curtain is disabled.
2305+
message = _("Screen curtain disabled")
2306+
elif scriptCount in (0, 2):
2307+
temporary = scriptCount == 0
2308+
if not vision.handler.initializeProvider(
2309+
screenCurtainName,
2310+
temporary=temporary,
2311+
):
2312+
# Translators: Reported when the screen curtain could not be enabled.
2313+
message = _("Could not enable screen curtain")
2314+
return
2315+
else:
2316+
if temporary:
2317+
# Translators: Reported when the screen curtain is temporarily enabled.
2318+
message = _("Screen curtain enabled until next restart")
2319+
else:
2320+
# Translators: Reported when the screen curtain is enabled.
2321+
message = _("Screen curtain enabled")
2322+
finally:
2323+
if message is None:
2324+
# Translators: Reported when the screen curtain is not available.
2325+
message = _("Screen curtain not available")
2326+
ui.message(message, speechPriority=speech.priorities.SPRI_NOW)
2327+
22812328
__gestures = {
22822329
# Basic
22832330
"kb:NVDA+n": "showGui",

source/ui.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import gui
1919
import speech
2020
import braille
21+
from typing import Optional
22+
2123

2224
# From urlmon.h
2325
URL_MK_UNIFORM = 1
@@ -64,21 +66,23 @@ def browseableMessage(message,title=None,isHtml=False):
6466
)
6567
gui.mainFrame.postPopup()
6668

67-
def message(text):
69+
def message(text: str, speechPriority: Optional[int] = None):
6870
"""Present a message to the user.
6971
The message will be presented in both speech and braille.
7072
@param text: The text of the message.
71-
@type text: str
73+
@param speechPriority: The speech priority.
74+
One of the C{speech.priorities.SPRI_*} constants.
7275
"""
73-
speech.speakMessage(text)
76+
speech.speakMessage(text, priority=speechPriority)
7477
braille.handler.message(text)
7578

76-
def reviewMessage(text):
79+
def reviewMessage(text: str, speechPriority: Optional[int] = None):
7780
"""Present a message from review or object navigation to the user.
7881
The message will always be presented in speech, and also in braille if it is tethered to review or when auto tethering is on.
7982
@param text: The text of the message.
80-
@type text: str
83+
@param speechPriority: The speech priority.
84+
One of the C{speech.priorities.SPRI_*} constants.
8185
"""
82-
speech.speakMessage(text)
86+
speech.speakMessage(text, priority=speechPriority)
8387
if braille.handler.shouldAutoTether or braille.handler.getTether() == braille.handler.TETHER_REVIEW:
8488
braille.handler.message(text)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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) 2018-2019 NV Access Limited, Babbage B.V., Leonard de Ruijter
5+
6+
"""Screen curtain implementation based on the windows magnification API.
7+
This implementation only works on Windows 8 and above.
8+
"""
9+
10+
import vision
11+
import winVersion
12+
from ctypes import Structure, windll, c_float, POINTER, WINFUNCTYPE, WinError
13+
from ctypes.wintypes import BOOL
14+
15+
16+
class MAGCOLOREFFECT(Structure):
17+
_fields_ = (("transform", c_float * 5 * 5),)
18+
19+
20+
TRANSFORM_BLACK = MAGCOLOREFFECT()
21+
TRANSFORM_BLACK.transform[4][4] = 1.0
22+
23+
24+
def _errCheck(result, func, args):
25+
if result == 0:
26+
raise WinError()
27+
return args
28+
29+
30+
class Magnification:
31+
"""Singleton that wraps necessary functions from the Windows magnification API."""
32+
33+
_magnification = windll.Magnification
34+
35+
_MagInitializeFuncType = WINFUNCTYPE(BOOL)
36+
_MagUninitializeFuncType = WINFUNCTYPE(BOOL)
37+
_MagSetFullscreenColorEffectFuncType = WINFUNCTYPE(BOOL, POINTER(MAGCOLOREFFECT))
38+
_MagSetFullscreenColorEffectArgTypes = ((1, "effect"),)
39+
_MagGetFullscreenColorEffectFuncType = WINFUNCTYPE(BOOL, POINTER(MAGCOLOREFFECT))
40+
_MagGetFullscreenColorEffectArgTypes = ((2, "effect"),)
41+
42+
MagInitialize = _MagInitializeFuncType(("MagInitialize", _magnification))
43+
MagInitialize.errcheck = _errCheck
44+
MagUninitialize = _MagUninitializeFuncType(("MagUninitialize", _magnification))
45+
MagUninitialize.errcheck = _errCheck
46+
try:
47+
MagSetFullscreenColorEffect = _MagSetFullscreenColorEffectFuncType(
48+
("MagSetFullscreenColorEffect", _magnification),
49+
_MagSetFullscreenColorEffectArgTypes
50+
)
51+
MagSetFullscreenColorEffect.errcheck = _errCheck
52+
MagGetFullscreenColorEffect = _MagGetFullscreenColorEffectFuncType(
53+
("MagGetFullscreenColorEffect", _magnification),
54+
_MagGetFullscreenColorEffectArgTypes
55+
)
56+
MagGetFullscreenColorEffect.errcheck = _errCheck
57+
except AttributeError:
58+
MagSetFullscreenColorEffect = None
59+
MagGetFullscreenColorEffect = None
60+
61+
62+
class VisionEnhancementProvider(vision.providerBase.VisionEnhancementProvider):
63+
name = "screenCurtain"
64+
# Translators: Description of a vision enhancement provider that disables output to the screen,
65+
# making it black.
66+
description = _("Screen Curtain")
67+
supportedRoles = frozenset([vision.constants.Role.COLORENHANCER])
68+
69+
@classmethod
70+
def canStart(cls):
71+
return winVersion.isFullScreenMagnificationAvailable()
72+
73+
def __init__(self):
74+
super(VisionEnhancementProvider, self).__init__()
75+
Magnification.MagInitialize()
76+
Magnification.MagSetFullscreenColorEffect(TRANSFORM_BLACK)
77+
78+
def terminate(self):
79+
super(VisionEnhancementProvider, self).terminate()
80+
Magnification.MagUninitialize()
81+
82+
def registerEventExtensionPoints(self, extensionPoints):
83+
# The screen curtain isn't interested in any events
84+
pass

source/winVersion.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,7 @@ def isWin10(version=1507, atLeast=True):
5656
except KeyError:
5757
log.error("Unknown Windows 10 version {}".format(version))
5858
return False
59+
60+
61+
def isFullScreenMagnificationAvailable():
62+
return (winVersion.major, winVersion.minor) >= (6, 2)

0 commit comments

Comments
 (0)