diff options
Diffstat (limited to 'chrome/browser/automation/ui_controls.cc')
-rw-r--r-- | chrome/browser/automation/ui_controls.cc | 228 |
1 files changed, 208 insertions, 20 deletions
diff --git a/chrome/browser/automation/ui_controls.cc b/chrome/browser/automation/ui_controls.cc index 811bd89..f7c7806 100644 --- a/chrome/browser/automation/ui_controls.cc +++ b/chrome/browser/automation/ui_controls.cc @@ -30,8 +30,131 @@ #include "chrome/browser/automation/ui_controls.h" #include "base/logging.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/task.h" +#include "chrome/views/view.h" -// Methods private to this file +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); + + ~InputDispatcher(); + + // 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: + // 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 @@ -58,9 +181,11 @@ bool SendKeyEvent(wchar_t key, bool up) { return true; } -namespace ui_controls { +bool SendKeyPressImpl(wchar_t key, bool control, bool shift, bool alt, + Task* task) { + scoped_refptr<InputDispatcher> dispatcher( + task ? new InputDispatcher(task, WM_KEYUP) : NULL); -bool SendKeyPress(wchar_t key, bool control, bool shift, bool alt) { INPUT input[8] = { 0 }; // 8, assuming all the modifiers are activated int i = 0; @@ -108,21 +233,17 @@ bool SendKeyPress(wchar_t key, bool control, bool shift, bool alt) { i++; } - unsigned int rv = ::SendInput(i, input, sizeof(INPUT)); - return rv == i; -} - -bool SendKeyDown(wchar_t key) { - return SendKeyEvent(key, false); -} + if (rv != i) + return false; -bool SendKeyUp(wchar_t key) { - return SendKeyEvent(key, true); + if (dispatcher.get()) + dispatcher->AddRef(); + return true; } -bool SendMouseMove(long x, long y) { +bool SendMouseMoveImpl(long x, long y, Task* task) { INPUT input = { 0 }; int screen_width = ::GetSystemMetrics(SM_CXSCREEN) - 1; @@ -135,48 +256,115 @@ bool SendMouseMove(long x, long y) { 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 SendMouseClick(MouseButton type) { - +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 (!::SendInput(1, &input, sizeof(INPUT))) + if ((state & DOWN) && !::SendInput(1, &input, sizeof(INPUT))) return false; input.mi.dwFlags = up_flags; - if (!::SendInput(1, &input, sizeof(INPUT))) + if ((state & UP) && !::SendInput(1, &input, sizeof(INPUT))) return false; + if (dispatcher.get()) + dispatcher->AddRef(); + return true; } +} // namespace + +// public functions ----------------------------------------------------------- + +bool SendKeyPress(wchar_t key, bool control, bool shift, bool alt) { + return SendKeyPressImpl(key, control, shift, alt, NULL); +} + +bool SendKeyPressNotifyWhenDone(wchar_t key, bool control, bool shift, + bool alt, Task* task) { + return SendKeyPressImpl(key, control, shift, alt, task); +} + +bool SendKeyDown(wchar_t key) { + return SendKeyEvent(key, false); +} + +bool SendKeyUp(wchar_t key) { + return SendKeyEvent(key, true); +} + +bool SendMouseMove(long x, long y) { + return SendMouseMoveImpl(x, y, NULL); +} + +void SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { + SendMouseMoveImpl(x, y, task); +} + +bool SendMouseEvents(MouseButton type, int state) { + return SendMouseEventsImpl(type, state, NULL); +} + +void SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { + SendMouseEventsImpl(type, state, task); +} + +bool SendMouseClick(MouseButton type) { + return SendMouseEventsImpl(type, UP | DOWN, NULL); +} + +void MoveMouseToCenterAndPress( + ChromeViews::View* view, MouseButton button, int state, Task* task) { + DCHECK(view); + DCHECK(view->GetViewContainer()); + CPoint view_center(view->GetWidth() / 2, view->GetHeight() / 2); + ChromeViews::View::ConvertPointToScreen(view, &view_center); + SendMouseMove(view_center.x, view_center.y); + SendMouseEventsNotifyWhenDone(button, state, task); +} + } // ui_controls |