1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/renderer_host/input/web_input_event_builders_gtk.h"
8 #include <gdk/gdkkeysyms.h>
11 #include "base/logging.h"
12 #include "content/browser/renderer_host/input/web_input_event_util_posix.h"
13 #include "third_party/WebKit/public/web/WebInputEvent.h"
14 #include "ui/events/keycodes/keyboard_code_conversion_gtk.h"
16 using blink::WebInputEvent
;
17 using blink::WebMouseEvent
;
18 using blink::WebMouseWheelEvent
;
19 using blink::WebKeyboardEvent
;
23 // For click count tracking.
24 static int num_clicks
= 0;
25 static GdkWindow
* last_click_event_window
= 0;
26 static gint last_click_time
= 0;
27 static gint last_click_x
= 0;
28 static gint last_click_y
= 0;
29 static WebMouseEvent::Button last_click_button
= WebMouseEvent::ButtonNone
;
31 bool ShouldForgetPreviousClick(GdkWindow
* window
, gint time
, gint x
, gint y
) {
32 static GtkSettings
* settings
= gtk_settings_get_default();
34 if (window
!= last_click_event_window
)
37 gint double_click_time
= 250;
38 gint double_click_distance
= 5;
39 g_object_get(G_OBJECT(settings
),
40 "gtk-double-click-time",
42 "gtk-double-click-distance",
43 &double_click_distance
,
45 return (time
- last_click_time
) > double_click_time
||
46 std::abs(x
- last_click_x
) > double_click_distance
||
47 std::abs(y
- last_click_y
) > double_click_distance
;
50 void ResetClickCountState() {
52 last_click_event_window
= 0;
56 last_click_button
= blink::WebMouseEvent::ButtonNone
;
59 bool IsKeyPadKeyval(guint keyval
) {
60 // Keypad keyvals all fall into one range.
61 return keyval
>= GDK_KP_Space
&& keyval
<= GDK_KP_9
;
64 double GdkEventTimeToWebEventTime(guint32 time
) {
65 // Convert from time in ms to time in sec.
69 int GdkStateToWebEventModifiers(guint state
) {
71 if (state
& GDK_SHIFT_MASK
)
72 modifiers
|= WebInputEvent::ShiftKey
;
73 if (state
& GDK_CONTROL_MASK
)
74 modifiers
|= WebInputEvent::ControlKey
;
75 if (state
& GDK_MOD1_MASK
)
76 modifiers
|= WebInputEvent::AltKey
;
77 if (state
& GDK_META_MASK
)
78 modifiers
|= WebInputEvent::MetaKey
;
79 if (state
& GDK_BUTTON1_MASK
)
80 modifiers
|= WebInputEvent::LeftButtonDown
;
81 if (state
& GDK_BUTTON2_MASK
)
82 modifiers
|= WebInputEvent::MiddleButtonDown
;
83 if (state
& GDK_BUTTON3_MASK
)
84 modifiers
|= WebInputEvent::RightButtonDown
;
85 if (state
& GDK_LOCK_MASK
)
86 modifiers
|= WebInputEvent::CapsLockOn
;
87 if (state
& GDK_MOD2_MASK
)
88 modifiers
|= WebInputEvent::NumLockOn
;
92 ui::KeyboardCode
GdkEventToWindowsKeyCode(const GdkEventKey
* event
) {
93 static const unsigned int kHardwareCodeToGDKKeyval
[] = {
103 0, // 0x09: GDK_Escape
104 GDK_1
, // 0x0A: GDK_1
105 GDK_2
, // 0x0B: GDK_2
106 GDK_3
, // 0x0C: GDK_3
107 GDK_4
, // 0x0D: GDK_4
108 GDK_5
, // 0x0E: GDK_5
109 GDK_6
, // 0x0F: GDK_6
110 GDK_7
, // 0x10: GDK_7
111 GDK_8
, // 0x11: GDK_8
112 GDK_9
, // 0x12: GDK_9
113 GDK_0
, // 0x13: GDK_0
114 GDK_minus
, // 0x14: GDK_minus
115 GDK_equal
, // 0x15: GDK_equal
116 0, // 0x16: GDK_BackSpace
118 GDK_q
, // 0x18: GDK_q
119 GDK_w
, // 0x19: GDK_w
120 GDK_e
, // 0x1A: GDK_e
121 GDK_r
, // 0x1B: GDK_r
122 GDK_t
, // 0x1C: GDK_t
123 GDK_y
, // 0x1D: GDK_y
124 GDK_u
, // 0x1E: GDK_u
125 GDK_i
, // 0x1F: GDK_i
126 GDK_o
, // 0x20: GDK_o
127 GDK_p
, // 0x21: GDK_p
128 GDK_bracketleft
, // 0x22: GDK_bracketleft
129 GDK_bracketright
, // 0x23: GDK_bracketright
130 0, // 0x24: GDK_Return
131 0, // 0x25: GDK_Control_L
132 GDK_a
, // 0x26: GDK_a
133 GDK_s
, // 0x27: GDK_s
134 GDK_d
, // 0x28: GDK_d
135 GDK_f
, // 0x29: GDK_f
136 GDK_g
, // 0x2A: GDK_g
137 GDK_h
, // 0x2B: GDK_h
138 GDK_j
, // 0x2C: GDK_j
139 GDK_k
, // 0x2D: GDK_k
140 GDK_l
, // 0x2E: GDK_l
141 GDK_semicolon
, // 0x2F: GDK_semicolon
142 GDK_apostrophe
, // 0x30: GDK_apostrophe
143 GDK_grave
, // 0x31: GDK_grave
144 0, // 0x32: GDK_Shift_L
145 GDK_backslash
, // 0x33: GDK_backslash
146 GDK_z
, // 0x34: GDK_z
147 GDK_x
, // 0x35: GDK_x
148 GDK_c
, // 0x36: GDK_c
149 GDK_v
, // 0x37: GDK_v
150 GDK_b
, // 0x38: GDK_b
151 GDK_n
, // 0x39: GDK_n
152 GDK_m
, // 0x3A: GDK_m
153 GDK_comma
, // 0x3B: GDK_comma
154 GDK_period
, // 0x3C: GDK_period
155 GDK_slash
, // 0x3D: GDK_slash
156 0, // 0x3E: GDK_Shift_R
209 GDK_Super_L
, // 0x73: GDK_Super_L
210 GDK_Super_R
, // 0x74: GDK_Super_R
213 // |windows_key_code| has to include a valid virtual-key code even when we
214 // use non-US layouts, e.g. even when we type an 'A' key of a US keyboard
215 // on the Hebrew layout, |windows_key_code| should be VK_A.
216 // On the other hand, |event->keyval| value depends on the current
217 // GdkKeymap object, i.e. when we type an 'A' key of a US keyboard on
218 // the Hebrew layout, |event->keyval| becomes GDK_hebrew_shin and this
219 // ui::WindowsKeyCodeForGdkKeyCode() call returns 0.
220 // To improve compatibilty with Windows, we use |event->hardware_keycode|
221 // for retrieving its Windows key-code for the keys when the
222 // WebCore::windows_key_codeForEvent() call returns 0.
223 // We shouldn't use |event->hardware_keycode| for keys that GdkKeymap
224 // objects cannot change because |event->hardware_keycode| doesn't change
225 // even when we change the layout options, e.g. when we swap a control
226 // key and a caps-lock key, GTK doesn't swap their
227 // |event->hardware_keycode| values but swap their |event->keyval| values.
228 ui::KeyboardCode windows_key_code
=
229 ui::WindowsKeyCodeForGdkKeyCode(event
->keyval
);
230 if (windows_key_code
)
231 return windows_key_code
;
233 if (event
->hardware_keycode
< arraysize(kHardwareCodeToGDKKeyval
)) {
234 int keyval
= kHardwareCodeToGDKKeyval
[event
->hardware_keycode
];
236 return ui::WindowsKeyCodeForGdkKeyCode(keyval
);
239 // This key is one that keyboard-layout drivers cannot change.
240 // Use |event->keyval| to retrieve its |windows_key_code| value.
241 return ui::WindowsKeyCodeForGdkKeyCode(event
->keyval
);
244 // Normalizes event->state to make it Windows/Mac compatible. Since the way
245 // of setting modifier mask on X is very different than Windows/Mac as shown
246 // in http://crbug.com/127142#c8, the normalization is necessary.
247 guint
NormalizeEventState(const GdkEventKey
* event
) {
249 switch (GdkEventToWindowsKeyCode(event
)) {
250 case ui::VKEY_CONTROL
:
251 case ui::VKEY_LCONTROL
:
252 case ui::VKEY_RCONTROL
:
253 mask
= GDK_CONTROL_MASK
;
256 case ui::VKEY_LSHIFT
:
257 case ui::VKEY_RSHIFT
:
258 mask
= GDK_SHIFT_MASK
;
263 mask
= GDK_MOD1_MASK
;
265 case ui::VKEY_CAPITAL
:
266 mask
= GDK_LOCK_MASK
;
271 if (event
->type
== GDK_KEY_PRESS
)
272 return event
->state
| mask
;
273 return event
->state
& ~mask
;
276 // Gets the corresponding control character of a specified key code. See:
277 // http://en.wikipedia.org/wiki/Control_characters
278 // We emulate Windows behavior here.
279 int GetControlCharacter(ui::KeyboardCode windows_key_code
, bool shift
) {
280 if (windows_key_code
>= ui::VKEY_A
&& windows_key_code
<= ui::VKEY_Z
) {
281 // ctrl-A ~ ctrl-Z map to \x01 ~ \x1A
282 return windows_key_code
- ui::VKEY_A
+ 1;
285 // following graphics chars require shift key to input.
286 switch (windows_key_code
) {
287 // ctrl-@ maps to \x00 (Null byte)
290 // ctrl-^ maps to \x1E (Record separator, Information separator two)
293 // ctrl-_ maps to \x1F (Unit separator, Information separator one)
294 case ui::VKEY_OEM_MINUS
:
296 // Returns 0 for all other keys to avoid inputting unexpected chars.
301 switch (windows_key_code
) {
302 // ctrl-[ maps to \x1B (Escape)
305 // ctrl-\ maps to \x1C (File separator, Information separator four)
308 // ctrl-] maps to \x1D (Group separator, Information separator three)
311 // ctrl-Enter maps to \x0A (Line feed)
312 case ui::VKEY_RETURN
:
314 // Returns 0 for all other keys to avoid inputting unexpected chars.
325 // WebKeyboardEvent -----------------------------------------------------------
327 WebKeyboardEvent
WebKeyboardEventBuilder::Build(const GdkEventKey
* event
) {
328 WebKeyboardEvent result
;
330 result
.timeStampSeconds
= GdkEventTimeToWebEventTime(event
->time
);
331 result
.modifiers
= GdkStateToWebEventModifiers(NormalizeEventState(event
));
333 switch (event
->type
) {
334 case GDK_KEY_RELEASE
:
335 result
.type
= WebInputEvent::KeyUp
;
338 result
.type
= WebInputEvent::RawKeyDown
;
344 // According to MSDN:
345 // http://msdn.microsoft.com/en-us/library/ms646286(VS.85).aspx
346 // Key events with Alt modifier and F10 are system key events.
347 // We just emulate this behavior. It's necessary to prevent webkit from
348 // processing keypress event generated by alt-d, etc.
349 // F10 is not special on Linux, so don't treat it as system key.
350 if (result
.modifiers
& WebInputEvent::AltKey
)
351 result
.isSystemKey
= true;
353 // The key code tells us which physical key was pressed (for example, the
354 // A key went down or up). It does not determine whether A should be lower
355 // or upper case. This is what text does, which should be the keyval.
356 ui::KeyboardCode windows_key_code
= GdkEventToWindowsKeyCode(event
);
357 result
.windowsKeyCode
= GetWindowsKeyCodeWithoutLocation(windows_key_code
);
358 result
.modifiers
|= GetLocationModifiersFromWindowsKeyCode(windows_key_code
);
359 result
.nativeKeyCode
= event
->hardware_keycode
;
361 if (result
.windowsKeyCode
== ui::VKEY_RETURN
) {
362 // We need to treat the enter key as a key press of character \r. This
363 // is apparently just how webkit handles it and what it expects.
364 result
.unmodifiedText
[0] = '\r';
366 // FIXME: fix for non BMP chars
367 result
.unmodifiedText
[0] =
368 static_cast<int>(gdk_keyval_to_unicode(event
->keyval
));
371 // If ctrl key is pressed down, then control character shall be input.
372 if (result
.modifiers
& WebInputEvent::ControlKey
) {
374 GetControlCharacter(ui::KeyboardCode(result
.windowsKeyCode
),
375 result
.modifiers
& WebInputEvent::ShiftKey
);
377 result
.text
[0] = result
.unmodifiedText
[0];
380 result
.setKeyIdentifierFromWindowsKeyCode();
382 // FIXME: Do we need to set IsAutoRepeat?
383 if (IsKeyPadKeyval(event
->keyval
))
384 result
.modifiers
|= WebInputEvent::IsKeyPad
;
389 WebKeyboardEvent
WebKeyboardEventBuilder::Build(wchar_t character
,
391 double timeStampSeconds
) {
392 // keyboardEvent(const GdkEventKey*) depends on the GdkEventKey object and
393 // it is hard to use/ it from signal handlers which don't use GdkEventKey
394 // objects (e.g. GtkIMContext signal handlers.) For such handlers, this
395 // function creates a WebInputEvent::Char event without using a
396 // GdkEventKey object.
397 WebKeyboardEvent result
;
398 result
.type
= blink::WebInputEvent::Char
;
399 result
.timeStampSeconds
= timeStampSeconds
;
400 result
.modifiers
= GdkStateToWebEventModifiers(state
);
401 result
.windowsKeyCode
= character
;
402 result
.nativeKeyCode
= character
;
403 result
.text
[0] = character
;
404 result
.unmodifiedText
[0] = character
;
406 // According to MSDN:
407 // http://msdn.microsoft.com/en-us/library/ms646286(VS.85).aspx
408 // Key events with Alt modifier and F10 are system key events.
409 // We just emulate this behavior. It's necessary to prevent webkit from
410 // processing keypress event generated by alt-d, etc.
411 // F10 is not special on Linux, so don't treat it as system key.
412 if (result
.modifiers
& WebInputEvent::AltKey
)
413 result
.isSystemKey
= true;
418 // WebMouseEvent --------------------------------------------------------------
420 WebMouseEvent
WebMouseEventBuilder::Build(const GdkEventButton
* event
) {
421 WebMouseEvent result
;
423 result
.timeStampSeconds
= GdkEventTimeToWebEventTime(event
->time
);
425 result
.modifiers
= GdkStateToWebEventModifiers(event
->state
);
426 result
.x
= static_cast<int>(event
->x
);
427 result
.y
= static_cast<int>(event
->y
);
428 result
.windowX
= result
.x
;
429 result
.windowY
= result
.y
;
430 result
.globalX
= static_cast<int>(event
->x_root
);
431 result
.globalY
= static_cast<int>(event
->y_root
);
432 result
.clickCount
= 0;
434 switch (event
->type
) {
435 case GDK_BUTTON_PRESS
:
436 result
.type
= WebInputEvent::MouseDown
;
438 case GDK_BUTTON_RELEASE
:
439 result
.type
= WebInputEvent::MouseUp
;
441 case GDK_3BUTTON_PRESS
:
442 case GDK_2BUTTON_PRESS
:
447 result
.button
= WebMouseEvent::ButtonNone
;
448 if (event
->button
== 1)
449 result
.button
= WebMouseEvent::ButtonLeft
;
450 else if (event
->button
== 2)
451 result
.button
= WebMouseEvent::ButtonMiddle
;
452 else if (event
->button
== 3)
453 result
.button
= WebMouseEvent::ButtonRight
;
455 if (result
.type
== WebInputEvent::MouseDown
) {
456 bool forgetPreviousClick
= ShouldForgetPreviousClick(
457 event
->window
, event
->time
, event
->x
, event
->y
);
459 if (!forgetPreviousClick
&& result
.button
== last_click_button
) {
464 last_click_event_window
= event
->window
;
465 last_click_x
= event
->x
;
466 last_click_y
= event
->y
;
467 last_click_button
= result
.button
;
469 last_click_time
= event
->time
;
471 result
.clickCount
= num_clicks
;
476 WebMouseEvent
WebMouseEventBuilder::Build(const GdkEventMotion
* event
) {
477 WebMouseEvent result
;
479 result
.timeStampSeconds
= GdkEventTimeToWebEventTime(event
->time
);
480 result
.modifiers
= GdkStateToWebEventModifiers(event
->state
);
481 result
.x
= static_cast<int>(event
->x
);
482 result
.y
= static_cast<int>(event
->y
);
483 result
.windowX
= result
.x
;
484 result
.windowY
= result
.y
;
485 result
.globalX
= static_cast<int>(event
->x_root
);
486 result
.globalY
= static_cast<int>(event
->y_root
);
488 switch (event
->type
) {
489 case GDK_MOTION_NOTIFY
:
490 result
.type
= WebInputEvent::MouseMove
;
496 result
.button
= WebMouseEvent::ButtonNone
;
497 if (event
->state
& GDK_BUTTON1_MASK
)
498 result
.button
= WebMouseEvent::ButtonLeft
;
499 else if (event
->state
& GDK_BUTTON2_MASK
)
500 result
.button
= WebMouseEvent::ButtonMiddle
;
501 else if (event
->state
& GDK_BUTTON3_MASK
)
502 result
.button
= WebMouseEvent::ButtonRight
;
504 if (ShouldForgetPreviousClick(event
->window
, event
->time
, event
->x
, event
->y
))
505 ResetClickCountState();
510 WebMouseEvent
WebMouseEventBuilder::Build(const GdkEventCrossing
* event
) {
511 WebMouseEvent result
;
513 result
.timeStampSeconds
= GdkEventTimeToWebEventTime(event
->time
);
514 result
.modifiers
= GdkStateToWebEventModifiers(event
->state
);
515 result
.x
= static_cast<int>(event
->x
);
516 result
.y
= static_cast<int>(event
->y
);
517 result
.windowX
= result
.x
;
518 result
.windowY
= result
.y
;
519 result
.globalX
= static_cast<int>(event
->x_root
);
520 result
.globalY
= static_cast<int>(event
->y_root
);
522 switch (event
->type
) {
523 case GDK_ENTER_NOTIFY
:
524 case GDK_LEAVE_NOTIFY
:
525 // Note that if we sent MouseEnter or MouseLeave to WebKit, it
526 // wouldn't work - they don't result in the proper JavaScript events.
527 // MouseMove does the right thing.
528 result
.type
= WebInputEvent::MouseMove
;
534 result
.button
= WebMouseEvent::ButtonNone
;
535 if (event
->state
& GDK_BUTTON1_MASK
)
536 result
.button
= WebMouseEvent::ButtonLeft
;
537 else if (event
->state
& GDK_BUTTON2_MASK
)
538 result
.button
= WebMouseEvent::ButtonMiddle
;
539 else if (event
->state
& GDK_BUTTON3_MASK
)
540 result
.button
= WebMouseEvent::ButtonRight
;
542 if (ShouldForgetPreviousClick(event
->window
, event
->time
, event
->x
, event
->y
))
543 ResetClickCountState();
548 // WebMouseWheelEvent ---------------------------------------------------------
550 float WebMouseWheelEventBuilder::ScrollbarPixelsPerTick() {
551 // How much should we scroll per mouse wheel event?
552 // - Windows uses 3 lines by default and obeys a system setting.
553 // - Mozilla has a pref that lets you either use the "system" number of lines
554 // to scroll, or lets the user override it.
555 // For the "system" number of lines, it appears they've hardcoded 3.
556 // See case NS_MOUSE_SCROLL in content/events/src/nsEventStateManager.cpp
557 // and InitMouseScrollEvent in widget/src/gtk2/nsCommonWidget.cpp .
558 // - Gtk makes the scroll amount a function of the size of the scroll bar,
559 // which is not available to us here.
560 // Instead, we pick a number that empirically matches Firefox's behavior.
561 return 160.0f
/ 3.0f
;
564 WebMouseWheelEvent
WebMouseWheelEventBuilder::Build(
565 const GdkEventScroll
* event
) {
566 WebMouseWheelEvent result
;
568 result
.type
= WebInputEvent::MouseWheel
;
569 result
.button
= WebMouseEvent::ButtonNone
;
571 result
.timeStampSeconds
= GdkEventTimeToWebEventTime(event
->time
);
572 result
.modifiers
= GdkStateToWebEventModifiers(event
->state
);
573 result
.x
= static_cast<int>(event
->x
);
574 result
.y
= static_cast<int>(event
->y
);
575 result
.windowX
= result
.x
;
576 result
.windowY
= result
.y
;
577 result
.globalX
= static_cast<int>(event
->x_root
);
578 result
.globalY
= static_cast<int>(event
->y_root
);
580 static const float scrollbarPixelsPerTick
= ScrollbarPixelsPerTick();
581 switch (event
->direction
) {
583 result
.deltaY
= scrollbarPixelsPerTick
;
584 result
.wheelTicksY
= 1;
586 case GDK_SCROLL_DOWN
:
587 result
.deltaY
= -scrollbarPixelsPerTick
;
588 result
.wheelTicksY
= -1;
590 case GDK_SCROLL_LEFT
:
591 result
.deltaX
= scrollbarPixelsPerTick
;
592 result
.wheelTicksX
= 1;
594 case GDK_SCROLL_RIGHT
:
595 result
.deltaX
= -scrollbarPixelsPerTick
;
596 result
.wheelTicksX
= -1;
603 } // namespace content