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