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