Home | History | Annotate | Download | only in desktop_aura
      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 "ui/views/widget/desktop_aura/x11_topmost_window_finder.h"
      6 
      7 #include <algorithm>
      8 #include <vector>
      9 #include <X11/extensions/shape.h>
     10 #include <X11/Xlib.h>
     11 #include <X11/Xregion.h>
     12 
     13 // Get rid of X11 macros which conflict with gtest.
     14 #undef Bool
     15 #undef None
     16 
     17 #include "base/memory/scoped_ptr.h"
     18 #include "third_party/skia/include/core/SkRect.h"
     19 #include "third_party/skia/include/core/SkRegion.h"
     20 #include "ui/aura/window.h"
     21 #include "ui/aura/window_tree_host.h"
     22 #include "ui/events/platform/x11/x11_event_source.h"
     23 #include "ui/gfx/path.h"
     24 #include "ui/gfx/path_x11.h"
     25 #include "ui/gfx/x/x11_atom_cache.h"
     26 #include "ui/views/test/views_test_base.h"
     27 #include "ui/views/test/x11_property_change_waiter.h"
     28 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
     29 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
     30 #include "ui/views/widget/widget.h"
     31 
     32 namespace views {
     33 
     34 namespace {
     35 
     36 // Waits till |window| is minimized.
     37 class MinimizeWaiter : public X11PropertyChangeWaiter {
     38  public:
     39   explicit MinimizeWaiter(XID window)
     40       : X11PropertyChangeWaiter(window, "_NET_WM_STATE") {
     41     const char* kAtomsToCache[] = { "_NET_WM_STATE_HIDDEN", NULL };
     42     atom_cache_.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache));
     43   }
     44 
     45   virtual ~MinimizeWaiter() {
     46   }
     47 
     48  private:
     49   // X11PropertyChangeWaiter:
     50   virtual bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) OVERRIDE {
     51     std::vector<Atom> wm_states;
     52     if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &wm_states)) {
     53       std::vector<Atom>::iterator it = std::find(
     54           wm_states.begin(),
     55           wm_states.end(),
     56           atom_cache_->GetAtom("_NET_WM_STATE_HIDDEN"));
     57       return it == wm_states.end();
     58     }
     59     return true;
     60   }
     61 
     62   scoped_ptr<ui::X11AtomCache> atom_cache_;
     63 
     64   DISALLOW_COPY_AND_ASSIGN(MinimizeWaiter);
     65 };
     66 
     67 // Waits till |_NET_CLIENT_LIST_STACKING| is updated to include
     68 // |expected_windows|.
     69 class StackingClientListWaiter : public X11PropertyChangeWaiter {
     70  public:
     71   StackingClientListWaiter(XID* expected_windows, size_t count)
     72       : X11PropertyChangeWaiter(ui::GetX11RootWindow(),
     73                                 "_NET_CLIENT_LIST_STACKING"),
     74         expected_windows_(expected_windows, expected_windows + count) {
     75   }
     76 
     77   virtual ~StackingClientListWaiter() {
     78   }
     79 
     80   // X11PropertyChangeWaiter:
     81   virtual void Wait() OVERRIDE {
     82     // StackingClientListWaiter may be created after
     83     // _NET_CLIENT_LIST_STACKING already contains |expected_windows|.
     84     if (!ShouldKeepOnWaiting(NULL))
     85       return;
     86 
     87     X11PropertyChangeWaiter::Wait();
     88   }
     89 
     90  private:
     91   // X11PropertyChangeWaiter:
     92   virtual bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) OVERRIDE {
     93     std::vector<XID> stack;
     94     ui::GetXWindowStack(ui::GetX11RootWindow(), &stack);
     95     for (size_t i = 0; i < expected_windows_.size(); ++i) {
     96       std::vector<XID>::iterator it = std::find(
     97           stack.begin(), stack.end(), expected_windows_[i]);
     98       if (it == stack.end())
     99         return true;
    100     }
    101     return false;
    102   }
    103 
    104   std::vector<XID> expected_windows_;
    105 
    106   DISALLOW_COPY_AND_ASSIGN(StackingClientListWaiter);
    107 };
    108 
    109 }  // namespace
    110 
    111 class X11TopmostWindowFinderTest : public ViewsTestBase {
    112  public:
    113   X11TopmostWindowFinderTest() {
    114   }
    115 
    116   virtual ~X11TopmostWindowFinderTest() {
    117   }
    118 
    119   // Creates and shows a Widget with |bounds|. The caller takes ownership of
    120   // the returned widget.
    121   scoped_ptr<Widget> CreateAndShowWidget(const gfx::Rect& bounds) {
    122     scoped_ptr<Widget> toplevel(new Widget);
    123     Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
    124     params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    125     params.native_widget = new DesktopNativeWidgetAura(toplevel.get());
    126     params.bounds = bounds;
    127     params.remove_standard_frame = true;
    128     toplevel->Init(params);
    129     toplevel->Show();
    130     return toplevel.Pass();
    131   }
    132 
    133   // Creates and shows an X window with |bounds|.
    134   XID CreateAndShowXWindow(const gfx::Rect& bounds) {
    135     XID root = DefaultRootWindow(xdisplay());
    136     XID xid = XCreateSimpleWindow(xdisplay(),
    137                                   root,
    138                                   0, 0, 1, 1,
    139                                   0,   // border_width
    140                                   0,   // border
    141                                   0);  // background
    142 
    143     ui::SetUseOSWindowFrame(xid, false);
    144     ShowAndSetXWindowBounds(xid, bounds);
    145     return xid;
    146   }
    147 
    148   // Shows |xid| and sets its bounds.
    149   void ShowAndSetXWindowBounds(XID xid, const gfx::Rect& bounds) {
    150     XMapWindow(xdisplay(), xid);
    151 
    152     XWindowChanges changes = {0};
    153     changes.x = bounds.x();
    154     changes.y = bounds.y();
    155     changes.width = bounds.width();
    156     changes.height = bounds.height();
    157     XConfigureWindow(xdisplay(),
    158                      xid,
    159                      CWX | CWY | CWWidth | CWHeight,
    160                      &changes);
    161   }
    162 
    163   Display* xdisplay() {
    164     return gfx::GetXDisplay();
    165   }
    166 
    167   // Returns the topmost X window at the passed in screen position.
    168   XID FindTopmostXWindowAt(int screen_x, int screen_y) {
    169     X11TopmostWindowFinder finder;
    170     return finder.FindWindowAt(gfx::Point(screen_x, screen_y));
    171   }
    172 
    173   // Returns the topmost aura::Window at the passed in screen position. Returns
    174   // NULL if the topmost window does not have an associated aura::Window.
    175   aura::Window* FindTopmostLocalProcessWindowAt(int screen_x, int screen_y) {
    176     X11TopmostWindowFinder finder;
    177     return finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y),
    178                                            std::set<aura::Window*>());
    179   }
    180 
    181   // Returns the topmost aura::Window at the passed in screen position ignoring
    182   // |ignore_window|. Returns NULL if the topmost window does not have an
    183   // associated aura::Window.
    184   aura::Window* FindTopmostLocalProcessWindowWithIgnore(
    185       int screen_x,
    186       int screen_y,
    187       aura::Window* ignore_window) {
    188     std::set<aura::Window*> ignore;
    189     ignore.insert(ignore_window);
    190     X11TopmostWindowFinder finder;
    191     return finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y),
    192                                            ignore);
    193   }
    194 
    195   // ViewsTestBase:
    196   virtual void SetUp() OVERRIDE {
    197     ViewsTestBase::SetUp();
    198 
    199     // Make X11 synchronous for our display connection. This does not force the
    200     // window manager to behave synchronously.
    201     XSynchronize(xdisplay(), True);
    202 
    203     // Ensure that the X11DesktopHandler exists. The X11DesktopHandler is
    204     // necessary to properly track menu windows.
    205     X11DesktopHandler::get();
    206   }
    207 
    208   virtual void TearDown() OVERRIDE {
    209     XSynchronize(xdisplay(), False);
    210     ViewsTestBase::TearDown();
    211   }
    212 
    213  private:
    214   DISALLOW_COPY_AND_ASSIGN(X11TopmostWindowFinderTest);
    215 };
    216 
    217 TEST_F(X11TopmostWindowFinderTest, Basic) {
    218   // Avoid positioning test windows at 0x0 because window managers often have a
    219   // panel/launcher along one of the screen edges and do not allow windows to
    220   // position themselves to overlap the panel/launcher.
    221   scoped_ptr<Widget> widget1(
    222       CreateAndShowWidget(gfx::Rect(100, 100, 200, 100)));
    223   aura::Window* window1 = widget1->GetNativeWindow();
    224   XID xid1 = window1->GetHost()->GetAcceleratedWidget();
    225 
    226   XID xid2 = CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200));
    227 
    228   scoped_ptr<Widget> widget3(
    229       CreateAndShowWidget(gfx::Rect(100, 190, 200, 110)));
    230   aura::Window* window3 = widget3->GetNativeWindow();
    231   XID xid3 = window3->GetHost()->GetAcceleratedWidget();
    232 
    233   XID xids[] = { xid1, xid2, xid3 };
    234   StackingClientListWaiter waiter(xids, arraysize(xids));
    235   waiter.Wait();
    236   ui::X11EventSource::GetInstance()->DispatchXEvents();
    237 
    238   EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150));
    239   EXPECT_EQ(window1, FindTopmostLocalProcessWindowAt(150, 150));
    240 
    241   EXPECT_EQ(xid2, FindTopmostXWindowAt(250, 150));
    242   EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(250, 150));
    243 
    244   EXPECT_EQ(xid3, FindTopmostXWindowAt(250, 250));
    245   EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(250, 250));
    246 
    247   EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 250));
    248   EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 250));
    249 
    250   EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 195));
    251   EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 195));
    252 
    253   EXPECT_NE(xid1, FindTopmostXWindowAt(1000, 1000));
    254   EXPECT_NE(xid2, FindTopmostXWindowAt(1000, 1000));
    255   EXPECT_NE(xid3, FindTopmostXWindowAt(1000, 1000));
    256   EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(1000, 1000));
    257 
    258   EXPECT_EQ(window1,
    259             FindTopmostLocalProcessWindowWithIgnore(150, 150, window3));
    260   EXPECT_EQ(NULL,
    261             FindTopmostLocalProcessWindowWithIgnore(250, 250, window3));
    262   EXPECT_EQ(NULL,
    263             FindTopmostLocalProcessWindowWithIgnore(150, 250, window3));
    264   EXPECT_EQ(window1,
    265             FindTopmostLocalProcessWindowWithIgnore(150, 195, window3));
    266 
    267   XDestroyWindow(xdisplay(), xid2);
    268 }
    269 
    270 // Test that the minimized state is properly handled.
    271 TEST_F(X11TopmostWindowFinderTest, Minimized) {
    272   scoped_ptr<Widget> widget1(
    273       CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
    274   aura::Window* window1 = widget1->GetNativeWindow();
    275   XID xid1 = window1->GetHost()->GetAcceleratedWidget();
    276   XID xid2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
    277 
    278   XID xids[] = { xid1, xid2 };
    279   StackingClientListWaiter stack_waiter(xids, arraysize(xids));
    280   stack_waiter.Wait();
    281   ui::X11EventSource::GetInstance()->DispatchXEvents();
    282 
    283   EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150));
    284   {
    285     MinimizeWaiter minimize_waiter(xid1);
    286     XIconifyWindow(xdisplay(), xid1, 0);
    287     minimize_waiter.Wait();
    288   }
    289   EXPECT_NE(xid1, FindTopmostXWindowAt(150, 150));
    290   EXPECT_NE(xid2, FindTopmostXWindowAt(150, 150));
    291 
    292   // Repeat test for an X window which does not belong to a views::Widget
    293   // because the code path is different.
    294   EXPECT_EQ(xid2, FindTopmostXWindowAt(350, 150));
    295   {
    296     MinimizeWaiter minimize_waiter(xid2);
    297     XIconifyWindow(xdisplay(), xid2, 0);
    298     minimize_waiter.Wait();
    299   }
    300   EXPECT_NE(xid1, FindTopmostXWindowAt(350, 150));
    301   EXPECT_NE(xid2, FindTopmostXWindowAt(350, 150));
    302 
    303   XDestroyWindow(xdisplay(), xid2);
    304 }
    305 
    306 // Test that non-rectangular windows are properly handled.
    307 TEST_F(X11TopmostWindowFinderTest, NonRectangular) {
    308   if (!ui::IsShapeExtensionAvailable())
    309     return;
    310 
    311   scoped_ptr<Widget> widget1(
    312       CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
    313   XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
    314   SkRegion* skregion1 = new SkRegion;
    315   skregion1->op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op);
    316   skregion1->op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op);
    317   // Widget takes ownership of |skregion1|.
    318   widget1->SetShape(skregion1);
    319 
    320   SkRegion skregion2;
    321   skregion2.op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op);
    322   skregion2.op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op);
    323   XID xid2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
    324   REGION* region2 = gfx::CreateRegionFromSkRegion(skregion2);
    325   XShapeCombineRegion(xdisplay(), xid2, ShapeBounding, 0, 0, region2,
    326       false);
    327   XDestroyRegion(region2);
    328 
    329   XID xids[] = { xid1, xid2 };
    330   StackingClientListWaiter stack_waiter(xids, arraysize(xids));
    331   stack_waiter.Wait();
    332   ui::X11EventSource::GetInstance()->DispatchXEvents();
    333 
    334   EXPECT_EQ(xid1, FindTopmostXWindowAt(105, 120));
    335   EXPECT_NE(xid1, FindTopmostXWindowAt(105, 105));
    336   EXPECT_NE(xid2, FindTopmostXWindowAt(105, 105));
    337 
    338   // Repeat test for an X window which does not belong to a views::Widget
    339   // because the code path is different.
    340   EXPECT_EQ(xid2, FindTopmostXWindowAt(305, 120));
    341   EXPECT_NE(xid1, FindTopmostXWindowAt(305, 105));
    342   EXPECT_NE(xid2, FindTopmostXWindowAt(305, 105));
    343 
    344   XDestroyWindow(xdisplay(), xid2);
    345 }
    346 
    347 // Test that the TopmostWindowFinder finds windows which belong to menus
    348 // (which may or may not belong to Chrome).
    349 TEST_F(X11TopmostWindowFinderTest, Menu) {
    350   XID xid = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100));
    351 
    352   XID root = DefaultRootWindow(xdisplay());
    353   XSetWindowAttributes swa;
    354   swa.override_redirect = True;
    355   XID menu_xid = XCreateWindow(xdisplay(),
    356                                root,
    357                                0, 0, 1, 1,
    358                                0,                  // border width
    359                                CopyFromParent,     // depth
    360                                InputOutput,
    361                                CopyFromParent,     // visual
    362                                CWOverrideRedirect,
    363                                &swa);
    364   {
    365     const char* kAtomsToCache[] = { "_NET_WM_WINDOW_TYPE_MENU", NULL };
    366     ui::X11AtomCache atom_cache(gfx::GetXDisplay(), kAtomsToCache);
    367     ui::SetAtomProperty(menu_xid,
    368                         "_NET_WM_WINDOW_TYPE",
    369                         "ATOM",
    370                         atom_cache.GetAtom("_NET_WM_WINDOW_TYPE_MENU"));
    371   }
    372   ui::SetUseOSWindowFrame(menu_xid, false);
    373   ShowAndSetXWindowBounds(menu_xid, gfx::Rect(140, 110, 100, 100));
    374   ui::X11EventSource::GetInstance()->DispatchXEvents();
    375 
    376   // |menu_xid| is never added to _NET_CLIENT_LIST_STACKING.
    377   XID xids[] = { xid };
    378   StackingClientListWaiter stack_waiter(xids, arraysize(xids));
    379   stack_waiter.Wait();
    380 
    381   EXPECT_EQ(xid, FindTopmostXWindowAt(110, 110));
    382   EXPECT_EQ(menu_xid, FindTopmostXWindowAt(150, 120));
    383   EXPECT_EQ(menu_xid, FindTopmostXWindowAt(210, 120));
    384 
    385   XDestroyWindow(xdisplay(), xid);
    386   XDestroyWindow(xdisplay(), menu_xid);
    387 }
    388 
    389 }  // namespace views
    390