Home | History | Annotate | Download | only in input
      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 "content/browser/renderer_host/input/web_input_event_builders_win.h"
      6 
      7 #include "base/logging.h"
      8 #include "content/browser/renderer_host/input/web_input_event_util.h"
      9 
     10 using WebKit::WebInputEvent;
     11 using WebKit::WebKeyboardEvent;
     12 using WebKit::WebMouseEvent;
     13 using WebKit::WebMouseWheelEvent;
     14 
     15 namespace content {
     16 
     17 static const unsigned long kDefaultScrollLinesPerWheelDelta = 3;
     18 static const unsigned long kDefaultScrollCharsPerWheelDelta = 1;
     19 
     20 static bool IsKeyDown(WPARAM wparam) {
     21   return (GetKeyState(wparam) & 0x8000) != 0;
     22 }
     23 
     24 static int GetLocationModifier(WPARAM wparam, LPARAM lparam) {
     25   int modifier = 0;
     26   switch (wparam) {
     27   case VK_RETURN:
     28     if ((lparam >> 16) & KF_EXTENDED)
     29       modifier = WebInputEvent::IsKeyPad;
     30     break;
     31   case VK_INSERT:
     32   case VK_DELETE:
     33   case VK_HOME:
     34   case VK_END:
     35   case VK_PRIOR:
     36   case VK_NEXT:
     37   case VK_UP:
     38   case VK_DOWN:
     39   case VK_LEFT:
     40   case VK_RIGHT:
     41     if (!((lparam >> 16) & KF_EXTENDED))
     42       modifier = WebInputEvent::IsKeyPad;
     43     break;
     44   case VK_NUMLOCK:
     45   case VK_NUMPAD0:
     46   case VK_NUMPAD1:
     47   case VK_NUMPAD2:
     48   case VK_NUMPAD3:
     49   case VK_NUMPAD4:
     50   case VK_NUMPAD5:
     51   case VK_NUMPAD6:
     52   case VK_NUMPAD7:
     53   case VK_NUMPAD8:
     54   case VK_NUMPAD9:
     55   case VK_DIVIDE:
     56   case VK_MULTIPLY:
     57   case VK_SUBTRACT:
     58   case VK_ADD:
     59   case VK_DECIMAL:
     60   case VK_CLEAR:
     61     modifier = WebInputEvent::IsKeyPad;
     62     break;
     63   case VK_SHIFT:
     64     if (IsKeyDown(VK_LSHIFT))
     65       modifier = WebInputEvent::IsLeft;
     66     else if (IsKeyDown(VK_RSHIFT))
     67       modifier = WebInputEvent::IsRight;
     68     break;
     69   case VK_CONTROL:
     70     if (IsKeyDown(VK_LCONTROL))
     71       modifier = WebInputEvent::IsLeft;
     72     else if (IsKeyDown(VK_RCONTROL))
     73       modifier = WebInputEvent::IsRight;
     74     break;
     75   case VK_MENU:
     76     if (IsKeyDown(VK_LMENU))
     77       modifier = WebInputEvent::IsLeft;
     78     else if (IsKeyDown(VK_RMENU))
     79       modifier = WebInputEvent::IsRight;
     80     break;
     81   case VK_LWIN:
     82     modifier = WebInputEvent::IsLeft;
     83     break;
     84   case VK_RWIN:
     85     modifier = WebInputEvent::IsRight;
     86     break;
     87   }
     88 
     89   DCHECK(!modifier
     90          || modifier == WebInputEvent::IsKeyPad
     91          || modifier == WebInputEvent::IsLeft
     92          || modifier == WebInputEvent::IsRight);
     93   return modifier;
     94 }
     95 
     96 // Loads the state for toggle keys into the event.
     97 static void SetToggleKeyState(WebInputEvent* event) {
     98   // Low bit set from GetKeyState indicates "toggled".
     99   if (::GetKeyState(VK_NUMLOCK) & 1)
    100     event->modifiers |= WebInputEvent::NumLockOn;
    101   if (::GetKeyState(VK_CAPITAL) & 1)
    102     event->modifiers |= WebInputEvent::CapsLockOn;
    103 }
    104 
    105 WebKeyboardEvent WebKeyboardEventBuilder::Build(HWND hwnd, UINT message,
    106                                                 WPARAM wparam, LPARAM lparam) {
    107   WebKeyboardEvent result;
    108 
    109   // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
    110   // GetMessageTime() refers to is the same one that we're passed in? Perhaps
    111   // one of the construction parameters should be the time passed by the
    112   // caller, who would know for sure.
    113   result.timeStampSeconds = ::GetMessageTime() / 1000.0;
    114 
    115   result.windowsKeyCode = static_cast<int>(wparam);
    116   // Record the scan code (along with other context bits) for this key event.
    117   result.nativeKeyCode = static_cast<int>(lparam);
    118 
    119   switch (message) {
    120   case WM_SYSKEYDOWN:
    121     result.isSystemKey = true;
    122   case WM_KEYDOWN:
    123     result.type = WebInputEvent::RawKeyDown;
    124     break;
    125   case WM_SYSKEYUP:
    126     result.isSystemKey = true;
    127   case WM_KEYUP:
    128     result.type = WebInputEvent::KeyUp;
    129     break;
    130   case WM_IME_CHAR:
    131     result.type = WebInputEvent::Char;
    132     break;
    133   case WM_SYSCHAR:
    134     result.isSystemKey = true;
    135     result.type = WebInputEvent::Char;
    136   case WM_CHAR:
    137     result.type = WebInputEvent::Char;
    138     break;
    139   default:
    140     NOTREACHED();
    141   }
    142 
    143   if (result.type == WebInputEvent::Char
    144    || result.type == WebInputEvent::RawKeyDown) {
    145     result.text[0] = result.windowsKeyCode;
    146     result.unmodifiedText[0] = result.windowsKeyCode;
    147   }
    148   if (result.type != WebInputEvent::Char) {
    149     UpdateWindowsKeyCodeAndKeyIdentifier(
    150         &result,
    151         static_cast<ui::KeyboardCode>(result.windowsKeyCode));
    152   }
    153 
    154   if (::GetKeyState(VK_SHIFT) & 0x8000)
    155     result.modifiers |= WebInputEvent::ShiftKey;
    156   if (::GetKeyState(VK_CONTROL) & 0x8000)
    157     result.modifiers |= WebInputEvent::ControlKey;
    158   if (::GetKeyState(VK_MENU) & 0x8000)
    159     result.modifiers |= WebInputEvent::AltKey;
    160   // NOTE: There doesn't seem to be a way to query the mouse button state in
    161   // this case.
    162 
    163   if (LOWORD(lparam) > 1)
    164     result.modifiers |= WebInputEvent::IsAutoRepeat;
    165 
    166   result.modifiers |= GetLocationModifier(wparam, lparam);
    167 
    168   SetToggleKeyState(&result);
    169   return result;
    170 }
    171 
    172 // WebMouseEvent --------------------------------------------------------------
    173 
    174 static int g_last_click_count = 0;
    175 static double g_last_click_time = 0;
    176 
    177 static LPARAM GetRelativeCursorPos(HWND hwnd) {
    178   POINT pos = {-1, -1};
    179   GetCursorPos(&pos);
    180   ScreenToClient(hwnd, &pos);
    181   return MAKELPARAM(pos.x, pos.y);
    182 }
    183 
    184 WebMouseEvent WebMouseEventBuilder::Build(HWND hwnd, UINT message,
    185                                           WPARAM wparam, LPARAM lparam) {
    186   WebMouseEvent result;
    187 
    188   switch (message) {
    189   case WM_MOUSEMOVE:
    190     result.type = WebInputEvent::MouseMove;
    191     if (wparam & MK_LBUTTON)
    192       result.button = WebMouseEvent::ButtonLeft;
    193     else if (wparam & MK_MBUTTON)
    194       result.button = WebMouseEvent::ButtonMiddle;
    195     else if (wparam & MK_RBUTTON)
    196       result.button = WebMouseEvent::ButtonRight;
    197     else
    198       result.button = WebMouseEvent::ButtonNone;
    199     break;
    200   case WM_MOUSELEAVE:
    201     result.type = WebInputEvent::MouseLeave;
    202     result.button = WebMouseEvent::ButtonNone;
    203     // set the current mouse position (relative to the client area of the
    204     // current window) since none is specified for this event
    205     lparam = GetRelativeCursorPos(hwnd);
    206     break;
    207   case WM_LBUTTONDOWN:
    208   case WM_LBUTTONDBLCLK:
    209     result.type = WebInputEvent::MouseDown;
    210     result.button = WebMouseEvent::ButtonLeft;
    211     break;
    212   case WM_MBUTTONDOWN:
    213   case WM_MBUTTONDBLCLK:
    214     result.type = WebInputEvent::MouseDown;
    215     result.button = WebMouseEvent::ButtonMiddle;
    216     break;
    217   case WM_RBUTTONDOWN:
    218   case WM_RBUTTONDBLCLK:
    219     result.type = WebInputEvent::MouseDown;
    220     result.button = WebMouseEvent::ButtonRight;
    221     break;
    222   case WM_LBUTTONUP:
    223     result.type = WebInputEvent::MouseUp;
    224     result.button = WebMouseEvent::ButtonLeft;
    225     break;
    226   case WM_MBUTTONUP:
    227     result.type = WebInputEvent::MouseUp;
    228     result.button = WebMouseEvent::ButtonMiddle;
    229     break;
    230   case WM_RBUTTONUP:
    231     result.type = WebInputEvent::MouseUp;
    232     result.button = WebMouseEvent::ButtonRight;
    233     break;
    234   default:
    235     NOTREACHED();
    236   }
    237 
    238   // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
    239   // GetMessageTime() refers to is the same one that we're passed in? Perhaps
    240   // one of the construction parameters should be the time passed by the
    241   // caller, who would know for sure.
    242   result.timeStampSeconds = ::GetMessageTime() / 1000.0;
    243 
    244   // set position fields:
    245 
    246   result.x = static_cast<short>(LOWORD(lparam));
    247   result.y = static_cast<short>(HIWORD(lparam));
    248   result.windowX = result.x;
    249   result.windowY = result.y;
    250 
    251   POINT global_point = { result.x, result.y };
    252   ClientToScreen(hwnd, &global_point);
    253 
    254   result.globalX = global_point.x;
    255   result.globalY = global_point.y;
    256 
    257   // calculate number of clicks:
    258 
    259   // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
    260   // where their original code looks buggy.
    261   static int last_click_position_x;
    262   static int last_click_position_y;
    263   static WebMouseEvent::Button last_click_button = WebMouseEvent::ButtonLeft;
    264 
    265   double current_time = result.timeStampSeconds;
    266   bool cancel_previous_click =
    267       (abs(last_click_position_x - result.x) >
    268           (::GetSystemMetrics(SM_CXDOUBLECLK) / 2))
    269       || (abs(last_click_position_y - result.y) >
    270           (::GetSystemMetrics(SM_CYDOUBLECLK) / 2))
    271       || ((current_time - g_last_click_time) * 1000.0 > ::GetDoubleClickTime());
    272 
    273   if (result.type == WebInputEvent::MouseDown) {
    274     if (!cancel_previous_click && (result.button == last_click_button)) {
    275       ++g_last_click_count;
    276     } else {
    277       g_last_click_count = 1;
    278       last_click_position_x = result.x;
    279       last_click_position_y = result.y;
    280     }
    281     g_last_click_time = current_time;
    282     last_click_button = result.button;
    283   } else if (result.type == WebInputEvent::MouseMove
    284           || result.type == WebInputEvent::MouseLeave) {
    285     if (cancel_previous_click) {
    286       g_last_click_count = 0;
    287       last_click_position_x = 0;
    288       last_click_position_y = 0;
    289       g_last_click_time = 0;
    290     }
    291   }
    292   result.clickCount = g_last_click_count;
    293 
    294   // set modifiers:
    295 
    296   if (wparam & MK_CONTROL)
    297     result.modifiers |= WebInputEvent::ControlKey;
    298   if (wparam & MK_SHIFT)
    299     result.modifiers |= WebInputEvent::ShiftKey;
    300   if (::GetKeyState(VK_MENU) & 0x8000)
    301     result.modifiers |= WebInputEvent::AltKey;
    302   if (wparam & MK_LBUTTON)
    303     result.modifiers |= WebInputEvent::LeftButtonDown;
    304   if (wparam & MK_MBUTTON)
    305     result.modifiers |= WebInputEvent::MiddleButtonDown;
    306   if (wparam & MK_RBUTTON)
    307     result.modifiers |= WebInputEvent::RightButtonDown;
    308 
    309   SetToggleKeyState(&result);
    310   return result;
    311 }
    312 
    313 // WebMouseWheelEvent ---------------------------------------------------------
    314 
    315 WebMouseWheelEvent
    316 WebMouseWheelEventBuilder::Build(HWND hwnd, UINT message,
    317                                  WPARAM wparam, LPARAM lparam) {
    318   WebMouseWheelEvent result;
    319 
    320   result.type = WebInputEvent::MouseWheel;
    321 
    322   // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
    323   // GetMessageTime() refers to is the same one that we're passed in? Perhaps
    324   // one of the construction parameters should be the time passed by the
    325   // caller, who would know for sure.
    326   result.timeStampSeconds = ::GetMessageTime() / 1000.0;
    327 
    328   result.button = WebMouseEvent::ButtonNone;
    329 
    330   // Get key state, coordinates, and wheel delta from event.
    331   typedef SHORT (WINAPI *GetKeyStateFunction)(int key);
    332   GetKeyStateFunction get_key_state_func;
    333   UINT key_state;
    334   float wheel_delta;
    335   bool horizontal_scroll = false;
    336   if ((message == WM_VSCROLL) || (message == WM_HSCROLL)) {
    337     // Synthesize mousewheel event from a scroll event.  This is needed to
    338     // simulate middle mouse scrolling in some laptops.  Use GetAsyncKeyState
    339     // for key state since we are synthesizing the input event.
    340     get_key_state_func = GetAsyncKeyState;
    341     key_state = 0;
    342     if (get_key_state_func(VK_SHIFT))
    343       key_state |= MK_SHIFT;
    344     if (get_key_state_func(VK_CONTROL))
    345       key_state |= MK_CONTROL;
    346     // NOTE: There doesn't seem to be a way to query the mouse button state
    347     // in this case.
    348 
    349     POINT cursor_position = {0};
    350     GetCursorPos(&cursor_position);
    351     result.globalX = cursor_position.x;
    352     result.globalY = cursor_position.y;
    353 
    354     switch (LOWORD(wparam)) {
    355       case SB_LINEUP:    // == SB_LINELEFT
    356         wheel_delta = WHEEL_DELTA;
    357         break;
    358       case SB_LINEDOWN:  // == SB_LINERIGHT
    359         wheel_delta = -WHEEL_DELTA;
    360         break;
    361       case SB_PAGEUP:
    362         wheel_delta = 1;
    363         result.scrollByPage = true;
    364         break;
    365       case SB_PAGEDOWN:
    366         wheel_delta = -1;
    367         result.scrollByPage = true;
    368         break;
    369       default:  // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
    370         wheel_delta = 0;
    371         break;
    372     }
    373 
    374     if (message == WM_HSCROLL)
    375       horizontal_scroll = true;
    376   } else {
    377     // Non-synthesized event; we can just read data off the event.
    378     get_key_state_func = ::GetKeyState;
    379     key_state = GET_KEYSTATE_WPARAM(wparam);
    380 
    381     result.globalX = static_cast<short>(LOWORD(lparam));
    382     result.globalY = static_cast<short>(HIWORD(lparam));
    383 
    384     wheel_delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam));
    385     if (message == WM_MOUSEHWHEEL) {
    386       horizontal_scroll = true;
    387       wheel_delta = -wheel_delta;  // Windows is <- -/+ ->, WebKit <- +/- ->.
    388     }
    389   }
    390   if (key_state & MK_SHIFT)
    391     horizontal_scroll = true;
    392 
    393   // Set modifiers based on key state.
    394   if (key_state & MK_SHIFT)
    395     result.modifiers |= WebInputEvent::ShiftKey;
    396   if (key_state & MK_CONTROL)
    397     result.modifiers |= WebInputEvent::ControlKey;
    398   if (get_key_state_func(VK_MENU) & 0x8000)
    399     result.modifiers |= WebInputEvent::AltKey;
    400   if (key_state & MK_LBUTTON)
    401     result.modifiers |= WebInputEvent::LeftButtonDown;
    402   if (key_state & MK_MBUTTON)
    403     result.modifiers |= WebInputEvent::MiddleButtonDown;
    404   if (key_state & MK_RBUTTON)
    405     result.modifiers |= WebInputEvent::RightButtonDown;
    406 
    407   SetToggleKeyState(&result);
    408 
    409   // Set coordinates by translating event coordinates from screen to client.
    410   POINT client_point = { result.globalX, result.globalY };
    411   MapWindowPoints(0, hwnd, &client_point, 1);
    412   result.x = client_point.x;
    413   result.y = client_point.y;
    414   result.windowX = result.x;
    415   result.windowY = result.y;
    416 
    417   // Convert wheel delta amount to a number of pixels to scroll.
    418   //
    419   // How many pixels should we scroll per line?  Gecko uses the height of the
    420   // current line, which means scroll distance changes as you go through the
    421   // page or go to different pages.  IE 8 is ~60 px/line, although the value
    422   // seems to vary slightly by page and zoom level.  Also, IE defaults to
    423   // smooth scrolling while Firefox doesn't, so it can get away with somewhat
    424   // larger scroll values without feeling as jerky.  Here we use 100 px per
    425   // three lines (the default scroll amount is three lines per wheel tick).
    426   // Even though we have smooth scrolling, we don't make this as large as IE
    427   // because subjectively IE feels like it scrolls farther than you want while
    428   // reading articles.
    429   static const float kScrollbarPixelsPerLine = 100.0f / 3.0f;
    430   wheel_delta /= WHEEL_DELTA;
    431   float scroll_delta = wheel_delta;
    432   if (horizontal_scroll) {
    433     unsigned long scroll_chars = kDefaultScrollCharsPerWheelDelta;
    434     SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &scroll_chars, 0);
    435     // TODO(pkasting): Should probably have a different multiplier
    436     // scrollbarPixelsPerChar here.
    437     scroll_delta *= static_cast<float>(scroll_chars) * kScrollbarPixelsPerLine;
    438   } else {
    439     unsigned long scroll_lines = kDefaultScrollLinesPerWheelDelta;
    440     SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scroll_lines, 0);
    441     if (scroll_lines == WHEEL_PAGESCROLL)
    442       result.scrollByPage = true;
    443     if (!result.scrollByPage) {
    444       scroll_delta *=
    445           static_cast<float>(scroll_lines) * kScrollbarPixelsPerLine;
    446     }
    447   }
    448 
    449   // Set scroll amount based on above calculations.  WebKit expects positive
    450   // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
    451   if (horizontal_scroll) {
    452     result.deltaX = scroll_delta;
    453     result.wheelTicksX = wheel_delta;
    454   } else {
    455     result.deltaY = scroll_delta;
    456     result.wheelTicksY = wheel_delta;
    457   }
    458 
    459   return result;
    460 }
    461 
    462 }  // namespace content
    463