blob: 337273f189ed4abb2d15bf270e5031434208116f [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 <X11/Xatom.h>
#include <X11/XKBlib.h>
#include <X11/Xlib.h>
#include "base/logging.h"
#include "base/metrics/histogram_macros.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"
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
Bool supported_return;
if (!XkbSetDetectableAutoRepeat(display, 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 CurrentTime;
}
void UpdateDeviceList() {
XDisplay* display = gfx::GetXDisplay();
DeviceListCacheX11::GetInstance()->UpdateDeviceList(display);
TouchFactory::GetInstance()->UpdateDeviceList(display);
DeviceDataManagerX11::GetInstance()->UpdateDeviceList(display);
}
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),
last_seen_server_time_(CurrentTime),
dummy_initialized_(false),
continue_stream_(true) {
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::BlockUntilWindowMapped(XID window) {
BlockOnWindowStructureEvent(window, MapNotify);
}
void X11EventSource::BlockUntilWindowUnmapped(XID window) {
BlockOnWindowStructureEvent(window, UnmapNotify);
}
Time X11EventSource::UpdateLastSeenServerTime() {
base::TimeTicks start = base::TimeTicks::Now();
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_ = XInternAtom(display_, "CHROMIUM_TIMESTAMP", False);
XSelectInput(display_, dummy_window_, PropertyChangeMask);
dummy_initialized_ = true;
}
// 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_));
last_seen_server_time_ = event.xproperty.time;
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Event.Latency.X11EventSource.UpdateServerTime",
(base::TimeTicks::Now() - start).InMicroseconds(), 0,
base::TimeDelta::FromMilliseconds(1).InMicroseconds(), 50);
return last_seen_server_time_;
}
////////////////////////////////////////////////////////////////////////////////
// X11EventSource, protected
void X11EventSource::ExtractCookieDataDispatchEvent(XEvent* xevent) {
bool have_cookie = false;
if (xevent->type == GenericEvent &&
XGetEventData(xevent->xgeneric.display, &xevent->xcookie)) {
have_cookie = true;
}
Time event_time = ExtractTimeFromXEvent(*xevent);
if (event_time != CurrentTime) {
int64_t event_time_64 = event_time;
int64_t time_difference = last_seen_server_time_ - event_time_64;
// Ignore timestamps that go backwards. However, X server time is a 32-bit
// millisecond counter, so if the time goes backwards by more than half the
// range of the 32-bit counter, treat it as a rollover.
if (time_difference < 0 || time_difference > (UINT32_MAX >> 1))
last_seen_server_time_ = event_time;
}
delegate_->ProcessXEvent(xevent);
PostDispatchEvent(xevent);
if (have_cookie)
XFreeEventData(xevent->xgeneric.display, &xevent->xcookie);
}
void X11EventSource::PostDispatchEvent(XEvent* xevent) {
if (xevent->type == GenericEvent &&
(xevent->xgeneric.evtype == XI_HierarchyChanged ||
xevent->xgeneric.evtype == XI_DeviceChanged)) {
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();
}
}
void X11EventSource::BlockOnWindowStructureEvent(XID window, int type) {
XEvent event;
do {
// Block until there's a StructureNotify event of |type| on |window|. Then
// remove it from the queue and stuff it in |event|.
XWindowEvent(display_, window, StructureNotifyMask, &event);
ExtractCookieDataDispatchEvent(&event);
} while (event.type != type);
}
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