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

Check Z-Order for making content accessible on the lock screen. #14416

Merged
merged 28 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6039b53
Check Z-Order for making content accessible on the lock screen.
seanbudd Nov 10, 2022
b78dc61
add unit tests for lockscreen order tracking
seanbudd Dec 5, 2022
25e5b4c
improve temp documentation
seanbudd Dec 5, 2022
cb47620
improve test docs
seanbudd Dec 6, 2022
cea66f3
improve docs and testing
seanbudd Dec 6, 2022
3b31a3c
improve tests
seanbudd Dec 7, 2022
da56e3f
fix up typing
seanbudd Dec 7, 2022
49e6de0
use bi-directional search
seanbudd Dec 8, 2022
f35fe01
add missing tests
seanbudd Dec 8, 2022
7070762
remove redundant initialization changes
seanbudd Dec 8, 2022
f8fb374
make approach more robust
seanbudd Dec 8, 2022
452babc
walk backwards algorithm
seanbudd Dec 9, 2022
a69a4a2
fix up algorithm and tests
seanbudd Dec 12, 2022
962c26f
fix up docstring
seanbudd Dec 12, 2022
7e99d81
address review comments
seanbudd Dec 13, 2022
828ccea
minor further fixups
seanbudd Dec 13, 2022
544fa7a
fix up todos
seanbudd Dec 14, 2022
97d86ad
search for lock app appmodule in core pump
seanbudd Dec 15, 2022
c4b3f8e
refer as below instead of above
seanbudd Dec 15, 2022
dfa30dd
fixup comment from review suggestions
seanbudd Dec 15, 2022
0a73109
fix up suggestion
seanbudd Dec 15, 2022
a979250
fixup incorrect comment
seanbudd Dec 15, 2022
a951ebe
Merge branch 'try-new-lock-tracking' of https://github.com/nvaccess/n…
seanbudd Dec 15, 2022
49cc36a
fix up comment
seanbudd Dec 15, 2022
0b305bc
fix unit tests
seanbudd Dec 15, 2022
363a1a2
Use lock screen window class names instead of checking for lockapp pr…
seanbudd Dec 15, 2022
d9565d6
log as exception
seanbudd Dec 16, 2022
cc14d58
update changes
seanbudd Dec 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
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:
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
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"]
seanbudd marked this conversation as resolved.
Show resolved Hide resolved


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
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
):
"""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
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
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