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.
5 #include "content/browser/renderer_host/input/web_input_event_builders_win.h"
7 #include "base/logging.h"
8 #include "content/browser/renderer_host/input/web_input_event_util.h"
9 #include "ui/gfx/win/dpi.h"
11 using blink::WebInputEvent
;
12 using blink::WebKeyboardEvent
;
13 using blink::WebMouseEvent
;
14 using blink::WebMouseWheelEvent
;
18 static const unsigned long kDefaultScrollLinesPerWheelDelta
= 3;
19 static const unsigned long kDefaultScrollCharsPerWheelDelta
= 1;
21 static bool IsKeyDown(WPARAM wparam
) {
22 return (GetKeyState(wparam
) & 0x8000) != 0;
25 static int GetLocationModifier(WPARAM wparam
, LPARAM lparam
) {
29 if ((lparam
>> 16) & KF_EXTENDED
)
30 modifier
= WebInputEvent::IsKeyPad
;
42 if (!((lparam
>> 16) & KF_EXTENDED
))
43 modifier
= WebInputEvent::IsKeyPad
;
62 modifier
= WebInputEvent::IsKeyPad
;
65 if (IsKeyDown(VK_LSHIFT
))
66 modifier
= WebInputEvent::IsLeft
;
67 else if (IsKeyDown(VK_RSHIFT
))
68 modifier
= WebInputEvent::IsRight
;
71 if (IsKeyDown(VK_LCONTROL
))
72 modifier
= WebInputEvent::IsLeft
;
73 else if (IsKeyDown(VK_RCONTROL
))
74 modifier
= WebInputEvent::IsRight
;
77 if (IsKeyDown(VK_LMENU
))
78 modifier
= WebInputEvent::IsLeft
;
79 else if (IsKeyDown(VK_RMENU
))
80 modifier
= WebInputEvent::IsRight
;
83 modifier
= WebInputEvent::IsLeft
;
86 modifier
= WebInputEvent::IsRight
;
91 || modifier
== WebInputEvent::IsKeyPad
92 || modifier
== WebInputEvent::IsLeft
93 || modifier
== WebInputEvent::IsRight
);
97 // Loads the state for toggle keys into the event.
98 static void SetToggleKeyState(WebInputEvent
* event
) {
99 // Low bit set from GetKeyState indicates "toggled".
100 if (::GetKeyState(VK_NUMLOCK
) & 1)
101 event
->modifiers
|= WebInputEvent::NumLockOn
;
102 if (::GetKeyState(VK_CAPITAL
) & 1)
103 event
->modifiers
|= WebInputEvent::CapsLockOn
;
106 WebKeyboardEvent
WebKeyboardEventBuilder::Build(HWND hwnd
,
111 WebKeyboardEvent result
;
113 result
.timeStampSeconds
= time_ms
/ 1000.0;
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
);
121 result
.isSystemKey
= true;
123 result
.type
= WebInputEvent::RawKeyDown
;
126 result
.isSystemKey
= true;
128 result
.type
= WebInputEvent::KeyUp
;
131 result
.type
= WebInputEvent::Char
;
134 result
.isSystemKey
= true;
135 result
.type
= WebInputEvent::Char
;
137 result
.type
= WebInputEvent::Char
;
143 if (result
.type
== WebInputEvent::Char
144 || result
.type
== WebInputEvent::RawKeyDown
) {
145 result
.text
[0] = result
.windowsKeyCode
;
146 result
.unmodifiedText
[0] = result
.windowsKeyCode
;
148 if (result
.type
!= WebInputEvent::Char
) {
149 UpdateWindowsKeyCodeAndKeyIdentifier(
151 static_cast<ui::KeyboardCode
>(result
.windowsKeyCode
));
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
163 // Bit 30 of lParam represents the "previous key state". If set, the key was
164 // already down, therefore this is an auto-repeat. Only apply this to key
165 // down events, to match DOM semantics.
166 if ((result
.type
== WebInputEvent::RawKeyDown
) && (lparam
& 0x40000000))
167 result
.modifiers
|= WebInputEvent::IsAutoRepeat
;
169 result
.modifiers
|= GetLocationModifier(wparam
, lparam
);
171 SetToggleKeyState(&result
);
175 // WebMouseEvent --------------------------------------------------------------
177 static int g_last_click_count
= 0;
178 static double g_last_click_time
= 0;
180 static LPARAM
GetRelativeCursorPos(HWND hwnd
) {
181 POINT pos
= {-1, -1};
183 ScreenToClient(hwnd
, &pos
);
184 return MAKELPARAM(pos
.x
, pos
.y
);
187 WebMouseEvent
WebMouseEventBuilder::Build(HWND hwnd
,
192 WebMouseEvent result
;
196 result
.type
= WebInputEvent::MouseMove
;
197 if (wparam
& MK_LBUTTON
)
198 result
.button
= WebMouseEvent::ButtonLeft
;
199 else if (wparam
& MK_MBUTTON
)
200 result
.button
= WebMouseEvent::ButtonMiddle
;
201 else if (wparam
& MK_RBUTTON
)
202 result
.button
= WebMouseEvent::ButtonRight
;
204 result
.button
= WebMouseEvent::ButtonNone
;
207 // TODO(rbyers): This should be MouseLeave but is disabled temporarily.
208 // See http://crbug.com/450631
209 result
.type
= WebInputEvent::MouseMove
;
210 result
.button
= WebMouseEvent::ButtonNone
;
211 // set the current mouse position (relative to the client area of the
212 // current window) since none is specified for this event
213 lparam
= GetRelativeCursorPos(hwnd
);
216 case WM_LBUTTONDBLCLK
:
217 result
.type
= WebInputEvent::MouseDown
;
218 result
.button
= WebMouseEvent::ButtonLeft
;
221 case WM_MBUTTONDBLCLK
:
222 result
.type
= WebInputEvent::MouseDown
;
223 result
.button
= WebMouseEvent::ButtonMiddle
;
226 case WM_RBUTTONDBLCLK
:
227 result
.type
= WebInputEvent::MouseDown
;
228 result
.button
= WebMouseEvent::ButtonRight
;
231 result
.type
= WebInputEvent::MouseUp
;
232 result
.button
= WebMouseEvent::ButtonLeft
;
235 result
.type
= WebInputEvent::MouseUp
;
236 result
.button
= WebMouseEvent::ButtonMiddle
;
239 result
.type
= WebInputEvent::MouseUp
;
240 result
.button
= WebMouseEvent::ButtonRight
;
246 result
.timeStampSeconds
= time_ms
/ 1000.0;
248 // set position fields:
250 result
.x
= static_cast<short>(LOWORD(lparam
));
251 result
.y
= static_cast<short>(HIWORD(lparam
));
252 result
.windowX
= result
.x
;
253 result
.windowY
= result
.y
;
255 POINT global_point
= { result
.x
, result
.y
};
256 ClientToScreen(hwnd
, &global_point
);
258 // We need to convert the global point back to DIP before using it.
259 gfx::Point dip_global_point
= gfx::win::ScreenToDIPPoint(
260 gfx::Point(global_point
.x
, global_point
.y
));
262 result
.globalX
= dip_global_point
.x();
263 result
.globalY
= dip_global_point
.y();
265 // calculate number of clicks:
267 // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
268 // where their original code looks buggy.
269 static int last_click_position_x
;
270 static int last_click_position_y
;
271 static WebMouseEvent::Button last_click_button
= WebMouseEvent::ButtonLeft
;
273 double current_time
= result
.timeStampSeconds
;
274 bool cancel_previous_click
=
275 (abs(last_click_position_x
- result
.x
) >
276 (::GetSystemMetrics(SM_CXDOUBLECLK
) / 2))
277 || (abs(last_click_position_y
- result
.y
) >
278 (::GetSystemMetrics(SM_CYDOUBLECLK
) / 2))
279 || ((current_time
- g_last_click_time
) * 1000.0 > ::GetDoubleClickTime());
281 if (result
.type
== WebInputEvent::MouseDown
) {
282 if (!cancel_previous_click
&& (result
.button
== last_click_button
)) {
283 ++g_last_click_count
;
285 g_last_click_count
= 1;
286 last_click_position_x
= result
.x
;
287 last_click_position_y
= result
.y
;
289 g_last_click_time
= current_time
;
290 last_click_button
= result
.button
;
291 } else if (result
.type
== WebInputEvent::MouseMove
292 || result
.type
== WebInputEvent::MouseLeave
) {
293 if (cancel_previous_click
) {
294 g_last_click_count
= 0;
295 last_click_position_x
= 0;
296 last_click_position_y
= 0;
297 g_last_click_time
= 0;
300 result
.clickCount
= g_last_click_count
;
304 if (wparam
& MK_CONTROL
)
305 result
.modifiers
|= WebInputEvent::ControlKey
;
306 if (wparam
& MK_SHIFT
)
307 result
.modifiers
|= WebInputEvent::ShiftKey
;
308 if (::GetKeyState(VK_MENU
) & 0x8000)
309 result
.modifiers
|= WebInputEvent::AltKey
;
310 if (wparam
& MK_LBUTTON
)
311 result
.modifiers
|= WebInputEvent::LeftButtonDown
;
312 if (wparam
& MK_MBUTTON
)
313 result
.modifiers
|= WebInputEvent::MiddleButtonDown
;
314 if (wparam
& MK_RBUTTON
)
315 result
.modifiers
|= WebInputEvent::RightButtonDown
;
317 SetToggleKeyState(&result
);
321 // WebMouseWheelEvent ---------------------------------------------------------
323 WebMouseWheelEvent
WebMouseWheelEventBuilder::Build(HWND hwnd
,
328 WebMouseWheelEvent result
;
330 result
.type
= WebInputEvent::MouseWheel
;
332 result
.timeStampSeconds
= time_ms
/ 1000.0;
334 result
.button
= WebMouseEvent::ButtonNone
;
336 // Get key state, coordinates, and wheel delta from event.
337 typedef SHORT (WINAPI
*GetKeyStateFunction
)(int key
);
338 GetKeyStateFunction get_key_state_func
;
341 bool horizontal_scroll
= false;
342 if ((message
== WM_VSCROLL
) || (message
== WM_HSCROLL
)) {
343 // Synthesize mousewheel event from a scroll event. This is needed to
344 // simulate middle mouse scrolling in some laptops. Use GetAsyncKeyState
345 // for key state since we are synthesizing the input event.
346 get_key_state_func
= GetAsyncKeyState
;
348 if (get_key_state_func(VK_SHIFT
) & 0x8000)
349 key_state
|= MK_SHIFT
;
350 if (get_key_state_func(VK_CONTROL
) & 0x8000)
351 key_state
|= MK_CONTROL
;
352 // NOTE: There doesn't seem to be a way to query the mouse button state
355 POINT cursor_position
= {0};
356 GetCursorPos(&cursor_position
);
357 result
.globalX
= cursor_position
.x
;
358 result
.globalY
= cursor_position
.y
;
360 switch (LOWORD(wparam
)) {
361 case SB_LINEUP
: // == SB_LINELEFT
362 wheel_delta
= WHEEL_DELTA
;
364 case SB_LINEDOWN
: // == SB_LINERIGHT
365 wheel_delta
= -WHEEL_DELTA
;
369 result
.scrollByPage
= true;
373 result
.scrollByPage
= true;
375 default: // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
380 if (message
== WM_HSCROLL
)
381 horizontal_scroll
= true;
383 // Non-synthesized event; we can just read data off the event.
384 get_key_state_func
= ::GetKeyState
;
385 key_state
= GET_KEYSTATE_WPARAM(wparam
);
387 result
.globalX
= static_cast<short>(LOWORD(lparam
));
388 result
.globalY
= static_cast<short>(HIWORD(lparam
));
390 wheel_delta
= static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam
));
391 if (message
== WM_MOUSEHWHEEL
) {
392 horizontal_scroll
= true;
393 wheel_delta
= -wheel_delta
; // Windows is <- -/+ ->, WebKit <- +/- ->.
396 if (key_state
& MK_SHIFT
)
397 horizontal_scroll
= true;
399 // Set modifiers based on key state.
400 if (key_state
& MK_SHIFT
)
401 result
.modifiers
|= WebInputEvent::ShiftKey
;
402 if (key_state
& MK_CONTROL
)
403 result
.modifiers
|= WebInputEvent::ControlKey
;
404 if (get_key_state_func(VK_MENU
) & 0x8000)
405 result
.modifiers
|= WebInputEvent::AltKey
;
406 if (key_state
& MK_LBUTTON
)
407 result
.modifiers
|= WebInputEvent::LeftButtonDown
;
408 if (key_state
& MK_MBUTTON
)
409 result
.modifiers
|= WebInputEvent::MiddleButtonDown
;
410 if (key_state
& MK_RBUTTON
)
411 result
.modifiers
|= WebInputEvent::RightButtonDown
;
413 SetToggleKeyState(&result
);
415 // Set coordinates by translating event coordinates from screen to client.
416 POINT client_point
= { result
.globalX
, result
.globalY
};
417 MapWindowPoints(0, hwnd
, &client_point
, 1);
418 result
.x
= client_point
.x
;
419 result
.y
= client_point
.y
;
420 result
.windowX
= result
.x
;
421 result
.windowY
= result
.y
;
423 // Convert wheel delta amount to a number of pixels to scroll.
425 // How many pixels should we scroll per line? Gecko uses the height of the
426 // current line, which means scroll distance changes as you go through the
427 // page or go to different pages. IE 8 is ~60 px/line, although the value
428 // seems to vary slightly by page and zoom level. Also, IE defaults to
429 // smooth scrolling while Firefox doesn't, so it can get away with somewhat
430 // larger scroll values without feeling as jerky. Here we use 100 px per
431 // three lines (the default scroll amount is three lines per wheel tick).
432 // Even though we have smooth scrolling, we don't make this as large as IE
433 // because subjectively IE feels like it scrolls farther than you want while
435 static const float kScrollbarPixelsPerLine
= 100.0f
/ 3.0f
;
436 wheel_delta
/= WHEEL_DELTA
;
437 float scroll_delta
= wheel_delta
;
438 if (horizontal_scroll
) {
439 unsigned long scroll_chars
= kDefaultScrollCharsPerWheelDelta
;
440 SystemParametersInfo(SPI_GETWHEELSCROLLCHARS
, 0, &scroll_chars
, 0);
441 // TODO(pkasting): Should probably have a different multiplier
442 // scrollbarPixelsPerChar here.
443 scroll_delta
*= static_cast<float>(scroll_chars
) * kScrollbarPixelsPerLine
;
445 unsigned long scroll_lines
= kDefaultScrollLinesPerWheelDelta
;
446 SystemParametersInfo(SPI_GETWHEELSCROLLLINES
, 0, &scroll_lines
, 0);
447 if (scroll_lines
== WHEEL_PAGESCROLL
)
448 result
.scrollByPage
= true;
449 if (!result
.scrollByPage
) {
451 static_cast<float>(scroll_lines
) * kScrollbarPixelsPerLine
;
455 // Set scroll amount based on above calculations. WebKit expects positive
456 // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
457 if (horizontal_scroll
) {
458 result
.deltaX
= scroll_delta
;
459 result
.wheelTicksX
= wheel_delta
;
461 result
.deltaY
= scroll_delta
;
462 result
.wheelTicksY
= wheel_delta
;
468 } // namespace content