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 46 namespace { 47 48 // For click count tracking. 49 static int gNumClicks = 0; 50 static GdkWindow* gLastClickEventWindow = 0; 51 static gint gLastClickTime = 0; 52 static gint gLastClickX = 0; 53 static gint gLastClickY = 0; 54 static WebKit::WebMouseEvent::Button gLastClickButton = WebKit::WebMouseEvent::ButtonNone; 55 56 bool 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 73 void 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 85 namespace WebKit { 86 87 static double gdkEventTimeToWebEventTime(guint32 time) 88 { 89 // Convert from time in ms to time in sec. 90 return time / 1000.0; 91 } 92 93 static 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 119 static 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. 275 static 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 320 WebKeyboardEvent 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 376 WebKeyboardEvent 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 406 WebMouseEvent 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 462 WebMouseEvent 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 497 WebMouseEvent 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 538 WebMouseWheelEvent 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