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 "KeyboardCodes.h"
35#include "KeyCodeConversion.h"
36
37#include "WebInputEvent.h"
38
39#include <gdk/gdk.h>
40#include <gdk/gdkkeysyms.h>
41#include <gtk/gtk.h>
42#include <gtk/gtkversion.h>
43
44#include <wtf/Assertions.h>
45
46namespace {
47
48// For click count tracking.
49static int gNumClicks = 0;
50static GdkWindow* gLastClickEventWindow = 0;
51static gint gLastClickTime = 0;
52static gint gLastClickX = 0;
53static gint gLastClickY = 0;
54static WebKit::WebMouseEvent::Button gLastClickButton = WebKit::WebMouseEvent::ButtonNone;
55
56bool shouldForgetPreviousClick(GdkWindow* window, gint time, gint x, gint y)
57{
58    static GtkSettings* settings = gtk_settings_get_default();
59
60    if (window != gLastClickEventWindow)
61      return true;
62
63    gint doubleClickTime = 250;
64    gint doubleClickDistance = 5;
65    g_object_get(G_OBJECT(settings),
66                 "gtk-double-click-time", &doubleClickTime,
67                 "gtk-double-click-distance", &doubleClickDistance, NULL);
68    return (time - gLastClickTime) > doubleClickTime
69           || abs(x - gLastClickX) > doubleClickDistance
70           || abs(y - gLastClickY) > doubleClickDistance;
71}
72
73void resetClickCountState()
74{
75    gNumClicks = 0;
76    gLastClickEventWindow = 0;
77    gLastClickTime = 0;
78    gLastClickX = 0;
79    gLastClickY = 0;
80    gLastClickButton = WebKit::WebMouseEvent::ButtonNone;
81}
82
83}  // namespace
84
85namespace WebKit {
86
87static double gdkEventTimeToWebEventTime(guint32 time)
88{
89    // Convert from time in ms to time in sec.
90    return time / 1000.0;
91}
92
93static int gdkStateToWebEventModifiers(guint state)
94{
95    int modifiers = 0;
96    if (state & GDK_SHIFT_MASK)
97        modifiers |= WebInputEvent::ShiftKey;
98    if (state & GDK_CONTROL_MASK)
99        modifiers |= WebInputEvent::ControlKey;
100    if (state & GDK_MOD1_MASK)
101        modifiers |= WebInputEvent::AltKey;
102#if GTK_CHECK_VERSION(2, 10, 0)
103    if (state & GDK_META_MASK)
104        modifiers |= WebInputEvent::MetaKey;
105#endif
106    if (state & GDK_BUTTON1_MASK)
107        modifiers |= WebInputEvent::LeftButtonDown;
108    if (state & GDK_BUTTON2_MASK)
109        modifiers |= WebInputEvent::MiddleButtonDown;
110    if (state & GDK_BUTTON3_MASK)
111        modifiers |= WebInputEvent::RightButtonDown;
112    if (state & GDK_LOCK_MASK)
113        modifiers |= WebInputEvent::CapsLockOn;
114    if (state & GDK_MOD2_MASK)
115        modifiers |= WebInputEvent::NumLockOn;
116    return modifiers;
117}
118
119static int gdkEventToWindowsKeyCode(const GdkEventKey* event)
120{
121    static const unsigned int hardwareCodeToGDKKeyval[] = {
122        0,                 // 0x00:
123        0,                 // 0x01:
124        0,                 // 0x02:
125        0,                 // 0x03:
126        0,                 // 0x04:
127        0,                 // 0x05:
128        0,                 // 0x06:
129        0,                 // 0x07:
130        0,                 // 0x08:
131        0,                 // 0x09: GDK_Escape
132        GDK_1,             // 0x0A: GDK_1
133        GDK_2,             // 0x0B: GDK_2
134        GDK_3,             // 0x0C: GDK_3
135        GDK_4,             // 0x0D: GDK_4
136        GDK_5,             // 0x0E: GDK_5
137        GDK_6,             // 0x0F: GDK_6
138        GDK_7,             // 0x10: GDK_7
139        GDK_8,             // 0x11: GDK_8
140        GDK_9,             // 0x12: GDK_9
141        GDK_0,             // 0x13: GDK_0
142        GDK_minus,         // 0x14: GDK_minus
143        GDK_equal,         // 0x15: GDK_equal
144        0,                 // 0x16: GDK_BackSpace
145        0,                 // 0x17: GDK_Tab
146        GDK_q,             // 0x18: GDK_q
147        GDK_w,             // 0x19: GDK_w
148        GDK_e,             // 0x1A: GDK_e
149        GDK_r,             // 0x1B: GDK_r
150        GDK_t,             // 0x1C: GDK_t
151        GDK_y,             // 0x1D: GDK_y
152        GDK_u,             // 0x1E: GDK_u
153        GDK_i,             // 0x1F: GDK_i
154        GDK_o,             // 0x20: GDK_o
155        GDK_p,             // 0x21: GDK_p
156        GDK_bracketleft,   // 0x22: GDK_bracketleft
157        GDK_bracketright,  // 0x23: GDK_bracketright
158        0,                 // 0x24: GDK_Return
159        0,                 // 0x25: GDK_Control_L
160        GDK_a,             // 0x26: GDK_a
161        GDK_s,             // 0x27: GDK_s
162        GDK_d,             // 0x28: GDK_d
163        GDK_f,             // 0x29: GDK_f
164        GDK_g,             // 0x2A: GDK_g
165        GDK_h,             // 0x2B: GDK_h
166        GDK_j,             // 0x2C: GDK_j
167        GDK_k,             // 0x2D: GDK_k
168        GDK_l,             // 0x2E: GDK_l
169        GDK_semicolon,     // 0x2F: GDK_semicolon
170        GDK_apostrophe,    // 0x30: GDK_apostrophe
171        GDK_grave,         // 0x31: GDK_grave
172        0,                 // 0x32: GDK_Shift_L
173        GDK_backslash,     // 0x33: GDK_backslash
174        GDK_z,             // 0x34: GDK_z
175        GDK_x,             // 0x35: GDK_x
176        GDK_c,             // 0x36: GDK_c
177        GDK_v,             // 0x37: GDK_v
178        GDK_b,             // 0x38: GDK_b
179        GDK_n,             // 0x39: GDK_n
180        GDK_m,             // 0x3A: GDK_m
181        GDK_comma,         // 0x3B: GDK_comma
182        GDK_period,        // 0x3C: GDK_period
183        GDK_slash,         // 0x3D: GDK_slash
184        0,                 // 0x3E: GDK_Shift_R
185        0,                 // 0x3F:
186        0,                 // 0x40:
187        0,                 // 0x41:
188        0,                 // 0x42:
189        0,                 // 0x43:
190        0,                 // 0x44:
191        0,                 // 0x45:
192        0,                 // 0x46:
193        0,                 // 0x47:
194        0,                 // 0x48:
195        0,                 // 0x49:
196        0,                 // 0x4A:
197        0,                 // 0x4B:
198        0,                 // 0x4C:
199        0,                 // 0x4D:
200        0,                 // 0x4E:
201        0,                 // 0x4F:
202        0,                 // 0x50:
203        0,                 // 0x51:
204        0,                 // 0x52:
205        0,                 // 0x53:
206        0,                 // 0x54:
207        0,                 // 0x55:
208        0,                 // 0x56:
209        0,                 // 0x57:
210        0,                 // 0x58:
211        0,                 // 0x59:
212        0,                 // 0x5A:
213        0,                 // 0x5B:
214        0,                 // 0x5C:
215        0,                 // 0x5D:
216        0,                 // 0x5E:
217        0,                 // 0x5F:
218        0,                 // 0x60:
219        0,                 // 0x61:
220        0,                 // 0x62:
221        0,                 // 0x63:
222        0,                 // 0x64:
223        0,                 // 0x65:
224        0,                 // 0x66:
225        0,                 // 0x67:
226        0,                 // 0x68:
227        0,                 // 0x69:
228        0,                 // 0x6A:
229        0,                 // 0x6B:
230        0,                 // 0x6C:
231        0,                 // 0x6D:
232        0,                 // 0x6E:
233        0,                 // 0x6F:
234        0,                 // 0x70:
235        0,                 // 0x71:
236        0,                 // 0x72:
237        GDK_Super_L,       // 0x73: GDK_Super_L
238        GDK_Super_R,       // 0x74: GDK_Super_R
239    };
240
241    // |windowsKeyCode| has to include a valid virtual-key code even when we
242    // use non-US layouts, e.g. even when we type an 'A' key of a US keyboard
243    // on the Hebrew layout, |windowsKeyCode| should be VK_A.
244    // On the other hand, |event->keyval| value depends on the current
245    // GdkKeymap object, i.e. when we type an 'A' key of a US keyboard on
246    // the Hebrew layout, |event->keyval| becomes GDK_hebrew_shin and this
247    // WebCore::windowsKeyCodeForKeyEvent() call returns 0.
248    // To improve compatibilty with Windows, we use |event->hardware_keycode|
249    // for retrieving its Windows key-code for the keys when the
250    // WebCore::windowsKeyCodeForEvent() call returns 0.
251    // We shouldn't use |event->hardware_keycode| for keys that GdkKeymap
252    // objects cannot change because |event->hardware_keycode| doesn't change
253    // even when we change the layout options, e.g. when we swap a control
254    // key and a caps-lock key, GTK doesn't swap their
255    // |event->hardware_keycode| values but swap their |event->keyval| values.
256    int windowsKeyCode = WebCore::windowsKeyCodeForKeyEvent(event->keyval);
257    if (windowsKeyCode)
258        return windowsKeyCode;
259
260    const int tableSize = sizeof(hardwareCodeToGDKKeyval) / sizeof(hardwareCodeToGDKKeyval[0]);
261    if (event->hardware_keycode < tableSize) {
262        int keyval = hardwareCodeToGDKKeyval[event->hardware_keycode];
263        if (keyval)
264            return WebCore::windowsKeyCodeForKeyEvent(keyval);
265    }
266
267    // This key is one that keyboard-layout drivers cannot change.
268    // Use |event->keyval| to retrieve its |windowsKeyCode| value.
269    return WebCore::windowsKeyCodeForKeyEvent(event->keyval);
270}
271
272// Gets the corresponding control character of a specified key code. See:
273// http://en.wikipedia.org/wiki/Control_characters
274// We emulate Windows behavior here.
275static WebUChar getControlCharacter(int windowsKeyCode, bool shift)
276{
277    if (windowsKeyCode >= WebCore::VKEY_A && windowsKeyCode <= WebCore::VKEY_Z) {
278        // ctrl-A ~ ctrl-Z map to \x01 ~ \x1A
279        return windowsKeyCode - WebCore::VKEY_A + 1;
280    }
281    if (shift) {
282        // following graphics chars require shift key to input.
283        switch (windowsKeyCode) {
284        // ctrl-@ maps to \x00 (Null byte)
285        case WebCore::VKEY_2:
286            return 0;
287        // ctrl-^ maps to \x1E (Record separator, Information separator two)
288        case WebCore::VKEY_6:
289            return 0x1E;
290        // ctrl-_ maps to \x1F (Unit separator, Information separator one)
291        case WebCore::VKEY_OEM_MINUS:
292            return 0x1F;
293        // Returns 0 for all other keys to avoid inputting unexpected chars.
294        default:
295            return 0;
296        }
297    } else {
298        switch (windowsKeyCode) {
299        // ctrl-[ maps to \x1B (Escape)
300        case WebCore::VKEY_OEM_4:
301            return 0x1B;
302        // ctrl-\ maps to \x1C (File separator, Information separator four)
303        case WebCore::VKEY_OEM_5:
304            return 0x1C;
305        // ctrl-] maps to \x1D (Group separator, Information separator three)
306        case WebCore::VKEY_OEM_6:
307            return 0x1D;
308        // ctrl-Enter maps to \x0A (Line feed)
309        case WebCore::VKEY_RETURN:
310            return 0x0A;
311        // Returns 0 for all other keys to avoid inputting unexpected chars.
312        default:
313            return 0;
314        }
315    }
316}
317
318// WebKeyboardEvent -----------------------------------------------------------
319
320WebKeyboardEvent WebInputEventFactory::keyboardEvent(const GdkEventKey* event)
321{
322    WebKeyboardEvent result;
323
324    result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time);
325    result.modifiers = gdkStateToWebEventModifiers(event->state);
326
327    switch (event->type) {
328    case GDK_KEY_RELEASE:
329        result.type = WebInputEvent::KeyUp;
330        break;
331    case GDK_KEY_PRESS:
332        result.type = WebInputEvent::RawKeyDown;
333        break;
334    default:
335        ASSERT_NOT_REACHED();
336    }
337
338    // According to MSDN:
339    // http://msdn.microsoft.com/en-us/library/ms646286(VS.85).aspx
340    // Key events with Alt modifier and F10 are system key events.
341    // We just emulate this behavior. It's necessary to prevent webkit from
342    // processing keypress event generated by alt-d, etc.
343    // F10 is not special on Linux, so don't treat it as system key.
344    if (result.modifiers & WebInputEvent::AltKey)
345        result.isSystemKey = true;
346
347    // The key code tells us which physical key was pressed (for example, the
348    // A key went down or up).  It does not determine whether A should be lower
349    // or upper case.  This is what text does, which should be the keyval.
350    result.windowsKeyCode = gdkEventToWindowsKeyCode(event);
351    result.nativeKeyCode = event->hardware_keycode;
352
353    if (result.windowsKeyCode == WebCore::VKEY_RETURN)
354        // We need to treat the enter key as a key press of character \r.  This
355        // is apparently just how webkit handles it and what it expects.
356        result.unmodifiedText[0] = '\r';
357    else
358        // FIXME: fix for non BMP chars
359        result.unmodifiedText[0] =
360            static_cast<WebUChar>(gdk_keyval_to_unicode(event->keyval));
361
362    // If ctrl key is pressed down, then control character shall be input.
363    if (result.modifiers & WebInputEvent::ControlKey)
364        result.text[0] = getControlCharacter(
365            result.windowsKeyCode, result.modifiers & WebInputEvent::ShiftKey);
366    else
367        result.text[0] = result.unmodifiedText[0];
368
369    result.setKeyIdentifierFromWindowsKeyCode();
370
371    // FIXME: Do we need to set IsAutoRepeat or IsKeyPad?
372
373    return result;
374}
375
376WebKeyboardEvent WebInputEventFactory::keyboardEvent(wchar_t character, int state, double timeStampSeconds)
377{
378    // keyboardEvent(const GdkEventKey*) depends on the GdkEventKey object and
379    // it is hard to use/ it from signal handlers which don't use GdkEventKey
380    // objects (e.g. GtkIMContext signal handlers.) For such handlers, this
381    // function creates a WebInputEvent::Char event without using a
382    // GdkEventKey object.
383    WebKeyboardEvent result;
384    result.type = WebKit::WebInputEvent::Char;
385    result.timeStampSeconds = timeStampSeconds;
386    result.modifiers = gdkStateToWebEventModifiers(state);
387    result.windowsKeyCode = character;
388    result.nativeKeyCode = character;
389    result.text[0] = character;
390    result.unmodifiedText[0] = character;
391
392    // According to MSDN:
393    // http://msdn.microsoft.com/en-us/library/ms646286(VS.85).aspx
394    // Key events with Alt modifier and F10 are system key events.
395    // We just emulate this behavior. It's necessary to prevent webkit from
396    // processing keypress event generated by alt-d, etc.
397    // F10 is not special on Linux, so don't treat it as system key.
398    if (result.modifiers & WebInputEvent::AltKey)
399        result.isSystemKey = true;
400
401    return result;
402}
403
404// WebMouseEvent --------------------------------------------------------------
405
406WebMouseEvent WebInputEventFactory::mouseEvent(const GdkEventButton* event)
407{
408    WebMouseEvent result;
409
410    result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time);
411
412    result.modifiers = gdkStateToWebEventModifiers(event->state);
413    result.x = static_cast<int>(event->x);
414    result.y = static_cast<int>(event->y);
415    result.windowX = result.x;
416    result.windowY = result.y;
417    result.globalX = static_cast<int>(event->x_root);
418    result.globalY = static_cast<int>(event->y_root);
419    result.clickCount = 0;
420
421    switch (event->type) {
422    case GDK_BUTTON_PRESS:
423        result.type = WebInputEvent::MouseDown;
424        break;
425    case GDK_BUTTON_RELEASE:
426        result.type = WebInputEvent::MouseUp;
427        break;
428    case GDK_3BUTTON_PRESS:
429    case GDK_2BUTTON_PRESS:
430    default:
431        ASSERT_NOT_REACHED();
432    };
433
434    result.button = WebMouseEvent::ButtonNone;
435    if (event->button == 1)
436        result.button = WebMouseEvent::ButtonLeft;
437    else if (event->button == 2)
438        result.button = WebMouseEvent::ButtonMiddle;
439    else if (event->button == 3)
440        result.button = WebMouseEvent::ButtonRight;
441
442    if (result.type == WebInputEvent::MouseDown) {
443        bool forgetPreviousClick = shouldForgetPreviousClick(event->window, event->time, event->x, event->y);
444
445        if (!forgetPreviousClick && result.button == gLastClickButton)
446            ++gNumClicks;
447        else {
448            gNumClicks = 1;
449
450            gLastClickEventWindow = event->window;
451            gLastClickX = event->x;
452            gLastClickY = event->y;
453            gLastClickButton = result.button;
454        }
455        gLastClickTime = event->time;
456    }
457    result.clickCount = gNumClicks;
458
459    return result;
460}
461
462WebMouseEvent WebInputEventFactory::mouseEvent(const GdkEventMotion* event)
463{
464    WebMouseEvent result;
465
466    result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time);
467    result.modifiers = gdkStateToWebEventModifiers(event->state);
468    result.x = static_cast<int>(event->x);
469    result.y = static_cast<int>(event->y);
470    result.windowX = result.x;
471    result.windowY = result.y;
472    result.globalX = static_cast<int>(event->x_root);
473    result.globalY = static_cast<int>(event->y_root);
474
475    switch (event->type) {
476    case GDK_MOTION_NOTIFY:
477        result.type = WebInputEvent::MouseMove;
478        break;
479    default:
480        ASSERT_NOT_REACHED();
481    }
482
483    result.button = WebMouseEvent::ButtonNone;
484    if (event->state & GDK_BUTTON1_MASK)
485        result.button = WebMouseEvent::ButtonLeft;
486    else if (event->state & GDK_BUTTON2_MASK)
487        result.button = WebMouseEvent::ButtonMiddle;
488    else if (event->state & GDK_BUTTON3_MASK)
489        result.button = WebMouseEvent::ButtonRight;
490
491    if (shouldForgetPreviousClick(event->window, event->time, event->x, event->y))
492        resetClickCountState();
493
494    return result;
495}
496
497WebMouseEvent WebInputEventFactory::mouseEvent(const GdkEventCrossing* event)
498{
499    WebMouseEvent result;
500
501    result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time);
502    result.modifiers = gdkStateToWebEventModifiers(event->state);
503    result.x = static_cast<int>(event->x);
504    result.y = static_cast<int>(event->y);
505    result.windowX = result.x;
506    result.windowY = result.y;
507    result.globalX = static_cast<int>(event->x_root);
508    result.globalY = static_cast<int>(event->y_root);
509
510    switch (event->type) {
511    case GDK_ENTER_NOTIFY:
512    case GDK_LEAVE_NOTIFY:
513        // Note that if we sent MouseEnter or MouseLeave to WebKit, it
514        // wouldn't work - they don't result in the proper JavaScript events.
515        // MouseMove does the right thing.
516        result.type = WebInputEvent::MouseMove;
517        break;
518    default:
519        ASSERT_NOT_REACHED();
520    }
521
522    result.button = WebMouseEvent::ButtonNone;
523    if (event->state & GDK_BUTTON1_MASK)
524        result.button = WebMouseEvent::ButtonLeft;
525    else if (event->state & GDK_BUTTON2_MASK)
526        result.button = WebMouseEvent::ButtonMiddle;
527    else if (event->state & GDK_BUTTON3_MASK)
528        result.button = WebMouseEvent::ButtonRight;
529
530    if (shouldForgetPreviousClick(event->window, event->time, event->x, event->y))
531        resetClickCountState();
532
533    return result;
534}
535
536// WebMouseWheelEvent ---------------------------------------------------------
537
538WebMouseWheelEvent WebInputEventFactory::mouseWheelEvent(const GdkEventScroll* event)
539{
540    WebMouseWheelEvent result;
541
542    result.type = WebInputEvent::MouseWheel;
543    result.button = WebMouseEvent::ButtonNone;
544
545    result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time);
546    result.modifiers = gdkStateToWebEventModifiers(event->state);
547    result.x = static_cast<int>(event->x);
548    result.y = static_cast<int>(event->y);
549    result.windowX = result.x;
550    result.windowY = result.y;
551    result.globalX = static_cast<int>(event->x_root);
552    result.globalY = static_cast<int>(event->y_root);
553
554    // How much should we scroll per mouse wheel event?
555    // - Windows uses 3 lines by default and obeys a system setting.
556    // - Mozilla has a pref that lets you either use the "system" number of lines
557    //   to scroll, or lets the user override it.
558    //   For the "system" number of lines, it appears they've hardcoded 3.
559    //   See case NS_MOUSE_SCROLL in content/events/src/nsEventStateManager.cpp
560    //   and InitMouseScrollEvent in widget/src/gtk2/nsCommonWidget.cpp .
561    // - Gtk makes the scroll amount a function of the size of the scroll bar,
562    //   which is not available to us here.
563    // Instead, we pick a number that empirically matches Firefox's behavior.
564    static const float scrollbarPixelsPerTick = 160.0f / 3.0f;
565
566    switch (event->direction) {
567    case GDK_SCROLL_UP:
568        result.deltaY = scrollbarPixelsPerTick;
569        result.wheelTicksY = 1;
570        break;
571    case GDK_SCROLL_DOWN:
572        result.deltaY = -scrollbarPixelsPerTick;
573        result.wheelTicksY = -1;
574        break;
575    case GDK_SCROLL_LEFT:
576        result.deltaX = scrollbarPixelsPerTick;
577        result.wheelTicksX = 1;
578        break;
579    case GDK_SCROLL_RIGHT:
580        result.deltaX = -scrollbarPixelsPerTick;
581        result.wheelTicksX = -1;
582        break;
583    }
584
585    return result;
586}
587
588} // namespace WebKit
589