Skip to content

Commit

Permalink
Check Z-Order for making content accessible on the lock screen.
Browse files Browse the repository at this point in the history
  • Loading branch information
seanbudd committed Dec 2, 2022
1 parent 9823556 commit 6039b53
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 294 deletions.
5 changes: 4 additions & 1 deletion source/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,10 @@ def setReviewPosition(
@param isCaret: Whether the review position is changed due to caret following.
@param isMouse: Whether the review position is changed due to mouse following.
"""
if _isSecureObjectWhileLockScreenActivated(reviewPosition.obj):
reviewObj = reviewPosition.obj
if isinstance(reviewObj, treeInterceptorHandler.TreeInterceptor):
reviewObj = reviewObj.rootNVDAObject
if _isSecureObjectWhileLockScreenActivated(reviewObj):
return False
globalVars.reviewPosition=reviewPosition.copy()
globalVars.reviewPositionObj=reviewPosition.obj
Expand Down
30 changes: 26 additions & 4 deletions source/baseObject.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2007-2020 NV Access Limited, Christopher Toth, Babbage B.V., Julien Cochuyt
# Copyright (C) 2007-2022 NV Access Limited, Christopher Toth, Babbage B.V., Julien Cochuyt
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

"""Contains the base classes that many of NVDA's classes such as NVDAObjects, virtualBuffers, appModules, synthDrivers inherit from. These base classes provide such things as auto properties, and methods and properties for scripting and key binding.
"""

from typing import (
Any,
Callable,
Optional,
Set,
Union,
)
import weakref
import garbageHandler
from logHandler import log
from abc import ABCMeta, abstractproperty

GetterReturnT = Any
GetterMethodT = Callable[["AutoPropertyObject"], GetterReturnT]


class Getter(object):

def __init__(self,fget, abstract=False):
self.fget=fget
if abstract:
self._abstract = self.__isabstractmethod__ = abstract

def __get__(self,instance,owner):
def __get__(
self,
instance: Union[Any, None, "AutoPropertyObject"],
owner,
) -> Union[GetterReturnT, "Getter"]:
if isinstance(self.fget, classmethod):
return self.fget.__get__(instance, owner)()
elif instance is None:
Expand All @@ -31,16 +46,22 @@ def setter(self,func):
def deleter(self,func):
return (abstractproperty if self._abstract else property)(fget=self.fget,fdel=func)


class CachingGetter(Getter):

def __get__(self, instance, owner):
def __get__(
self,
instance: Union[Any, None, "AutoPropertyObject"],
owner,
) -> Union[GetterReturnT, "CachingGetter"]:
if isinstance(self.fget, classmethod):
log.warning("Class properties do not support caching")
return self.fget.__get__(instance, owner)()
elif instance is None:
return self
return instance._getPropertyViaCache(self.fget)


class AutoPropertyType(ABCMeta):

def __init__(self,name,bases,dict):
Expand Down Expand Up @@ -125,6 +146,7 @@ class AutoPropertyObject(garbageHandler.TrackedObject, metaclass=AutoPropertyTyp
#: @type: bool
cachePropertiesByDefault = False

_propertyCache: Set[GetterMethodT]

def __new__(cls, *args, **kwargs):
self = super(AutoPropertyObject, cls).__new__(cls)
Expand All @@ -134,7 +156,7 @@ def __new__(cls, *args, **kwargs):
self.__instances[self]=None
return self

def _getPropertyViaCache(self,getterMethod=None):
def _getPropertyViaCache(self, getterMethod: Optional[GetterMethodT] = None) -> GetterReturnT:
if not getterMethod:
raise ValueError("getterMethod is None")
missing=False
Expand Down
22 changes: 5 additions & 17 deletions source/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,6 @@ def onEndSession(evt):
wx.CallAfter(audioDucking.initialize)

from winAPI.messageWindow import WindowMessage
from winAPI import sessionTracking
import winUser
# #3763: In wxPython 3, the class name of frame windows changed from wxWindowClassNR to wxWindowNR.
# NVDA uses the main frame to check for and quit another instance of NVDA.
Expand Down Expand Up @@ -630,27 +629,12 @@ def __init__(self, windowName=None):
self.orientationCoordsCache = (0,0)
self.handlePowerStatusChange()

# Call must be paired with a call to sessionTracking.unregister
if not sessionTracking.register(self.handle):
import utils.security
wx.CallAfter(utils.security.warnSessionLockStateUnknown)

def destroy(self):
"""
NVDA must unregister session tracking before destroying the message window.
"""
# Requires an active message window and a handle to unregister.
sessionTracking.unregister(self.handle)
super().destroy()

def windowProc(self, hwnd, msg, wParam, lParam):
post_windowMessageReceipt.notify(msg=msg, wParam=wParam, lParam=lParam)
if msg == WindowMessage.POWER_BROADCAST and wParam == self.PBT_APMPOWERSTATUSCHANGE:
self.handlePowerStatusChange()
elif msg == winUser.WM_DISPLAYCHANGE:
self.handleScreenOrientationChange(lParam)
elif msg == WindowMessage.WTS_SESSION_CHANGE:
sessionTracking.handleSessionChange(sessionTracking.WindowsTrackedSession(wParam), lParam)

def handleScreenOrientationChange(self, lParam):
# TODO: move to winAPI
Expand Down Expand Up @@ -809,7 +793,8 @@ def run(self):
mouseHandler.pumpAll()
braille.pumpAll()
vision.pumpAll()
except:
sessionTracking.pumpAll()
except Exception:
log.exception("errors in this core pump cycle")
baseObject.AutoPropertyObject.invalidateCaches()
watchdog.asleep()
Expand All @@ -832,6 +817,9 @@ def run(self):
log.debug("initializing updateCheck")
updateCheck.initialize()

from winAPI import sessionTracking
sessionTracking.initialize()

_TrackNVDAInitialization.markInitializationComplete()

log.info("NVDA initialized")
Expand Down
3 changes: 2 additions & 1 deletion source/globalVars.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

if typing.TYPE_CHECKING:
import NVDAObjects # noqa: F401 used for type checking only
import textInfos # noqa: F401 used for type checking only


class DefaultAppArgs(argparse.Namespace):
Expand Down Expand Up @@ -70,7 +71,7 @@ class DefaultAppArgs(argparse.Namespace):
mouseOldY=None
navigatorObject: typing.Optional['NVDAObjects.NVDAObject'] = None
reviewPosition=None
reviewPositionObj=None
reviewPositionObj: typing.Optional["textInfos.TextInfoObjT"] = None
lastProgressValue=0
appArgs = DefaultAppArgs()
unknownAppArgs: typing.List[str] = []
Expand Down
12 changes: 9 additions & 3 deletions source/nvda.pyw
Original file line number Diff line number Diff line change
Expand Up @@ -350,13 +350,19 @@ if mutex is None:
sys.exit(1)


if _isSecureDesktop():
def _serviceDebugEnabled() -> bool:
import winreg
try:
k = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\NVDA")
if not winreg.QueryValueEx(k, u"serviceDebug")[0]:
globalVars.appArgs.secure = True
if winreg.QueryValueEx(k, "serviceDebug")[0]:
return True
except WindowsError:
pass
return False


if _isSecureDesktop():
if not _serviceDebugEnabled():
globalVars.appArgs.secure = True
globalVars.appArgs.changeScreenReaderFlag = False
globalVars.appArgs.minimal = True
Expand Down
16 changes: 12 additions & 4 deletions source/textInfos/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 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) 2006-2021 NV Access Limited, Babbage B.V., Accessolutions, Julien Cochuyt
# Copyright (C) 2006-2022 NV Access Limited, Babbage B.V., Accessolutions, Julien Cochuyt

"""Framework for accessing text content in widgets.
The core component of this framework is the L{TextInfo} class.
Expand Down Expand Up @@ -32,6 +32,7 @@

if typing.TYPE_CHECKING:
import NVDAObjects
import treeInterceptorHandler # noqa: F401 used for type checking only

SpeechSequence = List[Union[Any, str]]

Expand Down Expand Up @@ -287,6 +288,9 @@ def _logBadSequenceTypes(sequence: SpeechSequence, shouldRaise: bool = True):
return speech.types.logBadSequenceTypes(sequence, raiseExceptionOnError=shouldRaise)


TextInfoObjT = Union["NVDAObjects.NVDAObject", "treeInterceptorHandler.TreeInterceptor"]


class TextInfo(baseObject.AutoPropertyObject):
"""Provides information about a range of text in an object and facilitates access to all text in the widget.
A TextInfo represents a specific range of text, providing access to the text itself, as well as information about the text such as its formatting and any associated controls.
Expand All @@ -307,7 +311,11 @@ class TextInfo(baseObject.AutoPropertyObject):
@type bookmark: L{Bookmark}
"""

def __init__(self,obj,position):
def __init__(
self,
obj: TextInfoObjT,
position
):
"""Constructor.
Subclasses must extend this, calling the superclass method first.
@param position: The initial position of this range; one of the POSITION_* constants or a position object supported by the implementation.
Expand Down Expand Up @@ -338,9 +346,9 @@ def _set_end(self, otherEndpoint: "TextInfoEndpoint"):
self.end.moveTo(otherEndpoint)

#: Typing information for auto-property: _get_obj
obj: "NVDAObjects.NVDAObject"
obj: TextInfoObjT

def _get_obj(self) -> "NVDAObjects.NVDAObject":
def _get_obj(self) -> TextInfoObjT:
"""The object containing the range of text being represented."""
return self._obj()

Expand Down
18 changes: 12 additions & 6 deletions source/treeInterceptorHandler.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# treeInterceptorHandler.py
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2020 NV Access Limited, Davy Kager, Accessolutions, Julien Cochuyt
# Copyright (C) 2006-2022 NV Access Limited, Davy Kager, Accessolutions, Julien Cochuyt
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

from typing import Optional, Dict
from typing import (
TYPE_CHECKING,
Dict,
Optional,
)

from logHandler import log
import baseObject
Expand All @@ -18,6 +21,10 @@
from speech.types import SpeechSequence
from controlTypes import OutputReason

if TYPE_CHECKING:
import NVDAObjects


runningTable=set()

def getTreeInterceptor(obj):
Expand Down Expand Up @@ -84,12 +91,11 @@ class TreeInterceptor(baseObject.ScriptableObject):

shouldTrapNonCommandGestures=False #: If true then gestures that do not have a script and are not a command gesture should be trapped from going through to Windows.

def __init__(self, rootNVDAObject):
def __init__(self, rootNVDAObject: "NVDAObjects.NVDAObject"):
super(TreeInterceptor, self).__init__()
self._passThrough = False
#: The root object of the tree wherein events and scripts are intercepted.
#: @type: L{NVDAObjects.NVDAObject}
self.rootNVDAObject = rootNVDAObject
self.rootNVDAObject: "NVDAObjects.NVDAObject" = rootNVDAObject

def terminate(self):
"""Terminate this interceptor.
Expand Down
45 changes: 38 additions & 7 deletions source/utils/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@
import winUser

if typing.TYPE_CHECKING:
import appModuleHandler
import appModuleHandler # noqa: F401, use for typing
import scriptHandler # noqa: F401, use for typing
import NVDAObjects
import NVDAObjects # noqa: F401, use for typing


postSessionLockStateChanged = extensionPoints.Action()
"""
# TODO: maintain backwards compat
Notifies when a session lock or unlock event occurs.
Usage:
Expand Down Expand Up @@ -141,7 +142,13 @@ def _isSecureObjectWhileLockScreenActivated(
As such, NVDA must prevent accessing and reading objects outside of the lockscreen when Windows is locked.
@return: C{True} if the Windows 10/11 lockscreen is active and C{obj} is outside of the lock screen.
"""
if isWindowsLocked() and not isObjectAboveLockScreen(obj):
try:
isObjectInSecure = isWindowsLocked() and not isObjectAboveLockScreen(obj)
except Exception:
log.exception()
return False

if isObjectInSecure:
if shouldLog and log.isEnabledFor(log.DEBUG):
devInfo = '\n'.join(obj.devInfo)
log.debug(f"Attempt at navigating to a secure object: {devInfo}")
Expand All @@ -155,7 +162,6 @@ def isObjectAboveLockScreen(obj: "NVDAObjects.NVDAObject") -> bool:
When Windows is locked, the foreground Window is usually LockApp,
but other Windows can be focused (e.g. Windows Magnifier).
"""
import appModuleHandler
from IAccessibleHandler import SecureDesktopNVDAObject
from NVDAObjects.IAccessible import TaskListIcon

Expand All @@ -176,19 +182,44 @@ def isObjectAboveLockScreen(obj: "NVDAObjects.NVDAObject") -> bool:
or isinstance(obj, SecureDesktopNVDAObject)
):
return True
return _isObjectAboveLockScreenCheckZOrder(obj)


def _isObjectAboveLockScreenCheckZOrder(obj: "NVDAObjects.NVDAObject") -> bool:
"""
This is a risky hack.
If the order is incorrectly detected,
the Windows UX may become inaccessible
or secure information may become accessible.
If these functions fail, where possible,
NVDA should make NVDA objects accessible.
"""
import appModuleHandler
from NVDAObjects.window import Window
if not isinstance(obj, Window):
# must be a window to get its HWNDVal
return True
runningAppModules = appModuleHandler.runningTable.values()
lockAppModule = next(filter(_isLockAppAndAlive, runningAppModules), None)

if lockAppModule is None:
# lockAppModule not running/registered by NVDA yet
log.debugWarning(
log.debug(
"lockAppModule not detected when Windows is locked. "
"Cannot detect if object is in lock app, considering object as insecure. "
"Cannot detect if object is in lock app, considering object as safe. "
)
elif lockAppModule is not None and obj.processID == lockAppModule.processID:
return True

desktopWindow = winUser.getDesktopWindow()
nextWindow = winUser.getTopWindow(desktopWindow)
while nextWindow:
windowProcessId = winUser.getWindowThreadProcessID(nextWindow)
if nextWindow == obj.windowHandle:
return True
elif windowProcessId == lockAppModule.processID:
return False
nextWindow = winUser.getWindow(nextWindow, winUser.GW_HWNDNEXT)
return False


Expand Down
2 changes: 0 additions & 2 deletions source/winAPI/messageWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,4 @@ class WindowMessage(enum.IntEnum):
"""
WM_WTSSESSION_CHANGE
Windows Message for when a Session State Changes.
Receiving these messages is registered by sessionTracking.register.
handleSessionChange handles these messages.
"""
Loading

0 comments on commit 6039b53

Please sign in to comment.