1// Copyright (c) 2012 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/x11_whole_screen_move_loop.h" 6 7#include <X11/Xlib.h> 8 9#include "base/bind.h" 10#include "base/message_loop/message_loop.h" 11#include "base/run_loop.h" 12#include "ui/aura/client/capture_client.h" 13#include "ui/aura/env.h" 14#include "ui/aura/window.h" 15#include "ui/aura/window_event_dispatcher.h" 16#include "ui/aura/window_tree_host.h" 17#include "ui/base/x/x11_util.h" 18#include "ui/events/event.h" 19#include "ui/events/event_utils.h" 20#include "ui/events/keycodes/keyboard_code_conversion_x.h" 21#include "ui/events/platform/scoped_event_dispatcher.h" 22#include "ui/events/platform/x11/x11_event_source.h" 23 24namespace views { 25 26X11WholeScreenMoveLoop::X11WholeScreenMoveLoop(X11MoveLoopDelegate* delegate) 27 : delegate_(delegate), 28 in_move_loop_(false), 29 initial_cursor_(ui::kCursorNull), 30 should_reset_mouse_flags_(false), 31 grab_input_window_(None), 32 grabbed_pointer_(false), 33 canceled_(false), 34 weak_factory_(this) { 35 last_xmotion_.type = LASTEvent; 36} 37 38X11WholeScreenMoveLoop::~X11WholeScreenMoveLoop() {} 39 40void X11WholeScreenMoveLoop::DispatchMouseMovement() { 41 if (!weak_factory_.HasWeakPtrs()) 42 return; 43 weak_factory_.InvalidateWeakPtrs(); 44 DCHECK_EQ(MotionNotify, last_xmotion_.type); 45 delegate_->OnMouseMovement(&last_xmotion_); 46 last_xmotion_.type = LASTEvent; 47} 48 49//////////////////////////////////////////////////////////////////////////////// 50// DesktopWindowTreeHostLinux, ui::PlatformEventDispatcher implementation: 51 52bool X11WholeScreenMoveLoop::CanDispatchEvent(const ui::PlatformEvent& event) { 53 return in_move_loop_; 54} 55 56uint32_t X11WholeScreenMoveLoop::DispatchEvent(const ui::PlatformEvent& event) { 57 // This method processes all events while the move loop is active. 58 if (!in_move_loop_) 59 return ui::POST_DISPATCH_PERFORM_DEFAULT; 60 61 XEvent* xev = event; 62 switch (xev->type) { 63 case MotionNotify: { 64 last_xmotion_ = xev->xmotion; 65 if (!weak_factory_.HasWeakPtrs()) { 66 // Post a task to dispatch mouse movement event when control returns to 67 // the message loop. This allows smoother dragging since the events are 68 // dispatched without waiting for the drag widget updates. 69 base::MessageLoopForUI::current()->PostTask( 70 FROM_HERE, 71 base::Bind(&X11WholeScreenMoveLoop::DispatchMouseMovement, 72 weak_factory_.GetWeakPtr())); 73 } 74 return ui::POST_DISPATCH_NONE; 75 } 76 case ButtonRelease: { 77 if (xev->xbutton.button == Button1) { 78 // Assume that drags are being done with the left mouse button. Only 79 // break the drag if the left mouse button was released. 80 DispatchMouseMovement(); 81 delegate_->OnMouseReleased(); 82 83 if (!grabbed_pointer_) { 84 // If the source widget had capture prior to the move loop starting, 85 // it may be relying on views::Widget getting the mouse release and 86 // releasing capture in Widget::OnMouseEvent(). 87 return ui::POST_DISPATCH_PERFORM_DEFAULT; 88 } 89 } 90 return ui::POST_DISPATCH_NONE; 91 } 92 case KeyPress: { 93 if (ui::KeyboardCodeFromXKeyEvent(xev) == ui::VKEY_ESCAPE) { 94 canceled_ = true; 95 EndMoveLoop(); 96 return ui::POST_DISPATCH_NONE; 97 } 98 break; 99 } 100 case GenericEvent: { 101 ui::EventType type = ui::EventTypeFromNative(xev); 102 switch (type) { 103 case ui::ET_MOUSE_MOVED: 104 case ui::ET_MOUSE_DRAGGED: 105 case ui::ET_MOUSE_RELEASED: { 106 XEvent xevent = {0}; 107 if (type == ui::ET_MOUSE_RELEASED) { 108 xevent.type = ButtonRelease; 109 xevent.xbutton.button = ui::EventButtonFromNative(xev); 110 } else { 111 xevent.type = MotionNotify; 112 } 113 xevent.xany.display = xev->xgeneric.display; 114 xevent.xany.window = grab_input_window_; 115 // The fields used below are in the same place for all of events 116 // above. Using xmotion from XEvent's unions to avoid repeating 117 // the code. 118 xevent.xmotion.root = DefaultRootWindow(xev->xgeneric.display); 119 xevent.xmotion.time = ui::EventTimeFromNative(xev).InMilliseconds(); 120 gfx::Point point(ui::EventSystemLocationFromNative(xev)); 121 xevent.xmotion.x_root = point.x(); 122 xevent.xmotion.y_root = point.y(); 123 return DispatchEvent(&xevent); 124 } 125 default: 126 break; 127 } 128 } 129 } 130 131 return ui::POST_DISPATCH_PERFORM_DEFAULT; 132} 133 134bool X11WholeScreenMoveLoop::RunMoveLoop(aura::Window* source, 135 gfx::NativeCursor cursor) { 136 DCHECK(!in_move_loop_); // Can only handle one nested loop at a time. 137 138 // Query the mouse cursor prior to the move loop starting so that it can be 139 // restored when the move loop finishes. 140 initial_cursor_ = source->GetHost()->last_cursor(); 141 142 grab_input_window_ = CreateDragInputWindow(gfx::GetXDisplay()); 143 144 // Only grab mouse capture of |grab_input_window_| if |source| does not have 145 // capture. 146 // - The caller may intend to transfer capture to a different aura::Window 147 // when the move loop ends and not release capture. 148 // - Releasing capture and X window destruction are both asynchronous. We drop 149 // events targeted at |grab_input_window_| in the time between the move 150 // loop ends and |grab_input_window_| loses capture. 151 grabbed_pointer_ = false; 152 if (!source->HasCapture()) { 153 aura::client::CaptureClient* capture_client = 154 aura::client::GetCaptureClient(source->GetRootWindow()); 155 CHECK(capture_client->GetGlobalCaptureWindow() == NULL); 156 grabbed_pointer_ = GrabPointer(cursor); 157 if (!grabbed_pointer_) { 158 XDestroyWindow(gfx::GetXDisplay(), grab_input_window_); 159 return false; 160 } 161 } 162 163 if (!GrabKeyboard()) { 164 XDestroyWindow(gfx::GetXDisplay(), grab_input_window_); 165 return false; 166 } 167 168 scoped_ptr<ui::ScopedEventDispatcher> old_dispatcher = 169 nested_dispatcher_.Pass(); 170 nested_dispatcher_ = 171 ui::PlatformEventSource::GetInstance()->OverrideDispatcher(this); 172 173 // We are handling a mouse drag outside of the aura::Window system. We must 174 // manually make aura think that the mouse button is pressed so that we don't 175 // draw extraneous tooltips. 176 aura::Env* env = aura::Env::GetInstance(); 177 if (!env->IsMouseButtonDown()) { 178 env->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON); 179 should_reset_mouse_flags_ = true; 180 } 181 182 in_move_loop_ = true; 183 canceled_ = false; 184 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); 185 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop); 186 base::RunLoop run_loop; 187 quit_closure_ = run_loop.QuitClosure(); 188 run_loop.Run(); 189 nested_dispatcher_ = old_dispatcher.Pass(); 190 return !canceled_; 191} 192 193void X11WholeScreenMoveLoop::UpdateCursor(gfx::NativeCursor cursor) { 194 if (in_move_loop_) { 195 // We cannot call GrabPointer() because we do not want to change the 196 // "owner_events" property of the active pointer grab. 197 XChangeActivePointerGrab( 198 gfx::GetXDisplay(), 199 ButtonPressMask | ButtonReleaseMask | PointerMotionMask, 200 cursor.platform(), 201 CurrentTime); 202 } 203} 204 205void X11WholeScreenMoveLoop::EndMoveLoop() { 206 if (!in_move_loop_) 207 return; 208 209 // Prevent DispatchMouseMovement from dispatching any posted motion event. 210 weak_factory_.InvalidateWeakPtrs(); 211 last_xmotion_.type = LASTEvent; 212 213 // We undo our emulated mouse click from RunMoveLoop(); 214 if (should_reset_mouse_flags_) { 215 aura::Env::GetInstance()->set_mouse_button_flags(0); 216 should_reset_mouse_flags_ = false; 217 } 218 219 // TODO(erg): Is this ungrab the cause of having to click to give input focus 220 // on drawn out windows? Not ungrabbing here screws the X server until I kill 221 // the chrome process. 222 223 // Ungrab before we let go of the window. 224 XDisplay* display = gfx::GetXDisplay(); 225 if (grabbed_pointer_) 226 XUngrabPointer(display, CurrentTime); 227 else 228 UpdateCursor(initial_cursor_); 229 230 XUngrabKeyboard(display, CurrentTime); 231 232 // Restore the previous dispatcher. 233 nested_dispatcher_.reset(); 234 delegate_->OnMoveLoopEnded(); 235 XDestroyWindow(display, grab_input_window_); 236 grab_input_window_ = None; 237 238 in_move_loop_ = false; 239 quit_closure_.Run(); 240} 241 242bool X11WholeScreenMoveLoop::GrabPointer(gfx::NativeCursor cursor) { 243 XDisplay* display = gfx::GetXDisplay(); 244 XGrabServer(display); 245 246 // Pass "owner_events" as false so that X sends all mouse events to 247 // |grab_input_window_|. 248 int ret = XGrabPointer( 249 display, 250 grab_input_window_, 251 False, // owner_events 252 ButtonPressMask | ButtonReleaseMask | PointerMotionMask, 253 GrabModeAsync, 254 GrabModeAsync, 255 None, 256 cursor.platform(), 257 CurrentTime); 258 if (ret != GrabSuccess) { 259 DLOG(ERROR) << "Grabbing pointer for dragging failed: " 260 << ui::GetX11ErrorString(display, ret); 261 } 262 XUngrabServer(display); 263 XFlush(display); 264 return ret == GrabSuccess; 265} 266 267bool X11WholeScreenMoveLoop::GrabKeyboard() { 268 XDisplay* display = gfx::GetXDisplay(); 269 int ret = XGrabKeyboard(display, 270 grab_input_window_, 271 False, 272 GrabModeAsync, 273 GrabModeAsync, 274 CurrentTime); 275 if (ret != GrabSuccess) { 276 DLOG(ERROR) << "Grabbing keyboard for dragging failed: " 277 << ui::GetX11ErrorString(display, ret); 278 return false; 279 } 280 return true; 281} 282 283Window X11WholeScreenMoveLoop::CreateDragInputWindow(XDisplay* display) { 284 unsigned long attribute_mask = CWEventMask | CWOverrideRedirect; 285 XSetWindowAttributes swa; 286 memset(&swa, 0, sizeof(swa)); 287 swa.event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | 288 KeyPressMask | KeyReleaseMask | StructureNotifyMask; 289 swa.override_redirect = True; 290 Window window = XCreateWindow(display, 291 DefaultRootWindow(display), 292 -100, -100, 10, 10, 293 0, CopyFromParent, InputOnly, CopyFromParent, 294 attribute_mask, &swa); 295 XMapRaised(display, window); 296 ui::X11EventSource::GetInstance()->BlockUntilWindowMapped(window); 297 return window; 298} 299 300} // namespace views 301