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