summaryrefslogtreecommitdiffstats
path: root/chrome/browser/automation/ui_controls_win.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/automation/ui_controls_win.cc')
-rw-r--r--chrome/browser/automation/ui_controls_win.cc370
1 files changed, 370 insertions, 0 deletions
diff --git a/chrome/browser/automation/ui_controls_win.cc b/chrome/browser/automation/ui_controls_win.cc
new file mode 100644
index 0000000..50bb88f
--- /dev/null
+++ b/chrome/browser/automation/ui_controls_win.cc
@@ -0,0 +1,370 @@
+// Copyright (c) 2010 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 "chrome/browser/automation/ui_controls.h"
+
+#include "base/keyboard_codes.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/win_util.h"
+#include "base/ref_counted.h"
+#include "base/task.h"
+#include "views/view.h"
+
+namespace ui_controls {
+
+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(Task* 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.
+ scoped_ptr<Task> 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(Task* task, UINT 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.
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, NewRunnableMethod(this, &InputDispatcher::NotifyTask), 0);
+}
+
+void InputDispatcher::NotifyTask() {
+ task_->Run();
+ Release();
+}
+
+// Private functions ----------------------------------------------------------
+
+// Populate the INPUT structure with the appropriate keyboard event
+// parameters required by SendInput
+bool FillKeyboardInput(base::KeyboardCode key, INPUT* input, bool key_up) {
+ memset(input, 0, sizeof(INPUT));
+ input->type = INPUT_KEYBOARD;
+ input->ki.wVk = win_util::KeyboardCodeToWin(key);
+ input->ki.dwFlags = key_up ? KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP :
+ KEYEVENTF_EXTENDEDKEY;
+
+ return true;
+}
+
+// Send a key event (up/down)
+bool SendKeyEvent(base::KeyboardCode key, bool up) {
+ INPUT input = { 0 };
+
+ if (!FillKeyboardInput(key, &input, up))
+ return false;
+
+ if (!::SendInput(1, &input, sizeof(INPUT)))
+ return false;
+
+ return true;
+}
+
+bool SendKeyPressImpl(base::KeyboardCode key,
+ bool control, bool shift, bool alt,
+ Task* task) {
+ scoped_refptr<InputDispatcher> dispatcher(
+ task ? 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 = win_util::KeyboardCodeToWin(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(base::VKEY_CONTROL, &input[i], false))
+ return false;
+ i++;
+ }
+
+ if (shift) {
+ if (!FillKeyboardInput(base::VKEY_SHIFT, &input[i], false))
+ return false;
+ i++;
+ }
+
+ if (alt) {
+ if (!FillKeyboardInput(base::VKEY_MENU, &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(base::VKEY_MENU, &input[i], true))
+ return false;
+ i++;
+ }
+
+ if (shift) {
+ if (!FillKeyboardInput(base::VKEY_SHIFT, &input[i], true))
+ return false;
+ i++;
+ }
+
+ if (control) {
+ if (!FillKeyboardInput(base::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 x, long y, Task* task) {
+ // First check if the mouse is already there.
+ POINT current_pos;
+ ::GetCursorPos(&current_pos);
+ if (x == current_pos.x && y == current_pos.y) {
+ if (task)
+ 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>(x * (65535.0f / screen_width));
+ LONG pixel_y = static_cast<LONG>(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 ? 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, Task* 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 ? 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
+
+// public functions -----------------------------------------------------------
+
+bool SendKeyPress(gfx::NativeWindow window, base::KeyboardCode key,
+ bool control, bool shift, bool alt, bool command) {
+ DCHECK(command == false); // No command key on Windows
+ return SendKeyPressImpl(key, control, shift, alt, NULL);
+}
+
+bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
+ base::KeyboardCode key,
+ bool control, bool shift, bool alt,
+ bool command,
+ Task* task) {
+ DCHECK(command == false); // No command key on Windows
+ return SendKeyPressImpl(key, control, shift, alt, task);
+}
+
+bool SendMouseMove(long x, long y) {
+ return SendMouseMoveImpl(x, y, NULL);
+}
+
+bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) {
+ return SendMouseMoveImpl(x, y, task);
+}
+
+bool SendMouseEvents(MouseButton type, int state) {
+ return SendMouseEventsImpl(type, state, NULL);
+}
+
+bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) {
+ return SendMouseEventsImpl(type, state, task);
+}
+
+bool SendMouseClick(MouseButton type) {
+ return SendMouseEventsImpl(type, UP | DOWN, NULL);
+}
+
+void MoveMouseToCenterAndPress(views::View* view, MouseButton button,
+ int state, Task* task) {
+ DCHECK(view);
+ DCHECK(view->GetWidget());
+ gfx::Point view_center(view->width() / 2, view->height() / 2);
+ views::View::ConvertPointToScreen(view, &view_center);
+ SendMouseMove(view_center.x(), view_center.y());
+ SendMouseEventsNotifyWhenDone(button, state, task);
+}
+
+} // ui_controls