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 // Loads the state for toggle keys into the event.
22 static void SetToggleKeyState(WebInputEvent
* event
) {
23 // Low bit set from GetKeyState indicates "toggled".
24 if (::GetKeyState(VK_NUMLOCK
) & 1)
25 event
->modifiers
|= WebInputEvent::NumLockOn
;
26 if (::GetKeyState(VK_CAPITAL
) & 1)
27 event
->modifiers
|= WebInputEvent::CapsLockOn
;
30 WebKeyboardEvent
WebKeyboardEventBuilder::Build(HWND hwnd
,
35 WebKeyboardEvent result
;
37 result
.timeStampSeconds
= time_ms
/ 1000.0;
39 result
.windowsKeyCode
= static_cast<int>(wparam
);
40 // Record the scan code (along with other context bits) for this key event.
41 result
.nativeKeyCode
= static_cast<int>(lparam
);
45 result
.isSystemKey
= true;
47 result
.type
= WebInputEvent::RawKeyDown
;
50 result
.isSystemKey
= true;
52 result
.type
= WebInputEvent::KeyUp
;
55 result
.type
= WebInputEvent::Char
;
58 result
.isSystemKey
= true;
59 result
.type
= WebInputEvent::Char
;
61 result
.type
= WebInputEvent::Char
;
67 if (result
.type
== WebInputEvent::Char
68 || result
.type
== WebInputEvent::RawKeyDown
) {
69 result
.text
[0] = result
.windowsKeyCode
;
70 result
.unmodifiedText
[0] = result
.windowsKeyCode
;
72 if (result
.type
!= WebInputEvent::Char
) {
73 UpdateWindowsKeyCodeAndKeyIdentifier(
75 static_cast<ui::KeyboardCode
>(result
.windowsKeyCode
));
78 if (::GetKeyState(VK_SHIFT
) & 0x8000)
79 result
.modifiers
|= WebInputEvent::ShiftKey
;
80 if (::GetKeyState(VK_CONTROL
) & 0x8000)
81 result
.modifiers
|= WebInputEvent::ControlKey
;
82 if (::GetKeyState(VK_MENU
) & 0x8000)
83 result
.modifiers
|= WebInputEvent::AltKey
;
84 // NOTE: There doesn't seem to be a way to query the mouse button state in
87 // Bit 30 of lParam represents the "previous key state". If set, the key was
88 // already down, therefore this is an auto-repeat. Only apply this to key
89 // down events, to match DOM semantics.
90 if ((result
.type
== WebInputEvent::RawKeyDown
) && (lparam
& 0x40000000))
91 result
.modifiers
|= WebInputEvent::IsAutoRepeat
;
93 SetToggleKeyState(&result
);
97 // WebMouseEvent --------------------------------------------------------------
99 static int g_last_click_count
= 0;
100 static double g_last_click_time
= 0;
102 static LPARAM
GetRelativeCursorPos(HWND hwnd
) {
103 POINT pos
= {-1, -1};
105 ScreenToClient(hwnd
, &pos
);
106 return MAKELPARAM(pos
.x
, pos
.y
);
109 WebMouseEvent
WebMouseEventBuilder::Build(HWND hwnd
,
114 WebMouseEvent result
;
118 result
.type
= WebInputEvent::MouseMove
;
119 if (wparam
& MK_LBUTTON
)
120 result
.button
= WebMouseEvent::ButtonLeft
;
121 else if (wparam
& MK_MBUTTON
)
122 result
.button
= WebMouseEvent::ButtonMiddle
;
123 else if (wparam
& MK_RBUTTON
)
124 result
.button
= WebMouseEvent::ButtonRight
;
126 result
.button
= WebMouseEvent::ButtonNone
;
129 // TODO(rbyers): This should be MouseLeave but is disabled temporarily.
130 // See http://crbug.com/450631
131 result
.type
= WebInputEvent::MouseMove
;
132 result
.button
= WebMouseEvent::ButtonNone
;
133 // set the current mouse position (relative to the client area of the
134 // current window) since none is specified for this event
135 lparam
= GetRelativeCursorPos(hwnd
);
138 case WM_LBUTTONDBLCLK
:
139 result
.type
= WebInputEvent::MouseDown
;
140 result
.button
= WebMouseEvent::ButtonLeft
;
143 case WM_MBUTTONDBLCLK
:
144 result
.type
= WebInputEvent::MouseDown
;
145 result
.button
= WebMouseEvent::ButtonMiddle
;
148 case WM_RBUTTONDBLCLK
:
149 result
.type
= WebInputEvent::MouseDown
;
150 result
.button
= WebMouseEvent::ButtonRight
;
153 result
.type
= WebInputEvent::MouseUp
;
154 result
.button
= WebMouseEvent::ButtonLeft
;
157 result
.type
= WebInputEvent::MouseUp
;
158 result
.button
= WebMouseEvent::ButtonMiddle
;
161 result
.type
= WebInputEvent::MouseUp
;
162 result
.button
= WebMouseEvent::ButtonRight
;
168 result
.timeStampSeconds
= time_ms
/ 1000.0;
170 // set position fields:
172 result
.x
= static_cast<short>(LOWORD(lparam
));
173 result
.y
= static_cast<short>(HIWORD(lparam
));
174 result
.windowX
= result
.x
;
175 result
.windowY
= result
.y
;
177 POINT global_point
= { result
.x
, result
.y
};
178 ClientToScreen(hwnd
, &global_point
);
180 // We need to convert the global point back to DIP before using it.
181 gfx::Point dip_global_point
= gfx::win::ScreenToDIPPoint(
182 gfx::Point(global_point
.x
, global_point
.y
));
184 result
.globalX
= dip_global_point
.x();
185 result
.globalY
= dip_global_point
.y();
187 // calculate number of clicks:
189 // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
190 // where their original code looks buggy.
191 static int last_click_position_x
;
192 static int last_click_position_y
;
193 static WebMouseEvent::Button last_click_button
= WebMouseEvent::ButtonLeft
;
195 double current_time
= result
.timeStampSeconds
;
196 bool cancel_previous_click
=
197 (abs(last_click_position_x
- result
.x
) >
198 (::GetSystemMetrics(SM_CXDOUBLECLK
) / 2))
199 || (abs(last_click_position_y
- result
.y
) >
200 (::GetSystemMetrics(SM_CYDOUBLECLK
) / 2))
201 || ((current_time
- g_last_click_time
) * 1000.0 > ::GetDoubleClickTime());
203 if (result
.type
== WebInputEvent::MouseDown
) {
204 if (!cancel_previous_click
&& (result
.button
== last_click_button
)) {
205 ++g_last_click_count
;
207 g_last_click_count
= 1;
208 last_click_position_x
= result
.x
;
209 last_click_position_y
= result
.y
;
211 g_last_click_time
= current_time
;
212 last_click_button
= result
.button
;
213 } else if (result
.type
== WebInputEvent::MouseMove
214 || result
.type
== WebInputEvent::MouseLeave
) {
215 if (cancel_previous_click
) {
216 g_last_click_count
= 0;
217 last_click_position_x
= 0;
218 last_click_position_y
= 0;
219 g_last_click_time
= 0;
222 result
.clickCount
= g_last_click_count
;
226 if (wparam
& MK_CONTROL
)
227 result
.modifiers
|= WebInputEvent::ControlKey
;
228 if (wparam
& MK_SHIFT
)
229 result
.modifiers
|= WebInputEvent::ShiftKey
;
230 if (::GetKeyState(VK_MENU
) & 0x8000)
231 result
.modifiers
|= WebInputEvent::AltKey
;
232 if (wparam
& MK_LBUTTON
)
233 result
.modifiers
|= WebInputEvent::LeftButtonDown
;
234 if (wparam
& MK_MBUTTON
)
235 result
.modifiers
|= WebInputEvent::MiddleButtonDown
;
236 if (wparam
& MK_RBUTTON
)
237 result
.modifiers
|= WebInputEvent::RightButtonDown
;
239 SetToggleKeyState(&result
);
243 // WebMouseWheelEvent ---------------------------------------------------------
245 WebMouseWheelEvent
WebMouseWheelEventBuilder::Build(HWND hwnd
,
250 WebMouseWheelEvent result
;
252 result
.type
= WebInputEvent::MouseWheel
;
254 result
.timeStampSeconds
= time_ms
/ 1000.0;
256 result
.button
= WebMouseEvent::ButtonNone
;
258 // Get key state, coordinates, and wheel delta from event.
259 typedef SHORT (WINAPI
*GetKeyStateFunction
)(int key
);
260 GetKeyStateFunction get_key_state_func
;
263 bool horizontal_scroll
= false;
264 if ((message
== WM_VSCROLL
) || (message
== WM_HSCROLL
)) {
265 // Synthesize mousewheel event from a scroll event. This is needed to
266 // simulate middle mouse scrolling in some laptops. Use GetAsyncKeyState
267 // for key state since we are synthesizing the input event.
268 get_key_state_func
= GetAsyncKeyState
;
270 if (get_key_state_func(VK_SHIFT
) & 0x8000)
271 key_state
|= MK_SHIFT
;
272 if (get_key_state_func(VK_CONTROL
) & 0x8000)
273 key_state
|= MK_CONTROL
;
274 // NOTE: There doesn't seem to be a way to query the mouse button state
277 POINT cursor_position
= {0};
278 GetCursorPos(&cursor_position
);
279 result
.globalX
= cursor_position
.x
;
280 result
.globalY
= cursor_position
.y
;
282 switch (LOWORD(wparam
)) {
283 case SB_LINEUP
: // == SB_LINELEFT
284 wheel_delta
= WHEEL_DELTA
;
286 case SB_LINEDOWN
: // == SB_LINERIGHT
287 wheel_delta
= -WHEEL_DELTA
;
291 result
.scrollByPage
= true;
295 result
.scrollByPage
= true;
297 default: // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
302 if (message
== WM_HSCROLL
)
303 horizontal_scroll
= true;
305 // Non-synthesized event; we can just read data off the event.
306 get_key_state_func
= ::GetKeyState
;
307 key_state
= GET_KEYSTATE_WPARAM(wparam
);
309 result
.globalX
= static_cast<short>(LOWORD(lparam
));
310 result
.globalY
= static_cast<short>(HIWORD(lparam
));
312 wheel_delta
= static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam
));
313 if (message
== WM_MOUSEHWHEEL
) {
314 horizontal_scroll
= true;
315 wheel_delta
= -wheel_delta
; // Windows is <- -/+ ->, WebKit <- +/- ->.
318 if (key_state
& MK_SHIFT
)
319 horizontal_scroll
= true;
321 // Set modifiers based on key state.
322 if (key_state
& MK_SHIFT
)
323 result
.modifiers
|= WebInputEvent::ShiftKey
;
324 if (key_state
& MK_CONTROL
)
325 result
.modifiers
|= WebInputEvent::ControlKey
;
326 if (get_key_state_func(VK_MENU
) & 0x8000)
327 result
.modifiers
|= WebInputEvent::AltKey
;
328 if (key_state
& MK_LBUTTON
)
329 result
.modifiers
|= WebInputEvent::LeftButtonDown
;
330 if (key_state
& MK_MBUTTON
)
331 result
.modifiers
|= WebInputEvent::MiddleButtonDown
;
332 if (key_state
& MK_RBUTTON
)
333 result
.modifiers
|= WebInputEvent::RightButtonDown
;
335 SetToggleKeyState(&result
);
337 // Set coordinates by translating event coordinates from screen to client.
338 POINT client_point
= { result
.globalX
, result
.globalY
};
339 MapWindowPoints(0, hwnd
, &client_point
, 1);
340 result
.x
= client_point
.x
;
341 result
.y
= client_point
.y
;
342 result
.windowX
= result
.x
;
343 result
.windowY
= result
.y
;
345 // Convert wheel delta amount to a number of pixels to scroll.
347 // How many pixels should we scroll per line? Gecko uses the height of the
348 // current line, which means scroll distance changes as you go through the
349 // page or go to different pages. IE 8 is ~60 px/line, although the value
350 // seems to vary slightly by page and zoom level. Also, IE defaults to
351 // smooth scrolling while Firefox doesn't, so it can get away with somewhat
352 // larger scroll values without feeling as jerky. Here we use 100 px per
353 // three lines (the default scroll amount is three lines per wheel tick).
354 // Even though we have smooth scrolling, we don't make this as large as IE
355 // because subjectively IE feels like it scrolls farther than you want while
357 static const float kScrollbarPixelsPerLine
= 100.0f
/ 3.0f
;
358 wheel_delta
/= WHEEL_DELTA
;
359 float scroll_delta
= wheel_delta
;
360 if (horizontal_scroll
) {
361 unsigned long scroll_chars
= kDefaultScrollCharsPerWheelDelta
;
362 SystemParametersInfo(SPI_GETWHEELSCROLLCHARS
, 0, &scroll_chars
, 0);
363 // TODO(pkasting): Should probably have a different multiplier
364 // scrollbarPixelsPerChar here.
365 scroll_delta
*= static_cast<float>(scroll_chars
) * kScrollbarPixelsPerLine
;
367 unsigned long scroll_lines
= kDefaultScrollLinesPerWheelDelta
;
368 SystemParametersInfo(SPI_GETWHEELSCROLLLINES
, 0, &scroll_lines
, 0);
369 if (scroll_lines
== WHEEL_PAGESCROLL
)
370 result
.scrollByPage
= true;
371 if (!result
.scrollByPage
) {
373 static_cast<float>(scroll_lines
) * kScrollbarPixelsPerLine
;
377 // Set scroll amount based on above calculations. WebKit expects positive
378 // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
379 if (horizontal_scroll
) {
380 result
.deltaX
= scroll_delta
;
381 result
.wheelTicksX
= wheel_delta
;
383 result
.deltaY
= scroll_delta
;
384 result
.wheelTicksY
= wheel_delta
;
390 } // namespace content