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. 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. 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. 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. 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 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 115 InputDispatcher::~InputDispatcher() { 116 // Make sure the hook isn't installed. 117 UninstallHook(this); 118 } 119 120 void InputDispatcher::DispatchedMessage(WPARAM message) { 121 if (message == message_waiting_for_) 122 MatchingMessageFound(); 123 } 124 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 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 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) 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 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 242 bool SendMouseMoveImpl(long x, long y, Task* task) { 243 // First check if the mouse is already there. 244 POINT current_pos; 245 ::GetCursorPos(¤t_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 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 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 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 349 bool SendMouseMove(long x, long y) { 350 return SendMouseMoveImpl(x, y, NULL); 351 } 352 353 bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { 354 return SendMouseMoveImpl(x, y, task); 355 } 356 357 bool SendMouseEvents(MouseButton type, int state) { 358 return SendMouseEventsImpl(type, state, NULL); 359 } 360 361 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { 362 return SendMouseEventsImpl(type, state, task); 363 } 364 365 bool SendMouseClick(MouseButton type) { 366 return SendMouseEventsImpl(type, UP | DOWN, NULL); 367 } 368 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