| // Copyright (c) 2012 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. |
| |
| #include "chrome_frame/test/simulate_input.h" |
| |
| #include <atlbase.h> |
| #include <atlwin.h> |
| |
| #include "base/test/test_timeouts.h" |
| #include "base/threading/platform_thread.h" |
| #include "chrome_frame/utils.h" |
| |
| namespace simulate_input { |
| |
| class ForegroundHelperWindow : public CWindowImpl<ForegroundHelperWindow> { |
| public: |
| BEGIN_MSG_MAP(ForegroundHelperWindow) |
| MESSAGE_HANDLER(WM_HOTKEY, OnHotKey) |
| END_MSG_MAP() |
| |
| ForegroundHelperWindow() : window_(NULL) {} |
| |
| HRESULT SetForeground(HWND window) { |
| DCHECK(::IsWindow(window)); |
| window_ = window; |
| if (NULL == Create(NULL, NULL, NULL, WS_POPUP)) |
| return AtlHresultFromLastError(); |
| |
| static const int kHotKeyId = 0x0000baba; |
| static const int kHotKeyWaitTimeout = 2000; |
| |
| RegisterHotKey(m_hWnd, kHotKeyId, 0, VK_F22); |
| |
| MSG msg = {0}; |
| PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); |
| |
| SendMnemonic(VK_F22, NONE, false, false, KEY_DOWN); |
| // There are scenarios where the WM_HOTKEY is not dispatched by the |
| // the corresponding foreground thread. To prevent us from indefinitely |
| // waiting for the hotkey, we set a timer and exit the loop. |
| SetTimer(kHotKeyId, kHotKeyWaitTimeout, NULL); |
| |
| while (GetMessage(&msg, NULL, 0, 0)) { |
| TranslateMessage(&msg); |
| DispatchMessage(&msg); |
| if (msg.message == WM_HOTKEY) { |
| break; |
| } |
| if (msg.message == WM_TIMER) { |
| SetForegroundWindow(window); |
| break; |
| } |
| } |
| |
| UnregisterHotKey(m_hWnd, kHotKeyId); |
| KillTimer(kHotKeyId); |
| DestroyWindow(); |
| return S_OK; |
| } |
| |
| LRESULT OnHotKey(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled) { // NOLINT |
| SetForegroundWindow(window_); |
| return 1; |
| } |
| private: |
| HWND window_; |
| }; |
| |
| bool ForceSetForegroundWindow(HWND window) { |
| if (GetForegroundWindow() == window) |
| return true; |
| ForegroundHelperWindow foreground_helper_window; |
| HRESULT hr = foreground_helper_window.SetForeground(window); |
| return SUCCEEDED(hr); |
| } |
| |
| struct PidAndWindow { |
| base::ProcessId pid; |
| HWND hwnd; |
| }; |
| |
| BOOL CALLBACK FindWindowInProcessCallback(HWND hwnd, LPARAM param) { |
| PidAndWindow* paw = reinterpret_cast<PidAndWindow*>(param); |
| base::ProcessId pid; |
| GetWindowThreadProcessId(hwnd, &pid); |
| if (pid == paw->pid && IsWindowVisible(hwnd)) { |
| paw->hwnd = hwnd; |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| bool EnsureProcessInForeground(base::ProcessId process_id) { |
| HWND hwnd = GetForegroundWindow(); |
| base::ProcessId current_foreground_pid = 0; |
| DWORD active_thread_id = GetWindowThreadProcessId(hwnd, |
| ¤t_foreground_pid); |
| if (current_foreground_pid == process_id) |
| return true; |
| |
| PidAndWindow paw = { process_id }; |
| EnumWindows(FindWindowInProcessCallback, reinterpret_cast<LPARAM>(&paw)); |
| if (!IsWindow(paw.hwnd)) { |
| LOG(ERROR) << "failed to find process window"; |
| return false; |
| } |
| |
| bool ret = ForceSetForegroundWindow(paw.hwnd); |
| LOG_IF(ERROR, !ret) << "ForceSetForegroundWindow: " << ret; |
| |
| return ret; |
| } |
| |
| void SendScanCode(short scan_code, uint32 modifiers) { |
| DCHECK(-1 != scan_code); |
| |
| // High order byte in |scan_code| is SHIFT/CTRL/ALT key state. |
| modifiers = static_cast<Modifier>(modifiers | HIBYTE(scan_code)); |
| DCHECK(modifiers <= ALT); |
| |
| // Low order byte in |scan_code| is the actual scan code. |
| SendMnemonic(LOBYTE(scan_code), modifiers, false, true, KEY_DOWN); |
| } |
| |
| void SendCharA(char c, uint32 modifiers) { |
| SendScanCode(VkKeyScanA(c), modifiers); |
| } |
| |
| void SendCharW(wchar_t c, uint32 modifiers) { |
| SendScanCode(VkKeyScanW(c), modifiers); |
| } |
| |
| // Sends a keystroke to the currently active application with optional |
| // modifiers set. |
| void SendMnemonic(WORD mnemonic_char, |
| uint32 modifiers, |
| bool extended, |
| bool unicode, |
| KeyMode key_mode) { |
| const int kMaxInputs = 4; |
| INPUT keys[kMaxInputs] = {0}; // Keyboard events |
| int key_count = 0; // Number of generated events |
| |
| if (modifiers & SHIFT) { |
| keys[key_count].type = INPUT_KEYBOARD; |
| keys[key_count].ki.wVk = VK_SHIFT; |
| keys[key_count].ki.wScan = MapVirtualKey(VK_SHIFT, 0); |
| key_count++; |
| } |
| |
| if (modifiers & CONTROL) { |
| keys[key_count].type = INPUT_KEYBOARD; |
| keys[key_count].ki.wVk = VK_CONTROL; |
| keys[key_count].ki.wScan = MapVirtualKey(VK_CONTROL, 0); |
| key_count++; |
| } |
| |
| if (modifiers & ALT) { |
| keys[key_count].type = INPUT_KEYBOARD; |
| keys[key_count].ki.wVk = VK_MENU; |
| keys[key_count].ki.wScan = MapVirtualKey(VK_MENU, 0); |
| key_count++; |
| } |
| |
| if (mnemonic_char) { |
| keys[key_count].type = INPUT_KEYBOARD; |
| keys[key_count].ki.wVk = mnemonic_char; |
| keys[key_count].ki.wScan = MapVirtualKey(mnemonic_char, 0); |
| |
| if (extended) |
| keys[key_count].ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; |
| if (unicode) |
| keys[key_count].ki.dwFlags |= KEYEVENTF_UNICODE; |
| key_count++; |
| } |
| |
| DCHECK_LE(key_count, kMaxInputs); |
| |
| // Add the key up bit if needed. |
| if (key_mode == KEY_UP) { |
| for (int i = 0; i < key_count; i++) { |
| keys[i].ki.dwFlags |= KEYEVENTF_KEYUP; |
| } |
| } |
| |
| SendInput(key_count, &keys[0], sizeof(keys[0])); |
| } |
| |
| void SetKeyboardFocusToWindow(HWND window) { |
| SendMouseClick(window, 1, 1, LEFT); |
| } |
| |
| void SendMouseClick(int x, int y, MouseButton button) { |
| const base::TimeDelta kMessageTimeout = TestTimeouts::tiny_timeout(); |
| // TODO(joshia): Fix this. GetSystemMetrics(SM_CXSCREEN) will |
| // retrieve screen size of the primarary monitor only. And monitors |
| // arrangement could be pretty arbitrary. |
| double screen_width = ::GetSystemMetrics(SM_CXSCREEN) - 1; |
| double screen_height = ::GetSystemMetrics(SM_CYSCREEN) - 1; |
| double location_x = x * (65535.0f / screen_width); |
| double location_y = y * (65535.0f / screen_height); |
| |
| // Take advantage of button flag bitmask layout |
| unsigned int button_flag = MOUSEEVENTF_LEFTDOWN << (button + button); |
| |
| INPUT input_info = {0}; |
| input_info.type = INPUT_MOUSE; |
| input_info.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; |
| input_info.mi.dx = static_cast<LONG>(location_x); |
| input_info.mi.dy = static_cast<LONG>(location_y); |
| ::SendInput(1, &input_info, sizeof(INPUT)); |
| base::PlatformThread::Sleep(kMessageTimeout); |
| |
| input_info.mi.dwFlags = button_flag | MOUSEEVENTF_ABSOLUTE; |
| ::SendInput(1, &input_info, sizeof(INPUT)); |
| base::PlatformThread::Sleep(kMessageTimeout); |
| |
| input_info.mi.dwFlags = (button_flag << 1) | MOUSEEVENTF_ABSOLUTE; |
| ::SendInput(1, &input_info, sizeof(INPUT)); |
| base::PlatformThread::Sleep(kMessageTimeout); |
| } |
| |
| void SendMouseClick(HWND window, int x, int y, MouseButton button) { |
| if (!IsWindow(window)) { |
| NOTREACHED() << "Invalid window handle."; |
| return; |
| } |
| |
| HWND top_level_window = window; |
| if (!IsTopLevelWindow(top_level_window)) { |
| top_level_window = GetAncestor(window, GA_ROOT); |
| } |
| |
| ForceSetForegroundWindow(top_level_window); |
| |
| POINT cursor_position = {x, y}; |
| ClientToScreen(window, &cursor_position); |
| SendMouseClick(cursor_position.x, cursor_position.y, button); |
| } |
| |
| void SendExtendedKey(WORD key, uint32 modifiers) { |
| SendMnemonic(key, modifiers, true, false, KEY_UP); |
| } |
| |
| void SendStringW(const std::wstring& s) { |
| for (size_t i = 0; i < s.length(); i++) { |
| SendCharW(s[i], NONE); |
| Sleep(10); |
| } |
| } |
| |
| void SendStringA(const std::string& s) { |
| for (size_t i = 0; i < s.length(); i++) { |
| SendCharA(s[i], NONE); |
| Sleep(10); |
| } |
| } |
| |
| } // namespace simulate_input |