blob: 0858b17540b4a5553f2a9ed736557135caec1e7b [file] [log] [blame]
// Copyright 2019 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/events/x/x11_event_translation.h"
#include <vector>
#include "base/check.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "ui/events/devices/x11/touch_factory_x11.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/events/pointer_details.h"
#include "ui/events/types/event_type.h"
#include "ui/events/x/events_x_utils.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/x/xproto.h"
#if defined(USE_OZONE)
#include "ui/base/ui_base_features.h"
#endif
namespace ui {
namespace {
int XkbGroupForCoreState(int state) {
return (state >> 13) & 0x3;
}
// In X11 touch events, a new tracking_id/slot mapping is set up for each new
// event (see |ui::GetTouchIdFromXEvent| function), which needs to be cleared
// at destruction time for corresponding release/cancel events. In this
// particular case, ui::TouchEvent class is extended so that dtor can be
// overridden in order to implement this platform-specific behavior.
class TouchEventX11 : public ui::TouchEvent {
public:
TouchEventX11(EventType type,
gfx::Point location,
base::TimeTicks timestamp,
const PointerDetails& pointer_details)
: TouchEvent(type, location, timestamp, pointer_details) {}
~TouchEventX11() override {
if (type() == ET_TOUCH_RELEASED || type() == ET_TOUCH_CANCELLED)
TouchFactory::GetInstance()->ReleaseSlot(pointer_details().id);
}
};
Event::Properties GetEventPropertiesFromXEvent(EventType type,
const x11::Event& x11_event) {
using Values = std::vector<uint8_t>;
Event::Properties properties;
if (type == ET_KEY_PRESSED || type == ET_KEY_RELEASED) {
auto* key = x11_event.As<x11::KeyEvent>();
// Keyboard group
auto state = static_cast<uint32_t>(key->state);
uint8_t group = XkbGroupForCoreState(state);
properties.emplace(kPropertyKeyboardGroup, Values{group});
// Hardware keycode
uint8_t hw_keycode = static_cast<uint8_t>(key->detail);
properties.emplace(kPropertyKeyboardHwKeyCode, Values{hw_keycode});
// IBus-gtk specific flags
uint8_t ibus_flags = (state >> kPropertyKeyboardIBusFlagOffset) &
kPropertyKeyboardIBusFlagMask;
if (ibus_flags)
properties.emplace(kPropertyKeyboardIBusFlag, Values{ibus_flags});
} else if (type == ET_MOUSE_EXITED) {
// NotifyVirtual events are created for intermediate windows that the
// pointer crosses through. These occur when middle clicking.
// Change these into mouse move events.
auto* crossing = x11_event.As<x11::CrossingEvent>();
bool crossing_intermediate_window =
crossing->detail == x11::NotifyDetail::Virtual;
if (crossing_intermediate_window) {
properties.emplace(kPropertyMouseCrossedIntermediateWindow,
crossing_intermediate_window);
}
}
return properties;
}
std::unique_ptr<KeyEvent> CreateKeyEvent(EventType event_type,
const x11::Event& x11_event) {
KeyboardCode key_code = KeyboardCodeFromXKeyEvent(x11_event);
int event_flags = EventFlagsFromXEvent(x11_event);
// In Ozone builds, keep DomCode/DomKey unset, so they are extracted lazily
// in KeyEvent::ApplyLayout() which makes it possible for CrOS/Linux, for
// example, to support host system keyboard layouts.
std::unique_ptr<KeyEvent> event;
#if defined(USE_OZONE)
if (features::IsUsingOzonePlatform()) {
event = std::make_unique<KeyEvent>(event_type, key_code, event_flags,
EventTimeFromXEvent(x11_event));
}
#endif
#if defined(USE_X11)
if (!event) {
event = std::make_unique<KeyEvent>(
event_type, key_code, CodeFromXEvent(x11_event), event_flags,
GetDomKeyFromXEvent(x11_event), EventTimeFromXEvent(x11_event));
}
#endif
DCHECK(event);
event->SetProperties(GetEventPropertiesFromXEvent(event_type, x11_event));
event->InitializeNative();
return event;
}
void SetEventSourceDeviceId(MouseEvent* event, const x11::Event& xev) {
DCHECK(event);
if (auto* xiev = xev.As<x11::Input::DeviceEvent>())
event->set_source_device_id(static_cast<uint16_t>(xiev->sourceid));
}
std::unique_ptr<MouseEvent> CreateMouseEvent(EventType type,
const x11::Event& x11_event) {
// Ignore EventNotify and LeaveNotify events from children of |xwindow_|.
// NativeViewGLSurfaceGLX adds a child to |xwindow_|.
// https://crbug.com/792322
auto* crossing = x11_event.As<x11::CrossingEvent>();
if (crossing && crossing->detail == x11::NotifyDetail::Inferior)
return nullptr;
PointerDetails details{EventPointerType::kMouse};
auto event = std::make_unique<MouseEvent>(
type, EventLocationFromXEvent(x11_event),
EventSystemLocationFromXEvent(x11_event), EventTimeFromXEvent(x11_event),
EventFlagsFromXEvent(x11_event),
GetChangedMouseButtonFlagsFromXEvent(x11_event), details);
DCHECK(event);
SetEventSourceDeviceId(event.get(), x11_event);
event->SetProperties(GetEventPropertiesFromXEvent(type, x11_event));
event->InitializeNative();
return event;
}
std::unique_ptr<MouseWheelEvent> CreateMouseWheelEvent(
const x11::Event& x11_event) {
int button_flags = x11_event.As<x11::Input::DeviceEvent>()
? GetChangedMouseButtonFlagsFromXEvent(x11_event)
: 0;
auto event = std::make_unique<MouseWheelEvent>(
GetMouseWheelOffsetFromXEvent(x11_event),
EventLocationFromXEvent(x11_event),
EventSystemLocationFromXEvent(x11_event), EventTimeFromXEvent(x11_event),
EventFlagsFromXEvent(x11_event), button_flags);
DCHECK(event);
event->InitializeNative();
return event;
}
std::unique_ptr<TouchEvent> CreateTouchEvent(EventType type,
const x11::Event& xev) {
auto event = std::make_unique<TouchEventX11>(
type, EventLocationFromXEvent(xev), EventTimeFromXEvent(xev),
GetTouchPointerDetailsFromXEvent(xev));
#if defined(USE_OZONE)
if (features::IsUsingOzonePlatform()) {
// Touch events don't usually have |root_location| set differently than
// |location|, since there is a touch device to display association, but
// this doesn't happen in Ozone X11.
event->set_root_location(EventSystemLocationFromXEvent(xev));
}
#endif
return event;
}
std::unique_ptr<ScrollEvent> CreateScrollEvent(EventType type,
const x11::Event& xev) {
float x_offset, y_offset, x_offset_ordinal, y_offset_ordinal;
int finger_count = 0;
if (type == ET_SCROLL) {
GetScrollOffsetsFromXEvent(xev, &x_offset, &y_offset, &x_offset_ordinal,
&y_offset_ordinal, &finger_count);
} else {
GetFlingDataFromXEvent(xev, &x_offset, &y_offset, &x_offset_ordinal,
&y_offset_ordinal, nullptr);
}
auto event = std::make_unique<ScrollEvent>(
type, EventLocationFromXEvent(xev), EventTimeFromXEvent(xev),
EventFlagsFromXEvent(xev), x_offset, y_offset, x_offset_ordinal,
y_offset_ordinal, finger_count);
DCHECK(event);
// We need to filter zero scroll offset here. Because MouseWheelEventQueue
// assumes we'll never get a zero scroll offset event and we need delta to
// determine which element to scroll on phaseBegan.
return (event->x_offset() != 0.0 || event->y_offset() != 0.0)
? std::move(event)
: nullptr;
}
// Translates XI2 XEvent into a ui::Event.
std::unique_ptr<ui::Event> TranslateFromXI2Event(const x11::Event& xev,
EventType event_type) {
switch (event_type) {
case ET_KEY_PRESSED:
case ET_KEY_RELEASED:
return CreateKeyEvent(event_type, xev);
case ET_MOUSE_PRESSED:
case ET_MOUSE_RELEASED:
case ET_MOUSE_MOVED:
case ET_MOUSE_DRAGGED:
case ET_MOUSE_ENTERED:
case ET_MOUSE_EXITED:
return CreateMouseEvent(event_type, xev);
case ET_MOUSEWHEEL:
return CreateMouseWheelEvent(xev);
case ET_SCROLL_FLING_START:
case ET_SCROLL_FLING_CANCEL:
case ET_SCROLL:
return CreateScrollEvent(event_type, xev);
case ET_TOUCH_MOVED:
case ET_TOUCH_PRESSED:
case ET_TOUCH_CANCELLED:
case ET_TOUCH_RELEASED:
return CreateTouchEvent(event_type, xev);
case ET_UNKNOWN:
return nullptr;
default:
break;
}
return nullptr;
}
std::unique_ptr<Event> TranslateFromXEvent(const x11::Event& xev) {
EventType event_type = EventTypeFromXEvent(xev);
if (xev.As<x11::CrossingEvent>() || xev.As<x11::MotionNotifyEvent>())
return CreateMouseEvent(event_type, xev);
if (xev.As<x11::KeyEvent>())
return CreateKeyEvent(event_type, xev);
if (xev.As<x11::ButtonEvent>()) {
switch (event_type) {
case ET_MOUSEWHEEL:
return CreateMouseWheelEvent(xev);
case ET_MOUSE_PRESSED:
case ET_MOUSE_RELEASED:
return CreateMouseEvent(event_type, xev);
case ET_UNKNOWN:
// No event is created for X11-release events for mouse-wheel
// buttons.
break;
default:
NOTREACHED();
}
}
if (xev.As<x11::Input::DeviceEvent>())
return TranslateFromXI2Event(xev, event_type);
return nullptr;
}
} // namespace
// Translates a XEvent into a ui::Event.
std::unique_ptr<Event> BuildEventFromXEvent(const x11::Event& xev) {
auto event = TranslateFromXEvent(xev);
if (event)
ui::ComputeEventLatencyOS(event.get());
return event;
}
// Convenience function that translates XEvent into ui::KeyEvent
std::unique_ptr<KeyEvent> BuildKeyEventFromXEvent(const x11::Event& xev) {
auto event = BuildEventFromXEvent(xev);
if (!event || !event->IsKeyEvent())
return nullptr;
return std::unique_ptr<KeyEvent>{event.release()->AsKeyEvent()};
}
// Convenience function that translates XEvent into ui::MouseEvent
std::unique_ptr<MouseEvent> BuildMouseEventFromXEvent(const x11::Event& xev) {
auto event = BuildEventFromXEvent(xev);
if (!event || !event->IsMouseEvent())
return nullptr;
return std::unique_ptr<MouseEvent>{event.release()->AsMouseEvent()};
}
// Convenience function that translates XEvent into ui::TouchEvent
std::unique_ptr<TouchEvent> BuildTouchEventFromXEvent(const x11::Event& xev) {
auto event = BuildEventFromXEvent(xev);
if (!event || !event->IsTouchEvent())
return nullptr;
return std::unique_ptr<TouchEvent>{event.release()->AsTouchEvent()};
}
// Convenience function that translates XEvent into ui::MouseWheelEvent
std::unique_ptr<MouseWheelEvent> BuildMouseWheelEventFromXEvent(
const x11::Event& xev) {
auto event = BuildEventFromXEvent(xev);
if (!event || !event->IsMouseWheelEvent())
return nullptr;
return std::unique_ptr<MouseWheelEvent>{event.release()->AsMouseWheelEvent()};
}
} // namespace ui