blob: 8a66c6e032717e46a21ab5390aaa305c76b6565a [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <vector>
#include <X11/extensions/shape.h>
#include <X11/Xlib.h>
// Get rid of X11 macros which conflict with gtest.
#undef Bool
#undef None
#include "base/memory/scoped_ptr.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/hit_test.h"
#include "ui/base/x/x11_util.h"
#include "ui/events/platform/x11/x11_event_source.h"
#include "ui/gfx/path.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/x11_property_change_waiter.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/non_client_view.h"
namespace views {
namespace {
// Blocks till the window state hint, |hint|, is set or unset.
class WMStateWaiter : public X11PropertyChangeWaiter {
public:
WMStateWaiter(XID window,
const char* hint,
bool wait_till_set)
: X11PropertyChangeWaiter(window, "_NET_WM_STATE"),
hint_(hint),
wait_till_set_(wait_till_set) {
const char* kAtomsToCache[] = {
hint,
NULL
};
atom_cache_.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache));
}
virtual ~WMStateWaiter() {
}
private:
// X11PropertyChangeWaiter:
virtual bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) OVERRIDE {
std::vector<Atom> hints;
if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &hints)) {
std::vector<Atom>::iterator it = std::find(
hints.begin(),
hints.end(),
atom_cache_->GetAtom(hint_));
bool hint_set = (it != hints.end());
return hint_set != wait_till_set_;
}
return true;
}
scoped_ptr<ui::X11AtomCache> atom_cache_;
// The name of the hint to wait to get set or unset.
const char* hint_;
// Whether we are waiting for |hint| to be set or unset.
bool wait_till_set_;
DISALLOW_COPY_AND_ASSIGN(WMStateWaiter);
};
// A NonClientFrameView with a window mask with the bottom right corner cut out.
class ShapedNonClientFrameView : public NonClientFrameView {
public:
explicit ShapedNonClientFrameView() {
}
virtual ~ShapedNonClientFrameView() {
}
// NonClientFrameView:
virtual gfx::Rect GetBoundsForClientView() const OVERRIDE {
return bounds();
}
virtual gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const OVERRIDE {
return client_bounds;
}
virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE {
return HTNOWHERE;
}
virtual void GetWindowMask(const gfx::Size& size,
gfx::Path* window_mask) OVERRIDE {
int right = size.width();
int bottom = size.height();
window_mask->moveTo(0, 0);
window_mask->lineTo(0, bottom);
window_mask->lineTo(right, bottom);
window_mask->lineTo(right, 10);
window_mask->lineTo(right - 10, 10);
window_mask->lineTo(right - 10, 0);
window_mask->close();
}
virtual void ResetWindowControls() OVERRIDE {
}
virtual void UpdateWindowIcon() OVERRIDE {
}
virtual void UpdateWindowTitle() OVERRIDE {
}
private:
DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView);
};
class ShapedWidgetDelegate : public WidgetDelegateView {
public:
ShapedWidgetDelegate() {
}
virtual ~ShapedWidgetDelegate() {
}
// WidgetDelegateView:
virtual NonClientFrameView* CreateNonClientFrameView(
Widget* widget) OVERRIDE {
return new ShapedNonClientFrameView;
}
private:
DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate);
};
// Creates a widget of size 100x100.
scoped_ptr<Widget> CreateWidget(WidgetDelegate* delegate) {
scoped_ptr<Widget> widget(new Widget);
Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
params.delegate = delegate;
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.remove_standard_frame = true;
params.native_widget = new DesktopNativeWidgetAura(widget.get());
params.bounds = gfx::Rect(100, 100, 100, 100);
widget->Init(params);
return widget.Pass();
}
// Returns the list of rectangles which describe |xid|'s bounding region via the
// X shape extension.
std::vector<gfx::Rect> GetShapeRects(XID xid) {
int dummy;
int shape_rects_size;
XRectangle* shape_rects = XShapeGetRectangles(gfx::GetXDisplay(),
xid,
ShapeBounding,
&shape_rects_size,
&dummy);
std::vector<gfx::Rect> shape_vector;
for (int i = 0; i < shape_rects_size; ++i) {
shape_vector.push_back(gfx::Rect(
shape_rects[i].x,
shape_rects[i].y,
shape_rects[i].width,
shape_rects[i].height));
}
XFree(shape_rects);
return shape_vector;
}
// Returns true if one of |rects| contains point (x,y).
bool ShapeRectContainsPoint(const std::vector<gfx::Rect>& shape_rects,
int x,
int y) {
gfx::Point point(x, y);
for (size_t i = 0; i < shape_rects.size(); ++i) {
if (shape_rects[i].Contains(point))
return true;
}
return false;
}
} // namespace
class DesktopWindowTreeHostX11Test : public ViewsTestBase {
public:
DesktopWindowTreeHostX11Test() {
}
virtual ~DesktopWindowTreeHostX11Test() {
}
virtual void SetUp() OVERRIDE {
ViewsTestBase::SetUp();
// Make X11 synchronous for our display connection. This does not force the
// window manager to behave synchronously.
XSynchronize(gfx::GetXDisplay(), True);
}
virtual void TearDown() OVERRIDE {
XSynchronize(gfx::GetXDisplay(), False);
ViewsTestBase::TearDown();
}
private:
DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11Test);
};
// Tests that the shape is properly set on the x window.
TEST_F(DesktopWindowTreeHostX11Test, Shape) {
if (!ui::IsShapeExtensionAvailable())
return;
// 1) Test setting the window shape via the NonClientFrameView. This technique
// is used to get rounded corners on Chrome windows when not using the native
// window frame.
scoped_ptr<Widget> widget1 = CreateWidget(new ShapedWidgetDelegate());
widget1->Show();
ui::X11EventSource::GetInstance()->DispatchXEvents();
XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
std::vector<gfx::Rect> shape_rects = GetShapeRects(xid1);
ASSERT_FALSE(shape_rects.empty());
// The widget was supposed to be 100x100, but the WM might have ignored this
// suggestion.
int widget_width = widget1->GetWindowBoundsInScreen().width();
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 15, 5));
EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 5));
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 15));
EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width + 5, 15));
// Changing widget's size should update the shape.
widget1->SetBounds(gfx::Rect(100, 100, 200, 200));
ui::X11EventSource::GetInstance()->DispatchXEvents();
if (widget1->GetWindowBoundsInScreen().width() == 200) {
shape_rects = GetShapeRects(xid1);
ASSERT_FALSE(shape_rects.empty());
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 85, 5));
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 5));
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 185, 5));
EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 195, 5));
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 195, 15));
EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 205, 15));
}
if (ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"))) {
// The shape should be changed to a rectangle which fills the entire screen
// when |widget1| is maximized.
{
WMStateWaiter waiter(xid1, "_NET_WM_STATE_MAXIMIZED_VERT", true);
widget1->Maximize();
waiter.Wait();
}
// xvfb does not support Xrandr so we cannot check the maximized window's
// bounds.
gfx::Rect maximized_bounds;
ui::GetWindowRect(xid1, &maximized_bounds);
shape_rects = GetShapeRects(xid1);
ASSERT_FALSE(shape_rects.empty());
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects,
maximized_bounds.width() - 1,
5));
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects,
maximized_bounds.width() - 1,
15));
}
// 2) Test setting the window shape via Widget::SetShape().
gfx::Path shape2;
shape2.moveTo(10, 0);
shape2.lineTo(10, 10);
shape2.lineTo(0, 10);
shape2.lineTo(0, 100);
shape2.lineTo(100, 100);
shape2.lineTo(100, 0);
shape2.close();
scoped_ptr<Widget> widget2(CreateWidget(NULL));
widget2->Show();
widget2->SetShape(shape2.CreateNativeRegion());
ui::X11EventSource::GetInstance()->DispatchXEvents();
XID xid2 = widget2->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
shape_rects = GetShapeRects(xid2);
ASSERT_FALSE(shape_rects.empty());
EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5));
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15));
// Changing the widget's size should not affect the shape.
widget2->SetBounds(gfx::Rect(100, 100, 200, 200));
shape_rects = GetShapeRects(xid2);
ASSERT_FALSE(shape_rects.empty());
EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5));
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15));
}
// Test that the widget ignores changes in fullscreen state initiated by the
// window manager (e.g. via a window manager accelerator key).
TEST_F(DesktopWindowTreeHostX11Test, WindowManagerTogglesFullscreen) {
if (!ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_FULLSCREEN")))
return;
scoped_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate());
XID xid = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
widget->Show();
ui::X11EventSource::GetInstance()->DispatchXEvents();
gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen();
{
WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", true);
widget->SetFullscreen(true);
waiter.Wait();
}
EXPECT_TRUE(widget->IsFullscreen());
// Emulate the window manager exiting fullscreen via a window manager
// accelerator key. It should not affect the widget's fullscreen state.
{
const char* kAtomsToCache[] = {
"_NET_WM_STATE",
"_NET_WM_STATE_FULLSCREEN",
NULL
};
Display* display = gfx::GetXDisplay();
ui::X11AtomCache atom_cache(display, kAtomsToCache);
XEvent xclient;
memset(&xclient, 0, sizeof(xclient));
xclient.type = ClientMessage;
xclient.xclient.window = xid;
xclient.xclient.message_type = atom_cache.GetAtom("_NET_WM_STATE");
xclient.xclient.format = 32;
xclient.xclient.data.l[0] = 0;
xclient.xclient.data.l[1] = atom_cache.GetAtom("_NET_WM_STATE_FULLSCREEN");
xclient.xclient.data.l[2] = 0;
xclient.xclient.data.l[3] = 1;
xclient.xclient.data.l[4] = 0;
XSendEvent(display, DefaultRootWindow(display), False,
SubstructureRedirectMask | SubstructureNotifyMask,
&xclient);
WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", false);
waiter.Wait();
}
EXPECT_TRUE(widget->IsFullscreen());
// Calling Widget::SetFullscreen(false) should clear the widget's fullscreen
// state and clean things up.
widget->SetFullscreen(false);
EXPECT_FALSE(widget->IsFullscreen());
EXPECT_EQ(initial_bounds.ToString(),
widget->GetWindowBoundsInScreen().ToString());
}
// Tests that the minimization information is propagated to the content window.
TEST_F(DesktopWindowTreeHostX11Test, ToggleMinimizePropogateToContentWindow) {
Widget widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.native_widget = new DesktopNativeWidgetAura(&widget);
widget.Init(params);
widget.Show();
ui::X11EventSource::GetInstance()->DispatchXEvents();
XID xid = widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget();
Display* display = gfx::GetXDisplay();
// Minimize by sending _NET_WM_STATE_HIDDEN
{
const char* kAtomsToCache[] = {
"_NET_WM_STATE",
"_NET_WM_STATE_HIDDEN",
NULL
};
ui::X11AtomCache atom_cache(display, kAtomsToCache);
std::vector< ::Atom> atom_list;
atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_HIDDEN"));
ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list);
XEvent xevent;
memset(&xevent, 0, sizeof(xevent));
xevent.type = PropertyNotify;
xevent.xproperty.type = PropertyNotify;
xevent.xproperty.send_event = 1;
xevent.xproperty.display = display;
xevent.xproperty.window = xid;
xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE");
xevent.xproperty.state = 0;
XSendEvent(display, DefaultRootWindow(display), False,
SubstructureRedirectMask | SubstructureNotifyMask,
&xevent);
WMStateWaiter waiter(xid, "_NET_WM_STATE_HIDDEN", true);
waiter.Wait();
}
EXPECT_FALSE(widget.GetNativeWindow()->IsVisible());
// Show from minimized by sending _NET_WM_STATE_FOCUSED
{
const char* kAtomsToCache[] = {
"_NET_WM_STATE",
"_NET_WM_STATE_FOCUSED",
NULL
};
ui::X11AtomCache atom_cache(display, kAtomsToCache);
std::vector< ::Atom> atom_list;
atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_FOCUSED"));
ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list);
XEvent xevent;
memset(&xevent, 0, sizeof(xevent));
xevent.type = PropertyNotify;
xevent.xproperty.type = PropertyNotify;
xevent.xproperty.send_event = 1;
xevent.xproperty.display = display;
xevent.xproperty.window = xid;
xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE");
xevent.xproperty.state = 0;
XSendEvent(display, DefaultRootWindow(display), False,
SubstructureRedirectMask | SubstructureNotifyMask,
&xevent);
WMStateWaiter waiter(xid, "_NET_WM_STATE_FOCUSED", true);
waiter.Wait();
}
EXPECT_TRUE(widget.GetNativeWindow()->IsVisible());
}
} // namespace views