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