blob: 415621b8e9f601257c2359d0a84ac5b960970120 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/x/x11_whole_screen_move_loop.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "ui/base/x/x11_pointer_grab.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/platform_event_source.h"
#include "ui/events/platform/scoped_event_dispatcher.h"
#include "ui/events/x/events_x_utils.h"
#include "ui/events/x/x11_event_translation.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/keysyms/keysyms.h"
#include "ui/gfx/x/window_event_manager.h"
#include "ui/gfx/x/xproto.h"
namespace ui {
namespace {
// XGrabKey requires the modifier mask to explicitly be specified.
constexpr x11::ModMask kModifiersMasks[] = {
{}, // No additional modifier.
x11::ModMask::c_2, // Num lock
x11::ModMask::Lock, // Caps lock
x11::ModMask::c_5, // Scroll lock
x11::ModMask::c_2 | x11::ModMask::Lock,
x11::ModMask::c_2 | x11::ModMask::c_5,
x11::ModMask::Lock | x11::ModMask::c_5,
x11::ModMask::c_2 | x11::ModMask::Lock | x11::ModMask::c_5,
};
const char* GrabStatusToString(x11::GrabStatus grab_status) {
switch (grab_status) {
case x11::GrabStatus::Success:
return "Success";
case x11::GrabStatus::AlreadyGrabbed:
return "AlreadyGrabbed";
case x11::GrabStatus::InvalidTime:
return "InvalidTime";
case x11::GrabStatus::NotViewable:
return "NotViewable";
case x11::GrabStatus::Frozen:
return "Frozen";
}
NOTREACHED();
}
} // namespace
X11WholeScreenMoveLoop::X11WholeScreenMoveLoop(X11MoveLoopDelegate* delegate)
: delegate_(delegate),
in_move_loop_(false),
grab_input_window_(x11::Window::None),
grabbed_pointer_(false),
canceled_(false) {}
X11WholeScreenMoveLoop::~X11WholeScreenMoveLoop() {
EndMoveLoop();
}
void X11WholeScreenMoveLoop::PostDispatchIfNeeded(const ui::MouseEvent& event) {
}
////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostLinux, ui::PlatformEventDispatcher implementation:
bool X11WholeScreenMoveLoop::CanDispatchEvent(const ui::PlatformEvent& event) {
return in_move_loop_;
}
uint32_t X11WholeScreenMoveLoop::DispatchEvent(const ui::PlatformEvent& event) {
DCHECK(base::CurrentUIThread::IsSet());
// This method processes all events while the move loop is active.
if (!in_move_loop_) {
return ui::POST_DISPATCH_PERFORM_DEFAULT;
}
switch (event->type()) {
case ui::EventType::kMouseMoved:
case ui::EventType::kMouseDragged: {
auto& current_xevent = *x11::Connection::Get()->dispatching_event();
x11::Event last_xevent;
std::unique_ptr<ui::Event> last_motion;
auto* mouse_event = event->AsMouseEvent();
if ((current_xevent.As<x11::MotionNotifyEvent>() ||
current_xevent.As<x11::Input::DeviceEvent>()) &&
ui::CoalescePendingMotionEvents(current_xevent, &last_xevent)) {
last_motion = ui::BuildEventFromXEvent(last_xevent);
mouse_event = last_motion->AsMouseEvent();
}
delegate_->OnMouseMovement(mouse_event->root_location(),
mouse_event->flags(),
mouse_event->time_stamp());
return ui::POST_DISPATCH_NONE;
}
case ui::EventType::kMouseReleased: {
if (event->AsMouseEvent()->IsLeftMouseButton()) {
// Assume that drags are being done with the left mouse button. Only
// break the drag if the left mouse button was released.
delegate_->OnMouseReleased();
if (!grabbed_pointer_) {
// If the source widget had capture prior to the move loop starting,
// it may be relying on views::Widget getting the mouse release and
// releasing capture in Widget::OnMouseEvent().
return ui::POST_DISPATCH_PERFORM_DEFAULT;
}
}
return ui::POST_DISPATCH_NONE;
}
case ui::EventType::kKeyPressed:
if (event->AsKeyEvent()->key_code() == ui::VKEY_ESCAPE) {
canceled_ = true;
EndMoveLoop();
return ui::POST_DISPATCH_NONE;
}
break;
default:
break;
}
return ui::POST_DISPATCH_PERFORM_DEFAULT;
}
bool X11WholeScreenMoveLoop::RunMoveLoop(
bool can_grab_pointer,
scoped_refptr<ui::X11Cursor> old_cursor,
scoped_refptr<ui::X11Cursor> new_cursor,
base::OnceClosure started_callback) {
DCHECK(!in_move_loop_); // Can only handle one nested loop at a time.
// Query the mouse cursor prior to the move loop starting so that it can be
// restored when the move loop finishes.
initial_cursor_ = old_cursor;
auto* connection = x11::Connection::Get();
CreateDragInputWindow(connection);
// Only grab mouse capture of |grab_input_window_| if |can_grab_pointer| is
// true aka the source that initiated the move loop doesn't have explicit
// grab.
// - The caller may intend to transfer capture to a different X11Window
// when the move loop ends and not release capture.
// - Releasing capture and X window destruction are both asynchronous. We drop
// events targeted at |grab_input_window_| in the time between the move
// loop ends and |grab_input_window_| loses capture.
grabbed_pointer_ = false;
if (can_grab_pointer) {
grabbed_pointer_ = GrabPointer(new_cursor);
if (!grabbed_pointer_) {
x11::Connection::Get()->DestroyWindow({grab_input_window_});
return false;
}
}
GrabEscKey();
std::unique_ptr<ui::ScopedEventDispatcher> old_dispatcher =
std::move(nested_dispatcher_);
nested_dispatcher_ =
ui::PlatformEventSource::GetInstance()->OverrideDispatcher(this);
std::move(started_callback).Run();
base::WeakPtr<X11WholeScreenMoveLoop> alive(weak_factory_.GetWeakPtr());
in_move_loop_ = true;
canceled_ = false;
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
if (!alive) {
return false;
}
nested_dispatcher_ = std::move(old_dispatcher);
return !canceled_;
}
void X11WholeScreenMoveLoop::UpdateCursor(scoped_refptr<ui::X11Cursor> cursor) {
if (in_move_loop_) {
ui::ChangeActivePointerGrabCursor(cursor);
}
}
void X11WholeScreenMoveLoop::EndMoveLoop() {
if (!in_move_loop_) {
return;
}
// 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.
if (grabbed_pointer_) {
ui::UngrabPointer();
} else {
UpdateCursor(initial_cursor_);
}
auto* connection = x11::Connection::Get();
auto esc_keycode = connection->KeysymToKeycode(XK_Escape);
for (auto mask : kModifiersMasks) {
connection->UngrabKey({esc_keycode, grab_input_window_, mask});
}
// Restore the previous dispatcher.
nested_dispatcher_.reset();
delegate_->OnMoveLoopEnded();
grab_input_window_events_.Reset();
connection->DestroyWindow({grab_input_window_});
grab_input_window_ = x11::Window::None;
in_move_loop_ = false;
std::move(quit_closure_).Run();
}
bool X11WholeScreenMoveLoop::GrabPointer(scoped_refptr<X11Cursor> cursor) {
auto* connection = x11::Connection::Get();
// Pass "owner_events" as false so that X sends all mouse events to
// |grab_input_window_|.
auto ret = ui::GrabPointer(grab_input_window_, false, cursor);
if (ret != x11::GrabStatus::Success) {
DLOG(ERROR) << "Grabbing pointer for dragging failed: "
<< GrabStatusToString(ret);
}
connection->Flush();
return ret == x11::GrabStatus::Success;
}
void X11WholeScreenMoveLoop::GrabEscKey() {
auto* connection = x11::Connection::Get();
auto esc_keycode = connection->KeysymToKeycode(XK_Escape);
for (auto mask : kModifiersMasks) {
connection->GrabKey({false, grab_input_window_, mask, esc_keycode,
x11::GrabMode::Async, x11::GrabMode::Async});
}
}
void X11WholeScreenMoveLoop::CreateDragInputWindow(
x11::Connection* connection) {
grab_input_window_ = connection->GenerateId<x11::Window>();
connection->CreateWindow(x11::CreateWindowRequest{
.wid = grab_input_window_,
.parent = connection->default_root(),
.x = -100,
.y = -100,
.width = 10,
.height = 10,
.c_class = x11::WindowClass::InputOnly,
.override_redirect = x11::Bool32(true),
});
auto event_mask =
x11::EventMask::ButtonPress | x11::EventMask::ButtonRelease |
x11::EventMask::PointerMotion | x11::EventMask::KeyPress |
x11::EventMask::KeyRelease | x11::EventMask::StructureNotify;
grab_input_window_events_ =
connection->ScopedSelectEvent(grab_input_window_, event_mask);
connection->MapWindow(grab_input_window_);
connection->RaiseWindow(grab_input_window_);
}
} // namespace ui