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