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