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