| Index: tools/accessibility/nvda/winapi.py
|
| diff --git a/tools/accessibility/nvda/winapi.py b/tools/accessibility/nvda/winapi.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..aeb5e4e11e51a4b79987a6f2498d4026dc850e38
|
| --- /dev/null
|
| +++ b/tools/accessibility/nvda/winapi.py
|
| @@ -0,0 +1,144 @@
|
| +#!/usr/bin/env python
|
| +# Copyright 2014 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +"""Windows API functions required by tests of Chrome with NVDA."""
|
| +
|
| +
|
| +from ctypes import wintypes
|
| +
|
| +import ctypes
|
| +
|
| +_user = ctypes.windll.user32
|
| +# Argument types for Windows API functions.
|
| +_user.VkKeyScanW.argtypes = [wintypes.WCHAR]
|
| +_user.VkKeyScanW.restype = wintypes.SHORT
|
| +_ENUM_WINDOWS_CALLBACK = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
|
| +_user.EnumWindows.argtypes = [_ENUM_WINDOWS_CALLBACK, wintypes.LPARAM]
|
| +_user.GetWindowThreadProcessId.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.DWORD)]
|
| +# Flags required by some API calls.
|
| +_MAP_VKCODE_TO_SCAN_CODE = 0
|
| +_PRESS_KEY = 0
|
| +_RELEASE_KEY = 2
|
| +
|
| +_MODIFIER_KEYS = {
|
| + 'shift': 0x10,
|
| + 'control': 0x11,
|
| + 'alt': 0x12,
|
| + 'windows': 0x5B,
|
| +
|
| + # Screen reader modifiers.
|
| + 'insert': 0x2D,
|
| + 'capslock': 0x14,
|
| +}
|
| +
|
| +_SPECIAL_KEYS = {
|
| + 'enter': 0x0D,
|
| + 'space': 0x20,
|
| + 'tab': 0x09,
|
| + 'applications': 0x5D, # Context menu key.
|
| + 'escape': 0x1B,
|
| + 'backspace': 0x08,
|
| + 'delete': 0x2E,
|
| +
|
| + # Navigation keys.
|
| + 'pageup': 0x21,
|
| + 'pagedown': 0x22,
|
| + 'end': 0x23,
|
| + 'home': 0x24,
|
| + 'left': 0x25,
|
| + 'up': 0x26,
|
| + 'right': 0x27,
|
| + 'down': 0x28,
|
| +
|
| + # Browser keys.
|
| + 'back': 0xA6,
|
| + 'forward': 0xA7,
|
| + 'refresh': 0xA8,
|
| + 'home': 0xAC,
|
| +}
|
| +
|
| +def _getKeyAndScanCode(key):
|
| + if key[0] == '{' and key[-1] == '}':
|
| + # This is a modifier or a special key.
|
| + key = key[1:-1]
|
| + keyCode = dict(_MODIFIER_KEYS.items() + _SPECIAL_KEYS.items()).get(key)
|
| + if keyCode is not None:
|
| + return keyCode, _user.MapVirtualKeyW(keyCode, _MAP_VKCODE_TO_SCAN_CODE)
|
| + else:
|
| + raise ValueError, key
|
| + else:
|
| + # This is a character to be typed.
|
| + keyCodes = []
|
| + retVal = _user.VkKeyScanW(key)
|
| + modifiers = retVal >> 8 # Upper byte only.
|
| + keyCode = retVal & 0xFF # Lower byte only.
|
| + if modifiers == -1 or keyCode == -1:
|
| + raise ValueError, key
|
| + if modifiers & 0x1: # Shift key.
|
| + keyCodes.append(_getKeyAndScanCode('{shift}'))
|
| + if modifiers & 0x2: # Control key.
|
| + keyCodes.append(_getKeyAndScanCode('{control}'))
|
| + if modifiers & 0x4: # Alt key.
|
| + keyCodes.append(_getKeyAndScanCode('{alt}'))
|
| + keyCodes.append((keyCode, _user.MapVirtualKeyW(keyCode, _MAP_VKCODE_TO_SCAN_CODE)))
|
| + return keyCodes
|
| +
|
| +def typeKeys(keys):
|
| + """Enters the given keys in the active application's input queue.
|
| +
|
| + The argument to this function should be a string containing a sequence of
|
| + keys to be pressed. Keys are separated using '+'. Special keys like control
|
| + should be placed in braces.
|
| + Examples:
|
| + Open the Task Manager: winapi.typeKeys('{control}+{shift}{escape}')
|
| + Read the current line using NVDA: winapi.typeKeys('{insert}{up}')
|
| + Type a combination of capital and small letters: winapi.typeKeys('TeStIng')
|
| + """
|
| + try:
|
| + pressedModifiers = []
|
| + for key in keys.split('+'):
|
| + if key[0] == '{' and key[-1] == '}':
|
| + # This is a modifier or a special key.
|
| + keyCode, scanCode = _getKeyAndScanCode(key)
|
| + _user.keybd_event(keyCode, scanCode, _PRESS_KEY)
|
| + if key[1:-1] in _MODIFIER_KEYS:
|
| + pressedModifiers.append((keyCode, scanCode))
|
| + else:
|
| + # Key is a sequence of one or more characters.
|
| + textCodes = [_getKeyAndScanCode(c) for c in key]
|
| + for charCodes in textCodes:
|
| + for keyCode, scanCode in charCodes:
|
| + _user.keybd_event(keyCode, scanCode, _PRESS_KEY)
|
| + for keyCode, scanCode in reversed(charCodes):
|
| + _user.keybd_event(keyCode, scanCode, _RELEASE_KEY)
|
| + except ValueError:
|
| + raise
|
| + finally:
|
| + # Release any modifiers in reverse order.
|
| + for keyCode, scanCode in reversed(pressedModifiers):
|
| + _user.keybd_event(keyCode, scanCode, _RELEASE_KEY)
|
| +
|
| +def _getWindowHandleFromProcessId(processId):
|
| + hWnds = []
|
| + def enumWindowsCallback(hWnd, lParam):
|
| + if _isWindowHandleOwnedByProcess(processId, hWnd):
|
| + hWnds.append(hWnd)
|
| + return True # Continue enumerating.
|
| + _user.EnumWindows(_ENUM_WINDOWS_CALLBACK(enumWindowsCallback), 0)
|
| + return hWnds[0] if len(hWnds) > 0 else None
|
| +
|
| +def _isWindowHandleOwnedByProcess(processId, hWnd):
|
| + windowProcessId = wintypes.DWORD()
|
| + if _user.GetWindowThreadProcessId(hWnd, ctypes.byref(windowProcessId)):
|
| + if windowProcessId.value == processId:
|
| + return True
|
| + return False
|
| +
|
| +def focusWindow(processId):
|
| + hWnd = _getWindowHandleFromProcessId(processId)
|
| + if hWnd is None:
|
| + raise ValueError, processId
|
| + _user.SwitchToThisWindow(hWnd)
|
| +
|
|
|