Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  gdkseat-wayland.c

  Sprache: C
 

/* 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, see <http://www.gnu.org/licenses/>.
 */


#include "config.h"

#include "gdkdevice-wayland-private.h"
#include "gdksurface-wayland-private.h"

#include "gdkeventsourceprivate.h"
#include "gdkclipboard-wayland.h"
#include "gdkclipboardprivate.h"
#include "gdkcursorprivate.h"
#include "gdkdeviceprivate.h"
#include "gdkdevicepadprivate.h"
#include "gdkdevicetoolprivate.h"
#include "gdkdropprivate.h"
#include "gdkeventsprivate.h"
#include "gdkkeysprivate.h"
#include "gdkkeysyms.h"
#include "gdkprimary-wayland.h"
#include "gdkkeymap-wayland.h"
#include "gdkdrop-wayland.h"
#include "gdkseat-wayland.h"
#include "gdkseatprivate.h"
#include "gdksurfaceprivate.h"
#include "gdktypes.h"
#include "gdkwayland.h"
#include "gdkprivate.h"

#include "pointer-gestures-unstable-v1-client-protocol.h"
#include "tablet-v2-client-protocol.h"
#include "cursor-shape-v1-client-protocol.h"

#include <xkbcommon/xkbcommon.h>

#if defined(HAVE_DEV_EVDEV_INPUT_H)
#include <dev/evdev/input.h>
#elif defined(HAVE_LINUX_INPUT_H)
#include <linux/input.h>
#endif

#define WL_POINTER_HAS_FRAME 5

/**
 * GdkWaylandDevice:
 *
 * The Wayland implementation of `GdkDevice`.
 *
 * Beyond the regular [class@Gdk.Device] API, the Wayland implementation
 * provides access to Wayland objects such as the `wl_seat` with
 * [method@GdkWayland.WaylandDevice.get_wl_seat], the `wl_keyboard` with
 * [method@GdkWayland.WaylandDevice.get_wl_keyboard] and the `wl_pointer` with
 * [method@GdkWayland.WaylandDevice.get_wl_pointer].
 */


/**
 * GdkWaylandSeat:
 *
 * The Wayland implementation of `GdkSeat`.
 *
 * Beyond the regular [class@Gdk.Seat] API, the Wayland implementation
 * provides access to the Wayland `wl_seat` object with
 * [method@GdkWayland.WaylandSeat.get_wl_seat].
 */


#define BUTTON_BASE (BTN_LEFT - 1/* Used to translate to 1-indexed buttons */

#ifndef BTN_STYLUS3
#define BTN_STYLUS3 0x149 /* Linux 4.15 */
#endif

#define ALL_BUTTONS_MASK (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | \
                          GDK_BUTTON3_MASK | GDK_BUTTON4_MASK | \
                          GDK_BUTTON5_MASK)

#define GDK_SEAT_DEBUG(seat,type,...) GDK_DISPLAY_DEBUG(gdk_seat_get_display (GDK_SEAT (seat)),type,__VA_ARGS__)

G_DEFINE_TYPE (GdkWaylandSeat, gdk_wayland_seat, GDK_TYPE_SEAT)

#define GDK_SLOT_TO_EVENT_SEQUENCE(s) ((GdkEventSequence *) GUINT_TO_POINTER((s) + 1))
#define GDK_EVENT_SEQUENCE_TO_SLOT(s) (GPOINTER_TO_UINT(s) - 1)

static void init_pointer_data (GdkWaylandPointerData *pointer_data,
                               GdkDisplay            *display,
                               GdkDevice             *logical_device);

typedef struct
{
  uint32_t event_type;
  uint32_t button;
} TabletEvent;

/* {{{ Utilities */

GdkWaylandTabletData *
gdk_wayland_seat_find_tablet (GdkWaylandSeat *seat,
                              GdkDevice      *device)
{
  GList *l;

  for (l = seat->tablets; l; l = l->next)
    {
      GdkWaylandTabletData *tablet = l->data;

      if (tablet->logical_device == device ||
          tablet->stylus_device == device)
        return tablet;
    }

  return NULL;
}

GdkWaylandTabletPadData *
gdk_wayland_seat_find_pad (GdkWaylandSeat *seat,
                           GdkDevice      *device)
{
  GList *l;

  for (l = seat->tablet_pads; l; l = l->next)
    {
      GdkWaylandTabletPadData *pad = l->data;

      if (pad->device == device)
        return pad;
    }

  return NULL;
}

static void
emulate_crossing (GdkSurface       *surface,
                  GdkSurface       *child_surface,
                  GdkDevice       *device,
                  GdkEventType     type,
                  GdkCrossingMode  mode,
                  uint32_t         time_)
{
  GdkEvent *event;
  GdkModifierType state;
  double x, y;

  gdk_surface_get_device_position (surface, device, &x, &y, &state);
  event = gdk_crossing_event_new (type,
                                  surface,
                                  device,
                                  time_,
                                  state,
                                  x, y,
                                  mode,
                                  GDK_NOTIFY_NONLINEAR);

  _gdk_wayland_display_deliver_event (gdk_surface_get_display (surface), event);
}

static void
emulate_touch_crossing (GdkSurface           *surface,
                        GdkSurface           *child_surface,
                        GdkDevice           *device,
                        GdkDevice           *source,
                        GdkWaylandTouchData *touch,
                        GdkEventType         type,
                        GdkCrossingMode      mode,
                        uint32_t             time_)
{
  GdkEvent *event;

  event = gdk_crossing_event_new (type,
                                  surface,
                                  device,
                                  time_,
                                  0,
                                  touch->x, touch->y,
                                  mode,
                                  GDK_NOTIFY_NONLINEAR);

  _gdk_wayland_display_deliver_event (gdk_surface_get_display (surface), event);
}

static void
gdk_wayland_seat_discard_pending_offer (GdkWaylandSeat *seat)
{
  if (seat->pending_builder)
    {
      GdkContentFormats *ignore = gdk_content_formats_builder_free_to_formats (seat->pending_builder);
      gdk_content_formats_unref (ignore);
      seat->pending_builder = NULL;
    }
  g_clear_pointer (&seat->pending_offer, wl_data_offer_destroy);
  seat->pending_source_actions = 0;
  seat->pending_action = 0;
}

static inline GdkDragAction
gdk_wayland_actions_to_gdk_actions (uint32_t dnd_actions)
{
  GdkDragAction actions = 0;

  if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
    actions |= GDK_ACTION_COPY;
  if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
    actions |= GDK_ACTION_MOVE;
  if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
    actions |= GDK_ACTION_ASK;

  return actions;
}

static const char *
get_axis_source_name (enum wl_pointer_axis_source source)
{
  switch (source)
    {
    case WL_POINTER_AXIS_SOURCE_WHEEL:
      return "wheel";
    case WL_POINTER_AXIS_SOURCE_FINGER:
      return "finger";
    case WL_POINTER_AXIS_SOURCE_CONTINUOUS:
      return "continuous";
    case WL_POINTER_AXIS_SOURCE_WHEEL_TILT:
      return "wheel-tilt";
    default:
      return "unknown";
    }
}

/* }}} */
/* {{{ Data offer listener */

static void
data_offer_offer (void                 *data,
                  struct wl_data_offer *offer,
                  const char           *type)
{
  GdkWaylandSeat *seat = data;

  if (seat->pending_offer != offer)
    {
      GDK_SEAT_DEBUG (seat, EVENTS,
                      "%p: offer for unknown offer %p of %s",
                      seat, offer, type);
      return;
    }

  /* skip magic mime types */
  if (g_str_equal (type, GDK_WAYLAND_LOCAL_DND_MIME_TYPE))
    return;

  gdk_content_formats_builder_add_mime_type (seat->pending_builder, type);
}

static void
data_offer_source_actions (void                 *data,
                           struct wl_data_offer *offer,
                           uint32_t              source_actions)
{
  GdkWaylandSeat *seat = data;

  if (offer == seat->pending_offer)
    {
      seat->pending_source_actions = gdk_wayland_actions_to_gdk_actions (source_actions);
      return;
    }

  if (seat->drop == NULL)
    return;

  gdk_wayland_drop_set_source_actions (seat->drop, source_actions);

  gdk_drop_emit_motion_event (seat->drop,
                              FALSE,
                              seat->pointer_info.surface_x,
                              seat->pointer_info.surface_y,
                              GDK_CURRENT_TIME);
}

static void
data_offer_action (void                 *data,
                   struct wl_data_offer *offer,
                   uint32_t              action)
{
  GdkWaylandSeat *seat = data;

  if (offer == seat->pending_offer)
    {
      seat->pending_action = gdk_wayland_actions_to_gdk_actions (action);
      return;
    }

  if (seat->drop == NULL)
    return;

  gdk_wayland_drop_set_action (seat->drop, action);

  gdk_drop_emit_motion_event (seat->drop,
                              FALSE,
                              seat->pointer_info.surface_x,
                              seat->pointer_info.surface_y,
                              GDK_CURRENT_TIME);
}

static const struct wl_data_offer_listener data_offer_listener = {
  data_offer_offer,
  data_offer_source_actions,
  data_offer_action
};

/* }}} */
/* {{{ Data device listener */

static void
data_device_data_offer (void                  *data,
                        struct wl_data_device *data_device,
                        struct wl_data_offer  *offer)
{
  GdkWaylandSeat *seat = data;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "data device data offer, data device %p, offer %p",
                  data_device, offer);

  gdk_wayland_seat_discard_pending_offer (seat);

  seat->pending_offer = offer;
  wl_data_offer_add_listener (offer,
                              &data_offer_listener,
                              seat);

  seat->pending_builder = gdk_content_formats_builder_new ();
  seat->pending_source_actions = 0;
  seat->pending_action = 0;
}

static void
data_device_enter (void                  *data,
                   struct wl_data_device *data_device,
                   uint32_t               serial,
                   struct wl_surface     *surface,
                   wl_fixed_t             x,
                   wl_fixed_t             y,
                   struct wl_data_offer  *offer)
{
  GdkWaylandSeat *seat = data;
  GdkSurface *dest_surface;
  GdkContentFormats *formats;
  GdkDevice *device;

  if (!surface)
    return;

  dest_surface = wl_surface_get_user_data (surface);

  if (!GDK_IS_SURFACE (dest_surface))
    return;

  if (offer != seat->pending_offer)
    {
      GDK_SEAT_DEBUG (seat, EVENTS,
                      "%p: enter event for unknown offer %p, expected %p",
                      seat, offer, seat->pending_offer);
      return;
    }

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "data device enter, data device %p serial %u, surface %p, x %f y %f, offer %p",
                  data_device, serial, surface, wl_fixed_to_double (x), wl_fixed_to_double (y), offer);

  /* Update pointer state, so device state queries work during DnD */
  seat->pointer_info.focus = g_object_ref (dest_surface);
  seat->pointer_info.surface_x = wl_fixed_to_double (x);
  seat->pointer_info.surface_y = wl_fixed_to_double (y);

  if (seat->logical_pointer)
    device = seat->logical_pointer;
  else if (seat->logical_touch)
    device = seat->logical_touch;
  else
    {
      g_warning ("No device for DND enter, ignoring.");
      return;
    }

  formats = gdk_content_formats_builder_free_to_formats (seat->pending_builder);
  seat->pending_builder = NULL;
  seat->pending_offer = NULL;

  seat->drop = gdk_wayland_drop_new (device, seat->drag, formats, dest_surface, offer, serial);
  gdk_wayland_drop_set_source_actions (seat->drop, seat->pending_source_actions);
  gdk_wayland_drop_set_action (seat->drop, seat->pending_action);

  gdk_content_formats_unref (formats);

  gdk_wayland_seat_discard_pending_offer (seat);

  gdk_drop_emit_enter_event (seat->drop,
                             FALSE,
                             seat->pointer_info.surface_x,
                             seat->pointer_info.surface_y,
                             GDK_CURRENT_TIME);
}

static void
data_device_leave (void                  *data,
                   struct wl_data_device *data_device)
{
  GdkWaylandSeat *seat = data;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "data device leave, data device %p", data_device);

  if (seat->drop == NULL)
    return;

  g_clear_object (&seat->pointer_info.focus);

  gdk_drop_emit_leave_event (seat->drop,
                             FALSE,
                             GDK_CURRENT_TIME);

  g_clear_object (&seat->drop);
}

static void
data_device_motion (void                  *data,
                    struct wl_data_device *data_device,
                    uint32_t               time,
                    wl_fixed_t             x,
                    wl_fixed_t             y)
{
  GdkWaylandSeat *seat = data;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "data device motion, data_device = %p, time = %d, x = %f, y = %f",
                  data_device, time, wl_fixed_to_double (x), wl_fixed_to_double (y));

  if (seat->drop == NULL)
    return;

  /* Update pointer state, so device state queries work during DnD */
  seat->pointer_info.surface_x = wl_fixed_to_double (x);
  seat->pointer_info.surface_y = wl_fixed_to_double (y);

  gdk_drop_emit_motion_event (seat->drop,
                              FALSE,
                              seat->pointer_info.surface_x,
                              seat->pointer_info.surface_y,
                              time);
}

static void
data_device_drop (void                  *data,
                  struct wl_data_device *data_device)
{
  GdkWaylandSeat *seat = data;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "data device drop, data device %p", data_device);

  gdk_drop_emit_drop_event (seat->drop,
                            FALSE,
                            seat->pointer_info.surface_x,
                            seat->pointer_info.surface_y,
                            GDK_CURRENT_TIME);
}

static void
data_device_selection (void                  *data,
                       struct wl_data_device *wl_data_device,
                       struct wl_data_offer  *offer)
{
  GdkWaylandSeat *seat = data;
  GdkContentFormats *formats;

  if (offer)
    {
      if (offer == seat->pending_offer)
        {
          formats = gdk_content_formats_builder_free_to_formats (seat->pending_builder);
          seat->pending_builder = NULL;
          seat->pending_offer = NULL;
        }
      else
        {
          formats = gdk_content_formats_new (NULL, 0);
          offer = NULL;
        }

      gdk_wayland_seat_discard_pending_offer (seat);
    }
  else
    {
      formats = gdk_content_formats_new (NULL, 0);
    }

  gdk_wayland_clipboard_claim_remote (GDK_WAYLAND_CLIPBOARD (seat->clipboard),
                                      offer,
                                      formats);
}

static const struct wl_data_device_listener data_device_listener = {
  data_device_data_offer,
  data_device_enter,
  data_device_leave,
  data_device_motion,
  data_device_drop,
  data_device_selection
};

/* }}} */
/* {{{ Scroll event utilities */

static GdkDevice * get_scroll_device (GdkWaylandSeat              *seat,
                                      enum wl_pointer_axis_source  source);

static void
flush_discrete_scroll_event (GdkWaylandSeat     *seat,
                             gint                value120_x,
                             gint                value120_y)
{
  GdkEvent *event = NULL;
  GdkDevice *source;
  GdkScrollDirection direction;

  if (value120_x > 0)
    direction = GDK_SCROLL_LEFT;
  else if (value120_x < 0)
    direction = GDK_SCROLL_RIGHT;
  else if (value120_y > 0)
    direction = GDK_SCROLL_DOWN;
  else
    direction = GDK_SCROLL_UP;

  source = get_scroll_device (seat, seat->pointer_info.frame.source);

  if (wl_seat_get_version (seat->wl_seat) >= WL_POINTER_AXIS_VALUE120_SINCE_VERSION)
    {
      event = gdk_scroll_event_new_value120 (seat->pointer_info.focus,
                                             source,
                                             NULL,
                                             seat->pointer_info.time,
                                             gdk_wayland_device_get_modifiers (seat->logical_pointer),
                                             direction,
                                             value120_x,
                                             value120_y,
                                             seat->pointer_info.frame.relative_direction);
    }
  else
    {
      gint discrete_x = value120_x / 120;
      gint discrete_y = value120_y / 120;

      if (discrete_x != 0 || discrete_y != 0)
        {
          event = gdk_scroll_event_new_discrete (seat->pointer_info.focus,
                                                 source,
                                                 NULL,
                                                 seat->pointer_info.time,
                                                 gdk_wayland_device_get_modifiers (seat->logical_pointer),
                                                 direction,
                                                 seat->pointer_info.frame.relative_direction);
        }
    }

  if (event)
    _gdk_wayland_display_deliver_event (seat->display, event);
}

static void
flush_smooth_scroll_event (GdkWaylandSeat *seat,
                           double          delta_x,
                           double          delta_y,
                           gboolean        is_stop)
{
  GdkEvent *event;
  GdkDevice *source;

  source = get_scroll_device (seat, seat->pointer_info.frame.source);
  event = gdk_scroll_event_new (seat->pointer_info.focus,
                                source,
                                NULL,
                                seat->pointer_info.time,
                                gdk_wayland_device_get_modifiers (seat->logical_pointer),
                                delta_x, delta_y,
                                is_stop,
                                GDK_SCROLL_UNIT_SURFACE,
                                seat->pointer_info.frame.relative_direction);

  _gdk_wayland_display_deliver_event (seat->display, event);
}

static void
flush_scroll_event (GdkWaylandSeat             *seat,
                    GdkWaylandPointerFrameData *pointer_frame)
{
  gboolean is_stop = FALSE;

  if (pointer_frame->value120_x || pointer_frame->value120_y)
    {
      flush_discrete_scroll_event (seat,
                                   pointer_frame->value120_x,
                                   pointer_frame->value120_y);
      pointer_frame->value120_x = 0;
      pointer_frame->value120_y = 0;
    }
  else if (pointer_frame->is_scroll_stop ||
           pointer_frame->delta_x != 0 ||
           pointer_frame->delta_y != 0)
    {
      /* Axes can stop independently, if we stop on one axis but have a
       * delta on the other, we don't count it as a stop event.
       */

      if (pointer_frame->is_scroll_stop &&
          pointer_frame->delta_x == 0 &&
          pointer_frame->delta_y == 0)
        is_stop = TRUE;

      flush_smooth_scroll_event (seat,
                                 pointer_frame->delta_x,
                                 pointer_frame->delta_y,
                                 is_stop);
    }

  pointer_frame->value120_x = 0;
  pointer_frame->value120_y = 0;
  pointer_frame->delta_x = 0;
  pointer_frame->delta_y = 0;
  pointer_frame->is_scroll_stop = FALSE;
  pointer_frame->relative_direction =
    GDK_SCROLL_RELATIVE_DIRECTION_UNKNOWN;
}

