1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/automation/ui_controls.h"
6
7 #include "base/logging.h"
8 #include "base/memory/ref_counted.h"
9 #include "base/message_loop.h"
10 #include "base/task.h"
11 #include "ui/base/keycodes/keyboard_codes.h"
12 #include "ui/base/keycodes/keyboard_code_conversion_win.h"
13 #include "views/view.h"
14
15 namespace ui_controls {
16
17 namespace {
18
19 // InputDispatcher ------------------------------------------------------------
20
21 // InputDispatcher is used to listen for a mouse/keyboard event. When the
22 // appropriate event is received the task is notified.
23 class InputDispatcher : public base::RefCounted<InputDispatcher> {
24 public:
25 InputDispatcher(Task* task, WPARAM message_waiting_for);
26
27 // Invoked from the hook. If mouse_message matches message_waiting_for_
28 // MatchingMessageFound is invoked.
29 void DispatchedMessage(WPARAM mouse_message);
30
31 // Invoked when a matching event is found. Uninstalls the hook and schedules
32 // an event that notifies the task.
33 void MatchingMessageFound();
34
35 private:
36 friend class base::RefCounted<InputDispatcher>;
37
38 ~InputDispatcher();
39
40 // Notifies the task and release this (which should delete it).
41 void NotifyTask();
42
43 // The task we notify.
44 scoped_ptr<Task> task_;
45
46 // Message we're waiting for. Not used for keyboard events.
47 const WPARAM message_waiting_for_;
48
49 DISALLOW_COPY_AND_ASSIGN(InputDispatcher);
50 };
51
52 // Have we installed the hook?
53 bool installed_hook_ = false;
54
55 // Return value from SetWindowsHookEx.
56 HHOOK next_hook_ = NULL;
57
58 // If a hook is installed, this is the dispatcher.
59 InputDispatcher* current_dispatcher_ = NULL;
60
61 // Callback from hook when a mouse message is received.
MouseHook(int n_code,WPARAM w_param,LPARAM l_param)62 LRESULT CALLBACK MouseHook(int n_code, WPARAM w_param, LPARAM l_param) {
63 HHOOK next_hook = next_hook_;
64 if (n_code == HC_ACTION) {
65 DCHECK(current_dispatcher_);
66 current_dispatcher_->DispatchedMessage(w_param);
67 }
68 return CallNextHookEx(next_hook, n_code, w_param, l_param);
69 }
70
71 // Callback from hook when a key message is received.
KeyHook(int n_code,WPARAM w_param,LPARAM l_param)72 LRESULT CALLBACK KeyHook(int n_code, WPARAM w_param, LPARAM l_param) {
73 HHOOK next_hook = next_hook_;
74 if (n_code == HC_ACTION) {
75 DCHECK(current_dispatcher_);
76 if (l_param & (1 << 30)) {
77 // Only send on key up.
78 current_dispatcher_->MatchingMessageFound();
79 }
80 }
81 return CallNextHookEx(next_hook, n_code, w_param, l_param);
82 }
83
84 // Installs dispatcher as the current hook.
InstallHook(InputDispatcher * dispatcher,bool key_hook)85 void InstallHook(InputDispatcher* dispatcher, bool key_hook) {
86 DCHECK(!installed_hook_);
87 current_dispatcher_ = dispatcher;
88 installed_hook_ = true;
89 if (key_hook) {
90 next_hook_ = SetWindowsHookEx(WH_KEYBOARD, &KeyHook, NULL,
91 GetCurrentThreadId());
92 } else {
93 // NOTE: I originally tried WH_CALLWNDPROCRET, but for some reason I
94 // didn't get a mouse message like I do with MouseHook.
95 next_hook_ = SetWindowsHookEx(WH_MOUSE, &MouseHook, NULL,
96 GetCurrentThreadId());
97 }
98 DCHECK(next_hook_);
99 }
100
101 // Uninstalls the hook set in InstallHook.
UninstallHook(InputDispatcher * dispatcher)102 void UninstallHook(InputDispatcher* dispatcher) {
103 if (current_dispatcher_ == dispatcher) {
104 installed_hook_ = false;
105 current_dispatcher_ = NULL;
106 UnhookWindowsHookEx(next_hook_);
107 }
108 }
109
InputDispatcher(Task * task,UINT message_waiting_for)110 InputDispatcher::InputDispatcher(Task* task, UINT message_waiting_for)
111 : task_(task), message_waiting_for_(message_waiting_for) {
112 InstallHook(this, message_waiting_for == WM_KEYUP);
113 }
114
~InputDispatcher()115 InputDispatcher::~InputDispatcher() {
116 // Make sure the hook isn't installed.
117 UninstallHook(this);
118 }
119
DispatchedMessage(WPARAM message)120 void InputDispatcher::DispatchedMessage(WPARAM message) {
121 if (message == message_waiting_for_)
122 MatchingMessageFound();
123 }
124
MatchingMessageFound()125 void InputDispatcher::MatchingMessageFound() {
126 UninstallHook(this);
127 // At the time we're invoked the event has not actually been processed.
128 // Use PostTask to make sure the event has been processed before notifying.
129 MessageLoop::current()->PostDelayedTask(
130 FROM_HERE, NewRunnableMethod(this, &InputDispatcher::NotifyTask), 0);
131 }
132
NotifyTask()133 void InputDispatcher::NotifyTask() {
134 task_->Run();
135 Release();
136 }
137
138 // Private functions ----------------------------------------------------------
139
140 // Populate the INPUT structure with the appropriate keyboard event
141 // parameters required by SendInput
FillKeyboardInput(ui::KeyboardCode key,INPUT * input,bool key_up)142 bool FillKeyboardInput(ui::KeyboardCode key, INPUT* input, bool key_up) {
143 memset(input, 0, sizeof(INPUT));
144 input->type = INPUT_KEYBOARD;
145 input->ki.wVk = ui::WindowsKeyCodeForKeyboardCode(key);
146 input->ki.dwFlags = key_up ? KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP :
147 KEYEVENTF_EXTENDEDKEY;
148
149 return true;
150 }
151
152 // Send a key event (up/down)
SendKeyEvent(ui::KeyboardCode key,bool up)153 bool SendKeyEvent(ui::KeyboardCode key, bool up) {
154 INPUT input = { 0 };
155
156 if (!FillKeyboardInput(key, &input, up))
157 return false;
158
159 if (!::SendInput(1, &input, sizeof(INPUT)))
160 return false;
161
162 return true;
163 }
164
SendKeyPressImpl(ui::KeyboardCode key,bool control,bool shift,bool alt,Task * task)165 bool SendKeyPressImpl(ui::KeyboardCode key,
166 bool control, bool shift, bool alt,
167 Task* task) {
168 scoped_refptr<InputDispatcher> dispatcher(
169 task ? new InputDispatcher(task, WM_KEYUP) : NULL);
170
171 // If a pop-up menu is open, it won't receive events sent using SendInput.
172 // Check for a pop-up menu using its window class (#32768) and if one
173 // exists, send the key event directly there.
174 HWND popup_menu = ::FindWindow(L"#32768", 0);
175 if (popup_menu != NULL && popup_menu == ::GetTopWindow(NULL)) {
176 WPARAM w_param = ui::WindowsKeyCodeForKeyboardCode(key);
177 LPARAM l_param = 0;
178 ::SendMessage(popup_menu, WM_KEYDOWN, w_param, l_param);
179 ::SendMessage(popup_menu, WM_KEYUP, w_param, l_param);
180
181 if (dispatcher.get())
182 dispatcher->AddRef();
183 return true;
184 }
185
186 INPUT input[8] = { 0 }; // 8, assuming all the modifiers are activated.
187
188 UINT i = 0;
189 if (control) {
190 if (!FillKeyboardInput(ui::VKEY_CONTROL, &input[i], false))
191 return false;
192 i++;
193 }
194
195 if (shift) {
196 if (!FillKeyboardInput(ui::VKEY_SHIFT, &input[i], false))
197 return false;
198 i++;
199 }
200
201 if (alt) {
202 if (!FillKeyboardInput(ui::VKEY_MENU, &input[i], false))
203 return false;
204 i++;
205 }
206
207 if (!FillKeyboardInput(key, &input[i], false))
208 return false;
209 i++;
210
211 if (!FillKeyboardInput(key, &input[i], true))
212 return false;
213 i++;
214
215 if (alt) {
216 if (!FillKeyboardInput(ui::VKEY_MENU, &input[i], true))
217 return false;
218 i++;
219 }
220
221 if (shift) {
222 if (!FillKeyboardInput(ui::VKEY_SHIFT, &input[i], true))
223 return false;
224 i++;
225 }
226
227 if (control) {
228 if (!FillKeyboardInput(ui::VKEY_CONTROL, &input[i], true))
229 return false;
230 i++;
231 }
232
233 if (::SendInput(i, input, sizeof(INPUT)) != i)
234 return false;
235
236 if (dispatcher.get())
237 dispatcher->AddRef();
238
239 return true;
240 }
241
SendMouseMoveImpl(long x,long y,Task * task)242 bool SendMouseMoveImpl(long x, long y, Task* task) {
243 // First check if the mouse is already there.
244 POINT current_pos;
245 ::GetCursorPos(¤t_pos);
246 if (x == current_pos.x && y == current_pos.y) {
247 if (task)
248 MessageLoop::current()->PostTask(FROM_HERE, task);
249 return true;
250 }
251
252 INPUT input = { 0 };
253
254 int screen_width = ::GetSystemMetrics(SM_CXSCREEN) - 1;
255 int screen_height = ::GetSystemMetrics(SM_CYSCREEN) - 1;
256 LONG pixel_x = static_cast<LONG>(x * (65535.0f / screen_width));
257 LONG pixel_y = static_cast<LONG>(y * (65535.0f / screen_height));
258
259 input.type = INPUT_MOUSE;
260 input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
261 input.mi.dx = pixel_x;
262 input.mi.dy = pixel_y;
263
264 scoped_refptr<InputDispatcher> dispatcher(
265 task ? new InputDispatcher(task, WM_MOUSEMOVE) : NULL);
266
267 if (!::SendInput(1, &input, sizeof(INPUT)))
268 return false;
269
270 if (dispatcher.get())
271 dispatcher->AddRef();
272
273 return true;
274 }
275
SendMouseEventsImpl(MouseButton type,int state,Task * task)276 bool SendMouseEventsImpl(MouseButton type, int state, Task* task) {
277 DWORD down_flags = MOUSEEVENTF_ABSOLUTE;
278 DWORD up_flags = MOUSEEVENTF_ABSOLUTE;
279 UINT last_event;
280
281 switch (type) {
282 case LEFT:
283 down_flags |= MOUSEEVENTF_LEFTDOWN;
284 up_flags |= MOUSEEVENTF_LEFTUP;
285 last_event = (state & UP) ? WM_LBUTTONUP : WM_LBUTTONDOWN;
286 break;
287
288 case MIDDLE:
289 down_flags |= MOUSEEVENTF_MIDDLEDOWN;
290 up_flags |= MOUSEEVENTF_MIDDLEUP;
291 last_event = (state & UP) ? WM_MBUTTONUP : WM_MBUTTONDOWN;
292 break;
293
294 case RIGHT:
295 down_flags |= MOUSEEVENTF_RIGHTDOWN;
296 up_flags |= MOUSEEVENTF_RIGHTUP;
297 last_event = (state & UP) ? WM_RBUTTONUP : WM_RBUTTONDOWN;
298 break;
299
300 default:
301 NOTREACHED();
302 return false;
303 }
304
305 scoped_refptr<InputDispatcher> dispatcher(
306 task ? new InputDispatcher(task, last_event) : NULL);
307
308 INPUT input = { 0 };
309 input.type = INPUT_MOUSE;
310 input.mi.dwFlags = down_flags;
311 if ((state & DOWN) && !::SendInput(1, &input, sizeof(INPUT)))
312 return false;
313
314 input.mi.dwFlags = up_flags;
315 if ((state & UP) && !::SendInput(1, &input, sizeof(INPUT)))
316 return false;
317
318 if (dispatcher.get())
319 dispatcher->AddRef();
320
321 return true;
322 }
323
324 } // namespace
325
326 // public functions -----------------------------------------------------------
327
SendKeyPress(gfx::NativeWindow window,ui::KeyboardCode key,bool control,bool shift,bool alt,bool command)328 bool SendKeyPress(gfx::NativeWindow window,
329 ui::KeyboardCode key,
330 bool control,
331 bool shift,
332 bool alt,
333 bool command) {
334 DCHECK(!command); // No command key on Windows
335 return SendKeyPressImpl(key, control, shift, alt, NULL);
336 }
337
SendKeyPressNotifyWhenDone(gfx::NativeWindow window,ui::KeyboardCode key,bool control,bool shift,bool alt,bool command,Task * task)338 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
339 ui::KeyboardCode key,
340 bool control,
341 bool shift,
342 bool alt,
343 bool command,
344 Task* task) {
345 DCHECK(!command); // No command key on Windows
346 return SendKeyPressImpl(key, control, shift, alt, task);
347 }
348
SendMouseMove(long x,long y)349 bool SendMouseMove(long x, long y) {
350 return SendMouseMoveImpl(x, y, NULL);
351 }
352
SendMouseMoveNotifyWhenDone(long x,long y,Task * task)353 bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) {
354 return SendMouseMoveImpl(x, y, task);
355 }
356
SendMouseEvents(MouseButton type,int state)357 bool SendMouseEvents(MouseButton type, int state) {
358 return SendMouseEventsImpl(type, state, NULL);
359 }
360
SendMouseEventsNotifyWhenDone(MouseButton type,int state,Task * task)361 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) {
362 return SendMouseEventsImpl(type, state, task);
363 }
364
SendMouseClick(MouseButton type)365 bool SendMouseClick(MouseButton type) {
366 return SendMouseEventsImpl(type, UP | DOWN, NULL);
367 }
368
MoveMouseToCenterAndPress(views::View * view,MouseButton button,int state,Task * task)369 void MoveMouseToCenterAndPress(views::View* view,
370 MouseButton button,
371 int state,
372 Task* task) {
373 DCHECK(view);
374 DCHECK(view->GetWidget());
375 gfx::Point view_center(view->width() / 2, view->height() / 2);
376 views::View::ConvertPointToScreen(view, &view_center);
377 SendMouseMove(view_center.x(), view_center.y());
378 SendMouseEventsNotifyWhenDone(button, state, task);
379 }
380
381 } // ui_controls
382