1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
|
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/test/ui_controls_internal_win.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
#include "ui/events/keycodes/keyboard_codes.h"
namespace {
// InputDispatcher ------------------------------------------------------------
// InputDispatcher is used to listen for a mouse/keyboard event. When the
// appropriate event is received the task is notified.
class InputDispatcher : public base::RefCounted<InputDispatcher> {
public:
InputDispatcher(const base::Closure& task, WPARAM message_waiting_for);
// Invoked from the hook. If mouse_message matches message_waiting_for_
// MatchingMessageFound is invoked.
void DispatchedMessage(WPARAM mouse_message);
// Invoked when a matching event is found. Uninstalls the hook and schedules
// an event that notifies the task.
void MatchingMessageFound();
private:
friend class base::RefCounted<InputDispatcher>;
~InputDispatcher();
// Notifies the task and release this (which should delete it).
void NotifyTask();
// The task we notify.
base::Closure task_;
// Message we're waiting for. Not used for keyboard events.
const WPARAM message_waiting_for_;
DISALLOW_COPY_AND_ASSIGN(InputDispatcher);
};
// Have we installed the hook?
bool installed_hook_ = false;
// Return value from SetWindowsHookEx.
HHOOK next_hook_ = NULL;
// If a hook is installed, this is the dispatcher.
InputDispatcher* current_dispatcher_ = NULL;
// Callback from hook when a mouse message is received.
LRESULT CALLBACK MouseHook(int n_code, WPARAM w_param, LPARAM l_param) {
HHOOK next_hook = next_hook_;
if (n_code == HC_ACTION) {
DCHECK(current_dispatcher_);
current_dispatcher_->DispatchedMessage(w_param);
}
return CallNextHookEx(next_hook, n_code, w_param, l_param);
}
// Callback from hook when a key message is received.
LRESULT CALLBACK KeyHook(int n_code, WPARAM w_param, LPARAM l_param) {
HHOOK next_hook = next_hook_;
if (n_code == HC_ACTION) {
DCHECK(current_dispatcher_);
if (l_param & (1 << 30)) {
// Only send on key up.
current_dispatcher_->MatchingMessageFound();
}
}
return CallNextHookEx(next_hook, n_code, w_param, l_param);
}
// Installs dispatcher as the current hook.
void InstallHook(InputDispatcher* dispatcher, bool key_hook) {
DCHECK(!installed_hook_);
current_dispatcher_ = dispatcher;
installed_hook_ = true;
if (key_hook) {
next_hook_ = SetWindowsHookEx(WH_KEYBOARD, &KeyHook, NULL,
GetCurrentThreadId());
} else {
// NOTE: I originally tried WH_CALLWNDPROCRET, but for some reason I
// didn't get a mouse message like I do with MouseHook.
next_hook_ = SetWindowsHookEx(WH_MOUSE, &MouseHook, NULL,
GetCurrentThreadId());
}
DCHECK(next_hook_);
}
// Uninstalls the hook set in InstallHook.
void UninstallHook(InputDispatcher* dispatcher) {
if (current_dispatcher_ == dispatcher) {
installed_hook_ = false;
current_dispatcher_ = NULL;
UnhookWindowsHookEx(next_hook_);
}
}
InputDispatcher::InputDispatcher(const base::Closure& task,
WPARAM message_waiting_for)
: task_(task), message_waiting_for_(message_waiting_for) {
InstallHook(this, message_waiting_for == WM_KEYUP);
}
InputDispatcher::~InputDispatcher() {
// Make sure the hook isn't installed.
UninstallHook(this);
}
void InputDispatcher::DispatchedMessage(WPARAM message) {
if (message == message_waiting_for_)
MatchingMessageFound();
}
void InputDispatcher::MatchingMessageFound() {
UninstallHook(this);
// At the time we're invoked the event has not actually been processed.
// Use PostTask to make sure the event has been processed before notifying.
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(&InputDispatcher::NotifyTask, this));
}
void InputDispatcher::NotifyTask() {
task_.Run();
Release();
}
// Private functions ----------------------------------------------------------
// Populate the INPUT structure with the appropriate keyboard event
// parameters required by SendInput
bool FillKeyboardInput(ui::KeyboardCode key, INPUT* input, bool key_up) {
memset(input, 0, sizeof(INPUT));
input->type = INPUT_KEYBOARD;
input->ki.wVk = ui::WindowsKeyCodeForKeyboardCode(key);
input->ki.dwFlags = key_up ? KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP :
KEYEVENTF_EXTENDEDKEY;
return true;
}
// Send a key event (up/down)
bool SendKeyEvent(ui::KeyboardCode key, bool up) {
INPUT input = { 0 };
if (!FillKeyboardInput(key, &input, up))
return false;
if (!::SendInput(1, &input, sizeof(INPUT)))
return false;
return true;
}
} // namespace
namespace ui_controls {
namespace internal {
bool SendKeyPressImpl(HWND window,
ui::KeyboardCode key,
bool control,
bool shift,
bool alt,
const base::Closure& task) {
// SendInput only works as we expect it if one of our windows is the
// foreground window already.
HWND target_window = (::GetActiveWindow() &&
::GetWindow(::GetActiveWindow(), GW_OWNER) == window) ?
::GetActiveWindow() :
window;
if (window && ::GetForegroundWindow() != target_window)
return false;
scoped_refptr<InputDispatcher> dispatcher(
!task.is_null() ? new InputDispatcher(task, WM_KEYUP) : NULL);
// If a pop-up menu is open, it won't receive events sent using SendInput.
// Check for a pop-up menu using its window class (#32768) and if one
// exists, send the key event directly there.
HWND popup_menu = ::FindWindow(L"#32768", 0);
if (popup_menu != NULL && popup_menu == ::GetTopWindow(NULL)) {
WPARAM w_param = ui::WindowsKeyCodeForKeyboardCode(key);
LPARAM l_param = 0;
::SendMessage(popup_menu, WM_KEYDOWN, w_param, l_param);
::SendMessage(popup_menu, WM_KEYUP, w_param, l_param);
if (dispatcher.get())
dispatcher->AddRef();
return true;
}
INPUT input[8] = { 0 }; // 8, assuming all the modifiers are activated.
UINT i = 0;
if (control) {
if (!FillKeyboardInput(ui::VKEY_CONTROL, &input[i], false))
return false;
i++;
}
if (shift) {
if (!FillKeyboardInput(ui::VKEY_SHIFT, &input[i], false))
return false;
i++;
}
if (alt) {
if (!FillKeyboardInput(ui::VKEY_LMENU, &input[i], false))
return false;
i++;
}
if (!FillKeyboardInput(key, &input[i], false))
return false;
i++;
if (!FillKeyboardInput(key, &input[i], true))
return false;
i++;
if (alt) {
if (!FillKeyboardInput(ui::VKEY_LMENU, &input[i], true))
return false;
i++;
}
if (shift) {
if (!FillKeyboardInput(ui::VKEY_SHIFT, &input[i], true))
return false;
i++;
}
if (control) {
if (!FillKeyboardInput(ui::VKEY_CONTROL, &input[i], true))
return false;
i++;
}
if (::SendInput(i, input, sizeof(INPUT)) != i)
return false;
if (dispatcher.get())
dispatcher->AddRef();
return true;
}
bool SendMouseMoveImpl(long screen_x,
long screen_y,
const base::Closure& task) {
// First check if the mouse is already there.
POINT current_pos;
::GetCursorPos(¤t_pos);
if (screen_x == current_pos.x && screen_y == current_pos.y) {
if (!task.is_null())
base::MessageLoop::current()->PostTask(FROM_HERE, task);
return true;
}
INPUT input = { 0 };
int screen_width = ::GetSystemMetrics(SM_CXSCREEN) - 1;
int screen_height = ::GetSystemMetrics(SM_CYSCREEN) - 1;
LONG pixel_x = static_cast<LONG>(screen_x * (65535.0f / screen_width));
LONG pixel_y = static_cast<LONG>(screen_y * (65535.0f / screen_height));
input.type = INPUT_MOUSE;
input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
input.mi.dx = pixel_x;
input.mi.dy = pixel_y;
scoped_refptr<InputDispatcher> dispatcher(
!task.is_null() ? new InputDispatcher(task, WM_MOUSEMOVE) : NULL);
if (!::SendInput(1, &input, sizeof(INPUT)))
return false;
if (dispatcher.get())
dispatcher->AddRef();
return true;
}
bool SendMouseEventsImpl(MouseButton type, int state,
const base::Closure& task) {
DWORD down_flags = MOUSEEVENTF_ABSOLUTE;
DWORD up_flags = MOUSEEVENTF_ABSOLUTE;
UINT last_event;
switch (type) {
case LEFT:
down_flags |= MOUSEEVENTF_LEFTDOWN;
up_flags |= MOUSEEVENTF_LEFTUP;
last_event = (state & UP) ? WM_LBUTTONUP : WM_LBUTTONDOWN;
break;
case MIDDLE:
down_flags |= MOUSEEVENTF_MIDDLEDOWN;
up_flags |= MOUSEEVENTF_MIDDLEUP;
last_event = (state & UP) ? WM_MBUTTONUP : WM_MBUTTONDOWN;
break;
case RIGHT:
down_flags |= MOUSEEVENTF_RIGHTDOWN;
up_flags |= MOUSEEVENTF_RIGHTUP;
last_event = (state & UP) ? WM_RBUTTONUP : WM_RBUTTONDOWN;
break;
default:
NOTREACHED();
return false;
}
scoped_refptr<InputDispatcher> dispatcher(
!task.is_null() ? new InputDispatcher(task, last_event) : NULL);
INPUT input = { 0 };
input.type = INPUT_MOUSE;
input.mi.dwFlags = down_flags;
if ((state & DOWN) && !::SendInput(1, &input, sizeof(INPUT)))
return false;
input.mi.dwFlags = up_flags;
if ((state & UP) && !::SendInput(1, &input, sizeof(INPUT)))
return false;
if (dispatcher.get())
dispatcher->AddRef();
return true;
}
} // namespace internal
} // namespace ui_controls
|