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