| OLD | NEW |
| (Empty) |
| 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 | |
| OLD | NEW |