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