Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vision framework: add a screen curtain #10090

Merged
merged 10 commits into from
Sep 6, 2019
43 changes: 43 additions & 0 deletions source/globalCommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import core
import winVersion
from base64 import b16encode
import vision

#: Script category for text review commands.
# Translators: The name of a category of NVDA commands.
Expand All @@ -68,6 +69,9 @@
#: Script category for Braille commands.
# Translators: The name of a category of NVDA commands.
SCRCAT_BRAILLE = _("Braille")
#: Script category for Vision commands.
# Translators: The name of a category of NVDA commands.
SCRCAT_VISION = _("Vision")
#: Script category for tools commands.
# Translators: The name of a category of NVDA commands.
SCRCAT_TOOLS = pgettext('script category', 'Tools')
Expand Down Expand Up @@ -2278,6 +2282,45 @@ def script_recognizeWithUwpOcr(self, gesture):
# Translators: Describes a command.
script_recognizeWithUwpOcr.__doc__ = _("Recognizes the content of the current navigator object with Windows 10 OCR")

@script(
# Translators: Describes a command.
description=_(
"Toggles the state of the screen curtain, "
"either hiding or SHOWING the contents of the screen. "
"If pressed to enable once, the screen curtain is enabled until you restart NVDA. "
"If pressed tree times, it is enabled until you disable it"
),
gesture="kb:NVDA+control+/",
category=SCRCAT_VISION
)
def script_toggleScreenCurtain(self, gesture):
if not winVersion.isFullScreenMagnificationAvailable():
# Translators: Reported when the screen curtain is not available.
ui.message(_("Screen curtain not available"))
return
scriptCount = scriptHandler.getLastScriptRepeatCount()
screenCurtainName = "screenCurtain"
if scriptCount == 0 and screenCurtainName in vision.handler.providers:
vision.handler.terminateProvider(screenCurtainName)
# Translators: Reported when the screen curtain is disabled.
ui.message(_("Screen curtain disabled"))
elif scriptCount in (0, 2):
temporary = scriptCount == 0
if not vision.handler.initializeProvider(
screenCurtainName,
temporary=temporary,
):
# Translators: Reported when the screen curtain could not be enabled.
ui.message(_("Could not enable screen curtain"))
return
else:
if temporary:
# Translators: Reported when the screen curtain is temporarily enabled.
ui.message(_("Screen curtain enabled until next restart"))
else:
# Translators: Reported when the screen curtain is enabled.
ui.message(_("Screen curtain enabled"))

__gestures = {
# Basic
"kb:NVDA+n": "showGui",
Expand Down
84 changes: 84 additions & 0 deletions source/visionEnhancementProviders/screenCurtain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2018-2019 NV Access Limited, Babbage B.V., Leonard de Ruijter

"""Screen curtain implementation based on the windows magnification API.
This implementation only works on Windows 8 and above.
"""

import vision
import winVersion
from ctypes import Structure, windll, c_float, POINTER, WINFUNCTYPE, WinError
from ctypes.wintypes import BOOL


class MAGCOLOREFFECT(Structure):
_fields_ = (("transform", c_float * 5 * 5),)


TRANSFORM_BLACK = MAGCOLOREFFECT()
TRANSFORM_BLACK.transform[4][4] = 1.0


def _errCheck(result, func, args):
if result == 0:
raise WinError()
return args


class Magnification:
"""Singleton that wraps necessary functions from the Windows magnification API."""
feerrenrut marked this conversation as resolved.
Show resolved Hide resolved

_magnification = windll.Magnification

_MagInitializeFuncType = WINFUNCTYPE(BOOL)
_MagUninitializeFuncType = WINFUNCTYPE(BOOL)
_MagSetFullscreenColorEffectFuncType = WINFUNCTYPE(BOOL, POINTER(MAGCOLOREFFECT))
_MagSetFullscreenColorEffectArgTypes = ((1, "effect"),)
_MagGetFullscreenColorEffectFuncType = WINFUNCTYPE(BOOL, POINTER(MAGCOLOREFFECT))
_MagGetFullscreenColorEffectArgTypes = ((2, "effect"),)

MagInitialize = _MagInitializeFuncType(("MagInitialize", _magnification))
MagInitialize.errcheck = _errCheck
MagUninitialize = _MagUninitializeFuncType(("MagUninitialize", _magnification))
MagUninitialize.errcheck = _errCheck
try:
MagSetFullscreenColorEffect = _MagSetFullscreenColorEffectFuncType(
("MagSetFullscreenColorEffect", _magnification),
_MagSetFullscreenColorEffectArgTypes
)
MagSetFullscreenColorEffect.errcheck = _errCheck
MagGetFullscreenColorEffect = _MagGetFullscreenColorEffectFuncType(
("MagGetFullscreenColorEffect", _magnification),
_MagGetFullscreenColorEffectArgTypes
)
MagGetFullscreenColorEffect.errcheck = _errCheck
except AttributeError:
MagSetFullscreenColorEffect = None
MagGetFullscreenColorEffect = None


class VisionEnhancementProvider(vision.providerBase.VisionEnhancementProvider):
name = "screenCurtain"
# Translators: Description of a vision enhancement provider that disables output to the screen,
# making it blac k.
LeonarddeR marked this conversation as resolved.
Show resolved Hide resolved
description = _("Screen Curtain")
supportedRoles = frozenset([vision.constants.Role.COLORENHANCER])

@classmethod
def canStart(cls):
return winVersion.isFullScreenMagnificationAvailable()

def __init__(self):
super(VisionEnhancementProvider, self).__init__()
Magnification.MagInitialize()
Magnification.MagSetFullscreenColorEffect(TRANSFORM_BLACK)

def terminate(self):
super(VisionEnhancementProvider, self).terminate()
Magnification.MagUninitialize()

def registerEventExtensionPoints(self, extensionPoints):
# The screen curtain isn't interested in any events
pass
4 changes: 4 additions & 0 deletions source/winVersion.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ def isWin10(version=1507, atLeast=True):
except KeyError:
log.error("Unknown Windows 10 version {}".format(version))
return False


def isFullScreenMagnificationAvailable():
return (winVersion.major, winVersion.minor) >= (6, 2)