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 "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
6
7#include <X11/Xlib.h>
8
9// Get rid of X11 macros which conflict with gtest.
10#undef Bool
11#undef None
12
13#include "base/memory/scoped_ptr.h"
14#include "base/path_service.h"
15#include "ui/aura/env.h"
16#include "ui/aura/window.h"
17#include "ui/aura/window_tree_host.h"
18#include "ui/base/resource/resource_bundle.h"
19#include "ui/base/ui_base_paths.h"
20#include "ui/base/x/x11_util.h"
21#include "ui/events/event_handler.h"
22#include "ui/events/platform/x11/x11_event_source.h"
23#include "ui/gfx/rect.h"
24#include "ui/gfx/x/x11_atom_cache.h"
25#include "ui/gl/gl_surface.h"
26#include "ui/views/test/views_test_base.h"
27#include "ui/views/test/x11_property_change_waiter.h"
28#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
29
30namespace views {
31
32namespace {
33
34// Blocks till |window| gets activated.
35class ActivationWaiter : public X11PropertyChangeWaiter {
36 public:
37  explicit ActivationWaiter(XID window)
38      : X11PropertyChangeWaiter(ui::GetX11RootWindow(), "_NET_ACTIVE_WINDOW"),
39        window_(window) {
40  }
41
42  virtual ~ActivationWaiter() {
43  }
44
45 private:
46  // X11PropertyChangeWaiter:
47  virtual bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) OVERRIDE {
48    XID xid = 0;
49    ui::GetXIDProperty(ui::GetX11RootWindow(), "_NET_ACTIVE_WINDOW", &xid);
50    return xid != window_;
51  }
52
53  XID window_;
54
55  DISALLOW_COPY_AND_ASSIGN(ActivationWaiter);
56};
57
58// An event handler which counts the number of mouse moves it has seen.
59class MouseMoveCounterHandler : public ui::EventHandler {
60 public:
61  MouseMoveCounterHandler() : count_(0) {
62  }
63  virtual ~MouseMoveCounterHandler() {
64  }
65
66  // ui::EventHandler:
67  virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
68    if (event->type() == ui::ET_MOUSE_MOVED)
69      ++count_;
70  }
71
72  int num_mouse_moves() const {
73    return count_;
74  }
75
76 private:
77  int count_;
78
79  DISALLOW_COPY_AND_ASSIGN(MouseMoveCounterHandler);
80};
81
82// Creates a widget with the given bounds.
83scoped_ptr<Widget> CreateWidget(const gfx::Rect& bounds) {
84  scoped_ptr<Widget> widget(new Widget);
85  Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
86  params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
87  params.remove_standard_frame = true;
88  params.native_widget = new DesktopNativeWidgetAura(widget.get());
89  params.bounds = bounds;
90  widget->Init(params);
91  return widget.Pass();
92}
93
94// Dispatches an XMotionEvent targeted at |host|'s X window with location
95// |point_in_screen|.
96void DispatchMouseMotionEvent(DesktopWindowTreeHostX11* desktop_host,
97                              const gfx::Point& point_in_screen) {
98  aura::WindowTreeHost* host = static_cast<aura::WindowTreeHost*>(desktop_host);
99  gfx::Rect bounds_in_screen = desktop_host->window()->GetBoundsInScreen();
100
101  Display* display = gfx::GetXDisplay();
102  XEvent xev;
103  xev.xmotion.type = MotionNotify;
104  xev.xmotion.display = display;
105  xev.xmotion.window = host->GetAcceleratedWidget();
106  xev.xmotion.root = DefaultRootWindow(display);
107  xev.xmotion.subwindow = 0;
108  xev.xmotion.time = CurrentTime;
109  xev.xmotion.x = point_in_screen.x() - bounds_in_screen.x();
110  xev.xmotion.y = point_in_screen.y() - bounds_in_screen.y();
111  xev.xmotion.x_root = point_in_screen.x();
112  xev.xmotion.y_root = point_in_screen.y();
113  xev.xmotion.state = 0;
114  xev.xmotion.is_hint = NotifyNormal;
115  xev.xmotion.same_screen = True;
116
117  static_cast<ui::PlatformEventDispatcher*>(desktop_host)->DispatchEvent(&xev);
118}
119
120}  // namespace
121
122class DesktopWindowTreeHostX11Test : public ViewsTestBase {
123 public:
124  DesktopWindowTreeHostX11Test() {
125  }
126  virtual ~DesktopWindowTreeHostX11Test() {
127  }
128
129  static void SetUpTestCase() {
130    gfx::GLSurface::InitializeOneOffForTests();
131    ui::RegisterPathProvider();
132    base::FilePath ui_test_pak_path;
133    ASSERT_TRUE(PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path));
134    ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path);
135  }
136
137  // testing::Test
138  virtual void SetUp() OVERRIDE {
139    ViewsTestBase::SetUp();
140
141    // Make X11 synchronous for our display connection. This does not force the
142    // window manager to behave synchronously.
143    XSynchronize(gfx::GetXDisplay(), True);
144  }
145
146  virtual void TearDown() OVERRIDE {
147    XSynchronize(gfx::GetXDisplay(), False);
148    ViewsTestBase::TearDown();
149  }
150
151 private:
152  DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11Test);
153};
154
155// Test that calling Widget::Deactivate() sets the widget as inactive wrt to
156// Chrome even if it not possible to deactivate the window wrt to the x server.
157// This behavior is required by several interactive_ui_tests.
158TEST_F(DesktopWindowTreeHostX11Test, Deactivate) {
159  scoped_ptr<Widget> widget(CreateWidget(gfx::Rect(100, 100, 100, 100)));
160
161  ActivationWaiter waiter(
162      widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
163  widget->Show();
164  widget->Activate();
165  waiter.Wait();
166
167  widget->Deactivate();
168  // Regardless of whether |widget|'s X11 window eventually gets deactivated,
169  // |widget|'s "active" state should change.
170  EXPECT_FALSE(widget->IsActive());
171
172  // |widget|'s X11 window should still be active. Reactivating |widget| should
173  // update the widget's "active" state.
174  // Note: Activating a widget whose X11 window is not active does not
175  // synchronously update the widget's "active" state.
176  widget->Activate();
177  EXPECT_TRUE(widget->IsActive());
178}
179
180// Chrome attempts to make mouse capture look synchronous on Linux. Test that
181// Chrome synchronously switches the window that mouse events are forwarded to
182// when capture is changed.
183TEST_F(DesktopWindowTreeHostX11Test, CaptureEventForwarding) {
184  scoped_ptr<Widget> widget1(CreateWidget(gfx::Rect(100, 100, 100, 100)));
185  aura::Window* window1 = widget1->GetNativeWindow();
186  DesktopWindowTreeHostX11* host1 =
187      static_cast<DesktopWindowTreeHostX11*>(window1->GetHost());
188  widget1->Show();
189
190  scoped_ptr<Widget> widget2(CreateWidget(gfx::Rect(200, 100, 100, 100)));
191  aura::Window* window2 = widget2->GetNativeWindow();
192  DesktopWindowTreeHostX11* host2 =
193      static_cast<DesktopWindowTreeHostX11*>(window2->GetHost());
194  widget2->Show();
195
196  MouseMoveCounterHandler recorder1;
197  window1->AddPreTargetHandler(&recorder1);
198  MouseMoveCounterHandler recorder2;
199  window2->AddPreTargetHandler(&recorder2);
200
201  // Move the mouse to the center of |widget2|.
202  gfx::Point point_in_screen = widget2->GetWindowBoundsInScreen().CenterPoint();
203  DispatchMouseMotionEvent(host2, point_in_screen);
204  EXPECT_EQ(0, recorder1.num_mouse_moves());
205  EXPECT_EQ(1, recorder2.num_mouse_moves());
206  EXPECT_EQ(point_in_screen.ToString(),
207            aura::Env::GetInstance()->last_mouse_location().ToString());
208
209  // Set capture to |widget1|. Because DesktopWindowTreeHostX11 calls
210  // XGrabPointer() with owner == False, the X server sends events to |widget2|
211  // as long as the mouse is hovered over |widget2|. Verify that Chrome
212  // redirects mouse events to |widget1|.
213  widget1->SetCapture(NULL);
214  point_in_screen += gfx::Vector2d(1, 0);
215  DispatchMouseMotionEvent(host2, point_in_screen);
216  EXPECT_EQ(1, recorder1.num_mouse_moves());
217  EXPECT_EQ(1, recorder2.num_mouse_moves());
218  // If the event's location was correctly changed to be relative to |widget1|,
219  // Env's last mouse position will be correct.
220  EXPECT_EQ(point_in_screen.ToString(),
221            aura::Env::GetInstance()->last_mouse_location().ToString());
222
223  // Set capture to |widget2|. Subsequent events sent to |widget2| should not be
224  // forwarded.
225  widget2->SetCapture(NULL);
226  point_in_screen += gfx::Vector2d(1, 0);
227  DispatchMouseMotionEvent(host2, point_in_screen);
228  EXPECT_EQ(1, recorder1.num_mouse_moves());
229  EXPECT_EQ(2, recorder2.num_mouse_moves());
230  EXPECT_EQ(point_in_screen.ToString(),
231            aura::Env::GetInstance()->last_mouse_location().ToString());
232
233  // If the mouse is not hovered over |widget1| or |widget2|, the X server will
234  // send events to the window which has capture. Test the mouse events sent to
235  // |widget2| are not forwarded.
236  DispatchMouseMotionEvent(host2, point_in_screen);
237  EXPECT_EQ(1, recorder1.num_mouse_moves());
238  EXPECT_EQ(3, recorder2.num_mouse_moves());
239  EXPECT_EQ(point_in_screen.ToString(),
240            aura::Env::GetInstance()->last_mouse_location().ToString());
241
242  // Release capture. Test that when capture is released, mouse events are no
243  // longer forwarded to other widgets.
244  widget2->ReleaseCapture();
245  point_in_screen = widget1->GetWindowBoundsInScreen().CenterPoint();
246  DispatchMouseMotionEvent(host1, point_in_screen);
247  EXPECT_EQ(2, recorder1.num_mouse_moves());
248  EXPECT_EQ(3, recorder2.num_mouse_moves());
249  EXPECT_EQ(point_in_screen.ToString(),
250            aura::Env::GetInstance()->last_mouse_location().ToString());
251
252  // Cleanup
253  window1->RemovePreTargetHandler(&recorder1);
254  window2->RemovePreTargetHandler(&recorder2);
255}
256
257}  // namespace views
258