Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/cpp/iedriver/ActionSimulators/SendInputActionSimulator.cpp
2868 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
17
#include "SendInputActionSimulator.h"
18
19
#include "errorcodes.h"
20
#include "logging.h"
21
22
#include "../DocumentHost.h"
23
#include "../HookProcessor.h"
24
#include "../messages.h"
25
26
#define WAIT_TIME_IN_MILLISECONDS_PER_INPUT_EVENT 100
27
28
namespace webdriver {
29
30
31
SendInputActionSimulator::SendInputActionSimulator() {
32
}
33
34
SendInputActionSimulator::~SendInputActionSimulator() {
35
}
36
37
int SendInputActionSimulator::SimulateActions(BrowserHandle browser_wrapper,
38
std::vector<INPUT> inputs,
39
InputState* input_state) {
40
LOG(TRACE) << "Entering SendInputActionSimulator::SimulateActions";
41
// SendInput simulates mouse and keyboard events at a very low level, so
42
// low that there is no guarantee that IE will have processed the resulting
43
// windows messages before this method returns. Therefore, we'll install
44
// keyboard and mouse hooks that will count the number of Windows messages
45
// processed by any application the system. There is a potential for this
46
// code to be wrong if the user is interacting with the system via mouse and
47
// keyboard during this process. Since this code path should only be hit if
48
// the requireWindowFocus capability is turned on, and since SendInput is
49
// documented to not allow other input events to be interspersed into the
50
// input queue, the risk is hopefully minimized.
51
HookProcessor keyboard_hook;
52
keyboard_hook.Initialize("KeyboardHookProc", WH_KEYBOARD);
53
54
HookProcessor mouse_hook;
55
mouse_hook.Initialize("MouseHookProc", WH_MOUSE);
56
57
bool is_button_swapped = ::GetSystemMetrics(SM_SWAPBUTTON) != 0;
58
59
HWND window_handle = browser_wrapper->GetContentWindowHandle();
60
// Loop through all of the input items, and find all of the sleeps.
61
std::vector<size_t> sleep_indexes;
62
for (size_t i = 0; i < inputs.size(); ++i) {
63
INPUT current_input = inputs[i];
64
this->UpdateInputState(current_input, input_state);
65
if (current_input.type == INPUT_HARDWARE && current_input.hi.uMsg > 0) {
66
sleep_indexes.push_back(i);
67
} else if (current_input.type == INPUT_MOUSE) {
68
// We use the INPUT structure to store absolute pixel
69
// coordinates for the SendMessage case, but SendInput
70
// requires normalized coordinates.
71
int normalized_x = 0, normalized_y = 0;
72
this->GetNormalizedCoordinates(window_handle,
73
current_input.mi.dx,
74
current_input.mi.dy,
75
&normalized_x,
76
&normalized_y);
77
current_input.mi.dx = normalized_x;
78
current_input.mi.dy = normalized_y;
79
80
// If the buttons are swapped on the mouse (most often referred to
81
// as "left-handed"), where the right button is primary and the
82
// left button is secondary, we need to swap those when using
83
// SendInput.
84
unsigned long normalized_flags = this->NormalizeButtons(is_button_swapped,
85
current_input.mi.dwFlags);
86
87
current_input.mi.dwFlags = normalized_flags;
88
inputs[i] = current_input;
89
}
90
}
91
92
// Send all inputs between sleeps, sleeping in between.
93
size_t next_input_index = 0;
94
std::vector<size_t>::const_iterator it = sleep_indexes.begin();
95
for (; it != sleep_indexes.end(); ++it) {
96
size_t sleep_input_index = *it;
97
INPUT sleep_input = inputs[sleep_input_index];
98
size_t number_of_inputs = sleep_input_index - next_input_index;
99
if (number_of_inputs > 0) {
100
this->SendInputToBrowser(browser_wrapper,
101
inputs,
102
static_cast<int>(next_input_index),
103
static_cast<int>(number_of_inputs));
104
}
105
LOG(DEBUG) << "Processing pause event";
106
::Sleep(inputs[sleep_input_index].hi.uMsg);
107
next_input_index = sleep_input_index + 1;
108
}
109
// Now send any inputs after the last sleep, if any.
110
size_t last_inputs = inputs.size() - next_input_index;
111
if (last_inputs > 0) {
112
this->SendInputToBrowser(browser_wrapper,
113
inputs,
114
static_cast<int>(next_input_index),
115
static_cast<int>(last_inputs));
116
}
117
118
// We're done here, so uninstall the hooks, and reset the buffer size.
119
keyboard_hook.Dispose();
120
mouse_hook.Dispose();
121
122
return WD_SUCCESS;
123
}
124
125
void SendInputActionSimulator::SendInputToBrowser(BrowserHandle browser_wrapper,
126
std::vector<INPUT> inputs,
127
int start_index,
128
int input_count) {
129
if (input_count > 0) {
130
bool focus_set = browser_wrapper->SetFocusToBrowser();
131
if (!focus_set) {
132
LOG(WARN) << "Focus not set to browser window";
133
}
134
HookProcessor::ResetEventCount();
135
int sent_inputs = 0;
136
for (int i = start_index; i < start_index + input_count; ++i) {
137
::SendInput(1, &inputs[i], sizeof(INPUT));
138
sent_inputs += 1;
139
}
140
this->WaitForInputEventProcessing(sent_inputs);
141
}
142
}
143
144
void SendInputActionSimulator::GetNormalizedCoordinates(HWND window_handle,
145
int x,
146
int y,
147
int* normalized_x,
148
int* normalized_y) {
149
LOG(TRACE) << "Entering InputManager::GetNormalizedCoordinates";
150
POINT cursor_position;
151
cursor_position.x = x;
152
cursor_position.y = y;
153
::ClientToScreen(window_handle, &cursor_position);
154
155
int screen_width = ::GetSystemMetrics(SM_CXSCREEN) - 1;
156
int screen_height = ::GetSystemMetrics(SM_CYSCREEN) - 1;
157
*normalized_x = static_cast<int>(cursor_position.x * (65535.0f / screen_width));
158
*normalized_y = static_cast<int>(cursor_position.y * (65535.0f / screen_height));
159
}
160
161
unsigned long SendInputActionSimulator::NormalizeButtons(bool is_button_swapped,
162
unsigned long input_flags) {
163
unsigned long flags = input_flags;
164
if (is_button_swapped) {
165
if (flags & MOUSEEVENTF_LEFTDOWN) {
166
flags &= ~(MOUSEEVENTF_LEFTDOWN);
167
flags |= MOUSEEVENTF_RIGHTDOWN;
168
}
169
else if (flags & MOUSEEVENTF_LEFTUP) {
170
flags &= ~(MOUSEEVENTF_LEFTUP);
171
flags |= MOUSEEVENTF_RIGHTUP;
172
}
173
else if (flags & MOUSEEVENTF_RIGHTDOWN) {
174
flags &= ~(MOUSEEVENTF_RIGHTDOWN);
175
flags |= MOUSEEVENTF_LEFTDOWN;
176
}
177
else if (flags & MOUSEEVENTF_RIGHTUP) {
178
flags &= ~(MOUSEEVENTF_RIGHTUP);
179
flags |= MOUSEEVENTF_LEFTUP;
180
}
181
}
182
return flags;
183
}
184
185
bool SendInputActionSimulator::WaitForInputEventProcessing(int input_count) {
186
LOG(TRACE) << "Entering InputManager::WaitForInputEventProcessing";
187
// Adaptive wait. The total wait time is the number of input messages
188
// expected by the hook multiplied by a static wait time for each
189
// message to be processed (currently 100 milliseconds). We should
190
// exit out of this loop once the number of processed windows keyboard
191
// or mouse messages processed by the system exceeds the number of
192
// input events created by the call to SendInput.
193
int total_timeout_in_milliseconds = input_count * WAIT_TIME_IN_MILLISECONDS_PER_INPUT_EVENT;
194
clock_t end = clock() + static_cast<clock_t>(((total_timeout_in_milliseconds / 1000.0) * CLOCKS_PER_SEC));
195
196
int processed_event_count = HookProcessor::GetEventCount();
197
bool inputs_processed = processed_event_count >= input_count;
198
while (!inputs_processed && clock() < end) {
199
// Sleep a short amount of time to prevent starving the processor.
200
::Sleep(25);
201
processed_event_count = HookProcessor::GetEventCount();
202
inputs_processed = processed_event_count >= input_count;
203
}
204
LOG(DEBUG) << "Requested waiting for " << input_count
205
<< " events, processed " << processed_event_count << " events,"
206
<< " timed out after " << total_timeout_in_milliseconds
207
<< " milliseconds";
208
return inputs_processed;
209
}
210
211
} // namespace webdriver
212
213
#ifdef __cplusplus
214
extern "C" {
215
#endif
216
217
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
218
if (nCode == HC_ACTION) {
219
webdriver::HookProcessor::IncrementEventCount(1);
220
}
221
return ::CallNextHookEx(NULL, nCode, wParam, lParam);
222
}
223
224
LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
225
if (nCode == HC_ACTION) {
226
webdriver::HookProcessor::IncrementEventCount(1);
227
}
228
return ::CallNextHookEx(NULL, nCode, wParam, lParam);
229
}
230
231
#ifdef __cplusplus
232
}
233
#endif
234
235