Background
GTK+ uses XInput2 for input events, which caters for a fully dynamic device hierarchy, and support for multiple pointer/keyboard pairs.
carlos@sacarino:~$ xinput list ⎡ Virtual core pointer id=2 [master pointer (3)] ⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)] ⎜ ↳ Wacom ISDv4 E6 Pen stylus id=10 [slave pointer (2)] ⎜ ↳ Wacom ISDv4 E6 Finger touch id=11 [slave pointer (2)] ⎜ ↳ SynPS/2 Synaptics TouchPad id=13 [slave pointer (2)] ⎜ ↳ TPPS/2 IBM TrackPoint id=14 [slave pointer (2)] ⎜ ↳ Wacom ISDv4 E6 Pen eraser id=16 [slave pointer (2)] ⎣ Virtual core keyboard id=3 [master keyboard (2)] ↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)] ↳ Power Button id=6 [slave keyboard (3)] ↳ Video Bus id=7 [slave keyboard (3)] ↳ Sleep Button id=8 [slave keyboard (3)] ↳ Integrated Camera id=9 [slave keyboard (3)] ↳ AT Translated Set 2 keyboard id=12 [slave keyboard (3)] ↳ ThinkPad Extra Buttons id=15 [slave keyboard (3)] carlos@sacarino:~$ xinput create-master eek carlos@sacarino:~$ xinput list ... ⎡ eek pointer id=17 [master pointer (18)] ⎜ ↳ eek XTEST pointer id=19 [slave pointer (17)] ⎣ eek keyboard id=18 [master keyboard (17)] ↳ eek XTEST keyboard id=20 [slave keyboard (18)] carlos@sacarino:~$ xinput reattach 10 17 carlos@sacarino:~$ xinput list ... ⎡ eek pointer id=17 [master pointer (18)] ⎜ ↳ Wacom ISDv4 E6 Pen stylus id=10 [slave pointer (17)] ⎜ ↳ eek XTEST pointer id=19 [slave pointer (17)] ⎣ eek keyboard id=18 [master keyboard (17)] ↳ eek XTEST keyboard id=20 [slave keyboard (18)]
Listing and modifying the device hierarchy
The device hierarchy is formed by master and slave devices. Master devices are those represented in the screen (ie. mouse cursor and keyboard focus indicator), while slave devices represent the hardware connected to the computer. Slave devices can be either connected to a master (thus being able to drive them) or floating.
The client pointer
Under the presence of multiple pointers, XInput2 uses the "client pointer" principle to allow several legacy applications to interact simultaneously with different pointer/keyboard pairs, it would be usually set by the window manager for a focused window, so different applications could have different client pointers.
Under the hood, X11 uses the client pointer (or its paired keyboard) to satisfy core calls such as XGrabPointer/Keyboard, XQueryPointer and others that have been superseded by XInput2
Apps that don't want to deal with devices
There are applications that could have little gain in being multidevice aware, but there are still stituations where a device could be needed (eg. popping up a menu on the pointer coordinates).
For such applications, the client pointer may be a good approximation for these operations.
GdkDisplay *display; GdkDeviceManager *device_manager; GdkDevice *client_pointer, client_keyboard; display = gdk_display_get_default (); device_manager = gdk_display_get_device_manager (display); client_pointer = gdk_device_manager_get_client_pointer (device_manager); /* Or if we need a keyboard too */ client_keyboard = gdk_device_get_associated_device (client_pointer);
Getting the client pointer and keyboard
With multiple pointers, this gives a behavior most similar to that of legacy applications (i.e. gtk+2)
Dealing with multiple devices
There may be several usecases to deal with multiple devices, but the patterns to make them work are about the same
Event handling
- Each device will emit its own event stream, this means that you will need
to check the GdkEvent you get in your event handlers
static gboolean my_widget_motion_notify (GtkWidget *widget, GdkEventMotion *event) { GdkDevice *device, *source_device; device = gdk_event_get_device ((GdkEvent *) event); source_device = gdk_event_get_source_device ((GdkEvent *) event); g_print ("Motion event by '%s', coming from HW device '%s'\n", gdk_device_get_name (device), gdk_device_get_name (source_device)); /* Some fictional specialized handling based on the event source */ if (gdk_device_get_source (source_device) == GDK_SOURCE_ERASER) { ... } else { ... } return TRUE; }
Reacting differently to devices
- The mechanism above could also be used for fine grained event discarding (so rubberband selection doesn't jump to another pointer entering the widget for example)
static gboolean my_widget_button_press (Gtkwidget *widget, GdkEventButton *event) { GET_PRIV(widget)->current_pointer = gdk_event_get_device ((GdkEvent *) event); ... } static gboolean my_widget_button_release (Gtkwidget *widget, GdkEventButton *event) { GET_PRIV(widget)->current_pointer = NULL; ... } static gboolean my_widget_motion_notify (Gtkwidget *widget, GdkEventMotion *event) { if (gdk_event_get_device (event) != GET_PRIV(widget)->current_pointer) return FALSE; ... }
Grabs
- Grabs are a mechanism to coerce a device into sending events to a window, but with multidevice there's an other side of the coin, how other devices are supposed to interact while the grab is in effect.
The GdkGrabOwnership enum passed to gdk_device_grab() may be used to block other devices' interaction. GDK_OWNERSHIP_NONE applies no restrictions, allowing other devices to interact, even with the grab window. GDK_OWNERSHIP_WINDOW blocks other devices from interacting with the grab window, but they'll still be able to interact with the rest of the application, whereas GDK_OWNERSHIP_APPLICATION will render the whole application insensitive to other devices. Different devices may have simultaneous grabs on the same or different windows.
gboolean my_widget_button_press (GtkWidget *widget, GdkEventButton *event) { GdkDevice *pointer, *keyboard; pointer = gdk_event_get_device ((GdkEvent *) event); keyboard = gdk_device_get_associated_device (pointer); /* Grab both keyboard/pointer, other devices will be * unable to interact with the widget window meanwhile */ gdk_device_grab (pointer, gtk_widget_get_window (widget), GDK_OWNERSHIP_WINDOW, ...); gdk_device_grab (keyboard, gtk_widget_get_window (widget), GDK_OWNERSHIP_WINDOW, ...); return FALSE; }
For GTK+ grabs, there's only a boolean value, equivalent to GDK_OWNERSHIP_NONE/WINDOW, but the mechanism is quite similar. Once the device is grabbed, there may be different situations that could break thegrabs, so the widget needs to listen to GdkGrabBrokenEvent and ::grab-notify to handle these situations.
static gboolean my_widget_grab_broken (GtkWidget *widget, GdkEventGrabBroken *event) { MyWidgetPrivate *priv = GET_PRIV (widget); if (gdk_event_get_device (event) == priv->grab_pointer) { /* Undo state */ ... priv->grab_pointer = NULL; return TRUE; } return FALSE; } static void my_widget_grab_notify (GtkWidget *widget, gboolean was_grabbed) { MyWidgetPrivate *priv = GET_PRIV (widget); if (gtk_widget_device_is_shadowed (widget, priv->grab_device)) { /* Device was "shadowed" by another widget's grab, * release and undo state */ ... priv->grab_pointer = NULL; } }
Handling broken grabs
Handling multipointer
- Widgets do react by default to every master device, although most (all?) are set in a compatibility mode that make them behave better with multiple pointers, without necessarily being multipointer aware. This compatibility mode most notably disables per-device enter/leave events, so these are stacked, and the event is only emitted when the first/last pointer enters/leaves the window.
/* Prepare for enter/leave crazyness */ gtk_widget_set_support_multidevice (widget, TRUE);
Reading device axes
- Button and motion events provide further information about the device axes' current state. Note the device axes are HW and driver dependent, therefore the set of axes is far from set in stone, although there are a few more common ones.
carlos@sacarino:~$ xinput list "Wacom ISDv4 E6 Pen stylus" |grep "Label" Label: Abs X Label: Abs Y Label: Abs Pressure Label: Abs Tilt X Label: Abs Tilt Y Label: Abs Wheel
Getting to know a device axes
gboolean my_widget_motion_notify (GtkWidget *widget, GdkEventMotion *event) { GdkAtom *label_atom; gdouble pressure; label_atom = gdk_atom_intern_static_string ("Abs Pressure"); gdk_device_get_axis_value (gdk_event_get_device ((GdkEvent *) event), event->axes, label_atom, &pressure); /* Do something with pressure */ ... }
Getting an axis value
- All pointer devices report axes information, master and slave. to achieve this, master pointers modify their list of axes at runtime to reflect those of the currently routed slave, emitting ::changed on the way.
Dealing directly with slave (or floating) devices
- GTK+ listens by default to all master devices, although some applications may want to interact in detail with a hardware device, such as a tablet stylus. To do so, the device must be enabled, and the widget wanting its events needs to add the event mask.
GdkDevice *device; /* Gets the first device found with the given GdkInputSource */ device = get_device (gtk_widget_get_display (widget), GDK_SOURCE_PEN); gdk_device_set_mode (device, GDK_MODE_SCREEN); gtk_widget_add_device_events (widget, device, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
enabling events for a slave device
- After these calls, the widget would specifically receive events from the stylus, regardless of it being floating or connected to a master device, in this second case, and if you want exclusive control of the device, you can temporarily detach the stylus device from its master by doing a GDK grab on it. For events coming directly from slave devices, both gdk_event_get_device() and gdk_event_get_source_device() will return the same device of type GDK_DEVICE_TYPE_SLAVE/FLOATING. Note that this is less useful than it used to be in GTK+2/XInput1, at least for attached slaves, as there is gdk_event_get_source_device(), and master devices' events provide axes information.
Recommendations
- Device operations often come up as a result of input events,
- favor gdk_event_get_device() and gtk_get_current_event_device() before gdk_device_manager_get_client_pointer()
- Store the devices the widget is currently interacting with, handle
GdkEventGrabBroken and ::grab-notify to undo/nullify these.