/* }}} */
/* {{{ Frame event utilities */

static void
gdk_wayland_seat_flush_frame_event (GdkWaylandSeat *seat)
{
  if (seat->pointer_info.frame.event)
    {
      _gdk_wayland_display_deliver_event (gdk_seat_get_display (GDK_SEAT (seat)),
                                          seat->pointer_info.frame.event);
      seat->pointer_info.frame.event = NULL;
    }
  else
    {
      flush_scroll_event (seat, &seat->pointer_info.frame);
      seat->pointer_info.frame.source = 0;
    }
}

static void
gdk_wayland_seat_set_frame_event (GdkWaylandSeat *seat,
                                  GdkEvent       *event)
{
  if (seat->pointer_info.frame.event &&
      gdk_event_get_event_type (seat->pointer_info.frame.event) != gdk_event_get_event_type (event))
    gdk_wayland_seat_flush_frame_event (seat);

  seat->pointer_info.frame.event = event;
}

/* }}} */
/* {{{ Pointer listener */

static void
pointer_handle_enter (void              *data,
                      struct wl_pointer *pointer,
                      uint32_t           serial,
                      struct wl_surface *surface,
                      wl_fixed_t         sx,
                      wl_fixed_t         sy)
{
  GdkWaylandSeat *seat = data;
  GdkEvent *event;

  if (!surface)
    return;

  if (!GDK_IS_SURFACE (wl_surface_get_user_data (surface)))
    return;

  seat->pointer_info.focus = wl_surface_get_user_data (surface);
  g_object_ref (seat->pointer_info.focus);

  seat->pointer_info.button_modifiers = 0;

  seat->pointer_info.surface_x = wl_fixed_to_double (sx);
  seat->pointer_info.surface_y = wl_fixed_to_double (sy);
  seat->pointer_info.enter_serial = serial;
  seat->pointer_info.cursor_shape = 0;

  event = gdk_crossing_event_new (GDK_ENTER_NOTIFY,
                                  seat->pointer_info.focus,
                                  seat->logical_pointer,
                                  0,
                                  0,
                                  seat->pointer_info.surface_x,
                                  seat->pointer_info.surface_y,
                                  GDK_CROSSING_NORMAL,
                                  GDK_NOTIFY_NONLINEAR);
  gdk_wayland_seat_set_frame_event (seat, event);

  gdk_wayland_device_update_surface_cursor (seat->logical_pointer);

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "enter, seat %p surface %p",
                  seat, seat->pointer_info.focus);

  if (wl_seat_get_version (seat->wl_seat) < WL_POINTER_HAS_FRAME)
    gdk_wayland_seat_flush_frame_event (seat);
}

static void
pointer_handle_leave (void              *data,
                      struct wl_pointer *pointer,
                      uint32_t           serial,
                      struct wl_surface *surface)
{
  GdkWaylandSeat *seat = data;
  GdkEvent *event;

  if (!seat->pointer_info.focus)
    return;

  event = gdk_crossing_event_new (GDK_LEAVE_NOTIFY,
                                  seat->pointer_info.focus,
                                  seat->logical_pointer,
                                  0,
                                  0,
                                  seat->pointer_info.surface_x,
                                  seat->pointer_info.surface_y,
                                  GDK_CROSSING_NORMAL,
                                  GDK_NOTIFY_NONLINEAR);
  gdk_wayland_seat_set_frame_event (seat, event);

  gdk_wayland_device_update_surface_cursor (seat->logical_pointer);

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "leave, seat %p surface %p",
                  seat, seat->pointer_info.focus);

  g_clear_object (&seat->pointer_info.focus);
  seat->pointer_info.cursor_shape = 0;

  seat->pointer_info.has_cursor_surface = FALSE;

  if (wl_seat_get_version (seat->wl_seat) < WL_POINTER_HAS_FRAME)
    gdk_wayland_seat_flush_frame_event (seat);
}

static void
pointer_handle_motion (void              *data,
                       struct wl_pointer *pointer,
                       uint32_t           time,
                       wl_fixed_t         sx,
                       wl_fixed_t         sy)
{
  GdkWaylandSeat *seat = data;
  GdkEvent *event;

  if (!seat->pointer_info.focus)
    return;

  seat->pointer_info.time = time;
  seat->pointer_info.surface_x = wl_fixed_to_double (sx);
  seat->pointer_info.surface_y = wl_fixed_to_double (sy);

  event = gdk_motion_event_new (seat->pointer_info.focus,
                                seat->logical_pointer,
                                NULL,
                                time,
                                gdk_wayland_device_get_modifiers (seat->logical_pointer),
                                seat->pointer_info.surface_x,
                                seat->pointer_info.surface_y,
                                NULL);
  gdk_wayland_seat_set_frame_event (seat, event);

  if (GDK_DISPLAY_DEBUG_CHECK (gdk_seat_get_display (GDK_SEAT (seat)), EVENTS))
    {
      double x, y;
      gdk_event_get_position (event, &x, &y);
      gdk_debug_message ("motion %f %f, seat %p state %d",
                         x, y, seat, gdk_event_get_modifier_state (event));
    }

  if (wl_seat_get_version (seat->wl_seat) < WL_POINTER_HAS_FRAME)
    gdk_wayland_seat_flush_frame_event (seat);
}

static void
pointer_handle_button (void              *data,
                       struct wl_pointer *pointer,
                       uint32_t           serial,
                       uint32_t           time,
                       uint32_t           button,
                       uint32_t           state)
{
  GdkWaylandSeat *seat = data;
  GdkEvent *event;
  uint32_t modifier;
  int gdk_button;

  if (!seat->pointer_info.focus)
    return;

  switch (button)
    {
    case BTN_LEFT:
      gdk_button = GDK_BUTTON_PRIMARY;
      break;
    case BTN_MIDDLE:
      gdk_button = GDK_BUTTON_MIDDLE;
      break;
    case BTN_RIGHT:
      gdk_button = GDK_BUTTON_SECONDARY;
      break;
    default:
       /* For compatibility reasons, all additional buttons go after the old 4-7 scroll ones */
      gdk_button = button - BUTTON_BASE + 4;
      break;
    }

  seat->pointer_info.time = time;
  if (state)
    seat->pointer_info.press_serial = serial;

  event = gdk_button_event_new (state ? GDK_BUTTON_PRESS : GDK_BUTTON_RELEASE,
                                seat->pointer_info.focus,
                                seat->logical_pointer,
                                NULL,
                                time,
                                gdk_wayland_device_get_modifiers (seat->logical_pointer),
                                gdk_button,
                                seat->pointer_info.surface_x,
                                seat->pointer_info.surface_y,
                                NULL);

  gdk_wayland_seat_set_frame_event (seat, event);

  switch (button)
    {
    case BTN_RIGHT:
      modifier = GDK_BUTTON3_MASK;
      break;
    case BTN_MIDDLE:
      modifier = GDK_BUTTON2_MASK;
      break;
    default:
      modifier = (GDK_BUTTON1_MASK << (button - BUTTON_BASE - 1)) & ALL_BUTTONS_MASK;
      break;
    }

  if (state)
    seat->pointer_info.button_modifiers |= modifier;
  else
    seat->pointer_info.button_modifiers &= ~modifier;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "button %d %s, seat %p state %d",
                  gdk_button_event_get_button (event),
                  state ? "press" : "release",
                  seat,
                  gdk_event_get_modifier_state (event));

  if (wl_seat_get_version (seat->wl_seat) < WL_POINTER_HAS_FRAME)
    gdk_wayland_seat_flush_frame_event (seat);
}

static const char *
get_axis_name (uint32_t axis)
{
  switch (axis)
    {
    case WL_POINTER_AXIS_VERTICAL_SCROLL:
      return "vertical";
    case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
      return "horizontal";
    default:
      return "unknown";
    }
}

static const char *
get_direction_name (uint32_t direction)
{
  switch (direction)
    {
    case WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL:
      return "identical";
    case WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED:
      return "inverted";
    default:
      return "unknown";
    }
}

static void
pointer_handle_axis (void              *data,
                     struct wl_pointer *pointer,
                     uint32_t           time,
                     uint32_t           axis,
                     wl_fixed_t         value)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandPointerFrameData *pointer_frame = &seat->pointer_info.frame;

  if (!seat->pointer_info.focus)
    return;

  /* get the delta and convert it into the expected range */
  switch (axis)
    {
    case WL_POINTER_AXIS_VERTICAL_SCROLL:
      pointer_frame->delta_y = wl_fixed_to_double (value);
      break;
    case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
      pointer_frame->delta_x = wl_fixed_to_double (value);
      break;
    default:
      g_return_if_reached ();
    }

  seat->pointer_info.time = time;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "scroll, axis %s, value %f, seat %p",
                  get_axis_name (axis), wl_fixed_to_double (value),
                  seat);

  if (wl_seat_get_version (seat->wl_seat) < WL_POINTER_HAS_FRAME)
    gdk_wayland_seat_flush_frame_event (seat);
}

static void
pointer_handle_frame (void              *data,
                      struct wl_pointer *pointer)
{
  GdkWaylandSeat *seat = data;

  GDK_SEAT_DEBUG (seat, EVENTS, "frame, seat %p", seat);

  gdk_wayland_seat_flush_frame_event (seat);
}

static void
pointer_handle_axis_source (void                        *data,
                            struct wl_pointer           *pointer,
                            enum wl_pointer_axis_source  source)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandPointerFrameData *pointer_frame = &seat->pointer_info.frame;

  if (!seat->pointer_info.focus)
    return;

  pointer_frame->source = source;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "axis source %s, seat %p", get_axis_source_name (source), seat);
}

static void
pointer_handle_axis_stop (void              *data,
                          struct wl_pointer *pointer,
                          uint32_t           time,
                          uint32_t           axis)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandPointerFrameData *pointer_frame = &seat->pointer_info.frame;

  if (!seat->pointer_info.focus)
    return;

  seat->pointer_info.time = time;

  switch (axis)
    {
    case WL_POINTER_AXIS_VERTICAL_SCROLL:
      pointer_frame->delta_y = 0;
      break;
    case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
      pointer_frame->delta_x = 0;
      break;
    default:
      g_return_if_reached ();
    }

  pointer_frame->is_scroll_stop = TRUE;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "axis %s stop, seat %p", get_axis_name (axis), seat);
}

static void
pointer_handle_axis_discrete (void              *data,
                              struct wl_pointer *pointer,
                              uint32_t           axis,
                              int32_t            value)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandPointerFrameData *pointer_frame = &seat->pointer_info.frame;

  if (!seat->pointer_info.focus)
    return;

  switch (axis)
    {
    case WL_POINTER_AXIS_VERTICAL_SCROLL:
      pointer_frame->value120_y = value * 120;
      break;
    case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
      pointer_frame->value120_x = value * 120;
      break;
    default:
      g_return_if_reached ();
    }

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "discrete scroll, axis %s, value %d, seat %p",
                  get_axis_name (axis), value, seat);
}

static void
pointer_handle_axis_value120 (void              *data,
                              struct wl_pointer *pointer,
                              uint32_t           axis,
                              int32_t            value)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandPointerFrameData *pointer_frame = &seat->pointer_info.frame;

  if (!seat->pointer_info.focus)
    return;

  switch (axis)
    {
    case WL_POINTER_AXIS_VERTICAL_SCROLL:
      pointer_frame->value120_y = value;
      break;
    case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
      pointer_frame->value120_x = value;
      break;
    default:
      g_return_if_reached ();
    }

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "value120 scroll, axis %s, value %d, seat %p",
                  get_axis_name (axis), value, seat);
}

static void
pointer_handle_axis_relative_direction (void              *data,
                                        struct wl_pointer *pointer,
                                        uint32_t           axis,
                                        uint32_t           direction)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandPointerFrameData *pointer_frame = &seat->pointer_info.frame;

  if (!seat->pointer_info.focus)
    return;

  switch (axis)
    {
    case WL_POINTER_AXIS_VERTICAL_SCROLL:
    case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
      pointer_frame->relative_direction = direction;
      break;
    default:
      g_return_if_reached ();
    }

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "scroll relative direction, axis %s, direction %s, seat %p",
                  get_axis_name (axis), get_direction_name (direction), seat);
}

static const struct wl_pointer_listener pointer_listener = {
  pointer_handle_enter,
  pointer_handle_leave,
  pointer_handle_motion,
  pointer_handle_button,
  pointer_handle_axis,
  pointer_handle_frame,
  pointer_handle_axis_source,
  pointer_handle_axis_stop,
  pointer_handle_axis_discrete,
  pointer_handle_axis_value120,
  pointer_handle_axis_relative_direction,
};

/* }}} */
/* {{{ Key event utilities */

static gboolean keyboard_repeat (gpointer data);

static gboolean
get_key_repeat (GdkWaylandSeat *seat,
                uint32_t       *delay,
                uint32_t       *interval)
{
  gboolean repeat;

  if (seat->have_server_repeat)
    {
      if (seat->server_repeat_rate > 0)
        {
          repeat = TRUE;
          *delay = seat->server_repeat_delay;
          *interval = (1000 / seat->server_repeat_rate);
        }
      else
        {
          repeat = FALSE;
        }
    }
  else
    {
      repeat = TRUE;
      *delay = 400;
      *interval = 80;
    }

  return repeat;
}

static void
stop_key_repeat (GdkWaylandSeat *seat)
{
  g_clear_handle_id (&seat->repeat_timer, g_source_remove);

  g_clear_pointer (&seat->repeat_callback, wl_callback_destroy);
}

static void
deliver_key_event (GdkWaylandSeat *seat,
                   uint32_t        time_,
                   uint32_t        key,
                   uint32_t        state,
                   gboolean        from_key_repeat)
{
  GdkEvent *event;
  struct xkb_state *xkb_state;
  struct xkb_keymap *xkb_keymap;
  GdkKeymap *keymap;
  uint32_t delay, interval, timeout;
  int64_t begin_time, now;
  xkb_mod_mask_t consumed;
  GdkTranslatedKey translated;
  GdkTranslatedKey no_lock;
  xkb_mod_mask_t modifiers;
  xkb_mod_index_t caps_lock;

  begin_time = g_get_monotonic_time ();

  stop_key_repeat (seat);

  keymap = seat->keymap;
  xkb_state = _gdk_wayland_keymap_get_xkb_state (keymap);
  xkb_keymap = _gdk_wayland_keymap_get_xkb_keymap (keymap);

  translated.keyval = xkb_state_key_get_one_sym (xkb_state, key);
  modifiers = xkb_state_serialize_mods (xkb_state, XKB_STATE_MODS_EFFECTIVE);
  consumed = modifiers & xkb_state_key_get_consumed_mods2 (xkb_state, key, XKB_CONSUMED_MODE_GTK);
  translated.consumed = gdk_wayland_keymap_get_gdk_modifiers (keymap, consumed);
  translated.layout = xkb_state_key_get_layout (xkb_state, key);
  translated.level = xkb_state_key_get_level (xkb_state, key, translated.layout);

  if (translated.keyval == XKB_KEY_NoSymbol)
    return;

  seat->pointer_info.time = time_;
  seat->key_modifiers = gdk_keymap_get_modifier_state (keymap);


  modifiers = xkb_state_serialize_mods (xkb_state, XKB_STATE_MODS_EFFECTIVE);
  caps_lock = xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_CAPS);
  if (modifiers & (1 << caps_lock))
    {
      struct xkb_state *tmp_state = xkb_state_new (xkb_keymap);
      xkb_layout_index_t layout;

      modifiers &= ~(1 << caps_lock);
      layout = xkb_state_serialize_layout (xkb_state, XKB_STATE_LAYOUT_EFFECTIVE);
      xkb_state_update_mask (tmp_state, modifiers, 00, layout, 00);

      no_lock.keyval = xkb_state_key_get_one_sym (tmp_state, key);
      consumed = modifiers & ~xkb_state_mod_mask_remove_consumed (tmp_state, key, modifiers);
      no_lock.consumed = gdk_wayland_keymap_get_gdk_modifiers (keymap, consumed);
      no_lock.layout = xkb_state_key_get_layout (tmp_state, key);
      no_lock.level = xkb_state_key_get_level (tmp_state, key, no_lock.layout);

      xkb_state_unref (tmp_state);
    }
  else
    {
      no_lock = translated;
    }

  event = gdk_key_event_new (state ? GDK_KEY_PRESS : GDK_KEY_RELEASE,
                             seat->keyboard_focus,
                             seat->logical_keyboard,
                             time_,
                             key,
                             gdk_wayland_device_get_modifiers (seat->logical_pointer),
                             _gdk_wayland_keymap_key_is_modifier (keymap, key),
                             &translated,
                             &no_lock,
                             NULL);

  _gdk_wayland_display_deliver_event (seat->display, event);

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "keyboard %s event%s, surface %p, code %d, sym %d (%s), "
                  "mods 0x%x, consumed 0x%x, layout %d level %d",
                  (state ? "press" : "release"),
                  (from_key_repeat ? " (repeat)" : ""),
                  gdk_event_get_surface (event),
                  gdk_key_event_get_keycode (event),
                  gdk_key_event_get_keyval (event),
                  gdk_keyval_name (gdk_key_event_get_keyval (event)),
                  gdk_event_get_modifier_state (event),
                  gdk_key_event_get_consumed_modifiers (event),
                  gdk_key_event_get_layout (event),
                  gdk_key_event_get_level (event));

  if (!xkb_keymap_key_repeats (xkb_keymap, key))
    return;

  if (!get_key_repeat (seat, &delay, &interval))
    return;

  if (!from_key_repeat)
    {
      if (state) /* Another key is pressed */
        {
          seat->repeat_key = key;
        }
      else if (seat->repeat_key == key) /* Repeated key is released */
        {
          seat->repeat_key = 0;
        }
    }

  if (!seat->repeat_key)
    return;

  seat->repeat_count++;

  interval *= 1000L;
  delay *= 1000L;

  now = g_get_monotonic_time ();

  if (seat->repeat_count == 1)
    seat->repeat_deadline = begin_time + delay;
  else if (seat->repeat_deadline + interval > now)
    seat->repeat_deadline += interval;
  else
    /* frame delay caused us to miss repeat deadline */
    seat->repeat_deadline = now;

  timeout = (seat->repeat_deadline - now) / 1000L;

  seat->repeat_timer = g_timeout_add (timeout, keyboard_repeat, seat);
  gdk_source_set_static_name_by_id (seat->repeat_timer, "[gtk] keyboard_repeat");
}

