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 blink::WebInputEvent; 11 using blink::WebKeyboardEvent; 12 using blink::WebMouseEvent; 13 using blink::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, 106 UINT message, 107 WPARAM wparam, 108 LPARAM lparam, 109 DWORD time_ms) { 110 WebKeyboardEvent result; 111 112 DCHECK(time_ms); 113 result.timeStampSeconds = time_ms / 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 // Bit 30 of lParam represents the "previous key state". If set, the key was 164 // already down, therefore this is an auto-repeat. 165 if (lparam & 0x40000000) 166 result.modifiers |= WebInputEvent::IsAutoRepeat; 167 168 result.modifiers |= GetLocationModifier(wparam, lparam); 169 170 SetToggleKeyState(&result); 171 return result; 172 } 173 174 // WebMouseEvent -------------------------------------------------------------- 175 176 static int g_last_click_count = 0; 177 static double g_last_click_time = 0; 178 179 static LPARAM GetRelativeCursorPos(HWND hwnd) { 180 POINT pos = {-1, -1}; 181 GetCursorPos(&pos); 182 ScreenToClient(hwnd, &pos); 183 return MAKELPARAM(pos.x, pos.y); 184 } 185 186 WebMouseEvent WebMouseEventBuilder::Build(HWND hwnd, 187 UINT message, 188 WPARAM wparam, 189 LPARAM lparam, 190 DWORD time_ms) { 191 WebMouseEvent result; 192 193 switch (message) { 194 case WM_MOUSEMOVE: 195 result.type = WebInputEvent::MouseMove; 196 if (wparam & MK_LBUTTON) 197 result.button = WebMouseEvent::ButtonLeft; 198 else if (wparam & MK_MBUTTON) 199 result.button = WebMouseEvent::ButtonMiddle; 200 else if (wparam & MK_RBUTTON) 201 result.button = WebMouseEvent::ButtonRight; 202 else 203 result.button = WebMouseEvent::ButtonNone; 204 break; 205 case WM_MOUSELEAVE: 206 result.type = WebInputEvent::MouseLeave; 207 result.button = WebMouseEvent::ButtonNone; 208 // set the current mouse position (relative to the client area of the 209 // current window) since none is specified for this event 210 lparam = GetRelativeCursorPos(hwnd); 211 break; 212 case WM_LBUTTONDOWN: 213 case WM_LBUTTONDBLCLK: 214 result.type = WebInputEvent::MouseDown; 215 result.button = WebMouseEvent::ButtonLeft; 216 break; 217 case WM_MBUTTONDOWN: 218 case WM_MBUTTONDBLCLK: 219 result.type = WebInputEvent::MouseDown; 220 result.button = WebMouseEvent::ButtonMiddle; 221 break; 222 case WM_RBUTTONDOWN: 223 case WM_RBUTTONDBLCLK: 224 result.type = WebInputEvent::MouseDown; 225 result.button = WebMouseEvent::ButtonRight; 226 break; 227 case WM_LBUTTONUP: 228 result.type = WebInputEvent::MouseUp; 229 result.button = WebMouseEvent::ButtonLeft; 230 break; 231 case WM_MBUTTONUP: 232 result.type = WebInputEvent::MouseUp; 233 result.button = WebMouseEvent::ButtonMiddle; 234 break; 235 case WM_RBUTTONUP: 236 result.type = WebInputEvent::MouseUp; 237 result.button = WebMouseEvent::ButtonRight; 238 break; 239 default: 240 NOTREACHED(); 241 } 242 243 DCHECK(time_ms); 244 result.timeStampSeconds = time_ms / 1000.0; 245 246 // set position fields: 247 248 result.x = static_cast<short>(LOWORD(lparam)); 249 result.y = static_cast<short>(HIWORD(lparam)); 250 result.windowX = result.x; 251 result.windowY = result.y; 252 253 POINT global_point = { result.x, result.y }; 254 ClientToScreen(hwnd, &global_point); 255 256 result.globalX = global_point.x; 257 result.globalY = global_point.y; 258 259 // calculate number of clicks: 260 261 // This differs slightly from the WebKit code in WebKit/win/WebView.cpp 262 // where their original code looks buggy. 263 static int last_click_position_x; 264 static int last_click_position_y; 265 static WebMouseEvent::Button last_click_button = WebMouseEvent::ButtonLeft; 266 267 double current_time = result.timeStampSeconds; 268 bool cancel_previous_click = 269 (abs(last_click_position_x - result.x) > 270 (::GetSystemMetrics(SM_CXDOUBLECLK) / 2)) 271 || (abs(last_click_position_y - result.y) > 272 (::GetSystemMetrics(SM_CYDOUBLECLK) / 2)) 273 || ((current_time - g_last_click_time) * 1000.0 > ::GetDoubleClickTime()); 274 275 if (result.type == WebInputEvent::MouseDown) { 276 if (!cancel_previous_click && (result.button == last_click_button)) { 277 ++g_last_click_count; 278 } else { 279 g_last_click_count = 1; 280 last_click_position_x = result.x; 281 last_click_position_y = result.y; 282 } 283 g_last_click_time = current_time; 284 last_click_button = result.button; 285 } else if (result.type == WebInputEvent::MouseMove 286 || result.type == WebInputEvent::MouseLeave) { 287 if (cancel_previous_click) { 288 g_last_click_count = 0; 289 last_click_position_x = 0; 290 last_click_position_y = 0; 291 g_last_click_time = 0; 292 } 293 } 294 result.clickCount = g_last_click_count; 295 296 // set modifiers: 297 298 if (wparam & MK_CONTROL) 299 result.modifiers |= WebInputEvent::ControlKey; 300 if (wparam & MK_SHIFT) 301 result.modifiers |= WebInputEvent::ShiftKey; 302 if (::GetKeyState(VK_MENU) & 0x8000) 303 result.modifiers |= WebInputEvent::AltKey; 304 if (wparam & MK_LBUTTON) 305 result.modifiers |= WebInputEvent::LeftButtonDown; 306 if (wparam & MK_MBUTTON) 307 result.modifiers |= WebInputEvent::MiddleButtonDown; 308 if (wparam & MK_RBUTTON) 309 result.modifiers |= WebInputEvent::RightButtonDown; 310 311 SetToggleKeyState(&result); 312 return result; 313 } 314 315 // WebMouseWheelEvent --------------------------------------------------------- 316 317 WebMouseWheelEvent WebMouseWheelEventBuilder::Build(HWND hwnd, 318 UINT message, 319 WPARAM wparam, 320 LPARAM lparam, 321 DWORD time_ms) { 322 WebMouseWheelEvent result; 323 324 result.type = WebInputEvent::MouseWheel; 325 326 DCHECK(time_ms); 327 result.timeStampSeconds = time_ms / 1000.0; 328 329 result.button = WebMouseEvent::ButtonNone; 330 331 // Get key state, coordinates, and wheel delta from event. 332 typedef SHORT (WINAPI *GetKeyStateFunction)(int key); 333 GetKeyStateFunction get_key_state_func; 334 UINT key_state; 335 float wheel_delta; 336 bool horizontal_scroll = false; 337 if ((message == WM_VSCROLL) || (message == WM_HSCROLL)) { 338 // Synthesize mousewheel event from a scroll event. This is needed to 339 // simulate middle mouse scrolling in some laptops. Use GetAsyncKeyState 340 // for key state since we are synthesizing the input event. 341 get_key_state_func = GetAsyncKeyState; 342 key_state = 0; 343 if (get_key_state_func(VK_SHIFT) & 0x8000) 344 key_state |= MK_SHIFT; 345 if (get_key_state_func(VK_CONTROL) & 0x8000) 346 key_state |= MK_CONTROL; 347 // NOTE: There doesn't seem to be a way to query the mouse button state 348 // in this case. 349 350 POINT cursor_position = {0}; 351 GetCursorPos(&cursor_position); 352 result.globalX = cursor_position.x; 353 result.globalY = cursor_position.y; 354 355 switch (LOWORD(wparam)) { 356 case SB_LINEUP: // == SB_LINELEFT 357 wheel_delta = WHEEL_DELTA; 358 break; 359 case SB_LINEDOWN: // == SB_LINERIGHT 360 wheel_delta = -WHEEL_DELTA; 361 break; 362 case SB_PAGEUP: 363 wheel_delta = 1; 364 result.scrollByPage = true; 365 break; 366 case SB_PAGEDOWN: 367 wheel_delta = -1; 368 result.scrollByPage = true; 369 break; 370 default: // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here. 371 wheel_delta = 0; 372 break; 373 } 374 375 if (message == WM_HSCROLL) 376 horizontal_scroll = true; 377 } else { 378 // Non-synthesized event; we can just read data off the event. 379 get_key_state_func = ::GetKeyState; 380 key_state = GET_KEYSTATE_WPARAM(wparam); 381 382 result.globalX = static_cast<short>(LOWORD(lparam)); 383 result.globalY = static_cast<short>(HIWORD(lparam)); 384 385 wheel_delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam)); 386 if (message == WM_MOUSEHWHEEL) { 387 horizontal_scroll = true; 388 wheel_delta = -wheel_delta; // Windows is <- -/+ ->, WebKit <- +/- ->. 389 } 390 } 391 if (key_state & MK_SHIFT) 392 horizontal_scroll = true; 393 394 // Set modifiers based on key state. 395 if (key_state & MK_SHIFT) 396 result.modifiers |= WebInputEvent::ShiftKey; 397 if (key_state & MK_CONTROL) 398 result.modifiers |= WebInputEvent::ControlKey; 399 if (get_key_state_func(VK_MENU) & 0x8000) 400 result.modifiers |= WebInputEvent::AltKey; 401 if (key_state & MK_LBUTTON) 402 result.modifiers |= WebInputEvent::LeftButtonDown; 403 if (key_state & MK_MBUTTON) 404 result.modifiers |= WebInputEvent::MiddleButtonDown; 405 if (key_state & MK_RBUTTON) 406 result.modifiers |= WebInputEvent::RightButtonDown; 407 408 SetToggleKeyState(&result); 409 410 // Set coordinates by translating event coordinates from screen to client. 411 POINT client_point = { result.globalX, result.globalY }; 412 MapWindowPoints(0, hwnd, &client_point, 1); 413 result.x = client_point.x; 414 result.y = client_point.y; 415 result.windowX = result.x; 416 result.windowY = result.y; 417 418 // Convert wheel delta amount to a number of pixels to scroll. 419 // 420 // How many pixels should we scroll per line? Gecko uses the height of the 421 // current line, which means scroll distance changes as you go through the 422 // page or go to different pages. IE 8 is ~60 px/line, although the value 423 // seems to vary slightly by page and zoom level. Also, IE defaults to 424 // smooth scrolling while Firefox doesn't, so it can get away with somewhat 425 // larger scroll values without feeling as jerky. Here we use 100 px per 426 // three lines (the default scroll amount is three lines per wheel tick). 427 // Even though we have smooth scrolling, we don't make this as large as IE 428 // because subjectively IE feels like it scrolls farther than you want while 429 // reading articles. 430 static const float kScrollbarPixelsPerLine = 100.0f / 3.0f; 431 wheel_delta /= WHEEL_DELTA; 432 float scroll_delta = wheel_delta; 433 if (horizontal_scroll) { 434 unsigned long scroll_chars = kDefaultScrollCharsPerWheelDelta; 435 SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &scroll_chars, 0); 436 // TODO(pkasting): Should probably have a different multiplier 437 // scrollbarPixelsPerChar here. 438 scroll_delta *= static_cast<float>(scroll_chars) * kScrollbarPixelsPerLine; 439 } else { 440 unsigned long scroll_lines = kDefaultScrollLinesPerWheelDelta; 441 SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scroll_lines, 0); 442 if (scroll_lines == WHEEL_PAGESCROLL) 443 result.scrollByPage = true; 444 if (!result.scrollByPage) { 445 scroll_delta *= 446 static_cast<float>(scroll_lines) * kScrollbarPixelsPerLine; 447 } 448 } 449 450 // Set scroll amount based on above calculations. WebKit expects positive 451 // deltaY to mean "scroll up" and positive deltaX to mean "scroll left". 452 if (horizontal_scroll) { 453 result.deltaX = scroll_delta; 454 result.wheelTicksX = wheel_delta; 455 } else { 456 result.deltaY = scroll_delta; 457 result.wheelTicksY = wheel_delta; 458 } 459 460 return result; 461 } 462 463 } // namespace content 464