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.
5 #include "ui/views/widget/desktop_aura/x11_whole_screen_move_loop.h"
7 #include <X11/keysym.h>
10 #include "base/bind.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/run_loop.h"
13 #include "ui/aura/client/capture_client.h"
14 #include "ui/aura/env.h"
15 #include "ui/aura/window.h"
16 #include "ui/aura/window_event_dispatcher.h"
17 #include "ui/aura/window_tree_host.h"
18 #include "ui/base/x/x11_util.h"
19 #include "ui/events/event.h"
20 #include "ui/events/event_utils.h"
21 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
22 #include "ui/events/platform/scoped_event_dispatcher.h"
23 #include "ui/events/platform/x11/x11_event_source.h"
27 // XGrabKey requires the modifier mask to explicitly be specified.
28 const unsigned int kModifiersMasks
[] = {
29 0, // No additional modifier.
31 LockMask
, // Caps lock
32 Mod5Mask
, // Scroll lock
36 Mod2Mask
| LockMask
| Mod5Mask
39 X11WholeScreenMoveLoop::X11WholeScreenMoveLoop(X11MoveLoopDelegate
* delegate
)
40 : delegate_(delegate
),
42 initial_cursor_(ui::kCursorNull
),
43 should_reset_mouse_flags_(false),
44 grab_input_window_(None
),
45 grabbed_pointer_(false),
48 last_xmotion_
.type
= LASTEvent
;
51 X11WholeScreenMoveLoop::~X11WholeScreenMoveLoop() {}
53 void X11WholeScreenMoveLoop::DispatchMouseMovement() {
54 if (!weak_factory_
.HasWeakPtrs())
56 weak_factory_
.InvalidateWeakPtrs();
57 DCHECK_EQ(MotionNotify
, last_xmotion_
.type
);
58 delegate_
->OnMouseMovement(&last_xmotion_
);
59 last_xmotion_
.type
= LASTEvent
;
62 ////////////////////////////////////////////////////////////////////////////////
63 // DesktopWindowTreeHostLinux, ui::PlatformEventDispatcher implementation:
65 bool X11WholeScreenMoveLoop::CanDispatchEvent(const ui::PlatformEvent
& event
) {
69 uint32_t X11WholeScreenMoveLoop::DispatchEvent(const ui::PlatformEvent
& event
) {
70 // This method processes all events while the move loop is active.
72 return ui::POST_DISPATCH_PERFORM_DEFAULT
;
77 last_xmotion_
= xev
->xmotion
;
78 if (!weak_factory_
.HasWeakPtrs()) {
79 // Post a task to dispatch mouse movement event when control returns to
80 // the message loop. This allows smoother dragging since the events are
81 // dispatched without waiting for the drag widget updates.
82 base::MessageLoopForUI::current()->PostTask(
84 base::Bind(&X11WholeScreenMoveLoop::DispatchMouseMovement
,
85 weak_factory_
.GetWeakPtr()));
87 return ui::POST_DISPATCH_NONE
;
90 if (xev
->xbutton
.button
== Button1
) {
91 // Assume that drags are being done with the left mouse button. Only
92 // break the drag if the left mouse button was released.
93 DispatchMouseMovement();
94 delegate_
->OnMouseReleased();
96 if (!grabbed_pointer_
) {
97 // If the source widget had capture prior to the move loop starting,
98 // it may be relying on views::Widget getting the mouse release and
99 // releasing capture in Widget::OnMouseEvent().
100 return ui::POST_DISPATCH_PERFORM_DEFAULT
;
103 return ui::POST_DISPATCH_NONE
;
106 if (ui::KeyboardCodeFromXKeyEvent(xev
) == ui::VKEY_ESCAPE
) {
109 return ui::POST_DISPATCH_NONE
;
114 ui::EventType type
= ui::EventTypeFromNative(xev
);
116 case ui::ET_MOUSE_MOVED
:
117 case ui::ET_MOUSE_DRAGGED
:
118 case ui::ET_MOUSE_RELEASED
: {
120 if (type
== ui::ET_MOUSE_RELEASED
) {
121 xevent
.type
= ButtonRelease
;
122 xevent
.xbutton
.button
= ui::EventButtonFromNative(xev
);
124 xevent
.type
= MotionNotify
;
126 xevent
.xany
.display
= xev
->xgeneric
.display
;
127 xevent
.xany
.window
= grab_input_window_
;
128 // The fields used below are in the same place for all of events
129 // above. Using xmotion from XEvent's unions to avoid repeating
131 xevent
.xmotion
.root
= DefaultRootWindow(xev
->xgeneric
.display
);
132 xevent
.xmotion
.time
= ui::EventTimeFromNative(xev
).InMilliseconds();
133 gfx::Point
point(ui::EventSystemLocationFromNative(xev
));
134 xevent
.xmotion
.x_root
= point
.x();
135 xevent
.xmotion
.y_root
= point
.y();
136 return DispatchEvent(&xevent
);
144 return ui::POST_DISPATCH_PERFORM_DEFAULT
;
147 bool X11WholeScreenMoveLoop::RunMoveLoop(aura::Window
* source
,
148 gfx::NativeCursor cursor
) {
149 DCHECK(!in_move_loop_
); // Can only handle one nested loop at a time.
151 // Query the mouse cursor prior to the move loop starting so that it can be
152 // restored when the move loop finishes.
153 initial_cursor_
= source
->GetHost()->last_cursor();
155 grab_input_window_
= CreateDragInputWindow(gfx::GetXDisplay());
157 // Only grab mouse capture of |grab_input_window_| if |source| does not have
159 // - The caller may intend to transfer capture to a different aura::Window
160 // when the move loop ends and not release capture.
161 // - Releasing capture and X window destruction are both asynchronous. We drop
162 // events targeted at |grab_input_window_| in the time between the move
163 // loop ends and |grab_input_window_| loses capture.
164 grabbed_pointer_
= false;
165 if (!source
->HasCapture()) {
166 aura::client::CaptureClient
* capture_client
=
167 aura::client::GetCaptureClient(source
->GetRootWindow());
168 CHECK(capture_client
->GetGlobalCaptureWindow() == NULL
);
169 grabbed_pointer_
= GrabPointer(cursor
);
170 if (!grabbed_pointer_
) {
171 XDestroyWindow(gfx::GetXDisplay(), grab_input_window_
);
178 scoped_ptr
<ui::ScopedEventDispatcher
> old_dispatcher
=
179 nested_dispatcher_
.Pass();
181 ui::PlatformEventSource::GetInstance()->OverrideDispatcher(this);
183 // We are handling a mouse drag outside of the aura::Window system. We must
184 // manually make aura think that the mouse button is pressed so that we don't
185 // draw extraneous tooltips.
186 aura::Env
* env
= aura::Env::GetInstance();
187 if (!env
->IsMouseButtonDown()) {
188 env
->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON
);
189 should_reset_mouse_flags_
= true;
192 in_move_loop_
= true;
194 base::MessageLoopForUI
* loop
= base::MessageLoopForUI::current();
195 base::MessageLoop::ScopedNestableTaskAllower
allow_nested(loop
);
196 base::RunLoop run_loop
;
197 quit_closure_
= run_loop
.QuitClosure();
199 nested_dispatcher_
= old_dispatcher
.Pass();
203 void X11WholeScreenMoveLoop::UpdateCursor(gfx::NativeCursor cursor
) {
205 // We cannot call GrabPointer() because we do not want to change the
206 // "owner_events" property of the active pointer grab.
207 XChangeActivePointerGrab(
209 ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
,
215 void X11WholeScreenMoveLoop::EndMoveLoop() {
219 // Prevent DispatchMouseMovement from dispatching any posted motion event.
220 weak_factory_
.InvalidateWeakPtrs();
221 last_xmotion_
.type
= LASTEvent
;
223 // We undo our emulated mouse click from RunMoveLoop();
224 if (should_reset_mouse_flags_
) {
225 aura::Env::GetInstance()->set_mouse_button_flags(0);
226 should_reset_mouse_flags_
= false;
229 // TODO(erg): Is this ungrab the cause of having to click to give input focus
230 // on drawn out windows? Not ungrabbing here screws the X server until I kill
231 // the chrome process.
233 // Ungrab before we let go of the window.
234 XDisplay
* display
= gfx::GetXDisplay();
235 if (grabbed_pointer_
)
236 XUngrabPointer(display
, CurrentTime
);
238 UpdateCursor(initial_cursor_
);
240 unsigned int esc_keycode
= XKeysymToKeycode(display
, XK_Escape
);
241 for (size_t i
= 0; i
< arraysize(kModifiersMasks
); ++i
) {
242 XUngrabKey(display
, esc_keycode
, kModifiersMasks
[i
], grab_input_window_
);
245 // Restore the previous dispatcher.
246 nested_dispatcher_
.reset();
247 delegate_
->OnMoveLoopEnded();
248 XDestroyWindow(display
, grab_input_window_
);
249 grab_input_window_
= None
;
251 in_move_loop_
= false;
255 bool X11WholeScreenMoveLoop::GrabPointer(gfx::NativeCursor cursor
) {
256 XDisplay
* display
= gfx::GetXDisplay();
257 XGrabServer(display
);
259 // Pass "owner_events" as false so that X sends all mouse events to
260 // |grab_input_window_|.
261 int ret
= XGrabPointer(
264 False
, // owner_events
265 ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
,
271 if (ret
!= GrabSuccess
) {
272 DLOG(ERROR
) << "Grabbing pointer for dragging failed: "
273 << ui::GetX11ErrorString(display
, ret
);
275 XUngrabServer(display
);
277 return ret
== GrabSuccess
;
280 void X11WholeScreenMoveLoop::GrabEscKey() {
281 XDisplay
* display
= gfx::GetXDisplay();
282 unsigned int esc_keycode
= XKeysymToKeycode(display
, XK_Escape
);
283 for (size_t i
= 0; i
< arraysize(kModifiersMasks
); ++i
) {
284 XGrabKey(display
, esc_keycode
, kModifiersMasks
[i
], grab_input_window_
,
285 False
, GrabModeAsync
, GrabModeAsync
);
289 Window
X11WholeScreenMoveLoop::CreateDragInputWindow(XDisplay
* display
) {
290 unsigned long attribute_mask
= CWEventMask
| CWOverrideRedirect
;
291 XSetWindowAttributes swa
;
292 memset(&swa
, 0, sizeof(swa
));
293 swa
.event_mask
= ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
|
294 KeyPressMask
| KeyReleaseMask
| StructureNotifyMask
;
295 swa
.override_redirect
= True
;
296 Window window
= XCreateWindow(display
,
297 DefaultRootWindow(display
),
299 0, CopyFromParent
, InputOnly
, CopyFromParent
,
300 attribute_mask
, &swa
);
301 XMapRaised(display
, window
);
302 ui::X11EventSource::GetInstance()->BlockUntilWindowMapped(window
);