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 blink::WebInputEvent;
     11 using blink::WebKeyboardEvent;
     12 using blink::WebMouseEvent;
     13 using blink::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,
    106                                                 UINT message,
    107                                                 WPARAM wparam,
    108                                                 LPARAM lparam,
    109                                                 DWORD time_ms) {
    110   WebKeyboardEvent result;
    111 
    112   DCHECK(time_ms);
    113   result.timeStampSeconds = time_ms / 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   // Bit 30 of lParam represents the "previous key state". If set, the key was
    164   // already down, therefore this is an auto-repeat.
    165   if (lparam & 0x40000000)
    166     result.modifiers |= WebInputEvent::IsAutoRepeat;
    167 
    168   result.modifiers |= GetLocationModifier(wparam, lparam);
    169 
    170   SetToggleKeyState(&result);
    171   return result;
    172 }
    173 
    174 // WebMouseEvent --------------------------------------------------------------
    175 
    176 static int g_last_click_count = 0;
    177 static double g_last_click_time = 0;
    178 
    179 static LPARAM GetRelativeCursorPos(HWND hwnd) {
    180   POINT pos = {-1, -1};
    181   GetCursorPos(&pos);
    182   ScreenToClient(hwnd, &pos);
    183   return MAKELPARAM(pos.x, pos.y);
    184 }
    185 
    186 WebMouseEvent WebMouseEventBuilder::Build(HWND hwnd,
    187                                           UINT message,
    188                                           WPARAM wparam,
    189                                           LPARAM lparam,
    190                                           DWORD time_ms) {
    191   WebMouseEvent result;
    192 
    193   switch (message) {
    194   case WM_MOUSEMOVE:
    195     result.type = WebInputEvent::MouseMove;
    196     if (wparam & MK_LBUTTON)
    197       result.button = WebMouseEvent::ButtonLeft;
    198     else if (wparam & MK_MBUTTON)
    199       result.button = WebMouseEvent::ButtonMiddle;
    200     else if (wparam & MK_RBUTTON)
    201       result.button = WebMouseEvent::ButtonRight;
    202     else
    203       result.button = WebMouseEvent::ButtonNone;
    204     break;
    205   case WM_MOUSELEAVE:
    206     result.type = WebInputEvent::MouseLeave;
    207     result.button = WebMouseEvent::ButtonNone;
    208     // set the current mouse position (relative to the client area of the
    209     // current window) since none is specified for this event
    210     lparam = GetRelativeCursorPos(hwnd);
    211     break;
    212   case WM_LBUTTONDOWN:
    213   case WM_LBUTTONDBLCLK:
    214     result.type = WebInputEvent::MouseDown;
    215     result.button = WebMouseEvent::ButtonLeft;
    216     break;
    217   case WM_MBUTTONDOWN:
    218   case WM_MBUTTONDBLCLK:
    219     result.type = WebInputEvent::MouseDown;
    220     result.button = WebMouseEvent::ButtonMiddle;
    221     break;
    222   case WM_RBUTTONDOWN:
    223   case WM_RBUTTONDBLCLK:
    224     result.type = WebInputEvent::MouseDown;
    225     result.button = WebMouseEvent::ButtonRight;
    226     break;
    227   case WM_LBUTTONUP:
    228     result.type = WebInputEvent::MouseUp;
    229     result.button = WebMouseEvent::ButtonLeft;
    230     break;
    231   case WM_MBUTTONUP:
    232     result.type = WebInputEvent::MouseUp;
    233     result.button = WebMouseEvent::ButtonMiddle;
    234     break;
    235   case WM_RBUTTONUP:
    236     result.type = WebInputEvent::MouseUp;
    237     result.button = WebMouseEvent::ButtonRight;
    238     break;
    239   default:
    240     NOTREACHED();
    241   }
    242 
    243   DCHECK(time_ms);
    244   result.timeStampSeconds = time_ms / 1000.0;
    245 
    246   // set position fields:
    247 
    248   result.x = static_cast<short>(LOWORD(lparam));
    249   result.y = static_cast<short>(HIWORD(lparam));
    250   result.windowX = result.x;
    251   result.windowY = result.y;
    252 
    253   POINT global_point = { result.x, result.y };
    254   ClientToScreen(hwnd, &global_point);
    255 
    256   result.globalX = global_point.x;
    257   result.globalY = global_point.y;
    258 
    259   // calculate number of clicks:
    260 
    261   // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
    262   // where their original code looks buggy.
    263   static int last_click_position_x;
    264   static int last_click_position_y;
    265   static WebMouseEvent::Button last_click_button = WebMouseEvent::ButtonLeft;
    266 
    267   double current_time = result.timeStampSeconds;
    268   bool cancel_previous_click =
    269       (abs(last_click_position_x - result.x) >
    270           (::GetSystemMetrics(SM_CXDOUBLECLK) / 2))
    271       || (abs(last_click_position_y - result.y) >
    272           (::GetSystemMetrics(SM_CYDOUBLECLK) / 2))
    273       || ((current_time - g_last_click_time) * 1000.0 > ::GetDoubleClickTime());
    274 
    275   if (result.type == WebInputEvent::MouseDown) {
    276     if (!cancel_previous_click && (result.button == last_click_button)) {
    277       ++g_last_click_count;
    278     } else {
    279       g_last_click_count = 1;
    280       last_click_position_x = result.x;
    281       last_click_position_y = result.y;
    282     }
    283     g_last_click_time = current_time;
    284     last_click_button = result.button;
    285   } else if (result.type == WebInputEvent::MouseMove
    286           || result.type == WebInputEvent::MouseLeave) {
    287     if (cancel_previous_click) {
    288       g_last_click_count = 0;
    289       last_click_position_x = 0;
    290       last_click_position_y = 0;
    291       g_last_click_time = 0;
    292     }
    293   }
    294   result.clickCount = g_last_click_count;
    295 
    296   // set modifiers:
    297 
    298   if (wparam & MK_CONTROL)
    299     result.modifiers |= WebInputEvent::ControlKey;
    300   if (wparam & MK_SHIFT)
    301     result.modifiers |= WebInputEvent::ShiftKey;
    302   if (::GetKeyState(VK_MENU) & 0x8000)
    303     result.modifiers |= WebInputEvent::AltKey;
    304   if (wparam & MK_LBUTTON)
    305     result.modifiers |= WebInputEvent::LeftButtonDown;
    306   if (wparam & MK_MBUTTON)
    307     result.modifiers |= WebInputEvent::MiddleButtonDown;
    308   if (wparam & MK_RBUTTON)
    309     result.modifiers |= WebInputEvent::RightButtonDown;
    310 
    311   SetToggleKeyState(&result);
    312   return result;
    313 }
    314 
    315 // WebMouseWheelEvent ---------------------------------------------------------
    316 
    317 WebMouseWheelEvent WebMouseWheelEventBuilder::Build(HWND hwnd,
    318                                                     UINT message,
    319                                                     WPARAM wparam,
    320                                                     LPARAM lparam,
    321                                                     DWORD time_ms) {
    322   WebMouseWheelEvent result;
    323 
    324   result.type = WebInputEvent::MouseWheel;
    325 
    326   DCHECK(time_ms);
    327   result.timeStampSeconds = time_ms / 1000.0;
    328 
    329   result.button = WebMouseEvent::ButtonNone;
    330 
    331   // Get key state, coordinates, and wheel delta from event.
    332   typedef SHORT (WINAPI *GetKeyStateFunction)(int key);
    333   GetKeyStateFunction get_key_state_func;
    334   UINT key_state;
    335   float wheel_delta;
    336   bool horizontal_scroll = false;
    337   if ((message == WM_VSCROLL) || (message == WM_HSCROLL)) {
    338     // Synthesize mousewheel event from a scroll event.  This is needed to
    339     // simulate middle mouse scrolling in some laptops.  Use GetAsyncKeyState
    340     // for key state since we are synthesizing the input event.
    341     get_key_state_func = GetAsyncKeyState;
    342     key_state = 0;
    343     if (get_key_state_func(VK_SHIFT) & 0x8000)
    344       key_state |= MK_SHIFT;
    345     if (get_key_state_func(VK_CONTROL) & 0x8000)
    346       key_state |= MK_CONTROL;
    347     // NOTE: There doesn't seem to be a way to query the mouse button state
    348     // in this case.
    349 
    350     POINT cursor_position = {0};
    351     GetCursorPos(&cursor_position);
    352     result.globalX = cursor_position.x;
    353     result.globalY = cursor_position.y;
    354 
    355     switch (LOWORD(wparam)) {
    356       case SB_LINEUP:    // == SB_LINELEFT
    357         wheel_delta = WHEEL_DELTA;
    358         break;
    359       case SB_LINEDOWN:  // == SB_LINERIGHT
    360         wheel_delta = -WHEEL_DELTA;
    361         break;
    362       case SB_PAGEUP:
    363         wheel_delta = 1;
    364         result.scrollByPage = true;
    365         break;
    366       case SB_PAGEDOWN:
    367         wheel_delta = -1;
    368         result.scrollByPage = true;
    369         break;
    370       default:  // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
    371         wheel_delta = 0;
    372         break;
    373     }
    374 
    375     if (message == WM_HSCROLL)
    376       horizontal_scroll = true;
    377   } else {
    378     // Non-synthesized event; we can just read data off the event.
    379     get_key_state_func = ::GetKeyState;
    380     key_state = GET_KEYSTATE_WPARAM(wparam);
    381 
    382     result.globalX = static_cast<short>(LOWORD(lparam));
    383     result.globalY = static_cast<short>(HIWORD(lparam));
    384 
    385     wheel_delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam));
    386     if (message == WM_MOUSEHWHEEL) {
    387       horizontal_scroll = true;
    388       wheel_delta = -wheel_delta;  // Windows is <- -/+ ->, WebKit <- +/- ->.
    389     }
    390   }
    391   if (key_state & MK_SHIFT)
    392     horizontal_scroll = true;
    393 
    394   // Set modifiers based on key state.
    395   if (key_state & MK_SHIFT)
    396     result.modifiers |= WebInputEvent::ShiftKey;
    397   if (key_state & MK_CONTROL)
    398     result.modifiers |= WebInputEvent::ControlKey;
    399   if (get_key_state_func(VK_MENU) & 0x8000)
    400     result.modifiers |= WebInputEvent::AltKey;
    401   if (key_state & MK_LBUTTON)
    402     result.modifiers |= WebInputEvent::LeftButtonDown;
    403   if (key_state & MK_MBUTTON)
    404     result.modifiers |= WebInputEvent::MiddleButtonDown;
    405   if (key_state & MK_RBUTTON)
    406     result.modifiers |= WebInputEvent::RightButtonDown;
    407 
    408   SetToggleKeyState(&result);
    409 
    410   // Set coordinates by translating event coordinates from screen to client.
    411   POINT client_point = { result.globalX, result.globalY };
    412   MapWindowPoints(0, hwnd, &client_point, 1);
    413   result.x = client_point.x;
    414   result.y = client_point.y;
    415   result.windowX = result.x;
    416   result.windowY = result.y;
    417 
    418   // Convert wheel delta amount to a number of pixels to scroll.
    419   //
    420   // How many pixels should we scroll per line?  Gecko uses the height of the
    421   // current line, which means scroll distance changes as you go through the
    422   // page or go to different pages.  IE 8 is ~60 px/line, although the value
    423   // seems to vary slightly by page and zoom level.  Also, IE defaults to
    424   // smooth scrolling while Firefox doesn't, so it can get away with somewhat
    425   // larger scroll values without feeling as jerky.  Here we use 100 px per
    426   // three lines (the default scroll amount is three lines per wheel tick).
    427   // Even though we have smooth scrolling, we don't make this as large as IE
    428   // because subjectively IE feels like it scrolls farther than you want while
    429   // reading articles.
    430   static const float kScrollbarPixelsPerLine = 100.0f / 3.0f;
    431   wheel_delta /= WHEEL_DELTA;
    432   float scroll_delta = wheel_delta;
    433   if (horizontal_scroll) {
    434     unsigned long scroll_chars = kDefaultScrollCharsPerWheelDelta;
    435     SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &scroll_chars, 0);
    436     // TODO(pkasting): Should probably have a different multiplier
    437     // scrollbarPixelsPerChar here.
    438     scroll_delta *= static_cast<float>(scroll_chars) * kScrollbarPixelsPerLine;
    439   } else {
    440     unsigned long scroll_lines = kDefaultScrollLinesPerWheelDelta;
    441     SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scroll_lines, 0);
    442     if (scroll_lines == WHEEL_PAGESCROLL)
    443       result.scrollByPage = true;
    444     if (!result.scrollByPage) {
    445       scroll_delta *=
    446           static_cast<float>(scroll_lines) * kScrollbarPixelsPerLine;
    447     }
    448   }
    449 
    450   // Set scroll amount based on above calculations.  WebKit expects positive
    451   // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
    452   if (horizontal_scroll) {
    453     result.deltaX = scroll_delta;
    454     result.wheelTicksX = wheel_delta;
    455   } else {
    456     result.deltaY = scroll_delta;
    457     result.wheelTicksY = wheel_delta;
    458   }
    459 
    460   return result;
    461 }
    462 
    463 }  // namespace content
    464