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 TEST_F(X11TopmostWindowFinderTest, Basic) { 218 // Avoid positioning test windows at 0x0 because window managers often have a 219 // panel/launcher along one of the screen edges and do not allow windows to 220 // position themselves to overlap the panel/launcher. 221 scoped_ptr<Widget> widget1( 222 CreateAndShowWidget(gfx::Rect(100, 100, 200, 100))); 223 aura::Window* window1 = widget1->GetNativeWindow(); 224 XID xid1 = window1->GetHost()->GetAcceleratedWidget(); 225 226 XID xid2 = CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200)); 227 228 scoped_ptr<Widget> widget3( 229 CreateAndShowWidget(gfx::Rect(100, 190, 200, 110))); 230 aura::Window* window3 = widget3->GetNativeWindow(); 231 XID xid3 = window3->GetHost()->GetAcceleratedWidget(); 232 233 XID xids[] = { xid1, xid2, xid3 }; 234 StackingClientListWaiter waiter(xids, arraysize(xids)); 235 waiter.Wait(); 236 ui::X11EventSource::GetInstance()->DispatchXEvents(); 237 238 EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150)); 239 EXPECT_EQ(window1, FindTopmostLocalProcessWindowAt(150, 150)); 240 241 EXPECT_EQ(xid2, FindTopmostXWindowAt(250, 150)); 242 EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(250, 150)); 243 244 EXPECT_EQ(xid3, FindTopmostXWindowAt(250, 250)); 245 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(250, 250)); 246 247 EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 250)); 248 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 250)); 249 250 EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 195)); 251 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 195)); 252 253 EXPECT_NE(xid1, FindTopmostXWindowAt(1000, 1000)); 254 EXPECT_NE(xid2, FindTopmostXWindowAt(1000, 1000)); 255 EXPECT_NE(xid3, FindTopmostXWindowAt(1000, 1000)); 256 EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(1000, 1000)); 257 258 EXPECT_EQ(window1, 259 FindTopmostLocalProcessWindowWithIgnore(150, 150, window3)); 260 EXPECT_EQ(NULL, 261 FindTopmostLocalProcessWindowWithIgnore(250, 250, window3)); 262 EXPECT_EQ(NULL, 263 FindTopmostLocalProcessWindowWithIgnore(150, 250, window3)); 264 EXPECT_EQ(window1, 265 FindTopmostLocalProcessWindowWithIgnore(150, 195, window3)); 266 267 XDestroyWindow(xdisplay(), xid2); 268 } 269 270 // Test that the minimized state is properly handled. 271 TEST_F(X11TopmostWindowFinderTest, Minimized) { 272 scoped_ptr<Widget> widget1( 273 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100))); 274 aura::Window* window1 = widget1->GetNativeWindow(); 275 XID xid1 = window1->GetHost()->GetAcceleratedWidget(); 276 XID xid2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100)); 277 278 XID xids[] = { xid1, xid2 }; 279 StackingClientListWaiter stack_waiter(xids, arraysize(xids)); 280 stack_waiter.Wait(); 281 ui::X11EventSource::GetInstance()->DispatchXEvents(); 282 283 EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150)); 284 { 285 MinimizeWaiter minimize_waiter(xid1); 286 XIconifyWindow(xdisplay(), xid1, 0); 287 minimize_waiter.Wait(); 288 } 289 EXPECT_NE(xid1, FindTopmostXWindowAt(150, 150)); 290 EXPECT_NE(xid2, FindTopmostXWindowAt(150, 150)); 291 292 // Repeat test for an X window which does not belong to a views::Widget 293 // because the code path is different. 294 EXPECT_EQ(xid2, FindTopmostXWindowAt(350, 150)); 295 { 296 MinimizeWaiter minimize_waiter(xid2); 297 XIconifyWindow(xdisplay(), xid2, 0); 298 minimize_waiter.Wait(); 299 } 300 EXPECT_NE(xid1, FindTopmostXWindowAt(350, 150)); 301 EXPECT_NE(xid2, FindTopmostXWindowAt(350, 150)); 302 303 XDestroyWindow(xdisplay(), xid2); 304 } 305 306 // Test that non-rectangular windows are properly handled. 307 TEST_F(X11TopmostWindowFinderTest, NonRectangular) { 308 if (!ui::IsShapeExtensionAvailable()) 309 return; 310 311 scoped_ptr<Widget> widget1( 312 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100))); 313 XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); 314 SkRegion* skregion1 = new SkRegion; 315 skregion1->op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op); 316 skregion1->op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op); 317 // Widget takes ownership of |skregion1|. 318 widget1->SetShape(skregion1); 319 320 SkRegion skregion2; 321 skregion2.op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op); 322 skregion2.op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op); 323 XID xid2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100)); 324 REGION* region2 = gfx::CreateRegionFromSkRegion(skregion2); 325 XShapeCombineRegion(xdisplay(), xid2, ShapeBounding, 0, 0, region2, 326 false); 327 XDestroyRegion(region2); 328 329 XID xids[] = { xid1, xid2 }; 330 StackingClientListWaiter stack_waiter(xids, arraysize(xids)); 331 stack_waiter.Wait(); 332 ui::X11EventSource::GetInstance()->DispatchXEvents(); 333 334 EXPECT_EQ(xid1, FindTopmostXWindowAt(105, 120)); 335 EXPECT_NE(xid1, FindTopmostXWindowAt(105, 105)); 336 EXPECT_NE(xid2, FindTopmostXWindowAt(105, 105)); 337 338 // Repeat test for an X window which does not belong to a views::Widget 339 // because the code path is different. 340 EXPECT_EQ(xid2, FindTopmostXWindowAt(305, 120)); 341 EXPECT_NE(xid1, FindTopmostXWindowAt(305, 105)); 342 EXPECT_NE(xid2, FindTopmostXWindowAt(305, 105)); 343 344 XDestroyWindow(xdisplay(), xid2); 345 } 346 347 // Test that the TopmostWindowFinder finds windows which belong to menus 348 // (which may or may not belong to Chrome). 349 TEST_F(X11TopmostWindowFinderTest, Menu) { 350 XID xid = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100)); 351 352 XID root = DefaultRootWindow(xdisplay()); 353 XSetWindowAttributes swa; 354 swa.override_redirect = True; 355 XID menu_xid = XCreateWindow(xdisplay(), 356 root, 357 0, 0, 1, 1, 358 0, // border width 359 CopyFromParent, // depth 360 InputOutput, 361 CopyFromParent, // visual 362 CWOverrideRedirect, 363 &swa); 364 { 365 const char* kAtomsToCache[] = { "_NET_WM_WINDOW_TYPE_MENU", NULL }; 366 ui::X11AtomCache atom_cache(gfx::GetXDisplay(), kAtomsToCache); 367 ui::SetAtomProperty(menu_xid, 368 "_NET_WM_WINDOW_TYPE", 369 "ATOM", 370 atom_cache.GetAtom("_NET_WM_WINDOW_TYPE_MENU")); 371 } 372 ui::SetUseOSWindowFrame(menu_xid, false); 373 ShowAndSetXWindowBounds(menu_xid, gfx::Rect(140, 110, 100, 100)); 374 ui::X11EventSource::GetInstance()->DispatchXEvents(); 375 376 // |menu_xid| is never added to _NET_CLIENT_LIST_STACKING. 377 XID xids[] = { xid }; 378 StackingClientListWaiter stack_waiter(xids, arraysize(xids)); 379 stack_waiter.Wait(); 380 381 EXPECT_EQ(xid, FindTopmostXWindowAt(110, 110)); 382 EXPECT_EQ(menu_xid, FindTopmostXWindowAt(150, 120)); 383 EXPECT_EQ(menu_xid, FindTopmostXWindowAt(210, 120)); 384 385 XDestroyWindow(xdisplay(), xid); 386 XDestroyWindow(xdisplay(), menu_xid); 387 } 388 389 } // namespace views 390