static void
sync_after_repeat_callback (void               *data,
                            struct wl_callback *callback,
                            uint32_t            time)
{
  GdkWaylandSeat *seat = data;

  g_clear_pointer (&seat->repeat_callback, wl_callback_destroy);
  deliver_key_event (seat, seat->keyboard_time, seat->repeat_key, 1TRUE);
}

static const struct wl_callback_listener sync_after_repeat_callback_listener = {
  sync_after_repeat_callback
};

static gboolean
keyboard_repeat (gpointer data)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandDisplay *display = GDK_WAYLAND_DISPLAY (seat->display);

  /* Ping the server and wait for the timeout.  We won't process
   * key repeat until it responds, since a hung server could lead
   * to a delayed key release event. We don't want to generate
   * repeat events long after the user released the key, just because
   * the server is tardy in telling us the user released the key.
   */

  seat->repeat_callback = wl_display_sync (display->wl_display);

  wl_callback_add_listener (seat->repeat_callback,
                            &sync_after_repeat_callback_listener,
                            seat);

  seat->repeat_timer = 0;
  return G_SOURCE_REMOVE;
}

/* }}} */
/* {{{ Keyboard listener */

static void
keyboard_handle_keymap (void               *data,
                        struct wl_keyboard *keyboard,
                        uint32_t            format,
                        int                 fd,
                        uint32_t            size)
{
  GdkWaylandSeat *seat = data;
  PangoDirection direction;
  gboolean bidi;
  gboolean caps_lock;
  gboolean num_lock;
  gboolean scroll_lock;
  GdkModifierType modifiers;
  int layout_index;
  GStrv old_layout_names, new_layout_names;

  direction = gdk_keymap_get_direction (seat->keymap);
  bidi = gdk_keymap_have_bidi_layouts (seat->keymap);
  caps_lock = gdk_keymap_get_caps_lock_state (seat->keymap);
  num_lock = gdk_keymap_get_num_lock_state (seat->keymap);
  scroll_lock = gdk_keymap_get_scroll_lock_state (seat->keymap);
  modifiers = gdk_keymap_get_modifier_state (seat->keymap);
  layout_index = gdk_keymap_get_active_layout_index (seat->keymap);
  old_layout_names = gdk_keymap_get_layout_names (seat->keymap);

  _gdk_wayland_keymap_update_from_fd (seat->keymap, format, fd, size);

  new_layout_names = gdk_keymap_get_layout_names (seat->keymap);
  if (new_layout_names)
    {
      if (GDK_DISPLAY_DEBUG_CHECK (seat->keymap->display, INPUT))
        {
          int n_layouts = g_strv_length (new_layout_names);
          GString *s = g_string_new ("");
          for (int i = 0; i < n_layouts; i++)
            {
              if (s->len > 0)
                g_string_append (s, ", ");
              if (i == layout_index)
                g_string_append (s, "*");
              g_string_append (s, new_layout_names[i]);
            }
          gdk_debug_message ("layouts: %s", s->str);
          g_string_free (s, TRUE);
        }
    }
  g_signal_emit_by_name (seat->keymap, "keys-changed");
  g_signal_emit_by_name (seat->keymap, "state-changed");
  if (direction != gdk_keymap_get_direction (seat->keymap))
    g_signal_emit_by_name (seat->keymap, "direction-changed");

  if (direction != gdk_keymap_get_direction (seat->keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "direction");
  if (bidi != gdk_keymap_have_bidi_layouts (seat->keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "has-bidi-layouts");
  if (caps_lock != gdk_keymap_get_caps_lock_state (seat->keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "caps-lock-state");
  if (num_lock != gdk_keymap_get_num_lock_state (seat->keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "num-lock-state");
  if (scroll_lock != gdk_keymap_get_scroll_lock_state (seat->keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "scroll-lock-state");
  if (modifiers != gdk_keymap_get_modifier_state (seat->keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "modifier-state");
  if (layout_index != gdk_keymap_get_active_layout_index (seat->keymap))
      g_object_notify (G_OBJECT (seat->logical_keyboard), "active-layout-index");
  if (!g_strv_equal ((const gchar * const *) old_layout_names,
                     (const gchar * const *) new_layout_names))
      g_object_notify (G_OBJECT (seat->logical_keyboard), "layout-names");

  g_strfreev (old_layout_names);
  g_strfreev (new_layout_names);

}

static void
keyboard_handle_enter (void               *data,
                       struct wl_keyboard *keyboard,
                       uint32_t            serial,
                       struct wl_surface  *surface,
                       struct wl_array    *keys)
{
  GdkWaylandSeat *seat = data;
  GdkEvent *event;

  if (!surface)
    return;

  if (!GDK_IS_SURFACE (wl_surface_get_user_data (surface)))
    return;

  seat->keyboard_focus = wl_surface_get_user_data (surface);
  g_object_ref (seat->keyboard_focus);
  seat->repeat_key = 0;

  event = gdk_focus_event_new (seat->keyboard_focus,
                               seat->logical_keyboard,
                               TRUE);

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "focus in, seat %p surface %p",
                  seat, seat->keyboard_focus);

  _gdk_wayland_display_deliver_event (seat->display, event);
}

static void
keyboard_handle_leave (void               *data,
                       struct wl_keyboard *keyboard,
                       uint32_t            serial,
                       struct wl_surface  *surface)
{
  GdkWaylandSeat *seat = data;
  GdkEvent *event;

  if (!seat->keyboard_focus)
    return;

  /* gdk_surface_is_destroyed() might already return TRUE for
   * seat->keyboard_focus here, which would happen if we destroyed the
   * surface before losing keyboard focus.
   */

  stop_key_repeat (seat);

  event = gdk_focus_event_new (seat->keyboard_focus,
                               seat->logical_keyboard,
                               FALSE);

  g_clear_object (&seat->keyboard_focus);
  seat->repeat_key = 0;
  seat->key_modifiers = 0;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "focus out, seat %p surface %p",
                  seat, gdk_event_get_surface (event));

  _gdk_wayland_display_deliver_event (seat->display, event);
}

static void
keyboard_handle_key (void               *data,
                     struct wl_keyboard *keyboard,
                     uint32_t            serial,
                     uint32_t            time,
                     uint32_t            key,
                     uint32_t            state_w)
{
  GdkWaylandSeat *seat = data;
  uint32_t press, repeat;

  if (!seat->keyboard_focus)
    return;

  if (state_w == WL_KEYBOARD_KEY_STATE_REPEATED &&
      wl_keyboard_get_version (keyboard) < WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION)
    {
      g_warning ("Unexpected 'repeated' key state, wl_keyboard version is %d\n",
                 wl_keyboard_get_version (keyboard));
      return;
    }

  press = state_w == WL_KEYBOARD_KEY_STATE_PRESSED ||
    state_w == WL_KEYBOARD_KEY_STATE_REPEATED;
  repeat = state_w == WL_KEYBOARD_KEY_STATE_REPEATED;

  seat->keyboard_time = time;
  seat->keyboard_key_serial = serial;
  seat->repeat_count = 0;
  deliver_key_event (data, time, key + 8, press, repeat);
}

static void
keyboard_handle_modifiers (void               *data,
                           struct wl_keyboard *keyboard,
                           uint32_t            serial,
                           uint32_t            mods_depressed,
                           uint32_t            mods_latched,
                           uint32_t            mods_locked,
                           uint32_t            group)
{
  GdkWaylandSeat *seat = data;
  GdkKeymap *keymap;
  struct xkb_state *xkb_state;
  PangoDirection direction;
  gboolean bidi;
  gboolean caps_lock;
  gboolean num_lock;
  gboolean scroll_lock;
  GdkModifierType modifiers;
  int layout_index;
  GStrv old_layout_names, new_layout_names;

  keymap = seat->keymap;
  xkb_state = _gdk_wayland_keymap_get_xkb_state (keymap);

  direction = gdk_keymap_get_direction (keymap);
  bidi = gdk_keymap_have_bidi_layouts (keymap);
  caps_lock = gdk_keymap_get_caps_lock_state (keymap);
  num_lock = gdk_keymap_get_num_lock_state (keymap);
  scroll_lock = gdk_keymap_get_scroll_lock_state (keymap);
  modifiers = gdk_keymap_get_modifier_state (keymap);
  layout_index = gdk_keymap_get_active_layout_index (keymap);
  old_layout_names = gdk_keymap_get_layout_names (keymap);

  /* Note: the docs for xkb_state_update mask state that all parameters
   * must be passed, or we may end up with an 'incoherent' state. But the
   * Wayland modifiers event only includes a single group field, so we
   * can't pass depressed/latched/locked groups.
   *
   * We assume that the compositor is sending us the 'effective' group
   * (the protocol is not clear on that point), and pass it as the depressed
   * group - we are basically pretending that the user holds down a key for
   * this group at all times.
   *
   * This means that our xkb_state would answer a few questions differently
   * from the compositors xkb_state - e.g. if you asked it about the latched
   * group. But nobody is asking it those questions, so it does not really
   * matter. We hope.
   */

  xkb_state_update_mask (xkb_state, mods_depressed, mods_latched, mods_locked, group, 00);

  seat->key_modifiers = gdk_keymap_get_modifier_state (keymap);

  new_layout_names = gdk_keymap_get_layout_names (keymap);

  g_signal_emit_by_name (keymap, "state-changed");
  if (layout_index != gdk_keymap_get_active_layout_index (keymap))
    {
      layout_index = gdk_keymap_get_active_layout_index (keymap);
      GDK_DISPLAY_DEBUG (keymap->display, INPUT, "active layout now: %s",
                         new_layout_names[layout_index]);

      g_signal_emit_by_name (keymap, "keys-changed");
      g_object_notify (G_OBJECT (seat->logical_keyboard), "active-layout-index");
    }
  if (direction != gdk_keymap_get_direction (keymap))
    {
      g_signal_emit_by_name (keymap, "direction-changed");
      g_object_notify (G_OBJECT (seat->logical_keyboard), "direction");
    }
  if (bidi != gdk_keymap_have_bidi_layouts (keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "has-bidi-layouts");
  if (caps_lock != gdk_keymap_get_caps_lock_state (keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "caps-lock-state");
  if (num_lock != gdk_keymap_get_num_lock_state (keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "num-lock-state");
  if (scroll_lock != gdk_keymap_get_scroll_lock_state (keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "scroll-lock-state");
  if (modifiers != gdk_keymap_get_modifier_state (keymap))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "modifier-state");
  if (!g_strv_equal ((const gchar * const *) old_layout_names,
                     (const gchar * const *) new_layout_names))
    g_object_notify (G_OBJECT (seat->logical_keyboard), "layout-names");

  g_strfreev (old_layout_names);
  g_strfreev (new_layout_names);
}

static void
keyboard_handle_repeat_info (void               *data,
                             struct wl_keyboard *keyboard,
                             int32_t             rate,
                             int32_t             delay)
{
  GdkWaylandSeat *seat = data;

  seat->have_server_repeat = TRUE;
  seat->server_repeat_rate = rate;
  seat->server_repeat_delay = delay;
}

static const struct wl_keyboard_listener keyboard_listener = {
  keyboard_handle_keymap,
  keyboard_handle_enter,
  keyboard_handle_leave,
  keyboard_handle_key,
  keyboard_handle_modifiers,
  keyboard_handle_repeat_info,
};

/* }}} */
/* {{{ Touch event utilities */

static GdkWaylandTouchData *
gdk_wayland_seat_add_touch (GdkWaylandSeat    *seat,
                            uint32_t           id,
                            struct wl_surface *surface)
{
  GdkWaylandTouchData *touch;

  touch = g_new0 (GdkWaylandTouchData, 1);
  touch->id = id;
  touch->surface = wl_surface_get_user_data (surface);
  touch->initial_touch = (g_hash_table_size (seat->touches) == 0);

  g_hash_table_insert (seat->touches, GUINT_TO_POINTER (id), touch);

  return touch;
}

GdkWaylandTouchData *
gdk_wayland_seat_get_touch (GdkWaylandSeat *seat,
                            uint32_t        id)
{
  return g_hash_table_lookup (seat->touches, GUINT_TO_POINTER (id));
}

static void
gdk_wayland_seat_remove_touch (GdkWaylandSeat *seat,
                               uint32_t        id)
{
  g_hash_table_remove (seat->touches, GUINT_TO_POINTER (id));
}

void
gdk_wayland_seat_clear_touchpoints (GdkWaylandSeat *seat,
                                    GdkSurface     *surface)
{
  GHashTableIter iter;
  GdkWaylandTouchData *touch;

  g_hash_table_iter_init (&iter, seat->touches);

  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &touch))
    {
      if (touch->surface == surface)
        g_hash_table_iter_remove (&iter);
    }
}

static void
mimic_pointer_emulating_touch_info (GdkDevice           *device,
                                    GdkWaylandTouchData *touch)
{
  GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (device);
  GdkWaylandPointerData *pointer;

  pointer = gdk_wayland_device_get_pointer (wayland_device);
  g_set_object (&pointer->focus, touch->surface);
  pointer->press_serial = pointer->enter_serial = touch->touch_down_serial;
  pointer->surface_x = touch->x;
  pointer->surface_y = touch->y;
}

static void
touch_handle_logical_pointer_crossing (GdkWaylandSeat      *seat,
                                       GdkWaylandTouchData *touch,
                                       uint32_t             time)
{
  GdkWaylandPointerData *pointer;

  pointer = gdk_wayland_device_get_pointer (GDK_WAYLAND_DEVICE (seat->logical_touch));

  if (pointer->focus == touch->surface)
    return;

  if (pointer->focus)
    {
      emulate_touch_crossing (pointer->focus, NULL,
                              seat->logical_touch, seat->touch, touch,
                              GDK_LEAVE_NOTIFY, GDK_CROSSING_NORMAL, time);
    }

  if (touch->surface)
    {
      emulate_touch_crossing (touch->surface, NULL,
                              seat->logical_touch, seat->touch, touch,
                              GDK_ENTER_NOTIFY, GDK_CROSSING_NORMAL, time);
    }
}

/* }}} */
/* {{{ Touch listener */

static void
touch_handle_down (void              *data,
                   struct wl_touch   *wl_touch,
                   uint32_t           serial,
                   uint32_t           time,
                   struct wl_surface *wl_surface,
                   int32_t            id,
                   wl_fixed_t         x,
                   wl_fixed_t         y)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandTouchData *touch;
  GdkEvent *event;

  if (!wl_surface)
    return;

  touch = gdk_wayland_seat_add_touch (seat, id, wl_surface);
  touch->x = wl_fixed_to_double (x);
  touch->y = wl_fixed_to_double (y);
  touch->touch_down_serial = serial;
  seat->latest_touch_down_serial = serial;

  event = gdk_touch_event_new (GDK_TOUCH_BEGIN,
                               GDK_SLOT_TO_EVENT_SEQUENCE (touch->id),
                               touch->surface,
                               seat->logical_touch,
                               time,
                               gdk_wayland_device_get_modifiers (seat->logical_touch),
                               touch->x, touch->y,
                               NULL,
                               touch->initial_touch);

  if (touch->initial_touch)
    {
      touch_handle_logical_pointer_crossing (seat, touch, time);
      gdk_wayland_device_set_emulating_touch (GDK_WAYLAND_DEVICE (seat->logical_touch),
                                              touch);
      mimic_pointer_emulating_touch_info (seat->logical_touch, touch);
    }

  if (GDK_DISPLAY_DEBUG_CHECK (gdk_seat_get_display (GDK_SEAT (seat)), EVENTS))
    {
      double xx, yy;
      gdk_event_get_position (event, &xx, &yy);
      gdk_debug_message ("touch begin %f %f", xx, yy);
    }

  _gdk_wayland_display_deliver_event (seat->display, event);
}

static void
touch_handle_up (void            *data,
                 struct wl_touch *wl_touch,
                 uint32_t         serial,
                 uint32_t         time,
                 int32_t          id)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandTouchData *touch;
  GdkEvent *event;

  touch = gdk_wayland_seat_get_touch (seat, id);
  if (!touch)
    return;

  event = gdk_touch_event_new (GDK_TOUCH_END,
                               GDK_SLOT_TO_EVENT_SEQUENCE (touch->id),
                               touch->surface,
                               seat->logical_touch,
                               time,
                               gdk_wayland_device_get_modifiers (seat->logical_touch),
                               touch->x, touch->y,
                               NULL,
                               touch->initial_touch);

  if (GDK_DISPLAY_DEBUG_CHECK (gdk_seat_get_display (GDK_SEAT (seat)), EVENTS))
    {
      double x, y;
      gdk_event_get_position (event, &x, &y);
      gdk_debug_message ("touch end %f %f", x, y);
    }

  _gdk_wayland_display_deliver_event (seat->display, event);

  if (touch->initial_touch)
    gdk_wayland_device_set_emulating_touch (GDK_WAYLAND_DEVICE (seat->logical_touch),
                                            NULL);

  gdk_wayland_seat_remove_touch (seat, id);
}

static void
touch_handle_motion (void            *data,
                     struct wl_touch *wl_touch,
                     uint32_t         time,
                     int32_t          id,
                     wl_fixed_t       x,
                     wl_fixed_t       y)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandTouchData *touch;
  GdkEvent *event;

  touch = gdk_wayland_seat_get_touch (seat, id);
  if (!touch)
    return;

  touch->x = wl_fixed_to_double (x);
  touch->y = wl_fixed_to_double (y);

  if (touch->initial_touch)
    mimic_pointer_emulating_touch_info (seat->logical_touch, touch);

  event = gdk_touch_event_new (GDK_TOUCH_UPDATE,
                               GDK_SLOT_TO_EVENT_SEQUENCE (touch->id),
                               touch->surface,
                               seat->logical_touch,
                               time,
                               gdk_wayland_device_get_modifiers (seat->logical_touch),
                               touch->x, touch->y,
                               NULL,
                               touch->initial_touch);

  if (GDK_DISPLAY_DEBUG_CHECK (gdk_seat_get_display (GDK_SEAT (seat)), EVENTS))
    {
      double xx, yy;
      gdk_event_get_position (event, &xx, &yy);
      gdk_debug_message ("touch update %f %f", xx, yy);
    }

  _gdk_wayland_display_deliver_event (seat->display, event);
}

static void
touch_handle_frame (void            *data,
                    struct wl_touch *wl_touch)
{
}

static void
touch_handle_cancel (void            *data,
                     struct wl_touch *wl_touch)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandTouchData *touch;
  GHashTableIter iter;
  GdkEvent *event;

  gdk_wayland_device_set_emulating_touch (GDK_WAYLAND_DEVICE (seat->logical_touch),
                                          NULL);

  g_hash_table_iter_init (&iter, seat->touches);

  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &touch))
    {
      event = gdk_touch_event_new (GDK_TOUCH_CANCEL,
                                   GDK_SLOT_TO_EVENT_SEQUENCE (touch->id),
                                   touch->surface,
                                   seat->logical_touch,
                                   GDK_CURRENT_TIME,
                                   gdk_wayland_device_get_modifiers (seat->logical_touch),
                                   touch->x, touch->y,
                                   NULL,
                                   touch->initial_touch);
      _gdk_wayland_display_deliver_event (seat->display, event);
      g_hash_table_iter_remove (&iter);
    }

  GDK_SEAT_DEBUG (seat, EVENTS, "touch cancel");
}

static void
touch_handle_shape (void            *data,
                    struct wl_touch *touch,
                    int32_t         id,
                    wl_fixed_t      major,
                    wl_fixed_t      minor)
{
}

static void
touch_handle_orientation (void            *data,
                          struct wl_touch *touch,
                          int32_t         id,
                          wl_fixed_t      orientation)
{
}

static const struct wl_touch_listener touch_listener = {
  touch_handle_down,
  touch_handle_up,
  touch_handle_motion,
  touch_handle_frame,
  touch_handle_cancel,
  touch_handle_shape,
  touch_handle_orientation,
};

/* }}} */
/* {{{ Swipe gesture listener */

static void
emit_gesture_swipe_event (GdkWaylandSeat          *seat,
                          GdkTouchpadGesturePhase  phase,
                          uint32_t                 _time,
                          uint32_t                 n_fingers,
                          double                   dx,
                          double                   dy)
{
  GdkEvent *event;

  if (!seat->pointer_info.focus)
    return;

  seat->pointer_info.time = _time;

  if (phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN)
    seat->pointer_info.touchpad_event_sequence++;

  event = gdk_touchpad_event_new_swipe (seat->pointer_info.focus,
                                        GDK_SLOT_TO_EVENT_SEQUENCE (seat->pointer_info.touchpad_event_sequence),
                                        seat->logical_pointer,
                                        _time,
                                        gdk_wayland_device_get_modifiers (seat->logical_pointer),
                                        phase,
                                        seat->pointer_info.surface_x,
                                        seat->pointer_info.surface_y,
                                        n_fingers,
                                        dx, dy);

  if (GDK_DISPLAY_DEBUG_CHECK (gdk_seat_get_display (GDK_SEAT (seat)), EVENTS))
    {
      double x, y;
      gdk_event_get_position (event, &x, &y);
      gdk_debug_message ("swipe event %d, coords: %f %f, seat %p state %d",
                         gdk_event_get_event_type (event), x, y, seat,
                         gdk_event_get_modifier_state (event));
    }

  _gdk_wayland_display_deliver_event (seat->display, event);
}

static void
gesture_swipe_begin (void                                *data,
                     struct zwp_pointer_gesture_swipe_v1 *swipe,
                     uint32_t                             serial,
                     uint32_t                             time,
                     struct wl_surface                   *surface,
                     uint32_t                             fingers)
{
  GdkWaylandSeat *seat = data;

  emit_gesture_swipe_event (seat,
                            GDK_TOUCHPAD_GESTURE_PHASE_BEGIN,
                            time, fingers, 00);
  seat->gesture_n_fingers = fingers;
}

static void
gesture_swipe_update (void                                *data,
                      struct zwp_pointer_gesture_swipe_v1 *swipe,
                      uint32_t                             time,
                      wl_fixed_t                           dx,
                      wl_fixed_t                           dy)
{
  GdkWaylandSeat *seat = data;

  emit_gesture_swipe_event (seat,
                            GDK_TOUCHPAD_GESTURE_PHASE_UPDATE,
                            time,
                            seat->gesture_n_fingers,
                            wl_fixed_to_double (dx),
                            wl_fixed_to_double (dy));
}

static void
gesture_swipe_end (void                                *data,
                   struct zwp_pointer_gesture_swipe_v1 *swipe,
                   uint32_t                             serial,
                   uint32_t                             time,
                   int32_t                              cancelled)
{
  GdkWaylandSeat *seat = data;
  GdkTouchpadGesturePhase phase;

  phase = (cancelled) ?
    GDK_TOUCHPAD_GESTURE_PHASE_CANCEL :
    GDK_TOUCHPAD_GESTURE_PHASE_END;

  emit_gesture_swipe_event (seat, phase, time,
                            seat->gesture_n_fingers, 00);
}

static const struct zwp_pointer_gesture_swipe_v1_listener gesture_swipe_listener = {
  gesture_swipe_begin,
  gesture_swipe_update,
  gesture_swipe_end
};

/* }}} */
/* {{{ Pinch gesture listener */

static void
emit_gesture_pinch_event (GdkWaylandSeat          *seat,
                          GdkTouchpadGesturePhase  phase,
                          uint32_t                 _time,
                          uint32_t                 n_fingers,
                          double                   dx,
                          double                   dy,
                          double                   scale,
                          double                   angle_delta)
{
  GdkEvent *event;

  if (!seat->pointer_info.focus)
    return;

  seat->pointer_info.time = _time;

  if (phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN)
    seat->pointer_info.touchpad_event_sequence++;

  event = gdk_touchpad_event_new_pinch (seat->pointer_info.focus,
                                        GDK_SLOT_TO_EVENT_SEQUENCE (seat->pointer_info.touchpad_event_sequence),
                                        seat->logical_pointer,
                                        _time,
                                        gdk_wayland_device_get_modifiers (seat->logical_pointer),
                                        phase,
                                        seat->pointer_info.surface_x,
                                        seat->pointer_info.surface_y,
                                        n_fingers,
                                        dx, dy,
                                        scale, angle_delta * G_PI / 180);

  if (GDK_DISPLAY_DEBUG_CHECK (gdk_seat_get_display (GDK_SEAT (seat)), EVENTS))
    {
      double x, y;
      gdk_event_get_position (event, &x, &y);
      gdk_debug_message ("pinch event %d, coords: %f %f, seat %p state %d",
                         gdk_event_get_event_type (event),
                         x, y, seat,
                         gdk_event_get_modifier_state (event));
    }

  _gdk_wayland_display_deliver_event (seat->display, event);
}

static void
gesture_pinch_begin (void                                *data,
                     struct zwp_pointer_gesture_pinch_v1 *pinch,
                     uint32_t                             serial,
                     uint32_t                             time,
                     struct wl_surface                   *surface,
                     uint32_t                             fingers)
{
  GdkWaylandSeat *seat = data;

  emit_gesture_pinch_event (seat,
                            GDK_TOUCHPAD_GESTURE_PHASE_BEGIN,
                            time, fingers, 0010);
  seat->gesture_n_fingers = fingers;
}

static void
gesture_pinch_update (void                                *data,
                      struct zwp_pointer_gesture_pinch_v1 *pinch,
                      uint32_t                             time,
                      wl_fixed_t                           dx,
                      wl_fixed_t                           dy,
                      wl_fixed_t                           scale,
                      wl_fixed_t                           rotation)
{
  GdkWaylandSeat *seat = data;

  emit_gesture_pinch_event (seat,
                            GDK_TOUCHPAD_GESTURE_PHASE_UPDATE, time,
                            seat->gesture_n_fingers,
                            wl_fixed_to_double (dx),
                            wl_fixed_to_double (dy),
                            wl_fixed_to_double (scale),
                            wl_fixed_to_double (rotation));
}

static void
gesture_pinch_end (void                                *data,
                   struct zwp_pointer_gesture_pinch_v1 *pinch,
                   uint32_t                             serial,
                   uint32_t                             time,
                   int32_t                              cancelled)
{
  GdkWaylandSeat *seat = data;
  GdkTouchpadGesturePhase phase;

  phase = (cancelled) ?
    GDK_TOUCHPAD_GESTURE_PHASE_CANCEL :
    GDK_TOUCHPAD_GESTURE_PHASE_END;

  emit_gesture_pinch_event (seat, phase,
                            time, seat->gesture_n_fingers,
                            0010);
}

static const struct zwp_pointer_gesture_pinch_v1_listener gesture_pinch_listener = {
  gesture_pinch_begin,
  gesture_pinch_update,
  gesture_pinch_end
};

/* }}} */
/* {{{ Hold gesture listener */

static void
emit_gesture_hold_event (GdkWaylandSeat          *seat,
                         GdkTouchpadGesturePhase  phase,
                         uint32_t                 _time,
                         uint32_t                 n_fingers)
{
  GdkEvent *event;

  if (!seat->pointer_info.focus)
    return;

  seat->pointer_info.time = _time;

  if (phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN)
    seat->pointer_info.touchpad_event_sequence++;

  event = gdk_touchpad_event_new_hold (seat->pointer_info.focus,
                                       GDK_SLOT_TO_EVENT_SEQUENCE (seat->pointer_info.touchpad_event_sequence),
                                       seat->logical_pointer,
                                       _time,
                                       gdk_wayland_device_get_modifiers (seat->logical_pointer),
                                       phase,
                                       seat->pointer_info.surface_x,
                                       seat->pointer_info.surface_y,
                                       n_fingers);

  if (GDK_DISPLAY_DEBUG_CHECK (gdk_seat_get_display (GDK_SEAT (seat)), EVENTS))
    {
      double x, y;
      gdk_event_get_position (event, &x, &y);
      gdk_debug_message ("hold event %d, coords: %f %f, seat %p state %d",
                         gdk_event_get_event_type (event),
                         x, y, seat,
                         gdk_event_get_modifier_state (event));
    }

  _gdk_wayland_display_deliver_event (seat->display, event);
}

static void
gesture_hold_begin (void                               *data,
                    struct zwp_pointer_gesture_hold_v1 *hold,
                    uint32_t                            serial,
                    uint32_t                            time,
                    struct wl_surface                  *surface,
                    uint32_t                            fingers)
{
  GdkWaylandSeat *seat = data;

  emit_gesture_hold_event (seat,
                           GDK_TOUCHPAD_GESTURE_PHASE_BEGIN,
                           time, fingers);
  seat->gesture_n_fingers = fingers;
}

static void
gesture_hold_end (void                               *data,
                  struct zwp_pointer_gesture_hold_v1 *hold,
                  uint32_t                            serial,
                  uint32_t                            time,
                  int32_t                             cancelled)
{
  GdkWaylandSeat *seat = data;
  GdkTouchpadGesturePhase phase;

  phase = (cancelled) ?
    GDK_TOUCHPAD_GESTURE_PHASE_CANCEL :
    GDK_TOUCHPAD_GESTURE_PHASE_END;

  emit_gesture_hold_event (seat, phase, time,
                           seat->gesture_n_fingers);
}

static const struct zwp_pointer_gesture_hold_v1_listener gesture_hold_listener = {
  gesture_hold_begin,
  gesture_hold_end
};

/* }}} */
/* {{{ Tablet utilities */

static void
_gdk_wayland_seat_remove_tool (GdkWaylandSeat           *seat,
                               GdkWaylandTabletToolData *tool)
{
  g_clear_pointer (&tool->shape_device, wp_cursor_shape_device_v1_destroy);

  seat->tablet_tools = g_list_remove (seat->tablet_tools, tool);

  gdk_seat_tool_removed (GDK_SEAT (seat), tool->tool);

  zwp_tablet_tool_v2_destroy (tool->wp_tablet_tool);
  g_object_unref (tool->tool);
  g_free (tool);
}

static void
_gdk_wayland_seat_remove_tablet (GdkWaylandSeat       *seat,
                                 GdkWaylandTabletData *tablet)
{
  seat->tablets = g_list_remove (seat->tablets, tablet);

  gdk_seat_device_removed (GDK_SEAT (seat), tablet->stylus_device);
  gdk_seat_device_removed (GDK_SEAT (seat), tablet->logical_device);

  while (tablet->pads)
    {
      GdkWaylandTabletPadData *pad = tablet->pads->data;

      pad->current_tablet = NULL;
      tablet->pads = g_list_remove (tablet->pads, pad);
    }

  zwp_tablet_v2_destroy (tablet->wp_tablet);

  _gdk_device_set_associated_device (tablet->logical_device, NULL);
  _gdk_device_set_associated_device (tablet->stylus_device, NULL);

  if (tablet->pointer_info.focus)
    g_object_unref (tablet->pointer_info.focus);

  wl_surface_destroy (tablet->pointer_info.pointer_surface);
  g_object_unref (tablet->logical_device);
  g_object_unref (tablet->stylus_device);
  g_free (tablet);
}

static void
_gdk_wayland_seat_remove_tablet_pad (GdkWaylandSeat          *seat,
                                     GdkWaylandTabletPadData *pad)
{
  seat->tablet_pads = g_list_remove (seat->tablet_pads, pad);

  if (pad->device)
    {
      gdk_seat_device_removed (GDK_SEAT (seat), pad->device);
      _gdk_device_set_associated_device (pad->device, NULL);
      g_object_unref (pad->device);
    }

  g_free (pad);
}

static GdkWaylandTabletPadGroupData *
tablet_pad_lookup_button_group (GdkWaylandTabletPadData *pad,
                                uint32_t                 button)
{
  GdkWaylandTabletPadGroupData *group;
  GList *l;

  for (l = pad->mode_groups; l; l = l->next)
    {
      group = l->data;

      if (g_list_find (group->buttons, GUINT_TO_POINTER (button)))
        return group;
    }

  return NULL;
}

/* }}} */
/* {{{ Tablet listener */

static void
tablet_handle_name (void                 *data,
                    struct zwp_tablet_v2 *wp_tablet,
                    const char           *name)
{
  GdkWaylandTabletData *tablet = data;

  tablet->name = g_strdup (name);
}

static void
tablet_handle_id (void                 *data,
                  struct zwp_tablet_v2 *wp_tablet,
                  uint32_t              vid,
                  uint32_t              pid)
{
  GdkWaylandTabletData *tablet = data;

  tablet->vid = vid;
  tablet->pid = pid;
}

static void
tablet_handle_path (void                 *data,
                    struct zwp_tablet_v2 *wp_tablet,
                    const char           *path)
{
  GdkWaylandTabletData *tablet = data;

  tablet->path = g_strdup (path);
}

static void
tablet_handle_done (void                 *data,
                    struct zwp_tablet_v2 *wp_tablet)
{
  GdkWaylandTabletData *tablet = data;
  GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (tablet->seat);
  GdkDisplay *display = gdk_seat_get_display (GDK_SEAT (seat));
  GdkDevice *logical_device, *stylus_device;
  char *logical_name;
  char *vid, *pid;

  vid = g_strdup_printf ("%.4x", tablet->vid);
  pid = g_strdup_printf ("%.4x", tablet->pid);

  logical_name = g_strdup_printf ("Logical pointer for %s", tablet->name);
  logical_device = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                 "name", logical_name,
                                 "source", GDK_SOURCE_MOUSE,
                                 "has-cursor"TRUE,
                                 "display", display,
                                 "seat", seat,
                                 NULL);

  gdk_wayland_device_set_pointer (GDK_WAYLAND_DEVICE (logical_device),
                                  &tablet->pointer_info);

  stylus_device = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                "name", tablet->name,
                                "source", GDK_SOURCE_PEN,
                                "has-cursor"FALSE,
                                "display", display,
                                "seat", seat,
                                "vendor-id", vid,
                                "product-id", pid,
                                NULL);

  tablet->logical_device = logical_device;
  init_pointer_data (&tablet->pointer_info, display, tablet->logical_device);

  tablet->stylus_device = stylus_device;

  _gdk_device_set_associated_device (logical_device, seat->logical_keyboard);
  _gdk_device_set_associated_device (stylus_device, logical_device);

  gdk_seat_device_added (GDK_SEAT (seat), logical_device);
  gdk_seat_device_added (GDK_SEAT (seat), stylus_device);

  g_free (logical_name);
  g_free (vid);
  g_free (pid);
}

static void
tablet_handle_removed (void                 *data,
                       struct zwp_tablet_v2 *wp_tablet)
{
  GdkWaylandTabletData *tablet = data;

  _gdk_wayland_seat_remove_tablet (GDK_WAYLAND_SEAT (tablet->seat), tablet);
}

static void
tablet_handle_bustype (void                 *data,
                       struct zwp_tablet_v2 *wp_tablet,
                       uint32_t              bustype)
{
  GdkWaylandTabletData *tablet = data;

  tablet->bustype = bustype;
}

static const struct zwp_tablet_v2_listener tablet_listener = {
  tablet_handle_name,
  tablet_handle_id,
  tablet_handle_path,
  tablet_handle_done,
  tablet_handle_removed,
  tablet_handle_bustype,
};

/* }}} */
/* {{{ Seat listener */

