| // 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 |