/* GDK - The GIMP Drawing Kit
 * Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
 *
 * 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 "gdkx11devicemanager-xi2.h"
#include "gdkx11device-xi2.h"

#include "gdkdevicemanagerprivate-core.h"
#include "gdkdeviceprivate.h"
#include "gdkdisplayprivate.h"
#include "gdkeventtranslator.h"
#include "gdkprivate-x11.h"
#include "gdkintl.h"
#include "gdkkeysyms.h"

#ifdef XINPUT_2

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XInput2.h>

#include <string.h>

#endif /* XINPUT_2 */

struct _GdkX11DeviceManagerXI2
{
  GdkX11DeviceManagerCore parent_object;

  GHashTable *id_table;

  GList *devices;

  gint opcode;
};

struct _GdkX11DeviceManagerXI2Class
{
  GdkDeviceManagerClass parent_class;
};

static void     gdk_x11_device_manager_xi2_event_translator_init (GdkEventTranslatorIface *iface);

G_DEFINE_TYPE_WITH_CODE (GdkX11DeviceManagerXI2, gdk_x11_device_manager_xi2, GDK_TYPE_X11_DEVICE_MANAGER_CORE,
                         G_IMPLEMENT_INTERFACE (GDK_TYPE_EVENT_TRANSLATOR,
                                                gdk_x11_device_manager_xi2_event_translator_init))


#ifdef XINPUT_2

#define HAS_FOCUS(toplevel) ((toplevel)->has_focus || (toplevel)->has_pointer_focus)


static void    gdk_x11_device_manager_xi2_constructed  (GObject      *object);
static void    gdk_x11_device_manager_xi2_dispose      (GObject      *object);
static void    gdk_x11_device_manager_xi2_set_property (GObject      *object,
                                                        guint         prop_id,
                                                        const GValue *value,
                                                        GParamSpec   *pspec);
static void    gdk_x11_device_manager_xi2_get_property (GObject      *object,
                                                        guint         prop_id,
                                                        GValue       *value,
                                                        GParamSpec   *pspec);

static GList * gdk_x11_device_manager_xi2_list_devices (GdkDeviceManager *device_manager,
                                                        GdkDeviceType     type);
static GdkDevice * gdk_x11_device_manager_xi2_get_client_pointer (GdkDeviceManager *device_manager);

static gboolean gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
                                                            GdkDisplay         *display,
                                                            GdkEvent           *event,
                                                            XEvent             *xevent);
static GdkEventMask gdk_x11_device_manager_xi2_get_handled_events   (GdkEventTranslator *translator);
static void         gdk_x11_device_manager_xi2_select_window_events (GdkEventTranslator *translator,
                                                                     Window              window,
                                                                     GdkEventMask        event_mask);
static GdkWindow *  gdk_x11_device_manager_xi2_get_window           (GdkEventTranslator *translator,
                                                                     XEvent             *xevent);

enum {
  PROP_0,
  PROP_OPCODE
};