static void
seat_handle_capabilities (void                    *data,
                          struct wl_seat          *wl_seat,
                          enum wl_seat_capability  caps)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (seat->display);

  GDK_SEAT_DEBUG (seat, MISC,
                  "seat %p with %s%s%s", wl_seat,
                  (caps & WL_SEAT_CAPABILITY_POINTER) ? " pointer, " : "",
                  (caps & WL_SEAT_CAPABILITY_KEYBOARD) ? " keyboard, " : "",
                  (caps & WL_SEAT_CAPABILITY_TOUCH) ? " touch" : "");

  if ((caps & WL_SEAT_CAPABILITY_POINTER) && !seat->wl_pointer)
    {
      seat->wl_pointer = wl_seat_get_pointer (wl_seat);
      wl_pointer_set_user_data (seat->wl_pointer, seat);
      wl_pointer_add_listener (seat->wl_pointer, &pointer_listener, seat);

      seat->pointer = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                    "name""Wayland Pointer",
                                    "source", GDK_SOURCE_MOUSE,
                                    "has-cursor"TRUE,
                                    "display", seat->display,
                                    "seat", seat,
                                    NULL);
      _gdk_device_set_associated_device (seat->pointer, seat->logical_pointer);
      gdk_seat_device_added (GDK_SEAT (seat), seat->pointer);

      if (display_wayland->pointer_gestures)
        {
          seat->wp_pointer_gesture_swipe =
            zwp_pointer_gestures_v1_get_swipe_gesture (display_wayland->pointer_gestures,
                                                       seat->wl_pointer);
          zwp_pointer_gesture_swipe_v1_set_user_data (seat->wp_pointer_gesture_swipe,
                                                      seat);
          zwp_pointer_gesture_swipe_v1_add_listener (seat->wp_pointer_gesture_swipe,
                                                     &gesture_swipe_listener, seat);

          seat->wp_pointer_gesture_pinch =
            zwp_pointer_gestures_v1_get_pinch_gesture (display_wayland->pointer_gestures,
                                                       seat->wl_pointer);
          zwp_pointer_gesture_pinch_v1_set_user_data (seat->wp_pointer_gesture_pinch,
                                                      seat);
          zwp_pointer_gesture_pinch_v1_add_listener (seat->wp_pointer_gesture_pinch,
                                                     &gesture_pinch_listener, seat);

          if (zwp_pointer_gestures_v1_get_version (display_wayland->pointer_gestures) >= ZWP_POINTER_GESTURES_V1_GET_HOLD_GESTURE_SINCE_VERSION)
            {
              seat->wp_pointer_gesture_hold =
                  zwp_pointer_gestures_v1_get_hold_gesture (display_wayland->pointer_gestures,
                                                            seat->wl_pointer);
              zwp_pointer_gesture_hold_v1_set_user_data (seat->wp_pointer_gesture_hold,
                                                         seat);
              zwp_pointer_gesture_hold_v1_add_listener (seat->wp_pointer_gesture_hold,
                                                        &gesture_hold_listener, seat);
            }
        }
      if (display_wayland->cursor_shape)
        {
          seat->pointer_info.shape_device =
              wp_cursor_shape_manager_v1_get_pointer (display_wayland->cursor_shape, seat->wl_pointer);
        }
    }
  else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer)
    {
      g_clear_pointer (&seat->wp_pointer_gesture_swipe,
                       zwp_pointer_gesture_swipe_v1_destroy);
      g_clear_pointer (&seat->wp_pointer_gesture_pinch,
                       zwp_pointer_gesture_pinch_v1_destroy);

      wl_pointer_release (seat->wl_pointer);
      seat->wl_pointer = NULL;
      gdk_seat_device_removed (GDK_SEAT (seat), seat->pointer);
      _gdk_device_set_associated_device (seat->pointer, NULL);

      g_clear_object (&seat->pointer);

      if (seat->wheel_scrolling)
        {
          gdk_seat_device_removed (GDK_SEAT (seat), seat->wheel_scrolling);
          _gdk_device_set_associated_device (seat->wheel_scrolling, NULL);

          g_clear_object (&seat->wheel_scrolling);
        }

      if (seat->finger_scrolling)
        {
          gdk_seat_device_removed (GDK_SEAT (seat), seat->finger_scrolling);
          _gdk_device_set_associated_device (seat->finger_scrolling, NULL);

          g_clear_object (&seat->finger_scrolling);
        }

      if (seat->continuous_scrolling)
        {
          gdk_seat_device_removed (GDK_SEAT (seat), seat->continuous_scrolling);
          _gdk_device_set_associated_device (seat->continuous_scrolling, NULL);

          g_clear_object (&seat->continuous_scrolling);
        }

      g_clear_pointer (&seat->pointer_info.shape_device, wp_cursor_shape_device_v1_destroy);
    }

  if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !seat->wl_keyboard)
    {
      seat->wl_keyboard = wl_seat_get_keyboard (wl_seat);
      wl_keyboard_set_user_data (seat->wl_keyboard, seat);
      wl_keyboard_add_listener (seat->wl_keyboard, &keyboard_listener, seat);

      seat->keyboard = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                     "name""Wayland Keyboard",
                                     "source", GDK_SOURCE_KEYBOARD,
                                     "has-cursor"FALSE,
                                     "display", seat->display,
                                     "seat", seat,
                                     NULL);
      _gdk_device_reset_axes (seat->keyboard);
      _gdk_device_set_associated_device (seat->keyboard, seat->logical_keyboard);
      gdk_seat_device_added (GDK_SEAT (seat), seat->keyboard);
    }
  else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && seat->wl_keyboard)
    {
      wl_keyboard_release (seat->wl_keyboard);
      seat->wl_keyboard = NULL;
      gdk_seat_device_removed (GDK_SEAT (seat), seat->keyboard);
      _gdk_device_set_associated_device (seat->keyboard, NULL);

      g_clear_object (&seat->keyboard);
    }

  if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !seat->wl_touch)
    {
      seat->wl_touch = wl_seat_get_touch (wl_seat);
      wl_touch_set_user_data (seat->wl_touch, seat);
      wl_touch_add_listener (seat->wl_touch, &touch_listener, seat);

      seat->logical_touch = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                         "name""Wayland Touch Logical Pointer",
                                         "source", GDK_SOURCE_TOUCHSCREEN,
                                         "has-cursor"TRUE,
                                         "display", seat->display,
                                         "seat", seat,
                                         NULL);

      gdk_wayland_device_set_pointer (GDK_WAYLAND_DEVICE (seat->logical_touch),
                                      &seat->touch_info);
      _gdk_device_set_associated_device (seat->logical_touch, seat->logical_keyboard);
      gdk_seat_device_added (GDK_SEAT (seat), seat->logical_touch);

      seat->touch = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                  "name""Wayland Touch",
                                  "source", GDK_SOURCE_TOUCHSCREEN,
                                  "has-cursor"FALSE,
                                  "display", seat->display,
                                  "seat", seat,
                                  NULL);
      _gdk_device_set_associated_device (seat->touch, seat->logical_touch);
      gdk_seat_device_added (GDK_SEAT (seat), seat->touch);
    }
  else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch)
    {
      wl_touch_release (seat->wl_touch);
      seat->wl_touch = NULL;
      gdk_seat_device_removed (GDK_SEAT (seat), seat->touch);
      gdk_seat_device_removed (GDK_SEAT (seat), seat->logical_touch);
      _gdk_device_set_associated_device (seat->logical_touch, NULL);
      _gdk_device_set_associated_device (seat->touch, NULL);

      g_clear_object (&seat->logical_touch);
      g_clear_object (&seat->touch);
    }
}

static GdkDevice *
get_scroll_device (GdkWaylandSeat              *seat,
                   enum wl_pointer_axis_source  source)
{
  if (!seat->pointer)
    return NULL;

  switch (source)
    {
    case WL_POINTER_AXIS_SOURCE_WHEEL:
      if (seat->wheel_scrolling == NULL)
        {
          seat->wheel_scrolling = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                                "name""Wayland Wheel Scrolling",
                                                "source", GDK_SOURCE_MOUSE,
                                                "has-cursor"TRUE,
                                                "display", seat->display,
                                                "seat", seat,
                                                NULL);
         gdk_seat_device_added (GDK_SEAT (seat), seat->wheel_scrolling);
        }
      return seat->wheel_scrolling;

    case WL_POINTER_AXIS_SOURCE_FINGER:
      if (seat->finger_scrolling == NULL)
        {
          seat->finger_scrolling = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                                 "name""Wayland Finger Scrolling",
                                                 "source", GDK_SOURCE_TOUCHPAD,
                                                 "has-cursor"TRUE,
                                                 "display", seat->display,
                                                 "seat", seat,
                                                 NULL);
         gdk_seat_device_added (GDK_SEAT (seat), seat->finger_scrolling);
        }
      return seat->finger_scrolling;

    case WL_POINTER_AXIS_SOURCE_CONTINUOUS:
      if (seat->continuous_scrolling == NULL)
        {
          seat->continuous_scrolling = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                                     "name""Wayland Continuous Scrolling",
                                                     "source", GDK_SOURCE_TRACKPOINT,
                                                     "has-cursor"TRUE,
                                                     "display", seat->display,
                                                     "seat", seat,
                                                     NULL);
         gdk_seat_device_added (GDK_SEAT (seat), seat->continuous_scrolling);
        }
      return seat->continuous_scrolling;

    case WL_POINTER_AXIS_SOURCE_WHEEL_TILT:
    default:
      return seat->pointer;
    }
}

static void
seat_handle_name (void           *data,
                  struct wl_seat *seat,
                  const char     *name)
{
  /* We don't care about the name. */
  GDK_SEAT_DEBUG (GDK_WAYLAND_SEAT (data), MISC,
                  "seat %p name %s", seat, name);
}

static const struct wl_seat_listener seat_listener = {
  seat_handle_capabilities,
  seat_handle_name,
};

/* }}} */
/* {{{ Tablet tool listener */

static void
tablet_tool_handle_type (void                      *data,
                         struct zwp_tablet_tool_v2 *wp_tablet_tool,
                         uint32_t                   tool_type)
{
  GdkWaylandTabletToolData *tool = data;

  switch (tool_type)
    {
    case ZWP_TABLET_TOOL_V2_TYPE_PEN:
      tool->type = GDK_DEVICE_TOOL_TYPE_PEN;
      break;
    case ZWP_TABLET_TOOL_V2_TYPE_BRUSH:
      tool->type = GDK_DEVICE_TOOL_TYPE_BRUSH;
      break;
    case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH:
      tool->type = GDK_DEVICE_TOOL_TYPE_AIRBRUSH;
      break;
    case ZWP_TABLET_TOOL_V2_TYPE_PENCIL:
      tool->type = GDK_DEVICE_TOOL_TYPE_PENCIL;
      break;
    case ZWP_TABLET_TOOL_V2_TYPE_ERASER:
      tool->type = GDK_DEVICE_TOOL_TYPE_ERASER;
      break;
    case ZWP_TABLET_TOOL_V2_TYPE_MOUSE:
      tool->type = GDK_DEVICE_TOOL_TYPE_MOUSE;
      break;
    case ZWP_TABLET_TOOL_V2_TYPE_LENS:
      tool->type = GDK_DEVICE_TOOL_TYPE_LENS;
      break;
    default:
      tool->type = GDK_DEVICE_TOOL_TYPE_UNKNOWN;
      break;
    };
}

static void
tablet_tool_handle_hardware_serial (void                      *data,
                                    struct zwp_tablet_tool_v2 *wp_tablet_tool,
                                    uint32_t                   serial_hi,
                                    uint32_t                   serial_lo)
{
  GdkWaylandTabletToolData *tool = data;

  tool->hardware_serial = ((uint64_t) serial_hi) << 32 | serial_lo;
}

static void
tablet_tool_handle_hardware_id_wacom (void                      *data,
                                      struct zwp_tablet_tool_v2 *wp_tablet_tool,
                                      uint32_t                   id_hi,
                                      uint32_t                   id_lo)
{
  GdkWaylandTabletToolData *tool = data;

  tool->hardware_id_wacom = ((uint64_t) id_hi) << 32 | id_lo;
}

static void
tablet_tool_handle_capability (void                      *data,
                               struct zwp_tablet_tool_v2 *wp_tablet_tool,
                               uint32_t                   capability)
{
  GdkWaylandTabletToolData *tool = data;

  switch (capability)
    {
    case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT:
      tool->axes |= GDK_AXIS_FLAG_XTILT | GDK_AXIS_FLAG_YTILT;
      break;
    case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE:
      tool->axes |= GDK_AXIS_FLAG_PRESSURE;
      break;
    case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE:
      tool->axes |= GDK_AXIS_FLAG_DISTANCE;
      break;
    case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION:
      tool->axes |= GDK_AXIS_FLAG_ROTATION;
      break;
    case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER:
      tool->axes |= GDK_AXIS_FLAG_SLIDER;
      break;
    default:
      break;
    }
}

static void
tablet_tool_handle_done (void                      *data,
                         struct zwp_tablet_tool_v2 *wp_tablet_tool)
{
  GdkWaylandTabletToolData *tool = data;

  tool->tool = gdk_device_tool_new (tool->hardware_serial,
                                    tool->hardware_id_wacom,
                                    tool->type, tool->axes);
  gdk_seat_tool_added (tool->seat, tool->tool);
}

static void
tablet_tool_handle_removed (void                      *data,
                            struct zwp_tablet_tool_v2 *wp_tablet_tool)
{
  GdkWaylandTabletToolData *tool = data;

  _gdk_wayland_seat_remove_tool (GDK_WAYLAND_SEAT (tool->seat), tool);
}

static double *
tablet_copy_axes (GdkWaylandTabletData *tablet)
{
  return g_memdup2 (tablet->axes, sizeof (double) * GDK_AXIS_LAST);
}

static void
gdk_wayland_tablet_flush_frame_events (GdkWaylandTabletData *tablet,
                                       uint32_t              time)
{
  GList *events, *l;

  events = g_list_reverse (tablet->events);
  tablet->events = NULL;

  for (l = events; l; l = l->next)
    {
      TabletEvent *tablet_event = l->data;
      GdkEvent *event = NULL;

      if (!tablet->current_tool)
        continue;

      if (tablet_event->event_type == GDK_PROXIMITY_OUT)
        emulate_crossing (tablet->pointer_info.focus, NULL,
                          tablet->logical_device, GDK_LEAVE_NOTIFY,
                          GDK_CROSSING_NORMAL, time);

      switch (tablet_event->event_type)
        {
        case GDK_PROXIMITY_IN:
          event = gdk_proximity_event_new (GDK_PROXIMITY_IN,
                                           tablet->pointer_info.focus,
                                           tablet->logical_device,
                                           tablet->current_tool->tool,
                                           time);
          break;
        case GDK_PROXIMITY_OUT:
          event = gdk_proximity_event_new (GDK_PROXIMITY_OUT,
                                           tablet->pointer_info.focus,
                                           tablet->logical_device,
                                           tablet->current_tool->tool,
                                           time);
          break;
        case GDK_BUTTON_PRESS:
        case GDK_BUTTON_RELEASE:
          {
            uint32_t button_mod;

            event = gdk_button_event_new (tablet_event->event_type,
                                          tablet->pointer_info.focus,
                                          tablet->logical_device,
                                          tablet->current_tool->tool,
                                          time,
                                          gdk_wayland_device_get_modifiers (tablet->logical_device),
                                          tablet_event->button,
                                          tablet->pointer_info.surface_x,
                                          tablet->pointer_info.surface_y,
                                          tablet_copy_axes (tablet));

            button_mod =
              (GDK_BUTTON1_MASK << (tablet_event->button - 1)) &
              (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK);

            if (tablet_event->event_type == GDK_BUTTON_PRESS)
              tablet->pointer_info.button_modifiers |= button_mod;
            else
              tablet->pointer_info.button_modifiers &= ~(button_mod);

            break;
          }
        case GDK_MOTION_NOTIFY:
          event = gdk_motion_event_new (tablet->pointer_info.focus,
                                        tablet->logical_device,
                                        tablet->current_tool->tool,
                                        time,
                                        gdk_wayland_device_get_modifiers (tablet->logical_device),
                                        tablet->pointer_info.surface_x,
                                        tablet->pointer_info.surface_y,
                                        tablet_copy_axes (tablet));
          break;
        default:
          break;
        }

      if (event)
        {
          _gdk_wayland_display_deliver_event (gdk_seat_get_display (tablet->seat),
                                              event);
        }

      if (tablet_event->event_type == GDK_PROXIMITY_IN)
        emulate_crossing (tablet->pointer_info.focus, NULL,
                          tablet->logical_device, GDK_ENTER_NOTIFY,
                          GDK_CROSSING_NORMAL, time);
      else if (tablet_event->event_type == GDK_PROXIMITY_OUT)
        {
          g_clear_object (&tablet->pointer_info.focus);
          tablet->pointer_info.has_cursor_surface = FALSE;

          tablet->pointer_info.button_modifiers &=
            ~(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK |
              GDK_BUTTON4_MASK | GDK_BUTTON5_MASK);

          gdk_device_update_tool (tablet->stylus_device, NULL);
          g_clear_object (&tablet->pointer_info.cursor);
          tablet->pointer_info.cursor_is_default = FALSE;

          tablet->current_tool->current_tablet = NULL;
          tablet->current_tool = NULL;
        }
    }

  g_list_free_full (tablet->events, g_free);
}

static void
gdk_wayland_tablet_add_frame_event (GdkWaylandTabletData *tablet,
                                    GdkEventType          event_type,
                                    uint32_t              button)
{
  TabletEvent *tablet_event;

  tablet_event = g_new0 (TabletEvent, 1);
  *tablet_event = (TabletEvent) { event_type, button };
  tablet->events = g_list_prepend (tablet->events, tablet_event);
}

