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