logonui.py 4 KB
#appModules/logonui.py
#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) 2009-2016 NV Access Limited, Joseph Lee

import speech
import api
import braille
import controlTypes
from NVDAObjects.IAccessible import IAccessible
from NVDAObjects.behaviors import Dialog
import appModuleHandler
import eventHandler
import UIAHandler
if UIAHandler.isUIAAvailable:
	from NVDAObjects.UIA import UIA

"""App module for the Windows Logon screen
"""

class LogonDialog(Dialog):

	role = controlTypes.ROLE_DIALOG
	isPresentableFocusAncestor = True

	def event_gainFocus(self):
		child = self.firstChild
		if child and controlTypes.STATE_FOCUSED in child.states and not eventHandler.isPendingEvents("gainFocus"):
			# UIA reports that focus is on the top level pane, even when it's actually on the frame below.
			# This causes us to incorrectly use UIA for the top level pane, which causes this pane to be spoken again when the focus moves.
			# Therefore, bounce the focus to the correct object.
			eventHandler.queueEvent("gainFocus", child)
			return

		return super(LogonDialog, self).event_gainFocus()

if UIAHandler.isUIAAvailable:
	class Win8PasswordField(UIA):

		#This UIA object has no invoke pattern, at least set focus.
		# #6024: Affects both Windows 8.x and 10.
		def doAction(self,index=None):
			if not index:
				self.setFocus()
			else:
				super(Win8PasswordField,self).doAction(index)

class XPPasswordField(IAccessible):

	def initOverlayClass(self):
		for gesture in ("kb:upArrow", "kb:downArrow"):
			self.bindGesture(gesture, "changeUser")

	def _get_name(self):
		# Focus automatically jumps to the password field when a user is selected. This field has no name.
		# This means that the new selected user is not reported.
		# However, the selected user name is the name of the window object for this field.
		try:
			self.parent.invalidateCache()
			return self.parent.name
		except:
			return super(XPPasswordField, self).name

	def script_changeUser(self, gesture):
		# The up and down arrow keys change the selected user, but there's no reliable NVDA event for detecting this.
		oldName = self.name
		gesture.send()
		self.invalidateCache()
		if oldName == self.name or controlTypes.STATE_FOCUSED not in self.states:
			return
		self.event_gainFocus()

class AppModule(appModuleHandler.AppModule):

	def event_NVDAObject_init(self, obj):
		if obj.windowClassName == "NativeHWNDHost" and obj.parent and not obj.parent.parent:
			# This is the top level pane of the XP logon screen.
			# Make sure it is always presented.
			obj.isPresentableFocusAncestor = True

	def chooseNVDAObjectOverlayClasses(self, obj, clsList):
		windowClass = obj.windowClassName

		if UIAHandler.isUIAAvailable:
			if isinstance(obj,UIA) and obj.UIAElement.cachedClassName in ("TouchEditInner", "PasswordBox") and obj.role==controlTypes.ROLE_EDITABLETEXT:
				clsList.insert(0,Win8PasswordField)
		# #6010: Allow Windows 10 version to be recognized as well.
		if ((windowClass == "AUTHUI.DLL: LogonUI Logon Window" and obj.parent and obj.parent.parent and not obj.parent.parent.parent)
		or (windowClass == "LogonUI Logon Window" and obj.UIAIsWindowElement)):
			clsList.insert(0, LogonDialog)
			return

		if windowClass == "Edit" and not obj.name:
			parent = obj.parent
			if parent and parent.role == controlTypes.ROLE_WINDOW:
				clsList.insert(0, XPPasswordField)
				return

	def event_gainFocus(self,obj,nextHandler):
		# #6010: Windows 10 version uses a different window class name.
		if obj.windowClassName in ("DirectUIHWND", "LogonUI Logon Window") and obj.role==controlTypes.ROLE_BUTTON and not obj.next:
			prev=obj.previous
			if prev and prev.role in (controlTypes.ROLE_STATICTEXT, controlTypes.ROLE_PANE):
				# This is for a popup message in the logon dialog.
				# Present the dialog again so the message will be reported.
				speech.speakObjectProperties(api.getForegroundObject(),name=True,role=True,description=True)
				braille.invalidateCachedFocusAncestors(1)
		nextHandler()