Repository: nvaccess/nvda

Issue:
Allow appModules to prefer UIA for certain windows
It is currently possible to implement isBadUIAWindow on the appModule level to use IAccessible for windows that are reported as native UIA. I also propose the opposite of this feature, i.e. an isGoodUIAWindow function that takes a window handle as a parameter and returns True if UIA should be used in all cases. I think this feature can be justified as follows:

1. It seems there are windows that do not report as native UIA even though they are, for example the window class of the Windows Defender Application Guard.
2. It makes it much easier to inspect within NVDA how an application behaves is UIA is used for a particular control.

Other arguments welcome of course. CC @dkager @michaeldcurran @josephsl


Discussion:

Resolving patch:
diff --git a/source/_UIAHandler.py b/source/_UIAHandler.py
--- a/source/_UIAHandler.py
+++ b/source/_UIAHandler.py
@@ -1,6 +1,6 @@
 #_UIAHandler.py
 #A part of NonVisual Desktop Access (NVDA)
-#Copyright (C) 2011-2017 NV Access Limited, Joseph Lee
+#Copyright (C) 2011-2018 NV Access Limited, Joseph Lee, Babbage B.V.
 #This file is covered by the GNU General Public License.
 #See the file COPYING for more details.
 
@@ -51,6 +51,11 @@
 # The name of the WDAG (Windows Defender Application Guard) process
 WDAG_PROCESS_NAME=u'hvsirdpclient'
 
+goodUIAWindowClassNames=[
+	# A WDAG (Windows Defender Application Guard) Window is always native UIA, even if it doesn't report as such.
+	'RAIL_WINDOW',
+]
+
 badUIAWindowClassNames=[
 	"SysTreeView32",
 	"WuDuiListView",
@@ -295,8 +300,13 @@ def _isUIAWindowHelper(self,hwnd):
 			return False
 		import NVDAObjects.window
 		windowClass=NVDAObjects.window.Window.normalizeWindowClassName(winUser.getClassName(hwnd))
-		# A WDAG (Windows Defender Application Guard) Window is always native UIA, even if it doesn't report as such.
-		if windowClass=='RAIL_WINDOW':
+		# For certain window classes, we always want to use UIA.
+		if windowClass in goodUIAWindowClassNames:
+			return True
+		# allow the appModule for the window to also choose if this window is good
+		# An appModule should be able to override bad UIA class names as prescribed by core
+		appModule=appModuleHandler.getAppModuleFromProcessID(processID)
+		if appModule and appModule.isGoodUIAWindow(hwnd):
 			return True
 		# There are certain window classes that just had bad UIA implementations
 		if windowClass in badUIAWindowClassNames:
@@ -308,7 +318,6 @@ def _isUIAWindowHelper(self,hwnd):
 			if winUser.getClassName(parentHwnd) in {"Net UI Tool Window","NUIDialog"}:
 				return False
 		# allow the appModule for the window to also choose if this window is bad
-		appModule=appModuleHandler.getAppModuleFromProcessID(processID)
 		if appModule and appModule.isBadUIAWindow(hwnd):
 			return False
 		# Ask the window if it supports UIA natively
diff --git a/source/appModuleHandler.py b/source/appModuleHandler.py
--- a/source/appModuleHandler.py
+++ b/source/appModuleHandler.py
@@ -1,7 +1,7 @@
 # -*- coding: UTF-8 -*-
 #appModuleHandler.py
 #A part of NonVisual Desktop Access (NVDA)
-#Copyright (C) 2006-2017 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Patrick Zajda, Joseph Lee
+#Copyright (C) 2006-2018 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Patrick Zajda, Joseph Lee, Babbage B.V.
 #This file is covered by the GNU General Public License.
 #See the file COPYING for more details.
 
@@ -434,9 +434,21 @@ def _get_is64BitProcess(self):
 		self.is64BitProcess = not res
 		return self.is64BitProcess
 
+	def isGoodUIAWindow(self,hwnd):
+		"""
+		returns C{True} if the UIA implementation of the given window must be used, regardless whether native or not.
+		This function is the counterpart of and takes precedence over L{isBadUIAWindow}.
+		If both functions return C{False}, the decision of whether to use UIA for the window is left to core.
+		Warning: this may be called outside of NVDA's main thread, therefore do not try accessing NVDAObjects and such, rather just check window  class names.
+		"""
+		return False
+
 	def isBadUIAWindow(self,hwnd):
 		"""
-		returns true if the UIA implementation of the given window must be ignored due to it being broken in some way.
+		returns C{True} if the UIA implementation of the given window must be ignored due to it being broken in some way.
+		This function is the counterpart of L{isGoodUIAWindow}.
+		When both functions return C{True}, L{isGoodUIAWindow} takes precedence.
+		If both functions return C{False}, the decision of whether to use UIA for the window is left to core.
 		Warning: this may be called outside of NVDA's main thread, therefore do not try accessing NVDAObjects and such, rather just check window  class names.
 		"""
 		return False


Test patch: