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 30 namespace views { 31 32 namespace { 33 34 // Blocks till |window| gets activated. 35 class 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. 59 class 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. 83 scoped_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|. 96 void 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 122 class 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. 158 TEST_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. 183 TEST_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