• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&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 
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