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_LMENU
, &input
[i
], false))
223 if (!FillKeyboardInput(key
, &input
[i
], false))
227 if (!FillKeyboardInput(key
, &input
[i
], true))
232 if (!FillKeyboardInput(ui::VKEY_LMENU
, &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 screen_x
,
260 const base::Closure
& task
) {
261 // First check if the mouse is already there.
263 ::GetCursorPos(¤t_pos
);
264 if (screen_x
== current_pos
.x
&& screen_y
== current_pos
.y
) {
266 base::MessageLoop::current()->PostTask(FROM_HERE
, task
);
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
));
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
;
282 scoped_refptr
<InputDispatcher
> dispatcher(
283 !task
.is_null() ? new InputDispatcher(task
, WM_MOUSEMOVE
) : NULL
);
285 if (!::SendInput(1, &input
, sizeof(INPUT
)))
288 if (dispatcher
.get())
289 dispatcher
->AddRef();
294 bool SendMouseEventsImpl(MouseButton type
, int state
,
295 const base::Closure
& task
) {
296 DWORD down_flags
= MOUSEEVENTF_ABSOLUTE
;
297 DWORD up_flags
= MOUSEEVENTF_ABSOLUTE
;
302 down_flags
|= MOUSEEVENTF_LEFTDOWN
;
303 up_flags
|= MOUSEEVENTF_LEFTUP
;
304 last_event
= (state
& UP
) ? WM_LBUTTONUP
: WM_LBUTTONDOWN
;
308 down_flags
|= MOUSEEVENTF_MIDDLEDOWN
;
309 up_flags
|= MOUSEEVENTF_MIDDLEUP
;
310 last_event
= (state
& UP
) ? WM_MBUTTONUP
: WM_MBUTTONDOWN
;
314 down_flags
|= MOUSEEVENTF_RIGHTDOWN
;
315 up_flags
|= MOUSEEVENTF_RIGHTUP
;
316 last_event
= (state
& UP
) ? WM_RBUTTONUP
: WM_RBUTTONDOWN
;
324 scoped_refptr
<InputDispatcher
> dispatcher(
325 !task
.is_null() ? new InputDispatcher(task
, last_event
) : NULL
);
328 input
.type
= INPUT_MOUSE
;
329 input
.mi
.dwFlags
= down_flags
;
330 if ((state
& DOWN
) && !::SendInput(1, &input
, sizeof(INPUT
)))
333 input
.mi
.dwFlags
= up_flags
;
334 if ((state
& UP
) && !::SendInput(1, &input
, sizeof(INPUT
)))
337 if (dispatcher
.get())
338 dispatcher
->AddRef();
343 } // namespace internal
344 } // namespace ui_controls