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