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