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 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 |
OLD | NEW |