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