ui_controls_win.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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 15namespace ui_controls { 16 17namespace { 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. 23class 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? 53bool installed_hook_ = false; 54 55// Return value from SetWindowsHookEx. 56HHOOK next_hook_ = NULL; 57 58// If a hook is installed, this is the dispatcher. 59InputDispatcher* current_dispatcher_ = NULL; 60 61// Callback from hook when a mouse message is received. 62LRESULT 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. 72LRESULT 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. 85void 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. 102void UninstallHook(InputDispatcher* dispatcher) { 103 if (current_dispatcher_ == dispatcher) { 104 installed_hook_ = false; 105 current_dispatcher_ = NULL; 106 UnhookWindowsHookEx(next_hook_); 107 } 108} 109 110InputDispatcher::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 115InputDispatcher::~InputDispatcher() { 116 // Make sure the hook isn't installed. 117 UninstallHook(this); 118} 119 120void InputDispatcher::DispatchedMessage(WPARAM message) { 121 if (message == message_waiting_for_) 122 MatchingMessageFound(); 123} 124 125void 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 133void 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 142bool 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) 153bool 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 165bool 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 242bool 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 276bool 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 328bool 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 338bool 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 349bool SendMouseMove(long x, long y) { 350 return SendMouseMoveImpl(x, y, NULL); 351} 352 353bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { 354 return SendMouseMoveImpl(x, y, task); 355} 356 357bool SendMouseEvents(MouseButton type, int state) { 358 return SendMouseEventsImpl(type, state, NULL); 359} 360 361bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { 362 return SendMouseEventsImpl(type, state, task); 363} 364 365bool SendMouseClick(MouseButton type) { 366 return SendMouseEventsImpl(type, UP | DOWN, NULL); 367} 368 369void 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