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_MENU, &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_MENU, &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 x,long y,const base::Closure & task)258 bool SendMouseMoveImpl(long x, long y, const base::Closure& task) {
259 // First check if the mouse is already there.
260 POINT current_pos;
261 ::GetCursorPos(¤t_pos);
262 if (x == current_pos.x && y == current_pos.y) {
263 if (!task.is_null())
264 base::MessageLoop::current()->PostTask(FROM_HERE, task);
265 return true;
266 }
267
268 INPUT input = { 0 };
269
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));
274
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;
279
280 scoped_refptr<InputDispatcher> dispatcher(
281 !task.is_null() ? new InputDispatcher(task, WM_MOUSEMOVE) : NULL);
282
283 if (!::SendInput(1, &input, sizeof(INPUT)))
284 return false;
285
286 if (dispatcher.get())
287 dispatcher->AddRef();
288
289 return true;
290 }
291
SendMouseEventsImpl(MouseButton type,int state,const base::Closure & task)292 bool SendMouseEventsImpl(MouseButton type, int state,
293 const base::Closure& task) {
294 DWORD down_flags = MOUSEEVENTF_ABSOLUTE;
295 DWORD up_flags = MOUSEEVENTF_ABSOLUTE;
296 UINT last_event;
297
298 switch (type) {
299 case LEFT:
300 down_flags |= MOUSEEVENTF_LEFTDOWN;
301 up_flags |= MOUSEEVENTF_LEFTUP;
302 last_event = (state & UP) ? WM_LBUTTONUP : WM_LBUTTONDOWN;
303 break;
304
305 case MIDDLE:
306 down_flags |= MOUSEEVENTF_MIDDLEDOWN;
307 up_flags |= MOUSEEVENTF_MIDDLEUP;
308 last_event = (state & UP) ? WM_MBUTTONUP : WM_MBUTTONDOWN;
309 break;
310
311 case RIGHT:
312 down_flags |= MOUSEEVENTF_RIGHTDOWN;
313 up_flags |= MOUSEEVENTF_RIGHTUP;
314 last_event = (state & UP) ? WM_RBUTTONUP : WM_RBUTTONDOWN;
315 break;
316
317 default:
318 NOTREACHED();
319 return false;
320 }
321
322 scoped_refptr<InputDispatcher> dispatcher(
323 !task.is_null() ? new InputDispatcher(task, last_event) : NULL);
324
325 INPUT input = { 0 };
326 input.type = INPUT_MOUSE;
327 input.mi.dwFlags = down_flags;
328 if ((state & DOWN) && !::SendInput(1, &input, sizeof(INPUT)))
329 return false;
330
331 input.mi.dwFlags = up_flags;
332 if ((state & UP) && !::SendInput(1, &input, sizeof(INPUT)))
333 return false;
334
335 if (dispatcher.get())
336 dispatcher->AddRef();
337
338 return true;
339 }
340
341 } // namespace internal
342 } // namespace ui_controls
343