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