static void
gdk_wayland_device_tablet_clone_tool_axes (GdkWaylandTabletData *tablet,
                                           GdkDeviceTool        *tool)
{
  int axis_pos;

  g_object_freeze_notify (G_OBJECT (tablet->stylus_device));
  _gdk_device_reset_axes (tablet->stylus_device);

  _gdk_device_add_axis (tablet->stylus_device, GDK_AXIS_X, 000);
  _gdk_device_add_axis (tablet->stylus_device, GDK_AXIS_Y, 000);

  if (tool->tool_axes & (GDK_AXIS_FLAG_XTILT | GDK_AXIS_FLAG_YTILT))
    {
      axis_pos = _gdk_device_add_axis (tablet->stylus_device,
                                       GDK_AXIS_XTILT, -90900);
      tablet->axis_indices[GDK_AXIS_XTILT] = axis_pos;

      axis_pos = _gdk_device_add_axis (tablet->stylus_device,
                                       GDK_AXIS_YTILT, -90900);
      tablet->axis_indices[GDK_AXIS_YTILT] = axis_pos;
    }
  if (tool->tool_axes & GDK_AXIS_FLAG_DISTANCE)
    {
      axis_pos = _gdk_device_add_axis (tablet->stylus_device,
                                       GDK_AXIS_DISTANCE, 0655350);
      tablet->axis_indices[GDK_AXIS_DISTANCE] = axis_pos;
    }
  if (tool->tool_axes & GDK_AXIS_FLAG_PRESSURE)
    {
      axis_pos = _gdk_device_add_axis (tablet->stylus_device,
                                       GDK_AXIS_PRESSURE, 0655350);
      tablet->axis_indices[GDK_AXIS_PRESSURE] = axis_pos;
    }

  if (tool->tool_axes & GDK_AXIS_FLAG_ROTATION)
    {
      axis_pos = _gdk_device_add_axis (tablet->stylus_device,
                                       GDK_AXIS_ROTATION, 03600);
      tablet->axis_indices[GDK_AXIS_ROTATION] = axis_pos;
    }

  if (tool->tool_axes & GDK_AXIS_FLAG_SLIDER)
    {
      axis_pos = _gdk_device_add_axis (tablet->stylus_device,
                                       GDK_AXIS_SLIDER, -65535655350);
      tablet->axis_indices[GDK_AXIS_SLIDER] = axis_pos;
    }

  g_object_thaw_notify (G_OBJECT (tablet->stylus_device));
}

static void
gdk_wayland_mimic_device_axes (GdkDevice *logical,
                               GdkDevice *physical)
{
  double axis_min, axis_max, axis_resolution;
  GdkAxisUse axis_use;
  int axis_count;
  int i;

  g_object_freeze_notify (G_OBJECT (logical));
  _gdk_device_reset_axes (logical);
  axis_count = gdk_device_get_n_axes (physical);

  for (i = 0; i < axis_count; i++)
    {
      _gdk_device_get_axis_info (physical, i, &axis_use, &axis_min,
                                 &axis_max, &axis_resolution);
      _gdk_device_add_axis (logical, axis_use, axis_min,
                            axis_max, axis_resolution);
    }

  g_object_thaw_notify (G_OBJECT (logical));
}

static void
tablet_tool_handle_proximity_in (void                      *data,
                                 struct zwp_tablet_tool_v2 *wp_tablet_tool,
                                 uint32_t                   serial,
                                 struct zwp_tablet_v2      *wp_tablet,
                                 struct wl_surface         *wsurface)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = zwp_tablet_v2_get_user_data (wp_tablet);
  GdkSurface *surface;

  if (!wsurface)
    return;

  surface = wl_surface_get_user_data (wsurface);

  if (!surface)
      return;
  if (!GDK_IS_SURFACE (surface))
      return;

  tool->current_tablet = tablet;
  tablet->current_tool = tool;

  tablet->pointer_info.enter_serial = serial;

  tablet->pointer_info.focus = g_object_ref (surface);
  tablet->pointer_info.cursor_shape = 0;

  gdk_device_update_tool (tablet->stylus_device, tool->tool);
  gdk_wayland_device_tablet_clone_tool_axes (tablet, tool->tool);
  gdk_wayland_mimic_device_axes (tablet->logical_device, tablet->stylus_device);

  gdk_wayland_tablet_add_frame_event (tablet, GDK_PROXIMITY_IN, 0);

  GDK_SEAT_DEBUG (tablet->seat, EVENTS,
                  "proximity in, seat %p surface %p tool %d",
                  tablet->seat, tablet->pointer_info.focus,
                  gdk_device_tool_get_tool_type (tool->tool));
}

static void
tablet_tool_handle_proximity_out (void                      *data,
                                  struct zwp_tablet_tool_v2 *wp_tablet_tool)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;

  if (!tablet)
    return;

  GDK_SEAT_DEBUG (tool->seat, EVENTS,
                  "proximity out, seat %p, tool %d", tool->seat,
                  gdk_device_tool_get_tool_type (tool->tool));

  gdk_wayland_tablet_add_frame_event (tablet, GDK_PROXIMITY_OUT, 0);
}

static void
tablet_tool_handle_down (void                      *data,
                         struct zwp_tablet_tool_v2 *wp_tablet_tool,
                         uint32_t                   serial)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;

  if (!tablet || !tablet->pointer_info.focus)
    return;

  tablet->pointer_info.press_serial = serial;

  gdk_wayland_tablet_add_frame_event (tablet, GDK_BUTTON_PRESS, GDK_BUTTON_PRIMARY);
}

static void
tablet_tool_handle_up (void                      *data,
                       struct zwp_tablet_tool_v2 *wp_tablet_tool)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;

  if (!tablet || !tablet->pointer_info.focus)
    return;

  gdk_wayland_tablet_add_frame_event (tablet, GDK_BUTTON_RELEASE, GDK_BUTTON_PRIMARY);
}

static void
tablet_tool_handle_motion (void                      *data,
                           struct zwp_tablet_tool_v2 *wp_tablet_tool,
                           wl_fixed_t                 sx,
                           wl_fixed_t                 sy)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;

  if (!tablet)
    return;

  tablet->pointer_info.surface_x = wl_fixed_to_double (sx);
  tablet->pointer_info.surface_y = wl_fixed_to_double (sy);

  GDK_SEAT_DEBUG (tool->seat, EVENTS,
                  "tablet motion %f %f",
                  tablet->pointer_info.surface_x,
                  tablet->pointer_info.surface_y);

  gdk_wayland_tablet_add_frame_event (tablet, GDK_MOTION_NOTIFY, 0);
}

static void
tablet_tool_handle_pressure (void                      *data,
                             struct zwp_tablet_tool_v2 *wp_tablet_tool,
                             uint32_t                   pressure)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;
  int axis_index;

  if (!tablet)
    return;

  axis_index = tablet->axis_indices[GDK_AXIS_PRESSURE];

  _gdk_device_translate_axis (tablet->stylus_device, axis_index,
                              pressure, &tablet->axes[GDK_AXIS_PRESSURE]);

  GDK_SEAT_DEBUG (tool->seat, EVENTS,
                  "tablet tool %d pressure %d",
                  gdk_device_tool_get_tool_type (tool->tool), pressure);
}

static void
tablet_tool_handle_distance (void                      *data,
                             struct zwp_tablet_tool_v2 *wp_tablet_tool,
                             uint32_t                   distance)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;
  int axis_index;

  if (!tablet)
    return;

  axis_index = tablet->axis_indices[GDK_AXIS_DISTANCE];

  _gdk_device_translate_axis (tablet->stylus_device, axis_index,
                              distance, &tablet->axes[GDK_AXIS_DISTANCE]);

  GDK_SEAT_DEBUG (tool->seat, EVENTS,
                  "tablet tool %d distance %d",
                  gdk_device_tool_get_tool_type (tool->tool), distance);
}

static void
tablet_tool_handle_tilt (void                      *data,
                         struct zwp_tablet_tool_v2 *wp_tablet_tool,
                         wl_fixed_t                 xtilt,
                         wl_fixed_t                 ytilt)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;
  int xtilt_axis_index;
  int ytilt_axis_index;

  if (!tablet)
    return;

  xtilt_axis_index = tablet->axis_indices[GDK_AXIS_XTILT];
  ytilt_axis_index = tablet->axis_indices[GDK_AXIS_YTILT];

  _gdk_device_translate_axis (tablet->stylus_device, xtilt_axis_index,
                              wl_fixed_to_double (xtilt),
                              &tablet->axes[GDK_AXIS_XTILT]);
  _gdk_device_translate_axis (tablet->stylus_device, ytilt_axis_index,
                              wl_fixed_to_double (ytilt),
                              &tablet->axes[GDK_AXIS_YTILT]);

  GDK_SEAT_DEBUG (tool->seat, EVENTS,
                  "tablet tool %d tilt %f/%f",
                  gdk_device_tool_get_tool_type (tool->tool),
                  wl_fixed_to_double (xtilt), wl_fixed_to_double (ytilt));
}

static void
tablet_tool_handle_button (void                      *data,
                           struct zwp_tablet_tool_v2 *wp_tablet_tool,
                           uint32_t                   serial,
                           uint32_t                   button,
                           uint32_t                   state)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;
  GdkEventType evtype;
  uint32_t n_button;

  if (!tablet || !tablet->pointer_info.focus)
    return;

  tablet->pointer_info.press_serial = serial;

  if (button == BTN_STYLUS)
    n_button = GDK_BUTTON_MIDDLE;
  else if (button == BTN_STYLUS2)
    n_button = GDK_BUTTON_SECONDARY;
  else if (button == BTN_STYLUS3)
    n_button = 8/* Back */
  else if (button == BTN_BACK)
    n_button = 8/* Back */
  else if (button == BTN_FORWARD)
    n_button = 9/* Forward */
  else
    return;

  if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED)
    evtype = GDK_BUTTON_PRESS;
  else if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_RELEASED)
    evtype = GDK_BUTTON_RELEASE;
  else
    return;

  gdk_wayland_tablet_add_frame_event (tablet, evtype, n_button);
}

static void
tablet_tool_handle_rotation (void                      *data,
                             struct zwp_tablet_tool_v2 *wp_tablet_tool,
                             wl_fixed_t                 degrees)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;
  int axis_index;

  if (!tablet)
    return;

  axis_index = tablet->axis_indices[GDK_AXIS_ROTATION];

  _gdk_device_translate_axis (tablet->stylus_device, axis_index,
                              wl_fixed_to_double (degrees),
                              &tablet->axes[GDK_AXIS_ROTATION]);

  GDK_SEAT_DEBUG (tool->seat, EVENTS,
                  "tablet tool %d rotation %f",
                  gdk_device_tool_get_tool_type (tool->tool),
                  wl_fixed_to_double (degrees));
}

static void
tablet_tool_handle_slider (void                      *data,
                           struct zwp_tablet_tool_v2 *wp_tablet_tool,
                           int32_t                    position)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;
  int axis_index;

  if (!tablet)
    return;

  axis_index = tablet->axis_indices[GDK_AXIS_SLIDER];

  _gdk_device_translate_axis (tablet->stylus_device, axis_index,
                              position, &tablet->axes[GDK_AXIS_SLIDER]);

  GDK_SEAT_DEBUG (tool->seat, EVENTS,
                  "tablet tool %d slider %d",
                  gdk_device_tool_get_tool_type (tool->tool), position);
}

static void
tablet_tool_handle_wheel (void                      *data,
                          struct zwp_tablet_tool_v2 *wp_tablet_tool,
                          int32_t                    degrees,
                          int32_t                    clicks)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;
  GdkWaylandSeat *seat;
  GdkEvent *event;

  if (!tablet)
    return;

  seat = GDK_WAYLAND_SEAT (tablet->seat);

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "tablet tool %d wheel %d/%d",
                  gdk_device_tool_get_tool_type (tool->tool), degrees, clicks);

  if (clicks == 0)
    return;

  /* Send smooth event */
  event = gdk_scroll_event_new (tablet->pointer_info.focus,
                                tablet->logical_device,
                                tablet->current_tool->tool,
                                tablet->pointer_info.time,
                                gdk_wayland_device_get_modifiers (tablet->logical_device),
                                0, clicks,
                                FALSE,
                                GDK_SCROLL_UNIT_WHEEL,
                                GDK_SCROLL_RELATIVE_DIRECTION_UNKNOWN);

  _gdk_wayland_display_deliver_event (seat->display, event);
}

static void
tablet_tool_handle_frame (void                      *data,
                          struct zwp_tablet_tool_v2 *wl_tablet_tool,
                          uint32_t                   time)
{
  GdkWaylandTabletToolData *tool = data;
  GdkWaylandTabletData *tablet = tool->current_tablet;

  if (!tablet)
    return;

  GDK_SEAT_DEBUG (tablet->seat, EVENTS,
                  "tablet frame, time %d", time);

  tablet->pointer_info.time = time;
  gdk_wayland_tablet_flush_frame_events (tablet, time);
}

static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = {
  tablet_tool_handle_type,
  tablet_tool_handle_hardware_serial,
  tablet_tool_handle_hardware_id_wacom,
  tablet_tool_handle_capability,
  tablet_tool_handle_done,
  tablet_tool_handle_removed,
  tablet_tool_handle_proximity_in,
  tablet_tool_handle_proximity_out,
  tablet_tool_handle_down,
  tablet_tool_handle_up,
  tablet_tool_handle_motion,
  tablet_tool_handle_pressure,
  tablet_tool_handle_distance,
  tablet_tool_handle_tilt,
  tablet_tool_handle_rotation,
  tablet_tool_handle_slider,
  tablet_tool_handle_wheel,
  tablet_tool_handle_button,
  tablet_tool_handle_frame,
};

/* }}} */
/* {{{ Tablet pad ring listener */

static void
tablet_pad_ring_handle_source (void                          *data,
                               struct zwp_tablet_pad_ring_v2 *wp_tablet_pad_ring,
                               uint32_t                       source)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad ring handle source, ring = %p source = %d",
                  wp_tablet_pad_ring, source);

  group->axis_tmp_info.source = source;
}

static void
tablet_pad_ring_handle_angle (void                          *data,
                              struct zwp_tablet_pad_ring_v2 *wp_tablet_pad_ring,
                              wl_fixed_t                     angle)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad ring handle angle, ring = %p angle = %f",
                  wp_tablet_pad_ring, wl_fixed_to_double (angle));

  group->axis_tmp_info.value = wl_fixed_to_double (angle);
}

static void
tablet_pad_ring_handle_stop (void                          *data,
                             struct zwp_tablet_pad_ring_v2 *wp_tablet_pad_ring)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad ring handle stop, ring = %p", wp_tablet_pad_ring);

  group->axis_tmp_info.is_stop = TRUE;
}

static void
tablet_pad_ring_handle_frame (void                          *data,
                              struct zwp_tablet_pad_ring_v2 *wp_tablet_pad_ring,
                              uint32_t                       time)
{
  GdkWaylandTabletPadGroupData *group = data;
  GdkWaylandTabletPadData *pad = group->pad;
  GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (pad->seat);
  GdkEvent *event;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "tablet pad ring handle frame, ring = %p", wp_tablet_pad_ring);

  event = gdk_pad_event_new_ring (seat->keyboard_focus,
                                  pad->device,
                                  time,
                                  g_list_index (pad->mode_groups, group),
                                  g_list_index (pad->rings, wp_tablet_pad_ring),
                                  group->current_mode,
                                  group->axis_tmp_info.value);

  _gdk_wayland_display_deliver_event (gdk_seat_get_display (pad->seat),
                                      event);
}

static const struct zwp_tablet_pad_ring_v2_listener tablet_pad_ring_listener = {
  tablet_pad_ring_handle_source,
  tablet_pad_ring_handle_angle,
  tablet_pad_ring_handle_stop,
  tablet_pad_ring_handle_frame,
};

/* }}} */
/* {{{ Tablet pad strip listener */

static void
tablet_pad_strip_handle_source (void                           *data,
                                struct zwp_tablet_pad_strip_v2 *wp_tablet_pad_strip,
                                uint32_t                        source)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad strip handle source, strip = %p source = %d",
                  wp_tablet_pad_strip, source);

  group->axis_tmp_info.source = source;
}

static void
tablet_pad_strip_handle_position (void                           *data,
                                  struct zwp_tablet_pad_strip_v2 *wp_tablet_pad_strip,
                                  uint32_t                        position)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad strip handle position, strip = %p position = %d",
                  wp_tablet_pad_strip, position);

  group->axis_tmp_info.value = (double) position / 65535;
}

static void
tablet_pad_strip_handle_stop (void                           *data,
                              struct zwp_tablet_pad_strip_v2 *wp_tablet_pad_strip)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad strip handle stop, strip = %p",
                  wp_tablet_pad_strip);

  group->axis_tmp_info.is_stop = TRUE;
}

static void
tablet_pad_strip_handle_frame (void                           *data,
                               struct zwp_tablet_pad_strip_v2 *wp_tablet_pad_strip,
                               uint32_t                        time)
{
  GdkWaylandTabletPadGroupData *group = data;
  GdkWaylandTabletPadData *pad = group->pad;
  GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (pad->seat);
  GdkEvent *event;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "tablet pad strip handle frame, strip = %p",
                  wp_tablet_pad_strip);

  event = gdk_pad_event_new_strip (seat->keyboard_focus,
                                   pad->device,
                                   time,
                                   g_list_index (pad->mode_groups, group),
                                   g_list_index (pad->strips, wp_tablet_pad_strip),
                                   group->current_mode,
                                   group->axis_tmp_info.value);

  _gdk_wayland_display_deliver_event (gdk_seat_get_display (pad->seat),
                                      event);
}

static const struct zwp_tablet_pad_strip_v2_listener tablet_pad_strip_listener = {
  tablet_pad_strip_handle_source,
  tablet_pad_strip_handle_position,
  tablet_pad_strip_handle_stop,
  tablet_pad_strip_handle_frame,
};

/* }}} */
/* {{{ Tablet pad group listener */

