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.
7 #include <X11/extensions/shape.h>
10 // Get rid of X11 macros which conflict with gtest.
14 #include "base/memory/scoped_ptr.h"
15 #include "base/run_loop.h"
16 #include "ui/aura/window.h"
17 #include "ui/aura/window_tree_host.h"
18 #include "ui/base/hit_test.h"
19 #include "ui/base/x/x11_util.h"
20 #include "ui/events/platform/x11/x11_event_source.h"
21 #include "ui/gfx/geometry/point.h"
22 #include "ui/gfx/geometry/rect.h"
23 #include "ui/gfx/path.h"
24 #include "ui/gfx/x/x11_atom_cache.h"
25 #include "ui/views/test/views_test_base.h"
26 #include "ui/views/test/x11_property_change_waiter.h"
27 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
28 #include "ui/views/widget/widget_delegate.h"
29 #include "ui/views/window/non_client_view.h"
35 // Blocks till the window state hint, |hint|, is set or unset.
36 class WMStateWaiter
: public X11PropertyChangeWaiter
{
38 WMStateWaiter(XID window
,
41 : X11PropertyChangeWaiter(window
, "_NET_WM_STATE"),
43 wait_till_set_(wait_till_set
) {
45 const char* kAtomsToCache
[] = {
49 atom_cache_
.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache
));
52 ~WMStateWaiter() override
{}
55 // X11PropertyChangeWaiter:
56 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(
62 atom_cache_
->GetAtom(hint_
));
63 bool hint_set
= (it
!= hints
.end());
64 return hint_set
!= wait_till_set_
;
69 scoped_ptr
<ui::X11AtomCache
> atom_cache_
;
71 // The name of the hint to wait to get set or unset.
74 // Whether we are waiting for |hint| to be set or unset.
77 DISALLOW_COPY_AND_ASSIGN(WMStateWaiter
);
80 // A NonClientFrameView with a window mask with the bottom right corner cut out.
81 class ShapedNonClientFrameView
: public NonClientFrameView
{
83 explicit ShapedNonClientFrameView() {
86 ~ShapedNonClientFrameView() override
{}
88 // NonClientFrameView:
89 gfx::Rect
GetBoundsForClientView() const override
{ return bounds(); }
90 gfx::Rect
GetWindowBoundsForClientBounds(
91 const gfx::Rect
& client_bounds
) const override
{
94 int NonClientHitTest(const gfx::Point
& point
) override
{ return HTNOWHERE
; }
95 void GetWindowMask(const gfx::Size
& size
, gfx::Path
* window_mask
) override
{
96 int right
= size
.width();
97 int bottom
= size
.height();
99 window_mask
->moveTo(0, 0);
100 window_mask
->lineTo(0, bottom
);
101 window_mask
->lineTo(right
, bottom
);
102 window_mask
->lineTo(right
, 10);
103 window_mask
->lineTo(right
- 10, 10);
104 window_mask
->lineTo(right
- 10, 0);
105 window_mask
->close();
107 void ResetWindowControls() override
{}
108 void UpdateWindowIcon() override
{}
109 void UpdateWindowTitle() override
{}
110 void SizeConstraintsChanged() override
{}
113 DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView
);
116 class ShapedWidgetDelegate
: public WidgetDelegateView
{
118 ShapedWidgetDelegate() {
121 ~ShapedWidgetDelegate() override
{}
123 // WidgetDelegateView:
124 NonClientFrameView
* CreateNonClientFrameView(Widget
* widget
) override
{
125 return new ShapedNonClientFrameView
;
129 DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate
);
132 // Creates a widget of size 100x100.
133 scoped_ptr
<Widget
> CreateWidget(WidgetDelegate
* delegate
) {
134 scoped_ptr
<Widget
> widget(new Widget
);
135 Widget::InitParams
params(Widget::InitParams::TYPE_WINDOW
);
136 params
.delegate
= delegate
;
137 params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
138 params
.remove_standard_frame
= true;
139 params
.native_widget
= new DesktopNativeWidgetAura(widget
.get());
140 params
.bounds
= gfx::Rect(100, 100, 100, 100);
141 widget
->Init(params
);
142 return widget
.Pass();
145 // Returns the list of rectangles which describe |xid|'s bounding region via the
146 // X shape extension.
147 std::vector
<gfx::Rect
> GetShapeRects(XID xid
) {
149 int shape_rects_size
;
150 XRectangle
* shape_rects
= XShapeGetRectangles(gfx::GetXDisplay(),
156 std::vector
<gfx::Rect
> shape_vector
;
157 for (int i
= 0; i
< shape_rects_size
; ++i
) {
158 shape_vector
.push_back(gfx::Rect(
161 shape_rects
[i
].width
,
162 shape_rects
[i
].height
));
168 // Returns true if one of |rects| contains point (x,y).
169 bool ShapeRectContainsPoint(const std::vector
<gfx::Rect
>& shape_rects
,
172 gfx::Point
point(x
, y
);
173 for (size_t i
= 0; i
< shape_rects
.size(); ++i
) {
174 if (shape_rects
[i
].Contains(point
))
180 // Flush the message loop.
181 void RunAllPendingInMessageLoop() {
182 base::RunLoop run_loop
;
183 run_loop
.RunUntilIdle();
188 class DesktopWindowTreeHostX11Test
: public ViewsTestBase
{
190 DesktopWindowTreeHostX11Test() {
192 ~DesktopWindowTreeHostX11Test() override
{}
194 void SetUp() override
{
195 ViewsTestBase::SetUp();
197 // Make X11 synchronous for our display connection. This does not force the
198 // window manager to behave synchronously.
199 XSynchronize(gfx::GetXDisplay(), True
);
202 void TearDown() override
{
203 XSynchronize(gfx::GetXDisplay(), False
);
204 ViewsTestBase::TearDown();
208 DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11Test
);
211 // Tests that the shape is properly set on the x window.
212 TEST_F(DesktopWindowTreeHostX11Test
, Shape
) {
213 if (!ui::IsShapeExtensionAvailable())
216 // 1) Test setting the window shape via the NonClientFrameView. This technique
217 // is used to get rounded corners on Chrome windows when not using the native
219 scoped_ptr
<Widget
> widget1
= CreateWidget(new ShapedWidgetDelegate());
221 ui::X11EventSource::GetInstance()->DispatchXEvents();
223 XID xid1
= widget1
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
224 std::vector
<gfx::Rect
> shape_rects
= GetShapeRects(xid1
);
225 ASSERT_FALSE(shape_rects
.empty());
227 // The widget was supposed to be 100x100, but the WM might have ignored this
229 int widget_width
= widget1
->GetWindowBoundsInScreen().width();
230 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, widget_width
- 15, 5));
231 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, widget_width
- 5, 5));
232 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, widget_width
- 5, 15));
233 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, widget_width
+ 5, 15));
235 // Changing widget's size should update the shape.
236 widget1
->SetBounds(gfx::Rect(100, 100, 200, 200));
237 ui::X11EventSource::GetInstance()->DispatchXEvents();
239 if (widget1
->GetWindowBoundsInScreen().width() == 200) {
240 shape_rects
= GetShapeRects(xid1
);
241 ASSERT_FALSE(shape_rects
.empty());
242 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 85, 5));
243 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 95, 5));
244 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 185, 5));
245 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, 195, 5));
246 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 195, 15));
247 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, 205, 15));
250 if (ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"))) {
251 // The shape should be changed to a rectangle which fills the entire screen
252 // when |widget1| is maximized.
254 WMStateWaiter
waiter(xid1
, "_NET_WM_STATE_MAXIMIZED_VERT", true);
259 // Ensure that the task which is posted when a window is resized is run.
260 RunAllPendingInMessageLoop();
262 // xvfb does not support Xrandr so we cannot check the maximized window's
264 gfx::Rect maximized_bounds
;
265 ui::GetOuterWindowBounds(xid1
, &maximized_bounds
);
267 shape_rects
= GetShapeRects(xid1
);
268 ASSERT_FALSE(shape_rects
.empty());
269 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
,
270 maximized_bounds
.width() - 1,
272 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
,
273 maximized_bounds
.width() - 1,
277 // 2) Test setting the window shape via Widget::SetShape().
279 shape2
.moveTo(10, 0);
280 shape2
.lineTo(10, 10);
281 shape2
.lineTo(0, 10);
282 shape2
.lineTo(0, 100);
283 shape2
.lineTo(100, 100);
284 shape2
.lineTo(100, 0);
287 scoped_ptr
<Widget
> widget2(CreateWidget(NULL
));
289 widget2
->SetShape(shape2
.CreateNativeRegion());
290 ui::X11EventSource::GetInstance()->DispatchXEvents();
292 XID xid2
= widget2
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
293 shape_rects
= GetShapeRects(xid2
);
294 ASSERT_FALSE(shape_rects
.empty());
295 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, 5, 5));
296 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 15, 5));
297 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 95, 15));
298 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, 105, 15));
300 // Changing the widget's size should not affect the shape.
301 widget2
->SetBounds(gfx::Rect(100, 100, 200, 200));
302 shape_rects
= GetShapeRects(xid2
);
303 ASSERT_FALSE(shape_rects
.empty());
304 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, 5, 5));
305 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 15, 5));
306 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 95, 15));
307 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, 105, 15));
309 // Setting the shape to NULL resets the shape back to the entire
311 widget2
->SetShape(NULL
);
312 shape_rects
= GetShapeRects(xid2
);
313 ASSERT_FALSE(shape_rects
.empty());
314 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 5, 5));
315 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 15, 5));
316 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 95, 15));
317 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 105, 15));
318 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, 500, 500));
321 // Test that the widget ignores changes in fullscreen state initiated by the
322 // window manager (e.g. via a window manager accelerator key).
323 TEST_F(DesktopWindowTreeHostX11Test
, WindowManagerTogglesFullscreen
) {
324 if (!ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_FULLSCREEN")))
327 scoped_ptr
<Widget
> widget
= CreateWidget(new ShapedWidgetDelegate());
328 XID xid
= widget
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
330 ui::X11EventSource::GetInstance()->DispatchXEvents();
332 gfx::Rect initial_bounds
= widget
->GetWindowBoundsInScreen();
334 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_FULLSCREEN", true);
335 widget
->SetFullscreen(true);
338 EXPECT_TRUE(widget
->IsFullscreen());
340 // Emulate the window manager exiting fullscreen via a window manager
341 // accelerator key. It should not affect the widget's fullscreen state.
343 const char* kAtomsToCache
[] = {
345 "_NET_WM_STATE_FULLSCREEN",
348 Display
* display
= gfx::GetXDisplay();
349 ui::X11AtomCache
atom_cache(display
, kAtomsToCache
);
352 memset(&xclient
, 0, sizeof(xclient
));
353 xclient
.type
= ClientMessage
;
354 xclient
.xclient
.window
= xid
;
355 xclient
.xclient
.message_type
= atom_cache
.GetAtom("_NET_WM_STATE");
356 xclient
.xclient
.format
= 32;
357 xclient
.xclient
.data
.l
[0] = 0;
358 xclient
.xclient
.data
.l
[1] = atom_cache
.GetAtom("_NET_WM_STATE_FULLSCREEN");
359 xclient
.xclient
.data
.l
[2] = 0;
360 xclient
.xclient
.data
.l
[3] = 1;
361 xclient
.xclient
.data
.l
[4] = 0;
362 XSendEvent(display
, DefaultRootWindow(display
), False
,
363 SubstructureRedirectMask
| SubstructureNotifyMask
,
366 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_FULLSCREEN", false);
369 EXPECT_TRUE(widget
->IsFullscreen());
371 // Calling Widget::SetFullscreen(false) should clear the widget's fullscreen
372 // state and clean things up.
373 widget
->SetFullscreen(false);
374 EXPECT_FALSE(widget
->IsFullscreen());
375 EXPECT_EQ(initial_bounds
.ToString(),
376 widget
->GetWindowBoundsInScreen().ToString());
379 // Tests that the minimization information is propagated to the content window.
380 TEST_F(DesktopWindowTreeHostX11Test
, ToggleMinimizePropogateToContentWindow
) {
382 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_WINDOW
);
383 params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
384 params
.native_widget
= new DesktopNativeWidgetAura(&widget
);
387 ui::X11EventSource::GetInstance()->DispatchXEvents();
389 XID xid
= widget
.GetNativeWindow()->GetHost()->GetAcceleratedWidget();
390 Display
* display
= gfx::GetXDisplay();
392 // Minimize by sending _NET_WM_STATE_HIDDEN
394 const char* kAtomsToCache
[] = {
396 "_NET_WM_STATE_HIDDEN",
400 ui::X11AtomCache
atom_cache(display
, kAtomsToCache
);
402 std::vector
< ::Atom
> atom_list
;
403 atom_list
.push_back(atom_cache
.GetAtom("_NET_WM_STATE_HIDDEN"));
404 ui::SetAtomArrayProperty(xid
, "_NET_WM_STATE", "ATOM", atom_list
);
407 memset(&xevent
, 0, sizeof(xevent
));
408 xevent
.type
= PropertyNotify
;
409 xevent
.xproperty
.type
= PropertyNotify
;
410 xevent
.xproperty
.send_event
= 1;
411 xevent
.xproperty
.display
= display
;
412 xevent
.xproperty
.window
= xid
;
413 xevent
.xproperty
.atom
= atom_cache
.GetAtom("_NET_WM_STATE");
414 xevent
.xproperty
.state
= 0;
415 XSendEvent(display
, DefaultRootWindow(display
), False
,
416 SubstructureRedirectMask
| SubstructureNotifyMask
,
419 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_HIDDEN", true);
422 EXPECT_FALSE(widget
.GetNativeWindow()->IsVisible());
424 // Show from minimized by sending _NET_WM_STATE_FOCUSED
426 const char* kAtomsToCache
[] = {
428 "_NET_WM_STATE_FOCUSED",
432 ui::X11AtomCache
atom_cache(display
, kAtomsToCache
);
434 std::vector
< ::Atom
> atom_list
;
435 atom_list
.push_back(atom_cache
.GetAtom("_NET_WM_STATE_FOCUSED"));
436 ui::SetAtomArrayProperty(xid
, "_NET_WM_STATE", "ATOM", atom_list
);
439 memset(&xevent
, 0, sizeof(xevent
));
440 xevent
.type
= PropertyNotify
;
441 xevent
.xproperty
.type
= PropertyNotify
;
442 xevent
.xproperty
.send_event
= 1;
443 xevent
.xproperty
.display
= display
;
444 xevent
.xproperty
.window
= xid
;
445 xevent
.xproperty
.atom
= atom_cache
.GetAtom("_NET_WM_STATE");
446 xevent
.xproperty
.state
= 0;
447 XSendEvent(display
, DefaultRootWindow(display
), False
,
448 SubstructureRedirectMask
| SubstructureNotifyMask
,
451 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_FOCUSED", true);
454 EXPECT_TRUE(widget
.GetNativeWindow()->IsVisible());