Chromium Code Reviews| 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) |
|
dmazzoni
2014/11/03 16:26:49
nit: wrap to 80 chars
|
| +_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 |
|
dmazzoni
2014/11/03 16:26:50
If there's more than one, could you return the one
|
| + |
| +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) |
| + |