| // Copyright 2013 Software Freedom Conservancy
|
| // Licensed under the Apache License, Version 2.0 (the "License");
|
| // you may not use this file except in compliance with the License.
|
| // You may obtain a copy of the License at
|
| //
|
| // http://www.apache.org/licenses/LICENSE-2.0
|
| //
|
| // Unless required by applicable law or agreed to in writing, software
|
| // distributed under the License is distributed on an "AS IS" BASIS,
|
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| // See the License for the specific language governing permissions and
|
| // limitations under the License.
|
|
|
| #include "errorcodes.h"
|
| #include "InputManager.h"
|
| #include "interactions.h"
|
| #include "logging.h"
|
| #include "Script.h"
|
| #include "Generated/atoms.h"
|
|
|
| namespace webdriver {
|
|
|
| InputManager::InputManager() {
|
| LOG(TRACE) << "Entering InputManager::InputManager";
|
| this->use_native_events_ = true;
|
| this->require_window_focus_ = true;
|
| this->scroll_behavior_ = TOP;
|
| this->is_alt_pressed_ = false;
|
| this->is_control_pressed_ = false;
|
| this->is_shift_pressed_ = false;
|
| this->last_known_mouse_x_ = 0;
|
| this->last_known_mouse_y_ = 0;
|
|
|
| CComVariant keyboard_state;
|
| keyboard_state.vt = VT_NULL;
|
| this->keyboard_state_ = keyboard_state;
|
|
|
| CComVariant mouse_state;
|
| mouse_state.vt = VT_NULL;
|
| this->mouse_state_ = mouse_state;
|
| }
|
|
|
| InputManager::~InputManager(void) {
|
| }
|
|
|
| void InputManager::Initialize(ElementRepository* element_map) {
|
| LOG(TRACE) << "Entering InputManager::Initialize";
|
| this->element_map_ = element_map;
|
| }
|
|
|
| int InputManager::PerformInputSequence(BrowserHandle browser_wrapper, const Json::Value& sequence) {
|
| LOG(TRACE) << "Entering InputManager::PerformInputSequence";
|
| if (!sequence.isArray()) {
|
| return EUNHANDLEDERROR;
|
| }
|
|
|
| // Use a single mutex, so that all instances synchronize on the same object
|
| // for focus purposes.
|
| HANDLE mutex_handle = ::CreateMutex(NULL, FALSE, USER_INTERACTION_MUTEX_NAME);
|
| if (mutex_handle != NULL) {
|
| // Wait for up to the timeout (currently 30 seconds) for other sessions
|
| // to completely initialize.
|
| DWORD mutex_wait_status = ::WaitForSingleObject(mutex_handle, 30000);
|
| if (mutex_wait_status == WAIT_ABANDONED) {
|
| LOG(WARN) << "Acquired mutex, but received wait abandoned status. This "
|
| << "could mean the process previously owning the mutex was "
|
| << "unexpectedly terminated.";
|
| } else if (mutex_wait_status == WAIT_TIMEOUT) {
|
| LOG(WARN) << "Could not acquire mutex within the timeout. Multiple "
|
| << "instances may have incorrect synchronization for interactions";
|
| } else if (mutex_wait_status == WAIT_OBJECT_0) {
|
| LOG(DEBUG) << "Mutex acquired for user interaction.";
|
| }
|
| } else {
|
| LOG(WARN) << "Could not create user interaction mutex. Multiple "
|
| << "instances of IE may behave unpredictably.";
|
| }
|
|
|
| if (this->require_window_focus_) {
|
| this->SetFocusToBrowser(browser_wrapper);
|
| }
|
| this->inputs_.clear();
|
| for (size_t i = 0; i < sequence.size(); ++i) {
|
| // N.B. If require_window_focus_ is true, all the following methods do is
|
| // fill the list of INPUT structs with the appropriate SendInput data
|
| // structures. Otherwise, the action gets performed within that method.
|
| Json::UInt index = static_cast<Json::UInt>(i);
|
| Json::Value action = sequence[index];
|
| std::string action_name = action["action"].asString();
|
| if (action_name == "moveto") {
|
| bool offset_specified = action.isMember("xoffset") && action.isMember("yoffset");
|
| this->MouseMoveTo(browser_wrapper,
|
| action.get("element", "").asString(),
|
| offset_specified,
|
| action.get("xoffset", 0).asInt(),
|
| action.get("yoffset", 0).asInt());
|
| } else if (action_name == "buttondown") {
|
| this->MouseButtonDown(browser_wrapper);
|
| } else if (action_name == "buttonup") {
|
| this->MouseButtonUp(browser_wrapper);
|
| } else if (action_name == "click") {
|
| this->MouseClick(browser_wrapper, action.get("button", 0).asInt());
|
| } else if (action_name == "doubleclick") {
|
| this->MouseDoubleClick(browser_wrapper);
|
| } else if (action_name == "keys") {
|
| if (action.isMember("value")) {
|
| this->SendKeystrokes(browser_wrapper, action.get("value", Json::Value(Json::arrayValue)), false);
|
| }
|
| }
|
| }
|
|
|
| // If there are inputs in the array, then we've queued up input actions
|
| // to be played back. So play them back.
|
| if (this->inputs_.size() > 0) {
|
| ::SendInput(static_cast<UINT>(this->inputs_.size()), &this->inputs_[0], sizeof(INPUT));
|
| }
|
|
|
| // Must always release the mutex.
|
| if (mutex_handle != NULL) {
|
| ::ReleaseMutex(mutex_handle);
|
| ::CloseHandle(mutex_handle);
|
| }
|
| return SUCCESS;
|
| }
|
|
|
| bool InputManager::SetFocusToBrowser(BrowserHandle browser_wrapper) {
|
| LOG(TRACE) << "Entering InputManager::SetFocusToBrowser";
|
| DWORD lock_timeout = 0;
|
| DWORD process_id = 0;
|
| DWORD thread_id = ::GetWindowThreadProcessId(browser_wrapper->GetWindowHandle(), &process_id);
|
| DWORD current_thread_id = ::GetCurrentThreadId();
|
| HWND current_foreground_window = ::GetForegroundWindow();
|
| if (current_foreground_window != browser_wrapper->GetTopLevelWindowHandle()) {
|
| if (current_thread_id != thread_id) {
|
| ::AttachThreadInput(current_thread_id, thread_id, TRUE);
|
| ::SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, &lock_timeout, 0);
|
| ::SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, 0, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
|
| ::AllowSetForegroundWindow(ASFW_ANY);
|
| }
|
| ::SetForegroundWindow(browser_wrapper->GetTopLevelWindowHandle());
|
| if (current_thread_id != thread_id) {
|
| ::SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, (PVOID)lock_timeout, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
|
| ::AttachThreadInput(current_thread_id, thread_id, FALSE);
|
| }
|
| }
|
| return ::GetForegroundWindow() == browser_wrapper->GetTopLevelWindowHandle();
|
| }
|
|
|
| int InputManager::MouseClick(BrowserHandle browser_wrapper, int button) {
|
| LOG(TRACE) << "Entering InputManager::MouseClick";
|
| if (this->use_native_events_) {
|
| HWND browser_window_handle = browser_wrapper->GetWindowHandle();
|
| if (this->require_window_focus_) {
|
| LOG(DEBUG) << "Queueing SendInput structure for mouse click";
|
| int down_flag = MOUSEEVENTF_LEFTDOWN;
|
| int up_flag = MOUSEEVENTF_LEFTUP;
|
| if (button == WD_CLIENT_MIDDLE_MOUSE_BUTTON) {
|
| down_flag = MOUSEEVENTF_MIDDLEDOWN;
|
| up_flag = MOUSEEVENTF_MIDDLEUP;
|
| } else if (button == WD_CLIENT_RIGHT_MOUSE_BUTTON) {
|
| down_flag = MOUSEEVENTF_RIGHTDOWN;
|
| up_flag = MOUSEEVENTF_RIGHTUP;
|
| }
|
| this->AddMouseInput(browser_window_handle, down_flag, this->last_known_mouse_x_, this->last_known_mouse_y_);
|
| this->AddMouseInput(browser_window_handle, up_flag, this->last_known_mouse_x_, this->last_known_mouse_y_);
|
| } else {
|
| LOG(DEBUG) << "Using SendMessage method for mouse click";
|
| clickAt(browser_window_handle,
|
| this->last_known_mouse_x_,
|
| this->last_known_mouse_y_,
|
| button);
|
| }
|
| } else {
|
| LOG(DEBUG) << "Using synthetic events for mouse click";
|
| int script_arg_count = 2;
|
| std::wstring script_source = L"(function() { return function(){" +
|
| atoms::asString(atoms::INPUTS) +
|
| L"; return webdriver.atoms.inputs.click(arguments[0], arguments[1]);" +
|
| L"};})();";
|
| if (button == WD_CLIENT_RIGHT_MOUSE_BUTTON) {
|
| script_arg_count = 1;
|
| script_source = L"(function() { return function(){" +
|
| atoms::asString(atoms::INPUTS) +
|
| L"; return webdriver.atoms.inputs.rightClick(arguments[0]);" +
|
| L"};})();";
|
| } else if (button == WD_CLIENT_MIDDLE_MOUSE_BUTTON) {
|
| LOG(WARN) << "Only right and left mouse click types are supported by synthetic events. A left mouse click will be performed.";
|
| } else if (button < WD_CLIENT_LEFT_MOUSE_BUTTON || button > WD_CLIENT_RIGHT_MOUSE_BUTTON) {
|
| // Write to the log, but still attempt the "click" anyway. The atom should catch the error.
|
| LOG(ERROR) << "Unsupported mouse button type is specified: " << button;
|
| }
|
|
|
| CComPtr<IHTMLDocument2> doc;
|
| browser_wrapper->GetDocument(&doc);
|
| Script script_wrapper(doc, script_source, script_arg_count);
|
|
|
| if (script_arg_count > 1) {
|
| // The click input atom takes an element as its first argument,
|
| // but if we're passing a mouse state (which we are), it contains
|
| // the element we're interested in, so pass a null value. Other
|
| // input atoms only take a single argument.
|
| script_wrapper.AddNullArgument();
|
| }
|
|
|
| script_wrapper.AddArgument(this->mouse_state_);
|
| int status_code = script_wrapper.Execute();
|
| if (status_code == SUCCESS) {
|
| this->mouse_state_ = script_wrapper.result();
|
| } else {
|
| LOG(WARN) << "Unable to execute js to perform mouse click";
|
| return status_code;
|
| }
|
| }
|
| return SUCCESS;
|
| }
|
|
|
| int InputManager::MouseButtonDown(BrowserHandle browser_wrapper) {
|
| LOG(TRACE) << "Entering InputManager::MouseButtonDown";
|
| if (this->use_native_events_) {
|
| HWND browser_window_handle = browser_wrapper->GetWindowHandle();
|
| if (this->require_window_focus_) {
|
| LOG(DEBUG) << "Queuing SendInput structure for mouse button down";
|
| this->AddMouseInput(browser_window_handle, MOUSEEVENTF_LEFTDOWN, this->last_known_mouse_x_, this->last_known_mouse_y_);
|
| } else {
|
| LOG(DEBUG) << "Using SendMessage method for mouse button down";
|
| //TODO: json wire protocol allows 3 mouse button types for this command
|
| mouseDownAt(browser_window_handle,
|
| this->last_known_mouse_x_,
|
| this->last_known_mouse_y_,
|
| MOUSEBUTTON_LEFT);
|
| }
|
| } else {
|
| LOG(DEBUG) << "Using synthetic events for mouse button down";
|
| std::wstring script_source = L"(function() { return function(){" +
|
| atoms::asString(atoms::INPUTS) +
|
| L"; return webdriver.atoms.inputs.mouseButtonDown(arguments[0]);" +
|
| L"};})();";
|
|
|
| CComPtr<IHTMLDocument2> doc;
|
| browser_wrapper->GetDocument(&doc);
|
| Script script_wrapper(doc, script_source, 1);
|
| script_wrapper.AddArgument(this->mouse_state_);
|
| int status_code = script_wrapper.Execute();
|
| if (status_code == SUCCESS) {
|
| this->mouse_state_ = script_wrapper.result();
|
| } else {
|
| LOG(WARN) << "Unable to execute js to perform mouse button down";
|
| return status_code;
|
| }
|
| }
|
| return SUCCESS;
|
| }
|
|
|
| int InputManager::MouseButtonUp(BrowserHandle browser_wrapper) {
|
| LOG(TRACE) << "Entering InputManager::MouseButtonUp";
|
| if (this->use_native_events_) {
|
| HWND browser_window_handle = browser_wrapper->GetWindowHandle();
|
| if (this->require_window_focus_) {
|
| LOG(DEBUG) << "Queuing SendInput structure for mouse button up";
|
| this->AddMouseInput(browser_window_handle, MOUSEEVENTF_LEFTUP, this->last_known_mouse_x_, this->last_known_mouse_y_);
|
| } else {
|
| LOG(DEBUG) << "Using SendMessage method for mouse button up";
|
| //TODO: json wire protocol allows 3 mouse button types for this command
|
| mouseUpAt(browser_window_handle,
|
| this->last_known_mouse_x_,
|
| this->last_known_mouse_y_,
|
| MOUSEBUTTON_LEFT);
|
| }
|
| } else {
|
| LOG(DEBUG) << "Using synthetic events for mouse button up";
|
| std::wstring script_source = L"(function() { return function(){" +
|
| atoms::asString(atoms::INPUTS) +
|
| L"; return webdriver.atoms.inputs.mouseButtonUp(arguments[0]);" +
|
| L"};})();";
|
|
|
| CComPtr<IHTMLDocument2> doc;
|
| browser_wrapper->GetDocument(&doc);
|
| Script script_wrapper(doc, script_source, 1);
|
| script_wrapper.AddArgument(this->mouse_state_);
|
| int status_code = script_wrapper.Execute();
|
| if (status_code == SUCCESS) {
|
| this->mouse_state_ = script_wrapper.result();
|
| } else {
|
| LOG(WARN) << "Unable to execute js to perform mouse button up";
|
| return status_code;
|
| }
|
| }
|
| return SUCCESS;
|
| }
|
|
|
| int InputManager::MouseDoubleClick(BrowserHandle browser_wrapper) {
|
| LOG(TRACE) << "Entering InputManager::MouseDoubleClick";
|
| if (this->use_native_events_) {
|
| HWND browser_window_handle = browser_wrapper->GetWindowHandle();
|
| if (this->require_window_focus_) {
|
| LOG(DEBUG) << "Queueing SendInput structure for mouse double click";
|
| this->AddMouseInput(browser_window_handle, MOUSEEVENTF_LEFTDOWN, this->last_known_mouse_x_, this->last_known_mouse_y_);
|
| this->AddMouseInput(browser_window_handle, MOUSEEVENTF_LEFTUP, this->last_known_mouse_x_, this->last_known_mouse_y_);
|
| this->AddMouseInput(browser_window_handle, MOUSEEVENTF_LEFTDOWN, this->last_known_mouse_x_, this->last_known_mouse_y_);
|
| this->AddMouseInput(browser_window_handle, MOUSEEVENTF_LEFTUP, this->last_known_mouse_x_, this->last_known_mouse_y_);
|
| } else {
|
| LOG(DEBUG) << "Using SendMessage method for mouse double click";
|
| doubleClickAt(browser_window_handle, this->last_known_mouse_x_, this->last_known_mouse_y_);
|
| }
|
| } else {
|
| LOG(DEBUG) << "Using synthetic events for mouse double click";
|
| std::wstring script_source = L"(function() { return function(){" +
|
| atoms::asString(atoms::INPUTS) +
|
| L"; return webdriver.atoms.inputs.doubleClick(arguments[0]);" +
|
| L"};})();";
|
|
|
| CComPtr<IHTMLDocument2> doc;
|
| browser_wrapper->GetDocument(&doc);
|
| Script script_wrapper(doc, script_source, 1);
|
| script_wrapper.AddArgument(this->mouse_state_);
|
| int status_code = script_wrapper.Execute();
|
| if (status_code == SUCCESS) {
|
| this->mouse_state_ = script_wrapper.result();
|
| } else {
|
| LOG(WARN) << "Unable to execute js to double click";
|
| return status_code;
|
| }
|
| }
|
| return SUCCESS;
|
| }
|
|
|
| int InputManager::MouseMoveTo(BrowserHandle browser_wrapper, std::string element_id, bool offset_specified, int x_offset, int y_offset) {
|
| LOG(TRACE) << "Entering InputManager::MouseMoveTo";
|
| int status_code = SUCCESS; |
| bool element_specified = element_id.size() != 0; |
| ElementHandle target_element; |
| if (element_specified) { |
| status_code = this->element_map_->GetManagedElement(element_id, &target_element); |
| if (status_code != SUCCESS) { |
| return status_code; |
| } |
| } |
| if (this->use_native_events_) { |
| long start_x = this->last_known_mouse_x_; |
| long start_y = this->last_known_mouse_y_; |
| |
| long end_x = start_x; |
| long end_y = start_y; |
| if (element_specified) { |
| LocationInfo element_location; |
| status_code = target_element->GetLocationOnceScrolledIntoView(this->scroll_behavior_, |
| &element_location); |
| // We can't use the status code alone here. GetLocationOnceScrolledIntoView |
| // returns EELEMENTNOTDISPLAYED if the element is visible, but the click |
| // point (the center of the element) is not within the viewport. However, |
| // we might still be able to move to whatever portion of the element *is* |
| // visible in the viewport, so we have to have an extra check. |
| if (status_code != SUCCESS && element_location.width == 0 && element_location.height == 0) { |
| LOG(WARN) << "Unable to get location after scrolling or element sizes are zero"; |
| return status_code; |
| } |
| |
| // An element was specified as the starting point, so we know the end of the mouse |
| // move will be at some offset from the element origin. |
| end_x = element_location.x; |
| end_y = element_location.y; |
| if (!offset_specified) { |
| // No offset was specified, which means move to the center of the element. All |
| // that remains is to determine whether that's the center of a block element, |
| // or the center of a the text (if the element's children only contain a single |
| // text node). |
| LOG(INFO) << "Checking whether element has single text node."; |
| if (target_element->HasOnlySingleTextNodeChild()) { |
| LOG(INFO) << "Element has single text node. Will use the middle of that."; |
| LocationInfo text_info; |
| target_element->GetTextBoundaries(&text_info); |
| // Get middle of selection if there's only one text node. |
| end_x += text_info.width / 2; |
| end_y += text_info.height / 2; |
| } else { |
| end_x += element_location.width / 2; |
| end_y += element_location.height / 2; |
| } |
| } |
| } |
| |
| if (offset_specified) { |
| // An offset was specified. At this point, the end coordinates should be |
| // set to either (1) the previous mouse position if there was no element |
| // specified, or (2) the origin of the element from which to calculate the |
| // offset. |
| end_x += x_offset; |
| end_y += y_offset; |
| } |
| |
| HWND browser_window_handle = browser_wrapper->GetWindowHandle(); |
| if (this->require_window_focus_) { |
| LOG(DEBUG) << "Queueing SendInput structure for mouse move";
|
| this->AddMouseInput(browser_window_handle, MOUSEEVENTF_MOVE, end_x, end_y);
|
| } else { |
| LOG(DEBUG) << "Using SendMessage method for mouse move";
|
| LRESULT move_result = mouseMoveTo(browser_window_handle, |
| 10, |
| start_x, |
| start_y, |
| end_x, |
| end_y); |
| } |
| this->last_known_mouse_x_ = end_x; |
| this->last_known_mouse_y_ = end_y; |
| } else { // Fall back on synthesized events. |
| LOG(DEBUG) << "Using synthetic events for mouse move";
|
| std::wstring script_source = L"(function() { return function(){" + |
| atoms::asString(atoms::INPUTS) + |
| L"; return webdriver.atoms.inputs.mouseMove(arguments[0], arguments[1], arguments[2], arguments[3]);" + |
| L"};})();"; |
| |
| CComPtr<IHTMLDocument2> doc; |
| browser_wrapper->GetDocument(&doc); |
| Script script_wrapper(doc, script_source, 4); |
| |
| if (element_specified) { |
| script_wrapper.AddArgument(target_element->element()); |
| } else { |
| script_wrapper.AddNullArgument(); |
| } |
| |
| if (offset_specified) { |
| script_wrapper.AddArgument(x_offset); |
| script_wrapper.AddArgument(y_offset); |
| } else { |
| script_wrapper.AddNullArgument(); |
| script_wrapper.AddNullArgument(); |
| } |
| |
| script_wrapper.AddArgument(this->mouse_state_); |
| status_code = script_wrapper.Execute(); |
| if (status_code == SUCCESS) { |
| this->mouse_state_ = script_wrapper.result(); |
| } else { |
| LOG(WARN) << "Unable to execute js to mouse move"; |
| } |
| } |
| return status_code; |
| }
|
|
|
| int InputManager::SendKeystrokes(BrowserHandle browser_wrapper, Json::Value keystroke_array, bool auto_release_modifier_keys) {
|
| LOG(TRACE) << "Entering InputManager::SendKeystrokes";
|
| int status_code = SUCCESS;
|
| std::wstring keys = L"";
|
| for (unsigned int i = 0; i < keystroke_array.size(); ++i ) {
|
| std::string key(keystroke_array[i].asString());
|
| keys.append(CA2W(key.c_str(), CP_UTF8));
|
| }
|
| if (this->enable_native_events()) {
|
| HWND window_handle = browser_wrapper->GetWindowHandle();
|
| if (this->require_window_focus_) {
|
| LOG(DEBUG) << "Queueing Sendinput structures for sending keys";
|
| for (unsigned int char_index = 0; char_index < keys.size(); ++char_index) {
|
| wchar_t character = keys[char_index];
|
| this->AddKeyboardInput(window_handle, character);
|
| }
|
| } else {
|
| LOG(DEBUG) << "Using SendMessage method for sending keys";
|
| sendKeys(window_handle, keys.c_str(), 0);
|
| if (auto_release_modifier_keys) {
|
| releaseModifierKeys(window_handle, 0);
|
| }
|
| }
|
| } else {
|
| LOG(DEBUG) << "Using synthetic events for sending keys";
|
| std::wstring script_source = L"(function() { return function(){" +
|
| atoms::asString(atoms::INPUTS) +
|
| L"; return webdriver.atoms.inputs.sendKeys(arguments[0], arguments[1], arguments[2]);" +
|
| L"};})();";
|
|
|
| CComPtr<IHTMLDocument2> doc;
|
| browser_wrapper->GetDocument(&doc);
|
| Script script_wrapper(doc, script_source, 3);
|
|
|
| script_wrapper.AddNullArgument();
|
| script_wrapper.AddArgument(this->keyboard_state());
|
| script_wrapper.AddArgument(keys);
|
| status_code = script_wrapper.Execute();
|
| if (status_code == SUCCESS) {
|
| this->set_keyboard_state(script_wrapper.result());
|
| } else { |
| LOG(WARN) << "Unable to execute js to send keystrokes"; |
| }
|
| }
|
| return status_code; |
| }
|
|
|
| void InputManager::GetNormalizedCoordinates(HWND window_handle, int x, int y, int* normalized_x, int* normalized_y) {
|
| LOG(TRACE) << "Entering InputManager::GetNormalizedCoordinates";
|
| POINT cursor_position;
|
| cursor_position.x = x;
|
| cursor_position.y = y;
|
| ::ClientToScreen(window_handle, &cursor_position);
|
|
|
| int screen_width = ::GetSystemMetrics(SM_CXSCREEN) - 1;
|
| int screen_height = ::GetSystemMetrics(SM_CYSCREEN) - 1;
|
| *normalized_x = static_cast<int>(cursor_position.x * (65535.0f / screen_width));
|
| *normalized_y = static_cast<int>(cursor_position.y * (65535.0f / screen_height));
|
| }
|
|
|
| void InputManager::AddMouseInput(HWND window_handle, long input_action, int x, int y) {
|
| LOG(TRACE) << "Entering InputManager::AddMouseInput";
|
| int normalized_x = 0, normalized_y = 0;
|
| this->GetNormalizedCoordinates(window_handle,
|
| x,
|
| y,
|
| &normalized_x,
|
| &normalized_y);
|
| INPUT mouse_input;
|
| mouse_input.type = INPUT_MOUSE;
|
| mouse_input.mi.dwFlags = input_action | MOUSEEVENTF_ABSOLUTE;
|
| mouse_input.mi.dx = normalized_x;
|
| mouse_input.mi.dy = normalized_y;
|
| mouse_input.mi.dwExtraInfo = 0;
|
| mouse_input.mi.mouseData = 0;
|
| mouse_input.mi.time = 0;
|
| this->inputs_.push_back(mouse_input);
|
| }
|
|
|
| void InputManager::AddKeyboardInput(HWND window_handle, wchar_t character) {
|
| LOG(TRACE) << "Entering InputManager::AddKeyboardInput";
|
| if (character == WD_KEY_SHIFT || character == WD_KEY_CONTROL || character == WD_KEY_ALT || character == WD_KEY_NULL) {
|
| if (character == WD_KEY_SHIFT || (character == WD_KEY_NULL && this->is_shift_pressed_)) {
|
| INPUT shift_input;
|
| shift_input.type = INPUT_KEYBOARD;
|
| shift_input.ki.wVk = VK_SHIFT;
|
| shift_input.ki.dwFlags = 0;
|
| shift_input.ki.wScan = 0;
|
| shift_input.ki.dwExtraInfo = 0;
|
| shift_input.ki.time = 0;
|
| if (this->is_shift_pressed_) {
|
| shift_input.ki.dwFlags |= KEYEVENTF_KEYUP;
|
| this->is_shift_pressed_ = false;
|
| } else {
|
| this->is_shift_pressed_ = true;
|
| }
|
| this->inputs_.push_back(shift_input);
|
| }
|
|
|
| if (character == WD_KEY_CONTROL || (character == WD_KEY_NULL && this->is_control_pressed_)) {
|
| INPUT control_input;
|
| control_input.type = INPUT_KEYBOARD;
|
| control_input.ki.wVk = VK_CONTROL;
|
| control_input.ki.dwFlags = 0;
|
| control_input.ki.wScan = 0;
|
| control_input.ki.dwExtraInfo = 0;
|
| control_input.ki.time = 0;
|
| if (this->is_control_pressed_) {
|
| control_input.ki.dwFlags |= KEYEVENTF_KEYUP;
|
| this->is_control_pressed_ = false;
|
| } else {
|
| this->is_control_pressed_ = true;
|
| }
|
| this->inputs_.push_back(control_input);
|
| }
|
|
|
| if (character == WD_KEY_ALT || (character == WD_KEY_NULL && this->is_alt_pressed_)) {
|
| INPUT alt_input;
|
| alt_input.type = INPUT_KEYBOARD;
|
| alt_input.ki.wVk = VK_MENU;
|
| alt_input.ki.dwFlags = 0;
|
| alt_input.ki.wScan = 0;
|
| alt_input.ki.dwExtraInfo = 0;
|
| alt_input.ki.time = 0;
|
| if (this->is_alt_pressed_) {
|
| alt_input.ki.dwFlags |= KEYEVENTF_KEYUP;
|
| this->is_alt_pressed_ = false;
|
| } else {
|
| this->is_alt_pressed_ = true;
|
| }
|
| this->inputs_.push_back(alt_input);
|
| }
|
| return;
|
| }
|
|
|
| int flag = 0;
|
| DWORD process_id = 0;
|
| DWORD thread_id = ::GetWindowThreadProcessId(window_handle, &process_id);
|
| HKL layout = ::GetKeyboardLayout(thread_id);
|
| UINT scan_code = 0;
|
| WORD key_code = 0;
|
| bool extended = false;
|
| if (character == WD_KEY_CANCEL) { // ^break
|
| key_code = VK_CANCEL;
|
| scan_code = VK_CANCEL;
|
| extended = true;
|
| } else if (character == WD_KEY_HELP) { // help
|
| key_code = VK_HELP;
|
| scan_code = VK_HELP;
|
| } else if (character == WD_KEY_BACKSPACE) { // back space
|
| key_code = VK_BACK;
|
| scan_code = VK_BACK;
|
| } else if (character == WD_KEY_TAB) { // tab
|
| key_code = VK_TAB;
|
| scan_code = VK_TAB;
|
| } else if (character == WD_KEY_CLEAR) { // clear
|
| key_code = VK_CLEAR;
|
| scan_code = VK_CLEAR;
|
| } else if (character == WD_KEY_RETURN) { // return
|
| key_code = VK_RETURN;
|
| scan_code = VK_RETURN;
|
| } else if (character == WD_KEY_ENTER) { // enter
|
| key_code = VK_RETURN;
|
| scan_code = VK_RETURN;
|
| } else if (character == WD_KEY_PAUSE) { // pause
|
| key_code = VK_PAUSE;
|
| scan_code = VK_PAUSE;
|
| extended = true;
|
| } else if (character == WD_KEY_ESCAPE) { // escape
|
| key_code = VK_ESCAPE;
|
| scan_code = VK_ESCAPE;
|
| } else if (character == WD_KEY_SPACE) { // space
|
| key_code = VK_SPACE;
|
| scan_code = VK_SPACE;
|
| } else if (character == WD_KEY_PAGEUP) { // page up
|
| key_code = VK_PRIOR;
|
| scan_code = VK_PRIOR;
|
| extended = true;
|
| } else if (character == WD_KEY_PAGEDOWN) { // page down
|
| key_code = VK_NEXT;
|
| scan_code = VK_NEXT;
|
| extended = true;
|
| } else if (character == WD_KEY_END) { // end
|
| key_code = VK_END;
|
| scan_code = VK_END;
|
| extended = true;
|
| } else if (character == WD_KEY_HOME) { // home
|
| key_code = VK_HOME;
|
| scan_code = VK_HOME;
|
| extended = true;
|
| } else if (character == WD_KEY_LEFT) { // left arrow
|
| key_code = VK_LEFT;
|
| scan_code = VK_LEFT;
|
| extended = true;
|
| } else if (character == WD_KEY_UP) { // up arrow
|
| key_code = VK_UP;
|
| scan_code = VK_UP;
|
| extended = true;
|
| } else if (character == WD_KEY_RIGHT) { // right arrow
|
| key_code = VK_RIGHT;
|
| scan_code = VK_RIGHT;
|
| extended = true;
|
| } else if (character == WD_KEY_DOWN) { // down arrow
|
| key_code = VK_DOWN;
|
| scan_code = VK_DOWN;
|
| extended = true;
|
| } else if (character == WD_KEY_INSERT) { // insert
|
| key_code = VK_INSERT;
|
| scan_code = VK_INSERT;
|
| extended = true;
|
| } else if (character == WD_KEY_DELETE) { // delete
|
| key_code = VK_DELETE;
|
| scan_code = VK_DELETE;
|
| extended = true;
|
| } else if (character == WD_KEY_SEMICOLON) { // semicolon
|
| key_code = VkKeyScanExW(L';', layout);
|
| scan_code = MapVirtualKeyExW(LOBYTE(key_code), 0, layout);
|
| } else if (character == WD_KEY_EQUALS) { // equals
|
| key_code = VkKeyScanExW(L'=', layout);
|
| scan_code = MapVirtualKeyExW(LOBYTE(key_code), 0, layout);
|
| } else if (character == WD_KEY_NUMPAD0) { // numpad0
|
| key_code = VK_NUMPAD0;
|
| scan_code = VK_NUMPAD0;
|
| extended = true;
|
| } else if (character == WD_KEY_NUMPAD1) { // numpad1
|
| key_code = VK_NUMPAD1;
|
| scan_code = VK_NUMPAD1;
|
| extended = true;
|
| } else if (character == WD_KEY_NUMPAD2) { // numpad2
|
| key_code = VK_NUMPAD2;
|
| scan_code = VK_NUMPAD2;
|
| extended = true;
|
| } else if (character == WD_KEY_NUMPAD3) { // numpad3
|
| key_code = VK_NUMPAD3;
|
| scan_code = VK_NUMPAD3;
|
| extended = true;
|
| } else if (character == WD_KEY_NUMPAD4) { // numpad4
|
| key_code = VK_NUMPAD4;
|
| scan_code = VK_NUMPAD4;
|
| extended = true;
|
| } else if (character == WD_KEY_NUMPAD5) { // numpad5
|
| key_code = VK_NUMPAD5;
|
| scan_code = VK_NUMPAD5;
|
| extended = true;
|
| } else if (character == WD_KEY_NUMPAD6) { // numpad6
|
| key_code = VK_NUMPAD6;
|
| scan_code = VK_NUMPAD6;
|
| extended = true;
|
| } else if (character == WD_KEY_NUMPAD7) { // numpad7
|
| key_code = VK_NUMPAD7;
|
| scan_code = VK_NUMPAD7;
|
| extended = true;
|
| } else if (character == WD_KEY_NUMPAD8) { // numpad8
|
| key_code = VK_NUMPAD8;
|
| scan_code = VK_NUMPAD8;
|
| extended = true;
|
| } else if (character == WD_KEY_NUMPAD9) { // numpad9
|
| key_code = VK_NUMPAD9;
|
| scan_code = VK_NUMPAD9;
|
| extended = true;
|
| } else if (character == WD_KEY_MULTIPLY) { // multiply
|
| key_code = VK_MULTIPLY;
|
| scan_code = VK_MULTIPLY;
|
| extended = true;
|
| } else if (character == WD_KEY_ADD) { // add
|
| key_code = VK_ADD;
|
| scan_code = VK_ADD;
|
| extended = true;
|
| } else if (character == WD_KEY_SEPARATOR) { // separator
|
| key_code = VkKeyScanExW(L',', layout);
|
| scan_code = MapVirtualKeyExW(LOBYTE(key_code), 0, layout);
|
| } else if (character == WD_KEY_SUBTRACT) { // subtract
|
| key_code = VK_SUBTRACT;
|
| scan_code = VK_SUBTRACT;
|
| extended = true;
|
| } else if (character == WD_KEY_DECIMAL) { // decimal
|
| key_code = VK_DECIMAL;
|
| scan_code = VK_DECIMAL;
|
| extended = true;
|
| } else if (character == WD_KEY_DIVIDE) { // divide
|
| key_code = VK_DIVIDE;
|
| scan_code = VK_DIVIDE;
|
| extended = true;
|
| } else if (character == WD_KEY_F1) { // F1
|
| key_code = VK_F1;
|
| scan_code = VK_F1;
|
| } else if (character == WD_KEY_F2) { // F2
|
| key_code = VK_F2;
|
| scan_code = VK_F2;
|
| } else if (character == WD_KEY_F3) { // F3
|
| key_code = VK_F3;
|
| scan_code = VK_F3;
|
| } else if (character == WD_KEY_F4) { // F4
|
| key_code = VK_F4;
|
| scan_code = VK_F4;
|
| } else if (character == WD_KEY_F5) { // F5
|
| key_code = VK_F5;
|
| scan_code = VK_F5;
|
| } else if (character == WD_KEY_F6) { // F6
|
| key_code = VK_F6;
|
| scan_code = VK_F6;
|
| } else if (character == WD_KEY_F7) { // F7
|
| key_code = VK_F7;
|
| scan_code = VK_F7;
|
| } else if (character == WD_KEY_F8) { // F8
|
| key_code = VK_F8;
|
| scan_code = VK_F8;
|
| } else if (character == WD_KEY_F9) { // F9
|
| key_code = VK_F9;
|
| scan_code = VK_F9;
|
| } else if (character == WD_KEY_F10) { // F10
|
| key_code = VK_F10;
|
| scan_code = VK_F10;
|
| } else if (character == WD_KEY_F11) { // F11
|
| key_code = VK_F11;
|
| scan_code = VK_F11;
|
| } else if (character == WD_KEY_F12) { // F12
|
| key_code = VK_F12;
|
| scan_code = VK_F12;
|
| } else if (character == L'\n') { // line feed
|
| key_code = VK_RETURN;
|
| scan_code = VK_RETURN;
|
| } else if (character == L'\r') { // carriage return
|
| // skip it
|
| } else {
|
| key_code = VkKeyScanExW(character, layout);
|
| scan_code = MapVirtualKeyExW(LOBYTE(key_code), 0, layout);
|
| if (!scan_code || (key_code == 0xFFFFU)) {
|
| LOG(WARN) << "No translation for key. Assuming unicode input: " << character;
|
| INPUT unicode_down;
|
| unicode_down.type = INPUT_KEYBOARD;
|
| unicode_down.ki.dwFlags = KEYEVENTF_UNICODE;
|
| unicode_down.ki.wVk = 0;
|
| unicode_down.ki.wScan = static_cast<int>(character);
|
| unicode_down.ki.dwExtraInfo = 0;
|
| unicode_down.ki.time = 0;
|
| this->inputs_.push_back(unicode_down);
|
|
|
| INPUT unicode_up;
|
| unicode_up.type = INPUT_KEYBOARD;
|
| unicode_up.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
|
| unicode_down.ki.wVk = 0;
|
| unicode_up.ki.wScan = static_cast<int>(character);
|
| unicode_up.ki.dwExtraInfo = 0;
|
| unicode_up.ki.time = 0;
|
| this->inputs_.push_back(unicode_up);
|
| return;
|
| }
|
| }
|
|
|
| INPUT key_down;
|
| INPUT key_up;
|
| if (HIBYTE(key_code) == 1 && !this->is_shift_pressed_) {
|
| INPUT shift_down;
|
| shift_down.type = INPUT_KEYBOARD;
|
| shift_down.ki.dwFlags = 0;
|
| shift_down.ki.wScan = 0;
|
| shift_down.ki.wVk = VK_SHIFT;
|
| shift_down.ki.dwExtraInfo = 0;
|
| shift_down.ki.time = 0;
|
| this->inputs_.push_back(shift_down);
|
|
|
| key_down.type = INPUT_KEYBOARD;
|
| key_down.ki.dwFlags = KEYEVENTF_SCANCODE;
|
| if (extended) {
|
| key_down.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
| }
|
| key_down.ki.wScan = scan_code;
|
| key_down.ki.wVk = 0;
|
| key_down.ki.dwExtraInfo = 0;
|
| key_down.ki.time = 0;
|
| this->inputs_.push_back(key_down);
|
|
|
| key_up.type = INPUT_KEYBOARD;
|
| key_up.ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_SCANCODE;
|
| if (extended) {
|
| key_up.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
| }
|
| key_up.ki.wScan = scan_code;
|
| key_up.ki.wVk = 0;
|
| key_up.ki.dwExtraInfo = 0;
|
| key_up.ki.time = 0;
|
| this->inputs_.push_back(key_up);
|
|
|
| INPUT shift_up;
|
| shift_up.type = INPUT_KEYBOARD;
|
| shift_up.ki.dwFlags = KEYEVENTF_KEYUP;
|
| shift_up.ki.wScan = 0;
|
| shift_up.ki.wVk = VK_SHIFT;
|
| shift_up.ki.dwExtraInfo = 0;
|
| shift_up.ki.time = 0;
|
| this->inputs_.push_back(shift_up);
|
| } else {
|
| key_down.type = INPUT_KEYBOARD;
|
| key_down.ki.dwFlags = 0;
|
| if (extended) {
|
| key_down.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
| }
|
| key_down.ki.wScan = 0;
|
| key_down.ki.wVk = key_code;
|
| key_down.ki.dwExtraInfo = 0;
|
| key_down.ki.time = 0;
|
| this->inputs_.push_back(key_down);
|
|
|
| key_up.type = INPUT_KEYBOARD;
|
| key_up.ki.dwFlags = KEYEVENTF_KEYUP;
|
| if (extended) {
|
| key_up.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
| }
|
| key_up.ki.wScan = 0;
|
| key_up.ki.wVk = key_code;
|
| key_up.ki.dwExtraInfo = 0;
|
| key_up.ki.time = 0;
|
| this->inputs_.push_back(key_up);
|
| }
|
| }
|
|
|
| } // namespace webdriver |