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 30 namespace views { 31 32 namespace { 33 34 // Blocks till the window state hint, |hint|, is set or unset. 35 class 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. 81 class 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 virtual void SizeConstraintsChanged() OVERRIDE { 120 } 121 122 private: 123 DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView); 124 }; 125 126 class ShapedWidgetDelegate : public WidgetDelegateView { 127 public: 128 ShapedWidgetDelegate() { 129 } 130 131 virtual ~ShapedWidgetDelegate() { 132 } 133 134 // WidgetDelegateView: 135 virtual NonClientFrameView* CreateNonClientFrameView( 136 Widget* widget) OVERRIDE { 137 return new ShapedNonClientFrameView; 138 } 139 140 private: 141 DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate); 142 }; 143 144 // Creates a widget of size 100x100. 145 scoped_ptr<Widget> CreateWidget(WidgetDelegate* delegate) { 146 scoped_ptr<Widget> widget(new Widget); 147 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW); 148 params.delegate = delegate; 149 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 150 params.remove_standard_frame = true; 151 params.native_widget = new DesktopNativeWidgetAura(widget.get()); 152 params.bounds = gfx::Rect(100, 100, 100, 100); 153 widget->Init(params); 154 return widget.Pass(); 155 } 156 157 // Returns the list of rectangles which describe |xid|'s bounding region via the 158 // X shape extension. 159 std::vector<gfx::Rect> GetShapeRects(XID xid) { 160 int dummy; 161 int shape_rects_size; 162 XRectangle* shape_rects = XShapeGetRectangles(gfx::GetXDisplay(), 163 xid, 164 ShapeBounding, 165 &shape_rects_size, 166 &dummy); 167 168 std::vector<gfx::Rect> shape_vector; 169 for (int i = 0; i < shape_rects_size; ++i) { 170 shape_vector.push_back(gfx::Rect( 171 shape_rects[i].x, 172 shape_rects[i].y, 173 shape_rects[i].width, 174 shape_rects[i].height)); 175 } 176 XFree(shape_rects); 177 return shape_vector; 178 } 179 180 // Returns true if one of |rects| contains point (x,y). 181 bool ShapeRectContainsPoint(const std::vector<gfx::Rect>& shape_rects, 182 int x, 183 int y) { 184 gfx::Point point(x, y); 185 for (size_t i = 0; i < shape_rects.size(); ++i) { 186 if (shape_rects[i].Contains(point)) 187 return true; 188 } 189 return false; 190 } 191 192 } // namespace 193 194 class DesktopWindowTreeHostX11Test : public ViewsTestBase { 195 public: 196 DesktopWindowTreeHostX11Test() { 197 } 198 virtual ~DesktopWindowTreeHostX11Test() { 199 } 200 201 virtual void SetUp() OVERRIDE { 202 ViewsTestBase::SetUp(); 203 204 // Make X11 synchronous for our display connection. This does not force the 205 // window manager to behave synchronously. 206 XSynchronize(gfx::GetXDisplay(), True); 207 } 208 209 virtual void TearDown() OVERRIDE { 210 XSynchronize(gfx::GetXDisplay(), False); 211 ViewsTestBase::TearDown(); 212 } 213 214 private: 215 DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11Test); 216 }; 217 218 // Tests that the shape is properly set on the x window. 219 TEST_F(DesktopWindowTreeHostX11Test, Shape) { 220 if (!ui::IsShapeExtensionAvailable()) 221 return; 222 223 // 1) Test setting the window shape via the NonClientFrameView. This technique 224 // is used to get rounded corners on Chrome windows when not using the native 225 // window frame. 226 scoped_ptr<Widget> widget1 = CreateWidget(new ShapedWidgetDelegate()); 227 widget1->Show(); 228 ui::X11EventSource::GetInstance()->DispatchXEvents(); 229 230 XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); 231 std::vector<gfx::Rect> shape_rects = GetShapeRects(xid1); 232 ASSERT_FALSE(shape_rects.empty()); 233 234 // The widget was supposed to be 100x100, but the WM might have ignored this 235 // suggestion. 236 int widget_width = widget1->GetWindowBoundsInScreen().width(); 237 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 15, 5)); 238 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 5)); 239 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 15)); 240 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width + 5, 15)); 241 242 // Changing widget's size should update the shape. 243 widget1->SetBounds(gfx::Rect(100, 100, 200, 200)); 244 ui::X11EventSource::GetInstance()->DispatchXEvents(); 245 246 if (widget1->GetWindowBoundsInScreen().width() == 200) { 247 shape_rects = GetShapeRects(xid1); 248 ASSERT_FALSE(shape_rects.empty()); 249 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 85, 5)); 250 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 5)); 251 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 185, 5)); 252 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 195, 5)); 253 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 195, 15)); 254 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 205, 15)); 255 } 256 257 if (ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"))) { 258 // The shape should be changed to a rectangle which fills the entire screen 259 // when |widget1| is maximized. 260 { 261 WMStateWaiter waiter(xid1, "_NET_WM_STATE_MAXIMIZED_VERT", true); 262 widget1->Maximize(); 263 waiter.Wait(); 264 } 265 266 // xvfb does not support Xrandr so we cannot check the maximized window's 267 // bounds. 268 gfx::Rect maximized_bounds; 269 ui::GetWindowRect(xid1, &maximized_bounds); 270 271 shape_rects = GetShapeRects(xid1); 272 ASSERT_FALSE(shape_rects.empty()); 273 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 274 maximized_bounds.width() - 1, 275 5)); 276 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 277 maximized_bounds.width() - 1, 278 15)); 279 } 280 281 // 2) Test setting the window shape via Widget::SetShape(). 282 gfx::Path shape2; 283 shape2.moveTo(10, 0); 284 shape2.lineTo(10, 10); 285 shape2.lineTo(0, 10); 286 shape2.lineTo(0, 100); 287 shape2.lineTo(100, 100); 288 shape2.lineTo(100, 0); 289 shape2.close(); 290 291 scoped_ptr<Widget> widget2(CreateWidget(NULL)); 292 widget2->Show(); 293 widget2->SetShape(shape2.CreateNativeRegion()); 294 ui::X11EventSource::GetInstance()->DispatchXEvents(); 295 296 XID xid2 = widget2->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); 297 shape_rects = GetShapeRects(xid2); 298 ASSERT_FALSE(shape_rects.empty()); 299 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5)); 300 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5)); 301 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15)); 302 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15)); 303 304 // Changing the widget's size should not affect the shape. 305 widget2->SetBounds(gfx::Rect(100, 100, 200, 200)); 306 shape_rects = GetShapeRects(xid2); 307 ASSERT_FALSE(shape_rects.empty()); 308 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5)); 309 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5)); 310 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15)); 311 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15)); 312 313 // Setting the shape to NULL resets the shape back to the entire 314 // window bounds. 315 widget2->SetShape(NULL); 316 shape_rects = GetShapeRects(xid2); 317 ASSERT_FALSE(shape_rects.empty()); 318 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 5, 5)); 319 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5)); 320 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15)); 321 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 105, 15)); 322 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 500, 500)); 323 } 324 325 // Test that the widget ignores changes in fullscreen state initiated by the 326 // window manager (e.g. via a window manager accelerator key). 327 TEST_F(DesktopWindowTreeHostX11Test, WindowManagerTogglesFullscreen) { 328 if (!ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_FULLSCREEN"))) 329 return; 330 331 scoped_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate()); 332 XID xid = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget(); 333 widget->Show(); 334 ui::X11EventSource::GetInstance()->DispatchXEvents(); 335 336 gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen(); 337 { 338 WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", true); 339 widget->SetFullscreen(true); 340 waiter.Wait(); 341 } 342 EXPECT_TRUE(widget->IsFullscreen()); 343 344 // Emulate the window manager exiting fullscreen via a window manager 345 // accelerator key. It should not affect the widget's fullscreen state. 346 { 347 const char* kAtomsToCache[] = { 348 "_NET_WM_STATE", 349 "_NET_WM_STATE_FULLSCREEN", 350 NULL 351 }; 352 Display* display = gfx::GetXDisplay(); 353 ui::X11AtomCache atom_cache(display, kAtomsToCache); 354 355 XEvent xclient; 356 memset(&xclient, 0, sizeof(xclient)); 357 xclient.type = ClientMessage; 358 xclient.xclient.window = xid; 359 xclient.xclient.message_type = atom_cache.GetAtom("_NET_WM_STATE"); 360 xclient.xclient.format = 32; 361 xclient.xclient.data.l[0] = 0; 362 xclient.xclient.data.l[1] = atom_cache.GetAtom("_NET_WM_STATE_FULLSCREEN"); 363 xclient.xclient.data.l[2] = 0; 364 xclient.xclient.data.l[3] = 1; 365 xclient.xclient.data.l[4] = 0; 366 XSendEvent(display, DefaultRootWindow(display), False, 367 SubstructureRedirectMask | SubstructureNotifyMask, 368 &xclient); 369 370 WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", false); 371 waiter.Wait(); 372 } 373 EXPECT_TRUE(widget->IsFullscreen()); 374 375 // Calling Widget::SetFullscreen(false) should clear the widget's fullscreen 376 // state and clean things up. 377 widget->SetFullscreen(false); 378 EXPECT_FALSE(widget->IsFullscreen()); 379 EXPECT_EQ(initial_bounds.ToString(), 380 widget->GetWindowBoundsInScreen().ToString()); 381 } 382 383 // Tests that the minimization information is propagated to the content window. 384 TEST_F(DesktopWindowTreeHostX11Test, ToggleMinimizePropogateToContentWindow) { 385 Widget widget; 386 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); 387 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 388 params.native_widget = new DesktopNativeWidgetAura(&widget); 389 widget.Init(params); 390 widget.Show(); 391 ui::X11EventSource::GetInstance()->DispatchXEvents(); 392 393 XID xid = widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget(); 394 Display* display = gfx::GetXDisplay(); 395 396 // Minimize by sending _NET_WM_STATE_HIDDEN 397 { 398 const char* kAtomsToCache[] = { 399 "_NET_WM_STATE", 400 "_NET_WM_STATE_HIDDEN", 401 NULL 402 }; 403 404 ui::X11AtomCache atom_cache(display, kAtomsToCache); 405 406 std::vector< ::Atom> atom_list; 407 atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_HIDDEN")); 408 ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list); 409 410 XEvent xevent; 411 memset(&xevent, 0, sizeof(xevent)); 412 xevent.type = PropertyNotify; 413 xevent.xproperty.type = PropertyNotify; 414 xevent.xproperty.send_event = 1; 415 xevent.xproperty.display = display; 416 xevent.xproperty.window = xid; 417 xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE"); 418 xevent.xproperty.state = 0; 419 XSendEvent(display, DefaultRootWindow(display), False, 420 SubstructureRedirectMask | SubstructureNotifyMask, 421 &xevent); 422 423 WMStateWaiter waiter(xid, "_NET_WM_STATE_HIDDEN", true); 424 waiter.Wait(); 425 } 426 EXPECT_FALSE(widget.GetNativeWindow()->IsVisible()); 427 428 // Show from minimized by sending _NET_WM_STATE_FOCUSED 429 { 430 const char* kAtomsToCache[] = { 431 "_NET_WM_STATE", 432 "_NET_WM_STATE_FOCUSED", 433 NULL 434 }; 435 436 ui::X11AtomCache atom_cache(display, kAtomsToCache); 437 438 std::vector< ::Atom> atom_list; 439 atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_FOCUSED")); 440 ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list); 441 442 XEvent xevent; 443 memset(&xevent, 0, sizeof(xevent)); 444 xevent.type = PropertyNotify; 445 xevent.xproperty.type = PropertyNotify; 446 xevent.xproperty.send_event = 1; 447 xevent.xproperty.display = display; 448 xevent.xproperty.window = xid; 449 xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE"); 450 xevent.xproperty.state = 0; 451 XSendEvent(display, DefaultRootWindow(display), False, 452 SubstructureRedirectMask | SubstructureNotifyMask, 453 &xevent); 454 455 WMStateWaiter waiter(xid, "_NET_WM_STATE_FOCUSED", true); 456 waiter.Wait(); 457 } 458 EXPECT_TRUE(widget.GetNativeWindow()->IsVisible()); 459 } 460 461 } // namespace views 462