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