static void
tablet_pad_group_handle_buttons (void                           *data,
                                 struct zwp_tablet_pad_group_v2 *wp_tablet_pad_group,
                                 struct wl_array                *buttons)
{
  GdkWaylandTabletPadGroupData *group = data;
  uint32_t *p;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad group handle buttons, pad group = %p, n_buttons = %" G_GSIZE_FORMAT,
                  wp_tablet_pad_group, buttons->size);

  wl_array_for_each (p, buttons)
    {
      group->buttons = g_list_prepend (group->buttons, GUINT_TO_POINTER (*p));
    }

  group->buttons = g_list_reverse (group->buttons);
}

static void
tablet_pad_group_handle_ring (void                           *data,
                              struct zwp_tablet_pad_group_v2 *wp_tablet_pad_group,
                              struct zwp_tablet_pad_ring_v2  *wp_tablet_pad_ring)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad group handle ring, pad group = %p, ring = %p",
                  wp_tablet_pad_group, wp_tablet_pad_ring);

  zwp_tablet_pad_ring_v2_add_listener (wp_tablet_pad_ring,
                                       &tablet_pad_ring_listener, group);
  zwp_tablet_pad_ring_v2_set_user_data (wp_tablet_pad_ring, group);

  group->rings = g_list_append (group->rings, wp_tablet_pad_ring);
  group->pad->rings = g_list_append (group->pad->rings, wp_tablet_pad_ring);
}

static void
tablet_pad_group_handle_strip (void                           *data,
                               struct zwp_tablet_pad_group_v2 *wp_tablet_pad_group,
                               struct zwp_tablet_pad_strip_v2 *wp_tablet_pad_strip)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad group handle strip, pad group = %p, strip = %p",
                  wp_tablet_pad_group, wp_tablet_pad_strip);

  zwp_tablet_pad_strip_v2_add_listener (wp_tablet_pad_strip,
                                       &tablet_pad_strip_listener, group);
  zwp_tablet_pad_strip_v2_set_user_data (wp_tablet_pad_strip, group);

  group->strips = g_list_append (group->strips, wp_tablet_pad_strip);
  group->pad->strips = g_list_append (group->pad->strips, wp_tablet_pad_strip);
}

static void
tablet_pad_group_handle_modes (void                           *data,
                               struct zwp_tablet_pad_group_v2 *wp_tablet_pad_group,
                               uint32_t                        modes)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad group handle modes, pad group = %p, n_modes = %d",
                  wp_tablet_pad_group, modes);

  group->n_modes = modes;
}

static void
tablet_pad_group_handle_done (void                           *data,
                              struct zwp_tablet_pad_group_v2 *wp_tablet_pad_group)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad group handle done, pad group = %p",
                  wp_tablet_pad_group);
}

static void
tablet_pad_group_handle_mode (void                           *data,
                              struct zwp_tablet_pad_group_v2 *wp_tablet_pad_group,
                              uint32_t                        time,
                              uint32_t                        serial,
                              uint32_t                        mode)
{
  GdkWaylandTabletPadGroupData *group = data;
  GdkWaylandTabletPadData *pad = group->pad;
  GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (pad->seat);
  GdkEvent *event;
  uint32_t n_group;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "tablet pad group handle mode, pad group = %p, mode = %d",
                  wp_tablet_pad_group, mode);

  group->mode_switch_serial = serial;
  group->current_mode = mode;
  n_group = g_list_index (pad->mode_groups, group);

  event = gdk_pad_event_new_group_mode (seat->keyboard_focus,
                                        pad->device,
                                        time,
                                        n_group,
                                        mode);

  _gdk_wayland_display_deliver_event (gdk_seat_get_display (pad->seat),
                                      event);
}

static void
tablet_pad_dial_handle_delta (void                          *data,
                              struct zwp_tablet_pad_dial_v2 *wp_tablet_pad_dial,
                              int32_t                        v120)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad dial handle delta, dial = %p delta = %d",
                  wp_tablet_pad_dial, v120);

  group->axis_tmp_info.value = v120;
}

static void
tablet_pad_dial_handle_frame (void                          *data,
                              struct zwp_tablet_pad_dial_v2 *wp_tablet_pad_dial,
                              uint32_t                       time)
{
  GdkWaylandTabletPadGroupData *group = data;
  GdkWaylandTabletPadData *pad = group->pad;
  GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (pad->seat);
  GdkEvent *event;

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "tablet pad dial handle frame, dial = %p", wp_tablet_pad_dial);

  event = gdk_pad_event_new_dial (seat->keyboard_focus,
                                  pad->device,
                                  time,
                                  g_list_index (pad->mode_groups, group),
                                  g_list_index (pad->dials, wp_tablet_pad_dial),
                                  group->current_mode,
                                  group->axis_tmp_info.value);

  _gdk_wayland_display_deliver_event (gdk_seat_get_display (pad->seat),
                                      event);
}

static const struct zwp_tablet_pad_dial_v2_listener tablet_pad_dial_listener = {
  tablet_pad_dial_handle_delta,
  tablet_pad_dial_handle_frame,
};

static void
tablet_pad_group_handle_dial (void                           *data,
                              struct zwp_tablet_pad_group_v2 *wp_tablet_pad_group,
                              struct zwp_tablet_pad_dial_v2  *wp_tablet_pad_dial)
{
  GdkWaylandTabletPadGroupData *group = data;

  GDK_SEAT_DEBUG (group->pad->seat, EVENTS,
                  "tablet pad group handle dial, pad group = %p, dial = %p",
                  wp_tablet_pad_group, wp_tablet_pad_dial);

  zwp_tablet_pad_dial_v2_add_listener (wp_tablet_pad_dial,
                                       &tablet_pad_dial_listener, group);
  zwp_tablet_pad_dial_v2_set_user_data (wp_tablet_pad_dial, group);

  group->dials = g_list_append (group->dials, wp_tablet_pad_dial);
  group->pad->dials = g_list_append (group->pad->dials, wp_tablet_pad_dial);
}

static const struct zwp_tablet_pad_group_v2_listener tablet_pad_group_listener = {
  tablet_pad_group_handle_buttons,
  tablet_pad_group_handle_ring,
  tablet_pad_group_handle_strip,
  tablet_pad_group_handle_modes,
  tablet_pad_group_handle_done,
  tablet_pad_group_handle_mode,
  tablet_pad_group_handle_dial,
};

/* }}} */
/* {{{ Tablet pad listener */

static void
tablet_pad_handle_group (void                           *data,
                         struct zwp_tablet_pad_v2       *wp_tablet_pad,
                         struct zwp_tablet_pad_group_v2 *wp_tablet_pad_group)
{
  GdkWaylandTabletPadData *pad = data;
  GdkWaylandTabletPadGroupData *group;

  GDK_SEAT_DEBUG (pad->seat, EVENTS,
                  "tablet pad handle group, pad group = %p, group = %p",
                  wp_tablet_pad_group, wp_tablet_pad_group);

  group = g_new0 (GdkWaylandTabletPadGroupData, 1);
  group->wp_tablet_pad_group = wp_tablet_pad_group;
  group->pad = pad;

  zwp_tablet_pad_group_v2_add_listener (wp_tablet_pad_group,
                                        &tablet_pad_group_listener, group);
  zwp_tablet_pad_group_v2_set_user_data (wp_tablet_pad_group, group);
  pad->mode_groups = g_list_append (pad->mode_groups, group);
}

static void
tablet_pad_handle_path (void                     *data,
                        struct zwp_tablet_pad_v2 *wp_tablet_pad,
                        const char               *path)
{
  GdkWaylandTabletPadData *pad = data;

  GDK_SEAT_DEBUG (pad->seat, EVENTS,
                  "tablet pad handle path, pad = %p, path = %s",
                  wp_tablet_pad, path);

  pad->path = g_strdup (path);
}

static void
tablet_pad_handle_buttons (void                     *data,
                           struct zwp_tablet_pad_v2 *wp_tablet_pad,
                           uint32_t                  buttons)
{
  GdkWaylandTabletPadData *pad = data;

  GDK_SEAT_DEBUG (pad->seat, EVENTS,
                  "tablet pad handle buttons, pad = %p, n_buttons = %d",
                  wp_tablet_pad, buttons);

  pad->n_buttons = buttons;
}

static void
tablet_pad_handle_done (void                     *data,
                        struct zwp_tablet_pad_v2 *wp_tablet_pad)
{
  G_GNUC_UNUSED GdkWaylandTabletPadData *pad = data;

  GDK_SEAT_DEBUG (pad->seat, EVENTS,
                  "tablet pad handle done, pad = %p", wp_tablet_pad);
}

static void
tablet_pad_handle_button (void                     *data,
                          struct zwp_tablet_pad_v2 *wp_tablet_pad,
                          uint32_t                  time,
                          uint32_t                  button,
                          uint32_t                  state)
{
  GdkWaylandTabletPadData *pad = data;
  GdkWaylandTabletPadGroupData *group;
  GdkEvent *event;
  int n_group;

  GDK_SEAT_DEBUG (pad->seat, EVENTS,
                  "tablet pad handle button, pad = %p, button = %d, state = %d",
                  wp_tablet_pad, button, state);

  group = tablet_pad_lookup_button_group (pad, button);

#ifdef G_DISABLE_ASSERT
  if (group == NULL)
    return;
#else
  g_assert (group != NULL);
#endif

  n_group = g_list_index (pad->mode_groups, group);

  event = gdk_pad_event_new_button (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED
                                       ? GDK_PAD_BUTTON_PRESS
                                       : GDK_PAD_BUTTON_RELEASE,
                                    GDK_WAYLAND_SEAT (pad->seat)->keyboard_focus,
                                    pad->device,
                                    time,
                                    n_group,
                                    button,
                                    group->current_mode);

  _gdk_wayland_display_deliver_event (gdk_seat_get_display (pad->seat), event);
}

static void
tablet_pad_handle_enter (void                     *data,
                         struct zwp_tablet_pad_v2 *wp_tablet_pad,
                         uint32_t                  serial,
                         struct zwp_tablet_v2     *wp_tablet,
                         struct wl_surface        *surface)
{
  GdkWaylandTabletPadData *pad = data;
  GdkWaylandTabletData *tablet = zwp_tablet_v2_get_user_data (wp_tablet);

  GDK_SEAT_DEBUG (pad->seat, EVENTS,
                  "tablet pad handle enter, pad = %p, tablet = %p surface = %p",
                  wp_tablet_pad, wp_tablet, surface);

  if (pad->device && pad->current_tablet != tablet)
    {
      gdk_seat_device_removed (GDK_SEAT (pad->seat), pad->device);
      _gdk_device_set_associated_device (pad->device, NULL);
      g_clear_object (&pad->device);
    }

  /* Relate pad and tablet */
  tablet->pads = g_list_prepend (tablet->pads, pad);
  pad->current_tablet = tablet;

  if (!pad->device)
    {
      gchar *name, *vid, *pid;

      name = g_strdup_printf ("%s Pad %d",
                              tablet->name,
                              g_list_index (tablet->pads, pad) + 1);
      vid = g_strdup_printf ("%.4x", tablet->vid);
      pid = g_strdup_printf ("%.4x", tablet->pid);

      pad->device =
        g_object_new (GDK_TYPE_WAYLAND_DEVICE_PAD,
                      "name", name,
                      "vendor-id", vid,
                      "product-id", pid,
                      "source", GDK_SOURCE_TABLET_PAD,
                      "display", gdk_seat_get_display (pad->seat),
                      "seat", pad->seat,
                      NULL);

      _gdk_device_set_associated_device (pad->device, GDK_WAYLAND_SEAT (pad->seat)->logical_keyboard);
      gdk_seat_device_added (GDK_SEAT (pad->seat), pad->device);

      g_free (name);
      g_free (vid);
      g_free (pid);
    }
}

static void
tablet_pad_handle_leave (void                     *data,
                         struct zwp_tablet_pad_v2 *wp_tablet_pad,
                         uint32_t                  serial,
                         struct wl_surface        *surface)
{
  GdkWaylandTabletPadData *pad = data;

  GDK_SEAT_DEBUG (pad->seat, EVENTS,
                  "tablet pad handle leave, pad = %p, surface = %p",
                  wp_tablet_pad, surface);

  if (pad->current_tablet)
    {
      pad->current_tablet->pads = g_list_remove (pad->current_tablet->pads, pad);
      pad->current_tablet = NULL;
    }
}

static void
tablet_pad_handle_removed (void                     *data,
                           struct zwp_tablet_pad_v2 *wp_tablet_pad)
{
  GdkWaylandTabletPadData *pad = data;

  GDK_SEAT_DEBUG (pad->seat, EVENTS,
                  "tablet pad handle removed, pad = %p", wp_tablet_pad);

  /* Remove from the current tablet */
  if (pad->current_tablet)
    {
      pad->current_tablet->pads = g_list_remove (pad->current_tablet->pads, pad);
      pad->current_tablet = NULL;
    }

  _gdk_wayland_seat_remove_tablet_pad (GDK_WAYLAND_SEAT (pad->seat), pad);
}

static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = {
  tablet_pad_handle_group,
  tablet_pad_handle_path,
  tablet_pad_handle_buttons,
  tablet_pad_handle_done,
  tablet_pad_handle_button,
  tablet_pad_handle_enter,
  tablet_pad_handle_leave,
  tablet_pad_handle_removed,
};

/* }}} */
/* {{{ Tablet seat listener */

static void
tablet_seat_handle_tablet_added (void                      *data,
                                 struct zwp_tablet_seat_v2 *wp_tablet_seat,
                                 struct zwp_tablet_v2      *wp_tablet)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandTabletData *tablet;

  tablet = g_new0 (GdkWaylandTabletData, 1);
  tablet->seat = GDK_SEAT (seat);

  tablet->wp_tablet = wp_tablet;

  seat->tablets = g_list_prepend (seat->tablets, tablet);

  zwp_tablet_v2_add_listener (wp_tablet, &tablet_listener, tablet);
  zwp_tablet_v2_set_user_data (wp_tablet, tablet);
}

static void
tablet_seat_handle_tool_added (void                      *data,
                               struct zwp_tablet_seat_v2 *wp_tablet_seat,
                               struct zwp_tablet_tool_v2 *wp_tablet_tool)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandTabletToolData *tool;
  GdkWaylandDisplay *display = GDK_WAYLAND_DISPLAY (seat->display);

  tool = g_new0 (GdkWaylandTabletToolData, 1);
  tool->wp_tablet_tool = wp_tablet_tool;
  tool->seat = GDK_SEAT (seat);

  zwp_tablet_tool_v2_add_listener (wp_tablet_tool, &tablet_tool_listener, tool);
  zwp_tablet_tool_v2_set_user_data (wp_tablet_tool, tool);

  seat->tablet_tools = g_list_prepend (seat->tablet_tools, tool);

  if (display->cursor_shape)
    {
      tool->shape_device =
          wp_cursor_shape_manager_v1_get_tablet_tool_v2 (
              display->cursor_shape, tool->wp_tablet_tool);
    }
}

static void
tablet_seat_handle_pad_added (void                      *data,
                              struct zwp_tablet_seat_v2 *wp_tablet_seat,
                              struct zwp_tablet_pad_v2  *wp_tablet_pad)
{
  GdkWaylandSeat *seat = data;
  GdkWaylandTabletPadData *pad;

  pad = g_new0 (GdkWaylandTabletPadData, 1);
  pad->wp_tablet_pad = wp_tablet_pad;
  pad->seat = GDK_SEAT (seat);

  zwp_tablet_pad_v2_add_listener (wp_tablet_pad, &tablet_pad_listener, pad);
  zwp_tablet_pad_v2_set_user_data (wp_tablet_pad, pad);

  seat->tablet_pads = g_list_prepend (seat->tablet_pads, pad);
}

static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
  tablet_seat_handle_tablet_added,
  tablet_seat_handle_tool_added,
  tablet_seat_handle_pad_added,
};

/* }}} */
/* {{{ Pointer surface listener */

static void
pointer_surface_enter (void              *data,
                       struct wl_surface *wl_surface,
                       struct wl_output  *output)

{
  GdkDevice *device = data;
  GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "pointer surface of seat %p entered output %p",
                  seat, output);
}

static void
pointer_surface_leave (void              *data,
                       struct wl_surface *wl_surface,
                       struct wl_output  *output)
{
  GdkDevice *device = data;
  GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));

  GDK_SEAT_DEBUG (seat, EVENTS,
                  "pointer surface of seat %p left output %p",
                  seat, output);
}

static void
pointer_surface_preferred_buffer_scale (void              *data,
                                        struct wl_surface *wl_surface,
                                        int32_t            factor)
{
  GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (data);
  GdkWaylandPointerData *pointer = gdk_wayland_device_get_pointer (wayland_device);

  if (pointer->fractional_scale != NULL)
    return;

  pointer->preferred_scale = GDK_FRACTIONAL_SCALE_INIT_INT (factor);
  gdk_wayland_device_update_surface_cursor (GDK_DEVICE (wayland_device));
}

static void
pointer_surface_preferred_buffer_transform (void              *data,
                                            struct wl_surface *wl_surface,
                                            uint32_t           transform)
{
}

static const struct wl_surface_listener pointer_surface_listener = {
  pointer_surface_enter,
  pointer_surface_leave,
  pointer_surface_preferred_buffer_scale,
  pointer_surface_preferred_buffer_transform,
};

/* }}} */
/* {{{ Pointer surface fractional scale listener */

static void
pointer_surface_fractional_scale_preferred_scale_cb (void *data,
                                                     struct wp_fractional_scale_v1 *fractional_scale,
                                                     uint32_t scale)
{
  GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (data);
  GdkWaylandPointerData *pointer = gdk_wayland_device_get_pointer (wayland_device);

  pointer->preferred_scale = GDK_FRACTIONAL_SCALE_INIT (scale);
  gdk_wayland_device_update_surface_cursor (GDK_DEVICE (wayland_device));
}

static const struct wp_fractional_scale_v1_listener fractional_scale_listener = {
  pointer_surface_fractional_scale_preferred_scale_cb,
};

/* }}} */
/* {{{ GObject boilerplate */

