blob: 15eb6b6e2c2009055ca53bedd0fb1cb7ad91e587 [file] [log] [blame]
// Copyright 2020 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/ozone/platform/wayland/host/wayland_event_source.h"
#include <algorithm>
#include <memory>
#include "base/bind.h"
#include "base/check.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/pointer_details.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/ozone/platform/wayland/host/wayland_connection.h"
#include "ui/ozone/platform/wayland/host/wayland_event_watcher.h"
#include "ui/ozone/platform/wayland/host/wayland_keyboard.h"
#include "ui/ozone/platform/wayland/host/wayland_window.h"
#include "ui/ozone/platform/wayland/host/wayland_window_manager.h"
namespace ui {
namespace {
bool HasAnyPointerButtonFlag(int flags) {
return (flags & (EF_LEFT_MOUSE_BUTTON | EF_MIDDLE_MOUSE_BUTTON |
EF_RIGHT_MOUSE_BUTTON | EF_BACK_MOUSE_BUTTON |
EF_FORWARD_MOUSE_BUTTON)) != 0;
}
// Number of fingers for scroll gestures.
constexpr int kGestureScrollFingerCount = 2;
// Maximum size of the stored recent pointer frame information.
constexpr int kRecentPointerFrameMaxSize = 20;
} // namespace
struct WaylandEventSource::TouchPoint {
TouchPoint(gfx::PointF location, WaylandWindow* current_window);
~TouchPoint() = default;
WaylandWindow* const window;
gfx::PointF last_known_location;
};
WaylandEventSource::TouchPoint::TouchPoint(gfx::PointF location,
WaylandWindow* current_window)
: window(current_window), last_known_location(location) {
DCHECK(window);
}
// WaylandEventSource implementation
WaylandEventSource::WaylandEventSource(wl_display* display,
WaylandWindowManager* window_manager)
: window_manager_(window_manager),
event_watcher_(std::make_unique<WaylandEventWatcher>(display)) {
DCHECK(window_manager_);
// Observes remove changes to know when touch points can be removed.
window_manager_->AddObserver(this);
}
WaylandEventSource::~WaylandEventSource() = default;
void WaylandEventSource::SetShutdownCb(base::OnceCallback<void()> shutdown_cb) {
event_watcher_->SetShutdownCb(std::move(shutdown_cb));
}
bool WaylandEventSource::StartProcessingEvents() {
return event_watcher_->StartProcessingEvents();
}
bool WaylandEventSource::StopProcessingEvents() {
return event_watcher_->StopProcessingEvents();
}
void WaylandEventSource::OnKeyboardCreated(WaylandKeyboard* keyboard) {
DCHECK(keyboard);
keyboard_ = keyboard;
}
void WaylandEventSource::OnKeyboardDestroyed(WaylandKeyboard* keyboard) {
DCHECK_EQ(keyboard_, keyboard);
keyboard_modifiers_ = 0;
keyboard_ = nullptr;
}
void WaylandEventSource::OnKeyboardFocusChanged(WaylandWindow* window,
bool focused) {
DCHECK(window);
HandleKeyboardFocusChange(window, focused);
}
void WaylandEventSource::OnKeyboardModifiersChanged(int modifiers) {
keyboard_modifiers_ = modifiers;
}
uint32_t WaylandEventSource::OnKeyboardKeyEvent(EventType type,
DomCode dom_code,
bool repeat,
base::TimeTicks timestamp) {
DCHECK(type == ET_KEY_PRESSED || type == ET_KEY_RELEASED);
if (!keyboard_)
return POST_DISPATCH_NONE;
DomKey dom_key;
KeyboardCode key_code;
if (!keyboard_->Decode(dom_code, keyboard_modifiers_, &dom_key, &key_code)) {
LOG(ERROR) << "Failed to decode key event.";
return POST_DISPATCH_NONE;
}
if (!repeat) {
int flag = ModifierDomKeyToEventFlag(dom_key);
UpdateKeyboardModifiers(flag, type == ET_KEY_PRESSED);
}
KeyEvent event(type, key_code, dom_code, keyboard_modifiers_, dom_key,
timestamp);
event.set_source_device_id(keyboard_->device_id());
return DispatchEvent(&event);
}
void WaylandEventSource::OnPointerCreated(WaylandPointer* pointer) {
DCHECK(pointer);
pointer_ = pointer;
}
void WaylandEventSource::OnPointerDestroyed(WaylandPointer* pointer) {
DCHECK_EQ(pointer_, pointer);
// Clear focused window, if any.
HandlePointerFocusChange(nullptr);
ResetPointerFlags();
pointer_ = nullptr;
}
void WaylandEventSource::OnPointerFocusChanged(WaylandWindow* window,
const gfx::PointF& location) {
if (!pointer_)
return;
// Save new pointer location.
pointer_location_ = location;
bool focused = !!window;
if (focused)
HandlePointerFocusChange(window);
EventType type = focused ? ET_MOUSE_ENTERED : ET_MOUSE_EXITED;
MouseEvent event(type, location, location, EventTimeForNow(), pointer_flags_,
0);
DispatchEvent(&event);
if (!focused)
HandlePointerFocusChange(nullptr);
}
void WaylandEventSource::OnPointerButtonEvent(EventType type,
int changed_button,
WaylandWindow* window) {
DCHECK(type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED);
DCHECK(HasAnyPointerButtonFlag(changed_button));
if (!pointer_)
return;
auto* prev_focused_window = window_with_pointer_focus_;
if (window)
HandlePointerFocusChange(window);
pointer_flags_ = type == ET_MOUSE_PRESSED
? (pointer_flags_ | changed_button)
: (pointer_flags_ & ~changed_button);
last_pointer_button_pressed_ = changed_button;
// MouseEvent's flags should contain the button that was released too.
int flags = pointer_flags_ | keyboard_modifiers_ | changed_button;
MouseEvent event(type, pointer_location_, pointer_location_,
EventTimeForNow(), flags, changed_button);
DispatchEvent(&event);
if (window)
HandlePointerFocusChange(prev_focused_window);
}
void WaylandEventSource::OnPointerMotionEvent(const gfx::PointF& location) {
pointer_location_ = location;
int flags = pointer_flags_ | keyboard_modifiers_;
MouseEvent event(ET_MOUSE_MOVED, pointer_location_, pointer_location_,
EventTimeForNow(), flags, 0);
DispatchEvent(&event);
}
void WaylandEventSource::OnPointerAxisEvent(const gfx::Vector2d& offset) {
int flags = pointer_flags_ | keyboard_modifiers_;
MouseWheelEvent event(offset, pointer_location_, pointer_location_,
EventTimeForNow(), flags, 0);
DispatchEvent(&event);
current_pointer_frame_.dx += offset.x();
current_pointer_frame_.dy += offset.y();
}
void WaylandEventSource::OnPointerFrameEvent() {
base::TimeTicks now = EventTimeForNow();
current_pointer_frame_.dt = now - last_pointer_frame_time_;
last_pointer_frame_time_ = now;
// Dispatch Fling event if pointer.axis_stop is notified and the recent
// pointer.axis events meets the criteria to start fling scroll.
if (current_pointer_frame_.dx == 0 && current_pointer_frame_.dy == 0 &&
current_pointer_frame_.is_axis_stop) {
gfx::Vector2dF initial_velocity = ComputeFlingVelocity();
float vx = initial_velocity.x();
float vy = initial_velocity.y();
ScrollEvent event(
vx == 0 && vy == 0 ? ET_SCROLL_FLING_CANCEL : ET_SCROLL_FLING_START,
pointer_location_, pointer_location_, now,
pointer_flags_ | keyboard_modifiers_, vx, vy, vx, vy,
kGestureScrollFingerCount);
DispatchEvent(&event);
recent_pointer_frames_.clear();
} else {
if (recent_pointer_frames_.size() + 1 > kRecentPointerFrameMaxSize)
recent_pointer_frames_.pop_back();
recent_pointer_frames_.push_front(current_pointer_frame_);
}
// Reset |current_pointer_frame_|.
current_pointer_frame_.dx = 0;
current_pointer_frame_.dy = 0;
current_pointer_frame_.is_axis_stop = false;
}
void WaylandEventSource::OnPointerAxisSourceEvent(uint32_t axis_source) {
current_pointer_frame_.axis_source = axis_source;
}
void WaylandEventSource::OnPointerAxisStopEvent(uint32_t axis) {
if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
current_pointer_frame_.dy = 0;
} else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
current_pointer_frame_.dx = 0;
}
current_pointer_frame_.is_axis_stop = true;
}
void WaylandEventSource::OnTouchCreated(WaylandTouch* touch) {
DCHECK(touch);
touch_ = touch;
}
void WaylandEventSource::OnTouchDestroyed(WaylandTouch* touch) {
DCHECK_EQ(touch_, touch);
touch_points_.clear();
touch_ = nullptr;
}
void WaylandEventSource::OnTouchPressEvent(WaylandWindow* window,
const gfx::PointF& location,
base::TimeTicks timestamp,
PointerId id) {
DCHECK(window);
HandleTouchFocusChange(window, true);
// Make sure this touch point wasn't present before.
auto success = touch_points_.try_emplace(
id, std::make_unique<TouchPoint>(location, window));
if (!success.second) {
LOG(WARNING) << "Touch down fired with wrong id";
return;
}
PointerDetails details(EventPointerType::kTouch, id);
TouchEvent event(ET_TOUCH_PRESSED, location, location, timestamp, details);
DispatchEvent(&event);
}
void WaylandEventSource::OnTouchReleaseEvent(base::TimeTicks timestamp,
PointerId id) {
// Make sure this touch point was present before.
const auto it = touch_points_.find(id);
if (it == touch_points_.end()) {
LOG(WARNING) << "Touch up fired with no matching touch down";
return;
}
TouchPoint* touch_point = it->second.get();
gfx::PointF location = touch_point->last_known_location;
PointerDetails details(EventPointerType::kTouch, id);
TouchEvent event(ET_TOUCH_RELEASED, location, location, timestamp, details);
DispatchEvent(&event);
HandleTouchFocusChange(touch_point->window, false, id);
touch_points_.erase(it);
}
void WaylandEventSource::OnTouchMotionEvent(const gfx::PointF& location,
base::TimeTicks timestamp,
PointerId id) {
const auto it = touch_points_.find(id);
// Make sure this touch point was present before.
if (it == touch_points_.end()) {
LOG(WARNING) << "Touch event fired with wrong id";
return;
}
it->second->last_known_location = location;
PointerDetails details(EventPointerType::kTouch, id);
TouchEvent event(ET_TOUCH_MOVED, location, location, timestamp, details);
DispatchEvent(&event);
}
void WaylandEventSource::OnTouchCancelEvent() {
gfx::PointF location;
base::TimeTicks timestamp = base::TimeTicks::Now();
for (auto& touch_point : touch_points_) {
PointerId id = touch_point.first;
TouchEvent event(ET_TOUCH_CANCELLED, location, location, timestamp,
PointerDetails(EventPointerType::kTouch, id));
DispatchEvent(&event);
HandleTouchFocusChange(touch_point.second->window, false);
}
touch_points_.clear();
}
bool WaylandEventSource::IsPointerButtonPressed(EventFlags button) const {
DCHECK(HasAnyPointerButtonFlag(button));
return pointer_flags_ & button;
}
void WaylandEventSource::ResetPointerFlags() {
pointer_flags_ = 0;
}
void WaylandEventSource::OnDispatcherListChanged() {
StartProcessingEvents();
}
void WaylandEventSource::OnWindowRemoved(WaylandWindow* window) {
// Clear pointer-related data.
if (window == window_with_pointer_focus_)
window_with_pointer_focus_ = nullptr;
// Clear touch-related data.
base::EraseIf(touch_points_, [window](const auto& point) {
return point.second->window == window;
});
}
// Currently EF_MOD3_DOWN means that the CapsLock key is currently down, and
// EF_CAPS_LOCK_ON means the caps lock state is enabled (and the key may or
// may not be down, but usually isn't). There does need to be two different
// flags, since the physical CapsLock key is subject to remapping, but the
// caps lock state (which can be triggered in a variety of ways) is not.
//
// TODO(crbug.com/1076661): This is likely caused by some CrOS-specific code.
// Get rid of this function once it is properly guarded under OS_CHROMEOS.
void WaylandEventSource::UpdateKeyboardModifiers(int modifier, bool down) {
if (modifier == EF_NONE)
return;
if (modifier == EF_CAPS_LOCK_ON) {
modifier = (modifier & ~EF_CAPS_LOCK_ON) | EF_MOD3_DOWN;
}
keyboard_modifiers_ = down ? (keyboard_modifiers_ | modifier)
: (keyboard_modifiers_ & ~modifier);
}
void WaylandEventSource::HandleKeyboardFocusChange(WaylandWindow* window,
bool focused) {
DCHECK(window);
window->set_keyboard_focus(focused);
}
void WaylandEventSource::HandlePointerFocusChange(WaylandWindow* window) {
// Focused window might have been destroyed at this point (eg: context menus),
// in this case, |window| is null.
if (window_with_pointer_focus_)
window_with_pointer_focus_->SetPointerFocus(false);
window_with_pointer_focus_ = window;
if (window_with_pointer_focus_)
window_with_pointer_focus_->SetPointerFocus(true);
}
void WaylandEventSource::HandleTouchFocusChange(WaylandWindow* window,
bool focused,
base::Optional<PointerId> id) {
DCHECK(window);
bool actual_focus = id ? !ShouldUnsetTouchFocus(window, id.value()) : focused;
window->set_touch_focus(actual_focus);
}
// Focus must not be unset if there is another touch point within |window|.
bool WaylandEventSource::ShouldUnsetTouchFocus(WaylandWindow* win,
PointerId id) {
auto result = std::find_if(
touch_points_.begin(), touch_points_.end(),
[win, id](auto& p) { return p.second->window == win && p.first != id; });
return result == touch_points_.end();
}
gfx::Vector2dF WaylandEventSource::ComputeFlingVelocity() {
// Return average velocity in the last 200ms.
// TODO(fukino): Make the formula similar to libgestures's
// RegressScrollVelocity(). crbug.com/1129263.
base::TimeDelta dt;
float dx = 0.0f;
float dy = 0.0f;
for (auto& frame : recent_pointer_frames_) {
if (frame.axis_source != WL_POINTER_AXIS_SOURCE_FINGER)
break;
if (frame.dx == 0 && frame.dy == 0)
break;
if (dt + frame.dt > base::TimeDelta::FromMilliseconds(200))
break;
dx += frame.dx;
dy += frame.dy;
dt += frame.dt;
}
float dt_inv = 1.0f / dt.InSecondsF();
return dt.is_zero() ? gfx::Vector2dF()
: gfx::Vector2dF(dx * dt_inv, dy * dt_inv);
}
} // namespace ui