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