root/Source/web/WebInputEventFactoryWin.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. isKeyDown
  2. getLocationModifier
  3. SetToggleKeyState
  4. keyboardEvent
  5. GetRelativeCursorPos
  6. resetLastClickState
  7. isSystemKeyEvent
  8. mouseEvent
  9. mouseWheelEvent

/*
 * Copyright (C) 2006-2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "WebInputEventFactory.h"

#include "WebInputEvent.h"

#include "wtf/Assertions.h"

namespace blink {

static const unsigned long defaultScrollLinesPerWheelDelta = 3;
static const unsigned long defaultScrollCharsPerWheelDelta = 1;

// WebKeyboardEvent -----------------------------------------------------------

static bool isKeyDown(WPARAM wparam)
{
    return GetKeyState(wparam) & 0x8000;
}

static int getLocationModifier(WPARAM wparam, LPARAM lparam)
{
    int modifier = 0;
    switch (wparam) {
    case VK_RETURN:
        if ((lparam >> 16) & KF_EXTENDED)
            modifier = WebInputEvent::IsKeyPad;
        break;
    case VK_INSERT:
    case VK_DELETE:
    case VK_HOME:
    case VK_END:
    case VK_PRIOR:
    case VK_NEXT:
    case VK_UP:
    case VK_DOWN:
    case VK_LEFT:
    case VK_RIGHT:
        if (!((lparam >> 16) & KF_EXTENDED))
            modifier = WebInputEvent::IsKeyPad;
        break;
    case VK_NUMPAD0:
    case VK_NUMPAD1:
    case VK_NUMPAD2:
    case VK_NUMPAD3:
    case VK_NUMPAD4:
    case VK_NUMPAD5:
    case VK_NUMPAD6:
    case VK_NUMPAD7:
    case VK_NUMPAD8:
    case VK_NUMPAD9:
    case VK_DIVIDE:
    case VK_MULTIPLY:
    case VK_SUBTRACT:
    case VK_ADD:
    case VK_DECIMAL:
    case VK_CLEAR:
        modifier = WebInputEvent::IsKeyPad;
        break;
    case VK_SHIFT:
        if (isKeyDown(VK_LSHIFT))
            modifier = WebInputEvent::IsLeft;
        else if (isKeyDown(VK_RSHIFT))
            modifier = WebInputEvent::IsRight;
        break;
    case VK_CONTROL:
        if (isKeyDown(VK_LCONTROL))
            modifier = WebInputEvent::IsLeft;
        else if (isKeyDown(VK_RCONTROL))
            modifier = WebInputEvent::IsRight;
        break;
    case VK_MENU:
        if (isKeyDown(VK_LMENU))
            modifier = WebInputEvent::IsLeft;
        else if (isKeyDown(VK_RMENU))
            modifier = WebInputEvent::IsRight;
        break;
    case VK_LWIN:
        modifier = WebInputEvent::IsLeft;
        break;
    case VK_RWIN:
        modifier = WebInputEvent::IsRight;
        break;
    }

    ASSERT(!modifier
           || modifier == WebInputEvent::IsKeyPad
           || modifier == WebInputEvent::IsLeft
           || modifier == WebInputEvent::IsRight);
    return modifier;
}

// Loads the state for toggle keys into the event.
static void SetToggleKeyState(WebInputEvent* event)
{
    // Low bit set from GetKeyState indicates "toggled".
    if (::GetKeyState(VK_NUMLOCK) & 1)
        event->modifiers |= WebInputEvent::NumLockOn;
    if (::GetKeyState(VK_CAPITAL) & 1)
        event->modifiers |= WebInputEvent::CapsLockOn;
}

WebKeyboardEvent WebInputEventFactory::keyboardEvent(HWND hwnd, UINT message,
                                                     WPARAM wparam, LPARAM lparam)
{
    WebKeyboardEvent result;

    // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
    // GetMessageTime() refers to is the same one that we're passed in? Perhaps
    // one of the construction parameters should be the time passed by the
    // caller, who would know for sure.
    result.timeStampSeconds = GetMessageTime() / 1000.0;

    result.windowsKeyCode = static_cast<int>(wparam);
    // Record the scan code (along with other context bits) for this key event.
    result.nativeKeyCode = static_cast<int>(lparam);

    switch (message) {
    case WM_SYSKEYDOWN:
        result.isSystemKey = true;
    case WM_KEYDOWN:
        result.type = WebInputEvent::RawKeyDown;
        break;
    case WM_SYSKEYUP:
        result.isSystemKey = true;
    case WM_KEYUP:
        result.type = WebInputEvent::KeyUp;
        break;
    case WM_IME_CHAR:
        result.type = WebInputEvent::Char;
        break;
    case WM_SYSCHAR:
        result.isSystemKey = true;
        result.type = WebInputEvent::Char;
    case WM_CHAR:
        result.type = WebInputEvent::Char;
        break;
    default:
        ASSERT_NOT_REACHED();
    }

    if (result.type == WebInputEvent::Char || result.type == WebInputEvent::RawKeyDown) {
        result.text[0] = result.windowsKeyCode;
        result.unmodifiedText[0] = result.windowsKeyCode;
    }
    if (result.type != WebInputEvent::Char)
        result.setKeyIdentifierFromWindowsKeyCode();

    if (GetKeyState(VK_SHIFT) & 0x8000)
        result.modifiers |= WebInputEvent::ShiftKey;
    if (GetKeyState(VK_CONTROL) & 0x8000)
        result.modifiers |= WebInputEvent::ControlKey;
    if (GetKeyState(VK_MENU) & 0x8000)
        result.modifiers |= WebInputEvent::AltKey;
    // NOTE: There doesn't seem to be a way to query the mouse button state in
    // this case.

    if (LOWORD(lparam) > 1)
        result.modifiers |= WebInputEvent::IsAutoRepeat;

    result.modifiers |= getLocationModifier(wparam, lparam);

    SetToggleKeyState(&result);
    return result;
}

// WebMouseEvent --------------------------------------------------------------

static int gLastClickCount;
static double gLastClickTime;

static LPARAM GetRelativeCursorPos(HWND hwnd)
{
    POINT pos = {-1, -1};
    GetCursorPos(&pos);
    ScreenToClient(hwnd, &pos);
    return MAKELPARAM(pos.x, pos.y);
}

void WebInputEventFactory::resetLastClickState()
{
    gLastClickTime = gLastClickCount = 0;
}

bool WebInputEventFactory::isSystemKeyEvent(const WebKeyboardEvent& event)
{
    // According to MSDN:
    // http://msdn.microsoft.com/en-us/library/ms646286(VS.85).aspx
    // Key events with Alt modifier and F10 are system key events.
    // We just emulate this behavior. It's necessary to prevent webkit from
    // processing keypress event generated by alt-d, etc.
    return event.modifiers & WebInputEvent::AltKey || event.windowsKeyCode == VK_F10;
}

WebMouseEvent WebInputEventFactory::mouseEvent(HWND hwnd, UINT message,
                                               WPARAM wparam, LPARAM lparam)
{
    WebMouseEvent result; //(WebInputEvent::Uninitialized());

    switch (message) {
    case WM_MOUSEMOVE:
        result.type = WebInputEvent::MouseMove;
        if (wparam & MK_LBUTTON)
            result.button = WebMouseEvent::ButtonLeft;
        else if (wparam & MK_MBUTTON)
            result.button = WebMouseEvent::ButtonMiddle;
        else if (wparam & MK_RBUTTON)
            result.button = WebMouseEvent::ButtonRight;
        else
            result.button = WebMouseEvent::ButtonNone;
        break;
    case WM_MOUSELEAVE:
        result.type = WebInputEvent::MouseLeave;
        result.button = WebMouseEvent::ButtonNone;
        // set the current mouse position (relative to the client area of the
        // current window) since none is specified for this event
        lparam = GetRelativeCursorPos(hwnd);
        break;
    case WM_LBUTTONDOWN:
    case WM_LBUTTONDBLCLK:
        result.type = WebInputEvent::MouseDown;
        result.button = WebMouseEvent::ButtonLeft;
        break;
    case WM_MBUTTONDOWN:
    case WM_MBUTTONDBLCLK:
        result.type = WebInputEvent::MouseDown;
        result.button = WebMouseEvent::ButtonMiddle;
        break;
    case WM_RBUTTONDOWN:
    case WM_RBUTTONDBLCLK:
        result.type = WebInputEvent::MouseDown;
        result.button = WebMouseEvent::ButtonRight;
        break;
    case WM_LBUTTONUP:
        result.type = WebInputEvent::MouseUp;
        result.button = WebMouseEvent::ButtonLeft;
        break;
    case WM_MBUTTONUP:
        result.type = WebInputEvent::MouseUp;
        result.button = WebMouseEvent::ButtonMiddle;
        break;
    case WM_RBUTTONUP:
        result.type = WebInputEvent::MouseUp;
        result.button = WebMouseEvent::ButtonRight;
        break;
    default:
        ASSERT_NOT_REACHED();
    }

    // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
    // GetMessageTime() refers to is the same one that we're passed in? Perhaps
    // one of the construction parameters should be the time passed by the
    // caller, who would know for sure.
    result.timeStampSeconds = GetMessageTime() / 1000.0;

    // set position fields:

    result.x = static_cast<short>(LOWORD(lparam));
    result.y = static_cast<short>(HIWORD(lparam));
    result.windowX = result.x;
    result.windowY = result.y;

    POINT globalPoint = { result.x, result.y };
    ClientToScreen(hwnd, &globalPoint);

    result.globalX = globalPoint.x;
    result.globalY = globalPoint.y;

    // calculate number of clicks:

    // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
    // where their original code looks buggy.
    static int lastClickPositionX;
    static int lastClickPositionY;
    static WebMouseEvent::Button lastClickButton = WebMouseEvent::ButtonLeft;

    double currentTime = result.timeStampSeconds;
    bool cancelPreviousClick =
        (abs(lastClickPositionX - result.x) > (GetSystemMetrics(SM_CXDOUBLECLK) / 2))
        || (abs(lastClickPositionY - result.y) > (GetSystemMetrics(SM_CYDOUBLECLK) / 2))
        || ((currentTime - gLastClickTime) * 1000.0 > GetDoubleClickTime());

    if (result.type == WebInputEvent::MouseDown) {
        if (!cancelPreviousClick && (result.button == lastClickButton))
            ++gLastClickCount;
        else {
            gLastClickCount = 1;
            lastClickPositionX = result.x;
            lastClickPositionY = result.y;
        }
        gLastClickTime = currentTime;
        lastClickButton = result.button;
    } else if (result.type == WebInputEvent::MouseMove
               || result.type == WebInputEvent::MouseLeave) {
        if (cancelPreviousClick) {
            gLastClickCount = 0;
            lastClickPositionX = 0;
            lastClickPositionY = 0;
            gLastClickTime = 0;
        }
    }
    result.clickCount = gLastClickCount;

    // set modifiers:

    if (wparam & MK_CONTROL)
        result.modifiers |= WebInputEvent::ControlKey;
    if (wparam & MK_SHIFT)
        result.modifiers |= WebInputEvent::ShiftKey;
    if (GetKeyState(VK_MENU) & 0x8000)
        result.modifiers |= WebInputEvent::AltKey;
    if (wparam & MK_LBUTTON)
        result.modifiers |= WebInputEvent::LeftButtonDown;
    if (wparam & MK_MBUTTON)
        result.modifiers |= WebInputEvent::MiddleButtonDown;
    if (wparam & MK_RBUTTON)
        result.modifiers |= WebInputEvent::RightButtonDown;

    SetToggleKeyState(&result);
    return result;
}

// WebMouseWheelEvent ---------------------------------------------------------

WebMouseWheelEvent WebInputEventFactory::mouseWheelEvent(HWND hwnd, UINT message,
                                                         WPARAM wparam, LPARAM lparam)
{
    WebMouseWheelEvent result; //(WebInputEvent::Uninitialized());

    result.type = WebInputEvent::MouseWheel;

    // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
    // GetMessageTime() refers to is the same one that we're passed in? Perhaps
    // one of the construction parameters should be the time passed by the
    // caller, who would know for sure.
    result.timeStampSeconds = GetMessageTime() / 1000.0;

    result.button = WebMouseEvent::ButtonNone;

    // Get key state, coordinates, and wheel delta from event.
    typedef SHORT (WINAPI *GetKeyStateFunction)(int key);
    GetKeyStateFunction getKeyState;
    UINT keyState;
    float wheelDelta;
    bool horizontalScroll = false;
    if ((message == WM_VSCROLL) || (message == WM_HSCROLL)) {
        // Synthesize mousewheel event from a scroll event.  This is needed to
        // simulate middle mouse scrolling in some laptops.  Use GetAsyncKeyState
        // for key state since we are synthesizing the input event.
        getKeyState = GetAsyncKeyState;
        keyState = 0;
        if (getKeyState(VK_SHIFT))
            keyState |= MK_SHIFT;
        if (getKeyState(VK_CONTROL))
            keyState |= MK_CONTROL;
        // NOTE: There doesn't seem to be a way to query the mouse button state
        // in this case.

        POINT cursorPosition = {0};
        GetCursorPos(&cursorPosition);
        result.globalX = cursorPosition.x;
        result.globalY = cursorPosition.y;

        switch (LOWORD(wparam)) {
        case SB_LINEUP:    // == SB_LINELEFT
            wheelDelta = WHEEL_DELTA;
            break;
        case SB_LINEDOWN:  // == SB_LINERIGHT
            wheelDelta = -WHEEL_DELTA;
            break;
        case SB_PAGEUP:
            wheelDelta = 1;
            result.scrollByPage = true;
            break;
        case SB_PAGEDOWN:
            wheelDelta = -1;
            result.scrollByPage = true;
            break;
        default:  // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
            wheelDelta = 0;
            break;
        }

        if (message == WM_HSCROLL)
            horizontalScroll = true;
    } else {
        // Non-synthesized event; we can just read data off the event.
        getKeyState = GetKeyState;
        keyState = GET_KEYSTATE_WPARAM(wparam);

        result.globalX = static_cast<short>(LOWORD(lparam));
        result.globalY = static_cast<short>(HIWORD(lparam));

        wheelDelta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam));
        if (message == WM_MOUSEHWHEEL) {
            horizontalScroll = true;
            wheelDelta = -wheelDelta;  // Windows is <- -/+ ->, WebKit <- +/- ->.
        }
    }
    if (keyState & MK_SHIFT)
        horizontalScroll = true;

    // Set modifiers based on key state.
    if (keyState & MK_SHIFT)
        result.modifiers |= WebInputEvent::ShiftKey;
    if (keyState & MK_CONTROL)
        result.modifiers |= WebInputEvent::ControlKey;
    if (getKeyState(VK_MENU) & 0x8000)
        result.modifiers |= WebInputEvent::AltKey;
    if (keyState & MK_LBUTTON)
        result.modifiers |= WebInputEvent::LeftButtonDown;
    if (keyState & MK_MBUTTON)
        result.modifiers |= WebInputEvent::MiddleButtonDown;
    if (keyState & MK_RBUTTON)
        result.modifiers |= WebInputEvent::RightButtonDown;

    SetToggleKeyState(&result);

    // Set coordinates by translating event coordinates from screen to client.
    POINT clientPoint = { result.globalX, result.globalY };
    MapWindowPoints(0, hwnd, &clientPoint, 1);
    result.x = clientPoint.x;
    result.y = clientPoint.y;
    result.windowX = result.x;
    result.windowY = result.y;

    // Convert wheel delta amount to a number of pixels to scroll.
    //
    // How many pixels should we scroll per line?  Gecko uses the height of the
    // current line, which means scroll distance changes as you go through the
    // page or go to different pages.  IE 8 is ~60 px/line, although the value
    // seems to vary slightly by page and zoom level.  Also, IE defaults to
    // smooth scrolling while Firefox doesn't, so it can get away with somewhat
    // larger scroll values without feeling as jerky.  Here we use 100 px per
    // three lines (the default scroll amount is three lines per wheel tick).
    // Even though we have smooth scrolling, we don't make this as large as IE
    // because subjectively IE feels like it scrolls farther than you want while
    // reading articles.
    static const float scrollbarPixelsPerLine = 100.0f / 3.0f;
    wheelDelta /= WHEEL_DELTA;
    float scrollDelta = wheelDelta;
    if (horizontalScroll) {
        unsigned long scrollChars = defaultScrollCharsPerWheelDelta;
        SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &scrollChars, 0);
        // TODO(pkasting): Should probably have a different multiplier
        // scrollbarPixelsPerChar here.
        scrollDelta *= static_cast<float>(scrollChars) * scrollbarPixelsPerLine;
    } else {
        unsigned long scrollLines = defaultScrollLinesPerWheelDelta;
        SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0);
        if (scrollLines == WHEEL_PAGESCROLL)
            result.scrollByPage = true;
        if (!result.scrollByPage)
            scrollDelta *= static_cast<float>(scrollLines) * scrollbarPixelsPerLine;
    }

    // Set scroll amount based on above calculations.  WebKit expects positive
    // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
    if (horizontalScroll) {
        result.deltaX = scrollDelta;
        result.wheelTicksX = wheelDelta;
    } else {
        result.deltaY = scrollDelta;
        result.wheelTicksY = wheelDelta;
    }

    return result;
}

} // namespace blink

/* [<][>][^][v][top][bottom][index][help] */