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.
11 // It is necessary to include this header before the rest so that Bool can be
13 #include "ui/events/test/events_test_utils_x11.h"
17 #include "base/command_line.h"
18 #include "base/memory/scoped_ptr.h"
19 #include "base/run_loop.h"
20 #include "ui/aura/window.h"
21 #include "ui/aura/window_tree_host.h"
22 #include "ui/base/hit_test.h"
23 #include "ui/base/x/x11_util.h"
24 #include "ui/events/devices/x11/touch_factory_x11.h"
25 #include "ui/events/platform/x11/x11_event_source.h"
26 #include "ui/gfx/geometry/point.h"
27 #include "ui/gfx/geometry/rect.h"
28 #include "ui/gfx/path.h"
29 #include "ui/gfx/switches.h"
30 #include "ui/gfx/x/x11_atom_cache.h"
31 #include "ui/views/test/views_test_base.h"
32 #include "ui/views/test/x11_property_change_waiter.h"
33 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
34 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
35 #include "ui/views/widget/widget_delegate.h"
36 #include "ui/views/window/non_client_view.h"
42 const int kPointerDeviceId
= 1;
44 // Blocks till the window state hint, |hint|, is set or unset.
45 class WMStateWaiter
: public X11PropertyChangeWaiter
{
47 WMStateWaiter(XID window
,
50 : X11PropertyChangeWaiter(window
, "_NET_WM_STATE"),
52 wait_till_set_(wait_till_set
) {
54 const char* kAtomsToCache
[] = {
58 atom_cache_
.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache
));
61 ~WMStateWaiter() override
{}
64 // X11PropertyChangeWaiter:
65 bool ShouldKeepOnWaiting(const ui::PlatformEvent
& event
) override
{
66 std::vector
<Atom
> hints
;
67 if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &hints
)) {
68 std::vector
<Atom
>::iterator it
= std::find(
71 atom_cache_
->GetAtom(hint_
));
72 bool hint_set
= (it
!= hints
.end());
73 return hint_set
!= wait_till_set_
;
78 scoped_ptr
<ui::X11AtomCache
> atom_cache_
;
80 // The name of the hint to wait to get set or unset.
83 // Whether we are waiting for |hint| to be set or unset.
86 DISALLOW_COPY_AND_ASSIGN(WMStateWaiter
);
89 // A NonClientFrameView with a window mask with the bottom right corner cut out.
90 class ShapedNonClientFrameView
: public NonClientFrameView
{
92 explicit ShapedNonClientFrameView() {
95 ~ShapedNonClientFrameView() override
{}
97 // NonClientFrameView:
98 gfx::Rect
GetBoundsForClientView() const override
{ return bounds(); }
99 gfx::Rect
GetWindowBoundsForClientBounds(
100 const gfx::Rect
& client_bounds
) const override
{
101 return client_bounds
;
103 int NonClientHitTest(const gfx::Point
& point
) override
{ return HTNOWHERE
; }
104 void GetWindowMask(const gfx::Size
& size
, gfx::Path
* window_mask
) override
{
105 int right
= size
.width();
106 int bottom
= size
.height();
108 window_mask
->moveTo(0, 0);
109 window_mask
->lineTo(0, bottom
);
110 window_mask
->lineTo(right
, bottom
);
111 window_mask
->lineTo(right
, 10);
112 window_mask
->lineTo(right
- 10, 10);
113 window_mask
->lineTo(right
- 10, 0);
114 window_mask
->close();
116 void ResetWindowControls() override
{}
117 void UpdateWindowIcon() override
{}
118 void UpdateWindowTitle() override
{}
119 void SizeConstraintsChanged() override
{}
122 DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView
);
125 class ShapedWidgetDelegate
: public WidgetDelegateView
{
127 ShapedWidgetDelegate() {
130 ~ShapedWidgetDelegate() override
{}
132 // WidgetDelegateView:
133 NonClientFrameView
* CreateNonClientFrameView(Widget
* widget
) override
{
134 return new ShapedNonClientFrameView
;
138 DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate
);
141 // Creates a widget of size 100x100.
142 scoped_ptr
<Widget
> CreateWidget(WidgetDelegate
* delegate
) {
143 scoped_ptr
<Widget
> widget(new Widget
);
144 Widget::InitParams
params(Widget::InitParams::TYPE_WINDOW
);
145 params
.delegate
= delegate
;
146 params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
147 params
.remove_standard_frame
= true;
148 params
.native_widget
= new DesktopNativeWidgetAura(widget
.get());
149 params
.bounds
= gfx::Rect(100, 100, 100, 100);
150 widget
->Init(params
);
151 return widget
.Pass();
154 // Returns the list of rectangles which describe |xid|'s bounding region via the
155 // X shape extension.
156 std::vector
<gfx::Rect
> GetShapeRects(XID xid
) {
158 int shape_rects_size
;
159 gfx::XScopedPtr
<XRectangle
[]> shape_rects(XShapeGetRectangles(
160 gfx::GetXDisplay(), xid
, ShapeBounding
, &shape_rects_size
, &dummy
));
162 std::vector
<gfx::Rect
> shape_vector
;
163 for (int i
= 0; i
< shape_rects_size
; ++i
) {
164 const XRectangle
& rect
= shape_rects
[i
];
165 shape_vector
.push_back(gfx::Rect(rect
.x
, rect
.y
, rect
.width
, rect
.height
));
170 // Returns true if one of |rects| contains point (x,y).
171 bool ShapeRectContainsPoint(const std::vector
<gfx::Rect
>& shape_rects
,
174 gfx::Point
point(x
, y
);
175 for (size_t i
= 0; i
< shape_rects
.size(); ++i
) {
176 if (shape_rects
[i
].Contains(point
))
182 // Flush the message loop.
183 void RunAllPendingInMessageLoop() {
184 base::RunLoop run_loop
;
185 run_loop
.RunUntilIdle();
190 class DesktopWindowTreeHostX11Test
: public ViewsTestBase
{
192 DesktopWindowTreeHostX11Test() {
194 ~DesktopWindowTreeHostX11Test() override
{}
196 void SetUp() override
{
197 ViewsTestBase::SetUp();
199 // Make X11 synchronous for our display connection. This does not force the
200 // window manager to behave synchronously.
201 XSynchronize(gfx::GetXDisplay(), True
);
204 void TearDown() override
{
205 XSynchronize(gfx::GetXDisplay(), False
);
206 ViewsTestBase::TearDown();
210 DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11Test
);
213 // Tests that the shape is properly set on the x window.
214 TEST_F(DesktopWindowTreeHostX11Test
, Shape
) {
215 if (!ui::IsShapeExtensionAvailable())
218 // 1) Test setting the window shape via the NonClientFrameView. This technique
219 // is used to get rounded corners on Chrome windows when not using the native
221 scoped_ptr
<Widget
> widget1
= CreateWidget(new ShapedWidgetDelegate());
223 ui::X11EventSource::GetInstance()->DispatchXEvents();
225 XID xid1
= widget1
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
226 std::vector
<gfx::Rect
> shape_rects
= GetShapeRects(xid1
);
227 ASSERT_FALSE(shape_rects
.empty());
229 // The widget was supposed to be 100x100, but the WM might have ignored this
231 int widget_width
= widget1
->GetWindowBoundsInScreen().width();
232 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, widget_width
- 15, 5));
233 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, widget_width
- 5, 5));
234 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, widget_width
- 5, 15));
235 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, widget_width
+ 5, 15));
237 // Changing widget's size should update the shape.
238 widget1
->SetBounds(gfx::Rect(100, 100, 200, 200));
239 ui::X11EventSource::GetInstance()->DispatchXEvents();
241 if (widget1
->GetWindowBoundsInScreen().width() == 200) {
242 shape_rects
= GetShapeRects(xid1
);
243 ASSERT_FALSE(shape_rects
.empty());
244 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 85, 5));
245 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 95, 5));
246 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 185, 5));
247 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, 195, 5));
248 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 195, 15));
249 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, 205, 15));
252 if (ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"))) {
253 // The shape should be changed to a rectangle which fills the entire screen
254 // when |widget1| is maximized.
256 WMStateWaiter
waiter(xid1
, "_NET_WM_STATE_MAXIMIZED_VERT", true);
261 // Ensure that the task which is posted when a window is resized is run.
262 RunAllPendingInMessageLoop();
264 // xvfb does not support Xrandr so we cannot check the maximized window's
266 gfx::Rect maximized_bounds
;
267 ui::GetOuterWindowBounds(xid1
, &maximized_bounds
);
269 shape_rects
= GetShapeRects(xid1
);
270 ASSERT_FALSE(shape_rects
.empty());
271 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
,
272 maximized_bounds
.width() - 1,
274 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
,
275 maximized_bounds
.width() - 1,
279 // 2) Test setting the window shape via Widget::SetShape().
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);
289 scoped_ptr
<Widget
> widget2(CreateWidget(NULL
));
291 widget2
->SetShape(shape2
.CreateNativeRegion());
292 ui::X11EventSource::GetInstance()->DispatchXEvents();
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));
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));
311 // Setting the shape to NULL resets the shape back to the entire
313 widget2
->SetShape(NULL
);
314 shape_rects
= GetShapeRects(xid2
);
315 ASSERT_FALSE(shape_rects
.empty());
316 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 5, 5));
317 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 15, 5));
318 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 95, 15));
319 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
, 105, 15));
320 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects
, 500, 500));
323 // Test that the widget ignores changes in fullscreen state initiated by the
324 // window manager (e.g. via a window manager accelerator key).
325 TEST_F(DesktopWindowTreeHostX11Test
, WindowManagerTogglesFullscreen
) {
326 if (!ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_FULLSCREEN")))
329 scoped_ptr
<Widget
> widget
= CreateWidget(new ShapedWidgetDelegate());
330 XID xid
= widget
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
332 ui::X11EventSource::GetInstance()->DispatchXEvents();
334 gfx::Rect initial_bounds
= widget
->GetWindowBoundsInScreen();
336 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_FULLSCREEN", true);
337 widget
->SetFullscreen(true);
340 EXPECT_TRUE(widget
->IsFullscreen());
342 // Emulate the window manager exiting fullscreen via a window manager
343 // accelerator key. It should not affect the widget's fullscreen state.
345 const char* kAtomsToCache
[] = {
347 "_NET_WM_STATE_FULLSCREEN",
350 Display
* display
= gfx::GetXDisplay();
351 ui::X11AtomCache
atom_cache(display
, kAtomsToCache
);
354 memset(&xclient
, 0, sizeof(xclient
));
355 xclient
.type
= ClientMessage
;
356 xclient
.xclient
.window
= xid
;
357 xclient
.xclient
.message_type
= atom_cache
.GetAtom("_NET_WM_STATE");
358 xclient
.xclient
.format
= 32;
359 xclient
.xclient
.data
.l
[0] = 0;
360 xclient
.xclient
.data
.l
[1] = atom_cache
.GetAtom("_NET_WM_STATE_FULLSCREEN");
361 xclient
.xclient
.data
.l
[2] = 0;
362 xclient
.xclient
.data
.l
[3] = 1;
363 xclient
.xclient
.data
.l
[4] = 0;
364 XSendEvent(display
, DefaultRootWindow(display
), False
,
365 SubstructureRedirectMask
| SubstructureNotifyMask
,
368 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_FULLSCREEN", false);
371 EXPECT_TRUE(widget
->IsFullscreen());
373 // Calling Widget::SetFullscreen(false) should clear the widget's fullscreen
374 // state and clean things up.
375 widget
->SetFullscreen(false);
376 EXPECT_FALSE(widget
->IsFullscreen());
377 EXPECT_EQ(initial_bounds
.ToString(),
378 widget
->GetWindowBoundsInScreen().ToString());
381 // Tests that the minimization information is propagated to the content window.
382 TEST_F(DesktopWindowTreeHostX11Test
, ToggleMinimizePropogateToContentWindow
) {
384 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_WINDOW
);
385 params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
386 params
.native_widget
= new DesktopNativeWidgetAura(&widget
);
389 ui::X11EventSource::GetInstance()->DispatchXEvents();
391 XID xid
= widget
.GetNativeWindow()->GetHost()->GetAcceleratedWidget();
392 Display
* display
= gfx::GetXDisplay();
394 // Minimize by sending _NET_WM_STATE_HIDDEN
396 const char* kAtomsToCache
[] = {
398 "_NET_WM_STATE_HIDDEN",
402 ui::X11AtomCache
atom_cache(display
, kAtomsToCache
);
404 std::vector
< ::Atom
> atom_list
;
405 atom_list
.push_back(atom_cache
.GetAtom("_NET_WM_STATE_HIDDEN"));
406 ui::SetAtomArrayProperty(xid
, "_NET_WM_STATE", "ATOM", atom_list
);
409 memset(&xevent
, 0, sizeof(xevent
));
410 xevent
.type
= PropertyNotify
;
411 xevent
.xproperty
.type
= PropertyNotify
;
412 xevent
.xproperty
.send_event
= 1;
413 xevent
.xproperty
.display
= display
;
414 xevent
.xproperty
.window
= xid
;
415 xevent
.xproperty
.atom
= atom_cache
.GetAtom("_NET_WM_STATE");
416 xevent
.xproperty
.state
= 0;
417 XSendEvent(display
, DefaultRootWindow(display
), False
,
418 SubstructureRedirectMask
| SubstructureNotifyMask
,
421 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_HIDDEN", true);
424 EXPECT_FALSE(widget
.GetNativeWindow()->IsVisible());
426 // Show from minimized by sending _NET_WM_STATE_FOCUSED
428 const char* kAtomsToCache
[] = {
430 "_NET_WM_STATE_FOCUSED",
434 ui::X11AtomCache
atom_cache(display
, kAtomsToCache
);
436 std::vector
< ::Atom
> atom_list
;
437 atom_list
.push_back(atom_cache
.GetAtom("_NET_WM_STATE_FOCUSED"));
438 ui::SetAtomArrayProperty(xid
, "_NET_WM_STATE", "ATOM", atom_list
);
441 memset(&xevent
, 0, sizeof(xevent
));
442 xevent
.type
= PropertyNotify
;
443 xevent
.xproperty
.type
= PropertyNotify
;
444 xevent
.xproperty
.send_event
= 1;
445 xevent
.xproperty
.display
= display
;
446 xevent
.xproperty
.window
= xid
;
447 xevent
.xproperty
.atom
= atom_cache
.GetAtom("_NET_WM_STATE");
448 xevent
.xproperty
.state
= 0;
449 XSendEvent(display
, DefaultRootWindow(display
), False
,
450 SubstructureRedirectMask
| SubstructureNotifyMask
,
453 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_FOCUSED", true);
456 EXPECT_TRUE(widget
.GetNativeWindow()->IsVisible());
459 class MouseEventRecorder
: public ui::EventHandler
{
461 MouseEventRecorder() {}
462 ~MouseEventRecorder() override
{}
464 void Reset() { mouse_events_
.clear(); }
466 const std::vector
<ui::MouseEvent
>& mouse_events() const {
467 return mouse_events_
;
472 void OnMouseEvent(ui::MouseEvent
* mouse
) override
{
473 mouse_events_
.push_back(*mouse
);
476 std::vector
<ui::MouseEvent
> mouse_events_
;
478 DISALLOW_COPY_AND_ASSIGN(MouseEventRecorder
);
481 // A custom event-source that can be used to directly dispatch synthetic X11
483 class CustomX11EventSource
: public ui::X11EventSource
{
485 CustomX11EventSource() : X11EventSource(gfx::GetXDisplay()) {}
486 ~CustomX11EventSource() override
{}
488 void DispatchSingleEvent(XEvent
* xevent
) {
489 PlatformEventSource::DispatchEvent(xevent
);
493 DISALLOW_COPY_AND_ASSIGN(CustomX11EventSource
);
496 class DesktopWindowTreeHostX11HighDPITest
497 : public DesktopWindowTreeHostX11Test
{
499 DesktopWindowTreeHostX11HighDPITest() {}
500 ~DesktopWindowTreeHostX11HighDPITest() override
{}
502 void DispatchSingleEventToWidget(XEvent
* event
, Widget
* widget
) {
503 DCHECK_EQ(GenericEvent
, event
->type
);
504 XIDeviceEvent
* device_event
=
505 static_cast<XIDeviceEvent
*>(event
->xcookie
.data
);
506 device_event
->event
=
507 widget
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
508 event_source_
.DispatchSingleEvent(event
);
511 void PretendCapture(views::Widget
* capture_widget
) {
512 DesktopWindowTreeHostX11
* capture_host
= nullptr;
513 if (capture_widget
) {
514 capture_host
= static_cast<DesktopWindowTreeHostX11
*>(
515 capture_widget
->GetNativeWindow()->GetHost());
517 DesktopWindowTreeHostX11::g_current_capture
= capture_host
;
519 capture_widget
->GetNativeWindow()->SetCapture();
523 void SetUp() override
{
524 base::CommandLine
* command_line
= base::CommandLine::ForCurrentProcess();
525 command_line
->AppendSwitchASCII(switches::kForceDeviceScaleFactor
, "2");
526 std::vector
<int> pointer_devices
;
527 pointer_devices
.push_back(kPointerDeviceId
);
528 ui::TouchFactory::GetInstance()->SetPointerDeviceForTest(pointer_devices
);
530 DesktopWindowTreeHostX11Test::SetUp();
533 CustomX11EventSource event_source_
;
534 DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11HighDPITest
);
537 TEST_F(DesktopWindowTreeHostX11HighDPITest
, LocatedEventDispatchWithCapture
) {
539 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_WINDOW
);
540 params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
541 params
.native_widget
= new DesktopNativeWidgetAura(&first
);
542 params
.bounds
= gfx::Rect(0, 0, 50, 50);
547 params
= CreateParams(Widget::InitParams::TYPE_WINDOW
);
548 params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
549 params
.native_widget
= new DesktopNativeWidgetAura(&second
);
550 params
.bounds
= gfx::Rect(50, 50, 50, 50);
554 ui::X11EventSource::GetInstance()->DispatchXEvents();
556 MouseEventRecorder first_recorder
, second_recorder
;
557 first
.GetNativeWindow()->AddPreTargetHandler(&first_recorder
);
558 second
.GetNativeWindow()->AddPreTargetHandler(&second_recorder
);
560 // Dispatch an event on |first|. Verify it gets the event.
561 ui::ScopedXI2Event event
;
562 event
.InitGenericButtonEvent(kPointerDeviceId
, ui::ET_MOUSEWHEEL
,
563 gfx::Point(50, 50), ui::EF_NONE
);
564 DispatchSingleEventToWidget(event
, &first
);
565 ASSERT_EQ(1u, first_recorder
.mouse_events().size());
566 EXPECT_EQ(ui::ET_MOUSEWHEEL
, first_recorder
.mouse_events()[0].type());
567 EXPECT_EQ(gfx::Point(25, 25).ToString(),
568 first_recorder
.mouse_events()[0].location().ToString());
569 ASSERT_EQ(0u, second_recorder
.mouse_events().size());
571 first_recorder
.Reset();
572 second_recorder
.Reset();
574 // Set a capture on |second|, and dispatch the same event to |first|. This
575 // event should reach |second| instead.
576 PretendCapture(&second
);
577 event
.InitGenericButtonEvent(kPointerDeviceId
, ui::ET_MOUSEWHEEL
,
578 gfx::Point(50, 50), ui::EF_NONE
);
579 DispatchSingleEventToWidget(event
, &first
);
581 ASSERT_EQ(0u, first_recorder
.mouse_events().size());
582 ASSERT_EQ(1u, second_recorder
.mouse_events().size());
583 EXPECT_EQ(ui::ET_MOUSEWHEEL
, second_recorder
.mouse_events()[0].type());
584 EXPECT_EQ(gfx::Point(-25, -25).ToString(),
585 second_recorder
.mouse_events()[0].location().ToString());
587 PretendCapture(nullptr);
588 first
.GetNativeWindow()->RemovePreTargetHandler(&first_recorder
);
589 second
.GetNativeWindow()->RemovePreTargetHandler(&second_recorder
);