message_pump_glib_x.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/message_pump_glib_x.h" 6 7#include <gdk/gdkx.h> 8#if defined(HAVE_XINPUT2) 9#include <X11/extensions/XInput2.h> 10#else 11#include <X11/Xlib.h> 12#endif 13 14#include "base/message_pump_glib_x_dispatch.h" 15 16namespace { 17 18gboolean PlaceholderDispatch(GSource* source, 19 GSourceFunc cb, 20 gpointer data) { 21 return TRUE; 22} 23 24#if defined(HAVE_XINPUT2) 25 26// Setup XInput2 select for the GtkWidget. 27gboolean GtkWidgetRealizeCallback(GSignalInvocationHint* hint, guint nparams, 28 const GValue* pvalues, gpointer data) { 29 GtkWidget* widget = GTK_WIDGET(g_value_get_object(pvalues)); 30 GdkWindow* window = widget->window; 31 base::MessagePumpGlibX* msgpump = static_cast<base::MessagePumpGlibX*>(data); 32 33 DCHECK(window); // TODO(sad): Remove once determined if necessary. 34 35 if (GDK_WINDOW_TYPE(window) != GDK_WINDOW_TOPLEVEL && 36 GDK_WINDOW_TYPE(window) != GDK_WINDOW_CHILD && 37 GDK_WINDOW_TYPE(window) != GDK_WINDOW_DIALOG) 38 return true; 39 40 // TODO(sad): Do we need to set a flag on |window| to make sure we don't 41 // select for the same GdkWindow multiple times? Does it matter? 42 msgpump->SetupXInput2ForXWindow(GDK_WINDOW_XID(window)); 43 44 return true; 45} 46 47// We need to capture all the GDK windows that get created, and start 48// listening for XInput2 events. So we setup a callback to the 'realize' 49// signal for GTK+ widgets, so that whenever the signal triggers for any 50// GtkWidget, which means the GtkWidget should now have a GdkWindow, we can 51// setup XInput2 events for the GdkWindow. 52static guint realize_signal_id = 0; 53static guint realize_hook_id = 0; 54 55void SetupGtkWidgetRealizeNotifier(base::MessagePumpGlibX* msgpump) { 56 gpointer klass = g_type_class_ref(GTK_TYPE_WIDGET); 57 58 g_signal_parse_name("realize", GTK_TYPE_WIDGET, 59 &realize_signal_id, NULL, FALSE); 60 realize_hook_id = g_signal_add_emission_hook(realize_signal_id, 0, 61 GtkWidgetRealizeCallback, static_cast<gpointer>(msgpump), NULL); 62 63 g_type_class_unref(klass); 64} 65 66void RemoveGtkWidgetRealizeNotifier() { 67 if (realize_signal_id != 0) 68 g_signal_remove_emission_hook(realize_signal_id, realize_hook_id); 69 realize_signal_id = 0; 70 realize_hook_id = 0; 71} 72 73#endif // HAVE_XINPUT2 74 75} // namespace 76 77namespace base { 78 79MessagePumpGlibX::MessagePumpGlibX() : base::MessagePumpForUI(), 80#if defined(HAVE_XINPUT2) 81 xiopcode_(-1), 82 masters_(), 83 slaves_(), 84#endif 85 gdksource_(NULL), 86 dispatching_event_(false), 87 capture_x_events_(0), 88 capture_gdk_events_(0) { 89 gdk_event_handler_set(&EventDispatcherX, this, NULL); 90 91#if defined(HAVE_XINPUT2) 92 InitializeXInput2(); 93#endif 94 InitializeEventsToCapture(); 95} 96 97MessagePumpGlibX::~MessagePumpGlibX() { 98#if defined(HAVE_XINPUT2) 99 RemoveGtkWidgetRealizeNotifier(); 100#endif 101} 102 103#if defined(HAVE_XINPUT2) 104void MessagePumpGlibX::SetupXInput2ForXWindow(Window xwindow) { 105 Display* xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); 106 107 // Setup mask for mouse events. 108 unsigned char mask[(XI_LASTEVENT + 7)/8]; 109 memset(mask, 0, sizeof(mask)); 110 111 XISetMask(mask, XI_ButtonPress); 112 XISetMask(mask, XI_ButtonRelease); 113 XISetMask(mask, XI_Motion); 114 115 // It is necessary to select only for the master devices. XInput2 provides 116 // enough information to the event callback to decide which slave device 117 // triggered the event, thus decide whether the 'pointer event' is a 'mouse 118 // event' or a 'touch event'. So it is not necessary to select for the slave 119 // devices here. 120 XIEventMask evmasks[masters_.size()]; 121 int count = 0; 122 for (std::set<int>::const_iterator iter = masters_.begin(); 123 iter != masters_.end(); 124 ++iter, ++count) { 125 evmasks[count].deviceid = *iter; 126 evmasks[count].mask_len = sizeof(mask); 127 evmasks[count].mask = mask; 128 } 129 130 XISelectEvents(xdisplay, xwindow, evmasks, masters_.size()); 131 132 // TODO(sad): Setup masks for keyboard events. 133 134 XFlush(xdisplay); 135} 136#endif // HAVE_XINPUT2 137 138bool MessagePumpGlibX::RunOnce(GMainContext* context, bool block) { 139 GdkDisplay* gdisp = gdk_display_get_default(); 140 if (!gdisp || !GetDispatcher()) 141 return MessagePumpForUI::RunOnce(context, block); 142 143 Display* display = GDK_DISPLAY_XDISPLAY(gdisp); 144 bool should_quit = false; 145 146 if (XPending(display)) { 147 XEvent xev; 148 XPeekEvent(display, &xev); 149 if (capture_x_events_[xev.type] 150#if defined(HAVE_XINPUT2) 151 && (xev.type != GenericEvent || xev.xcookie.extension == xiopcode_) 152#endif 153 ) { 154 XNextEvent(display, &xev); 155 156#if defined(HAVE_XINPUT2) 157 bool have_cookie = false; 158 if (xev.type == GenericEvent && 159 XGetEventData(xev.xgeneric.display, &xev.xcookie)) { 160 have_cookie = true; 161 } 162#endif 163 164 MessagePumpGlibXDispatcher::DispatchStatus status = 165 static_cast<MessagePumpGlibXDispatcher*> 166 (GetDispatcher())->DispatchX(&xev); 167 168 if (status == MessagePumpGlibXDispatcher::EVENT_QUIT) { 169 should_quit = true; 170 Quit(); 171 } else if (status == MessagePumpGlibXDispatcher::EVENT_IGNORED) { 172 DLOG(WARNING) << "Event (" << xev.type << ") not handled."; 173 174 // TODO(sad): It is necessary to put back the event so that the default 175 // GDK events handler can take care of it. Without this, it is 176 // impossible to use the omnibox at the moment. However, this will 177 // eventually be removed once the omnibox code is updated for touchui. 178 XPutBackEvent(display, &xev); 179 if (gdksource_) 180 gdksource_->source_funcs->dispatch = gdkdispatcher_; 181 g_main_context_iteration(context, FALSE); 182 } 183 184#if defined(HAVE_XINPUT2) 185 if (have_cookie) { 186 XFreeEventData(xev.xgeneric.display, &xev.xcookie); 187 } 188#endif 189 } else { 190 // TODO(sad): A couple of extra events can still sneak in during this. 191 // Those should be sent back to the X queue from the dispatcher 192 // EventDispatcherX. 193 if (gdksource_) 194 gdksource_->source_funcs->dispatch = gdkdispatcher_; 195 g_main_context_iteration(context, FALSE); 196 } 197 } 198 199 if (should_quit) 200 return true; 201 202 bool retvalue; 203 if (gdksource_) { 204 // Replace the dispatch callback of the GDK event source temporarily so that 205 // it doesn't read events from X. 206 gboolean (*cb)(GSource*, GSourceFunc, void*) = 207 gdksource_->source_funcs->dispatch; 208 gdksource_->source_funcs->dispatch = PlaceholderDispatch; 209 210 dispatching_event_ = true; 211 retvalue = g_main_context_iteration(context, block); 212 dispatching_event_ = false; 213 214 gdksource_->source_funcs->dispatch = cb; 215 } else { 216 retvalue = g_main_context_iteration(context, block); 217 } 218 219 return retvalue; 220} 221 222void MessagePumpGlibX::EventDispatcherX(GdkEvent* event, gpointer data) { 223 MessagePumpGlibX* pump_x = reinterpret_cast<MessagePumpGlibX*>(data); 224 225 if (!pump_x->gdksource_) { 226 pump_x->gdksource_ = g_main_current_source(); 227 pump_x->gdkdispatcher_ = pump_x->gdksource_->source_funcs->dispatch; 228 } else if (!pump_x->IsDispatchingEvent()) { 229 if (event->type != GDK_NOTHING && 230 pump_x->capture_gdk_events_[event->type]) { 231 // TODO(sad): An X event is caught by the GDK handler. Put it back in the 232 // X queue so that we catch it in the next iteration. When done, the 233 // following DLOG statement will be removed. 234 DLOG(WARNING) << "GDK received an event it shouldn't have"; 235 } 236 } 237 238 pump_x->DispatchEvents(event); 239} 240 241void MessagePumpGlibX::InitializeEventsToCapture(void) { 242 // TODO(sad): Decide which events we want to capture and update the tables 243 // accordingly. 244 capture_x_events_[KeyPress] = true; 245 capture_gdk_events_[GDK_KEY_PRESS] = true; 246 247 capture_x_events_[KeyRelease] = true; 248 capture_gdk_events_[GDK_KEY_RELEASE] = true; 249 250 capture_x_events_[ButtonPress] = true; 251 capture_gdk_events_[GDK_BUTTON_PRESS] = true; 252 253 capture_x_events_[ButtonRelease] = true; 254 capture_gdk_events_[GDK_BUTTON_RELEASE] = true; 255 256 capture_x_events_[MotionNotify] = true; 257 capture_gdk_events_[GDK_MOTION_NOTIFY] = true; 258 259#if defined(HAVE_XINPUT2) 260 capture_x_events_[GenericEvent] = true; 261#endif 262} 263 264#if defined(HAVE_XINPUT2) 265void MessagePumpGlibX::InitializeXInput2(void) { 266 GdkDisplay* display = gdk_display_get_default(); 267 if (!display) 268 return; 269 270 Display* xdisplay = GDK_DISPLAY_XDISPLAY(display); 271 int event, err; 272 273 if (!XQueryExtension(xdisplay, "XInputExtension", &xiopcode_, &event, &err)) { 274 DLOG(WARNING) << "X Input extension not available."; 275 xiopcode_ = -1; 276 return; 277 } 278 279 int major = 2, minor = 0; 280 if (XIQueryVersion(xdisplay, &major, &minor) == BadRequest) { 281 DLOG(WARNING) << "XInput2 not supported in the server."; 282 xiopcode_ = -1; 283 return; 284 } 285 286 // TODO(sad): Here, we only setup so that the X windows created by GTK+ are 287 // setup for XInput2 events. We need a way to listen for XInput2 events for X 288 // windows created by other means (e.g. for context menus). 289 SetupGtkWidgetRealizeNotifier(this); 290 291 // Instead of asking X for the list of devices all the time, let's maintain a 292 // list of slave (physical) and master (virtual) pointer devices. 293 int count = 0; 294 XIDeviceInfo* devices = XIQueryDevice(xdisplay, XIAllDevices, &count); 295 for (int i = 0; i < count; i++) { 296 XIDeviceInfo* devinfo = devices + i; 297 if (devinfo->use == XISlavePointer) { 298 slaves_.insert(devinfo->deviceid); 299 } else if (devinfo->use == XIMasterPointer) { 300 masters_.insert(devinfo->deviceid); 301 } 302 // We do not need to care about XIFloatingSlave, because the callback for 303 // XI_HierarchyChanged event will take care of it. 304 } 305 XIFreeDeviceInfo(devices); 306 307 // TODO(sad): Select on root for XI_HierarchyChanged so that slaves_ and 308 // masters_ can be kept up-to-date. This is a relatively rare event, so we can 309 // put it off for a later time. 310 // Note: It is not necessary to listen for XI_DeviceChanged events. 311} 312#endif // HAVE_XINPUT2 313 314} // namespace base 315