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  virtual void SizeConstraintsChanged() OVERRIDE {
120  }
121
122 private:
123  DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView);
124};
125
126class 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.
145scoped_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.
159std::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).
181bool 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
194class 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.
219TEST_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).
327TEST_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.
384TEST_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