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 <vector> |
| 8 |
| 9 #include "base/memory/scoped_ptr.h" |
| 10 #include "base/run_loop.h" |
| 11 #include "third_party/skia/include/core/SkRect.h" |
| 12 #include "third_party/skia/include/core/SkRegion.h" |
| 13 #include "ui/aura/window.h" |
| 14 #include "ui/aura/window_tree_host.h" |
| 15 #include "ui/events/platform/platform_event_dispatcher.h" |
| 16 #include "ui/events/platform/platform_event_source.h" |
| 17 #include "ui/gfx/path.h" |
| 18 #include "ui/gfx/x/x11_atom_cache.h" |
| 19 #include "ui/views/test/views_test_base.h" |
| 20 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" |
| 21 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h" |
| 22 #include "ui/views/widget/widget.h" |
| 23 |
| 24 #include <X11/extensions/shape.h> |
| 25 #include <X11/Xlib.h> |
| 26 #include <X11/Xregion.h> |
| 27 |
| 28 namespace views { |
| 29 |
| 30 namespace { |
| 31 |
| 32 // Blocks till the value of |property| on |window| changes. |
| 33 class PropertyChangeWaiter : public ui::PlatformEventDispatcher { |
| 34 public: |
| 35 PropertyChangeWaiter(XID window, const char* property); |
| 36 virtual ~PropertyChangeWaiter(); |
| 37 |
| 38 // Blocks till the value of |property_| changes. |
| 39 void Wait(); |
| 40 |
| 41 protected: |
| 42 // Returns whether the run loop can exit. |
| 43 virtual bool ShouldKeepOnWaiting(XEvent* event); |
| 44 |
| 45 XID xwindow() const { |
| 46 return x_window_; |
| 47 } |
| 48 |
| 49 private: |
| 50 // ui::PlatformEventDispatcher: |
| 51 virtual bool CanDispatchEvent(const ui::PlatformEvent& event) OVERRIDE; |
| 52 virtual uint32_t DispatchEvent(const ui::PlatformEvent& event) OVERRIDE; |
| 53 |
| 54 XID x_window_; |
| 55 const char* property_; |
| 56 |
| 57 // Whether Wait() should block. |
| 58 bool wait_; |
| 59 |
| 60 // Ends the run loop. |
| 61 base::Closure quit_closure_; |
| 62 |
| 63 // The event mask to be restored upon PropertyChangeWaiter's destruction. |
| 64 long old_event_mask_; |
| 65 |
| 66 scoped_ptr<ui::X11AtomCache> atom_cache_; |
| 67 |
| 68 DISALLOW_COPY_AND_ASSIGN(PropertyChangeWaiter); |
| 69 }; |
| 70 |
| 71 PropertyChangeWaiter::PropertyChangeWaiter(XID window, const char* property) |
| 72 : x_window_(window), |
| 73 property_(property), |
| 74 wait_(true), |
| 75 old_event_mask_(0) { |
| 76 Display* display = gfx::GetXDisplay(); |
| 77 |
| 78 // Ensure that we are listening to PropertyNotify events for |window|. This |
| 79 // is not the case for windows created via CreateAndShowXWindow(). |
| 80 XWindowAttributes attributes; |
| 81 XGetWindowAttributes(display, x_window_, &attributes); |
| 82 old_event_mask_ = attributes.your_event_mask; |
| 83 XSelectInput(display, x_window_, old_event_mask_ | PropertyChangeMask); |
| 84 |
| 85 const char* kAtomsToCache[] = { property, NULL }; |
| 86 atom_cache_.reset(new ui::X11AtomCache(display, kAtomsToCache)); |
| 87 ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); |
| 88 } |
| 89 |
| 90 PropertyChangeWaiter::~PropertyChangeWaiter() { |
| 91 XSelectInput(gfx::GetXDisplay(), x_window_, old_event_mask_); |
| 92 ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this); |
| 93 } |
| 94 |
| 95 void PropertyChangeWaiter::Wait() { |
| 96 if (!wait_) |
| 97 return; |
| 98 |
| 99 // PropertyChangeWaiter (or one of its subclasses) may be constructed after |
| 100 // the property change has occurred. |
| 101 if (!ShouldKeepOnWaiting(NULL)) { |
| 102 wait_ = false; |
| 103 return; |
| 104 } |
| 105 |
| 106 base::RunLoop run_loop; |
| 107 quit_closure_ = run_loop.QuitClosure(); |
| 108 run_loop.Run(); |
| 109 } |
| 110 |
| 111 bool PropertyChangeWaiter::ShouldKeepOnWaiting(XEvent* event) { |
| 112 // Stop waiting once we get a property change. |
| 113 return event != NULL; |
| 114 } |
| 115 |
| 116 bool PropertyChangeWaiter::CanDispatchEvent(const ui::PlatformEvent& event) { |
| 117 return event->type == PropertyNotify && |
| 118 event->xproperty.window == x_window_ && |
| 119 event->xproperty.atom == atom_cache_->GetAtom(property_); |
| 120 } |
| 121 |
| 122 uint32_t PropertyChangeWaiter::DispatchEvent(const ui::PlatformEvent& event) { |
| 123 if (wait_ && !ShouldKeepOnWaiting(event)) { |
| 124 wait_ = false; |
| 125 if (!quit_closure_.is_null()) |
| 126 quit_closure_.Run(); |
| 127 } |
| 128 return ui::POST_DISPATCH_NONE; |
| 129 } |
| 130 |
| 131 // Waits till |window| is minimized. |
| 132 class MinimizeWaiter : public PropertyChangeWaiter { |
| 133 public: |
| 134 explicit MinimizeWaiter(XID window); |
| 135 virtual ~MinimizeWaiter(); |
| 136 |
| 137 private: |
| 138 // PropertyChangeWaiter: |
| 139 virtual bool ShouldKeepOnWaiting(XEvent* event) OVERRIDE; |
| 140 |
| 141 scoped_ptr<ui::X11AtomCache> atom_cache_; |
| 142 |
| 143 DISALLOW_COPY_AND_ASSIGN(MinimizeWaiter); |
| 144 }; |
| 145 |
| 146 MinimizeWaiter::MinimizeWaiter(XID window) |
| 147 : PropertyChangeWaiter(window, "_NET_WM_STATE") { |
| 148 const char* kAtomsToCache[] = { "_NET_WM_STATE_HIDDEN", NULL }; |
| 149 atom_cache_.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache)); |
| 150 } |
| 151 |
| 152 MinimizeWaiter::~MinimizeWaiter() { |
| 153 } |
| 154 |
| 155 bool MinimizeWaiter::ShouldKeepOnWaiting(XEvent* event) { |
| 156 std::vector<Atom> wm_states; |
| 157 if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &wm_states)) { |
| 158 std::vector<Atom>::iterator it = std::find( |
| 159 wm_states.begin(), |
| 160 wm_states.end(), |
| 161 atom_cache_->GetAtom("_NET_WM_STATE_HIDDEN")); |
| 162 return it == wm_states.end(); |
| 163 } |
| 164 return true; |
| 165 } |
| 166 |
| 167 // Waits till |_NET_CLIENT_LIST_STACKING| is updated to include |
| 168 // |expected_windows|. |
| 169 class StackingClientListWaiter : public PropertyChangeWaiter { |
| 170 public: |
| 171 StackingClientListWaiter(XID* expected_windows, size_t count); |
| 172 virtual ~StackingClientListWaiter(); |
| 173 |
| 174 private: |
| 175 // PropertyChangeWaiter: |
| 176 virtual bool ShouldKeepOnWaiting(XEvent* event) OVERRIDE; |
| 177 |
| 178 std::vector<XID> expected_windows_; |
| 179 |
| 180 DISALLOW_COPY_AND_ASSIGN(StackingClientListWaiter); |
| 181 }; |
| 182 |
| 183 StackingClientListWaiter::StackingClientListWaiter(XID* expected_windows, |
| 184 size_t count) |
| 185 : PropertyChangeWaiter(ui::GetX11RootWindow(), |
| 186 "_NET_CLIENT_LIST_STACKING"), |
| 187 expected_windows_(expected_windows, expected_windows + count) { |
| 188 } |
| 189 |
| 190 StackingClientListWaiter::~StackingClientListWaiter() { |
| 191 } |
| 192 |
| 193 bool StackingClientListWaiter::ShouldKeepOnWaiting(XEvent* event) { |
| 194 std::vector<XID> stack; |
| 195 ui::GetXWindowStack(ui::GetX11RootWindow(), &stack); |
| 196 for (size_t i = 0; i < expected_windows_.size(); ++i) { |
| 197 std::vector<XID>::iterator it = std::find( |
| 198 stack.begin(), stack.end(), expected_windows_[i]); |
| 199 if (it == stack.end()) |
| 200 return true; |
| 201 } |
| 202 return false; |
| 203 } |
| 204 |
| 205 } // namespace |
| 206 |
| 207 class X11TopmostWindowFinderTest : public ViewsTestBase { |
| 208 public: |
| 209 X11TopmostWindowFinderTest() { |
| 210 } |
| 211 |
| 212 virtual ~X11TopmostWindowFinderTest() { |
| 213 } |
| 214 |
| 215 // Creates and shows a Widget with |bounds|. The caller takes ownership of |
| 216 // the returned widget. |
| 217 Widget* CreateAndShowWidget(const gfx::Rect& bounds) WARN_UNUSED_RESULT { |
| 218 Widget* toplevel = new Widget; |
| 219 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); |
| 220 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| 221 params.native_widget = new DesktopNativeWidgetAura(toplevel); |
| 222 params.bounds = bounds; |
| 223 params.remove_standard_frame = true; |
| 224 toplevel->Init(params); |
| 225 toplevel->Show(); |
| 226 return toplevel; |
| 227 } |
| 228 |
| 229 // Creates and shows an X window with |bounds|. |
| 230 XID CreateAndShowXWindow(const gfx::Rect& bounds) { |
| 231 XID root = DefaultRootWindow(xdisplay()); |
| 232 XID xid = XCreateSimpleWindow(xdisplay(), |
| 233 root, |
| 234 1, 1, 1, 1, |
| 235 0, // border_width |
| 236 0, // border |
| 237 0); // background |
| 238 ui::SetUseOSWindowFrame(xid, false); |
| 239 ShowAndSetXIDBounds(xid, bounds); |
| 240 return xid; |
| 241 } |
| 242 |
| 243 // Shows |xid| and sets its bounds. |
| 244 void ShowAndSetXIDBounds(XID xid, const gfx::Rect& bounds) { |
| 245 XMapWindow(xdisplay(), xid); |
| 246 |
| 247 XWindowChanges changes = {0}; |
| 248 changes.x = bounds.x(); |
| 249 changes.y = bounds.y(); |
| 250 changes.width = bounds.width(); |
| 251 changes.height = bounds.height(); |
| 252 XConfigureWindow(xdisplay(), |
| 253 xid, |
| 254 CWX | CWY | CWWidth | CWHeight, |
| 255 &changes); |
| 256 } |
| 257 |
| 258 // Returns the atom associated with |name|. |
| 259 Atom GetAtom(const char* name) { |
| 260 return XInternAtom(gfx::GetXDisplay(), name, false); |
| 261 } |
| 262 |
| 263 Display* xdisplay() { |
| 264 return gfx::GetXDisplay(); |
| 265 } |
| 266 |
| 267 // Returns the topmost X window at the passed in screen position. |
| 268 XID FindTopmostXWindowAt(int screen_x, int screen_y) { |
| 269 X11TopmostWindowFinder finder; |
| 270 return finder.FindWindowAt(gfx::Point(screen_x, screen_y)); |
| 271 } |
| 272 |
| 273 // Returns the topmost aura::Window at the passed in screen position. Returns |
| 274 // NULL if the topmost window does not have an associated aura::Window. |
| 275 aura::Window* FindTopmostLocalProcessWindowAt(int screen_x, int screen_y) { |
| 276 X11TopmostWindowFinder finder; |
| 277 return finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y), |
| 278 std::set<aura::Window*>()); |
| 279 } |
| 280 |
| 281 // Returns the topmost aura::Window at the passed in screen position ignoring |
| 282 // |ignore_window|. Returns NULL if the topmost window does not have an |
| 283 // associated aura::Window. |
| 284 aura::Window* FindTopmostLocalProcessWindowWithIgnore( |
| 285 int screen_x, |
| 286 int screen_y, |
| 287 aura::Window* ignore_window) { |
| 288 std::set<aura::Window*> ignore; |
| 289 ignore.insert(ignore_window); |
| 290 X11TopmostWindowFinder finder; |
| 291 return finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y), |
| 292 ignore); |
| 293 } |
| 294 |
| 295 virtual void SetUp() OVERRIDE { |
| 296 // Make X11 synchronous for our display connection. This does force the |
| 297 // window manager to behave synchronously. |
| 298 XSynchronize(xdisplay(), True); |
| 299 ViewsTestBase::SetUp(); |
| 300 |
| 301 // Ensure that the X11DesktopHandler exists. The X11DesktopHandler is |
| 302 // necessary to properly track menu windows. |
| 303 X11DesktopHandler::get(); |
| 304 } |
| 305 |
| 306 virtual void TearDown() OVERRIDE { |
| 307 ViewsTestBase::TearDown(); |
| 308 XSynchronize(xdisplay(), False); |
| 309 } |
| 310 |
| 311 private: |
| 312 DISALLOW_COPY_AND_ASSIGN(X11TopmostWindowFinderTest); |
| 313 }; |
| 314 |
| 315 TEST_F(X11TopmostWindowFinderTest, Basic) { |
| 316 // Avoid positioning test windows at 0x0 because window managers often have a |
| 317 // panel/launcher along one of the screen edges and do not allow windows to |
| 318 // position themselves to overlap the panel/launcher. |
| 319 scoped_ptr<Widget> widget1( |
| 320 CreateAndShowWidget(gfx::Rect(100, 100, 200, 100))); |
| 321 aura::Window* window1 = widget1->GetNativeWindow(); |
| 322 XID xid1 = window1->GetHost()->GetAcceleratedWidget(); |
| 323 |
| 324 XID xid2 = CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200)); |
| 325 |
| 326 scoped_ptr<Widget> widget3( |
| 327 CreateAndShowWidget(gfx::Rect(100, 190, 200, 110))); |
| 328 aura::Window* window3 = widget3->GetNativeWindow(); |
| 329 XID xid3 = window3->GetHost()->GetAcceleratedWidget(); |
| 330 |
| 331 XID xids[] = { xid1, xid2, xid3 }; |
| 332 StackingClientListWaiter waiter(xids, arraysize(xids)); |
| 333 waiter.Wait(); |
| 334 |
| 335 EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150)); |
| 336 EXPECT_EQ(window1, FindTopmostLocalProcessWindowAt(150, 150)); |
| 337 |
| 338 EXPECT_EQ(xid2, FindTopmostXWindowAt(250, 150)); |
| 339 EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(250, 150)); |
| 340 |
| 341 EXPECT_EQ(xid3, FindTopmostXWindowAt(250, 250)); |
| 342 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(250, 250)); |
| 343 |
| 344 EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 250)); |
| 345 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 250)); |
| 346 |
| 347 EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 195)); |
| 348 EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 195)); |
| 349 |
| 350 EXPECT_NE(xid1, FindTopmostXWindowAt(1000, 1000)); |
| 351 EXPECT_NE(xid2, FindTopmostXWindowAt(1000, 1000)); |
| 352 EXPECT_EQ(NULL, FindTopmostLocalProcessWindowAt(1000, 1000)); |
| 353 |
| 354 EXPECT_EQ(window1, |
| 355 FindTopmostLocalProcessWindowWithIgnore(150, 150, window3)); |
| 356 EXPECT_EQ(NULL, |
| 357 FindTopmostLocalProcessWindowWithIgnore(250, 250, window3)); |
| 358 EXPECT_EQ(NULL, |
| 359 FindTopmostLocalProcessWindowWithIgnore(150, 250, window3)); |
| 360 EXPECT_EQ(window1, |
| 361 FindTopmostLocalProcessWindowWithIgnore(150, 195, window3)); |
| 362 |
| 363 XDestroyWindow(xdisplay(), xid2); |
| 364 } |
| 365 |
| 366 // Test that the minimized state is properly handled. |
| 367 TEST_F(X11TopmostWindowFinderTest, Minimized) { |
| 368 XID xid = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100)); |
| 369 |
| 370 XID xids[] = { xid }; |
| 371 StackingClientListWaiter stack_waiter(xids, arraysize(xids)); |
| 372 stack_waiter.Wait(); |
| 373 |
| 374 EXPECT_EQ(xid, FindTopmostXWindowAt(150, 150)); |
| 375 { |
| 376 MinimizeWaiter minimize_waiter(xid); |
| 377 XIconifyWindow(xdisplay(), xid, 0); |
| 378 minimize_waiter.Wait(); |
| 379 } |
| 380 EXPECT_NE(xid, FindTopmostXWindowAt(150, 150)); |
| 381 |
| 382 XDestroyWindow(xdisplay(), xid); |
| 383 } |
| 384 |
| 385 // Test that non-rectangular windows are properly handled. |
| 386 TEST_F(X11TopmostWindowFinderTest, NonRectangular) { |
| 387 if (!ui::IsShapeExtensionAvailable()) |
| 388 return; |
| 389 |
| 390 scoped_ptr<Widget> widget( |
| 391 CreateAndShowWidget(gfx::Rect(100, 100, 100, 100))); |
| 392 XID xid = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); |
| 393 |
| 394 SkRegion* region = new SkRegion; |
| 395 region->op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op); |
| 396 region->op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op); |
| 397 // Widget takes ownership of |region|. |
| 398 widget->SetShape(region); |
| 399 |
| 400 XID xids[] = { xid }; |
| 401 StackingClientListWaiter stack_waiter(xids, arraysize(xids)); |
| 402 stack_waiter.Wait(); |
| 403 |
| 404 EXPECT_EQ(xid, FindTopmostXWindowAt(120, 120)); |
| 405 EXPECT_NE(xid, FindTopmostXWindowAt(105, 105)); |
| 406 } |
| 407 |
| 408 // Test that the TopmostWindowFinder finds windows which belong to menus |
| 409 // (which may or may not belong to Chrome). |
| 410 TEST_F(X11TopmostWindowFinderTest, Menu) { |
| 411 XID xid = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100)); |
| 412 |
| 413 XID root = DefaultRootWindow(xdisplay()); |
| 414 XSetWindowAttributes swa; |
| 415 swa.override_redirect = True; |
| 416 XID menu_xid = XCreateWindow(xdisplay(), |
| 417 root, |
| 418 0, 0, 1, 1, |
| 419 0, // border width |
| 420 CopyFromParent, // depth |
| 421 InputOutput, |
| 422 CopyFromParent, // visual |
| 423 CWOverrideRedirect, |
| 424 &swa); |
| 425 { |
| 426 PropertyChangeWaiter property_waiter(menu_xid, "_NET_WM_WINDOW_TYPE"); |
| 427 ui::SetAtomProperty(menu_xid, |
| 428 "_NET_WM_WINDOW_TYPE", |
| 429 "ATOM", |
| 430 GetAtom("_NET_WM_WINDOW_TYPE_MENU")); |
| 431 property_waiter.Wait(); |
| 432 } |
| 433 ui::SetUseOSWindowFrame(menu_xid, false); |
| 434 ShowAndSetXIDBounds(menu_xid, gfx::Rect(140, 110, 100, 100)); |
| 435 |
| 436 // |menu_xid| is never added to _NET_CLIENT_LIST_STACKING. |
| 437 XID xids[] = { xid }; |
| 438 StackingClientListWaiter stack_waiter(xids, arraysize(xids)); |
| 439 stack_waiter.Wait(); |
| 440 |
| 441 EXPECT_EQ(menu_xid, FindTopmostXWindowAt(150, 120)); |
| 442 EXPECT_EQ(menu_xid, FindTopmostXWindowAt(210, 120)); |
| 443 |
| 444 XDestroyWindow(xdisplay(), xid); |
| 445 XDestroyWindow(xdisplay(), menu_xid); |
| 446 } |
| 447 |
| 448 } // namespace views |
OLD | NEW |