diff options
-rw-r--r-- | base/message_pump_glib_x.cc | 143 | ||||
-rw-r--r-- | base/message_pump_glib_x.h | 22 | ||||
-rw-r--r-- | build/linux/system.gyp | 12 | ||||
-rw-r--r-- | views/event_x.cc | 62 | ||||
-rw-r--r-- | views/focus/accelerator_handler_touch.cc | 84 |
5 files changed, 318 insertions, 5 deletions
diff --git a/base/message_pump_glib_x.cc b/base/message_pump_glib_x.cc index 675774e..78c1799 100644 --- a/base/message_pump_glib_x.cc +++ b/base/message_pump_glib_x.cc @@ -5,7 +5,11 @@ #include "base/message_pump_glib_x.h" #include <gdk/gdkx.h> +#if defined(HAVE_XINPUT2) +#include <X11/extensions/XInput2.h> +#else #include <X11/Xlib.h> +#endif #include "base/message_pump_glib_x_dispatch.h" @@ -17,17 +21,61 @@ gboolean PlaceholderDispatch(GSource* source, return TRUE; } +#if defined(HAVE_XINPUT2) + +// Setup XInput2 select for the GtkWidget. +gboolean GtkWidgetRealizeCallback(GSignalInvocationHint* hint, guint nparams, + const GValue* pvalues, gpointer data) { + GtkWidget* widget = GTK_WIDGET(g_value_get_object(pvalues)); + GdkWindow* window = widget->window; + base::MessagePumpGlibX* msgpump = static_cast<base::MessagePumpGlibX*>(data); + + DCHECK(window); // TODO(sad): Remove once determined if necessary. + + // TODO(sad): Do we need to set a flag on |window| to make sure we don't + // select for the same GdkWindow multiple times? Does it matter? + msgpump->SetupXInput2ForXWindow(GDK_WINDOW_XID(window)); + + return true; +} + +// We need to capture all the GDK windows that get created, and start +// listening for XInput2 events. So we setup a callback to the 'realize' +// signal for GTK+ widgets, so that whenever the signal triggers for any +// GtkWidget, which means the GtkWidget should now have a GdkWindow, we can +// setup XInput2 events for the GdkWindow. +void SetupGtkWidgetRealizeNotifier(base::MessagePumpGlibX* msgpump) { + guint signal_id; + gpointer klass = g_type_class_ref(GTK_TYPE_WIDGET); + + g_signal_parse_name("realize", GTK_TYPE_WIDGET, &signal_id, NULL, FALSE); + g_signal_add_emission_hook(signal_id, 0, GtkWidgetRealizeCallback, + static_cast<gpointer>(msgpump), NULL); + + g_type_class_unref(klass); +} + +#endif // HAVE_XINPUT2 + } // namespace namespace base { MessagePumpGlibX::MessagePumpGlibX() : base::MessagePumpForUI(), +#if defined(HAVE_XINPUT2) + xiopcode_(-1), + masters_(), + slaves_(), +#endif gdksource_(NULL), dispatching_event_(false), capture_x_events_(0), capture_gdk_events_(0) { gdk_event_handler_set(&EventDispatcherX, this, NULL); +#if defined(HAVE_XINPUT2) + InitializeXInput2(); +#endif InitializeEventsToCapture(); } @@ -40,7 +88,11 @@ bool MessagePumpGlibX::RunOnce(GMainContext* context, bool block) { if (XPending(display)) { XEvent xev; XPeekEvent(display, &xev); - if (capture_x_events_[xev.type]) { + if (capture_x_events_[xev.type] +#if defined(HAVE_XINPUT2) + && (xev.type != GenericEvent || xev.xcookie.extension == xiopcode_) +#endif + ) { XNextEvent(display, &xev); bool processed = static_cast<MessagePumpGlibXDispatcher*> @@ -48,6 +100,13 @@ bool MessagePumpGlibX::RunOnce(GMainContext* context, bool block) { if (!processed) { DLOG(WARNING) << "Event (" << xev.type << ") not handled."; + + // TODO(sad): It is necessary to put back the event so that the default + // GDK events handler can take care of it. Without this, it is + // impossible to use the omnibox at the moment. However, this will + // eventually be removed once the omnibox code is updated for touchui. + XPutBackEvent(display, &xev); + g_main_context_iteration(context, FALSE); } } else { // TODO(sad): A couple of extra events can still sneak in during this. @@ -94,8 +153,90 @@ void MessagePumpGlibX::InitializeEventsToCapture(void) { capture_x_events_[MotionNotify] = true; capture_gdk_events_[GDK_MOTION_NOTIFY] = true; + +#if defined(HAVE_XINPUT2) + capture_x_events_[GenericEvent] = true; +#endif +} + +#if defined(HAVE_XINPUT2) +void MessagePumpGlibX::InitializeXInput2(void) { + GdkDisplay* display = gdk_display_get_default(); + Display* xdisplay = GDK_DISPLAY_XDISPLAY(display); + int event, err; + + if (!XQueryExtension(xdisplay, "XInputExtension", &xiopcode_, &event, &err)) { + DLOG(WARNING) << "X Input extension not available."; + xiopcode_ = -1; + return; + } + + int major = 2, minor = 0; + if (XIQueryVersion(xdisplay, &major, &minor) == BadRequest) { + DLOG(WARNING) << "XInput2 not supported in the server."; + xiopcode_ = -1; + return; + } + + SetupGtkWidgetRealizeNotifier(this); + + // Instead of asking X for the list of devices all the time, let's maintain a + // list of slave (physical) and master (virtual) pointer devices. + int count = 0; + XIDeviceInfo* devices = XIQueryDevice(xdisplay, XIAllDevices, &count); + for (int i = 0; i < count; i++) { + XIDeviceInfo* devinfo = devices + i; + if (devinfo->use == XISlavePointer) { + slaves_.insert(devinfo->deviceid); + } else if (devinfo->use == XIMasterPointer) { + masters_.insert(devinfo->deviceid); + } + // We do not need to care about XIFloatingSlave, because the callback for + // XI_HierarchyChanged event will take care of it. + } + XIFreeDeviceInfo(devices); + + // TODO(sad): Select on root for XI_HierarchyChanged so that slaves_ and + // masters_ can be kept up-to-date. This is a relatively rare event, so we can + // put it off for a later time. + // Note: It is not necessary to listen for XI_DeviceChanged events. } +void MessagePumpGlibX::SetupXInput2ForXWindow(Window xwindow) { + Display* xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + + // Setup mask for mouse events. + unsigned char mask[(XI_LASTEVENT + 7)/8]; + memset(mask, 0, sizeof(mask)); + + XISetMask(mask, XI_ButtonPress); + XISetMask(mask, XI_ButtonRelease); + XISetMask(mask, XI_Motion); + + // It is necessary to select only for the master devices. XInput2 provides + // enough information to the event callback to decide which slave device + // triggered the event, thus decide whether the 'pointer event' is a 'mouse + // event' or a 'touch event'. So it is not necessary to select for the slave + // devices here. + XIEventMask evmasks[masters_.size()]; + int count = 0; + for (std::set<int>::const_iterator iter = masters_.begin(); + iter != masters_.end(); + ++iter, ++count) { + evmasks[count].deviceid = *iter; + evmasks[count].mask_len = sizeof(mask); + evmasks[count].mask = mask; + } + + XISelectEvents(xdisplay, xwindow, evmasks, masters_.size()); + + // TODO(sad): Setup masks for keyboard events. + + XFlush(xdisplay); +} + +#endif // HAVE_XINPUT2 + void MessagePumpGlibX::EventDispatcherX(GdkEvent* event, gpointer data) { MessagePumpGlibX* pump_x = reinterpret_cast<MessagePumpGlibX*>(data); diff --git a/base/message_pump_glib_x.h b/base/message_pump_glib_x.h index 2f50731..c6d98e3 100644 --- a/base/message_pump_glib_x.h +++ b/base/message_pump_glib_x.h @@ -9,6 +9,7 @@ #include "base/message_pump_glib.h" #include <bitset> +#include <set> #include <glib.h> #include <gtk/gtk.h> @@ -28,6 +29,11 @@ class MessagePumpGlibX : public MessagePumpForUI { // was captured and being processed by GDK (when |false|). bool IsDispatchingEvent(void) { return dispatching_event_; } +#if defined(HAVE_XINPUT2) + // Setup an X Window for XInput2 events. + void SetupXInput2ForXWindow(Window xid); +#endif + private: static void EventDispatcherX(GdkEvent* event, gpointer data); @@ -35,6 +41,22 @@ class MessagePumpGlibX : public MessagePumpForUI { // processed so that GDK doesn't get to them. void InitializeEventsToCapture(void); +#if defined(HAVE_XINPUT2) + // Initialize X2 input. + void InitializeXInput2(void); + + // The opcode used for checking events. + int xiopcode_; + + // The list of master pointer devices. We maintain this list so that it is not + // necessary to query X for the list of devices for each GdkWindow created. + std::set<int> masters_; + + // The list of slave (physical) pointer devices. + // TODO(sad): This is currently unused, and may be removed eventually. + std::set<int> slaves_; +#endif + // The event source for GDK events. GSource* gdksource_; diff --git a/build/linux/system.gyp b/build/linux/system.gyp index cf25bb1..7193c67 100644 --- a/build/linux/system.gyp +++ b/build/linux/system.gyp @@ -225,7 +225,17 @@ '<!@(<(pkg-config) --libs-only-l x11)', ], }, - }]] + }], + # When XInput2 is available (i.e. inputproto version is 2.0), the + # pkg-config command will succeed, so the output will be empty. + ['"<!@(<(pkg-config) --atleast-version=2.0 inputproto || echo $?)"==""', { + 'direct_dependent_settings': { + 'defines': [ + 'HAVE_XINPUT2', + ], + }, + }], + ], }, { 'target_name': 'xext', diff --git a/views/event_x.cc b/views/event_x.cc index 69101bb..7f6e7b1 100644 --- a/views/event_x.cc +++ b/views/event_x.cc @@ -5,6 +5,9 @@ #include "views/event.h" #include <gdk/gdkx.h> +#if defined(HAVE_XINPUT2) +#include <X11/extensions/XInput2.h> +#endif #include "app/keyboard_code_conversion_x.h" #include "views/widget/root_view.h" @@ -32,8 +35,8 @@ int GetEventFlagsFromXState(unsigned int state) { return flags; } -// Get the event flag for the button in XButtonEvent. During a KeyPress event, -// |state| in XButtonEvent does not include the button that has just been +// Get the event flag for the button in XButtonEvent. During a ButtonPress +// event, |state| in XButtonEvent does not include the button that has just been // pressed. Instead |state| contains flags for the buttons (if any) that had // already been pressed before the current button, and |button| stores the most // current pressed button. So, if you press down left mouse button, and while @@ -54,6 +57,20 @@ int GetEventFlagsForButton(int button) { return 0; } +#if defined(HAVE_XINPUT2) +int GetButtonMaskForX2Event(XIDeviceEvent* xievent) { + int buttonflags = 0; + + for (int i = 0; i < 8 * xievent->buttons.mask_len; i++) { + if (XIMaskIsSet(xievent->buttons.mask, i)) { + buttonflags |= GetEventFlagsForButton(i); + } + } + + return buttonflags; +} +#endif // HAVE_XINPUT2 + Event::EventType GetMouseEventType(XEvent* xev) { switch (xev->type) { case ButtonPress: @@ -65,6 +82,21 @@ Event::EventType GetMouseEventType(XEvent* xev) { return Event::ET_MOUSE_DRAGGED; } return Event::ET_MOUSE_MOVED; +#if defined(HAVE_XINPUT2) + case GenericEvent: { + XIDeviceEvent* xievent = + static_cast<XIDeviceEvent*>(xev->xcookie.data); + switch (xievent->evtype) { + case XI_ButtonPress: + return Event::ET_MOUSE_PRESSED; + case XI_ButtonRelease: + return Event::ET_MOUSE_RELEASED; + case XI_Motion: + return GetButtonMaskForX2Event(xievent) ? Event::ET_MOUSE_DRAGGED : + Event::ET_MOUSE_MOVED; + } + } +#endif } return Event::ET_UNKNOWN; @@ -78,6 +110,15 @@ gfx::Point GetMouseEventLocation(XEvent* xev) { case MotionNotify: return gfx::Point(xev->xmotion.x, xev->xmotion.y); + +#if defined(HAVE_XINPUT2) + case GenericEvent: { + XIDeviceEvent* xievent = + static_cast<XIDeviceEvent*>(xev->xcookie.data); + return gfx::Point(static_cast<int>(xievent->event_x), + static_cast<int>(xievent->event_y)); + } +#endif } return gfx::Point(); @@ -92,6 +133,23 @@ int GetMouseEventFlags(XEvent* xev) { case MotionNotify: return GetEventFlagsFromXState(xev->xmotion.state); + +#if defined(HAVE_XINPUT2) + case GenericEvent: { + XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev->xcookie.data); + switch (xievent->evtype) { + case XI_ButtonPress: + case XI_ButtonRelease: + return GetButtonMaskForX2Event(xievent) | + GetEventFlagsFromXState(xievent->mods.effective) | + GetEventFlagsForButton(xievent->detail); + + case XI_Motion: + return GetButtonMaskForX2Event(xievent) | + GetEventFlagsFromXState(xievent->mods.effective); + } + } +#endif } return 0; diff --git a/views/focus/accelerator_handler_touch.cc b/views/focus/accelerator_handler_touch.cc index 9473600..a07b977 100644 --- a/views/focus/accelerator_handler_touch.cc +++ b/views/focus/accelerator_handler_touch.cc @@ -5,7 +5,11 @@ #include "views/focus/accelerator_handler.h" #include <gtk/gtk.h> +#if defined(HAVE_XINPUT2) +#include <X11/extensions/XInput2.h> +#else #include <X11/Xlib.h> +#endif #include "views/accelerator.h" #include "views/event.h" @@ -34,11 +38,81 @@ RootView* FindRootViewForGdkWindow(GdkWindow* gdk_window) { return widget_gtk->GetRootView(); } +#if defined(HAVE_XINPUT2) +bool X2EventIsTouchEvent(XEvent* xev) { + // TODO(sad): Determine if the captured event is a touch-event. + return false; +} +#endif // HAVE_XINPUT2 + } // namespace +#if defined(HAVE_XINPUT2) +bool DispatchX2Event(RootView* root, XEvent* xev) { + if (X2EventIsTouchEvent(xev)) { + // TODO(sad): Create a TouchEvent, and send it off to |root|. If the event + // is processed by |root|, then return. Otherwise let it fall through so it + // can be used (if desired) as a mouse event. + + // TouchEvent touch(xev); + // if (root->OnTouchEvent(touch)) + // return true; + } + + XGenericEventCookie* cookie = &xev->xcookie; + + switch (cookie->evtype) { + case XI_KeyPress: + case XI_KeyRelease: { + // TODO(sad): We don't capture XInput2 events from keyboard yet. + break; + } + case XI_ButtonPress: + case XI_ButtonRelease: { + MouseEvent mouseev(xev); + if (cookie->evtype == XI_ButtonPress) { + return root->OnMousePressed(mouseev); + } else { + root->OnMouseReleased(mouseev, false); + return true; + } + } + + case XI_Motion: { + MouseEvent mouseev(xev); + if (mouseev.GetType() == Event::ET_MOUSE_DRAGGED) { + return root->OnMouseDragged(mouseev); + } else { + root->OnMouseMoved(mouseev); + return true; + } + break; + } + } + + return false; +} + +#endif // HAVE_XINPUT2 + bool DispatchXEvent(XEvent* xev) { GdkDisplay* gdisp = gdk_display_get_default(); - GdkWindow* gwind = gdk_window_lookup_for_display(gdisp, xev->xany.window); + XID xwindow = xev->xany.window; + +#if defined(HAVE_XINPUT2) + if (xev->type == GenericEvent) { + if (XGetEventData(xev->xgeneric.display, &xev->xcookie)) { + XGenericEventCookie* cookie = &xev->xcookie; + XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(cookie->data); + xwindow = xiev->event; + } else { + DLOG(WARNING) << "Error fetching XGenericEventCookie for event."; + return false; + } + } +#endif + + GdkWindow* gwind = gdk_window_lookup_for_display(gdisp, xwindow); if (RootView* root = FindRootViewForGdkWindow(gwind)) { switch (xev->type) { @@ -77,6 +151,14 @@ bool DispatchXEvent(XEvent* xev) { return true; } } + +#if defined(HAVE_XINPUT2) + case GenericEvent: { + bool ret = DispatchX2Event(root, xev); + XFreeEventData(xev->xgeneric.display, &xev->xcookie); + return ret; + } +#endif } } |