desktop_window_tree_host_x11_unittest.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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 <vector> 6 7#include <X11/extensions/shape.h> 8#include <X11/Xlib.h> 9 10// Get rid of X11 macros which conflict with gtest. 11#undef Bool 12#undef None 13 14#include "base/memory/scoped_ptr.h" 15#include "ui/aura/window.h" 16#include "ui/aura/window_tree_host.h" 17#include "ui/base/hit_test.h" 18#include "ui/base/x/x11_util.h" 19#include "ui/events/platform/x11/x11_event_source.h" 20#include "ui/gfx/path.h" 21#include "ui/gfx/point.h" 22#include "ui/gfx/rect.h" 23#include "ui/gfx/x/x11_atom_cache.h" 24#include "ui/views/test/views_test_base.h" 25#include "ui/views/test/x11_property_change_waiter.h" 26#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" 27#include "ui/views/widget/widget_delegate.h" 28#include "ui/views/window/non_client_view.h" 29 30namespace views { 31 32namespace { 33 34// Blocks till the window state hint, |hint|, is set or unset. 35class WMStateWaiter : public X11PropertyChangeWaiter { 36 public: 37 WMStateWaiter(XID window, 38 const char* hint, 39 bool wait_till_set) 40 : X11PropertyChangeWaiter(window, "_NET_WM_STATE"), 41 hint_(hint), 42 wait_till_set_(wait_till_set) { 43 44 const char* kAtomsToCache[] = { 45 hint, 46 NULL 47 }; 48 atom_cache_.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache)); 49 } 50 51 virtual ~WMStateWaiter() { 52 } 53 54 private: 55 // X11PropertyChangeWaiter: 56 virtual bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) OVERRIDE { 57 std::vector<Atom> hints; 58 if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &hints)) { 59 std::vector<Atom>::iterator it = std::find( 60 hints.begin(), 61 hints.end(), 62 atom_cache_->GetAtom(hint_)); 63 bool hint_set = (it != hints.end()); 64 return hint_set != wait_till_set_; 65 } 66 return true; 67 } 68 69 scoped_ptr<ui::X11AtomCache> atom_cache_; 70 71 // The name of the hint to wait to get set or unset. 72 const char* hint_; 73 74 // Whether we are waiting for |hint| to be set or unset. 75 bool wait_till_set_; 76 77 DISALLOW_COPY_AND_ASSIGN(WMStateWaiter); 78}; 79 80// A NonClientFrameView with a window mask with the bottom right corner cut out. 81class ShapedNonClientFrameView : public NonClientFrameView { 82 public: 83 explicit ShapedNonClientFrameView() { 84 } 85 86 virtual ~ShapedNonClientFrameView() { 87 } 88 89 // NonClientFrameView: 90 virtual gfx::Rect GetBoundsForClientView() const OVERRIDE { 91 return bounds(); 92 } 93 virtual gfx::Rect GetWindowBoundsForClientBounds( 94 const gfx::Rect& client_bounds) const OVERRIDE { 95 return client_bounds; 96 } 97 virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE { 98 return HTNOWHERE; 99 } 100 virtual void GetWindowMask(const gfx::Size& size, 101 gfx::Path* window_mask) OVERRIDE { 102 int right = size.width(); 103 int bottom = size.height(); 104 105 window_mask->moveTo(0, 0); 106 window_mask->lineTo(0, bottom); 107 window_mask->lineTo(right, bottom); 108 window_mask->lineTo(right, 10); 109 window_mask->lineTo(right - 10, 10); 110 window_mask->lineTo(right - 10, 0); 111 window_mask->close(); 112 } 113 virtual void ResetWindowControls() OVERRIDE { 114 } 115 virtual void UpdateWindowIcon() OVERRIDE { 116 } 117 virtual void UpdateWindowTitle() OVERRIDE { 118 } 119 120 private: 121 DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView); 122}; 123 124class ShapedWidgetDelegate : public WidgetDelegateView { 125 public: 126 ShapedWidgetDelegate() { 127 } 128 129 virtual ~ShapedWidgetDelegate() { 130 } 131 132 // WidgetDelegateView: 133 virtual NonClientFrameView* CreateNonClientFrameView( 134 Widget* widget) OVERRIDE { 135 return new ShapedNonClientFrameView; 136 } 137 138 private: 139 DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate); 140}; 141 142// Creates a widget of size 100x100. 143scoped_ptr<Widget> CreateWidget(WidgetDelegate* delegate) { 144 scoped_ptr<Widget> widget(new Widget); 145 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW); 146 params.delegate = delegate; 147 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 148 params.remove_standard_frame = true; 149 params.native_widget = new DesktopNativeWidgetAura(widget.get()); 150 params.bounds = gfx::Rect(100, 100, 100, 100); 151 widget->Init(params); 152 return widget.Pass(); 153} 154 155// Returns the list of rectangles which describe |xid|'s bounding region via the 156// X shape extension. 157std::vector<gfx::Rect> GetShapeRects(XID xid) { 158 int dummy; 159 int shape_rects_size; 160 XRectangle* shape_rects = XShapeGetRectangles(gfx::GetXDisplay(), 161 xid, 162 ShapeBounding, 163 &shape_rects_size, 164 &dummy); 165 166 std::vector<gfx::Rect> shape_vector; 167 for (int i = 0; i < shape_rects_size; ++i) { 168 shape_vector.push_back(gfx::Rect( 169 shape_rects[i].x, 170 shape_rects[i].y, 171 shape_rects[i].width, 172 shape_rects[i].height)); 173 } 174 XFree(shape_rects); 175 return shape_vector; 176} 177 178// Returns true if one of |rects| contains point (x,y). 179bool ShapeRectContainsPoint(const std::vector<gfx::Rect>& shape_rects, 180 int x, 181 int y) { 182 gfx::Point point(x, y); 183 for (size_t i = 0; i < shape_rects.size(); ++i) { 184 if (shape_rects[i].Contains(point)) 185 return true; 186 } 187 return false; 188} 189 190} // namespace 191 192class DesktopWindowTreeHostX11Test : public ViewsTestBase { 193 public: 194 DesktopWindowTreeHostX11Test() { 195 } 196 virtual ~DesktopWindowTreeHostX11Test() { 197 } 198 199 virtual void SetUp() OVERRIDE { 200 ViewsTestBase::SetUp(); 201 202 // Make X11 synchronous for our display connection. This does not force the 203 // window manager to behave synchronously. 204 XSynchronize(gfx::GetXDisplay(), True); 205 } 206 207 virtual void TearDown() OVERRIDE { 208 XSynchronize(gfx::GetXDisplay(), False); 209 ViewsTestBase::TearDown(); 210 } 211 212 private: 213 DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11Test); 214}; 215 216// Tests that the shape is properly set on the x window. 217TEST_F(DesktopWindowTreeHostX11Test, Shape) { 218 if (!ui::IsShapeExtensionAvailable()) 219 return; 220 221 // 1) Test setting the window shape via the NonClientFrameView. This technique 222 // is used to get rounded corners on Chrome windows when not using the native 223 // window frame. 224 scoped_ptr<Widget> widget1 = CreateWidget(new ShapedWidgetDelegate()); 225 widget1->Show(); 226 ui::X11EventSource::GetInstance()->DispatchXEvents(); 227 228 XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); 229 std::vector<gfx::Rect> shape_rects = GetShapeRects(xid1); 230 ASSERT_FALSE(shape_rects.empty()); 231 232 // The widget was supposed to be 100x100, but the WM might have ignored this 233 // suggestion. 234 int widget_width = widget1->GetWindowBoundsInScreen().width(); 235 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 15, 5)); 236 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 5)); 237 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 15)); 238 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width + 5, 15)); 239 240 // Changing widget's size should update the shape. 241 widget1->SetBounds(gfx::Rect(100, 100, 200, 200)); 242 ui::X11EventSource::GetInstance()->DispatchXEvents(); 243 244 if (widget1->GetWindowBoundsInScreen().width() == 200) { 245 shape_rects = GetShapeRects(xid1); 246 ASSERT_FALSE(shape_rects.empty()); 247 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 85, 5)); 248 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 5)); 249 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 185, 5)); 250 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 195, 5)); 251 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 195, 15)); 252 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 205, 15)); 253 } 254 255 if (ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"))) { 256 // The shape should be changed to a rectangle which fills the entire screen 257 // when |widget1| is maximized. 258 { 259 WMStateWaiter waiter(xid1, "_NET_WM_STATE_MAXIMIZED_VERT", true); 260 widget1->Maximize(); 261 waiter.Wait(); 262 } 263 264 // xvfb does not support Xrandr so we cannot check the maximized window's 265 // bounds. 266 gfx::Rect maximized_bounds; 267 ui::GetWindowRect(xid1, &maximized_bounds); 268 269 shape_rects = GetShapeRects(xid1); 270 ASSERT_FALSE(shape_rects.empty()); 271 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 272 maximized_bounds.width() - 1, 273 5)); 274 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 275 maximized_bounds.width() - 1, 276 15)); 277 } 278 279 // 2) Test setting the window shape via Widget::SetShape(). 280 gfx::Path shape2; 281 shape2.moveTo(10, 0); 282 shape2.lineTo(10, 10); 283 shape2.lineTo(0, 10); 284 shape2.lineTo(0, 100); 285 shape2.lineTo(100, 100); 286 shape2.lineTo(100, 0); 287 shape2.close(); 288 289 scoped_ptr<Widget> widget2(CreateWidget(NULL)); 290 widget2->Show(); 291 widget2->SetShape(shape2.CreateNativeRegion()); 292 ui::X11EventSource::GetInstance()->DispatchXEvents(); 293 294 XID xid2 = widget2->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); 295 shape_rects = GetShapeRects(xid2); 296 ASSERT_FALSE(shape_rects.empty()); 297 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5)); 298 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5)); 299 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15)); 300 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15)); 301 302 // Changing the widget's size should not affect the shape. 303 widget2->SetBounds(gfx::Rect(100, 100, 200, 200)); 304 shape_rects = GetShapeRects(xid2); 305 ASSERT_FALSE(shape_rects.empty()); 306 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5)); 307 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5)); 308 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15)); 309 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15)); 310} 311 312// Test that the widget ignores changes in fullscreen state initiated by the 313// window manager (e.g. via a window manager accelerator key). 314TEST_F(DesktopWindowTreeHostX11Test, WindowManagerTogglesFullscreen) { 315 if (!ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_FULLSCREEN"))) 316 return; 317 318 scoped_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate()); 319 XID xid = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); 320 widget->Show(); 321 ui::X11EventSource::GetInstance()->DispatchXEvents(); 322 323 gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen(); 324 { 325 WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", true); 326 widget->SetFullscreen(true); 327 waiter.Wait(); 328 } 329 EXPECT_TRUE(widget->IsFullscreen()); 330 331 // Emulate the window manager exiting fullscreen via a window manager 332 // accelerator key. It should not affect the widget's fullscreen state. 333 { 334 const char* kAtomsToCache[] = { 335 "_NET_WM_STATE", 336 "_NET_WM_STATE_FULLSCREEN", 337 NULL 338 }; 339 Display* display = gfx::GetXDisplay(); 340 ui::X11AtomCache atom_cache(display, kAtomsToCache); 341 342 XEvent xclient; 343 memset(&xclient, 0, sizeof(xclient)); 344 xclient.type = ClientMessage; 345 xclient.xclient.window = xid; 346 xclient.xclient.message_type = atom_cache.GetAtom("_NET_WM_STATE"); 347 xclient.xclient.format = 32; 348 xclient.xclient.data.l[0] = 0; 349 xclient.xclient.data.l[1] = atom_cache.GetAtom("_NET_WM_STATE_FULLSCREEN"); 350 xclient.xclient.data.l[2] = 0; 351 xclient.xclient.data.l[3] = 1; 352 xclient.xclient.data.l[4] = 0; 353 XSendEvent(display, DefaultRootWindow(display), False, 354 SubstructureRedirectMask | SubstructureNotifyMask, 355 &xclient); 356 357 WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", false); 358 waiter.Wait(); 359 } 360 EXPECT_TRUE(widget->IsFullscreen()); 361 362 // Calling Widget::SetFullscreen(false) should clear the widget's fullscreen 363 // state and clean things up. 364 widget->SetFullscreen(false); 365 EXPECT_FALSE(widget->IsFullscreen()); 366 EXPECT_EQ(initial_bounds.ToString(), 367 widget->GetWindowBoundsInScreen().ToString()); 368} 369 370// Tests that the minimization information is propagated to the content window. 371TEST_F(DesktopWindowTreeHostX11Test, ToggleMinimizePropogateToContentWindow) { 372 Widget widget; 373 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); 374 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 375 params.native_widget = new DesktopNativeWidgetAura(&widget); 376 widget.Init(params); 377 widget.Show(); 378 ui::X11EventSource::GetInstance()->DispatchXEvents(); 379 380 XID xid = widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget(); 381 Display* display = gfx::GetXDisplay(); 382 383 // Minimize by sending _NET_WM_STATE_HIDDEN 384 { 385 const char* kAtomsToCache[] = { 386 "_NET_WM_STATE", 387 "_NET_WM_STATE_HIDDEN", 388 NULL 389 }; 390 391 ui::X11AtomCache atom_cache(display, kAtomsToCache); 392 393 std::vector< ::Atom> atom_list; 394 atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_HIDDEN")); 395 ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list); 396 397 XEvent xevent; 398 memset(&xevent, 0, sizeof(xevent)); 399 xevent.type = PropertyNotify; 400 xevent.xproperty.type = PropertyNotify; 401 xevent.xproperty.send_event = 1; 402 xevent.xproperty.display = display; 403 xevent.xproperty.window = xid; 404 xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE"); 405 xevent.xproperty.state = 0; 406 XSendEvent(display, DefaultRootWindow(display), False, 407 SubstructureRedirectMask | SubstructureNotifyMask, 408 &xevent); 409 410 WMStateWaiter waiter(xid, "_NET_WM_STATE_HIDDEN", true); 411 waiter.Wait(); 412 } 413 EXPECT_FALSE(widget.GetNativeWindow()->IsVisible()); 414 415 // Show from minimized by sending _NET_WM_STATE_FOCUSED 416 { 417 const char* kAtomsToCache[] = { 418 "_NET_WM_STATE", 419 "_NET_WM_STATE_FOCUSED", 420 NULL 421 }; 422 423 ui::X11AtomCache atom_cache(display, kAtomsToCache); 424 425 std::vector< ::Atom> atom_list; 426 atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_FOCUSED")); 427 ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list); 428 429 XEvent xevent; 430 memset(&xevent, 0, sizeof(xevent)); 431 xevent.type = PropertyNotify; 432 xevent.xproperty.type = PropertyNotify; 433 xevent.xproperty.send_event = 1; 434 xevent.xproperty.display = display; 435 xevent.xproperty.window = xid; 436 xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE"); 437 xevent.xproperty.state = 0; 438 XSendEvent(display, DefaultRootWindow(display), False, 439 SubstructureRedirectMask | SubstructureNotifyMask, 440 &xevent); 441 442 WMStateWaiter waiter(xid, "_NET_WM_STATE_FOCUSED", true); 443 waiter.Wait(); 444 } 445 EXPECT_TRUE(widget.GetNativeWindow()->IsVisible()); 446} 447 448} // namespace views 449