Path: blob/trunk/cpp/iedriver/ActionSimulators/SendInputActionSimulator.cpp
2868 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the "License");5// you may not use this file except in compliance with the License.6// You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing, software11// distributed under the License is distributed on an "AS IS" BASIS,12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13// See the License for the specific language governing permissions and14// limitations under the License.1516#include "SendInputActionSimulator.h"1718#include "errorcodes.h"19#include "logging.h"2021#include "../DocumentHost.h"22#include "../HookProcessor.h"23#include "../messages.h"2425#define WAIT_TIME_IN_MILLISECONDS_PER_INPUT_EVENT 1002627namespace webdriver {282930SendInputActionSimulator::SendInputActionSimulator() {31}3233SendInputActionSimulator::~SendInputActionSimulator() {34}3536int SendInputActionSimulator::SimulateActions(BrowserHandle browser_wrapper,37std::vector<INPUT> inputs,38InputState* input_state) {39LOG(TRACE) << "Entering SendInputActionSimulator::SimulateActions";40// SendInput simulates mouse and keyboard events at a very low level, so41// low that there is no guarantee that IE will have processed the resulting42// windows messages before this method returns. Therefore, we'll install43// keyboard and mouse hooks that will count the number of Windows messages44// processed by any application the system. There is a potential for this45// code to be wrong if the user is interacting with the system via mouse and46// keyboard during this process. Since this code path should only be hit if47// the requireWindowFocus capability is turned on, and since SendInput is48// documented to not allow other input events to be interspersed into the49// input queue, the risk is hopefully minimized.50HookProcessor keyboard_hook;51keyboard_hook.Initialize("KeyboardHookProc", WH_KEYBOARD);5253HookProcessor mouse_hook;54mouse_hook.Initialize("MouseHookProc", WH_MOUSE);5556bool is_button_swapped = ::GetSystemMetrics(SM_SWAPBUTTON) != 0;5758HWND window_handle = browser_wrapper->GetContentWindowHandle();59// Loop through all of the input items, and find all of the sleeps.60std::vector<size_t> sleep_indexes;61for (size_t i = 0; i < inputs.size(); ++i) {62INPUT current_input = inputs[i];63this->UpdateInputState(current_input, input_state);64if (current_input.type == INPUT_HARDWARE && current_input.hi.uMsg > 0) {65sleep_indexes.push_back(i);66} else if (current_input.type == INPUT_MOUSE) {67// We use the INPUT structure to store absolute pixel68// coordinates for the SendMessage case, but SendInput69// requires normalized coordinates.70int normalized_x = 0, normalized_y = 0;71this->GetNormalizedCoordinates(window_handle,72current_input.mi.dx,73current_input.mi.dy,74&normalized_x,75&normalized_y);76current_input.mi.dx = normalized_x;77current_input.mi.dy = normalized_y;7879// If the buttons are swapped on the mouse (most often referred to80// as "left-handed"), where the right button is primary and the81// left button is secondary, we need to swap those when using82// SendInput.83unsigned long normalized_flags = this->NormalizeButtons(is_button_swapped,84current_input.mi.dwFlags);8586current_input.mi.dwFlags = normalized_flags;87inputs[i] = current_input;88}89}9091// Send all inputs between sleeps, sleeping in between.92size_t next_input_index = 0;93std::vector<size_t>::const_iterator it = sleep_indexes.begin();94for (; it != sleep_indexes.end(); ++it) {95size_t sleep_input_index = *it;96INPUT sleep_input = inputs[sleep_input_index];97size_t number_of_inputs = sleep_input_index - next_input_index;98if (number_of_inputs > 0) {99this->SendInputToBrowser(browser_wrapper,100inputs,101static_cast<int>(next_input_index),102static_cast<int>(number_of_inputs));103}104LOG(DEBUG) << "Processing pause event";105::Sleep(inputs[sleep_input_index].hi.uMsg);106next_input_index = sleep_input_index + 1;107}108// Now send any inputs after the last sleep, if any.109size_t last_inputs = inputs.size() - next_input_index;110if (last_inputs > 0) {111this->SendInputToBrowser(browser_wrapper,112inputs,113static_cast<int>(next_input_index),114static_cast<int>(last_inputs));115}116117// We're done here, so uninstall the hooks, and reset the buffer size.118keyboard_hook.Dispose();119mouse_hook.Dispose();120121return WD_SUCCESS;122}123124void SendInputActionSimulator::SendInputToBrowser(BrowserHandle browser_wrapper,125std::vector<INPUT> inputs,126int start_index,127int input_count) {128if (input_count > 0) {129bool focus_set = browser_wrapper->SetFocusToBrowser();130if (!focus_set) {131LOG(WARN) << "Focus not set to browser window";132}133HookProcessor::ResetEventCount();134int sent_inputs = 0;135for (int i = start_index; i < start_index + input_count; ++i) {136::SendInput(1, &inputs[i], sizeof(INPUT));137sent_inputs += 1;138}139this->WaitForInputEventProcessing(sent_inputs);140}141}142143void SendInputActionSimulator::GetNormalizedCoordinates(HWND window_handle,144int x,145int y,146int* normalized_x,147int* normalized_y) {148LOG(TRACE) << "Entering InputManager::GetNormalizedCoordinates";149POINT cursor_position;150cursor_position.x = x;151cursor_position.y = y;152::ClientToScreen(window_handle, &cursor_position);153154int screen_width = ::GetSystemMetrics(SM_CXSCREEN) - 1;155int screen_height = ::GetSystemMetrics(SM_CYSCREEN) - 1;156*normalized_x = static_cast<int>(cursor_position.x * (65535.0f / screen_width));157*normalized_y = static_cast<int>(cursor_position.y * (65535.0f / screen_height));158}159160unsigned long SendInputActionSimulator::NormalizeButtons(bool is_button_swapped,161unsigned long input_flags) {162unsigned long flags = input_flags;163if (is_button_swapped) {164if (flags & MOUSEEVENTF_LEFTDOWN) {165flags &= ~(MOUSEEVENTF_LEFTDOWN);166flags |= MOUSEEVENTF_RIGHTDOWN;167}168else if (flags & MOUSEEVENTF_LEFTUP) {169flags &= ~(MOUSEEVENTF_LEFTUP);170flags |= MOUSEEVENTF_RIGHTUP;171}172else if (flags & MOUSEEVENTF_RIGHTDOWN) {173flags &= ~(MOUSEEVENTF_RIGHTDOWN);174flags |= MOUSEEVENTF_LEFTDOWN;175}176else if (flags & MOUSEEVENTF_RIGHTUP) {177flags &= ~(MOUSEEVENTF_RIGHTUP);178flags |= MOUSEEVENTF_LEFTUP;179}180}181return flags;182}183184bool SendInputActionSimulator::WaitForInputEventProcessing(int input_count) {185LOG(TRACE) << "Entering InputManager::WaitForInputEventProcessing";186// Adaptive wait. The total wait time is the number of input messages187// expected by the hook multiplied by a static wait time for each188// message to be processed (currently 100 milliseconds). We should189// exit out of this loop once the number of processed windows keyboard190// or mouse messages processed by the system exceeds the number of191// input events created by the call to SendInput.192int total_timeout_in_milliseconds = input_count * WAIT_TIME_IN_MILLISECONDS_PER_INPUT_EVENT;193clock_t end = clock() + static_cast<clock_t>(((total_timeout_in_milliseconds / 1000.0) * CLOCKS_PER_SEC));194195int processed_event_count = HookProcessor::GetEventCount();196bool inputs_processed = processed_event_count >= input_count;197while (!inputs_processed && clock() < end) {198// Sleep a short amount of time to prevent starving the processor.199::Sleep(25);200processed_event_count = HookProcessor::GetEventCount();201inputs_processed = processed_event_count >= input_count;202}203LOG(DEBUG) << "Requested waiting for " << input_count204<< " events, processed " << processed_event_count << " events,"205<< " timed out after " << total_timeout_in_milliseconds206<< " milliseconds";207return inputs_processed;208}209210} // namespace webdriver211212#ifdef __cplusplus213extern "C" {214#endif215216LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {217if (nCode == HC_ACTION) {218webdriver::HookProcessor::IncrementEventCount(1);219}220return ::CallNextHookEx(NULL, nCode, wParam, lParam);221}222223LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) {224if (nCode == HC_ACTION) {225webdriver::HookProcessor::IncrementEventCount(1);226}227return ::CallNextHookEx(NULL, nCode, wParam, lParam);228}229230#ifdef __cplusplus231}232#endif233234235