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