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