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