blob: 7e9f6df47628e6fcad5e51c599d19e06d54cde33 [file] [log] [blame]
// Copyright 2014 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/platform/x11/x11_event_source.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "ui/base/x/x11_window_event_manager.h"
#include "ui/events/devices/x11/device_data_manager_x11.h"
#include "ui/events/devices/x11/touch_factory_x11.h"
#include "ui/events/event_utils.h"
#include "ui/events/platform/platform_event_dispatcher.h"
#include "ui/events/platform/x11/x11_hotplug_event_handler.h"
#include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_atom_cache.h"
namespace ui {
namespace {
bool InitializeXkb(XDisplay* display) {
if (!display)
return false;
int opcode, event, error;
int major = XkbMajorVersion;
int minor = XkbMinorVersion;
if (!XkbQueryExtension(display, &opcode, &event, &error, &major, &minor)) {
DVLOG(1) << "Xkb extension not available.";
return false;
}
// Ask the server not to send KeyRelease event when the user holds down a key.
// crbug.com/138092
x11::Bool supported_return;
if (!XkbSetDetectableAutoRepeat(display, x11::True, &supported_return)) {
DVLOG(1) << "XKB not supported in the server.";
return false;
}
return true;
}
Time ExtractTimeFromXEvent(const XEvent& xevent) {
switch (xevent.type) {
case KeyPress:
case KeyRelease:
return xevent.xkey.time;
case ButtonPress:
case ButtonRelease:
return xevent.xbutton.time;
case MotionNotify:
return xevent.xmotion.time;
case EnterNotify:
case LeaveNotify:
return xevent.xcrossing.time;
case PropertyNotify:
return xevent.xproperty.time;
case SelectionClear:
return xevent.xselectionclear.time;
case SelectionRequest:
return xevent.xselectionrequest.time;
case SelectionNotify:
return xevent.xselection.time;
case GenericEvent:
if (DeviceDataManagerX11::GetInstance()->IsXIDeviceEvent(xevent))
return static_cast<XIDeviceEvent*>(xevent.xcookie.data)->time;
else
break;
}
return x11::CurrentTime;
}
void UpdateDeviceList() {
XDisplay* display = gfx::GetXDisplay();
DeviceListCacheX11::GetInstance()->UpdateDeviceList(display);
TouchFactory::GetInstance()->UpdateDeviceList(display);
DeviceDataManagerX11::GetInstance()->UpdateDeviceList(display);
}
x11::Bool IsPropertyNotifyForTimestamp(Display* display,
XEvent* event,
XPointer arg) {
return event->type == PropertyNotify &&
event->xproperty.window == *reinterpret_cast<Window*>(arg);
}
} // namespace
X11EventSource* X11EventSource::instance_ = nullptr;
X11EventSource::X11EventSource(X11EventSourceDelegate* delegate,
XDisplay* display)
: delegate_(delegate),
display_(display),
dispatching_event_(nullptr),
dummy_initialized_(false),
continue_stream_(true),
distribution_(0, 999) {
DCHECK(!instance_);
instance_ = this;
DCHECK(delegate_);
DCHECK(display_);
DeviceDataManagerX11::CreateInstance();
InitializeXkb(display_);
}
X11EventSource::~X11EventSource() {
DCHECK_EQ(this, instance_);
instance_ = nullptr;
if (dummy_initialized_)
XDestroyWindow(display_, dummy_window_);
}
bool X11EventSource::HasInstance() {
return instance_;
}
// static
X11EventSource* X11EventSource::GetInstance() {
DCHECK(instance_);
return instance_;
}
////////////////////////////////////////////////////////////////////////////////
// X11EventSource, public
void X11EventSource::DispatchXEvents() {
DCHECK(display_);
// Handle all pending events.
// It may be useful to eventually align this event dispatch with vsync, but
// not yet.
continue_stream_ = true;
while (XPending(display_) && continue_stream_) {
XEvent xevent;
XNextEvent(display_, &xevent);
ExtractCookieDataDispatchEvent(&xevent);
}
}
void X11EventSource::DispatchXEventNow(XEvent* event) {
ExtractCookieDataDispatchEvent(event);
}
Time X11EventSource::GetCurrentServerTime() {
DCHECK(display_);
if (!dummy_initialized_) {
// Create a new Window and Atom that will be used for the property change.
dummy_window_ = XCreateSimpleWindow(display_, DefaultRootWindow(display_),
0, 0, 1, 1, 0, 0, 0);
dummy_atom_ = gfx::GetAtom("CHROMIUM_TIMESTAMP");
dummy_window_events_.reset(
new XScopedEventSelector(dummy_window_, PropertyChangeMask));
dummy_initialized_ = true;
}
// No need to measure Linux.X11.ServerRTT on every call.
// base::TimeTicks::Now() itself has non-trivial overhead.
bool measure_rtt = distribution_(generator_) == 0;
base::TimeTicks start;
if (measure_rtt)
start = base::TimeTicks::Now();
// Make a no-op property change on |dummy_window_|.
XChangeProperty(display_, dummy_window_, dummy_atom_, XA_STRING, 8,
PropModeAppend, nullptr, 0);
// Observe the resulting PropertyNotify event to obtain the timestamp.
XEvent event;
XIfEvent(display_, &event, IsPropertyNotifyForTimestamp,
reinterpret_cast<XPointer>(&dummy_window_));
if (measure_rtt) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Linux.X11.ServerRTT",
(base::TimeTicks::Now() - start).InMicroseconds(), 1,
base::TimeDelta::FromMilliseconds(50).InMicroseconds(), 50);
}
return event.xproperty.time;
}
Time X11EventSource::GetTimestamp() {
if (dispatching_event_) {
Time timestamp = ExtractTimeFromXEvent(*dispatching_event_);
if (timestamp != x11::CurrentTime)
return timestamp;
}
DVLOG(1) << "Making a round trip to get a recent server timestamp.";
return GetCurrentServerTime();
}
#if !defined(USE_OZONE)
base::Optional<gfx::Point>
X11EventSource::GetRootCursorLocationFromCurrentEvent() const {
if (!dispatching_event_)
return base::nullopt;
XEvent* event = dispatching_event_;
DCHECK(event);
bool is_xi2_event = event->type == GenericEvent;
int event_type = is_xi2_event
? reinterpret_cast<XIDeviceEvent*>(event)->evtype
: event->type;
bool is_valid_event = false;
static_assert(XI_ButtonPress == ButtonPress, "");
static_assert(XI_ButtonRelease == ButtonRelease, "");
static_assert(XI_Motion == MotionNotify, "");
static_assert(XI_Enter == EnterNotify, "");
static_assert(XI_Leave == LeaveNotify, "");
switch (event_type) {
case ButtonPress:
case ButtonRelease:
case MotionNotify:
case EnterNotify:
case LeaveNotify:
is_valid_event =
is_xi2_event
? ui::TouchFactory::GetInstance()->ShouldProcessXI2Event(event)
: true;
}
if (is_valid_event)
return ui::EventSystemLocationFromNative(event);
return base::nullopt;
}
#endif
////////////////////////////////////////////////////////////////////////////////
// X11EventSource, protected
void X11EventSource::ExtractCookieDataDispatchEvent(XEvent* xevent) {
bool have_cookie = false;
if (xevent->type == GenericEvent &&
XGetEventData(xevent->xgeneric.display, &xevent->xcookie)) {
have_cookie = true;
}
dispatching_event_ = xevent;
delegate_->ProcessXEvent(xevent);
PostDispatchEvent(xevent);
dispatching_event_ = nullptr;
if (have_cookie)
XFreeEventData(xevent->xgeneric.display, &xevent->xcookie);
}
void X11EventSource::PostDispatchEvent(XEvent* xevent) {
bool should_update_device_list = false;
if (xevent->type == GenericEvent) {
if (xevent->xgeneric.evtype == XI_HierarchyChanged) {
should_update_device_list = true;
} else if (xevent->xgeneric.evtype == XI_DeviceChanged) {
XIDeviceChangedEvent* xev =
static_cast<XIDeviceChangedEvent*>(xevent->xcookie.data);
if (xev->reason == XIDeviceChange) {
should_update_device_list = true;
} else if (xev->reason == XISlaveSwitch) {
ui::DeviceDataManagerX11::GetInstance()->InvalidateScrollClasses(
xev->sourceid);
}
}
}
if (should_update_device_list) {
UpdateDeviceList();
hotplug_event_handler_->OnHotplugEvent();
}
if (xevent->type == EnterNotify &&
xevent->xcrossing.detail != NotifyInferior &&
xevent->xcrossing.mode != NotifyUngrab) {
// Clear stored scroll data
ui::DeviceDataManagerX11::GetInstance()->InvalidateScrollClasses(
DeviceDataManagerX11::kAllDevices);
}
}
void X11EventSource::StopCurrentEventStream() {
continue_stream_ = false;
}
void X11EventSource::OnDispatcherListChanged() {
if (!hotplug_event_handler_) {
hotplug_event_handler_.reset(new X11HotplugEventHandler());
// Force the initial device query to have an update list of active devices.
hotplug_event_handler_->OnHotplugEvent();
}
}
} // namespace ui