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