Home | History | Annotate | Download | only in tabs
      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)
     16 aura::Window* GetLocalProcessWindowAtPointAsh(
     17     const gfx::Point& screen_point,
     18     const std::set<aura::Window*>& ignore);
     19 #endif
     20 
     21 namespace {
     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.
     30 class 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.
     64 class 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.
    155 class 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 
    208 std::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 
    221 aura::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