1 // Copyright 2016 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 "ui/events/blink/web_input_event_builders_win.h"
6
7 #include "base/win/windowsx_shim.h"
8 #include "ui/base/ui_base_features.h"
9 #include "ui/display/win/screen_win.h"
10 #include "ui/events/blink/blink_event_util.h"
11 #include "ui/events/event_utils.h"
12
13 using blink::WebInputEvent;
14 using blink::WebKeyboardEvent;
15 using blink::WebMouseEvent;
16 using blink::WebMouseWheelEvent;
17
18 namespace ui {
19
20 static const unsigned long kDefaultScrollLinesPerWheelDelta = 3;
21 static const unsigned long kDefaultScrollCharsPerWheelDelta = 1;
22 static const unsigned long kScrollPercentPerLineOrChar = 5;
23
24 // WebMouseEvent --------------------------------------------------------------
25
26 static int g_last_click_count = 0;
27 static base::TimeTicks g_last_click_time;
28
GetRelativeCursorPos(HWND hwnd)29 static LPARAM GetRelativeCursorPos(HWND hwnd) {
30 POINT pos = {-1, -1};
31 GetCursorPos(&pos);
32 ScreenToClient(hwnd, &pos);
33 return MAKELPARAM(pos.x, pos.y);
34 }
35
Build(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam,base::TimeTicks time_stamp,blink::WebPointerProperties::PointerType pointer_type)36 WebMouseEvent WebMouseEventBuilder::Build(
37 HWND hwnd,
38 UINT message,
39 WPARAM wparam,
40 LPARAM lparam,
41 base::TimeTicks time_stamp,
42 blink::WebPointerProperties::PointerType pointer_type) {
43 WebInputEvent::Type type = WebInputEvent::Type::kUndefined;
44 WebMouseEvent::Button button = WebMouseEvent::Button::kNoButton;
45 switch (message) {
46 case WM_MOUSEMOVE:
47 type = WebInputEvent::kMouseMove;
48 if (wparam & MK_LBUTTON)
49 button = WebMouseEvent::Button::kLeft;
50 else if (wparam & MK_MBUTTON)
51 button = WebMouseEvent::Button::kMiddle;
52 else if (wparam & MK_RBUTTON)
53 button = WebMouseEvent::Button::kRight;
54 else
55 button = WebMouseEvent::Button::kNoButton;
56 break;
57 case WM_MOUSELEAVE:
58 case WM_NCMOUSELEAVE:
59 // TODO(rbyers): This should be MouseLeave but is disabled temporarily.
60 // See http://crbug.com/450631
61 type = WebInputEvent::kMouseMove;
62 button = WebMouseEvent::Button::kNoButton;
63 // set the current mouse position (relative to the client area of the
64 // current window) since none is specified for this event
65 lparam = GetRelativeCursorPos(hwnd);
66 break;
67 case WM_LBUTTONDOWN:
68 case WM_LBUTTONDBLCLK:
69 type = WebInputEvent::kMouseDown;
70 button = WebMouseEvent::Button::kLeft;
71 break;
72 case WM_MBUTTONDOWN:
73 case WM_MBUTTONDBLCLK:
74 type = WebInputEvent::kMouseDown;
75 button = WebMouseEvent::Button::kMiddle;
76 break;
77 case WM_RBUTTONDOWN:
78 case WM_RBUTTONDBLCLK:
79 type = WebInputEvent::kMouseDown;
80 button = WebMouseEvent::Button::kRight;
81 break;
82 case WM_XBUTTONDOWN:
83 case WM_XBUTTONDBLCLK:
84 type = WebInputEvent::kMouseDown;
85 if ((HIWORD(wparam) & XBUTTON1))
86 button = WebMouseEvent::Button::kBack;
87 else if ((HIWORD(wparam) & XBUTTON2))
88 button = WebMouseEvent::Button::kForward;
89 break;
90 case WM_LBUTTONUP:
91 type = WebInputEvent::kMouseUp;
92 button = WebMouseEvent::Button::kLeft;
93 break;
94 case WM_MBUTTONUP:
95 type = WebInputEvent::kMouseUp;
96 button = WebMouseEvent::Button::kMiddle;
97 break;
98 case WM_RBUTTONUP:
99 type = WebInputEvent::kMouseUp;
100 button = WebMouseEvent::Button::kRight;
101 break;
102 case WM_XBUTTONUP:
103 type = WebInputEvent::kMouseUp;
104 if ((HIWORD(wparam) & XBUTTON1))
105 button = WebMouseEvent::Button::kBack;
106 else if ((HIWORD(wparam) & XBUTTON2))
107 button = WebMouseEvent::Button::kForward;
108 break;
109 default:
110 NOTREACHED();
111 }
112
113 // set modifiers:
114 int modifiers =
115 ui::EventFlagsToWebEventModifiers(ui::GetModifiersFromKeyState());
116 if (wparam & MK_CONTROL)
117 modifiers |= WebInputEvent::kControlKey;
118 if (wparam & MK_SHIFT)
119 modifiers |= WebInputEvent::kShiftKey;
120 if (wparam & MK_LBUTTON)
121 modifiers |= WebInputEvent::kLeftButtonDown;
122 if (wparam & MK_MBUTTON)
123 modifiers |= WebInputEvent::kMiddleButtonDown;
124 if (wparam & MK_RBUTTON)
125 modifiers |= WebInputEvent::kRightButtonDown;
126 if (wparam & MK_XBUTTON1)
127 modifiers |= WebInputEvent::kBackButtonDown;
128 if (wparam & MK_XBUTTON2)
129 modifiers |= WebInputEvent::kForwardButtonDown;
130
131 WebMouseEvent result(type, modifiers, time_stamp);
132 result.pointer_type = pointer_type;
133 result.button = button;
134
135 // set position fields:
136 result.SetPositionInWidget(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
137
138 POINT global_point = {result.PositionInWidget().x(),
139 result.PositionInWidget().y()};
140 ClientToScreen(hwnd, &global_point);
141
142 // We need to convert the global point back to DIP before using it.
143 gfx::PointF dip_global_point = display::win::ScreenWin::ScreenToDIPPoint(
144 gfx::PointF(global_point.x, global_point.y));
145
146 result.SetPositionInScreen(dip_global_point.x(), dip_global_point.y());
147
148 // calculate number of clicks:
149
150 // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
151 // where their original code looks buggy.
152 static int last_click_position_x;
153 static int last_click_position_y;
154 static WebMouseEvent::Button last_click_button = WebMouseEvent::Button::kLeft;
155
156 base::TimeTicks current_time = result.TimeStamp();
157 bool cancel_previous_click =
158 (abs(last_click_position_x - result.PositionInWidget().x()) >
159 (::GetSystemMetrics(SM_CXDOUBLECLK) / 2)) ||
160 (abs(last_click_position_y - result.PositionInWidget().y()) >
161 (::GetSystemMetrics(SM_CYDOUBLECLK) / 2)) ||
162 ((current_time - g_last_click_time).InMilliseconds() >
163 ::GetDoubleClickTime());
164
165 if (result.GetType() == WebInputEvent::kMouseDown) {
166 if (!cancel_previous_click && (result.button == last_click_button)) {
167 ++g_last_click_count;
168 } else {
169 g_last_click_count = 1;
170 last_click_position_x = result.PositionInWidget().x();
171 last_click_position_y = result.PositionInWidget().y();
172 }
173 g_last_click_time = current_time;
174 last_click_button = result.button;
175 } else if (result.GetType() == WebInputEvent::kMouseMove ||
176 result.GetType() == WebInputEvent::kMouseLeave) {
177 if (cancel_previous_click) {
178 g_last_click_count = 0;
179 last_click_position_x = 0;
180 last_click_position_y = 0;
181 g_last_click_time = base::TimeTicks();
182 }
183 }
184 result.click_count = g_last_click_count;
185
186 return result;
187 }
188
189 // WebMouseWheelEvent ---------------------------------------------------------
190
Build(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam,base::TimeTicks time_stamp,blink::WebPointerProperties::PointerType pointer_type)191 WebMouseWheelEvent WebMouseWheelEventBuilder::Build(
192 HWND hwnd,
193 UINT message,
194 WPARAM wparam,
195 LPARAM lparam,
196 base::TimeTicks time_stamp,
197 blink::WebPointerProperties::PointerType pointer_type) {
198 WebMouseWheelEvent result(
199 WebInputEvent::kMouseWheel,
200 ui::EventFlagsToWebEventModifiers(ui::GetModifiersFromKeyState()),
201 time_stamp);
202
203 result.button = WebMouseEvent::Button::kNoButton;
204 result.pointer_type = pointer_type;
205
206 // Get key state, coordinates, and wheel delta from event.
207 UINT key_state;
208 float wheel_delta;
209 bool horizontal_scroll = false;
210 if ((message == WM_VSCROLL) || (message == WM_HSCROLL)) {
211 // Synthesize mousewheel event from a scroll event. This is needed to
212 // simulate middle mouse scrolling in some laptops. Use GetAsyncKeyState
213 // for key state since we are synthesizing the input event.
214 key_state = 0;
215 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
216 key_state |= MK_SHIFT;
217 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
218 key_state |= MK_CONTROL;
219 // NOTE: There doesn't seem to be a way to query the mouse button state
220 // in this case.
221
222 POINT cursor_position = {0};
223 GetCursorPos(&cursor_position);
224 result.SetPositionInScreen(cursor_position.x, cursor_position.y);
225
226 switch (LOWORD(wparam)) {
227 case SB_LINEUP: // == SB_LINELEFT
228 wheel_delta = WHEEL_DELTA;
229 break;
230 case SB_LINEDOWN: // == SB_LINERIGHT
231 wheel_delta = -WHEEL_DELTA;
232 break;
233 case SB_PAGEUP:
234 wheel_delta = 1;
235 result.delta_units = ui::ScrollGranularity::kScrollByPage;
236 break;
237 case SB_PAGEDOWN:
238 wheel_delta = -1;
239 result.delta_units = ui::ScrollGranularity::kScrollByPage;
240 break;
241 default: // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
242 wheel_delta = 0;
243 break;
244 }
245
246 if (message == WM_HSCROLL)
247 horizontal_scroll = true;
248 } else {
249 // Non-synthesized event; we can just read data off the event.
250 key_state = GET_KEYSTATE_WPARAM(wparam);
251
252 result.SetPositionInScreen(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
253
254 // Currently we leave hasPreciseScrollingDeltas false, even for trackpad
255 // scrolls that generate WM_MOUSEWHEEL, since we don't have a good way to
256 // distinguish these from real mouse wheels (crbug.com/545234).
257 wheel_delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam));
258
259 if (message == WM_MOUSEHWHEEL) {
260 horizontal_scroll = true;
261 wheel_delta = -wheel_delta; // Windows is <- -/+ ->, WebKit <- +/- ->.
262 }
263 }
264
265 // Set modifiers based on key state.
266 int modifiers = result.GetModifiers();
267 if (key_state & MK_SHIFT)
268 modifiers |= WebInputEvent::kShiftKey;
269 if (key_state & MK_CONTROL)
270 modifiers |= WebInputEvent::kControlKey;
271 if (key_state & MK_LBUTTON)
272 modifiers |= WebInputEvent::kLeftButtonDown;
273 if (key_state & MK_MBUTTON)
274 modifiers |= WebInputEvent::kMiddleButtonDown;
275 if (key_state & MK_RBUTTON)
276 modifiers |= WebInputEvent::kRightButtonDown;
277 result.SetModifiers(modifiers);
278
279 // Set coordinates by translating event coordinates from screen to client.
280 POINT client_point = {result.PositionInScreen().x(),
281 result.PositionInScreen().y()};
282 MapWindowPoints(0, hwnd, &client_point, 1);
283 result.SetPositionInWidget(client_point.x, client_point.y);
284
285 // |wheel_delta| is expressed in multiples or divisions of WHEEL_DELTA,
286 // divide this out here to get the number of wheel ticks.
287 float num_ticks = wheel_delta / WHEEL_DELTA;
288 float scroll_delta = num_ticks;
289 if (horizontal_scroll) {
290 unsigned long scroll_chars = kDefaultScrollCharsPerWheelDelta;
291 SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &scroll_chars, 0);
292 scroll_delta *= static_cast<float>(scroll_chars);
293 } else {
294 unsigned long scroll_lines = kDefaultScrollLinesPerWheelDelta;
295 SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scroll_lines, 0);
296 if (scroll_lines == WHEEL_PAGESCROLL)
297 result.delta_units = ui::ScrollGranularity::kScrollByPage;
298 else
299 scroll_delta *= static_cast<float>(scroll_lines);
300 }
301
302 if (result.delta_units != ui::ScrollGranularity::kScrollByPage) {
303 if (base::FeatureList::IsEnabled(features::kPercentBasedScrolling)) {
304 // If percent-based scrolling is enabled, the scroll_delta represents
305 // the percentage amount (out of 1, i.e. 1 == 100%) the targeted scroller
306 // should scroll. This percentage will be resolved against the size of
307 // the scroller in the renderer process.
308 scroll_delta *= kScrollPercentPerLineOrChar / 100.f;
309 result.delta_units = ui::ScrollGranularity::kScrollByPercentage;
310 } else {
311 // Convert wheel delta amount to a number of pixels to scroll.
312 //
313 // How many pixels should we scroll per line? Gecko uses the height of
314 // the current line, which means scroll distance changes as you go through
315 // the page or go to different pages. IE 8 is ~60 px/line, although the
316 // value seems to vary slightly by page and zoom level. Also, IE defaults
317 // to smooth scrolling while Firefox doesn't, so it can get away with
318 // somewhat larger scroll values without feeling as jerky. Here we use
319 // 100 px per three lines (the default scroll amount is three lines per
320 // wheel tick). Even though we have smooth scrolling, we don't make this
321 // as large as IE because subjectively IE feels like it scrolls farther
322 // than you want while reading articles.
323 static const float kScrollbarPixelsPerLine = 100.0f / 3.0f;
324
325 // TODO(pkasting): Should probably have a different multiplier for
326 // horizontal scrolls here.
327 scroll_delta *= kScrollbarPixelsPerLine;
328 }
329 }
330
331 // Set scroll amount based on above calculations. WebKit expects positive
332 // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
333 if (horizontal_scroll) {
334 result.delta_x = scroll_delta;
335 result.wheel_ticks_x = num_ticks;
336 } else {
337 result.delta_y = scroll_delta;
338 result.wheel_ticks_y = num_ticks;
339 }
340
341 return result;
342 }
343
344 } // namespace ui
345