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 <vector>
      6 
      7 #include <X11/extensions/shape.h>
      8 #include <X11/Xlib.h>
      9 
     10 // Get rid of X11 macros which conflict with gtest.
     11 #undef Bool
     12 #undef None
     13 
     14 #include "base/memory/scoped_ptr.h"
     15 #include "ui/aura/window.h"
     16 #include "ui/aura/window_tree_host.h"
     17 #include "ui/base/hit_test.h"
     18 #include "ui/base/x/x11_util.h"
     19 #include "ui/events/platform/x11/x11_event_source.h"
     20 #include "ui/gfx/path.h"
     21 #include "ui/gfx/point.h"
     22 #include "ui/gfx/rect.h"
     23 #include "ui/gfx/x/x11_atom_cache.h"
     24 #include "ui/views/test/views_test_base.h"
     25 #include "ui/views/test/x11_property_change_waiter.h"
     26 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
     27 #include "ui/views/widget/widget_delegate.h"
     28 #include "ui/views/window/non_client_view.h"
     29 
     30 namespace views {
     31 
     32 namespace {
     33 
     34 // Blocks till the window state hint, |hint|, is set or unset.
     35 class WMStateWaiter : public X11PropertyChangeWaiter {
     36  public:
     37   WMStateWaiter(XID window,
     38                 const char* hint,
     39                 bool wait_till_set)
     40       : X11PropertyChangeWaiter(window, "_NET_WM_STATE"),
     41         hint_(hint),
     42         wait_till_set_(wait_till_set) {
     43 
     44     const char* kAtomsToCache[] = {
     45         hint,
     46         NULL
     47     };
     48     atom_cache_.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache));
     49   }
     50 
     51   virtual ~WMStateWaiter() {
     52   }
     53 
     54  private:
     55   // X11PropertyChangeWaiter:
     56   virtual bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) OVERRIDE {
     57     std::vector<Atom> hints;
     58     if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &hints)) {
     59       std::vector<Atom>::iterator it = std::find(
     60           hints.begin(),
     61           hints.end(),
     62           atom_cache_->GetAtom(hint_));
     63       bool hint_set = (it != hints.end());
     64       return hint_set != wait_till_set_;
     65     }
     66     return true;
     67   }
     68 
     69   scoped_ptr<ui::X11AtomCache> atom_cache_;
     70 
     71   // The name of the hint to wait to get set or unset.
     72   const char* hint_;
     73 
     74   // Whether we are waiting for |hint| to be set or unset.
     75   bool wait_till_set_;
     76 
     77   DISALLOW_COPY_AND_ASSIGN(WMStateWaiter);
     78 };
     79 
     80 // A NonClientFrameView with a window mask with the bottom right corner cut out.
     81 class ShapedNonClientFrameView : public NonClientFrameView {
     82  public:
     83   explicit ShapedNonClientFrameView() {
     84   }
     85 
     86   virtual ~ShapedNonClientFrameView() {
     87   }
     88 
     89   // NonClientFrameView:
     90   virtual gfx::Rect GetBoundsForClientView() const OVERRIDE {
     91     return bounds();
     92   }
     93   virtual gfx::Rect GetWindowBoundsForClientBounds(
     94       const gfx::Rect& client_bounds) const OVERRIDE {
     95     return client_bounds;
     96   }
     97   virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE {
     98     return HTNOWHERE;
     99   }
    100   virtual void GetWindowMask(const gfx::Size& size,
    101                              gfx::Path* window_mask) OVERRIDE {
    102     int right = size.width();
    103     int bottom = size.height();
    104 
    105     window_mask->moveTo(0, 0);
    106     window_mask->lineTo(0, bottom);
    107     window_mask->lineTo(right, bottom);
    108     window_mask->lineTo(right, 10);
    109     window_mask->lineTo(right - 10, 10);
    110     window_mask->lineTo(right - 10, 0);
    111     window_mask->close();
    112   }
    113   virtual void ResetWindowControls() OVERRIDE {
    114   }
    115   virtual void UpdateWindowIcon() OVERRIDE {
    116   }
    117   virtual void UpdateWindowTitle() OVERRIDE {
    118   }
    119   virtual void SizeConstraintsChanged() OVERRIDE {
    120   }
    121 
    122  private:
    123   DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView);
    124 };
    125 
    126 class ShapedWidgetDelegate : public WidgetDelegateView {
    127  public:
    128   ShapedWidgetDelegate() {
    129   }
    130 
    131   virtual ~ShapedWidgetDelegate() {
    132   }
    133 
    134   // WidgetDelegateView:
    135   virtual NonClientFrameView* CreateNonClientFrameView(
    136       Widget* widget) OVERRIDE {
    137     return new ShapedNonClientFrameView;
    138   }
    139 
    140  private:
    141   DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate);
    142 };
    143 
    144 // Creates a widget of size 100x100.
    145 scoped_ptr<Widget> CreateWidget(WidgetDelegate* delegate) {
    146   scoped_ptr<Widget> widget(new Widget);
    147   Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
    148   params.delegate = delegate;
    149   params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    150   params.remove_standard_frame = true;
    151   params.native_widget = new DesktopNativeWidgetAura(widget.get());
    152   params.bounds = gfx::Rect(100, 100, 100, 100);
    153   widget->Init(params);
    154   return widget.Pass();
    155 }
    156 
    157 // Returns the list of rectangles which describe |xid|'s bounding region via the
    158 // X shape extension.
    159 std::vector<gfx::Rect> GetShapeRects(XID xid) {
    160   int dummy;
    161   int shape_rects_size;
    162   XRectangle* shape_rects = XShapeGetRectangles(gfx::GetXDisplay(),
    163                                                 xid,
    164                                                 ShapeBounding,
    165                                                 &shape_rects_size,
    166                                                 &dummy);
    167 
    168   std::vector<gfx::Rect> shape_vector;
    169   for (int i = 0; i < shape_rects_size; ++i) {
    170     shape_vector.push_back(gfx::Rect(
    171         shape_rects[i].x,
    172         shape_rects[i].y,
    173         shape_rects[i].width,
    174         shape_rects[i].height));
    175   }
    176   XFree(shape_rects);
    177   return shape_vector;
    178 }
    179 
    180 // Returns true if one of |rects| contains point (x,y).
    181 bool ShapeRectContainsPoint(const std::vector<gfx::Rect>& shape_rects,
    182                             int x,
    183                             int y) {
    184   gfx::Point point(x, y);
    185   for (size_t i = 0; i < shape_rects.size(); ++i) {
    186     if (shape_rects[i].Contains(point))
    187       return true;
    188   }
    189   return false;
    190 }
    191 
    192 }  // namespace
    193 
    194 class DesktopWindowTreeHostX11Test : public ViewsTestBase {
    195  public:
    196   DesktopWindowTreeHostX11Test() {
    197   }
    198   virtual ~DesktopWindowTreeHostX11Test() {
    199   }
    200 
    201   virtual void SetUp() OVERRIDE {
    202     ViewsTestBase::SetUp();
    203 
    204     // Make X11 synchronous for our display connection. This does not force the
    205     // window manager to behave synchronously.
    206     XSynchronize(gfx::GetXDisplay(), True);
    207   }
    208 
    209   virtual void TearDown() OVERRIDE {
    210     XSynchronize(gfx::GetXDisplay(), False);
    211     ViewsTestBase::TearDown();
    212   }
    213 
    214  private:
    215   DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11Test);
    216 };
    217 
    218 // Tests that the shape is properly set on the x window.
    219 TEST_F(DesktopWindowTreeHostX11Test, Shape) {
    220   if (!ui::IsShapeExtensionAvailable())
    221     return;
    222 
    223   // 1) Test setting the window shape via the NonClientFrameView. This technique
    224   // is used to get rounded corners on Chrome windows when not using the native
    225   // window frame.
    226   scoped_ptr<Widget> widget1 = CreateWidget(new ShapedWidgetDelegate());
    227   widget1->Show();
    228   ui::X11EventSource::GetInstance()->DispatchXEvents();
    229 
    230   XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
    231   std::vector<gfx::Rect> shape_rects = GetShapeRects(xid1);
    232   ASSERT_FALSE(shape_rects.empty());
    233 
    234   // The widget was supposed to be 100x100, but the WM might have ignored this
    235   // suggestion.
    236   int widget_width = widget1->GetWindowBoundsInScreen().width();
    237   EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 15, 5));
    238   EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 5));
    239   EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 15));
    240   EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width + 5, 15));
    241 
    242   // Changing widget's size should update the shape.
    243   widget1->SetBounds(gfx::Rect(100, 100, 200, 200));
    244   ui::X11EventSource::GetInstance()->DispatchXEvents();
    245 
    246   if (widget1->GetWindowBoundsInScreen().width() == 200) {
    247     shape_rects = GetShapeRects(xid1);
    248     ASSERT_FALSE(shape_rects.empty());
    249     EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 85, 5));
    250     EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 5));
    251     EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 185, 5));
    252     EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 195, 5));
    253     EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 195, 15));
    254     EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 205, 15));
    255   }
    256 
    257   if (ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"))) {
    258     // The shape should be changed to a rectangle which fills the entire screen
    259     // when |widget1| is maximized.
    260     {
    261       WMStateWaiter waiter(xid1, "_NET_WM_STATE_MAXIMIZED_VERT", true);
    262       widget1->Maximize();
    263       waiter.Wait();
    264     }
    265 
    266     // xvfb does not support Xrandr so we cannot check the maximized window's
    267     // bounds.
    268     gfx::Rect maximized_bounds;
    269     ui::GetWindowRect(xid1, &maximized_bounds);
    270 
    271     shape_rects = GetShapeRects(xid1);
    272     ASSERT_FALSE(shape_rects.empty());
    273     EXPECT_TRUE(ShapeRectContainsPoint(shape_rects,
    274                                        maximized_bounds.width() - 1,
    275                                        5));
    276     EXPECT_TRUE(ShapeRectContainsPoint(shape_rects,
    277                                        maximized_bounds.width() - 1,
    278                                        15));
    279   }
    280 
    281   // 2) Test setting the window shape via Widget::SetShape().
    282   gfx::Path shape2;
    283   shape2.moveTo(10, 0);
    284   shape2.lineTo(10, 10);
    285   shape2.lineTo(0, 10);
    286   shape2.lineTo(0, 100);
    287   shape2.lineTo(100, 100);
    288   shape2.lineTo(100, 0);
    289   shape2.close();
    290 
    291   scoped_ptr<Widget> widget2(CreateWidget(NULL));
    292   widget2->Show();
    293   widget2->SetShape(shape2.CreateNativeRegion());
    294   ui::X11EventSource::GetInstance()->DispatchXEvents();
    295 
    296   XID xid2 = widget2->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
    297   shape_rects = GetShapeRects(xid2);
    298   ASSERT_FALSE(shape_rects.empty());
    299   EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5));
    300   EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
    301   EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
    302   EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15));
    303 
    304   // Changing the widget's size should not affect the shape.
    305   widget2->SetBounds(gfx::Rect(100, 100, 200, 200));
    306   shape_rects = GetShapeRects(xid2);
    307   ASSERT_FALSE(shape_rects.empty());
    308   EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5));
    309   EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
    310   EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
    311   EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15));
    312 
    313   // Setting the shape to NULL resets the shape back to the entire
    314   // window bounds.
    315   widget2->SetShape(NULL);
    316   shape_rects = GetShapeRects(xid2);
    317   ASSERT_FALSE(shape_rects.empty());
    318   EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 5, 5));
    319   EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
    320   EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
    321   EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 105, 15));
    322   EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 500, 500));
    323 }
    324 
    325 // Test that the widget ignores changes in fullscreen state initiated by the
    326 // window manager (e.g. via a window manager accelerator key).
    327 TEST_F(DesktopWindowTreeHostX11Test, WindowManagerTogglesFullscreen) {
    328   if (!ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_FULLSCREEN")))
    329     return;
    330 
    331   scoped_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate());
    332   XID xid = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
    333   widget->Show();
    334   ui::X11EventSource::GetInstance()->DispatchXEvents();
    335 
    336   gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen();
    337   {
    338     WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", true);
    339     widget->SetFullscreen(true);
    340     waiter.Wait();
    341   }
    342   EXPECT_TRUE(widget->IsFullscreen());
    343 
    344   // Emulate the window manager exiting fullscreen via a window manager
    345   // accelerator key. It should not affect the widget's fullscreen state.
    346   {
    347     const char* kAtomsToCache[] = {
    348         "_NET_WM_STATE",
    349         "_NET_WM_STATE_FULLSCREEN",
    350         NULL
    351     };
    352     Display* display = gfx::GetXDisplay();
    353     ui::X11AtomCache atom_cache(display, kAtomsToCache);
    354 
    355     XEvent xclient;
    356     memset(&xclient, 0, sizeof(xclient));
    357     xclient.type = ClientMessage;
    358     xclient.xclient.window = xid;
    359     xclient.xclient.message_type = atom_cache.GetAtom("_NET_WM_STATE");
    360     xclient.xclient.format = 32;
    361     xclient.xclient.data.l[0] = 0;
    362     xclient.xclient.data.l[1] = atom_cache.GetAtom("_NET_WM_STATE_FULLSCREEN");
    363     xclient.xclient.data.l[2] = 0;
    364     xclient.xclient.data.l[3] = 1;
    365     xclient.xclient.data.l[4] = 0;
    366     XSendEvent(display, DefaultRootWindow(display), False,
    367                SubstructureRedirectMask | SubstructureNotifyMask,
    368                &xclient);
    369 
    370     WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", false);
    371     waiter.Wait();
    372   }
    373   EXPECT_TRUE(widget->IsFullscreen());
    374 
    375   // Calling Widget::SetFullscreen(false) should clear the widget's fullscreen
    376   // state and clean things up.
    377   widget->SetFullscreen(false);
    378   EXPECT_FALSE(widget->IsFullscreen());
    379   EXPECT_EQ(initial_bounds.ToString(),
    380             widget->GetWindowBoundsInScreen().ToString());
    381 }
    382 
    383 // Tests that the minimization information is propagated to the content window.
    384 TEST_F(DesktopWindowTreeHostX11Test, ToggleMinimizePropogateToContentWindow) {
    385   Widget widget;
    386   Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
    387   params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    388   params.native_widget = new DesktopNativeWidgetAura(&widget);
    389   widget.Init(params);
    390   widget.Show();
    391   ui::X11EventSource::GetInstance()->DispatchXEvents();
    392 
    393   XID xid = widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget();
    394   Display* display = gfx::GetXDisplay();
    395 
    396   // Minimize by sending _NET_WM_STATE_HIDDEN
    397   {
    398     const char* kAtomsToCache[] = {
    399         "_NET_WM_STATE",
    400         "_NET_WM_STATE_HIDDEN",
    401         NULL
    402     };
    403 
    404     ui::X11AtomCache atom_cache(display, kAtomsToCache);
    405 
    406     std::vector< ::Atom> atom_list;
    407     atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_HIDDEN"));
    408     ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list);
    409 
    410     XEvent xevent;
    411     memset(&xevent, 0, sizeof(xevent));
    412     xevent.type = PropertyNotify;
    413     xevent.xproperty.type = PropertyNotify;
    414     xevent.xproperty.send_event = 1;
    415     xevent.xproperty.display = display;
    416     xevent.xproperty.window = xid;
    417     xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE");
    418     xevent.xproperty.state = 0;
    419     XSendEvent(display, DefaultRootWindow(display), False,
    420         SubstructureRedirectMask | SubstructureNotifyMask,
    421         &xevent);
    422 
    423     WMStateWaiter waiter(xid, "_NET_WM_STATE_HIDDEN", true);
    424     waiter.Wait();
    425   }
    426   EXPECT_FALSE(widget.GetNativeWindow()->IsVisible());
    427 
    428   // Show from minimized by sending _NET_WM_STATE_FOCUSED
    429   {
    430     const char* kAtomsToCache[] = {
    431         "_NET_WM_STATE",
    432         "_NET_WM_STATE_FOCUSED",
    433         NULL
    434     };
    435 
    436     ui::X11AtomCache atom_cache(display, kAtomsToCache);
    437 
    438     std::vector< ::Atom> atom_list;
    439     atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_FOCUSED"));
    440     ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list);
    441 
    442     XEvent xevent;
    443     memset(&xevent, 0, sizeof(xevent));
    444     xevent.type = PropertyNotify;
    445     xevent.xproperty.type = PropertyNotify;
    446     xevent.xproperty.send_event = 1;
    447     xevent.xproperty.display = display;
    448     xevent.xproperty.window = xid;
    449     xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE");
    450     xevent.xproperty.state = 0;
    451     XSendEvent(display, DefaultRootWindow(display), False,
    452         SubstructureRedirectMask | SubstructureNotifyMask,
    453         &xevent);
    454 
    455     WMStateWaiter waiter(xid, "_NET_WM_STATE_FOCUSED", true);
    456     waiter.Wait();
    457   }
    458   EXPECT_TRUE(widget.GetNativeWindow()->IsVisible());
    459 }
    460 
    461 }  // namespace views
    462