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.
MouseHook(int n_code,WPARAM w_param,LPARAM l_param)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.
KeyHook(int n_code,WPARAM w_param,LPARAM l_param)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.
InstallHook(InputDispatcher * dispatcher,bool key_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.
UninstallHook(InputDispatcher * dispatcher)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
InputDispatcher(const base::Closure & task,WPARAM message_waiting_for)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
~InputDispatcher()114 InputDispatcher::~InputDispatcher() {
115 // Make sure the hook isn't installed.
116 UninstallHook(this);
117 }
118
DispatchedMessage(WPARAM message)119 void InputDispatcher::DispatchedMessage(WPARAM message) {
120 if (message == message_waiting_for_)
121 MatchingMessageFound();
122 }
123
MatchingMessageFound()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
NotifyTask()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
FillKeyboardInput(ui::KeyboardCode key,INPUT * input,bool key_up)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)
SendKeyEvent(ui::KeyboardCode key,bool up)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
SendKeyPressImpl(HWND window,ui::KeyboardCode key,bool control,bool shift,bool alt,const base::Closure & task)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
SendMouseMoveImpl(long screen_x,long screen_y,const base::Closure & task)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
SendMouseEventsImpl(MouseButton type,int state,const base::Closure & task)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