// 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