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) |
+ |