Home | History | Annotate | Download | only in web
      1 /*
      2  * Copyright (C) 2006-2009 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "WebInputEventFactory.h"
     33 
     34 #include "WebInputEvent.h"
     35 
     36 #include "wtf/Assertions.h"
     37 
     38 namespace blink {
     39 
     40 static const unsigned long defaultScrollLinesPerWheelDelta = 3;
     41 static const unsigned long defaultScrollCharsPerWheelDelta = 1;
     42 
     43 // WebKeyboardEvent -----------------------------------------------------------
     44 
     45 static bool isKeyDown(WPARAM wparam)
     46 {
     47     return GetKeyState(wparam) & 0x8000;
     48 }
     49 
     50 static int getLocationModifier(WPARAM wparam, LPARAM lparam)
     51 {
     52     int modifier = 0;
     53     switch (wparam) {
     54     case VK_RETURN:
     55         if ((lparam >> 16) & KF_EXTENDED)
     56             modifier = WebInputEvent::IsKeyPad;
     57         break;
     58     case VK_INSERT:
     59     case VK_DELETE:
     60     case VK_HOME:
     61     case VK_END:
     62     case VK_PRIOR:
     63     case VK_NEXT:
     64     case VK_UP:
     65     case VK_DOWN:
     66     case VK_LEFT:
     67     case VK_RIGHT:
     68         if (!((lparam >> 16) & KF_EXTENDED))
     69             modifier = WebInputEvent::IsKeyPad;
     70         break;
     71     case VK_NUMPAD0:
     72     case VK_NUMPAD1:
     73     case VK_NUMPAD2:
     74     case VK_NUMPAD3:
     75     case VK_NUMPAD4:
     76     case VK_NUMPAD5:
     77     case VK_NUMPAD6:
     78     case VK_NUMPAD7:
     79     case VK_NUMPAD8:
     80     case VK_NUMPAD9:
     81     case VK_DIVIDE:
     82     case VK_MULTIPLY:
     83     case VK_SUBTRACT:
     84     case VK_ADD:
     85     case VK_DECIMAL:
     86     case VK_CLEAR:
     87         modifier = WebInputEvent::IsKeyPad;
     88         break;
     89     case VK_SHIFT:
     90         if (isKeyDown(VK_LSHIFT))
     91             modifier = WebInputEvent::IsLeft;
     92         else if (isKeyDown(VK_RSHIFT))
     93             modifier = WebInputEvent::IsRight;
     94         break;
     95     case VK_CONTROL:
     96         if (isKeyDown(VK_LCONTROL))
     97             modifier = WebInputEvent::IsLeft;
     98         else if (isKeyDown(VK_RCONTROL))
     99             modifier = WebInputEvent::IsRight;
    100         break;
    101     case VK_MENU:
    102         if (isKeyDown(VK_LMENU))
    103             modifier = WebInputEvent::IsLeft;
    104         else if (isKeyDown(VK_RMENU))
    105             modifier = WebInputEvent::IsRight;
    106         break;
    107     case VK_LWIN:
    108         modifier = WebInputEvent::IsLeft;
    109         break;
    110     case VK_RWIN:
    111         modifier = WebInputEvent::IsRight;
    112         break;
    113     }
    114 
    115     ASSERT(!modifier
    116            || modifier == WebInputEvent::IsKeyPad
    117            || modifier == WebInputEvent::IsLeft
    118            || modifier == WebInputEvent::IsRight);
    119     return modifier;
    120 }
    121 
    122 // Loads the state for toggle keys into the event.
    123 static void SetToggleKeyState(WebInputEvent* event)
    124 {
    125     // Low bit set from GetKeyState indicates "toggled".
    126     if (::GetKeyState(VK_NUMLOCK) & 1)
    127         event->modifiers |= WebInputEvent::NumLockOn;
    128     if (::GetKeyState(VK_CAPITAL) & 1)
    129         event->modifiers |= WebInputEvent::CapsLockOn;
    130 }
    131 
    132 WebKeyboardEvent WebInputEventFactory::keyboardEvent(HWND hwnd, UINT message,
    133                                                      WPARAM wparam, LPARAM lparam)
    134 {
    135     WebKeyboardEvent result;
    136 
    137     // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
    138     // GetMessageTime() refers to is the same one that we're passed in? Perhaps
    139     // one of the construction parameters should be the time passed by the
    140     // caller, who would know for sure.
    141     result.timeStampSeconds = GetMessageTime() / 1000.0;
    142 
    143     result.windowsKeyCode = static_cast<int>(wparam);
    144     // Record the scan code (along with other context bits) for this key event.
    145     result.nativeKeyCode = static_cast<int>(lparam);
    146 
    147     switch (message) {
    148     case WM_SYSKEYDOWN:
    149         result.isSystemKey = true;
    150     case WM_KEYDOWN:
    151         result.type = WebInputEvent::RawKeyDown;
    152         break;
    153     case WM_SYSKEYUP:
    154         result.isSystemKey = true;
    155     case WM_KEYUP:
    156         result.type = WebInputEvent::KeyUp;
    157         break;
    158     case WM_IME_CHAR:
    159         result.type = WebInputEvent::Char;
    160         break;
    161     case WM_SYSCHAR:
    162         result.isSystemKey = true;
    163         result.type = WebInputEvent::Char;
    164     case WM_CHAR:
    165         result.type = WebInputEvent::Char;
    166         break;
    167     default:
    168         ASSERT_NOT_REACHED();
    169     }
    170 
    171     if (result.type == WebInputEvent::Char || result.type == WebInputEvent::RawKeyDown) {
    172         result.text[0] = result.windowsKeyCode;
    173         result.unmodifiedText[0] = result.windowsKeyCode;
    174     }
    175     if (result.type != WebInputEvent::Char)
    176         result.setKeyIdentifierFromWindowsKeyCode();
    177 
    178     if (GetKeyState(VK_SHIFT) & 0x8000)
    179         result.modifiers |= WebInputEvent::ShiftKey;
    180     if (GetKeyState(VK_CONTROL) & 0x8000)
    181         result.modifiers |= WebInputEvent::ControlKey;
    182     if (GetKeyState(VK_MENU) & 0x8000)
    183         result.modifiers |= WebInputEvent::AltKey;
    184     // NOTE: There doesn't seem to be a way to query the mouse button state in
    185     // this case.
    186 
    187     if (LOWORD(lparam) > 1)
    188         result.modifiers |= WebInputEvent::IsAutoRepeat;
    189 
    190     result.modifiers |= getLocationModifier(wparam, lparam);
    191 
    192     SetToggleKeyState(&result);
    193     return result;
    194 }
    195 
    196 // WebMouseEvent --------------------------------------------------------------
    197 
    198 static int gLastClickCount;
    199 static double gLastClickTime;
    200 
    201 static LPARAM GetRelativeCursorPos(HWND hwnd)
    202 {
    203     POINT pos = {-1, -1};
    204     GetCursorPos(&pos);
    205     ScreenToClient(hwnd, &pos);
    206     return MAKELPARAM(pos.x, pos.y);
    207 }
    208 
    209 void WebInputEventFactory::resetLastClickState()
    210 {
    211     gLastClickTime = gLastClickCount = 0;
    212 }
    213 
    214 bool WebInputEventFactory::isSystemKeyEvent(const WebKeyboardEvent& event)
    215 {
    216     // According to MSDN:
    217     // http://msdn.microsoft.com/en-us/library/ms646286(VS.85).aspx
    218     // Key events with Alt modifier and F10 are system key events.
    219     // We just emulate this behavior. It's necessary to prevent webkit from
    220     // processing keypress event generated by alt-d, etc.
    221     return event.modifiers & WebInputEvent::AltKey || event.windowsKeyCode == VK_F10;
    222 }
    223 
    224 WebMouseEvent WebInputEventFactory::mouseEvent(HWND hwnd, UINT message,
    225                                                WPARAM wparam, LPARAM lparam)
    226 {
    227     WebMouseEvent result; //(WebInputEvent::Uninitialized());
    228 
    229     switch (message) {
    230     case WM_MOUSEMOVE:
    231         result.type = WebInputEvent::MouseMove;
    232         if (wparam & MK_LBUTTON)
    233             result.button = WebMouseEvent::ButtonLeft;
    234         else if (wparam & MK_MBUTTON)
    235             result.button = WebMouseEvent::ButtonMiddle;
    236         else if (wparam & MK_RBUTTON)
    237             result.button = WebMouseEvent::ButtonRight;
    238         else
    239             result.button = WebMouseEvent::ButtonNone;
    240         break;
    241     case WM_MOUSELEAVE:
    242         result.type = WebInputEvent::MouseLeave;
    243         result.button = WebMouseEvent::ButtonNone;
    244         // set the current mouse position (relative to the client area of the
    245         // current window) since none is specified for this event
    246         lparam = GetRelativeCursorPos(hwnd);
    247         break;
    248     case WM_LBUTTONDOWN:
    249     case WM_LBUTTONDBLCLK:
    250         result.type = WebInputEvent::MouseDown;
    251         result.button = WebMouseEvent::ButtonLeft;
    252         break;
    253     case WM_MBUTTONDOWN:
    254     case WM_MBUTTONDBLCLK:
    255         result.type = WebInputEvent::MouseDown;
    256         result.button = WebMouseEvent::ButtonMiddle;
    257         break;
    258     case WM_RBUTTONDOWN:
    259     case WM_RBUTTONDBLCLK:
    260         result.type = WebInputEvent::MouseDown;
    261         result.button = WebMouseEvent::ButtonRight;
    262         break;
    263     case WM_LBUTTONUP:
    264         result.type = WebInputEvent::MouseUp;
    265         result.button = WebMouseEvent::ButtonLeft;
    266         break;
    267     case WM_MBUTTONUP:
    268         result.type = WebInputEvent::MouseUp;
    269         result.button = WebMouseEvent::ButtonMiddle;
    270         break;
    271     case WM_RBUTTONUP:
    272         result.type = WebInputEvent::MouseUp;
    273         result.button = WebMouseEvent::ButtonRight;
    274         break;
    275     default:
    276         ASSERT_NOT_REACHED();
    277     }
    278 
    279     // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
    280     // GetMessageTime() refers to is the same one that we're passed in? Perhaps
    281     // one of the construction parameters should be the time passed by the
    282     // caller, who would know for sure.
    283     result.timeStampSeconds = GetMessageTime() / 1000.0;
    284 
    285     // set position fields:
    286 
    287     result.x = static_cast<short>(LOWORD(lparam));
    288     result.y = static_cast<short>(HIWORD(lparam));
    289     result.windowX = result.x;
    290     result.windowY = result.y;
    291 
    292     POINT globalPoint = { result.x, result.y };
    293     ClientToScreen(hwnd, &globalPoint);
    294 
    295     result.globalX = globalPoint.x;
    296     result.globalY = globalPoint.y;
    297 
    298     // calculate number of clicks:
    299 
    300     // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
    301     // where their original code looks buggy.
    302     static int lastClickPositionX;
    303     static int lastClickPositionY;
    304     static WebMouseEvent::Button lastClickButton = WebMouseEvent::ButtonLeft;
    305 
    306     double currentTime = result.timeStampSeconds;
    307     bool cancelPreviousClick =
    308         (abs(lastClickPositionX - result.x) > (GetSystemMetrics(SM_CXDOUBLECLK) / 2))
    309         || (abs(lastClickPositionY - result.y) > (GetSystemMetrics(SM_CYDOUBLECLK) / 2))
    310         || ((currentTime - gLastClickTime) * 1000.0 > GetDoubleClickTime());
    311 
    312     if (result.type == WebInputEvent::MouseDown) {
    313         if (!cancelPreviousClick && (result.button == lastClickButton))
    314             ++gLastClickCount;
    315         else {
    316             gLastClickCount = 1;
    317             lastClickPositionX = result.x;
    318             lastClickPositionY = result.y;
    319         }
    320         gLastClickTime = currentTime;
    321         lastClickButton = result.button;
    322     } else if (result.type == WebInputEvent::MouseMove
    323                || result.type == WebInputEvent::MouseLeave) {
    324         if (cancelPreviousClick) {
    325             gLastClickCount = 0;
    326             lastClickPositionX = 0;
    327             lastClickPositionY = 0;
    328             gLastClickTime = 0;
    329         }
    330     }
    331     result.clickCount = gLastClickCount;
    332 
    333     // set modifiers:
    334 
    335     if (wparam & MK_CONTROL)
    336         result.modifiers |= WebInputEvent::ControlKey;
    337     if (wparam & MK_SHIFT)
    338         result.modifiers |= WebInputEvent::ShiftKey;
    339     if (GetKeyState(VK_MENU) & 0x8000)
    340         result.modifiers |= WebInputEvent::AltKey;
    341     if (wparam & MK_LBUTTON)
    342         result.modifiers |= WebInputEvent::LeftButtonDown;
    343     if (wparam & MK_MBUTTON)
    344         result.modifiers |= WebInputEvent::MiddleButtonDown;
    345     if (wparam & MK_RBUTTON)
    346         result.modifiers |= WebInputEvent::RightButtonDown;
    347 
    348     SetToggleKeyState(&result);
    349     return result;
    350 }
    351 
    352 // WebMouseWheelEvent ---------------------------------------------------------
    353 
    354 WebMouseWheelEvent WebInputEventFactory::mouseWheelEvent(HWND hwnd, UINT message,
    355                                                          WPARAM wparam, LPARAM lparam)
    356 {
    357     WebMouseWheelEvent result; //(WebInputEvent::Uninitialized());
    358 
    359     result.type = WebInputEvent::MouseWheel;
    360 
    361     // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
    362     // GetMessageTime() refers to is the same one that we're passed in? Perhaps
    363     // one of the construction parameters should be the time passed by the
    364     // caller, who would know for sure.
    365     result.timeStampSeconds = GetMessageTime() / 1000.0;
    366 
    367     result.button = WebMouseEvent::ButtonNone;
    368 
    369     // Get key state, coordinates, and wheel delta from event.
    370     typedef SHORT (WINAPI *GetKeyStateFunction)(int key);
    371     GetKeyStateFunction getKeyState;
    372     UINT keyState;
    373     float wheelDelta;
    374     bool horizontalScroll = false;
    375     if ((message == WM_VSCROLL) || (message == WM_HSCROLL)) {
    376         // Synthesize mousewheel event from a scroll event.  This is needed to
    377         // simulate middle mouse scrolling in some laptops.  Use GetAsyncKeyState
    378         // for key state since we are synthesizing the input event.
    379         getKeyState = GetAsyncKeyState;
    380         keyState = 0;
    381         if (getKeyState(VK_SHIFT))
    382             keyState |= MK_SHIFT;
    383         if (getKeyState(VK_CONTROL))
    384             keyState |= MK_CONTROL;
    385         // NOTE: There doesn't seem to be a way to query the mouse button state
    386         // in this case.
    387 
    388         POINT cursorPosition = {0};
    389         GetCursorPos(&cursorPosition);
    390         result.globalX = cursorPosition.x;
    391         result.globalY = cursorPosition.y;
    392 
    393         switch (LOWORD(wparam)) {
    394         case SB_LINEUP:    // == SB_LINELEFT
    395             wheelDelta = WHEEL_DELTA;
    396             break;
    397         case SB_LINEDOWN:  // == SB_LINERIGHT
    398             wheelDelta = -WHEEL_DELTA;
    399             break;
    400         case SB_PAGEUP:
    401             wheelDelta = 1;
    402             result.scrollByPage = true;
    403             break;
    404         case SB_PAGEDOWN:
    405             wheelDelta = -1;
    406             result.scrollByPage = true;
    407             break;
    408         default:  // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
    409             wheelDelta = 0;
    410             break;
    411         }
    412 
    413         if (message == WM_HSCROLL)
    414             horizontalScroll = true;
    415     } else {
    416         // Non-synthesized event; we can just read data off the event.
    417         getKeyState = GetKeyState;
    418         keyState = GET_KEYSTATE_WPARAM(wparam);
    419 
    420         result.globalX = static_cast<short>(LOWORD(lparam));
    421         result.globalY = static_cast<short>(HIWORD(lparam));
    422 
    423         wheelDelta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam));
    424         if (message == WM_MOUSEHWHEEL) {
    425             horizontalScroll = true;
    426             wheelDelta = -wheelDelta;  // Windows is <- -/+ ->, WebKit <- +/- ->.
    427         }
    428     }
    429     if (keyState & MK_SHIFT)
    430         horizontalScroll = true;
    431 
    432     // Set modifiers based on key state.
    433     if (keyState & MK_SHIFT)
    434         result.modifiers |= WebInputEvent::ShiftKey;
    435     if (keyState & MK_CONTROL)
    436         result.modifiers |= WebInputEvent::ControlKey;
    437     if (getKeyState(VK_MENU) & 0x8000)
    438         result.modifiers |= WebInputEvent::AltKey;
    439     if (keyState & MK_LBUTTON)
    440         result.modifiers |= WebInputEvent::LeftButtonDown;
    441     if (keyState & MK_MBUTTON)
    442         result.modifiers |= WebInputEvent::MiddleButtonDown;
    443     if (keyState & MK_RBUTTON)
    444         result.modifiers |= WebInputEvent::RightButtonDown;
    445 
    446     SetToggleKeyState(&result);
    447 
    448     // Set coordinates by translating event coordinates from screen to client.
    449     POINT clientPoint = { result.globalX, result.globalY };
    450     MapWindowPoints(0, hwnd, &clientPoint, 1);
    451     result.x = clientPoint.x;
    452     result.y = clientPoint.y;
    453     result.windowX = result.x;
    454     result.windowY = result.y;
    455 
    456     // Convert wheel delta amount to a number of pixels to scroll.
    457     //
    458     // How many pixels should we scroll per line?  Gecko uses the height of the
    459     // current line, which means scroll distance changes as you go through the
    460     // page or go to different pages.  IE 8 is ~60 px/line, although the value
    461     // seems to vary slightly by page and zoom level.  Also, IE defaults to
    462     // smooth scrolling while Firefox doesn't, so it can get away with somewhat
    463     // larger scroll values without feeling as jerky.  Here we use 100 px per
    464     // three lines (the default scroll amount is three lines per wheel tick).
    465     // Even though we have smooth scrolling, we don't make this as large as IE
    466     // because subjectively IE feels like it scrolls farther than you want while
    467     // reading articles.
    468     static const float scrollbarPixelsPerLine = 100.0f / 3.0f;
    469     wheelDelta /= WHEEL_DELTA;
    470     float scrollDelta = wheelDelta;
    471     if (horizontalScroll) {
    472         unsigned long scrollChars = defaultScrollCharsPerWheelDelta;
    473         SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &scrollChars, 0);
    474         // TODO(pkasting): Should probably have a different multiplier
    475         // scrollbarPixelsPerChar here.
    476         scrollDelta *= static_cast<float>(scrollChars) * scrollbarPixelsPerLine;
    477     } else {
    478         unsigned long scrollLines = defaultScrollLinesPerWheelDelta;
    479         SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0);
    480         if (scrollLines == WHEEL_PAGESCROLL)
    481             result.scrollByPage = true;
    482         if (!result.scrollByPage)
    483             scrollDelta *= static_cast<float>(scrollLines) * scrollbarPixelsPerLine;
    484     }
    485 
    486     // Set scroll amount based on above calculations.  WebKit expects positive
    487     // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
    488     if (horizontalScroll) {
    489         result.deltaX = scrollDelta;
    490         result.wheelTicksX = wheelDelta;
    491     } else {
    492         result.deltaY = scrollDelta;
    493         result.wheelTicksY = wheelDelta;
    494     }
    495 
    496     return result;
    497 }
    498 
    499 } // namespace blink
    500