| 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 |