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.
5 #include "ui/base/test/ui_controls_internal_win.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"
17 // InputDispatcher ------------------------------------------------------------
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> {
23 InputDispatcher(const base::Closure& task, WPARAM message_waiting_for);
25 // Invoked from the hook. If mouse_message matches message_waiting_for_
26 // MatchingMessageFound is invoked.
27 void DispatchedMessage(WPARAM mouse_message);
29 // Invoked when a matching event is found. Uninstalls the hook and schedules
30 // an event that notifies the task.
31 void MatchingMessageFound();
34 friend class base::RefCounted<InputDispatcher>;
38 // Notifies the task and release this (which should delete it).
41 // The task we notify.
44 // Message we're waiting for. Not used for keyboard events.
45 const WPARAM message_waiting_for_;
47 DISALLOW_COPY_AND_ASSIGN(InputDispatcher);
50 // Have we installed the hook?
51 bool installed_hook_ = false;
53 // Return value from SetWindowsHookEx.
54 HHOOK next_hook_ = NULL;
56 // If a hook is installed, this is the dispatcher.
57 InputDispatcher* current_dispatcher_ = NULL;
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);
66 return CallNextHookEx(next_hook, n_code, w_param, l_param);
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();
79 return CallNextHookEx(next_hook, n_code, w_param, l_param);
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;
88 next_hook_ = SetWindowsHookEx(WH_KEYBOARD, &KeyHook, NULL,
89 GetCurrentThreadId());
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());
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_);
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);
114 InputDispatcher::~InputDispatcher() {
115 // Make sure the hook isn't installed.
119 void InputDispatcher::DispatchedMessage(WPARAM message) {
120 if (message == message_waiting_for_)
121 MatchingMessageFound();
124 void InputDispatcher::MatchingMessageFound() {
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));
132 void InputDispatcher::NotifyTask() {
137 // Private functions ----------------------------------------------------------
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;
151 // Send a key event (up/down)
152 bool SendKeyEvent(ui::KeyboardCode key, bool up) {
155 if (!FillKeyboardInput(key, &input, up))
158 if (!::SendInput(1, &input, sizeof(INPUT)))
166 namespace ui_controls {
169 bool SendKeyPressImpl(HWND window,
170 ui::KeyboardCode key,
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() :
181 if (window && ::GetForegroundWindow() != target_window)
184 scoped_refptr<InputDispatcher> dispatcher(
185 !task.is_null() ? new InputDispatcher(task, WM_KEYUP) : NULL);
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);
194 ::SendMessage(popup_menu, WM_KEYDOWN, w_param, l_param);
195 ::SendMessage(popup_menu, WM_KEYUP, w_param, l_param);
197 if (dispatcher.get())
198 dispatcher->AddRef();
202 INPUT input[8] = { 0 }; // 8, assuming all the modifiers are activated.
206 if (!FillKeyboardInput(ui::VKEY_CONTROL, &input[i], false))
212 if (!FillKeyboardInput(ui::VKEY_SHIFT, &input[i], false))
218 if (!FillKeyboardInput(ui::VKEY_MENU, &input[i], false))
223 if (!FillKeyboardInput(key, &input[i], false))
227 if (!FillKeyboardInput(key, &input[i], true))
232 if (!FillKeyboardInput(ui::VKEY_MENU, &input[i], true))
238 if (!FillKeyboardInput(ui::VKEY_SHIFT, &input[i], true))
244 if (!FillKeyboardInput(ui::VKEY_CONTROL, &input[i], true))
249 if (::SendInput(i, input, sizeof(INPUT)) != i)
252 if (dispatcher.get())
253 dispatcher->AddRef();
258 bool SendMouseMoveImpl(long x, long y, const base::Closure& task) {
259 // First check if the mouse is already there.
261 ::GetCursorPos(¤t_pos);
262 if (x == current_pos.x && y == current_pos.y) {
264 base::MessageLoop::current()->PostTask(FROM_HERE, task);
270 int screen_width = ::GetSystemMetrics(SM_CXSCREEN) - 1;
271 int screen_height = ::GetSystemMetrics(SM_CYSCREEN) - 1;
272 LONG pixel_x = static_cast<LONG>(x * (65535.0f / screen_width));
273 LONG pixel_y = static_cast<LONG>(y * (65535.0f / screen_height));
275 input.type = INPUT_MOUSE;
276 input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
277 input.mi.dx = pixel_x;
278 input.mi.dy = pixel_y;
280 scoped_refptr<InputDispatcher> dispatcher(
281 !task.is_null() ? new InputDispatcher(task, WM_MOUSEMOVE) : NULL);
283 if (!::SendInput(1, &input, sizeof(INPUT)))
286 if (dispatcher.get())
287 dispatcher->AddRef();
292 bool SendMouseEventsImpl(MouseButton type, int state,
293 const base::Closure& task) {
294 DWORD down_flags = MOUSEEVENTF_ABSOLUTE;
295 DWORD up_flags = MOUSEEVENTF_ABSOLUTE;
300 down_flags |= MOUSEEVENTF_LEFTDOWN;
301 up_flags |= MOUSEEVENTF_LEFTUP;
302 last_event = (state & UP) ? WM_LBUTTONUP : WM_LBUTTONDOWN;
306 down_flags |= MOUSEEVENTF_MIDDLEDOWN;
307 up_flags |= MOUSEEVENTF_MIDDLEUP;
308 last_event = (state & UP) ? WM_MBUTTONUP : WM_MBUTTONDOWN;
312 down_flags |= MOUSEEVENTF_RIGHTDOWN;
313 up_flags |= MOUSEEVENTF_RIGHTUP;
314 last_event = (state & UP) ? WM_RBUTTONUP : WM_RBUTTONDOWN;
322 scoped_refptr<InputDispatcher> dispatcher(
323 !task.is_null() ? new InputDispatcher(task, last_event) : NULL);
326 input.type = INPUT_MOUSE;
327 input.mi.dwFlags = down_flags;
328 if ((state & DOWN) && !::SendInput(1, &input, sizeof(INPUT)))
331 input.mi.dwFlags = up_flags;
332 if ((state & UP) && !::SendInput(1, &input, sizeof(INPUT)))
335 if (dispatcher.get())
336 dispatcher->AddRef();
341 } // namespace internal
342 } // namespace ui_controls