blob: 8a986d7bc9a98fbb032f4eb63e04ce7315235e1a [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 "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.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 "base/path_service.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_paths.h"
#include "ui/base/x/x11_util.h"
#include "ui/events/event_handler.h"
#include "ui/events/platform/x11/x11_event_source.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gl/gl_surface.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"
namespace views {
namespace {
// Blocks till |window| gets activated.
class ActivationWaiter : public X11PropertyChangeWaiter {
public:
explicit ActivationWaiter(XID window)
: X11PropertyChangeWaiter(ui::GetX11RootWindow(), "_NET_ACTIVE_WINDOW"),
window_(window) {
}
virtual ~ActivationWaiter() {
}
private:
// X11PropertyChangeWaiter:
virtual bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) OVERRIDE {
XID xid = 0;
ui::GetXIDProperty(ui::GetX11RootWindow(), "_NET_ACTIVE_WINDOW", &xid);
return xid != window_;
}
XID window_;
DISALLOW_COPY_AND_ASSIGN(ActivationWaiter);
};
// An event handler which counts the number of mouse moves it has seen.
class MouseMoveCounterHandler : public ui::EventHandler {
public:
MouseMoveCounterHandler() : count_(0) {
}
virtual ~MouseMoveCounterHandler() {
}
// ui::EventHandler:
virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
if (event->type() == ui::ET_MOUSE_MOVED)
++count_;
}
int num_mouse_moves() const {
return count_;
}
private:
int count_;
DISALLOW_COPY_AND_ASSIGN(MouseMoveCounterHandler);
};
// Creates a widget with the given bounds.
scoped_ptr<Widget> CreateWidget(const gfx::Rect& bounds) {
scoped_ptr<Widget> widget(new Widget);
Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.remove_standard_frame = true;
params.native_widget = new DesktopNativeWidgetAura(widget.get());
params.bounds = bounds;
widget->Init(params);
return widget.Pass();
}
// Dispatches an XMotionEvent targeted at |host|'s X window with location
// |point_in_screen|.
void DispatchMouseMotionEvent(DesktopWindowTreeHostX11* desktop_host,
const gfx::Point& point_in_screen) {
aura::WindowTreeHost* host = static_cast<aura::WindowTreeHost*>(desktop_host);
gfx::Rect bounds_in_screen = desktop_host->window()->GetBoundsInScreen();
Display* display = gfx::GetXDisplay();
XEvent xev;
xev.xmotion.type = MotionNotify;
xev.xmotion.display = display;
xev.xmotion.window = host->GetAcceleratedWidget();
xev.xmotion.root = DefaultRootWindow(display);
xev.xmotion.subwindow = 0;
xev.xmotion.time = CurrentTime;
xev.xmotion.x = point_in_screen.x() - bounds_in_screen.x();
xev.xmotion.y = point_in_screen.y() - bounds_in_screen.y();
xev.xmotion.x_root = point_in_screen.x();
xev.xmotion.y_root = point_in_screen.y();
xev.xmotion.state = 0;
xev.xmotion.is_hint = NotifyNormal;
xev.xmotion.same_screen = True;
static_cast<ui::PlatformEventDispatcher*>(desktop_host)->DispatchEvent(&xev);
}
} // namespace
class DesktopWindowTreeHostX11Test : public ViewsTestBase {
public:
DesktopWindowTreeHostX11Test() {
}
virtual ~DesktopWindowTreeHostX11Test() {
}
static void SetUpTestCase() {
gfx::GLSurface::InitializeOneOffForTests();
ui::RegisterPathProvider();
base::FilePath ui_test_pak_path;
ASSERT_TRUE(PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path));
ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path);
}
// testing::Test
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);
};
// Test that calling Widget::Deactivate() sets the widget as inactive wrt to
// Chrome even if it not possible to deactivate the window wrt to the x server.
// This behavior is required by several interactive_ui_tests.
TEST_F(DesktopWindowTreeHostX11Test, Deactivate) {
scoped_ptr<Widget> widget(CreateWidget(gfx::Rect(100, 100, 100, 100)));
ActivationWaiter waiter(
widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
widget->Show();
widget->Activate();
waiter.Wait();
widget->Deactivate();
// Regardless of whether |widget|'s X11 window eventually gets deactivated,
// |widget|'s "active" state should change.
EXPECT_FALSE(widget->IsActive());
// |widget|'s X11 window should still be active. Reactivating |widget| should
// update the widget's "active" state.
// Note: Activating a widget whose X11 window is not active does not
// synchronously update the widget's "active" state.
widget->Activate();
EXPECT_TRUE(widget->IsActive());
}
// Chrome attempts to make mouse capture look synchronous on Linux. Test that
// Chrome synchronously switches the window that mouse events are forwarded to
// when capture is changed.
TEST_F(DesktopWindowTreeHostX11Test, CaptureEventForwarding) {
scoped_ptr<Widget> widget1(CreateWidget(gfx::Rect(100, 100, 100, 100)));
aura::Window* window1 = widget1->GetNativeWindow();
DesktopWindowTreeHostX11* host1 =
static_cast<DesktopWindowTreeHostX11*>(window1->GetHost());
widget1->Show();
scoped_ptr<Widget> widget2(CreateWidget(gfx::Rect(200, 100, 100, 100)));
aura::Window* window2 = widget2->GetNativeWindow();
DesktopWindowTreeHostX11* host2 =
static_cast<DesktopWindowTreeHostX11*>(window2->GetHost());
widget2->Show();
MouseMoveCounterHandler recorder1;
window1->AddPreTargetHandler(&recorder1);
MouseMoveCounterHandler recorder2;
window2->AddPreTargetHandler(&recorder2);
// Move the mouse to the center of |widget2|.
gfx::Point point_in_screen = widget2->GetWindowBoundsInScreen().CenterPoint();
DispatchMouseMotionEvent(host2, point_in_screen);
EXPECT_EQ(0, recorder1.num_mouse_moves());
EXPECT_EQ(1, recorder2.num_mouse_moves());
EXPECT_EQ(point_in_screen.ToString(),
aura::Env::GetInstance()->last_mouse_location().ToString());
// Set capture to |widget1|. Because DesktopWindowTreeHostX11 calls
// XGrabPointer() with owner == False, the X server sends events to |widget2|
// as long as the mouse is hovered over |widget2|. Verify that Chrome
// redirects mouse events to |widget1|.
widget1->SetCapture(NULL);
point_in_screen += gfx::Vector2d(1, 0);
DispatchMouseMotionEvent(host2, point_in_screen);
EXPECT_EQ(1, recorder1.num_mouse_moves());
EXPECT_EQ(1, recorder2.num_mouse_moves());
// If the event's location was correctly changed to be relative to |widget1|,
// Env's last mouse position will be correct.
EXPECT_EQ(point_in_screen.ToString(),
aura::Env::GetInstance()->last_mouse_location().ToString());
// Set capture to |widget2|. Subsequent events sent to |widget2| should not be
// forwarded.
widget2->SetCapture(NULL);
point_in_screen += gfx::Vector2d(1, 0);
DispatchMouseMotionEvent(host2, point_in_screen);
EXPECT_EQ(1, recorder1.num_mouse_moves());
EXPECT_EQ(2, recorder2.num_mouse_moves());
EXPECT_EQ(point_in_screen.ToString(),
aura::Env::GetInstance()->last_mouse_location().ToString());
// If the mouse is not hovered over |widget1| or |widget2|, the X server will
// send events to the window which has capture. Test the mouse events sent to
// |widget2| are not forwarded.
DispatchMouseMotionEvent(host2, point_in_screen);
EXPECT_EQ(1, recorder1.num_mouse_moves());
EXPECT_EQ(3, recorder2.num_mouse_moves());
EXPECT_EQ(point_in_screen.ToString(),
aura::Env::GetInstance()->last_mouse_location().ToString());
// Release capture. Test that when capture is released, mouse events are no
// longer forwarded to other widgets.
widget2->ReleaseCapture();
point_in_screen = widget1->GetWindowBoundsInScreen().CenterPoint();
DispatchMouseMotionEvent(host1, point_in_screen);
EXPECT_EQ(2, recorder1.num_mouse_moves());
EXPECT_EQ(3, recorder2.num_mouse_moves());
EXPECT_EQ(point_in_screen.ToString(),
aura::Env::GetInstance()->last_mouse_location().ToString());
// Cleanup
window1->RemovePreTargetHandler(&recorder1);
window2->RemovePreTargetHandler(&recorder2);
}
} // namespace views