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 result
.setKeyIdentifierFromWindowsKeyCode();
75 if (::GetKeyState(VK_SHIFT
) & 0x8000)
76 result
.modifiers
|= WebInputEvent::ShiftKey
;
77 if (::GetKeyState(VK_CONTROL
) & 0x8000)
78 result
.modifiers
|= WebInputEvent::ControlKey
;
79 if (::GetKeyState(VK_MENU
) & 0x8000)
80 result
.modifiers
|= WebInputEvent::AltKey
;
81 // NOTE: There doesn't seem to be a way to query the mouse button state in
84 // Bit 30 of lParam represents the "previous key state". If set, the key was
85 // already down, therefore this is an auto-repeat. Only apply this to key
86 // down events, to match DOM semantics.
87 if ((result
.type
== WebInputEvent::RawKeyDown
) && (lparam
& 0x40000000))
88 result
.modifiers
|= WebInputEvent::IsAutoRepeat
;
90 SetToggleKeyState(&result
);
94 // WebMouseEvent --------------------------------------------------------------
96 static int g_last_click_count
= 0;
97 static double g_last_click_time
= 0;
99 static LPARAM
GetRelativeCursorPos(HWND hwnd
) {
100 POINT pos
= {-1, -1};
102 ScreenToClient(hwnd
, &pos
);
103 return MAKELPARAM(pos
.x
, pos
.y
);
106 WebMouseEvent
WebMouseEventBuilder::Build(HWND hwnd
,
111 WebMouseEvent result
;
115 result
.type
= WebInputEvent::MouseMove
;
116 if (wparam
& MK_LBUTTON
)
117 result
.button
= WebMouseEvent::ButtonLeft
;
118 else if (wparam
& MK_MBUTTON
)
119 result
.button
= WebMouseEvent::ButtonMiddle
;
120 else if (wparam
& MK_RBUTTON
)
121 result
.button
= WebMouseEvent::ButtonRight
;
123 result
.button
= WebMouseEvent::ButtonNone
;
126 case WM_NCMOUSELEAVE
:
127 // TODO(rbyers): This should be MouseLeave but is disabled temporarily.
128 // See http://crbug.com/450631
129 result
.type
= WebInputEvent::MouseMove
;
130 result
.button
= WebMouseEvent::ButtonNone
;
131 // set the current mouse position (relative to the client area of the
132 // current window) since none is specified for this event
133 lparam
= GetRelativeCursorPos(hwnd
);
136 case WM_LBUTTONDBLCLK
:
137 result
.type
= WebInputEvent::MouseDown
;
138 result
.button
= WebMouseEvent::ButtonLeft
;
141 case WM_MBUTTONDBLCLK
:
142 result
.type
= WebInputEvent::MouseDown
;
143 result
.button
= WebMouseEvent::ButtonMiddle
;
146 case WM_RBUTTONDBLCLK
:
147 result
.type
= WebInputEvent::MouseDown
;
148 result
.button
= WebMouseEvent::ButtonRight
;
151 result
.type
= WebInputEvent::MouseUp
;
152 result
.button
= WebMouseEvent::ButtonLeft
;
155 result
.type
= WebInputEvent::MouseUp
;
156 result
.button
= WebMouseEvent::ButtonMiddle
;
159 result
.type
= WebInputEvent::MouseUp
;
160 result
.button
= WebMouseEvent::ButtonRight
;
166 result
.timeStampSeconds
= time_ms
/ 1000.0;
168 // set position fields:
170 result
.x
= static_cast<short>(LOWORD(lparam
));
171 result
.y
= static_cast<short>(HIWORD(lparam
));
172 result
.windowX
= result
.x
;
173 result
.windowY
= result
.y
;
175 POINT global_point
= { result
.x
, result
.y
};
176 ClientToScreen(hwnd
, &global_point
);
178 // We need to convert the global point back to DIP before using it.
179 gfx::Point dip_global_point
= gfx::win::ScreenToDIPPoint(
180 gfx::Point(global_point
.x
, global_point
.y
));
182 result
.globalX
= dip_global_point
.x();
183 result
.globalY
= dip_global_point
.y();
185 // calculate number of clicks:
187 // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
188 // where their original code looks buggy.
189 static int last_click_position_x
;
190 static int last_click_position_y
;
191 static WebMouseEvent::Button last_click_button
= WebMouseEvent::ButtonLeft
;
193 double current_time
= result
.timeStampSeconds
;
194 bool cancel_previous_click
=
195 (abs(last_click_position_x
- result
.x
) >
196 (::GetSystemMetrics(SM_CXDOUBLECLK
) / 2))
197 || (abs(last_click_position_y
- result
.y
) >
198 (::GetSystemMetrics(SM_CYDOUBLECLK
) / 2))
199 || ((current_time
- g_last_click_time
) * 1000.0 > ::GetDoubleClickTime());
201 if (result
.type
== WebInputEvent::MouseDown
) {
202 if (!cancel_previous_click
&& (result
.button
== last_click_button
)) {
203 ++g_last_click_count
;
205 g_last_click_count
= 1;
206 last_click_position_x
= result
.x
;
207 last_click_position_y
= result
.y
;
209 g_last_click_time
= current_time
;
210 last_click_button
= result
.button
;
211 } else if (result
.type
== WebInputEvent::MouseMove
212 || result
.type
== WebInputEvent::MouseLeave
) {
213 if (cancel_previous_click
) {
214 g_last_click_count
= 0;
215 last_click_position_x
= 0;
216 last_click_position_y
= 0;
217 g_last_click_time
= 0;
220 result
.clickCount
= g_last_click_count
;
224 if (wparam
& MK_CONTROL
)
225 result
.modifiers
|= WebInputEvent::ControlKey
;
226 if (wparam
& MK_SHIFT
)
227 result
.modifiers
|= WebInputEvent::ShiftKey
;
228 if (::GetKeyState(VK_MENU
) & 0x8000)
229 result
.modifiers
|= WebInputEvent::AltKey
;
230 if (wparam
& MK_LBUTTON
)
231 result
.modifiers
|= WebInputEvent::LeftButtonDown
;
232 if (wparam
& MK_MBUTTON
)
233 result
.modifiers
|= WebInputEvent::MiddleButtonDown
;
234 if (wparam
& MK_RBUTTON
)
235 result
.modifiers
|= WebInputEvent::RightButtonDown
;
237 SetToggleKeyState(&result
);
241 // WebMouseWheelEvent ---------------------------------------------------------
243 WebMouseWheelEvent
WebMouseWheelEventBuilder::Build(HWND hwnd
,
248 WebMouseWheelEvent result
;
250 result
.type
= WebInputEvent::MouseWheel
;
252 result
.timeStampSeconds
= time_ms
/ 1000.0;
254 result
.button
= WebMouseEvent::ButtonNone
;
256 // Get key state, coordinates, and wheel delta from event.
257 typedef SHORT (WINAPI
*GetKeyStateFunction
)(int key
);
258 GetKeyStateFunction get_key_state_func
;
261 bool horizontal_scroll
= false;
262 if ((message
== WM_VSCROLL
) || (message
== WM_HSCROLL
)) {
263 // Synthesize mousewheel event from a scroll event. This is needed to
264 // simulate middle mouse scrolling in some laptops. Use GetAsyncKeyState
265 // for key state since we are synthesizing the input event.
266 get_key_state_func
= GetAsyncKeyState
;
268 if (get_key_state_func(VK_SHIFT
) & 0x8000)
269 key_state
|= MK_SHIFT
;
270 if (get_key_state_func(VK_CONTROL
) & 0x8000)
271 key_state
|= MK_CONTROL
;
272 // NOTE: There doesn't seem to be a way to query the mouse button state
275 POINT cursor_position
= {0};
276 GetCursorPos(&cursor_position
);
277 result
.globalX
= cursor_position
.x
;
278 result
.globalY
= cursor_position
.y
;
280 switch (LOWORD(wparam
)) {
281 case SB_LINEUP
: // == SB_LINELEFT
282 wheel_delta
= WHEEL_DELTA
;
284 case SB_LINEDOWN
: // == SB_LINERIGHT
285 wheel_delta
= -WHEEL_DELTA
;
289 result
.scrollByPage
= true;
293 result
.scrollByPage
= true;
295 default: // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
300 if (message
== WM_HSCROLL
)
301 horizontal_scroll
= true;
303 // Non-synthesized event; we can just read data off the event.
304 get_key_state_func
= ::GetKeyState
;
305 key_state
= GET_KEYSTATE_WPARAM(wparam
);
307 result
.globalX
= static_cast<short>(LOWORD(lparam
));
308 result
.globalY
= static_cast<short>(HIWORD(lparam
));
310 wheel_delta
= static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam
));
311 if (message
== WM_MOUSEHWHEEL
) {
312 horizontal_scroll
= true;
313 wheel_delta
= -wheel_delta
; // Windows is <- -/+ ->, WebKit <- +/- ->.
316 if (key_state
& MK_SHIFT
)
317 horizontal_scroll
= true;
319 // Set modifiers based on key state.
320 if (key_state
& MK_SHIFT
)
321 result
.modifiers
|= WebInputEvent::ShiftKey
;
322 if (key_state
& MK_CONTROL
)
323 result
.modifiers
|= WebInputEvent::ControlKey
;
324 if (get_key_state_func(VK_MENU
) & 0x8000)
325 result
.modifiers
|= WebInputEvent::AltKey
;
326 if (key_state
& MK_LBUTTON
)
327 result
.modifiers
|= WebInputEvent::LeftButtonDown
;
328 if (key_state
& MK_MBUTTON
)
329 result
.modifiers
|= WebInputEvent::MiddleButtonDown
;
330 if (key_state
& MK_RBUTTON
)
331 result
.modifiers
|= WebInputEvent::RightButtonDown
;
333 SetToggleKeyState(&result
);
335 // Set coordinates by translating event coordinates from screen to client.
336 POINT client_point
= { result
.globalX
, result
.globalY
};
337 MapWindowPoints(0, hwnd
, &client_point
, 1);
338 result
.x
= client_point
.x
;
339 result
.y
= client_point
.y
;
340 result
.windowX
= result
.x
;
341 result
.windowY
= result
.y
;
343 // Convert wheel delta amount to a number of pixels to scroll.
345 // How many pixels should we scroll per line? Gecko uses the height of the
346 // current line, which means scroll distance changes as you go through the
347 // page or go to different pages. IE 8 is ~60 px/line, although the value
348 // seems to vary slightly by page and zoom level. Also, IE defaults to
349 // smooth scrolling while Firefox doesn't, so it can get away with somewhat
350 // larger scroll values without feeling as jerky. Here we use 100 px per
351 // three lines (the default scroll amount is three lines per wheel tick).
352 // Even though we have smooth scrolling, we don't make this as large as IE
353 // because subjectively IE feels like it scrolls farther than you want while
355 static const float kScrollbarPixelsPerLine
= 100.0f
/ 3.0f
;
356 wheel_delta
/= WHEEL_DELTA
;
357 float scroll_delta
= wheel_delta
;
358 if (horizontal_scroll
) {
359 unsigned long scroll_chars
= kDefaultScrollCharsPerWheelDelta
;
360 SystemParametersInfo(SPI_GETWHEELSCROLLCHARS
, 0, &scroll_chars
, 0);
361 // TODO(pkasting): Should probably have a different multiplier
362 // scrollbarPixelsPerChar here.
363 scroll_delta
*= static_cast<float>(scroll_chars
) * kScrollbarPixelsPerLine
;
365 unsigned long scroll_lines
= kDefaultScrollLinesPerWheelDelta
;
366 SystemParametersInfo(SPI_GETWHEELSCROLLLINES
, 0, &scroll_lines
, 0);
367 if (scroll_lines
== WHEEL_PAGESCROLL
)
368 result
.scrollByPage
= true;
369 if (!result
.scrollByPage
) {
371 static_cast<float>(scroll_lines
) * kScrollbarPixelsPerLine
;
375 // Set scroll amount based on above calculations. WebKit expects positive
376 // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
377 if (horizontal_scroll
) {
378 result
.deltaX
= scroll_delta
;
379 result
.wheelTicksX
= wheel_delta
;
381 result
.deltaY
= scroll_delta
;
382 result
.wheelTicksY
= wheel_delta
;
388 } // namespace content