blob: 1026cc401493403f26b502f2e90cfd0227f9ab57 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/widget/desktop_aura/x11_whole_screen_move_loop.h"
#include <X11/Xlib.h>
// Get rid of a macro from Xlib.h that conflicts with Aura's RootWindow class.
#undef RootWindow
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/x/x11_util.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/events/platform/scoped_event_dispatcher.h"
#include "ui/events/platform/x11/x11_event_source.h"
#include "ui/gfx/point_conversions.h"
#include "ui/gfx/screen.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
// The minimum alpha before we declare a pixel transparent when searching in
// our source image.
const uint32 kMinAlpha = 32;
const unsigned char kDragWidgetOpacity = 0xc0;
class ScopedCapturer {
public:
explicit ScopedCapturer(aura::WindowTreeHost* host)
: host_(host) {
host_->SetCapture();
}
~ScopedCapturer() {
host_->ReleaseCapture();
}
private:
aura::WindowTreeHost* host_;
DISALLOW_COPY_AND_ASSIGN(ScopedCapturer);
};
} // namespace
X11WholeScreenMoveLoop::X11WholeScreenMoveLoop(
X11WholeScreenMoveLoopDelegate* delegate)
: delegate_(delegate),
in_move_loop_(false),
should_reset_mouse_flags_(false),
grab_input_window_(None),
canceled_(false),
has_grab_(false),
weak_factory_(this) {
last_xmotion_.type = LASTEvent;
}
X11WholeScreenMoveLoop::~X11WholeScreenMoveLoop() {}
void X11WholeScreenMoveLoop::DispatchMouseMovement() {
if (!weak_factory_.HasWeakPtrs())
return;
weak_factory_.InvalidateWeakPtrs();
DCHECK_EQ(MotionNotify, last_xmotion_.type);
delegate_->OnMouseMovement(&last_xmotion_);
last_xmotion_.type = LASTEvent;
}
////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostLinux, ui::PlatformEventDispatcher implementation:
bool X11WholeScreenMoveLoop::CanDispatchEvent(const ui::PlatformEvent& event) {
return in_move_loop_;
}
uint32_t X11WholeScreenMoveLoop::DispatchEvent(const ui::PlatformEvent& event) {
// This method processes all events for the grab_input_window_ as well as
// mouse events for all windows while the move loop is active - even before
// the grab is granted by X. This allows mouse notification events that were
// sent after the capture was requested but before the capture was granted
// to be dispatched. It is especially important to process the mouse release
// event that should have stopped the drag even if that mouse release happened
// before the grab was granted.
if (!in_move_loop_)
return ui::POST_DISPATCH_PERFORM_DEFAULT;
XEvent* xev = event;
// Note: the escape key is handled in the tab drag controller, which has
// keyboard focus even though we took pointer grab.
switch (xev->type) {
case MotionNotify: {
if (drag_widget_.get()) {
gfx::Screen* screen = gfx::Screen::GetNativeScreen();
gfx::Point location = gfx::ToFlooredPoint(
screen->GetCursorScreenPoint() - drag_offset_);
drag_widget_->SetBounds(gfx::Rect(location, drag_image_.size()));
drag_widget_->StackAtTop();
}
last_xmotion_ = xev->xmotion;
if (!weak_factory_.HasWeakPtrs()) {
// Post a task to dispatch mouse movement event when control returns to
// the message loop. This allows smoother dragging since the events are
// dispatched without waiting for the drag widget updates.
base::MessageLoopForUI::current()->PostTask(
FROM_HERE,
base::Bind(&X11WholeScreenMoveLoop::DispatchMouseMovement,
weak_factory_.GetWeakPtr()));
}
return ui::POST_DISPATCH_NONE;
}
case ButtonRelease: {
if (xev->xbutton.button == Button1) {
// Assume that drags are being done with the left mouse button. Only
// break the drag if the left mouse button was released.
DispatchMouseMovement();
delegate_->OnMouseReleased();
}
return ui::POST_DISPATCH_NONE;
}
case KeyPress: {
if (ui::KeyboardCodeFromXKeyEvent(xev) == ui::VKEY_ESCAPE) {
canceled_ = true;
EndMoveLoop();
return ui::POST_DISPATCH_NONE;
}
break;
}
case FocusOut: {
if (xev->xfocus.mode != NotifyGrab)
has_grab_ = false;
break;
}
case GenericEvent: {
ui::EventType type = ui::EventTypeFromNative(xev);
switch (type) {
case ui::ET_MOUSE_MOVED:
case ui::ET_MOUSE_DRAGGED:
case ui::ET_MOUSE_RELEASED: {
XEvent xevent = {0};
if (type == ui::ET_MOUSE_RELEASED) {
xevent.type = ButtonRelease;
xevent.xbutton.button = ui::EventButtonFromNative(xev);
} else {
xevent.type = MotionNotify;
}
xevent.xany.display = xev->xgeneric.display;
xevent.xany.window = grab_input_window_;
// The fields used below are in the same place for all of events
// above. Using xmotion from XEvent's unions to avoid repeating
// the code.
xevent.xmotion.root = DefaultRootWindow(xev->xgeneric.display);
xevent.xmotion.time = ui::EventTimeFromNative(xev).InMilliseconds();
gfx::Point point(ui::EventSystemLocationFromNative(xev));
xevent.xmotion.x_root = point.x();
xevent.xmotion.y_root = point.y();
DispatchEvent(&xevent);
return ui::POST_DISPATCH_NONE;
}
default:
break;
}
}
}
return (event->xany.window == grab_input_window_) ?
ui::POST_DISPATCH_NONE : ui::POST_DISPATCH_PERFORM_DEFAULT;
}
bool X11WholeScreenMoveLoop::RunMoveLoop(aura::Window* source,
gfx::NativeCursor cursor) {
DCHECK(!in_move_loop_); // Can only handle one nested loop at a time.
// Start a capture on the host, so that it continues to receive events during
// the drag. This may be second time we are capturing the mouse events - the
// first being when a mouse is first pressed. That first capture needs to be
// released before the call to GrabPointerAndKeyboard below, otherwise it may
// get released while we still need the pointer grab, which is why we restrict
// the scope here.
{
ScopedCapturer capturer(source->GetHost());
grab_input_window_ = CreateDragInputWindow(gfx::GetXDisplay());
// Releasing ScopedCapturer ensures that any other instance of
// X11ScopedCapture will not prematurely release grab that will be acquired
// below.
}
// TODO(varkha): Consider integrating GrabPointerAndKeyboard with
// ScopedCapturer to avoid possibility of logically keeping multiple grabs.
if (!GrabPointerAndKeyboard(cursor)) {
XDestroyWindow(gfx::GetXDisplay(), grab_input_window_);
return false;
}
scoped_ptr<ui::ScopedEventDispatcher> old_dispatcher =
nested_dispatcher_.Pass();
nested_dispatcher_ =
ui::PlatformEventSource::GetInstance()->OverrideDispatcher(this);
if (!drag_image_.isNull() && CheckIfIconValid())
CreateDragImageWindow();
// We are handling a mouse drag outside of the aura::RootWindow system. We
// must manually make aura think that the mouse button is pressed so that we
// don't draw extraneous tooltips.
aura::Env* env = aura::Env::GetInstance();
if (!env->IsMouseButtonDown()) {
env->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON);
should_reset_mouse_flags_ = true;
}
in_move_loop_ = true;
canceled_ = false;
base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
nested_dispatcher_ = old_dispatcher.Pass();
return !canceled_;
}
void X11WholeScreenMoveLoop::UpdateCursor(gfx::NativeCursor cursor) {
if (in_move_loop_) {
// If we're still in the move loop, regrab the pointer with the updated
// cursor. Note: we can be called from handling an XdndStatus message after
// EndMoveLoop() was called, but before we return from the nested RunLoop.
GrabPointerAndKeyboard(cursor);
}
}
void X11WholeScreenMoveLoop::EndMoveLoop() {
if (!in_move_loop_)
return;
// Prevent DispatchMouseMovement from dispatching any posted motion event.
weak_factory_.InvalidateWeakPtrs();
last_xmotion_.type = LASTEvent;
// We undo our emulated mouse click from RunMoveLoop();
if (should_reset_mouse_flags_) {
aura::Env::GetInstance()->set_mouse_button_flags(0);
should_reset_mouse_flags_ = false;
}
// TODO(erg): Is this ungrab the cause of having to click to give input focus
// on drawn out windows? Not ungrabbing here screws the X server until I kill
// the chrome process.
// Ungrab before we let go of the window.
XDisplay* display = gfx::GetXDisplay();
// Only ungrab pointer if capture was not switched to another window.
if (has_grab_) {
XUngrabPointer(display, CurrentTime);
XUngrabKeyboard(display, CurrentTime);
}
// Restore the previous dispatcher.
nested_dispatcher_.reset();
drag_widget_.reset();
delegate_->OnMoveLoopEnded();
XDestroyWindow(display, grab_input_window_);
grab_input_window_ = None;
in_move_loop_ = false;
quit_closure_.Run();
}
void X11WholeScreenMoveLoop::SetDragImage(const gfx::ImageSkia& image,
gfx::Vector2dF offset) {
drag_image_ = image;
drag_offset_ = offset;
}
bool X11WholeScreenMoveLoop::GrabPointerAndKeyboard(gfx::NativeCursor cursor) {
XDisplay* display = gfx::GetXDisplay();
XGrabServer(display);
XUngrabPointer(display, CurrentTime);
int ret = XGrabPointer(
display,
grab_input_window_,
False,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync,
GrabModeAsync,
None,
cursor.platform(),
CurrentTime);
if (ret != GrabSuccess) {
DLOG(ERROR) << "Grabbing pointer for dragging failed: "
<< ui::GetX11ErrorString(display, ret);
} else {
has_grab_ = true;
XUngrabKeyboard(display, CurrentTime);
ret = XGrabKeyboard(
display,
grab_input_window_,
False,
GrabModeAsync,
GrabModeAsync,
CurrentTime);
if (ret != GrabSuccess) {
DLOG(ERROR) << "Grabbing keyboard for dragging failed: "
<< ui::GetX11ErrorString(display, ret);
}
}
XUngrabServer(display);
XFlush(display);
return ret == GrabSuccess;
}
Window X11WholeScreenMoveLoop::CreateDragInputWindow(XDisplay* display) {
// Creates an invisible, InputOnly toplevel window. This window will receive
// all mouse movement for drags. It turns out that normal windows doing a
// grab doesn't redirect pointer motion events if the pointer isn't over the
// grabbing window. But InputOnly windows are able to grab everything. This
// is what GTK+ does, and I found a patch to KDE that did something similar.
unsigned long attribute_mask = CWEventMask | CWOverrideRedirect;
XSetWindowAttributes swa;
memset(&swa, 0, sizeof(swa));
swa.event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
KeyPressMask | KeyReleaseMask | StructureNotifyMask;
swa.override_redirect = True;
Window window = XCreateWindow(display,
DefaultRootWindow(display),
-100, -100, 10, 10,
0, CopyFromParent, InputOnly, CopyFromParent,
attribute_mask, &swa);
XMapRaised(display, window);
ui::X11EventSource::GetInstance()->BlockUntilWindowMapped(window);
return window;
}
void X11WholeScreenMoveLoop::CreateDragImageWindow() {
Widget* widget = new Widget;
Widget::InitParams params(Widget::InitParams::TYPE_DRAG);
params.opacity = Widget::InitParams::OPAQUE_WINDOW;
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.accept_events = false;
gfx::Point location = gfx::ToFlooredPoint(
gfx::Screen::GetNativeScreen()->GetCursorScreenPoint() - drag_offset_);
params.bounds = gfx::Rect(location, drag_image_.size());
widget->set_focus_on_creation(false);
widget->set_frame_type(Widget::FRAME_TYPE_FORCE_NATIVE);
widget->Init(params);
widget->SetOpacity(kDragWidgetOpacity);
widget->GetNativeWindow()->SetName("DragWindow");
ImageView* image = new ImageView();
image->SetImage(drag_image_);
image->SetBounds(0, 0, drag_image_.width(), drag_image_.height());
widget->SetContentsView(image);
widget->Show();
widget->GetNativeWindow()->layer()->SetFillsBoundsOpaquely(false);
drag_widget_.reset(widget);
}
bool X11WholeScreenMoveLoop::CheckIfIconValid() {
// Because we need a GL context per window, we do a quick check so that we
// don't make another context if the window would just be displaying a mostly
// transparent image.
const SkBitmap* in_bitmap = drag_image_.bitmap();
SkAutoLockPixels in_lock(*in_bitmap);
for (int y = 0; y < in_bitmap->height(); ++y) {
uint32* in_row = in_bitmap->getAddr32(0, y);
for (int x = 0; x < in_bitmap->width(); ++x) {
if (SkColorGetA(in_row[x]) > kMinAlpha)
return true;
}
}
return false;
}
} // namespace views