1// Copyright 2014 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 "chrome/browser/ui/views/tabs/window_finder.h"
6
7#include "base/win/scoped_gdi_object.h"
8#include "base/win/windows_version.h"
9#include "ui/aura/window.h"
10#include "ui/gfx/screen.h"
11#include "ui/gfx/win/dpi.h"
12#include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h"
13#include "ui/views/win/hwnd_util.h"
14
15#if defined(USE_ASH)
16aura::Window* GetLocalProcessWindowAtPointAsh(
17    const gfx::Point& screen_point,
18    const std::set<aura::Window*>& ignore);
19#endif
20
21namespace {
22
23// BaseWindowFinder -----------------------------------------------------------
24
25// Base class used to locate a window. This is intended to be used with the
26// various win32 functions that iterate over windows.
27//
28// A subclass need only override ShouldStopIterating to determine when
29// iteration should stop.
30class BaseWindowFinder {
31 public:
32  // Creates a BaseWindowFinder with the specified set of HWNDs to ignore.
33  explicit BaseWindowFinder(const std::set<HWND>& ignore) : ignore_(ignore) {}
34  virtual ~BaseWindowFinder() {}
35
36 protected:
37  static BOOL CALLBACK WindowCallbackProc(HWND hwnd, LPARAM lParam) {
38    // Cast must match that in as_lparam().
39    BaseWindowFinder* finder = reinterpret_cast<BaseWindowFinder*>(lParam);
40    if (finder->ignore_.find(hwnd) != finder->ignore_.end())
41      return TRUE;
42
43    return finder->ShouldStopIterating(hwnd) ? FALSE : TRUE;
44  }
45
46  LPARAM as_lparam() {
47    // Cast must match that in WindowCallbackProc().
48    return reinterpret_cast<LPARAM>(static_cast<BaseWindowFinder*>(this));
49  }
50
51  // Returns true if iteration should stop, false if iteration should continue.
52  virtual bool ShouldStopIterating(HWND window) = 0;
53
54 private:
55  const std::set<HWND>& ignore_;
56
57  DISALLOW_COPY_AND_ASSIGN(BaseWindowFinder);
58};
59
60// TopMostFinder --------------------------------------------------------------
61
62// Helper class to determine if a particular point of a window is not obscured
63// by another window.
64class TopMostFinder : public BaseWindowFinder {
65 public:
66  // Returns true if |window| is the topmost window at the location
67  // |screen_loc|, not including the windows in |ignore|.
68  static bool IsTopMostWindowAtPoint(HWND window,
69                                     const gfx::Point& screen_loc,
70                                     const std::set<HWND>& ignore) {
71    TopMostFinder finder(window, screen_loc, ignore);
72    return finder.is_top_most_;
73  }
74
75  virtual bool ShouldStopIterating(HWND hwnd) {
76    if (hwnd == target_) {
77      // Window is topmost, stop iterating.
78      is_top_most_ = true;
79      return true;
80    }
81
82    if (!IsWindowVisible(hwnd)) {
83      // The window isn't visible, keep iterating.
84      return false;
85    }
86
87    RECT r;
88    if (!GetWindowRect(hwnd, &r) || !PtInRect(&r, screen_loc_.ToPOINT())) {
89      // The window doesn't contain the point, keep iterating.
90      return false;
91    }
92
93    LONG ex_styles = GetWindowLong(hwnd, GWL_EXSTYLE);
94    if (ex_styles & WS_EX_TRANSPARENT || ex_styles & WS_EX_LAYERED) {
95      // Mouse events fall through WS_EX_TRANSPARENT windows, so we ignore them.
96      //
97      // WS_EX_LAYERED is trickier. Apps like Switcher create a totally
98      // transparent WS_EX_LAYERED window that is always on top. If we don't
99      // ignore WS_EX_LAYERED windows and there are totally transparent
100      // WS_EX_LAYERED windows then there are effectively holes on the screen
101      // that the user can't reattach tabs to. So we ignore them. This is a bit
102      // problematic in so far as WS_EX_LAYERED windows need not be totally
103      // transparent in which case we treat chrome windows as not being obscured
104      // when they really are, but this is better than not being able to
105      // reattach tabs.
106      return false;
107    }
108
109    // hwnd is at the point. Make sure the point is within the windows region.
110    if (GetWindowRgn(hwnd, tmp_region_.Get()) == ERROR) {
111      // There's no region on the window and the window contains the point. Stop
112      // iterating.
113      return true;
114    }
115
116    // The region is relative to the window's rect.
117    BOOL is_point_in_region = PtInRegion(tmp_region_.Get(),
118        screen_loc_.x() - r.left, screen_loc_.y() - r.top);
119    tmp_region_ = CreateRectRgn(0, 0, 0, 0);
120    // Stop iterating if the region contains the point.
121    return !!is_point_in_region;
122  }
123
124 private:
125  TopMostFinder(HWND window,
126                const gfx::Point& screen_loc,
127                const std::set<HWND>& ignore)
128      : BaseWindowFinder(ignore),
129        target_(window),
130        is_top_most_(false),
131        tmp_region_(CreateRectRgn(0, 0, 0, 0)) {
132    screen_loc_ = gfx::win::DIPToScreenPoint(screen_loc);
133    EnumWindows(WindowCallbackProc, as_lparam());
134  }
135
136  // The window we're looking for.
137  HWND target_;
138
139  // Location of window to find in pixel coordinates.
140  gfx::Point screen_loc_;
141
142  // Is target_ the top most window? This is initially false but set to true
143  // in ShouldStopIterating if target_ is passed in.
144  bool is_top_most_;
145
146  base::win::ScopedRegion tmp_region_;
147
148  DISALLOW_COPY_AND_ASSIGN(TopMostFinder);
149};
150
151// WindowFinder ---------------------------------------------------------------
152
153// Helper class to determine if a particular point contains a window from our
154// process.
155class LocalProcessWindowFinder : public BaseWindowFinder {
156 public:
157  // Returns the hwnd from our process at screen_loc that is not obscured by
158  // another window. Returns NULL otherwise.
159  static gfx::NativeWindow GetProcessWindowAtPoint(
160      const gfx::Point& screen_loc,
161      const std::set<HWND>& ignore) {
162    LocalProcessWindowFinder finder(screen_loc, ignore);
163    // Windows 8 has a window that appears first in the list of iterated
164    // windows, yet is not visually on top of everything.
165    // TODO(sky): figure out a better way to ignore this window.
166    if (finder.result_ &&
167        ((base::win::OSInfo::GetInstance()->version() >=
168          base::win::VERSION_WIN8) ||
169         TopMostFinder::IsTopMostWindowAtPoint(finder.result_,
170                                               screen_loc,
171                                               ignore))) {
172      return views::DesktopWindowTreeHostWin::GetContentWindowForHWND(
173          finder.result_);
174    }
175    return NULL;
176  }
177
178 protected:
179  virtual bool ShouldStopIterating(HWND hwnd) {
180    RECT r;
181    if (IsWindowVisible(hwnd) && GetWindowRect(hwnd, &r) &&
182        PtInRect(&r, screen_loc_.ToPOINT())) {
183      result_ = hwnd;
184      return true;
185    }
186    return false;
187  }
188
189 private:
190  LocalProcessWindowFinder(const gfx::Point& screen_loc,
191                           const std::set<HWND>& ignore)
192      : BaseWindowFinder(ignore),
193        result_(NULL) {
194    screen_loc_ = gfx::win::DIPToScreenPoint(screen_loc);
195    EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, as_lparam());
196  }
197
198  // Position of the mouse in pixel coordinates.
199  gfx::Point screen_loc_;
200
201  // The resulting window. This is initially null but set to true in
202  // ShouldStopIterating if an appropriate window is found.
203  HWND result_;
204
205  DISALLOW_COPY_AND_ASSIGN(LocalProcessWindowFinder);
206};
207
208std::set<HWND> RemapIgnoreSet(const std::set<gfx::NativeView>& ignore) {
209  std::set<HWND> hwnd_set;
210  std::set<gfx::NativeView>::const_iterator it = ignore.begin();
211  for (; it != ignore.end(); ++it) {
212    HWND w = (*it)->GetHost()->GetAcceleratedWidget();
213    if (w)
214      hwnd_set.insert(w);
215  }
216  return hwnd_set;
217}
218
219}  // namespace
220
221aura::Window* GetLocalProcessWindowAtPoint(
222    chrome::HostDesktopType host_desktop_type,
223    const gfx::Point& screen_point,
224    const std::set<aura::Window*>& ignore) {
225#if defined(USE_ASH)
226  if (host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH)
227    return GetLocalProcessWindowAtPointAsh(screen_point, ignore);
228#endif
229  return LocalProcessWindowFinder::GetProcessWindowAtPoint(
230          screen_point, RemapIgnoreSet(ignore));
231}
232