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
15namespace ui_controls {
16
17namespace {
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.
23class 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?
53bool installed_hook_ = false;
54
55// Return value from SetWindowsHookEx.
56HHOOK next_hook_ = NULL;
57
58// If a hook is installed, this is the dispatcher.
59InputDispatcher* current_dispatcher_ = NULL;
60
61// Callback from hook when a mouse message is received.
62LRESULT 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.
72LRESULT 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.
85void 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.
102void UninstallHook(InputDispatcher* dispatcher) {
103  if (current_dispatcher_ == dispatcher) {
104    installed_hook_ = false;
105    current_dispatcher_ = NULL;
106    UnhookWindowsHookEx(next_hook_);
107  }
108}
109
110InputDispatcher::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
115InputDispatcher::~InputDispatcher() {
116  // Make sure the hook isn't installed.
117  UninstallHook(this);
118}
119
120void InputDispatcher::DispatchedMessage(WPARAM message) {
121  if (message == message_waiting_for_)
122    MatchingMessageFound();
123}
124
125void 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
133void 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
142bool 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)
153bool 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
165bool 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
242bool SendMouseMoveImpl(long x, long y, Task* task) {
243  // First check if the mouse is already there.
244  POINT current_pos;
245  ::GetCursorPos(&current_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
276bool 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
328bool 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
338bool 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
349bool SendMouseMove(long x, long y) {
350  return SendMouseMoveImpl(x, y, NULL);
351}
352
353bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) {
354  return SendMouseMoveImpl(x, y, task);
355}
356
357bool SendMouseEvents(MouseButton type, int state) {
358  return SendMouseEventsImpl(type, state, NULL);
359}
360
361bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) {
362  return SendMouseEventsImpl(type, state, task);
363}
364
365bool SendMouseClick(MouseButton type) {
366  return SendMouseEventsImpl(type, UP | DOWN, NULL);
367}
368
369void 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