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