OLD | NEW |
| (Empty) |
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 "ui/views/widget/desktop_aura/x11_topmost_window_finder.h" | |
6 | |
7 #include <algorithm> | |
8 #include <vector> | |
9 #include <X11/extensions/shape.h> | |
10 #include <X11/Xlib.h> | |
11 #include <X11/Xregion.h> | |
12 | |
13 // Get rid of X11 macros which conflict with gtest. | |
14 #undef Bool | |
15 #undef None | |
16 | |
17 #include "base/memory/scoped_ptr.h" | |
18 #include "third_party/skia/include/core/SkRect.h" | |
19 #include "third_party/skia/include/core/SkRegion.h" | |
20 #include "ui/aura/window.h" | |
21 #include "ui/aura/window_tree_host.h" | |
22 #include "ui/events/platform/x11/x11_event_source.h" | |
23 #include "ui/gfx/path.h" | |
24 #include "ui/gfx/path_x11.h" | |
25 #include "ui/gfx/x/x11_atom_cache.h" | |
26 #include "ui/views/test/views_test_base.h" | |
27 #include "ui/views/test/x11_property_change_waiter.h" | |
28 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" | |
29 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h" | |
30 #include "ui/views/widget/widget.h" | |
31 | |
32 namespace views { | |
33 | |
34 namespace { | |
35 | |
36 // Waits till |window| is minimized. | |
37 class MinimizeWaiter : public X11PropertyChangeWaiter { | |
38 public: | |
39 explicit MinimizeWaiter(XID window) | |
40 : X11PropertyChangeWaiter(window, "_NET_WM_STATE") { | |
41 const char* kAtomsToCache[] = { "_NET_WM_STATE_HIDDEN", NULL }; | |
42 atom_cache_.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache)); | |
43 } | |
44 | |
45 virtual ~MinimizeWaiter() { | |
46 } | |
47 | |
48 private: | |
49 // X11PropertyChangeWaiter: | |
50 virtual bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) OVERRIDE { | |
51 std::vector<Atom> wm_states; | |
52 if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &wm_states)) { | |
53 std::vector<Atom>::iterator it = std::find( | |
54 wm_states.begin(), | |
55 wm_states.end(), | |
56 atom_cache_->GetAtom("_NET_WM_STATE_HIDDEN")); | |
57 return it == wm_states.end(); | |
58 } | |
59 return true; | |
60 } | |
61 | |
62 scoped_ptr<ui::X11AtomCache> atom_cache_; | |
63 | |
64 DISALLOW_COPY_AND_ASSIGN(MinimizeWaiter); | |
65 }; | |
66 | |
67 // Waits till |_NET_CLIENT_LIST_STACKING| is updated to include | |
68 // |expected_windows|. | |
69 class StackingClientListWaiter : public X11PropertyChangeWaiter { | |
70 public: | |
71 StackingClientListWaiter(XID* expected_windows, size_t count) | |
72 : X11PropertyChangeWaiter(ui::GetX11RootWindow(), | |
73 "_NET_CLIENT_LIST_STACKING"), | |
74 expected_windows_(expected_windows, expected_windows + count) { | |
75 } | |
76 | |
77 virtual ~StackingClientListWaiter() { | |
78 } | |
79 | |
80 // X11PropertyChangeWaiter: | |
81 virtual void Wait() OVERRIDE { | |
82 // StackingClientListWaiter may be created after | |
83 // _NET_CLIENT_LIST_STACKING already contains |expected_windows|. | |
84 if (!ShouldKeepOnWaiting(NULL)) | |
85 return; | |
86 | |
87 X11PropertyChangeWaiter::Wait(); | |
88 } | |
89 | |
90 private: | |
91 // X11PropertyChangeWaiter: | |
92 virtual bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) OVERRIDE { | |
93 std::vector<XID> stack; | |
94 ui::GetXWindowStack(ui::GetX11RootWindow(), &stack); | |
95 for (size_t i = 0; i < expected_windows_.size(); ++i) { | |
96 std::vector<XID>::iterator it = std::find( | |
97 stack.begin(), stack.end(), expected_windows_[i]); | |
98 if (it == stack.end()) | |
99 return true; | |
100 } | |
101 return false; | |
102 } | |
103 | |
104 std::vector<XID> expected_windows_; | |
105 | |
106 DISALLOW_COPY_AND_ASSIGN(StackingClientListWaiter); | |
107 }; | |
108 | |
109 } // namespace | |
110 | |
111 class X11TopmostWindowFinderTest : public ViewsTestBase { | |
112 public: | |
113 X11TopmostWindowFinderTest() { | |
114 } | |
115 | |
116 virtual ~X11TopmostWindowFinderTest() { | |
117 } | |
118 | |
119 // Creates and shows a Widget with |bounds|. The caller takes ownership of | |
120 // the returned widget. | |
121 scoped_ptr<Widget> CreateAndShowWidget(const gfx::Rect& bounds) { | |
122 scoped_ptr<Widget> toplevel(new Widget); | |
123 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); | |
124 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | |
125 params.native_widget = new DesktopNativeWidgetAura(toplevel.get()); | |
126 params.bounds = bounds; | |
127 params.remove_standard_frame = true; | |
128 toplevel->Init(params); | |
129 toplevel->Show(); | |
130 return toplevel.Pass(); | |
131 } | |
132 | |
133 // Creates and shows an X window with |bounds|. | |
134 XID CreateAndShowXWindow(const gfx::Rect& bounds) { | |
135 XID root = DefaultRootWindow(xdisplay()); | |
136 XID xid = XCreateSimpleWindow(xdisplay(), | |
137 root, | |
138 0, 0, 1, 1, | |
139 0, // border_width | |
140 0, // border | |
141 0); // background | |
142 | |
143 ui::SetUseOSWindowFrame(xid, false); | |
144 ShowAndSetXWindowBounds(xid, bounds); | |
145 return xid; | |
146 } | |
147 | |
148 // Shows |xid| and sets its bounds. | |
149 void ShowAndSetXWindowBounds(XID xid, const gfx::Rect& bounds) { | |
150 XMapWindow(xdisplay(), xid); | |
151 | |
152 XWindowChanges changes = {0}; | |
153 changes.x = bounds.x(); | |
154 changes.y = bounds.y(); | |
155 changes.width = bounds.width(); | |
156 changes.height = bounds.height(); | |
157 XConfigureWindow(xdisplay(), | |
158 xid, | |
159 CWX | CWY | CWWidth | CWHeight, | |
160 &changes); | |
161 } | |
162 | |
163 Display* xdisplay() { | |
164 return gfx::GetXDisplay(); | |
165 } | |
166 | |
167 // Returns the topmost X window at the passed in screen position. | |
168 XID FindTopmostXWindowAt(int screen_x, int screen_y) { | |
169 X11TopmostWindowFinder finder; | |
170 return finder.FindWindowAt(gfx::Point(screen_x, screen_y)); | |
171 } | |
172 | |
173 // Returns the topmost aura::Window at the passed in screen position. Returns | |
174 // NULL if the topmost window does not have an associated aura::Window. | |
175 aura::Window* FindTopmostLocalProcessWindowAt(int screen_x, int screen_y) { | |
176 X11TopmostWindowFinder finder; | |
177 return finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y), | |
178 std::set<aura::Window*>()); | |
179 } | |
180 | |
181 // Returns the topmost aura::Window at the passed in screen position ignoring | |
182 // |ignore_window|. Returns NULL if the topmost window does not have an | |
183 // associated aura::Window. | |
184 aura::Window* FindTopmostLocalProcessWindowWithIgnore( | |
185 int screen_x, | |
186 int screen_y, | |
187 aura::Window* ignore_window) { | |
188 std::set<aura::Window*> ignore; | |
189 ignore.insert(ignore_window); | |
190 X11TopmostWindowFinder finder; | |
191 return finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y), | |
192 ignore); | |
193 } | |
194 | |
195 // ViewsTestBase: | |
196 virtual void SetUp() OVERRIDE { | |
197 ViewsTestBase::SetUp(); | |
198 | |
199 // Make X11 synchronous for our display connection. This does not force the | |
200 // window manager to behave synchronously. | |
201 XSynchronize(xdisplay(), True); | |
202 | |
203 // Ensure that the X11DesktopHandler exists. The X11DesktopHandler is | |
204 // necessary to properly track menu windows. | |
205 X11DesktopHandler::get(); | |
206 } | |
207 | |
208 virtual void TearDown() OVERRIDE { | |
209 XSynchronize(xdisplay(), False); | |
210 ViewsTestBase::TearDown(); | |
211 } | |
212 | |
213 private: | |
214 DISALLOW_COPY_AND_ASSIGN(X11TopmostWindowFinderTest); | |
215 }; | |
216 | |
217 // Flaky on Linux. http://crbug.com/388241 | |
218 TEST_F(X11TopmostWindowFinderTest, DISABLED_Basic) { | |
219 // Avoid positioning test windows at 0x0 because window managers often have a | |
220 // panel/launcher along one of the screen edges and do not allow windows to | |
221 // position themselves to overlap the panel/launcher. | |
222 scoped_ptr<Widget> widget1( | |
223 CreateAndShowWidget(gfx::Rect(100, 100, 200, 100))); | |
224 aura::Window* window1 = widget1->GetNativeWindow(); | |
225 XID xid1 = window1->GetHost()->GetAcceleratedWidget(); | |
226 | |
227 XID xid2 = CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200)); | |
228 | |
229 scoped_ptr<Widget> widget3( | |
230 CreateAndShowWidget(gfx::Rect(100, 190, 200, 110))); | |
231 aura::Window* window3 = widget3->GetNativeWindow(); | |
232 XID xid3 = window3->GetHost()->GetAcceleratedWidget(); | |
233 | |
234 XID xids[] = { xid1, xid2, xid3 }; | |
235 StackingClientListWaiter waiter(xids, arraysize(xids)); | |
236 waiter.Wait(); | |
237 ui::X11EventSource::GetInstance()->DispatchXEvents(); | |
238 | |
239 EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150)); | |
240 EXPECT_EQ(window1, FindTopmostLocalProcessWindowAt(150, 150)); | |
241 | |
242 EXPECT_EQ(xid2, FindTopmostXWindowAt(250, 150)); | |
243 EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(250, 150)); | |
244 | |
245 EXPECT_EQ(xid3, FindTopmostXWindowAt(250, 250)); | |
246 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(250, 250)); | |
247 | |
248 EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 250)); | |
249 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 250)); | |
250 | |
251 EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 195)); | |
252 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 195)); | |
253 | |
254 EXPECT_NE(xid1, FindTopmostXWindowAt(1000, 1000)); | |
255 EXPECT_NE(xid2, FindTopmostXWindowAt(1000, 1000)); | |
256 EXPECT_NE(xid3, FindTopmostXWindowAt(1000, 1000)); | |
257 EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(1000, 1000)); | |
258 | |
259 EXPECT_EQ(window1, | |
260 FindTopmostLocalProcessWindowWithIgnore(150, 150, window3)); | |
261 EXPECT_EQ(NULL, | |
262 FindTopmostLocalProcessWindowWithIgnore(250, 250, window3)); | |
263 EXPECT_EQ(NULL, | |
264 FindTopmostLocalProcessWindowWithIgnore(150, 250, window3)); | |
265 EXPECT_EQ(window1, | |
266 FindTopmostLocalProcessWindowWithIgnore(150, 195, window3)); | |
267 | |
268 XDestroyWindow(xdisplay(), xid2); | |
269 } | |
270 | |
271 // Test that the minimized state is properly handled. | |
272 // Flaky on Linux. http://crbug.com/388241 | |
273 TEST_F(X11TopmostWindowFinderTest, DISABLED_Minimized) { | |
274 scoped_ptr<Widget> widget1( | |
275 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100))); | |
276 aura::Window* window1 = widget1->GetNativeWindow(); | |
277 XID xid1 = window1->GetHost()->GetAcceleratedWidget(); | |
278 XID xid2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100)); | |
279 | |
280 XID xids[] = { xid1, xid2 }; | |
281 StackingClientListWaiter stack_waiter(xids, arraysize(xids)); | |
282 stack_waiter.Wait(); | |
283 ui::X11EventSource::GetInstance()->DispatchXEvents(); | |
284 | |
285 EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150)); | |
286 { | |
287 MinimizeWaiter minimize_waiter(xid1); | |
288 XIconifyWindow(xdisplay(), xid1, 0); | |
289 minimize_waiter.Wait(); | |
290 } | |
291 EXPECT_NE(xid1, FindTopmostXWindowAt(150, 150)); | |
292 EXPECT_NE(xid2, FindTopmostXWindowAt(150, 150)); | |
293 | |
294 // Repeat test for an X window which does not belong to a views::Widget | |
295 // because the code path is different. | |
296 EXPECT_EQ(xid2, FindTopmostXWindowAt(350, 150)); | |
297 { | |
298 MinimizeWaiter minimize_waiter(xid2); | |
299 XIconifyWindow(xdisplay(), xid2, 0); | |
300 minimize_waiter.Wait(); | |
301 } | |
302 EXPECT_NE(xid1, FindTopmostXWindowAt(350, 150)); | |
303 EXPECT_NE(xid2, FindTopmostXWindowAt(350, 150)); | |
304 | |
305 XDestroyWindow(xdisplay(), xid2); | |
306 } | |
307 | |
308 // Test that non-rectangular windows are properly handled. | |
309 // Flaky on Linux. http://crbug.com/388241 | |
310 TEST_F(X11TopmostWindowFinderTest, DISABLED_NonRectangular) { | |
311 if (!ui::IsShapeExtensionAvailable()) | |
312 return; | |
313 | |
314 scoped_ptr<Widget> widget1( | |
315 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100))); | |
316 XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); | |
317 SkRegion* skregion1 = new SkRegion; | |
318 skregion1->op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op); | |
319 skregion1->op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op); | |
320 // Widget takes ownership of |skregion1|. | |
321 widget1->SetShape(skregion1); | |
322 | |
323 SkRegion skregion2; | |
324 skregion2.op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op); | |
325 skregion2.op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op); | |
326 XID xid2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100)); | |
327 REGION* region2 = gfx::CreateRegionFromSkRegion(skregion2); | |
328 XShapeCombineRegion(xdisplay(), xid2, ShapeBounding, 0, 0, region2, | |
329 false); | |
330 XDestroyRegion(region2); | |
331 | |
332 XID xids[] = { xid1, xid2 }; | |
333 StackingClientListWaiter stack_waiter(xids, arraysize(xids)); | |
334 stack_waiter.Wait(); | |
335 ui::X11EventSource::GetInstance()->DispatchXEvents(); | |
336 | |
337 EXPECT_EQ(xid1, FindTopmostXWindowAt(105, 120)); | |
338 EXPECT_NE(xid1, FindTopmostXWindowAt(105, 105)); | |
339 EXPECT_NE(xid2, FindTopmostXWindowAt(105, 105)); | |
340 | |
341 // Repeat test for an X window which does not belong to a views::Widget | |
342 // because the code path is different. | |
343 EXPECT_EQ(xid2, FindTopmostXWindowAt(305, 120)); | |
344 EXPECT_NE(xid1, FindTopmostXWindowAt(305, 105)); | |
345 EXPECT_NE(xid2, FindTopmostXWindowAt(305, 105)); | |
346 | |
347 XDestroyWindow(xdisplay(), xid2); | |
348 } | |
349 | |
350 // Test that the TopmostWindowFinder finds windows which belong to menus | |
351 // (which may or may not belong to Chrome). | |
352 // Flaky on Linux. http://crbug.com/388241 | |
353 TEST_F(X11TopmostWindowFinderTest, DISABLED_Menu) { | |
354 XID xid = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100)); | |
355 | |
356 XID root = DefaultRootWindow(xdisplay()); | |
357 XSetWindowAttributes swa; | |
358 swa.override_redirect = True; | |
359 XID menu_xid = XCreateWindow(xdisplay(), | |
360 root, | |
361 0, 0, 1, 1, | |
362 0, // border width | |
363 CopyFromParent, // depth | |
364 InputOutput, | |
365 CopyFromParent, // visual | |
366 CWOverrideRedirect, | |
367 &swa); | |
368 { | |
369 const char* kAtomsToCache[] = { "_NET_WM_WINDOW_TYPE_MENU", NULL }; | |
370 ui::X11AtomCache atom_cache(gfx::GetXDisplay(), kAtomsToCache); | |
371 ui::SetAtomProperty(menu_xid, | |
372 "_NET_WM_WINDOW_TYPE", | |
373 "ATOM", | |
374 atom_cache.GetAtom("_NET_WM_WINDOW_TYPE_MENU")); | |
375 } | |
376 ui::SetUseOSWindowFrame(menu_xid, false); | |
377 ShowAndSetXWindowBounds(menu_xid, gfx::Rect(140, 110, 100, 100)); | |
378 ui::X11EventSource::GetInstance()->DispatchXEvents(); | |
379 | |
380 // |menu_xid| is never added to _NET_CLIENT_LIST_STACKING. | |
381 XID xids[] = { xid }; | |
382 StackingClientListWaiter stack_waiter(xids, arraysize(xids)); | |
383 stack_waiter.Wait(); | |
384 | |
385 EXPECT_EQ(xid, FindTopmostXWindowAt(110, 110)); | |
386 EXPECT_EQ(menu_xid, FindTopmostXWindowAt(150, 120)); | |
387 EXPECT_EQ(menu_xid, FindTopmostXWindowAt(210, 120)); | |
388 | |
389 XDestroyWindow(xdisplay(), xid); | |
390 XDestroyWindow(xdisplay(), menu_xid); | |
391 } | |
392 | |
393 } // namespace views | |
OLD | NEW |