desktop_window_tree_host_x11_unittest.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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
30namespace views {
31
32namespace {
33
34// Blocks till the window state hint, |hint|, is set or unset.
35class 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.
81class 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
120 private:
121  DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView);
122};
123
124class ShapedWidgetDelegate : public WidgetDelegateView {
125 public:
126  ShapedWidgetDelegate() {
127  }
128
129  virtual ~ShapedWidgetDelegate() {
130  }
131
132  // WidgetDelegateView:
133  virtual NonClientFrameView* CreateNonClientFrameView(
134      Widget* widget) OVERRIDE {
135    return new ShapedNonClientFrameView;
136  }
137
138 private:
139  DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate);
140};
141
142// Creates a widget of size 100x100.
143scoped_ptr<Widget> CreateWidget(WidgetDelegate* delegate) {
144  scoped_ptr<Widget> widget(new Widget);
145  Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
146  params.delegate = delegate;
147  params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
148  params.remove_standard_frame = true;
149  params.native_widget = new DesktopNativeWidgetAura(widget.get());
150  params.bounds = gfx::Rect(100, 100, 100, 100);
151  widget->Init(params);
152  return widget.Pass();
153}
154
155// Returns the list of rectangles which describe |xid|'s bounding region via the
156// X shape extension.
157std::vector<gfx::Rect> GetShapeRects(XID xid) {
158  int dummy;
159  int shape_rects_size;
160  XRectangle* shape_rects = XShapeGetRectangles(gfx::GetXDisplay(),
161                                                xid,
162                                                ShapeBounding,
163                                                &shape_rects_size,
164                                                &dummy);
165
166  std::vector<gfx::Rect> shape_vector;
167  for (int i = 0; i < shape_rects_size; ++i) {
168    shape_vector.push_back(gfx::Rect(
169        shape_rects[i].x,
170        shape_rects[i].y,
171        shape_rects[i].width,
172        shape_rects[i].height));
173  }
174  XFree(shape_rects);
175  return shape_vector;
176}
177
178// Returns true if one of |rects| contains point (x,y).
179bool ShapeRectContainsPoint(const std::vector<gfx::Rect>& shape_rects,
180                            int x,
181                            int y) {
182  gfx::Point point(x, y);
183  for (size_t i = 0; i < shape_rects.size(); ++i) {
184    if (shape_rects[i].Contains(point))
185      return true;
186  }
187  return false;
188}
189
190}  // namespace
191
192class DesktopWindowTreeHostX11Test : public ViewsTestBase {
193 public:
194  DesktopWindowTreeHostX11Test() {
195  }
196  virtual ~DesktopWindowTreeHostX11Test() {
197  }
198
199  virtual void SetUp() OVERRIDE {
200    ViewsTestBase::SetUp();
201
202    // Make X11 synchronous for our display connection. This does not force the
203    // window manager to behave synchronously.
204    XSynchronize(gfx::GetXDisplay(), True);
205  }
206
207  virtual void TearDown() OVERRIDE {
208    XSynchronize(gfx::GetXDisplay(), False);
209    ViewsTestBase::TearDown();
210  }
211
212 private:
213  DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11Test);
214};
215
216// Tests that the shape is properly set on the x window.
217TEST_F(DesktopWindowTreeHostX11Test, Shape) {
218  if (!ui::IsShapeExtensionAvailable())
219    return;
220
221  // 1) Test setting the window shape via the NonClientFrameView. This technique
222  // is used to get rounded corners on Chrome windows when not using the native
223  // window frame.
224  scoped_ptr<Widget> widget1 = CreateWidget(new ShapedWidgetDelegate());
225  widget1->Show();
226  ui::X11EventSource::GetInstance()->DispatchXEvents();
227
228  XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
229  std::vector<gfx::Rect> shape_rects = GetShapeRects(xid1);
230  ASSERT_FALSE(shape_rects.empty());
231
232  // The widget was supposed to be 100x100, but the WM might have ignored this
233  // suggestion.
234  int widget_width = widget1->GetWindowBoundsInScreen().width();
235  EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 15, 5));
236  EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 5));
237  EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 15));
238  EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width + 5, 15));
239
240  // Changing widget's size should update the shape.
241  widget1->SetBounds(gfx::Rect(100, 100, 200, 200));
242  ui::X11EventSource::GetInstance()->DispatchXEvents();
243
244  if (widget1->GetWindowBoundsInScreen().width() == 200) {
245    shape_rects = GetShapeRects(xid1);
246    ASSERT_FALSE(shape_rects.empty());
247    EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 85, 5));
248    EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 5));
249    EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 185, 5));
250    EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 195, 5));
251    EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 195, 15));
252    EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 205, 15));
253  }
254
255  if (ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"))) {
256    // The shape should be changed to a rectangle which fills the entire screen
257    // when |widget1| is maximized.
258    {
259      WMStateWaiter waiter(xid1, "_NET_WM_STATE_MAXIMIZED_VERT", true);
260      widget1->Maximize();
261      waiter.Wait();
262    }
263
264    // xvfb does not support Xrandr so we cannot check the maximized window's
265    // bounds.
266    gfx::Rect maximized_bounds;
267    ui::GetWindowRect(xid1, &maximized_bounds);
268
269    shape_rects = GetShapeRects(xid1);
270    ASSERT_FALSE(shape_rects.empty());
271    EXPECT_TRUE(ShapeRectContainsPoint(shape_rects,
272                                       maximized_bounds.width() - 1,
273                                       5));
274    EXPECT_TRUE(ShapeRectContainsPoint(shape_rects,
275                                       maximized_bounds.width() - 1,
276                                       15));
277  }
278
279  // 2) Test setting the window shape via Widget::SetShape().
280  gfx::Path shape2;
281  shape2.moveTo(10, 0);
282  shape2.lineTo(10, 10);
283  shape2.lineTo(0, 10);
284  shape2.lineTo(0, 100);
285  shape2.lineTo(100, 100);
286  shape2.lineTo(100, 0);
287  shape2.close();
288
289  scoped_ptr<Widget> widget2(CreateWidget(NULL));
290  widget2->Show();
291  widget2->SetShape(shape2.CreateNativeRegion());
292  ui::X11EventSource::GetInstance()->DispatchXEvents();
293
294  XID xid2 = widget2->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
295  shape_rects = GetShapeRects(xid2);
296  ASSERT_FALSE(shape_rects.empty());
297  EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5));
298  EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
299  EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
300  EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15));
301
302  // Changing the widget's size should not affect the shape.
303  widget2->SetBounds(gfx::Rect(100, 100, 200, 200));
304  shape_rects = GetShapeRects(xid2);
305  ASSERT_FALSE(shape_rects.empty());
306  EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5));
307  EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
308  EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
309  EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15));
310}
311
312// Test that the widget ignores changes in fullscreen state initiated by the
313// window manager (e.g. via a window manager accelerator key).
314TEST_F(DesktopWindowTreeHostX11Test, WindowManagerTogglesFullscreen) {
315  if (!ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_FULLSCREEN")))
316    return;
317
318  scoped_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate());
319  XID xid = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
320  widget->Show();
321  ui::X11EventSource::GetInstance()->DispatchXEvents();
322
323  gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen();
324  {
325    WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", true);
326    widget->SetFullscreen(true);
327    waiter.Wait();
328  }
329  EXPECT_TRUE(widget->IsFullscreen());
330
331  // Emulate the window manager exiting fullscreen via a window manager
332  // accelerator key. It should not affect the widget's fullscreen state.
333  {
334    const char* kAtomsToCache[] = {
335        "_NET_WM_STATE",
336        "_NET_WM_STATE_FULLSCREEN",
337        NULL
338    };
339    Display* display = gfx::GetXDisplay();
340    ui::X11AtomCache atom_cache(display, kAtomsToCache);
341
342    XEvent xclient;
343    memset(&xclient, 0, sizeof(xclient));
344    xclient.type = ClientMessage;
345    xclient.xclient.window = xid;
346    xclient.xclient.message_type = atom_cache.GetAtom("_NET_WM_STATE");
347    xclient.xclient.format = 32;
348    xclient.xclient.data.l[0] = 0;
349    xclient.xclient.data.l[1] = atom_cache.GetAtom("_NET_WM_STATE_FULLSCREEN");
350    xclient.xclient.data.l[2] = 0;
351    xclient.xclient.data.l[3] = 1;
352    xclient.xclient.data.l[4] = 0;
353    XSendEvent(display, DefaultRootWindow(display), False,
354               SubstructureRedirectMask | SubstructureNotifyMask,
355               &xclient);
356
357    WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", false);
358    waiter.Wait();
359  }
360  EXPECT_TRUE(widget->IsFullscreen());
361
362  // Calling Widget::SetFullscreen(false) should clear the widget's fullscreen
363  // state and clean things up.
364  widget->SetFullscreen(false);
365  EXPECT_FALSE(widget->IsFullscreen());
366  EXPECT_EQ(initial_bounds.ToString(),
367            widget->GetWindowBoundsInScreen().ToString());
368}
369
370// Tests that the minimization information is propagated to the content window.
371TEST_F(DesktopWindowTreeHostX11Test, ToggleMinimizePropogateToContentWindow) {
372  Widget widget;
373  Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
374  params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
375  params.native_widget = new DesktopNativeWidgetAura(&widget);
376  widget.Init(params);
377  widget.Show();
378  ui::X11EventSource::GetInstance()->DispatchXEvents();
379
380  XID xid = widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget();
381  Display* display = gfx::GetXDisplay();
382
383  // Minimize by sending _NET_WM_STATE_HIDDEN
384  {
385    const char* kAtomsToCache[] = {
386        "_NET_WM_STATE",
387        "_NET_WM_STATE_HIDDEN",
388        NULL
389    };
390
391    ui::X11AtomCache atom_cache(display, kAtomsToCache);
392
393    std::vector< ::Atom> atom_list;
394    atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_HIDDEN"));
395    ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list);
396
397    XEvent xevent;
398    memset(&xevent, 0, sizeof(xevent));
399    xevent.type = PropertyNotify;
400    xevent.xproperty.type = PropertyNotify;
401    xevent.xproperty.send_event = 1;
402    xevent.xproperty.display = display;
403    xevent.xproperty.window = xid;
404    xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE");
405    xevent.xproperty.state = 0;
406    XSendEvent(display, DefaultRootWindow(display), False,
407        SubstructureRedirectMask | SubstructureNotifyMask,
408        &xevent);
409
410    WMStateWaiter waiter(xid, "_NET_WM_STATE_HIDDEN", true);
411    waiter.Wait();
412  }
413  EXPECT_FALSE(widget.GetNativeWindow()->IsVisible());
414
415  // Show from minimized by sending _NET_WM_STATE_FOCUSED
416  {
417    const char* kAtomsToCache[] = {
418        "_NET_WM_STATE",
419        "_NET_WM_STATE_FOCUSED",
420        NULL
421    };
422
423    ui::X11AtomCache atom_cache(display, kAtomsToCache);
424
425    std::vector< ::Atom> atom_list;
426    atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_FOCUSED"));
427    ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list);
428
429    XEvent xevent;
430    memset(&xevent, 0, sizeof(xevent));
431    xevent.type = PropertyNotify;
432    xevent.xproperty.type = PropertyNotify;
433    xevent.xproperty.send_event = 1;
434    xevent.xproperty.display = display;
435    xevent.xproperty.window = xid;
436    xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE");
437    xevent.xproperty.state = 0;
438    XSendEvent(display, DefaultRootWindow(display), False,
439        SubstructureRedirectMask | SubstructureNotifyMask,
440        &xevent);
441
442    WMStateWaiter waiter(xid, "_NET_WM_STATE_FOCUSED", true);
443    waiter.Wait();
444  }
445  EXPECT_TRUE(widget.GetNativeWindow()->IsVisible());
446}
447
448}  // namespace views
449