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 <stddef.h>
8 
9 #include <memory>
10 #include <utility>
11 
12 #include "base/bind.h"
13 #include "base/logging.h"
14 #include "base/message_loop/message_loop_current.h"
15 #include "base/run_loop.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/stl_util.h"
18 #include "base/threading/thread_task_runner_handle.h"
19 #include "ui/aura/client/capture_client.h"
20 #include "ui/aura/env.h"
21 #include "ui/aura/window.h"
22 #include "ui/aura/window_event_dispatcher.h"
23 #include "ui/aura/window_tree_host.h"
24 #include "ui/base/mojom/cursor_type.mojom-shared.h"
25 #include "ui/base/x/x11_pointer_grab.h"
26 #include "ui/base/x/x11_util.h"
27 #include "ui/events/event.h"
28 #include "ui/events/event_utils.h"
29 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
30 #include "ui/events/platform/platform_event_source.h"
31 #include "ui/events/platform/scoped_event_dispatcher.h"
32 #include "ui/events/platform/x11/x11_event_source.h"
33 #include "ui/events/x/events_x_utils.h"
34 #include "ui/events/x/x11_window_event_manager.h"
35 #include "ui/gfx/x/x11.h"
36 
37 namespace views {
38 
39 // XGrabKey requires the modifier mask to explicitly be specified.
40 const unsigned int kModifiersMasks[] = {0,         // No additional modifier.
41                                         Mod2Mask,  // Num lock
42                                         LockMask,  // Caps lock
43                                         Mod5Mask,  // Scroll lock
44                                         Mod2Mask | LockMask,
45                                         Mod2Mask | Mod5Mask,
46                                         LockMask | Mod5Mask,
47                                         Mod2Mask | LockMask | Mod5Mask};
48 
X11WholeScreenMoveLoop(X11MoveLoopDelegate * delegate)49 X11WholeScreenMoveLoop::X11WholeScreenMoveLoop(X11MoveLoopDelegate* delegate)
50     : delegate_(delegate),
51       in_move_loop_(false),
52       initial_cursor_(ui::mojom::CursorType::kNull),
53       should_reset_mouse_flags_(false),
54       grab_input_window_(x11::None),
55       grabbed_pointer_(false),
56       canceled_(false) {}
57 
58 X11WholeScreenMoveLoop::~X11WholeScreenMoveLoop() = default;
59 
DispatchMouseMovement()60 void X11WholeScreenMoveLoop::DispatchMouseMovement() {
61   if (!last_motion_in_screen_)
62     return;
63   delegate_->OnMouseMovement(last_motion_in_screen_->root_location(),
64                              last_motion_in_screen_->flags(),
65                              last_motion_in_screen_->time_stamp());
66   last_motion_in_screen_.reset();
67 }
68 
PostDispatchIfNeeded(const ui::MouseEvent & event)69 void X11WholeScreenMoveLoop::PostDispatchIfNeeded(const ui::MouseEvent& event) {
70   bool dispatch_mouse_event = !last_motion_in_screen_;
71   last_motion_in_screen_ = std::make_unique<ui::MouseEvent>(event);
72   if (dispatch_mouse_event) {
73     // Post a task to dispatch mouse movement event when control returns to the
74     // message loop. This allows smoother dragging since the events are
75     // dispatched without waiting for the drag widget updates.
76     base::ThreadTaskRunnerHandle::Get()->PostTask(
77         FROM_HERE,
78         base::BindOnce(&X11WholeScreenMoveLoop::DispatchMouseMovement,
79                        weak_factory_.GetWeakPtr()));
80   }
81 }
82 
83 ////////////////////////////////////////////////////////////////////////////////
84 // DesktopWindowTreeHostLinux, ui::PlatformEventDispatcher implementation:
85 
CanDispatchEvent(const ui::PlatformEvent & event)86 bool X11WholeScreenMoveLoop::CanDispatchEvent(const ui::PlatformEvent& event) {
87   return in_move_loop_;
88 }
89 
DispatchEvent(const ui::PlatformEvent & event)90 uint32_t X11WholeScreenMoveLoop::DispatchEvent(const ui::PlatformEvent& event) {
91   DCHECK(base::MessageLoopCurrentForUI::IsSet());
92 
93   // This method processes all events while the move loop is active.
94   if (!in_move_loop_)
95     return ui::POST_DISPATCH_PERFORM_DEFAULT;
96 
97   switch (event->type()) {
98     case ui::ET_MOUSE_MOVED:
99     case ui::ET_MOUSE_DRAGGED: {
100       PostDispatchIfNeeded(*event->AsMouseEvent());
101       return ui::POST_DISPATCH_NONE;
102     }
103     case ui::ET_MOUSE_RELEASED: {
104       if (event->AsMouseEvent()->IsLeftMouseButton()) {
105         // Assume that drags are being done with the left mouse button. Only
106         // break the drag if the left mouse button was released.
107         DispatchMouseMovement();
108         delegate_->OnMouseReleased();
109 
110         if (!grabbed_pointer_) {
111           // If the source widget had capture prior to the move loop starting,
112           // it may be relying on views::Widget getting the mouse release and
113           // releasing capture in Widget::OnMouseEvent().
114           return ui::POST_DISPATCH_PERFORM_DEFAULT;
115         }
116       }
117       return ui::POST_DISPATCH_NONE;
118     }
119     case ui::ET_KEY_PRESSED:
120       if (event->AsKeyEvent()->key_code() == ui::VKEY_ESCAPE) {
121         canceled_ = true;
122         EndMoveLoop();
123         return ui::POST_DISPATCH_NONE;
124       }
125       break;
126     default:
127       break;
128   }
129   return ui::POST_DISPATCH_PERFORM_DEFAULT;
130 }
131 
RunMoveLoop(aura::Window * source,gfx::NativeCursor cursor)132 bool X11WholeScreenMoveLoop::RunMoveLoop(aura::Window* source,
133                                          gfx::NativeCursor cursor) {
134   DCHECK(!in_move_loop_);  // Can only handle one nested loop at a time.
135 
136   // Query the mouse cursor prior to the move loop starting so that it can be
137   // restored when the move loop finishes.
138   initial_cursor_ = source->GetHost()->last_cursor();
139 
140   CreateDragInputWindow(gfx::GetXDisplay());
141 
142   // Only grab mouse capture of |grab_input_window_| if |source| does not have
143   // capture.
144   // - The caller may intend to transfer capture to a different aura::Window
145   //   when the move loop ends and not release capture.
146   // - Releasing capture and X window destruction are both asynchronous. We drop
147   //   events targeted at |grab_input_window_| in the time between the move
148   //   loop ends and |grab_input_window_| loses capture.
149   grabbed_pointer_ = false;
150   if (!source->HasCapture()) {
151     aura::client::CaptureClient* capture_client =
152         aura::client::GetCaptureClient(source->GetRootWindow());
153     CHECK(capture_client->GetGlobalCaptureWindow() == nullptr);
154     grabbed_pointer_ = GrabPointer(cursor);
155     if (!grabbed_pointer_) {
156       XDestroyWindow(gfx::GetXDisplay(), grab_input_window_);
157       return false;
158     }
159   }
160 
161   GrabEscKey();
162 
163   std::unique_ptr<ui::ScopedEventDispatcher> old_dispatcher =
164       std::move(nested_dispatcher_);
165   nested_dispatcher_ =
166       ui::PlatformEventSource::GetInstance()->OverrideDispatcher(this);
167 
168   // We are handling a mouse drag outside of the aura::Window system. We must
169   // manually make aura think that the mouse button is pressed so that we don't
170   // draw extraneous tooltips.
171   aura::Env* env = aura::Env::GetInstance();
172   if (!env->IsMouseButtonDown()) {
173     env->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON);
174     should_reset_mouse_flags_ = true;
175   }
176 
177   base::WeakPtr<X11WholeScreenMoveLoop> alive(weak_factory_.GetWeakPtr());
178 
179   in_move_loop_ = true;
180   canceled_ = false;
181   base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
182   quit_closure_ = run_loop.QuitClosure();
183   run_loop.Run();
184 
185   if (!alive)
186     return false;
187 
188   nested_dispatcher_ = std::move(old_dispatcher);
189   return !canceled_;
190 }
191 
UpdateCursor(gfx::NativeCursor cursor)192 void X11WholeScreenMoveLoop::UpdateCursor(gfx::NativeCursor cursor) {
193   if (in_move_loop_)
194     ui::ChangeActivePointerGrabCursor(cursor.platform());
195 }
196 
EndMoveLoop()197 void X11WholeScreenMoveLoop::EndMoveLoop() {
198   if (!in_move_loop_)
199     return;
200 
201   // Prevent DispatchMouseMovement from dispatching any posted motion event.
202   last_motion_in_screen_.reset();
203 
204   // We undo our emulated mouse click from RunMoveLoop();
205   if (should_reset_mouse_flags_) {
206     aura::Env::GetInstance()->set_mouse_button_flags(0);
207     should_reset_mouse_flags_ = false;
208   }
209 
210   // TODO(erg): Is this ungrab the cause of having to click to give input focus
211   // on drawn out windows? Not ungrabbing here screws the X server until I kill
212   // the chrome process.
213 
214   // Ungrab before we let go of the window.
215   if (grabbed_pointer_)
216     ui::UngrabPointer();
217   else
218     UpdateCursor(initial_cursor_);
219 
220   XDisplay* display = gfx::GetXDisplay();
221   unsigned int esc_keycode = XKeysymToKeycode(display, XK_Escape);
222   for (auto mask : kModifiersMasks)
223     XUngrabKey(display, esc_keycode, mask, grab_input_window_);
224 
225   // Restore the previous dispatcher.
226   nested_dispatcher_.reset();
227   delegate_->OnMoveLoopEnded();
228   grab_input_window_events_.reset();
229   XDestroyWindow(display, grab_input_window_);
230   grab_input_window_ = x11::None;
231 
232   in_move_loop_ = false;
233   std::move(quit_closure_).Run();
234 }
235 
GrabPointer(gfx::NativeCursor cursor)236 bool X11WholeScreenMoveLoop::GrabPointer(gfx::NativeCursor cursor) {
237   XDisplay* display = gfx::GetXDisplay();
238 
239   // Pass "owner_events" as false so that X sends all mouse events to
240   // |grab_input_window_|.
241   int ret = ui::GrabPointer(grab_input_window_, false, cursor.platform());
242   if (ret != GrabSuccess) {
243     DLOG(ERROR) << "Grabbing pointer for dragging failed: "
244                 << ui::GetX11ErrorString(display, ret);
245   }
246   XFlush(display);
247   return ret == GrabSuccess;
248 }
249 
GrabEscKey()250 void X11WholeScreenMoveLoop::GrabEscKey() {
251   XDisplay* display = gfx::GetXDisplay();
252   unsigned int esc_keycode = XKeysymToKeycode(display, XK_Escape);
253   for (auto mask : kModifiersMasks) {
254     XGrabKey(display, esc_keycode, mask, grab_input_window_, x11::False,
255              GrabModeAsync, GrabModeAsync);
256   }
257 }
258 
CreateDragInputWindow(XDisplay * display)259 void X11WholeScreenMoveLoop::CreateDragInputWindow(XDisplay* display) {
260   XSetWindowAttributes swa;
261   memset(&swa, 0, sizeof(swa));
262   swa.override_redirect = x11::True;
263   grab_input_window_ = XCreateWindow(display, DefaultRootWindow(display), -100,
264                                      -100, 10, 10, 0, CopyFromParent, InputOnly,
265                                      CopyFromParent, CWOverrideRedirect, &swa);
266   uint32_t event_mask = ButtonPressMask | ButtonReleaseMask |
267                         PointerMotionMask | KeyPressMask | KeyReleaseMask |
268                         StructureNotifyMask;
269   grab_input_window_events_ = std::make_unique<ui::XScopedEventSelector>(
270       grab_input_window_, event_mask);
271 
272   XMapRaised(display, grab_input_window_);
273 }
274 
275 }  // namespace views
276