/* GDK - The GIMP Drawing Kit * Copyright (C) 2009 Carlos Garnacho * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "config.h" #include "gdkdevicemanager-xi.h" #include "gdkeventtranslator.h" #include "gdkdevice-xi.h" #include "gdkintl.h" #include "gdkx.h" #include struct _GdkDeviceManagerXIPrivate { GHashTable *id_table; gint event_base; GList *devices; gboolean ignore_core_events; }; static void gdk_device_manager_xi_constructed (GObject *object); static void gdk_device_manager_xi_dispose (GObject *object); static void gdk_device_manager_xi_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gdk_device_manager_xi_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gdk_device_manager_xi_event_translator_init (GdkEventTranslatorIface *iface); static gboolean gdk_device_manager_xi_translate_event (GdkEventTranslator *translator, GdkDisplay *display, GdkEvent *event, XEvent *xevent); static GList * gdk_device_manager_xi_list_devices (GdkDeviceManager *device_manager, GdkDeviceType type); G_DEFINE_TYPE_WITH_CODE (GdkDeviceManagerXI, gdk_device_manager_xi, GDK_TYPE_DEVICE_MANAGER_CORE, G_IMPLEMENT_INTERFACE (GDK_TYPE_EVENT_TRANSLATOR, gdk_device_manager_xi_event_translator_init)) enum { PROP_0, PROP_EVENT_BASE }; static void gdk_device_manager_xi_class_init (GdkDeviceManagerXIClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GdkDeviceManagerClass *device_manager_class = GDK_DEVICE_MANAGER_CLASS (klass); object_class->constructed = gdk_device_manager_xi_constructed; object_class->dispose = gdk_device_manager_xi_dispose; object_class->set_property = gdk_device_manager_xi_set_property; object_class->get_property = gdk_device_manager_xi_get_property; device_manager_class->list_devices = gdk_device_manager_xi_list_devices; g_object_class_install_property (object_class, PROP_EVENT_BASE, g_param_spec_int ("event-base", P_("Event base"), P_("Event base for XInput events"), 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_type_class_add_private (object_class, sizeof (GdkDeviceManagerXIPrivate)); } static GdkFilterReturn window_input_info_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data) { GdkDeviceManager *device_manager; GdkDisplay *display; GdkWindow *window; XEvent *xev; device_manager = user_data; xev = (XEvent *) xevent; display = gdk_device_manager_get_display (device_manager); window = gdk_window_lookup_for_display (display, xev->xany.window); if (window && xev->type == ConfigureNotify) gdk_device_xi_update_window_info (window); return GDK_FILTER_CONTINUE; } static void gdk_device_manager_xi_init (GdkDeviceManagerXI *device_manager) { GdkDeviceManagerXIPrivate *priv; device_manager->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (device_manager, GDK_TYPE_DEVICE_MANAGER_XI, GdkDeviceManagerXIPrivate); priv->id_table = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_object_unref); gdk_window_add_filter (NULL, window_input_info_filter, device_manager); } static void translate_class_info (GdkDevice *device, XDeviceInfo *info) { GdkDeviceXI *device_xi; XAnyClassPtr class; gint i, j; device_xi = GDK_DEVICE_XI (device); class = info->inputclassinfo; for (i = 0; i < info->num_classes; i++) { switch (class->class) { case ButtonClass: break; case KeyClass: { XKeyInfo *xki = (XKeyInfo *)class; guint num_keys; num_keys = xki->max_keycode - xki->min_keycode + 1; _gdk_device_set_keys (device, num_keys); device_xi->min_keycode = xki->min_keycode; break; } case ValuatorClass: { XValuatorInfo *xvi = (XValuatorInfo *)class; for (j = 0; j < xvi->num_axes; j++) { GdkAxisUse use; switch (j) { case 0: use = GDK_AXIS_X; break; case 1: use = GDK_AXIS_Y; break; case 2: use = GDK_AXIS_PRESSURE; break; case 3: use = GDK_AXIS_XTILT; break; case 4: use = GDK_AXIS_YTILT; break; case 5: use = GDK_AXIS_WHEEL; break; default: use = GDK_AXIS_IGNORE; } _gdk_device_add_axis (device, GDK_NONE, use, xvi->axes[j].min_value, xvi->axes[j].max_value, xvi->axes[j].resolution); } break; } } class = (XAnyClassPtr) (((char *) class) + class->length); } } /* old versions of XI.h don't define these */ #ifndef IsXExtensionKeyboard #define IsXExtensionKeyboard 3 #define IsXExtensionPointer 4 #endif static GdkDevice * create_device (GdkDeviceManager *device_manager, GdkDisplay *display, XDeviceInfo *info) { GdkInputSource input_source; GdkDevice *device; if (info->use != IsXExtensionPointer && info->use != IsXExtensionKeyboard) return NULL; if (info->use == IsXExtensionKeyboard) input_source = GDK_SOURCE_KEYBOARD; else { gchar *tmp_name; tmp_name = g_ascii_strdown (info->name, -1); if (strstr (tmp_name, "eraser")) input_source = GDK_SOURCE_ERASER; else if (strstr (tmp_name, "cursor")) input_source = GDK_SOURCE_CURSOR; else if (strstr (tmp_name, "wacom") || strstr (tmp_name, "pen")) input_source = GDK_SOURCE_PEN; else input_source = GDK_SOURCE_MOUSE; g_free (tmp_name); } device = g_object_new (GDK_TYPE_DEVICE_XI, "name", info->name, "type", GDK_DEVICE_TYPE_FLOATING, "input-source", input_source, "input-mode", GDK_MODE_DISABLED, "has-cursor", FALSE, "display", display, "device-manager", device_manager, "device-id", info->id, NULL); translate_class_info (device, info); return device; } static void gdk_device_manager_xi_constructed (GObject *object) { GdkDeviceManagerXIPrivate *priv; XDeviceInfo *devices; gint i, num_devices; GdkDisplay *display; priv = GDK_DEVICE_MANAGER_XI (object)->priv; display = gdk_device_manager_get_display (GDK_DEVICE_MANAGER (object)); devices = XListInputDevices(GDK_DISPLAY_XDISPLAY (display), &num_devices); for(i = 0; i < num_devices; i++) { GdkDevice *device; device = create_device (GDK_DEVICE_MANAGER (object), display, &devices[i]); if (device) { priv->devices = g_list_prepend (priv->devices, device); g_hash_table_insert (priv->id_table, GINT_TO_POINTER (devices[i].id), g_object_ref (device)); } } XFreeDeviceList(devices); gdk_x11_register_standard_event_type (display, priv->event_base, 15 /* Number of events */); if (G_OBJECT_CLASS (gdk_device_manager_xi_parent_class)->constructed) G_OBJECT_CLASS (gdk_device_manager_xi_parent_class)->constructed (object); } static void gdk_device_manager_xi_dispose (GObject *object) { GdkDeviceManagerXIPrivate *priv; priv = GDK_DEVICE_MANAGER_XI (object)->priv; g_list_foreach (priv->devices, (GFunc) g_object_unref, NULL); g_list_free (priv->devices); priv->devices = NULL; if (priv->id_table != NULL) { g_hash_table_destroy (priv->id_table); priv->id_table = NULL; } gdk_window_remove_filter (NULL, window_input_info_filter, object); G_OBJECT_CLASS (gdk_device_manager_xi_parent_class)->dispose (object); } static void gdk_device_manager_xi_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GdkDeviceManagerXIPrivate *priv; priv = GDK_DEVICE_MANAGER_XI (object)->priv; switch (prop_id) { case PROP_EVENT_BASE: priv->event_base = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gdk_device_manager_xi_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GdkDeviceManagerXIPrivate *priv; priv = GDK_DEVICE_MANAGER_XI (object)->priv; switch (prop_id) { case PROP_EVENT_BASE: g_value_set_int (value, priv->event_base); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gdk_device_manager_xi_event_translator_init (GdkEventTranslatorIface *iface) { iface->translate_event = gdk_device_manager_xi_translate_event; } /* combine the state of the core device and the device state * into one - for now we do this in a simple-minded manner - * we just take the keyboard portion of the core device and * the button portion (all of?) the device state. * Any button remapping should go on here. */ static guint translate_state (guint state, guint device_state) { return device_state | (state & 0xFF); } static GdkDevice * lookup_device (GdkDeviceManagerXI *device_manager, XEvent *xevent) { GdkDeviceManagerXIPrivate *priv; guint32 device_id; priv = GDK_DEVICE_MANAGER_XI (device_manager)->priv; /* This is a sort of a hack, as there isn't any XDeviceAnyEvent - but it's potentially faster than scanning through the types of every device. If we were deceived, then it won't match any of the types for the device anyways */ device_id = ((XDeviceButtonEvent *)xevent)->deviceid; return g_hash_table_lookup (priv->id_table, GINT_TO_POINTER (device_id)); } static gboolean gdk_device_manager_xi_translate_event (GdkEventTranslator *translator, GdkDisplay *display, GdkEvent *event, XEvent *xevent) { GdkDeviceManagerXIPrivate *priv; GdkDeviceManagerXI *device_manager; GdkEventTranslatorIface *parent_iface; GdkDeviceXI *device_xi; GdkDevice *device; GdkWindow *window; parent_iface = g_type_interface_peek_parent (GDK_EVENT_TRANSLATOR_GET_IFACE (translator)); device_manager = GDK_DEVICE_MANAGER_XI (translator); priv = device_manager->priv; if (!priv->ignore_core_events && parent_iface->translate_event (translator, display, event, xevent)) return TRUE; device = lookup_device (device_manager, xevent); device_xi = GDK_DEVICE_XI (device); if (!device) return FALSE; window = gdk_window_lookup_for_display (display, xevent->xany.window); if (!window) return FALSE; if ((xevent->type == device_xi->button_press_type) || (xevent->type == device_xi->button_release_type)) { XDeviceButtonEvent *xdbe = (XDeviceButtonEvent *) xevent; event->button.type = (xdbe->type == device_xi->button_press_type) ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE; event->button.device = device; event->button.window = g_object_ref (window); event->button.time = xdbe->time; event->button.x_root = (gdouble) xdbe->x_root; event->button.y_root = (gdouble) xdbe->y_root; event->button.axes = g_new0 (gdouble, gdk_device_get_n_axes (device)); gdk_device_xi_update_axes (device, xdbe->axes_count, xdbe->first_axis, xdbe->axis_data); gdk_device_xi_translate_axes (device, window, device_xi->axis_data, event->button.axes, &event->button.x, &event->button.y); event->button.state = translate_state (xdbe->state, xdbe->device_state); event->button.button = xdbe->button; if (event->button.type == GDK_BUTTON_PRESS) _gdk_event_button_generate (gdk_window_get_display (event->button.window), event); GDK_NOTE (EVENTS, g_print ("button %s:\t\twindow: %ld device: %ld x,y: %f %f button: %d\n", (event->button.type == GDK_BUTTON_PRESS) ? "press" : "release", xdbe->window, xdbe->deviceid, event->button.x, event->button.y, xdbe->button)); /* Update the timestamp of the latest user interaction, if the event has * a valid timestamp. */ if (gdk_event_get_time (event) != GDK_CURRENT_TIME) gdk_x11_window_set_user_time (gdk_window_get_toplevel (window), gdk_event_get_time (event)); return TRUE; } if ((xevent->type == device_xi->key_press_type) || (xevent->type == device_xi->key_release_type)) { XDeviceKeyEvent *xdke = (XDeviceKeyEvent *) xevent; GDK_NOTE (EVENTS, g_print ("device key %s:\twindow: %ld device: %ld keycode: %d\n", (event->key.type == GDK_KEY_PRESS) ? "press" : "release", xdke->window, xdke->deviceid, xdke->keycode)); if (xdke->keycode < device_xi->min_keycode || xdke->keycode >= device_xi->min_keycode + gdk_device_get_n_keys (device)) { g_warning ("Invalid device key code received"); return FALSE; } gdk_device_get_key (device, xdke->keycode - device_xi->min_keycode, &event->key.keyval, &event->key.state); if (event->key.keyval == 0) { GDK_NOTE (EVENTS, g_print ("\t\ttranslation - NONE\n")); return FALSE; } event->key.type = (xdke->type == device_xi->key_press_type) ? GDK_KEY_PRESS : GDK_KEY_RELEASE; event->key.window = g_object_ref (window); event->key.time = xdke->time; event->key.state |= translate_state (xdke->state, xdke->device_state); /* Add a string translation for the key event */ if ((event->key.keyval >= 0x20) && (event->key.keyval <= 0xFF)) { event->key.length = 1; event->key.string = g_new (gchar, 2); event->key.string[0] = (gchar) event->key.keyval; event->key.string[1] = 0; } else { event->key.length = 0; event->key.string = g_new0 (gchar, 1); } GDK_NOTE (EVENTS, g_print ("\t\ttranslation - keyval: %d modifiers: %#x\n", event->key.keyval, event->key.state)); /* Update the timestamp of the latest user interaction, if the event has * a valid timestamp. */ if (gdk_event_get_time (event) != GDK_CURRENT_TIME) gdk_x11_window_set_user_time (gdk_window_get_toplevel (window), gdk_event_get_time (event)); return TRUE; } if (xevent->type == device_xi->motion_notify_type) { XDeviceMotionEvent *xdme = (XDeviceMotionEvent *) xevent; event->motion.device = device; if (device_xi->in_proximity) priv->ignore_core_events = TRUE; event->motion.x_root = (gdouble) xdme->x_root; event->motion.y_root = (gdouble) xdme->y_root; event->motion.axes = g_new0 (gdouble, gdk_device_get_n_axes (device)); gdk_device_xi_update_axes (device, xdme->axes_count, xdme->first_axis, xdme->axis_data); gdk_device_xi_translate_axes (device, window, device_xi->axis_data, event->motion.axes, &event->motion.x, &event->motion.y); event->motion.type = GDK_MOTION_NOTIFY; event->motion.window = g_object_ref (window); event->motion.time = xdme->time; event->motion.state = translate_state (xdme->state, xdme->device_state); event->motion.is_hint = xdme->is_hint; GDK_NOTE (EVENTS, g_print ("motion notify:\t\twindow: %ld device: %ld x,y: %f %f state %#4x hint: %s\n", xdme->window, xdme->deviceid, event->motion.x, event->motion.y, event->motion.state, (xdme->is_hint) ? "true" : "false")); /* Update the timestamp of the latest user interaction, if the event has * a valid timestamp. */ if (gdk_event_get_time (event) != GDK_CURRENT_TIME) gdk_x11_window_set_user_time (gdk_window_get_toplevel (window), gdk_event_get_time (event)); return TRUE; } if (xevent->type == device_xi->proximity_in_type || xevent->type == device_xi->proximity_out_type) { XProximityNotifyEvent *xpne = (XProximityNotifyEvent *) xevent; if (xevent->type == device_xi->proximity_in_type) { event->proximity.type = GDK_PROXIMITY_IN; device_xi->in_proximity = TRUE; priv->ignore_core_events = TRUE; } else { event->proximity.type = GDK_PROXIMITY_OUT; device_xi->in_proximity = FALSE; priv->ignore_core_events = FALSE; } event->proximity.device = device; event->proximity.window = g_object_ref (window); event->proximity.time = xpne->time; /* Update the timestamp of the latest user interaction, if the event has * a valid timestamp. */ if (gdk_event_get_time (event) != GDK_CURRENT_TIME) gdk_x11_window_set_user_time (gdk_window_get_toplevel (window), gdk_event_get_time (event)); return TRUE; } if (xevent->type == device_xi->state_notify_type) { XDeviceStateNotifyEvent *xdse = (XDeviceStateNotifyEvent *) xevent; XInputClass *input_class = (XInputClass *) xdse->data; int i; for (i = 0; i < xdse->num_classes; i++) { if (input_class->class == ValuatorClass) gdk_device_xi_update_axes (device, gdk_device_get_n_axes (device), 0, ((XValuatorState *)input_class)->valuators); input_class = (XInputClass *)(((char *)input_class)+input_class->length); } GDK_NOTE (EVENTS, g_print ("device state notify:\t\twindow: %ld device: %ld\n", xdse->window, xdse->deviceid)); return FALSE; } return FALSE; } static GList * gdk_device_manager_xi_list_devices (GdkDeviceManager *device_manager, GdkDeviceType type) { GdkDeviceManagerXIPrivate *priv; priv = GDK_DEVICE_MANAGER_XI (device_manager)->priv; if (type == GDK_DEVICE_TYPE_MASTER) return GDK_DEVICE_MANAGER_CLASS (gdk_device_manager_xi_parent_class)->list_devices (device_manager, type); else if (type == GDK_DEVICE_TYPE_FLOATING) { return g_list_copy (priv->devices); } else return NULL; }