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