static void
gdk_x11_device_manager_xi2_class_init (GdkX11DeviceManagerXI2Class *klass)
{
  GdkDeviceManagerClass *device_manager_class = GDK_DEVICE_MANAGER_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->constructed = gdk_x11_device_manager_xi2_constructed;
  object_class->dispose = gdk_x11_device_manager_xi2_dispose;
  object_class->set_property = gdk_x11_device_manager_xi2_set_property;
  object_class->get_property = gdk_x11_device_manager_xi2_get_property;

  device_manager_class->list_devices = gdk_x11_device_manager_xi2_list_devices;
  device_manager_class->get_client_pointer = gdk_x11_device_manager_xi2_get_client_pointer;

  g_object_class_install_property (object_class,
                                   PROP_OPCODE,
                                   g_param_spec_int ("opcode",
                                                     P_("Opcode"),
                                                     P_("Opcode for XInput2 requests"),
                                                     0, G_MAXINT, 0,
                                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}

static void
gdk_x11_device_manager_xi2_init (GdkX11DeviceManagerXI2 *device_manager)
{
  device_manager->id_table = g_hash_table_new_full (g_direct_hash,
                                                    g_direct_equal,
                                                    NULL,
                                                    (GDestroyNotify) g_object_unref);
}

static void
_gdk_x11_device_manager_xi2_select_events (GdkDeviceManager *device_manager,
                                           Window            xwindow,
                                           XIEventMask      *event_mask)
{
  GdkDisplay *display;
  Display *xdisplay;

  display = gdk_device_manager_get_display (device_manager);
  xdisplay = GDK_DISPLAY_XDISPLAY (display);

  XISelectEvents (xdisplay, xwindow, event_mask, 1);
}

static void
translate_valuator_class (GdkDisplay          *display,
                          GdkDevice           *device,
                          XIValuatorClassInfo *info,
                          gint                 n_valuator)
{
  static gboolean initialized = FALSE;
  static Atom label_atoms [GDK_AXIS_LAST] = { 0 };
  GdkAxisUse use = GDK_AXIS_IGNORE;
  GdkAtom label;
  gint i;

  if (!initialized)
    {
      label_atoms [GDK_AXIS_X] = gdk_x11_get_xatom_by_name_for_display (display, "Abs X");
      label_atoms [GDK_AXIS_Y] = gdk_x11_get_xatom_by_name_for_display (display, "Abs Y");
      label_atoms [GDK_AXIS_PRESSURE] = gdk_x11_get_xatom_by_name_for_display (display, "Abs Pressure");
      label_atoms [GDK_AXIS_XTILT] = gdk_x11_get_xatom_by_name_for_display (display, "Abs Tilt X");
      label_atoms [GDK_AXIS_YTILT] = gdk_x11_get_xatom_by_name_for_display (display, "Abs Tilt Y");
      label_atoms [GDK_AXIS_WHEEL] = gdk_x11_get_xatom_by_name_for_display (display, "Abs Wheel");
      initialized = TRUE;
    }

  for (i = GDK_AXIS_IGNORE; i < GDK_AXIS_LAST; i++)
    {
      if (label_atoms[i] == info->label)
        {
          use = i;
          break;
        }
    }

  if (info->label != None)
    label = gdk_x11_xatom_to_atom_for_display (display, info->label);
  else
    label = GDK_NONE;

  _gdk_device_add_axis (device,
                        label,
                        use,
                        info->min,
                        info->max,
                        info->resolution);
}

static void
translate_device_classes (GdkDisplay      *display,
                          GdkDevice       *device,
                          XIAnyClassInfo **classes,
                          guint            n_classes)
{
  gint i, n_valuator = 0;

  g_object_freeze_notify (G_OBJECT (device));

  for (i = 0; i < n_classes; i++)
    {
      XIAnyClassInfo *class_info = classes[i];

      switch (class_info->type)
        {
        case XIKeyClass:
          {
            XIKeyClassInfo *key_info = (XIKeyClassInfo *) class_info;
            gint i;

            _gdk_device_set_keys (device, key_info->num_keycodes);

            for (i = 0; i < key_info->num_keycodes; i++)
              gdk_device_set_key (device, i, key_info->keycodes[i], 0);
          }
          break;
        case XIValuatorClass:
          translate_valuator_class (display, device,
                                    (XIValuatorClassInfo *) class_info,
                                    n_valuator);
          n_valuator++;
          break;
        default:
          /* Ignore */
          break;
        }
    }

  g_object_thaw_notify (G_OBJECT (device));
}

static GdkDevice *
create_device (GdkDeviceManager *device_manager,
               GdkDisplay       *display,
               XIDeviceInfo     *dev)
{
  GdkInputSource input_source;
  GdkDeviceType type;
  GdkDevice *device;
  GdkInputMode mode;

  if (dev->use == XIMasterKeyboard || dev->use == XISlaveKeyboard)
    input_source = GDK_SOURCE_KEYBOARD;
  else
    {
      gchar *tmp_name;

      tmp_name = g_ascii_strdown (dev->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);
    }

  switch (dev->use)
    {
    case XIMasterKeyboard:
    case XIMasterPointer:
      type = GDK_DEVICE_TYPE_MASTER;
      mode = GDK_MODE_SCREEN;
      break;
    case XISlaveKeyboard:
    case XISlavePointer:
      type = GDK_DEVICE_TYPE_SLAVE;
      mode = GDK_MODE_DISABLED;
      break;
    case XIFloatingSlave:
    default:
      type = GDK_DEVICE_TYPE_FLOATING;
      mode = GDK_MODE_DISABLED;
      break;
    }

  device = g_object_new (GDK_TYPE_X11_DEVICE_XI2,
                         "name", dev->name,
                         "type", type,
                         "input-source", input_source,
                         "input-mode", mode,
                         "has-cursor", (dev->use == XIMasterPointer),
                         "display", display,
                         "device-manager", device_manager,
                         "device-id", dev->deviceid,
                         NULL);

  translate_device_classes (display, device, dev->classes, dev->num_classes);

  return device;
}

static GdkDevice *
add_device (GdkX11DeviceManagerXI2 *device_manager,
            XIDeviceInfo           *dev,
            gboolean                emit_signal)
{
  GdkDisplay *display;
  GdkDevice *device;

  display = gdk_device_manager_get_display (GDK_DEVICE_MANAGER (device_manager));
  device = create_device (GDK_DEVICE_MANAGER (device_manager), display, dev);

  g_hash_table_replace (device_manager->id_table,
                        GINT_TO_POINTER (dev->deviceid),
                        g_object_ref (device));

  device_manager->devices = g_list_append (device_manager->devices, device);

  if (emit_signal)
    {
      if (dev->use == XISlavePointer || dev->use == XISlaveKeyboard)
        {
          GdkDevice *master;

          /* The device manager is already constructed, then
           * keep the hierarchy coherent for the added device.
           */
          master = g_hash_table_lookup (device_manager->id_table,
                                        GINT_TO_POINTER (dev->attachment));

          _gdk_device_set_associated_device (device, master);
          _gdk_device_add_slave (master, device);
        }

      g_signal_emit_by_name (device_manager, "device-added", device);
    }

  return device;
}

static void
remove_device (GdkX11DeviceManagerXI2 *device_manager,
               gint                    device_id)
{
  GdkDevice *device;

  device = g_hash_table_lookup (device_manager->id_table,
                                GINT_TO_POINTER (device_id));

  if (device)
    {
      device_manager->devices = g_list_remove (device_manager->devices, device);

      g_signal_emit_by_name (device_manager, "device-removed", device);

      g_object_run_dispose (G_OBJECT (device));

      g_hash_table_remove (device_manager->id_table,
                           GINT_TO_POINTER (device_id));
    }
}

static void
relate_masters (gpointer key,
                gpointer value,
                gpointer user_data)
{
  GdkX11DeviceManagerXI2 *device_manager;
  GdkDevice *device, *relative;

  device_manager = user_data;
  device = g_hash_table_lookup (device_manager->id_table, key);
  relative = g_hash_table_lookup (device_manager->id_table, value);

  _gdk_device_set_associated_device (device, relative);
  _gdk_device_set_associated_device (relative, device);
}

static void
relate_slaves (gpointer key,
               gpointer value,
               gpointer user_data)
{
  GdkX11DeviceManagerXI2 *device_manager;
  GdkDevice *slave, *master;

  device_manager = user_data;
  slave = g_hash_table_lookup (device_manager->id_table, key);
  master = g_hash_table_lookup (device_manager->id_table, value);

  _gdk_device_set_associated_device (slave, master);
  _gdk_device_add_slave (master, slave);
}

static void
gdk_x11_device_manager_xi2_constructed (GObject *object)
{
  GdkX11DeviceManagerXI2 *device_manager;
  GdkDisplay *display;
  GdkScreen *screen;
  GHashTable *masters, *slaves;
  Display *xdisplay;
  XIDeviceInfo *info, *dev;
  int ndevices, i;
  XIEventMask event_mask;
  unsigned char mask[2] = { 0 };

  device_manager = GDK_X11_DEVICE_MANAGER_XI2 (object);
  display = gdk_device_manager_get_display (GDK_DEVICE_MANAGER (object));
  xdisplay = GDK_DISPLAY_XDISPLAY (display);

  masters = g_hash_table_new (NULL, NULL);
  slaves = g_hash_table_new (NULL, NULL);

  info = XIQueryDevice (xdisplay, XIAllDevices, &ndevices);

  /* Initialize devices list */
  for (i = 0; i < ndevices; i++)
    {
      dev = &info[i];

      if (!dev->enabled)
	      continue;

      add_device (device_manager, dev, FALSE);

      if (dev->use == XIMasterPointer ||
          dev->use == XIMasterKeyboard)
        {
          g_hash_table_insert (masters,
                               GINT_TO_POINTER (dev->deviceid),
                               GINT_TO_POINTER (dev->attachment));
        }
      else if (dev->use == XISlavePointer ||
               dev->use == XISlaveKeyboard)
        {
          g_hash_table_insert (slaves,
                               GINT_TO_POINTER (dev->deviceid),
                               GINT_TO_POINTER (dev->attachment));
        }
    }

  XIFreeDeviceInfo (info);

  /* Stablish relationships between devices */
  g_hash_table_foreach (masters, relate_masters, object);
  g_hash_table_destroy (masters);

  g_hash_table_foreach (slaves, relate_slaves, object);
  g_hash_table_destroy (slaves);

  /* Connect to hierarchy change events */
  screen = gdk_display_get_default_screen (display);
  XISetMask (mask, XI_HierarchyChanged);
  XISetMask (mask, XI_DeviceChanged);

  event_mask.deviceid = XIAllDevices;
  event_mask.mask_len = sizeof (mask);
  event_mask.mask = mask;

  _gdk_x11_device_manager_xi2_select_events (GDK_DEVICE_MANAGER (object),
                                             GDK_WINDOW_XID (gdk_screen_get_root_window (screen)),
                                             &event_mask);
}

static void
gdk_x11_device_manager_xi2_dispose (GObject *object)
{
  GdkX11DeviceManagerXI2 *device_manager;

  device_manager = GDK_X11_DEVICE_MANAGER_XI2 (object);

  g_list_free_full (device_manager->devices, g_object_unref);
  device_manager->devices = NULL;

  if (device_manager->id_table)
    {
      g_hash_table_destroy (device_manager->id_table);
      device_manager->id_table = NULL;
    }

  G_OBJECT_CLASS (gdk_x11_device_manager_xi2_parent_class)->dispose (object);
}

static GList *
gdk_x11_device_manager_xi2_list_devices (GdkDeviceManager *device_manager,
                                         GdkDeviceType     type)
{
  GdkX11DeviceManagerXI2 *device_manager_xi2;
  GList *cur, *list = NULL;

  device_manager_xi2 = GDK_X11_DEVICE_MANAGER_XI2 (device_manager);

  for (cur = device_manager_xi2->devices; cur; cur = cur->next)
    {
      GdkDevice *dev = cur->data;

      if (type == gdk_device_get_device_type (dev))
        list = g_list_prepend (list, dev);
    }

  return list;
}

static GdkDevice *
gdk_x11_device_manager_xi2_get_client_pointer (GdkDeviceManager *device_manager)
{
  GdkX11DeviceManagerXI2 *device_manager_xi2;
  GdkDisplay *display;
  int device_id;

  device_manager_xi2 = (GdkX11DeviceManagerXI2 *) device_manager;
  display = gdk_device_manager_get_display (device_manager);

  XIGetClientPointer (GDK_DISPLAY_XDISPLAY (display),
                      None, &device_id);

  return g_hash_table_lookup (device_manager_xi2->id_table,
                              GINT_TO_POINTER (device_id));
}

static void
gdk_x11_device_manager_xi2_set_property (GObject      *object,
                                         guint         prop_id,
                                         const GValue *value,
                                         GParamSpec   *pspec)
{
  GdkX11DeviceManagerXI2 *device_manager;

  device_manager = GDK_X11_DEVICE_MANAGER_XI2 (object);

  switch (prop_id)
    {
    case PROP_OPCODE:
      device_manager->opcode = g_value_get_int (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gdk_x11_device_manager_xi2_get_property (GObject    *object,
                                         guint       prop_id,
                                         GValue     *value,
                                         GParamSpec *pspec)
{
  GdkX11DeviceManagerXI2 *device_manager;

  device_manager = GDK_X11_DEVICE_MANAGER_XI2 (object);

  switch (prop_id)
    {
    case PROP_OPCODE:
      g_value_set_int (value, device_manager->opcode);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gdk_x11_device_manager_xi2_event_translator_init (GdkEventTranslatorIface *iface)
{
  iface->translate_event = gdk_x11_device_manager_xi2_translate_event;
  iface->get_handled_events = gdk_x11_device_manager_xi2_get_handled_events;
  iface->select_window_events = gdk_x11_device_manager_xi2_select_window_events;
  iface->get_window = gdk_x11_device_manager_xi2_get_window;
}

static void
handle_hierarchy_changed (GdkX11DeviceManagerXI2 *device_manager,
                          XIHierarchyEvent       *ev)
{
  GdkDisplay *display;
  Display *xdisplay;
  XIDeviceInfo *info;
  int ndevices;
  gint i;

  display = gdk_device_manager_get_display (GDK_DEVICE_MANAGER (device_manager));
  xdisplay = GDK_DISPLAY_XDISPLAY (display);

  for (i = 0; i < ev->num_info; i++)
    {
      if (ev->info[i].flags & XIDeviceEnabled)
        {
          gdk_x11_display_error_trap_push (display);
          info = XIQueryDevice (xdisplay, ev->info[i].deviceid, &ndevices);
          gdk_x11_display_error_trap_pop_ignored (display);
          if (info)
            {
              add_device (device_manager, &info[0], TRUE);
              XIFreeDeviceInfo (info);
            }
        }
      else if (ev->info[i].flags & XIDeviceDisabled)
        remove_device (device_manager, ev->info[i].deviceid);
      else if (ev->info[i].flags & XISlaveAttached ||
               ev->info[i].flags & XISlaveDetached)
        {
          GdkDevice *master, *slave;

          slave = g_hash_table_lookup (device_manager->id_table,
                                       GINT_TO_POINTER (ev->info[i].deviceid));

          if (!slave)
            continue;

          /* Remove old master info */
          master = gdk_device_get_associated_device (slave);

          if (master)
            {
              _gdk_device_remove_slave (master, slave);
              _gdk_device_set_associated_device (slave, NULL);

              g_signal_emit_by_name (device_manager, "device-changed", master);
            }

          /* Add new master if it's an attachment event */
          if (ev->info[i].flags & XISlaveAttached)
            {
              gdk_x11_display_error_trap_push (display);
              info = XIQueryDevice (xdisplay, ev->info[i].deviceid, &ndevices);
              gdk_x11_display_error_trap_pop_ignored (display);
              if (info)
                {
                  master = g_hash_table_lookup (device_manager->id_table,
                                                GINT_TO_POINTER (info->attachment));
                  XIFreeDeviceInfo (info);
                }

              if (master)
                {
                  _gdk_device_set_associated_device (slave, master);
                  _gdk_device_add_slave (master, slave);

                  g_signal_emit_by_name (device_manager, "device-changed", master);
                }
            }

          g_signal_emit_by_name (device_manager, "device-changed", slave);
        }
    }
}

static void
handle_device_changed (GdkX11DeviceManagerXI2 *device_manager,
                       XIDeviceChangedEvent   *ev)
{
  GdkDisplay *display;
  GdkDevice *device;

  display = gdk_device_manager_get_display (GDK_DEVICE_MANAGER (device_manager));
  device = g_hash_table_lookup (device_manager->id_table,
                                GUINT_TO_POINTER (ev->deviceid));

  if (device)
    {
      _gdk_device_reset_axes (device);
      translate_device_classes (display, device, ev->classes, ev->num_classes);

      g_signal_emit_by_name (G_OBJECT (device), "changed");
    }
}

static GdkCrossingMode
translate_crossing_mode (gint mode)
{
  switch (mode)
    {
    case NotifyNormal:
      return GDK_CROSSING_NORMAL;
    case NotifyGrab:
      return GDK_CROSSING_GRAB;
    case NotifyUngrab:
      return GDK_CROSSING_UNGRAB;
    default:
      g_assert_not_reached ();
    }
}

static GdkNotifyType
translate_notify_type (gint detail)
{
  switch (detail)
    {
    case NotifyInferior:
      return GDK_NOTIFY_INFERIOR;
    case NotifyAncestor:
      return GDK_NOTIFY_ANCESTOR;
    case NotifyVirtual:
      return GDK_NOTIFY_VIRTUAL;
    case NotifyNonlinear:
      return GDK_NOTIFY_NONLINEAR;
    case NotifyNonlinearVirtual:
      return GDK_NOTIFY_NONLINEAR_VIRTUAL;
    default:
      g_assert_not_reached ();
    }
}

static gboolean
set_screen_from_root (GdkDisplay *display,
                      GdkEvent   *event,
                      Window      xrootwin)
{
  GdkScreen *screen;

  screen = _gdk_x11_display_screen_for_xrootwin (display, xrootwin);

  if (screen)
    {
      gdk_event_set_screen (event, screen);

      return TRUE;
    }

  return FALSE;
}

static void
set_user_time (GdkEvent *event)
{
  GdkWindow *window;
  guint32 time;

  window = gdk_window_get_toplevel (event->any.window);
  g_return_if_fail (GDK_IS_WINDOW (window));

  time = gdk_event_get_time (event);

  /* If an event doesn't have a valid timestamp, we shouldn't use it
   * to update the latest user interaction time.
   */
  if (time != GDK_CURRENT_TIME)
    gdk_x11_window_set_user_time (window, time);
}

static gdouble *
translate_axes (GdkDevice       *device,
                gdouble          x,
                gdouble          y,
                GdkWindow       *window,
                XIValuatorState *valuators)
{
  guint n_axes, i;
  gdouble *axes;
  gdouble *vals;

  g_object_get (device, "n-axes", &n_axes, NULL);

  axes = g_new0 (gdouble, n_axes);
  vals = valuators->values;

  for (i = 0; i < valuators->mask_len * 8; i++)
    {
      GdkAxisUse use;
      gdouble val;

      if (!XIMaskIsSet (valuators->mask, i))
        continue;

      use = gdk_device_get_axis_use (device, i);
      val = *vals++;

      switch (use)
        {
        case GDK_AXIS_X:
        case GDK_AXIS_Y:
          if (gdk_device_get_mode (device) == GDK_MODE_WINDOW)
            _gdk_device_translate_window_coord (device, window, i, val, &axes[i]);
          else
            {
              if (use == GDK_AXIS_X)
                axes[i] = x;
              else
                axes[i] = y;
            }
          break;
        default:
          _gdk_device_translate_axis (device, i, val, &axes[i]);
          break;
        }
    }

  return axes;
}

static gboolean
is_parent_of (GdkWindow *parent,
              GdkWindow *child)
{
  GdkWindow *w;

  w = child;
  while (w != NULL)
    {
      if (w == parent)
        return TRUE;

      w = gdk_window_get_parent (w);
    }

  return FALSE;
}

static GdkWindow *
get_event_window (GdkEventTranslator *translator,
                  XIEvent            *ev)
{
  GdkDisplay *display;
  GdkWindow *window = NULL;

  display = gdk_device_manager_get_display (GDK_DEVICE_MANAGER (translator));

  switch (ev->evtype)
    {
    case XI_KeyPress:
    case XI_KeyRelease:
    case XI_ButtonPress:
    case XI_ButtonRelease:
    case XI_Motion:
      {
        XIDeviceEvent *xev = (XIDeviceEvent *) ev;

        window = gdk_x11_window_lookup_for_display (display, xev->event);

        /* Apply keyboard grabs to non-native windows */
        if (ev->evtype == XI_KeyPress || ev->evtype == XI_KeyRelease)
          {
            GdkDeviceGrabInfo *info;
            GdkDevice *device;
            gulong serial;

            device = g_hash_table_lookup (GDK_X11_DEVICE_MANAGER_XI2 (translator)->id_table,
                                          GUINT_TO_POINTER (((XIDeviceEvent *) ev)->deviceid));

            serial = _gdk_display_get_next_serial (display);
            info = _gdk_display_has_device_grab (display, device, serial);

            if (info &&
                (!is_parent_of (info->window, window) ||
                 !info->owner_events))
              {
                /* Report key event against grab window */
                window = info->window;
              }
          }
      }
      break;
    case XI_Enter:
    case XI_Leave:
    case XI_FocusIn:
    case XI_FocusOut:
      {
        XIEnterEvent *xev = (XIEnterEvent *) ev;

        window = gdk_x11_window_lookup_for_display (display, xev->event);
      }
      break;
    }

  return window;
}

static gboolean
gdk_x11_device_manager_xi2_translate_core_event (GdkEventTranslator *translator,
						 GdkDisplay         *display,
						 GdkEvent           *event,
						 XEvent             *xevent)
{
  GdkEventTranslatorIface *parent_iface;
  gboolean keyboard = FALSE;
  GdkDevice *device;

  if ((xevent->type == KeyPress || xevent->type == KeyRelease) &&
      (xevent->xkey.keycode == 0 || xevent->xkey.serial == 0))
    {
      /* The X input methods (when triggered via XFilterEvent)
       * generate a core key press event with keycode 0 to signal the
       * end of a key sequence. We use the core translate_event
       * implementation to translate this event.
       *
       * Other less educated IM modules like to filter every keypress,
       * only to have these replaced by their own homegrown events,
       * these events oddly have serial=0, so we try to catch these.
       *
       * This is just a bandaid fix to keep xim working with a single
       * keyboard until XFilterEvent learns about XI2.
       */
      keyboard = TRUE;
    }
  else if (xevent->xany.send_event)
    {
      /* If another process sends us core events, process them; we
       * assume that it won't send us redundant core and XI2 events.
       * (At the moment, it's not possible to send XI2 events anyway.
       * In the future, an app that was trying to decide whether to
       * send core or XI2 events could look at the event mask on the
       * window to see which kind we are listening to.)
       */
      switch (xevent->type)
	{
	case KeyPress:
	case KeyRelease:
	case FocusIn:
	case FocusOut:
	  keyboard = TRUE;
	  break;

	case ButtonPress:
	case ButtonRelease:
	case MotionNotify:
	case EnterNotify:
	case LeaveNotify:
	  break;

	default:
	  return FALSE;
	}
    }
  else
    return FALSE;

  parent_iface = g_type_interface_peek_parent (GDK_EVENT_TRANSLATOR_GET_IFACE (translator));
  if (!parent_iface->translate_event (translator, display, event, xevent))
    return FALSE;

  /* The core device manager sets a core device on the event.
   * We need to override that with an XI2 device, since we are
   * using XI2.
   */
  device = gdk_x11_device_manager_xi2_get_client_pointer ((GdkDeviceManager *)translator);
  if (keyboard)
    device = gdk_device_get_associated_device (device);
  gdk_event_set_device (event, device);

  return TRUE;
}

static gboolean
gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
                                            GdkDisplay         *display,
                                            GdkEvent           *event,
                                            XEvent             *xevent)
{
  GdkX11DeviceManagerXI2 *device_manager;
  XGenericEventCookie *cookie;
  gboolean return_val = TRUE;
  GdkWindow *window;
  XIEvent *ev;

  device_manager = (GdkX11DeviceManagerXI2 *) translator;
  cookie = &xevent->xcookie;

  if (xevent->type != GenericEvent)
    return gdk_x11_device_manager_xi2_translate_core_event (translator, display, event, xevent);
  else if (cookie->extension != device_manager->opcode)
    return FALSE;

  ev = (XIEvent *) cookie->data;

  if (!ev)
    return FALSE;

  window = get_event_window (translator, ev);

  if (window && GDK_WINDOW_DESTROYED (window))
    return FALSE;

  if (ev->evtype == XI_Motion ||
      ev->evtype == XI_ButtonRelease)
    {
      if (_gdk_x11_moveresize_handle_event (xevent))
        return FALSE;
    }

  switch (ev->evtype)
    {
    case XI_HierarchyChanged:
      handle_hierarchy_changed (device_manager,
                                (XIHierarchyEvent *) ev);
      return_val = FALSE;
      break;
    case XI_DeviceChanged:
      handle_device_changed (device_manager,
                             (XIDeviceChangedEvent *) ev);
      return_val = FALSE;
      break;
    case XI_KeyPress:
    case XI_KeyRelease:
      {
        XIDeviceEvent *xev = (XIDeviceEvent *) ev;
        GdkKeymap *keymap = gdk_keymap_get_for_display (display);
        GdkModifierType consumed, state;
        GdkDevice *device, *source_device;

        event->key.type = xev->evtype == XI_KeyPress ? GDK_KEY_PRESS : GDK_KEY_RELEASE;

        event->key.window = window;

        event->key.time = xev->time;
        event->key.state = _gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);
        event->key.group = _gdk_x11_get_group_for_state (display, event->key.state);

        event->key.hardware_keycode = xev->detail;
        event->key.is_modifier = _gdk_x11_keymap_key_is_modifier (keymap, event->key.hardware_keycode);

        device = g_hash_table_lookup (device_manager->id_table,
                                      GUINT_TO_POINTER (xev->deviceid));
        gdk_event_set_device (event, device);

        source_device = g_hash_table_lookup (device_manager->id_table,
                                             GUINT_TO_POINTER (xev->sourceid));
        gdk_event_set_source_device (event, source_device);

        event->key.keyval = GDK_KEY_VoidSymbol;

        gdk_keymap_translate_keyboard_state (keymap,
                                             event->key.hardware_keycode,
                                             event->key.state,
                                             event->key.group,
                                             &event->key.keyval,
                                             NULL, NULL, &consumed);

        state = event->key.state & ~consumed;
        _gdk_x11_keymap_add_virt_mods (keymap, &state);
        event->key.state |= state;

        _gdk_x11_event_translate_keyboard_string (&event->key);

        if (ev->evtype == XI_KeyPress)
          set_user_time (event);

        /* FIXME: emulate autorepeat on key
         * release? XI2 seems attached to Xkb.
         */
      }

      break;
    case XI_ButtonPress:
    case XI_ButtonRelease:
      {
        XIDeviceEvent *xev = (XIDeviceEvent *) ev;
        GdkDevice *source_device;

        switch (xev->detail)
          {
          case 4:
          case 5:
          case 6:
          case 7:
             /* Button presses of button 4-7 are scroll events */
            if (ev->evtype == XI_ButtonPress)
              {
                event->scroll.type = GDK_SCROLL;

                if (xev->detail == 4)
                  event->scroll.direction = GDK_SCROLL_UP;
                else if (xev->detail == 5)
                  event->scroll.direction = GDK_SCROLL_DOWN;
                else if (xev->detail == 6)
                  event->scroll.direction = GDK_SCROLL_LEFT;
                else
                  event->scroll.direction = GDK_SCROLL_RIGHT;

                event->scroll.window = window;
                event->scroll.time = xev->time;
                event->scroll.x = (gdouble) xev->event_x;
                event->scroll.y = (gdouble) xev->event_y;
                event->scroll.x_root = (gdouble) xev->root_x;
                event->scroll.y_root = (gdouble) xev->root_y;

                event->scroll.device = g_hash_table_lookup (device_manager->id_table,
                                                            GUINT_TO_POINTER (xev->deviceid));

                source_device = g_hash_table_lookup (device_manager->id_table,
                                                     GUINT_TO_POINTER (xev->sourceid));
                gdk_event_set_source_device (event, source_device);

                event->scroll.state = _gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);
                break;
              }
            /* Button presses of button 4-7 are scroll events, so ignore the release */
            else if (ev->evtype == XI_ButtonRelease)
              {
                return_val = FALSE;
                break;
              }
            /* else (XI_ButtonRelease) fall thru */
          default:
            event->button.type = (ev->evtype == XI_ButtonPress) ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE;

            event->button.window = window;
            event->button.time = xev->time;
            event->button.x = (gdouble) xev->event_x;
            event->button.y = (gdouble) xev->event_y;
            event->button.x_root = (gdouble) xev->root_x;
            event->button.y_root = (gdouble) xev->root_y;

            event->button.device = g_hash_table_lookup (device_manager->id_table,
                                                        GUINT_TO_POINTER (xev->deviceid));

            source_device = g_hash_table_lookup (device_manager->id_table,
                                                 GUINT_TO_POINTER (xev->sourceid));
            gdk_event_set_source_device (event, source_device);

            event->button.axes = translate_axes (event->button.device,
                                                 event->button.x,
                                                 event->button.y,
                                                 event->button.window,
                                                 &xev->valuators);

            if (gdk_device_get_mode (event->button.device) == GDK_MODE_WINDOW)
              {
                GdkDevice *device = event->button.device;

                /* Update event coordinates from axes */
                gdk_device_get_axis (device, event->button.axes, GDK_AXIS_X, &event->button.x);
                gdk_device_get_axis (device, event->button.axes, GDK_AXIS_Y, &event->button.y);
              }

            event->button.state = _gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);
            event->button.button = xev->detail;
          }

        if (return_val == FALSE)
          break;

        if (!set_screen_from_root (display, event, xev->root))
          {
            return_val = FALSE;
            break;
          }

        if (ev->evtype == XI_ButtonPress)
	  set_user_time (event);

        break;
      }
    case XI_Motion:
      {
        XIDeviceEvent *xev = (XIDeviceEvent *) ev;
        GdkDevice *source_device;

        event->motion.type = GDK_MOTION_NOTIFY;

        event->motion.window = window;

        event->motion.time = xev->time;
        event->motion.x = (gdouble) xev->event_x;
        event->motion.y = (gdouble) xev->event_y;
        event->motion.x_root = (gdouble) xev->root_x;
        event->motion.y_root = (gdouble) xev->root_y;

        event->motion.device = g_hash_table_lookup (device_manager->id_table,
                                                    GINT_TO_POINTER (xev->deviceid));

        source_device = g_hash_table_lookup (device_manager->id_table,
                                             GUINT_TO_POINTER (xev->sourceid));
        gdk_event_set_source_device (event, source_device);

        event->motion.state = _gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);

        /* There doesn't seem to be motion hints in XI */
        event->motion.is_hint = FALSE;

        event->motion.axes = translate_axes (event->motion.device,
                                             event->motion.x,
                                             event->motion.y,
                                             event->motion.window,
                                             &xev->valuators);

        if (gdk_device_get_mode (event->motion.device) == GDK_MODE_WINDOW)
          {
            GdkDevice *device = event->motion.device;

            /* Update event coordinates from axes */
            gdk_device_get_axis (device, event->motion.axes, GDK_AXIS_X, &event->motion.x);
            gdk_device_get_axis (device, event->motion.axes, GDK_AXIS_Y, &event->motion.y);
          }
      }
      break;
    case XI_Enter:
    case XI_Leave:
      {
        XIEnterEvent *xev = (XIEnterEvent *) ev;
        GdkDevice *device, *source_device;

        event->crossing.type = (ev->evtype == XI_Enter) ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;

        event->crossing.x = (gdouble) xev->event_x;
        event->crossing.y = (gdouble) xev->event_y;
        event->crossing.x_root = (gdouble) xev->root_x;
        event->crossing.y_root = (gdouble) xev->root_y;
        event->crossing.time = xev->time;
        event->crossing.focus = xev->focus;

        event->crossing.window = window;
        event->crossing.subwindow = gdk_x11_window_lookup_for_display (display, xev->child);

        device = g_hash_table_lookup (device_manager->id_table,
                                      GINT_TO_POINTER (xev->deviceid));
        gdk_event_set_device (event, device);

        source_device = g_hash_table_lookup (device_manager->id_table,
                                             GUINT_TO_POINTER (xev->sourceid));
        gdk_event_set_source_device (event, source_device);

        event->crossing.mode = translate_crossing_mode (xev->mode);
        event->crossing.detail = translate_notify_type (xev->detail);
        event->crossing.state = _gdk_x11_device_xi2_translate_state (&xev->mods, &xev->buttons, &xev->group);
      }
      break;
    case XI_FocusIn:
    case XI_FocusOut:
      {
        XIEnterEvent *xev = (XIEnterEvent *) ev;
        GdkDevice *device, *source_device;

        device = g_hash_table_lookup (device_manager->id_table,
                                      GINT_TO_POINTER (xev->deviceid));

        source_device = g_hash_table_lookup (device_manager->id_table,
                                             GUINT_TO_POINTER (xev->sourceid));

        _gdk_device_manager_core_handle_focus (window,
                                               device,
                                               source_device,
                                               (ev->evtype == XI_FocusIn) ? TRUE : FALSE,
                                               xev->detail,
                                               xev->mode);

        return_val = FALSE;
      }
      break;
    default:
      return_val = FALSE;
      break;
    }

  event->any.send_event = cookie->send_event;

  if (return_val)
    {
      if (event->any.window)
        g_object_ref (event->any.window);

      if (((event->any.type == GDK_ENTER_NOTIFY) ||
           (event->any.type == GDK_LEAVE_NOTIFY)) &&
          (event->crossing.subwindow != NULL))
        g_object_ref (event->crossing.subwindow);
    }
  else
    {
      /* Mark this event as having no resources to be freed */
      event->any.window = NULL;
      event->any.type = GDK_NOTHING;
    }

  return return_val;
}

static GdkEventMask
gdk_x11_device_manager_xi2_get_handled_events (GdkEventTranslator *translator)
{
  return (GDK_KEY_PRESS_MASK |
          GDK_KEY_RELEASE_MASK |
          GDK_BUTTON_PRESS_MASK |
          GDK_BUTTON_RELEASE_MASK |
          GDK_SCROLL_MASK |
          GDK_ENTER_NOTIFY_MASK |
          GDK_LEAVE_NOTIFY_MASK |
          GDK_POINTER_MOTION_MASK |
          GDK_POINTER_MOTION_HINT_MASK |
          GDK_BUTTON1_MOTION_MASK |
          GDK_BUTTON2_MOTION_MASK |
          GDK_BUTTON3_MOTION_MASK |
          GDK_BUTTON_MOTION_MASK |
          GDK_FOCUS_CHANGE_MASK);
}

static void
gdk_x11_device_manager_xi2_select_window_events (GdkEventTranslator *translator,
                                                 Window              window,
                                                 GdkEventMask        evmask)
{
  GdkDeviceManager *device_manager;
  XIEventMask event_mask;

  device_manager = GDK_DEVICE_MANAGER (translator);

  event_mask.deviceid = XIAllMasterDevices;
  event_mask.mask = _gdk_x11_device_xi2_translate_event_mask (evmask, &event_mask.mask_len);

  _gdk_x11_device_manager_xi2_select_events (device_manager, window, &event_mask);
  g_free (event_mask.mask);
}

static GdkWindow *
gdk_x11_device_manager_xi2_get_window (GdkEventTranslator *translator,
                                       XEvent             *xevent)
{
  GdkX11DeviceManagerXI2 *device_manager;
  XIEvent *ev;

  device_manager = (GdkX11DeviceManagerXI2 *) translator;

  if (xevent->type != GenericEvent ||
      xevent->xcookie.extension != device_manager->opcode)
    return NULL;

  ev = (XIEvent *) xevent->xcookie.data;

  return get_event_window (translator, ev);
}

#else /* XINPUT_2 */

static void
gdk_x11_device_manager_xi2_class_init (GdkX11DeviceManagerXI2Class *klass)
{
}

static void
gdk_x11_device_manager_xi2_init (GdkX11DeviceManagerXI2 *device_manager)
{
}

static void
gdk_x11_device_manager_xi2_event_translator_init (GdkEventTranslatorIface *iface)
{
}

#endif /* XINPUT_2 */

GdkDevice *
_gdk_x11_device_manager_xi2_lookup (GdkX11DeviceManagerXI2 *device_manager_xi2,
                                    gint                    device_id)
{
  return g_hash_table_lookup (device_manager_xi2->id_table,
                              GINT_TO_POINTER (device_id));
}
