def _get_isCollapsed(self): """Works around a UIA bug on Windows 10 1903 and later.""" if not isWin10(1903): return super(consoleUIATextInfo, self)._get_isCollapsed() # Even when a console textRange's start and end have been moved to the # same position, the console incorrectly reports the end as being # past the start. # Therefore to decide if the textRange is collapsed, # Check if it has no text. return not bool(self._rangeObj.getText(1))
def __init__(self, obj, position, _rangeObj=None): super(consoleUIATextInfo, self).__init__(obj, position, _rangeObj) if position == textInfos.POSITION_CARET and isWin10(1903, atLeast=False): # The UIA implementation in 1903 causes the caret to be # off-by-one, so move it one position to the right # to compensate. self._rangeObj.MoveEndpointByUnit( UIAHandler.TextPatternRangeEndpoint_Start, UIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER], 1)
def collapse(self, end=False): """Works around a UIA bug on Windows 10 1903 and later.""" if not isWin10(1903): return super(consoleUIATextInfo, self).collapse(end=end) # When collapsing, consoles seem to incorrectly push the start of the # textRange back one character. # Correct this by bringing the start back up to where the end is. oldInfo = self.copy() super(consoleUIATextInfo, self).collapse() if not end: self._rangeObj.MoveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_Start, oldInfo._rangeObj, UIAHandler.TextPatternRangeEndpoint_Start)
def chooseNVDAObjectOverlayClasses(self, obj, clsList): if obj.windowClassName == self.TERMINAL_WINDOW_CLASS and isinstance( obj, IAccessible ) and obj.IAccessibleRole == oleacc.ROLE_SYSTEM_CLIENT: try: clsList.remove(DisplayModelEditableText) except ValueError: pass if isWin10(1607): clsList[0:0] = (KeyboardHandlerBasedTypedCharSupport, DisplayModelLiveText) else: clsList[0:0] = (Terminal, DisplayModelLiveText)
def onInstall(): import gui import wx import winVersion requiredVer = "Windows 10 Version 1909" if not winVersion.isWin10(version=1909) and gui.messageBox( # Translators: Dialog text shown when attempting to install the add-on on an unsupported version of Windows (minSupportedVersion is the minimum version required for this add-on). _("You are using an older version of Windows. This add-on requires {minSupportedVersion} or later. Are you sure you wish to install this add-on anyway?" ).format(minSupportedVersion=requiredVer), # Translators: title of the dialog shown when attempting to install the add-on on an old version of Windows. _("Old Windows version"), wx.YES | wx.NO | wx.CANCEL | wx.CENTER | wx.ICON_QUESTION) == wx.NO: raise RuntimeError("Old Windows version detected")
def shouldUseToUnicodeEx(focus=None): "Returns whether to use ToUnicodeEx to determine typed characters." if not focus: focus = api.getFocusObject() from NVDAObjects.behaviors import KeyboardHandlerBasedTypedCharSupport return ( # This is only possible in Windows 10 1607 and above winVersion.isWin10(1607) and ( # Either of # We couldn't inject in-process, and its not a legacy console window without keyboard support. # console windows have their own specific typed character support. (not focus.appModule.helperLocalBindingHandle and focus.windowClassName != 'ConsoleWindowClass') # or the focus is within a UWP app, where WM_CHAR never gets sent or focus.windowClassName.startswith('Windows.UI.Core') # Or this is a console with keyboard support, where WM_CHAR messages are doubled or isinstance(focus, KeyboardHandlerBasedTypedCharSupport)))
def shouldUseUIAConsole(setting=None): """Determines whether to use UIA in the Windows Console. @param setting: the config value to base this check on (if not provided, it is retrieved from config). """ if not setting: setting = config.conf['UIA']['winConsoleImplementation'] if setting == "legacy": return False elif setting == "UIA": return True # #7497: Windows 10 Fall Creators Update has an incomplete UIA # implementation for console windows, therefore for now we should # ignore it. # It does not implement caret/selection, and probably has no # new text events. return isWin10(1803)
def event_nameChange(self, obj, nextHandler): # NVDA Core issue 5641: try catching virtual desktop switch event, which will result in name change for the desktop object. # To be taken care of by NVDA Core, and for older releases, let the add-on handle it for a time. # This may degrade performance and/or cause NVDA to become verbose in situations other than virtual desktop switch, so exercise discretion. if obj.windowClassName == "#32769": if globalVars.appArgs.debugLogging: import tones tones.beep(512, 50) log.debug( f"W10: possible desktop name change from {obj}, app module: {obj.appModule}" ) # CSRSS: Client/Server Runtime Subsystem (Windows subsystem process/desktop object) if obj.appModule.appName == "csrss": import wx # Even with desktop name change handler added, older Windows 10 releases won't support this properly. if (not hasattr(eventHandler, "handlePossibleDesktopNameChange") or (hasattr(eventHandler, "handlePossibleDesktopNameChange") and not winVersion.isWin10(version=1909))): wx.CallLater(500, ui.message, obj.name) self.uiaDebugLogging(obj, "nameChange") nextHandler()
UIAEventIdsToNVDAEventNames={ UIA_LiveRegionChangedEventId:"liveRegionChange", UIA_SelectionItem_ElementSelectedEventId:"UIA_elementSelected", UIA_MenuOpenedEventId:"gainFocus", UIA_SelectionItem_ElementAddedToSelectionEventId:"stateChange", UIA_SelectionItem_ElementRemovedFromSelectionEventId:"stateChange", #UIA_MenuModeEndEventId:"menuModeEnd", UIA_ToolTipOpenedEventId:"UIA_toolTipOpened", #UIA_AsyncContentLoadedEventId:"documentLoadComplete", #UIA_ToolTipClosedEventId:"hide", UIA_Window_WindowOpenedEventId:"UIA_window_windowOpen", UIA_SystemAlertEventId:"UIA_systemAlert", } autoSelectDetectionAvailable = False if winVersion.isWin10(): UIAEventIdsToNVDAEventNames.update({ UIA.UIA_Text_TextChangedEventId: "textChange", UIA.UIA_Text_TextSelectionChangedEventId: "caret", }) autoSelectDetectionAvailable = True ignoreWinEventsMap = { UIA_AutomationPropertyChangedEventId: list(UIAPropertyIdsToNVDAEventNames.keys()), } for id in UIAEventIdsToNVDAEventNames.keys(): ignoreWinEventsMap[id] = [0] class UIAHandler(COMObject): _com_interfaces_=[IUIAutomationEventHandler,IUIAutomationFocusChangedEventHandler,IUIAutomationPropertyChangedEventHandler,IUIAutomationNotificationEventHandler] def __init__(self):
def check(cls): # Only present this as an available synth if this is Windows 10. return winVersion.isWin10()
def isGoodUIAWindow(self, hwnd): # #9204: shell raises window open event for emoji panel in build 18305 and later. if winVersion.isWin10(version=1903) and winUser.getClassName( hwnd) == "ApplicationFrameWindow": return True return False
from NVDAObjects.UIA import UIA, SearchField, Dialog from NVDAObjects.behaviors import EditableTextWithSuggestions import api import config import queueHandler import eventHandler import globalVars import UIAHandler from logHandler import log import winVersion import addonHandler addonHandler.initTranslation() # #52: forget everything if the current release is not a supported version of Windows 10. # NVDA 2019.2 includes a handy Windows 10 version check function. W10AddonSupported = winVersion.isWin10(version=1909) # Extra UIA constants UIA_Drag_DragStartEventId = 20026 UIA_Drag_DragCancelEventId = 20027 UIA_Drag_DragCompleteEventId = 20028 UIA_DropTarget_DragEnterEventId = 20029 UIA_DropTarget_DragLeaveEventId = 20030 UIA_DropTarget_DroppedEventId = 20031 UIA_Text_TextChangedEventId = 20015 # For convenience. W10Events = { UIA_Drag_DragStartEventId: "UIA_dragStart", UIA_Drag_DragCancelEventId: "UIA_dragCancel", UIA_Drag_DragCompleteEventId: "UIA_dragComplete",
def internal_keyDownEvent(vkCode, scanCode, extended, injected): """Event called by winInputHook when it receives a keyDown. """ gestureExecuted = False try: global lastNVDAModifier, lastNVDAModifierReleaseTime, bypassNVDAModifier, passKeyThroughCount, lastPassThroughKeyDown, currentModifiers, keyCounter, stickyNVDAModifier, stickyNVDAModifierLocked # Injected keys should be ignored in some cases. if injected and (ignoreInjected or not config.conf['keyboard']['handleInjectedKeys']): return True keyCode = (vkCode, extended) if passKeyThroughCount >= 0: # We're passing keys through. if lastPassThroughKeyDown != keyCode: # Increment the pass key through count. # We only do this if this isn't a repeat of the previous key down, as we don't receive key ups for repeated key downs. passKeyThroughCount += 1 lastPassThroughKeyDown = keyCode return True keyCounter += 1 stickyKeysFlags = winUser.getSystemStickyKeys().dwFlags if stickyNVDAModifier and not stickyKeysFlags & winUser.SKF_STICKYKEYSON: # Sticky keys has been disabled, # so clear the sticky NVDA modifier. currentModifiers.discard(stickyNVDAModifier) stickyNVDAModifier = None stickyNVDAModifierLocked = False gesture = KeyboardInputGesture(currentModifiers, vkCode, scanCode, extended) if not (stickyKeysFlags & winUser.SKF_STICKYKEYSON) and ( bypassNVDAModifier or (keyCode == lastNVDAModifier and lastNVDAModifierReleaseTime and time.time() - lastNVDAModifierReleaseTime < 0.5)): # The user wants the key to serve its normal function instead of acting as an NVDA modifier key. # There may be key repeats, so ensure we do this until they stop. bypassNVDAModifier = True gesture.isNVDAModifierKey = False lastNVDAModifierReleaseTime = None if gesture.isNVDAModifierKey: lastNVDAModifier = keyCode if stickyKeysFlags & winUser.SKF_STICKYKEYSON: if keyCode == stickyNVDAModifier: if stickyKeysFlags & winUser.SKF_TRISTATE and not stickyNVDAModifierLocked: # The NVDA modifier is being locked. stickyNVDAModifierLocked = True if stickyKeysFlags & winUser.SKF_AUDIBLEFEEDBACK: tones.beep(1984, 60) return False else: # The NVDA modifier is being unlatched/unlocked. stickyNVDAModifier = None stickyNVDAModifierLocked = False if stickyKeysFlags & winUser.SKF_AUDIBLEFEEDBACK: tones.beep(496, 60) return False else: # The NVDA modifier is being latched. if stickyNVDAModifier: # Clear the previous sticky NVDA modifier. currentModifiers.discard(stickyNVDAModifier) stickyNVDAModifierLocked = False stickyNVDAModifier = keyCode if stickyKeysFlags & winUser.SKF_AUDIBLEFEEDBACK: tones.beep(1984, 60) else: # Another key was pressed after the last NVDA modifier key, so it should not be passed through on the next press. lastNVDAModifier = None if gesture.isModifier: if gesture.speechEffectWhenExecuted in ( gesture.SPEECHEFFECT_PAUSE, gesture.SPEECHEFFECT_RESUME ) and keyCode in currentModifiers: # Ignore key repeats for the pause speech key to avoid speech stuttering as it continually pauses and resumes. return True currentModifiers.add(keyCode) elif stickyNVDAModifier and not stickyNVDAModifierLocked: # A non-modifier was pressed, so unlatch the NVDA modifier. currentModifiers.discard(stickyNVDAModifier) stickyNVDAModifier = None try: inputCore.manager.executeGesture(gesture) gestureExecuted = True trappedKeys.add(keyCode) if canModifiersPerformAction(gesture.generalizedModifiers): # #3472: These modifiers can perform an action if pressed alone # and we've just consumed the main key. # Send special reserved vkcode (0xff) to at least notify the app's key state that something happendd. # This allows alt and windows to be bound to scripts and # stops control+shift from switching keyboard layouts in cursorManager selection scripts. KeyboardInputGesture((), 0xff, 0, False).send() return False except inputCore.NoInputGestureAction: if gesture.isNVDAModifierKey: # Never pass the NVDA modifier key to the OS. trappedKeys.add(keyCode) return False except: log.error("internal_keyDownEvent", exc_info=True) finally: # #6017: handle typed characters in Win10 RS2 and above where we can't detect typed characters in-process # This code must be in the 'finally' block as code above returns in several places yet we still want to execute this particular code. focus = api.getFocusObject() from NVDAObjects.behaviors import KeyboardHandlerBasedTypedCharSupport if ( # This is only possible in Windows 10 1607 and above winVersion.isWin10(1607) # And we only want to do this if the gesture did not result in an executed action and not gestureExecuted # and not if this gesture is a modifier key and not isNVDAModifierKey(vkCode, extended) and not vkCode in KeyboardInputGesture.NORMAL_MODIFIER_KEYS and ( # Either of # We couldn't inject in-process, and its not a legacy console window without keyboard support. # console windows have their own specific typed character support. (not focus.appModule.helperLocalBindingHandle and focus.windowClassName != 'ConsoleWindowClass') # or the focus is within a UWP app, where WM_CHAR never gets sent or focus.windowClassName.startswith('Windows.UI.Core') #Or this is a console with keyboard support, where WM_CHAR messages are doubled or isinstance(focus, KeyboardHandlerBasedTypedCharSupport))): keyStates = (ctypes.c_byte * 256)() for k in range(256): keyStates[k] = ctypes.windll.user32.GetKeyState(k) charBuf = ctypes.create_unicode_buffer(5) hkl = ctypes.windll.user32.GetKeyboardLayout(focus.windowThreadID) # In previous Windows builds, calling ToUnicodeEx would destroy keyboard buffer state and therefore cause the app to not produce the right WM_CHAR message. # However, ToUnicodeEx now can take a new flag of 0x4, which stops it from destroying keyboard state, thus allowing us to safely call it here. res = ctypes.windll.user32.ToUnicodeEx(vkCode, scanCode, keyStates, charBuf, len(charBuf), 0x4, hkl) if res > 0: for ch in charBuf[:res]: eventHandler.queueEvent("typedCharacter", focus, ch=ch) return True
def findExtraOverlayClasses(obj, clsList): if isWin10(1607) and config.conf['terminals']['keyboardSupportInLegacy']: from NVDAObjects.behaviors import KeyboardHandlerBasedTypedCharSupport clsList.append(KeyboardHandlerBasedTypedCharSupport) clsList.append(WinConsole)
def findExtraOverlayClasses(obj, clsList): if isWin10(1607) and config.conf['terminals']['keyboardSupportInLegacy']: clsList.append(EnhancedLegacyWinConsole) else: clsList.append(LegacyWinConsole)