2013-01-11 22:18:32 +01:00
|
|
|
// 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")) {
|
2013-01-28 13:39:17 -05:00
|
|
|
Json::Value keystroke_array = action.get("value", Json::Value(Json::arrayValue));
|
|
|
|
|
bool auto_release_modifiers = action.get("releaseModifiers", false).asBool();
|
|
|
|
|
this->SendKeystrokes(browser_wrapper, keystroke_array, auto_release_modifiers);
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
2013-01-16 14:37:30 -05:00
|
|
|
return WD_SUCCESS;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2013-04-11 11:56:51 -04:00
|
|
|
::SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, 0, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
|
2013-01-11 22:18:32 +01:00
|
|
|
::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();
|
2013-01-16 14:37:30 -05:00
|
|
|
if (status_code == WD_SUCCESS) {
|
2013-01-11 22:18:32 +01:00
|
|
|
this->mouse_state_ = script_wrapper.result();
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "Unable to execute js to perform mouse click";
|
|
|
|
|
return status_code;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-01-16 14:37:30 -05:00
|
|
|
return WD_SUCCESS;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
2013-01-16 14:37:30 -05:00
|
|
|
if (status_code == WD_SUCCESS) {
|
2013-01-11 22:18:32 +01:00
|
|
|
this->mouse_state_ = script_wrapper.result();
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "Unable to execute js to perform mouse button down";
|
|
|
|
|
return status_code;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-01-16 14:37:30 -05:00
|
|
|
return WD_SUCCESS;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
2013-01-16 14:37:30 -05:00
|
|
|
if (status_code == WD_SUCCESS) {
|
2013-01-11 22:18:32 +01:00
|
|
|
this->mouse_state_ = script_wrapper.result();
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "Unable to execute js to perform mouse button up";
|
|
|
|
|
return status_code;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-01-16 14:37:30 -05:00
|
|
|
return WD_SUCCESS;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
2013-01-16 14:37:30 -05:00
|
|
|
if (status_code == WD_SUCCESS) {
|
2013-01-11 22:18:32 +01:00
|
|
|
this->mouse_state_ = script_wrapper.result();
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "Unable to execute js to double click";
|
|
|
|
|
return status_code;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-01-16 14:37:30 -05:00
|
|
|
return WD_SUCCESS;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int InputManager::MouseMoveTo(BrowserHandle browser_wrapper, std::string element_id, bool offset_specified, int x_offset, int y_offset) {
|
|
|
|
|
LOG(TRACE) << "Entering InputManager::MouseMoveTo";
|
2013-01-16 14:37:30 -05:00
|
|
|
int status_code = WD_SUCCESS;
|
2013-01-08 17:10:01 +00:00
|
|
|
bool element_specified = element_id.size() != 0;
|
|
|
|
|
ElementHandle target_element;
|
|
|
|
|
if (element_specified) {
|
|
|
|
|
status_code = this->element_map_->GetManagedElement(element_id, &target_element);
|
2013-01-16 14:37:30 -05:00
|
|
|
if (status_code != WD_SUCCESS) {
|
2013-01-08 17:10:01 +00:00
|
|
|
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) {
|
2013-02-05 13:20:22 -05:00
|
|
|
bool displayed;
|
|
|
|
|
status_code = target_element->IsDisplayed(&displayed);
|
|
|
|
|
if (status_code != WD_SUCCESS) {
|
|
|
|
|
LOG(WARN) << "Unable to determine element is displayed";
|
|
|
|
|
return status_code;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!displayed) {
|
|
|
|
|
LOG(WARN) << "Element is not displayed";
|
|
|
|
|
return EELEMENTNOTDISPLAYED;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-08 17:10:01 +00:00
|
|
|
LocationInfo element_location;
|
2013-04-10 03:55:39 +04:00
|
|
|
std::vector<LocationInfo> frame_locations;
|
2013-01-08 17:10:01 +00:00
|
|
|
status_code = target_element->GetLocationOnceScrolledIntoView(this->scroll_behavior_,
|
2013-04-10 03:55:39 +04:00
|
|
|
&element_location,
|
|
|
|
|
&frame_locations);
|
2013-01-08 17:10:01 +00:00
|
|
|
// 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.
|
2013-01-16 14:37:30 -05:00
|
|
|
if (status_code != WD_SUCCESS && element_location.width == 0 && element_location.height == 0) {
|
2013-01-08 17:10:01 +00:00
|
|
|
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_) {
|
2013-01-11 22:18:32 +01:00
|
|
|
LOG(DEBUG) << "Queueing SendInput structure for mouse move";
|
|
|
|
|
this->AddMouseInput(browser_window_handle, MOUSEEVENTF_MOVE, end_x, end_y);
|
2013-01-08 17:10:01 +00:00
|
|
|
} else {
|
2013-01-11 22:18:32 +01:00
|
|
|
LOG(DEBUG) << "Using SendMessage method for mouse move";
|
2013-01-08 17:10:01 +00:00
|
|
|
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.
|
2013-01-11 22:18:32 +01:00
|
|
|
LOG(DEBUG) << "Using synthetic events for mouse move";
|
2013-01-08 17:10:01 +00:00
|
|
|
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();
|
2013-01-16 14:37:30 -05:00
|
|
|
if (status_code == WD_SUCCESS) {
|
2013-01-08 17:10:01 +00:00
|
|
|
this->mouse_state_ = script_wrapper.result();
|
|
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "Unable to execute js to mouse move";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return status_code;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int InputManager::SendKeystrokes(BrowserHandle browser_wrapper, Json::Value keystroke_array, bool auto_release_modifier_keys) {
|
|
|
|
|
LOG(TRACE) << "Entering InputManager::SendKeystrokes";
|
2013-01-16 14:37:30 -05:00
|
|
|
int status_code = WD_SUCCESS;
|
2013-01-11 22:18:32 +01:00
|
|
|
std::wstring keys = L"";
|
|
|
|
|
for (unsigned int i = 0; i < keystroke_array.size(); ++i ) {
|
|
|
|
|
std::string key(keystroke_array[i].asString());
|
2013-03-21 13:58:47 -04:00
|
|
|
keys.append(StringUtilities::ToWString(key));
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
}
|
2013-02-07 12:51:49 -05:00
|
|
|
if (auto_release_modifier_keys) {
|
|
|
|
|
this->AddKeyboardInput(window_handle, WD_KEY_NULL);
|
|
|
|
|
}
|
2013-01-11 22:18:32 +01:00
|
|
|
} 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";
|
2013-01-26 13:15:47 -08:00
|
|
|
std::wstring script_source =
|
|
|
|
|
L"(function() { return function(){" +
|
|
|
|
|
atoms::asString(atoms::INPUTS) +
|
|
|
|
|
L"; return webdriver.atoms.inputs.sendKeys(" +
|
|
|
|
|
L"arguments[0], arguments[1], arguments[2], arguments[3]);" +
|
|
|
|
|
L"};})();";
|
|
|
|
|
bool persist_modifier_keys = !auto_release_modifier_keys;
|
2013-01-11 22:18:32 +01:00
|
|
|
|
|
|
|
|
CComPtr<IHTMLDocument2> doc;
|
|
|
|
|
browser_wrapper->GetDocument(&doc);
|
2013-04-15 15:45:26 -04:00
|
|
|
Script script_wrapper(doc, script_source, 4);
|
2013-01-11 22:18:32 +01:00
|
|
|
|
|
|
|
|
script_wrapper.AddNullArgument();
|
|
|
|
|
script_wrapper.AddArgument(keys);
|
2013-01-26 13:15:47 -08:00
|
|
|
script_wrapper.AddArgument(this->keyboard_state());
|
|
|
|
|
script_wrapper.AddArgument(persist_modifier_keys);
|
2013-01-11 22:18:32 +01:00
|
|
|
status_code = script_wrapper.Execute();
|
2013-01-16 14:37:30 -05:00
|
|
|
if (status_code == WD_SUCCESS) {
|
2013-01-11 22:18:32 +01:00
|
|
|
this->set_keyboard_state(script_wrapper.result());
|
2013-01-08 17:10:01 +00:00
|
|
|
} else {
|
|
|
|
|
LOG(WARN) << "Unable to execute js to send keystrokes";
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
2013-01-08 17:10:01 +00:00
|
|
|
return status_code;
|
2013-01-11 22:18:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2013-04-11 11:56:51 -04:00
|
|
|
UINT scan_code = 0;
|
|
|
|
|
WORD key_code = 0;
|
2013-01-11 22:18:32 +01:00
|
|
|
bool extended = false;
|
2013-04-11 11:56:51 -04:00
|
|
|
if (character == WD_KEY_CANCEL) { // ^break
|
|
|
|
|
key_code = VK_CANCEL;
|
|
|
|
|
scan_code = VK_CANCEL;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} 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;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} 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;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_PAGEDOWN) { // page down
|
|
|
|
|
key_code = VK_NEXT;
|
|
|
|
|
scan_code = VK_NEXT;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_END) { // end
|
|
|
|
|
key_code = VK_END;
|
|
|
|
|
scan_code = VK_END;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_HOME) { // home
|
|
|
|
|
key_code = VK_HOME;
|
|
|
|
|
scan_code = VK_HOME;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_LEFT) { // left arrow
|
|
|
|
|
key_code = VK_LEFT;
|
|
|
|
|
scan_code = VK_LEFT;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_UP) { // up arrow
|
|
|
|
|
key_code = VK_UP;
|
|
|
|
|
scan_code = VK_UP;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_RIGHT) { // right arrow
|
|
|
|
|
key_code = VK_RIGHT;
|
|
|
|
|
scan_code = VK_RIGHT;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_DOWN) { // down arrow
|
|
|
|
|
key_code = VK_DOWN;
|
|
|
|
|
scan_code = VK_DOWN;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_INSERT) { // insert
|
|
|
|
|
key_code = VK_INSERT;
|
|
|
|
|
scan_code = VK_INSERT;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_DELETE) { // delete
|
|
|
|
|
key_code = VK_DELETE;
|
|
|
|
|
scan_code = VK_DELETE;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} 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;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_NUMPAD1) { // numpad1
|
|
|
|
|
key_code = VK_NUMPAD1;
|
|
|
|
|
scan_code = VK_NUMPAD1;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_NUMPAD2) { // numpad2
|
|
|
|
|
key_code = VK_NUMPAD2;
|
|
|
|
|
scan_code = VK_NUMPAD2;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_NUMPAD3) { // numpad3
|
|
|
|
|
key_code = VK_NUMPAD3;
|
|
|
|
|
scan_code = VK_NUMPAD3;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_NUMPAD4) { // numpad4
|
|
|
|
|
key_code = VK_NUMPAD4;
|
|
|
|
|
scan_code = VK_NUMPAD4;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_NUMPAD5) { // numpad5
|
|
|
|
|
key_code = VK_NUMPAD5;
|
|
|
|
|
scan_code = VK_NUMPAD5;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_NUMPAD6) { // numpad6
|
|
|
|
|
key_code = VK_NUMPAD6;
|
|
|
|
|
scan_code = VK_NUMPAD6;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_NUMPAD7) { // numpad7
|
|
|
|
|
key_code = VK_NUMPAD7;
|
|
|
|
|
scan_code = VK_NUMPAD7;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_NUMPAD8) { // numpad8
|
|
|
|
|
key_code = VK_NUMPAD8;
|
|
|
|
|
scan_code = VK_NUMPAD8;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_NUMPAD9) { // numpad9
|
|
|
|
|
key_code = VK_NUMPAD9;
|
|
|
|
|
scan_code = VK_NUMPAD9;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_MULTIPLY) { // multiply
|
|
|
|
|
key_code = VK_MULTIPLY;
|
|
|
|
|
scan_code = VK_MULTIPLY;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_ADD) { // add
|
|
|
|
|
key_code = VK_ADD;
|
|
|
|
|
scan_code = VK_ADD;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} 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;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} else if (character == WD_KEY_DECIMAL) { // decimal
|
|
|
|
|
key_code = VK_DECIMAL;
|
|
|
|
|
scan_code = VK_DECIMAL;
|
2013-01-11 22:18:32 +01:00
|
|
|
extended = true;
|
2013-04-11 11:56:51 -04:00
|
|
|
} 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;
|
2013-01-11 22:18:32 +01:00
|
|
|
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;
|
2013-04-11 11:56:51 -04:00
|
|
|
}
|
|
|
|
|
}
|
2013-01-11 22:18:32 +01:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-08 17:10:01 +00:00
|
|
|
} // namespace webdriver
|