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