static void
init_devices (GdkWaylandSeat *seat)
{
  /* pointer */
  seat->logical_pointer = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                       "name""Core Pointer",
                                       "source", GDK_SOURCE_MOUSE,
                                       "has-cursor"TRUE,
                                       "display", seat->display,
                                       "seat", seat,
                                       NULL);

  gdk_wayland_device_set_pointer (GDK_WAYLAND_DEVICE (seat->logical_pointer),
                                  &seat->pointer_info);

  /* keyboard */
  seat->logical_keyboard = g_object_new (GDK_TYPE_WAYLAND_DEVICE,
                                        "name""Core Keyboard",
                                        "source", GDK_SOURCE_KEYBOARD,
                                        "has-cursor"FALSE,
                                        "display", seat->display,
                                        "seat", seat,
                                        NULL);
  _gdk_device_reset_axes (seat->logical_keyboard);

  /* link both */
  _gdk_device_set_associated_device (seat->logical_pointer, seat->logical_keyboard);
  _gdk_device_set_associated_device (seat->logical_keyboard, seat->logical_pointer);

  gdk_seat_device_added (GDK_SEAT (seat), seat->logical_pointer);
  gdk_seat_device_added (GDK_SEAT (seat), seat->logical_keyboard);
}

static void
init_pointer_data (GdkWaylandPointerData *pointer_data,
                   GdkDisplay            *display,
                   GdkDevice             *logical_device)
{
  GdkWaylandDisplay *display_wayland;

  display_wayland = GDK_WAYLAND_DISPLAY (display);

  pointer_data->pointer_surface =
    wl_compositor_create_surface (display_wayland->compositor);
  wl_surface_add_listener (pointer_data->pointer_surface,
                           &pointer_surface_listener,
                           logical_device);
  if (display_wayland->viewporter)
    pointer_data->pointer_surface_viewport = wp_viewporter_get_viewport (display_wayland->viewporter, pointer_data->pointer_surface);

  pointer_data->preferred_scale = GDK_FRACTIONAL_SCALE_INIT_INT (1);
  if (display_wayland->fractional_scale)
    {
      pointer_data->fractional_scale =
          wp_fractional_scale_manager_v1_get_fractional_scale (display_wayland->fractional_scale,
                                                               pointer_data->pointer_surface);
      wp_fractional_scale_v1_add_listener (pointer_data->fractional_scale,
                                           &fractional_scale_listener, logical_device);
    }
}

static void
gdk_wayland_pointer_data_finalize (GdkWaylandPointerData *pointer)
{
  g_clear_object (&pointer->focus);
  g_clear_object (&pointer->cursor);
  wl_surface_destroy (pointer->pointer_surface);
  g_clear_pointer (&pointer->pointer_surface_viewport, wp_viewport_destroy);
  g_clear_pointer (&pointer->fractional_scale, wp_fractional_scale_v1_destroy);
}

static void
gdk_wayland_seat_dispose (GObject *object)
{
  GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (object);

  g_clear_pointer (&seat->wl_seat, wl_seat_destroy);
  g_clear_pointer (&seat->wl_pointer, wl_pointer_destroy);
  g_clear_pointer (&seat->wl_keyboard, wl_keyboard_destroy);
  g_clear_pointer (&seat->wl_touch, wl_touch_destroy);
  g_clear_pointer (&seat->wp_pointer_gesture_swipe, zwp_pointer_gesture_swipe_v1_destroy);
  g_clear_pointer (&seat->wp_pointer_gesture_pinch, zwp_pointer_gesture_pinch_v1_destroy);
  g_clear_pointer (&seat->wp_pointer_gesture_hold, zwp_pointer_gesture_hold_v1_destroy);
  g_clear_pointer (&seat->wp_tablet_seat, zwp_tablet_seat_v2_destroy);

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

static void
gdk_wayland_seat_finalize (GObject *object)
{
  GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (object);
  GList *l;

  for (l = seat->tablet_tools; l != NULL; l = l->next)
    _gdk_wayland_seat_remove_tool (seat, l->data);

  for (l = seat->tablet_pads; l != NULL; l = l->next)
    _gdk_wayland_seat_remove_tablet_pad (seat, l->data);

  for (l = seat->tablets; l != NULL; l = l->next)
    _gdk_wayland_seat_remove_tablet (seat, l->data);

  seat_handle_capabilities (seat, seat->wl_seat, 0);
  g_object_unref (seat->keymap);
  gdk_wayland_pointer_data_finalize (&seat->pointer_info);
  /* FIXME: destroy data_device */
  g_clear_object (&seat->drag);
  g_clear_object (&seat->drop);
  g_clear_object (&seat->clipboard);
  g_clear_object (&seat->primary_clipboard);
  g_hash_table_destroy (seat->touches);
  zwp_tablet_seat_v2_destroy (seat->wp_tablet_seat);
  stop_key_repeat (seat);

  G_OBJECT_CLASS (gdk_wayland_seat_parent_class)->finalize (object);
}

static GdkSeatCapabilities
gdk_wayland_seat_get_capabilities (GdkSeat *seat)
{
  GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (seat);
  GdkSeatCapabilities caps = 0;

  if (wayland_seat->logical_pointer)
    caps |= GDK_SEAT_CAPABILITY_POINTER;
  if (wayland_seat->logical_keyboard)
    caps |= GDK_SEAT_CAPABILITY_KEYBOARD;
  if (wayland_seat->logical_touch)
    caps |= GDK_SEAT_CAPABILITY_TOUCH;

  return caps;
}

static GdkGrabStatus
gdk_wayland_seat_grab (GdkSeat    *seat,
                       GdkSurface *surface)
{
  GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (seat);
  uint32_t evtime = GDK_CURRENT_TIME;
  GList *l;

  if (surface == NULL || GDK_SURFACE_DESTROYED (surface) ||
      !gdk_wayland_surface_has_surface (surface))
    return GDK_GRAB_NOT_VIEWABLE;

  wayland_seat->grab_time = evtime;

  if (wayland_seat->logical_pointer)
    {
      gdk_wayland_device_maybe_emit_grab_crossing (wayland_seat->logical_pointer,
                                                   surface, evtime);
    }

  if (wayland_seat->logical_touch)
    {
      gdk_wayland_device_maybe_emit_grab_crossing (wayland_seat->logical_touch,
                                                   surface, evtime);
    }

  if (wayland_seat->logical_keyboard)
    {
      gdk_wayland_device_maybe_emit_grab_crossing (wayland_seat->logical_keyboard,
                                                   surface, evtime);
    }

  if (wayland_seat->tablets)
    {
      for (l = wayland_seat->tablets; l; l = l->next)
        {
          GdkWaylandTabletData *tablet = l->data;

          gdk_wayland_device_update_surface_cursor (tablet->logical_device);

          if (tablet->current_tool)
            {
              gdk_wayland_device_maybe_emit_grab_crossing (tablet->logical_device,
                                                           surface,
                                                           evtime);
            }
        }
    }

  if (GDK_IS_WAYLAND_POPUP (surface))
    _gdk_wayland_surface_set_grab_seat (surface, seat);

  return GDK_GRAB_SUCCESS;
}

static void
gdk_wayland_seat_ungrab (GdkSeat    *seat,
                         GdkSurface *surface)
{
  GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (seat);

  g_clear_object (&wayland_seat->grab_cursor);

  if (wayland_seat->logical_pointer)
    {
      gdk_wayland_device_maybe_emit_ungrab_crossing (wayland_seat->logical_pointer,
                                                     GDK_CURRENT_TIME);
      gdk_wayland_device_update_surface_cursor (wayland_seat->logical_pointer);
    }

  if (wayland_seat->logical_keyboard)
    {
      GdkSurface *prev_focus;

      prev_focus = gdk_wayland_device_maybe_emit_ungrab_crossing (wayland_seat->logical_keyboard,
                                                                  GDK_CURRENT_TIME);
      if (prev_focus)
        gdk_wayland_surface_restore_shortcuts (prev_focus, seat);
    }

  _gdk_wayland_surface_set_grab_seat (surface, NULL);
}

static GdkDevice *
gdk_wayland_seat_get_logical_device (GdkSeat             *seat,
                                     GdkSeatCapabilities  capabilities)
{
  GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (seat);

  if (capabilities == GDK_SEAT_CAPABILITY_POINTER)
    return wayland_seat->logical_pointer;
  else if (capabilities == GDK_SEAT_CAPABILITY_KEYBOARD)
    return wayland_seat->logical_keyboard;
  else if (capabilities == GDK_SEAT_CAPABILITY_TOUCH)
    return wayland_seat->logical_touch;

  return NULL;
}

static GList *
gdk_wayland_seat_get_devices (GdkSeat             *seat,
                              GdkSeatCapabilities  capabilities)
{
  GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (seat);
  GList *physical_devices = NULL;

  if (wayland_seat->finger_scrolling && (capabilities & GDK_SEAT_CAPABILITY_POINTER))
    physical_devices = g_list_prepend (physical_devices, wayland_seat->finger_scrolling);
  if (wayland_seat->continuous_scrolling && (capabilities & GDK_SEAT_CAPABILITY_POINTER))
    physical_devices = g_list_prepend (physical_devices, wayland_seat->continuous_scrolling);
  if (wayland_seat->wheel_scrolling && (capabilities & GDK_SEAT_CAPABILITY_POINTER))
    physical_devices = g_list_prepend (physical_devices, wayland_seat->wheel_scrolling);
  if (wayland_seat->pointer && (capabilities & GDK_SEAT_CAPABILITY_POINTER))
    physical_devices = g_list_prepend (physical_devices, wayland_seat->pointer);
  if (wayland_seat->keyboard && (capabilities & GDK_SEAT_CAPABILITY_KEYBOARD))
    physical_devices = g_list_prepend (physical_devices, wayland_seat->keyboard);
  if (wayland_seat->touch && (capabilities & GDK_SEAT_CAPABILITY_TOUCH))
    physical_devices = g_list_prepend (physical_devices, wayland_seat->touch);

  if (wayland_seat->tablets && (capabilities & GDK_SEAT_CAPABILITY_TABLET_STYLUS))
    {
      GList *l;

      for (l = wayland_seat->tablets; l; l = l->next)
        {
          GdkWaylandTabletData *tablet = l->data;

          physical_devices = g_list_prepend (physical_devices, tablet->stylus_device);
        }
    }

  if (wayland_seat->tablet_pads && (capabilities & GDK_SEAT_CAPABILITY_TABLET_PAD))
    {
      GList *l;

      for (l = wayland_seat->tablet_pads; l; l = l->next)
        {
          GdkWaylandTabletPadData *data = l->data;
          physical_devices = g_list_prepend (physical_devices, data->device);
        }
    }

  return physical_devices;
}

static GList *
gdk_wayland_seat_get_tools (GdkSeat *seat)
{
  GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (seat);
  GList *tools = NULL, *l;

  for (l = wayland_seat->tablet_tools; l; l = l->next)
    {
      GdkWaylandTabletToolData *tool = l->data;

      tools = g_list_prepend (tools, tool->tool);
    }

  return tools;
}

static void
gdk_wayland_seat_class_init (GdkWaylandSeatClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GdkSeatClass *seat_class = GDK_SEAT_CLASS (klass);

  object_class->finalize = gdk_wayland_seat_finalize;
  object_class->dispose = gdk_wayland_seat_dispose;

  seat_class->get_capabilities = gdk_wayland_seat_get_capabilities;
  seat_class->grab = gdk_wayland_seat_grab;
  seat_class->ungrab = gdk_wayland_seat_ungrab;
  seat_class->get_logical_device = gdk_wayland_seat_get_logical_device;
  seat_class->get_devices = gdk_wayland_seat_get_devices;
  seat_class->get_tools = gdk_wayland_seat_get_tools;
}

static void
gdk_wayland_seat_init (GdkWaylandSeat *seat)
{
}

/* }}} */
/* {{{ Private API */

void
gdk_wayland_display_create_seat (GdkWaylandDisplay *display_wayland,
                                 uint32_t           id,
                                 struct wl_seat    *wl_seat)
{
  GdkDisplay *display = GDK_DISPLAY (display_wayland);
  GdkWaylandSeat *seat;

  seat = g_object_new (GDK_TYPE_WAYLAND_SEAT,
                       "display", display_wayland,
                       NULL);
  seat->id = id;
  seat->keymap = _gdk_wayland_keymap_new (display);
  seat->display = display;
  seat->touches = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_free);
  seat->wl_seat = wl_seat;

  wl_seat_add_listener (seat->wl_seat, &seat_listener, seat);
  wl_seat_set_user_data (seat->wl_seat, seat);

  if (display_wayland->primary_selection_manager)
    {
      seat->primary_clipboard = gdk_wayland_primary_new (seat);
    }
  else
    {
      /* If the compositor doesn't support primary clipboard,
       * just do it local-only */

      seat->primary_clipboard = gdk_clipboard_new (display);
    }

  seat->data_device =
    wl_data_device_manager_get_data_device (display_wayland->data_device_manager,
                                            seat->wl_seat);
  seat->clipboard = gdk_wayland_clipboard_new (display);
  wl_data_device_add_listener (seat->data_device,
                               &data_device_listener, seat);

  init_devices (seat);
  init_pointer_data (&seat->pointer_info, display, seat->logical_pointer);

  if (display_wayland->tablet_manager)
    {
      seat->wp_tablet_seat =
        zwp_tablet_manager_v2_get_tablet_seat (display_wayland->tablet_manager,
                                               wl_seat);
      zwp_tablet_seat_v2_add_listener (seat->wp_tablet_seat, &tablet_seat_listener,
                                       seat);
    }

  if (display->clipboard == NULL)
    display->clipboard = g_object_ref (seat->clipboard);
  if (display->primary_clipboard == NULL)
    display->primary_clipboard = g_object_ref (seat->primary_clipboard);

  gdk_display_add_seat (display, GDK_SEAT (seat));
}

void
gdk_wayland_display_remove_seat (GdkWaylandDisplay *display_wayland,
                                 uint32_t           id)
{
  GdkDisplay *display = GDK_DISPLAY (display_wayland);
  GList *l, *seats;

  seats = gdk_display_list_seats (display);

  for (l = seats; l != NULL; l = l->next)
    {
      GdkWaylandSeat *seat = l->data;

      if (seat->id != id)
        continue;

      gdk_display_remove_seat (display, GDK_SEAT (seat));
      break;
    }

  g_list_free (seats);
}

uint32_t
_gdk_wayland_seat_get_implicit_grab_serial (GdkSeat          *seat,
                                            GdkDevice        *device,
                                            GdkEventSequence *sequence)
{
  GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (seat);
  GList *l;

  if (sequence)
    {
      GdkWaylandTouchData *touch = NULL;

      touch = gdk_wayland_seat_get_touch (GDK_WAYLAND_SEAT (seat),
                                          GDK_EVENT_SEQUENCE_TO_SLOT (sequence));
      if (touch)
        return touch->touch_down_serial;
    }
  else if (device == wayland_seat->logical_touch)
    {
      GdkWaylandTouchData *touch;
      GHashTableIter iter;

      /* Pick the first sequence */
      g_hash_table_iter_init (&iter, wayland_seat->touches);
      g_hash_table_iter_next (&iter, NULL, (gpointer *) &touch);
      return touch->touch_down_serial;
    }

  for (l = wayland_seat->tablets; l; l = l->next)
    {
      GdkWaylandTabletData *tablet = l->data;

      if (tablet->logical_device == device)
        return tablet->pointer_info.press_serial;
    }

  return GDK_WAYLAND_SEAT (seat)->pointer_info.press_serial;
}

uint32_t
_gdk_wayland_seat_get_last_implicit_grab_serial (GdkWaylandSeat    *seat,
                                                 GdkEventSequence **sequence)
{
  GdkWaylandTouchData *touch;
  GHashTableIter iter;
  GList *l;
  uint32_t serial;

  g_hash_table_iter_init (&iter, seat->touches);

  if (sequence)
    *sequence = NULL;

  serial = seat->keyboard_key_serial;

  if (seat->pointer_info.press_serial > serial)
    serial = seat->pointer_info.press_serial;

  for (l = seat->tablets; l; l = l->next)
    {
      GdkWaylandTabletData *tablet = l->data;

      if (tablet->pointer_info.press_serial > serial)
        serial = tablet->pointer_info.press_serial;
    }

  if (g_hash_table_size (seat->touches) > 0)
    {
      while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &touch))
        {
          if (touch->touch_down_serial > serial)
            {
              if (sequence)
                *sequence = GDK_SLOT_TO_EVENT_SEQUENCE (touch->id);
              serial = touch->touch_down_serial;

            }
        }
    }
  else
    {
      if (seat->latest_touch_down_serial > serial)
        serial = seat->latest_touch_down_serial;
    }

  return serial;
}

void
gdk_wayland_seat_set_global_cursor (GdkSeat   *seat,
                                    GdkCursor *cursor)
{
  GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (seat);
  GdkDevice *pointer;

  pointer = gdk_seat_get_pointer (seat);

  g_set_object (&wayland_seat->grab_cursor, cursor);
  GDK_DEVICE_GET_CLASS (pointer)->set_surface_cursor (pointer,
                                                      gdk_wayland_device_get_focus (pointer),
                                                      NULL);
}

void
gdk_wayland_seat_set_drag (GdkSeat *seat,
                           GdkDrag *drag)
{
  GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (seat);

  g_set_object (&wayland_seat->drag, drag);
}

/* }}} */
/* {{{ Public API */

/**
 * gdk_wayland_seat_get_wl_seat: (skip)
 * @seat: (type GdkWaylandSeat): a `GdkSeat`
 *
 * Returns the Wayland `wl_seat` of a `GdkSeat`.
 *
 * Returns: (transfer none): a Wayland `wl_seat`
 */

struct wl_seat *
gdk_wayland_seat_get_wl_seat (GdkSeat *seat)
{
  g_return_val_if_fail (GDK_IS_WAYLAND_SEAT (seat), NULL);

  return GDK_WAYLAND_SEAT (seat)->wl_seat;
}

/* }}} */
/* vim:set foldmethod=marker: */

Messung V0.5 in Prozent
C=98 H=96 G=96

¤ Dauer der Verarbeitung: 0.70 Sekunden  (vorverarbeitet am  2026-07-02) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik