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.
5 #include "ui/views/widget/desktop_aura/x11_topmost_window_finder.h"
9 #include <X11/extensions/shape.h>
11 #include <X11/Xregion.h>
13 // Get rid of X11 macros which conflict with gtest.
17 #include "base/memory/scoped_ptr.h"
18 #include "base/path_service.h"
19 #include "third_party/skia/include/core/SkRect.h"
20 #include "third_party/skia/include/core/SkRegion.h"
21 #include "ui/aura/window.h"
22 #include "ui/aura/window_tree_host.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/base/ui_base_paths.h"
25 #include "ui/events/platform/x11/x11_event_source.h"
26 #include "ui/gfx/path.h"
27 #include "ui/gfx/path_x11.h"
28 #include "ui/gfx/x/x11_atom_cache.h"
29 #include "ui/gl/gl_surface.h"
30 #include "ui/views/test/views_test_base.h"
31 #include "ui/views/test/x11_property_change_waiter.h"
32 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
33 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
34 #include "ui/views/widget/widget.h"
40 // Waits till |window| is minimized.
41 class MinimizeWaiter
: public X11PropertyChangeWaiter
{
43 explicit MinimizeWaiter(XID window
)
44 : X11PropertyChangeWaiter(window
, "_NET_WM_STATE") {
45 const char* kAtomsToCache
[] = { "_NET_WM_STATE_HIDDEN", NULL
};
46 atom_cache_
.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache
));
49 ~MinimizeWaiter() override
{}
52 // X11PropertyChangeWaiter:
53 bool ShouldKeepOnWaiting(const ui::PlatformEvent
& event
) override
{
54 std::vector
<Atom
> wm_states
;
55 if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &wm_states
)) {
56 std::vector
<Atom
>::iterator it
= std::find(
59 atom_cache_
->GetAtom("_NET_WM_STATE_HIDDEN"));
60 return it
== wm_states
.end();
65 scoped_ptr
<ui::X11AtomCache
> atom_cache_
;
67 DISALLOW_COPY_AND_ASSIGN(MinimizeWaiter
);
70 // Waits till |_NET_CLIENT_LIST_STACKING| is updated to include
71 // |expected_windows|.
72 class StackingClientListWaiter
: public X11PropertyChangeWaiter
{
74 StackingClientListWaiter(XID
* expected_windows
, size_t count
)
75 : X11PropertyChangeWaiter(ui::GetX11RootWindow(),
76 "_NET_CLIENT_LIST_STACKING"),
77 expected_windows_(expected_windows
, expected_windows
+ count
) {
80 ~StackingClientListWaiter() override
{}
82 // X11PropertyChangeWaiter:
83 void Wait() override
{
84 // StackingClientListWaiter may be created after
85 // _NET_CLIENT_LIST_STACKING already contains |expected_windows|.
86 if (!ShouldKeepOnWaiting(NULL
))
89 X11PropertyChangeWaiter::Wait();
93 // X11PropertyChangeWaiter:
94 bool ShouldKeepOnWaiting(const ui::PlatformEvent
& event
) override
{
95 std::vector
<XID
> stack
;
96 ui::GetXWindowStack(ui::GetX11RootWindow(), &stack
);
97 for (size_t i
= 0; i
< expected_windows_
.size(); ++i
) {
98 std::vector
<XID
>::iterator it
= std::find(
99 stack
.begin(), stack
.end(), expected_windows_
[i
]);
100 if (it
== stack
.end())
106 std::vector
<XID
> expected_windows_
;
108 DISALLOW_COPY_AND_ASSIGN(StackingClientListWaiter
);
113 class X11TopmostWindowFinderTest
: public ViewsTestBase
{
115 X11TopmostWindowFinderTest() {
118 ~X11TopmostWindowFinderTest() override
{}
120 // Creates and shows a Widget with |bounds|. The caller takes ownership of
121 // the returned widget.
122 scoped_ptr
<Widget
> CreateAndShowWidget(const gfx::Rect
& bounds
) {
123 scoped_ptr
<Widget
> toplevel(new Widget
);
124 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_WINDOW
);
125 params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
126 params
.native_widget
= new DesktopNativeWidgetAura(toplevel
.get());
127 params
.bounds
= bounds
;
128 params
.remove_standard_frame
= true;
129 toplevel
->Init(params
);
131 return toplevel
.Pass();
134 // Creates and shows an X window with |bounds|.
135 XID
CreateAndShowXWindow(const gfx::Rect
& bounds
) {
136 XID root
= DefaultRootWindow(xdisplay());
137 XID xid
= XCreateSimpleWindow(xdisplay(),
144 ui::SetUseOSWindowFrame(xid
, false);
145 ShowAndSetXWindowBounds(xid
, bounds
);
149 // Shows |xid| and sets its bounds.
150 void ShowAndSetXWindowBounds(XID xid
, const gfx::Rect
& bounds
) {
151 XMapWindow(xdisplay(), xid
);
153 XWindowChanges changes
= {0};
154 changes
.x
= bounds
.x();
155 changes
.y
= bounds
.y();
156 changes
.width
= bounds
.width();
157 changes
.height
= bounds
.height();
158 XConfigureWindow(xdisplay(),
160 CWX
| CWY
| CWWidth
| CWHeight
,
164 Display
* xdisplay() {
165 return gfx::GetXDisplay();
168 // Returns the topmost X window at the passed in screen position.
169 XID
FindTopmostXWindowAt(int screen_x
, int screen_y
) {
170 X11TopmostWindowFinder finder
;
171 return finder
.FindWindowAt(gfx::Point(screen_x
, screen_y
));
174 // Returns the topmost aura::Window at the passed in screen position. Returns
175 // NULL if the topmost window does not have an associated aura::Window.
176 aura::Window
* FindTopmostLocalProcessWindowAt(int screen_x
, int screen_y
) {
177 X11TopmostWindowFinder finder
;
178 return finder
.FindLocalProcessWindowAt(gfx::Point(screen_x
, screen_y
),
179 std::set
<aura::Window
*>());
182 // Returns the topmost aura::Window at the passed in screen position ignoring
183 // |ignore_window|. Returns NULL if the topmost window does not have an
184 // associated aura::Window.
185 aura::Window
* FindTopmostLocalProcessWindowWithIgnore(
188 aura::Window
* ignore_window
) {
189 std::set
<aura::Window
*> ignore
;
190 ignore
.insert(ignore_window
);
191 X11TopmostWindowFinder finder
;
192 return finder
.FindLocalProcessWindowAt(gfx::Point(screen_x
, screen_y
),
196 static void SetUpTestCase() {
197 gfx::GLSurface::InitializeOneOffForTests();
198 ui::RegisterPathProvider();
199 base::FilePath ui_test_pak_path
;
200 ASSERT_TRUE(PathService::Get(ui::UI_TEST_PAK
, &ui_test_pak_path
));
201 ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path
);
205 void SetUp() override
{
206 ViewsTestBase::SetUp();
208 // Make X11 synchronous for our display connection. This does not force the
209 // window manager to behave synchronously.
210 XSynchronize(xdisplay(), True
);
212 // Ensure that the X11DesktopHandler exists. The X11DesktopHandler is
213 // necessary to properly track menu windows.
214 X11DesktopHandler::get();
217 void TearDown() override
{
218 XSynchronize(xdisplay(), False
);
219 ViewsTestBase::TearDown();
223 DISALLOW_COPY_AND_ASSIGN(X11TopmostWindowFinderTest
);
226 TEST_F(X11TopmostWindowFinderTest
, Basic
) {
227 // Avoid positioning test windows at 0x0 because window managers often have a
228 // panel/launcher along one of the screen edges and do not allow windows to
229 // position themselves to overlap the panel/launcher.
230 scoped_ptr
<Widget
> widget1(
231 CreateAndShowWidget(gfx::Rect(100, 100, 200, 100)));
232 aura::Window
* window1
= widget1
->GetNativeWindow();
233 XID xid1
= window1
->GetHost()->GetAcceleratedWidget();
235 XID xid2
= CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200));
237 scoped_ptr
<Widget
> widget3(
238 CreateAndShowWidget(gfx::Rect(100, 190, 200, 110)));
239 aura::Window
* window3
= widget3
->GetNativeWindow();
240 XID xid3
= window3
->GetHost()->GetAcceleratedWidget();
242 XID xids
[] = { xid1
, xid2
, xid3
};
243 StackingClientListWaiter
waiter(xids
, arraysize(xids
));
245 ui::X11EventSource::GetInstance()->DispatchXEvents();
247 EXPECT_EQ(xid1
, FindTopmostXWindowAt(150, 150));
248 EXPECT_EQ(window1
, FindTopmostLocalProcessWindowAt(150, 150));
250 EXPECT_EQ(xid2
, FindTopmostXWindowAt(250, 150));
251 EXPECT_EQ(NULL
, FindTopmostLocalProcessWindowAt(250, 150));
253 EXPECT_EQ(xid3
, FindTopmostXWindowAt(250, 250));
254 EXPECT_EQ(window3
, FindTopmostLocalProcessWindowAt(250, 250));
256 EXPECT_EQ(xid3
, FindTopmostXWindowAt(150, 250));
257 EXPECT_EQ(window3
, FindTopmostLocalProcessWindowAt(150, 250));
259 EXPECT_EQ(xid3
, FindTopmostXWindowAt(150, 195));
260 EXPECT_EQ(window3
, FindTopmostLocalProcessWindowAt(150, 195));
262 EXPECT_NE(xid1
, FindTopmostXWindowAt(1000, 1000));
263 EXPECT_NE(xid2
, FindTopmostXWindowAt(1000, 1000));
264 EXPECT_NE(xid3
, FindTopmostXWindowAt(1000, 1000));
265 EXPECT_EQ(NULL
, FindTopmostLocalProcessWindowAt(1000, 1000));
268 FindTopmostLocalProcessWindowWithIgnore(150, 150, window3
));
270 FindTopmostLocalProcessWindowWithIgnore(250, 250, window3
));
272 FindTopmostLocalProcessWindowWithIgnore(150, 250, window3
));
274 FindTopmostLocalProcessWindowWithIgnore(150, 195, window3
));
276 XDestroyWindow(xdisplay(), xid2
);
279 // Test that the minimized state is properly handled.
280 TEST_F(X11TopmostWindowFinderTest
, Minimized
) {
281 scoped_ptr
<Widget
> widget1(
282 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
283 aura::Window
* window1
= widget1
->GetNativeWindow();
284 XID xid1
= window1
->GetHost()->GetAcceleratedWidget();
285 XID xid2
= CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
287 XID xids
[] = { xid1
, xid2
};
288 StackingClientListWaiter
stack_waiter(xids
, arraysize(xids
));
290 ui::X11EventSource::GetInstance()->DispatchXEvents();
292 EXPECT_EQ(xid1
, FindTopmostXWindowAt(150, 150));
294 MinimizeWaiter
minimize_waiter(xid1
);
295 XIconifyWindow(xdisplay(), xid1
, 0);
296 minimize_waiter
.Wait();
298 EXPECT_NE(xid1
, FindTopmostXWindowAt(150, 150));
299 EXPECT_NE(xid2
, FindTopmostXWindowAt(150, 150));
301 // Repeat test for an X window which does not belong to a views::Widget
302 // because the code path is different.
303 EXPECT_EQ(xid2
, FindTopmostXWindowAt(350, 150));
305 MinimizeWaiter
minimize_waiter(xid2
);
306 XIconifyWindow(xdisplay(), xid2
, 0);
307 minimize_waiter
.Wait();
309 EXPECT_NE(xid1
, FindTopmostXWindowAt(350, 150));
310 EXPECT_NE(xid2
, FindTopmostXWindowAt(350, 150));
312 XDestroyWindow(xdisplay(), xid2
);
315 // Test that non-rectangular windows are properly handled.
316 TEST_F(X11TopmostWindowFinderTest
, NonRectangular
) {
317 if (!ui::IsShapeExtensionAvailable())
320 scoped_ptr
<Widget
> widget1(
321 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
322 XID xid1
= widget1
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
323 SkRegion
* skregion1
= new SkRegion
;
324 skregion1
->op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op
);
325 skregion1
->op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op
);
326 // Widget takes ownership of |skregion1|.
327 widget1
->SetShape(skregion1
);
330 skregion2
.op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op
);
331 skregion2
.op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op
);
332 XID xid2
= CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
333 REGION
* region2
= gfx::CreateRegionFromSkRegion(skregion2
);
334 XShapeCombineRegion(xdisplay(), xid2
, ShapeBounding
, 0, 0, region2
,
336 XDestroyRegion(region2
);
338 XID xids
[] = { xid1
, xid2
};
339 StackingClientListWaiter
stack_waiter(xids
, arraysize(xids
));
341 ui::X11EventSource::GetInstance()->DispatchXEvents();
343 EXPECT_EQ(xid1
, FindTopmostXWindowAt(105, 120));
344 EXPECT_NE(xid1
, FindTopmostXWindowAt(105, 105));
345 EXPECT_NE(xid2
, FindTopmostXWindowAt(105, 105));
347 // Repeat test for an X window which does not belong to a views::Widget
348 // because the code path is different.
349 EXPECT_EQ(xid2
, FindTopmostXWindowAt(305, 120));
350 EXPECT_NE(xid1
, FindTopmostXWindowAt(305, 105));
351 EXPECT_NE(xid2
, FindTopmostXWindowAt(305, 105));
353 XDestroyWindow(xdisplay(), xid2
);
356 // Test that a window with an empty shape are properly handled.
357 TEST_F(X11TopmostWindowFinderTest
, NonRectangularEmptyShape
) {
358 if (!ui::IsShapeExtensionAvailable())
361 scoped_ptr
<Widget
> widget1(
362 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
363 XID xid1
= widget1
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
364 SkRegion
* skregion1
= new SkRegion
;
365 skregion1
->op(SkIRect::MakeXYWH(0, 0, 0, 0), SkRegion::kUnion_Op
);
366 // Widget takes ownership of |skregion1|.
367 widget1
->SetShape(skregion1
);
369 XID xids
[] = { xid1
};
370 StackingClientListWaiter
stack_waiter(xids
, arraysize(xids
));
372 ui::X11EventSource::GetInstance()->DispatchXEvents();
374 EXPECT_NE(xid1
, FindTopmostXWindowAt(105, 105));
377 // Test that setting a Null shape removes the shape.
378 TEST_F(X11TopmostWindowFinderTest
, NonRectangularNullShape
) {
379 if (!ui::IsShapeExtensionAvailable())
382 scoped_ptr
<Widget
> widget1(
383 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
384 XID xid1
= widget1
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
385 SkRegion
* skregion1
= new SkRegion
;
386 skregion1
->op(SkIRect::MakeXYWH(0, 0, 0, 0), SkRegion::kUnion_Op
);
387 // Widget takes ownership of |skregion1|.
388 widget1
->SetShape(skregion1
);
390 // Remove the shape - this is now just a normal window.
391 widget1
->SetShape(NULL
);
393 XID xids
[] = { xid1
};
394 StackingClientListWaiter
stack_waiter(xids
, arraysize(xids
));
396 ui::X11EventSource::GetInstance()->DispatchXEvents();
398 EXPECT_EQ(xid1
, FindTopmostXWindowAt(105, 105));
401 // Test that the TopmostWindowFinder finds windows which belong to menus
402 // (which may or may not belong to Chrome).
403 TEST_F(X11TopmostWindowFinderTest
, Menu
) {
404 XID xid
= CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100));
406 XID root
= DefaultRootWindow(xdisplay());
407 XSetWindowAttributes swa
;
408 swa
.override_redirect
= True
;
409 XID menu_xid
= XCreateWindow(xdisplay(),
413 CopyFromParent
, // depth
415 CopyFromParent
, // visual
419 const char* kAtomsToCache
[] = { "_NET_WM_WINDOW_TYPE_MENU", NULL
};
420 ui::X11AtomCache
atom_cache(gfx::GetXDisplay(), kAtomsToCache
);
421 ui::SetAtomProperty(menu_xid
,
422 "_NET_WM_WINDOW_TYPE",
424 atom_cache
.GetAtom("_NET_WM_WINDOW_TYPE_MENU"));
426 ui::SetUseOSWindowFrame(menu_xid
, false);
427 ShowAndSetXWindowBounds(menu_xid
, gfx::Rect(140, 110, 100, 100));
428 ui::X11EventSource::GetInstance()->DispatchXEvents();
430 // |menu_xid| is never added to _NET_CLIENT_LIST_STACKING.
431 XID xids
[] = { xid
};
432 StackingClientListWaiter
stack_waiter(xids
, arraysize(xids
));
435 EXPECT_EQ(xid
, FindTopmostXWindowAt(110, 110));
436 EXPECT_EQ(menu_xid
, FindTopmostXWindowAt(150, 120));
437 EXPECT_EQ(menu_xid
, FindTopmostXWindowAt(210, 120));
439 XDestroyWindow(xdisplay(), xid
);
440 XDestroyWindow(xdisplay(), menu_xid
);