1 // Copyright (c) 2009 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/base/win/mouse_wheel_util.h"
6 
7 #include "base/auto_reset.h"
8 #include "base/win/windowsx_shim.h"
9 #include "ui/base/view_prop.h"
10 #include "ui/gfx/win/hwnd_util.h"
11 
12 namespace ui {
13 
14 // Property used to indicate the HWND supports having mouse wheel messages
15 // rerouted to it.
16 static const char* const kHWNDSupportMouseWheelRerouting =
17     "__HWND_MW_REROUTE_OK";
18 
WindowSupportsRerouteMouseWheel(HWND window)19 static bool WindowSupportsRerouteMouseWheel(HWND window) {
20   while (GetWindowLong(window, GWL_STYLE) & WS_CHILD) {
21     if (!IsWindow(window))
22       break;
23 
24     if (ViewProp::GetValue(window, kHWNDSupportMouseWheelRerouting) != NULL) {
25       return true;
26     }
27     window = GetParent(window);
28   }
29   return false;
30 }
31 
IsCompatibleWithMouseWheelRedirection(HWND window)32 static bool IsCompatibleWithMouseWheelRedirection(HWND window) {
33   std::wstring class_name = gfx::GetClassName(window);
34   // Mousewheel redirection to comboboxes is a surprising and
35   // undesireable user behavior.
36   return !(class_name == L"ComboBox" ||
37            class_name == L"ComboBoxEx32");
38 }
39 
CanRedirectMouseWheelFrom(HWND window)40 static bool CanRedirectMouseWheelFrom(HWND window) {
41   std::wstring class_name = gfx::GetClassName(window);
42 
43   // Older Thinkpad mouse wheel drivers create a window under mouse wheel
44   // pointer. Detect if we are dealing with this window. In this case we
45   // don't need to do anything as the Thinkpad mouse driver will send
46   // mouse wheel messages to the right window.
47   if ((class_name == L"Syn Visual Class") ||
48      (class_name == L"SynTrackCursorWindowClass"))
49     return false;
50 
51   return true;
52 }
53 
SetWindowSupportsRerouteMouseWheel(HWND hwnd)54 ViewProp* SetWindowSupportsRerouteMouseWheel(HWND hwnd) {
55   return new ViewProp(hwnd, kHWNDSupportMouseWheelRerouting,
56                       reinterpret_cast<HANDLE>(true));
57 }
58 
RerouteMouseWheel(HWND window,WPARAM w_param,LPARAM l_param)59 bool RerouteMouseWheel(HWND window, WPARAM w_param, LPARAM l_param) {
60   // Since this is called from a subclass for every window, we can get
61   // here recursively. This will happen if, for example, a control
62   // reflects wheel scroll messages to its parent. Bail out if we got
63   // here recursively.
64   static bool recursion_break = false;
65   if (recursion_break)
66     return false;
67   // Check if this window's class has a bad interaction with rerouting.
68   if (!IsCompatibleWithMouseWheelRedirection(window))
69     return false;
70 
71   DWORD current_process = GetCurrentProcessId();
72   POINT wheel_location = { GET_X_LPARAM(l_param), GET_Y_LPARAM(l_param) };
73   HWND window_under_wheel = WindowFromPoint(wheel_location);
74 
75   if (!CanRedirectMouseWheelFrom(window_under_wheel))
76     return false;
77 
78   // Find the lowest Chrome window in the hierarchy that can be the
79   // target of mouse wheel redirection.
80   while (window != window_under_wheel) {
81     // If window_under_wheel is not a valid Chrome window, then return true to
82     // suppress further processing of the message.
83     if (!::IsWindow(window_under_wheel))
84       return true;
85     DWORD wheel_window_process = 0;
86     GetWindowThreadProcessId(window_under_wheel, &wheel_window_process);
87     if (current_process != wheel_window_process) {
88       if (IsChild(window, window_under_wheel)) {
89         // If this message is reflected from a child window in a different
90         // process (happens with out of process windowed plugins) then
91         // we don't want to reroute the wheel message.
92         return false;
93       } else {
94         // The wheel is scrolling over an unrelated window. Make sure that we
95         // have marked that window as supporting mouse wheel rerouting.
96         // Otherwise, we cannot send random WM_MOUSEWHEEL messages to arbitrary
97         // windows. So just drop the message.
98         if (!WindowSupportsRerouteMouseWheel(window_under_wheel))
99           return true;
100       }
101     }
102 
103     // If the child window is transparent, then it is not interested in
104     // receiving wheel events.
105     if (IsChild(window, window_under_wheel) &&
106         ::GetWindowLong(
107             window_under_wheel, GWL_EXSTYLE) & WS_EX_TRANSPARENT) {
108       return false;
109     }
110 
111     // window_under_wheel is a Chrome window.  If allowed, redirect.
112     if (IsCompatibleWithMouseWheelRedirection(window_under_wheel)) {
113       base::AutoReset<bool> auto_reset_recursion_break(&recursion_break, true);
114       SendMessage(window_under_wheel, WM_MOUSEWHEEL, w_param, l_param);
115       return true;
116     }
117     // If redirection is disallowed, try the parent.
118     window_under_wheel = GetAncestor(window_under_wheel, GA_PARENT);
119   }
120   // If we traversed back to the starting point, we should process
121   // this message normally; return false.
122   return false;
123 }
124 
125 }  // namespace ui
126