[gtk/nacho/macos-stylus] Support stylus devices on macos




commit 169f48a3e13db7675029d508953694a7ca8e8707
Author: Ignacio Casal Quinteiro <qignacio amazon com>
Date:   Thu Jan 7 13:40:12 2021 +0100

    Support stylus devices on macos

 gdk/macos/gdkmacosdisplay-translate.c |  54 ++-
 gdk/macos/gdkmacosseat-private.h      |  12 +
 gdk/macos/gdkmacosseat.c              | 624 ++++++++++++++++++++++++++++++++--
 gdk/macos/gdkmacosseat.h              |  40 +++
 gdk/macos/meson.build                 |   1 +
 5 files changed, 704 insertions(+), 27 deletions(-)
---
diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c
index e6551581a2..ce0c045e11 100644
--- a/gdk/macos/gdkmacosdisplay-translate.c
+++ b/gdk/macos/gdkmacosdisplay-translate.c
@@ -28,6 +28,7 @@
 #include "gdkmacosdisplay-private.h"
 #include "gdkmacoskeymap-private.h"
 #include "gdkmacossurface-private.h"
+#include "gdkmacosseat-private.h"
 
 #define GDK_MOD2_MASK (1 << 4)
 #define GRIP_WIDTH 15
@@ -205,6 +206,9 @@ fill_button_event (GdkMacosDisplay *display,
   GdkSeat *seat;
   GdkEventType type;
   GdkModifierType state;
+  GdkDevice *pointer;
+  GdkDeviceTool *tool = NULL;
+  double *axes = NULL;
 
   g_assert (GDK_IS_MACOS_DISPLAY (display));
   g_assert (GDK_IS_MACOS_SURFACE (surface));
@@ -241,16 +245,26 @@ fill_button_event (GdkMacosDisplay *display,
        y < 0 || y > GDK_SURFACE (surface)->height))
     return NULL;
 
+  if ([nsevent subtype] == NSEventSubtypeTabletPoint)
+    {
+      _gdk_macos_seat_get_tablet_for_nsevent (GDK_MACOS_SEAT (seat), nsevent, &pointer, &tool);
+      axes = _gdk_macos_seat_get_tablet_axes_from_nsevent (GDK_MACOS_SEAT (seat), nsevent);
+    }
+  else
+    {
+      pointer = gdk_seat_get_pointer (seat);
+    }
+
   return gdk_button_event_new (type,
                                GDK_SURFACE (surface),
-                               gdk_seat_get_pointer (seat),
-                               NULL,
+                               pointer,
+                               tool,
                                get_time_from_ns_event (nsevent),
                                state,
                                get_mouse_button_from_ns_event (nsevent),
                                x,
                                y,
-                               NULL);
+                               axes);
 }
 
 static GdkEvent *
@@ -557,6 +571,9 @@ fill_motion_event (GdkMacosDisplay *display,
 {
   GdkSeat *seat;
   GdkModifierType state;
+  GdkDevice *pointer;
+  GdkDeviceTool *tool = NULL;
+  double *axes = NULL;
 
   g_assert (GDK_IS_MACOS_SURFACE (surface));
   g_assert (nsevent != NULL);
@@ -566,14 +583,24 @@ fill_motion_event (GdkMacosDisplay *display,
   state = get_keyboard_modifiers_from_ns_event (nsevent) |
           _gdk_macos_display_get_current_mouse_modifiers (display);
 
+  if ([nsevent subtype] == NSEventSubtypeTabletPoint)
+    {
+      _gdk_macos_seat_get_tablet_for_nsevent (GDK_MACOS_SEAT (seat), nsevent, &pointer, &tool);
+      axes = _gdk_macos_seat_get_tablet_axes_from_nsevent (GDK_MACOS_SEAT (seat), nsevent);
+    }
+  else
+    {
+      pointer = gdk_seat_get_pointer (seat);
+    }
+
   return gdk_motion_event_new (GDK_SURFACE (surface),
-                               gdk_seat_get_pointer (seat),
-                               NULL,
+                               pointer,
+                               tool,
                                get_time_from_ns_event (nsevent),
                                state,
                                x,
                                y,
-                               NULL);
+                               axes);
 }
 
 static GdkEvent *
@@ -1051,6 +1078,21 @@ _gdk_macos_display_translate (GdkMacosDisplay *self,
       return NULL;
     }
 
+  /* We need to register the proximity event from any point on the screen
+   * to properly register the devices
+   * FIXME: is there a better way to detect if a tablet has been plugged?
+   */
+  if (event_type == NSEventTypeTabletProximity)
+    {
+      _gdk_macos_seat_handle_tablet_tool_event (self, nsevent);
+
+      /* FIXME: we might want to cache this proximity event and propagate it
+       * but proximity events in gdk work at a window level while on macos
+       * works at a screen level. For now we just skip them.
+       */
+      return NULL;
+    }
+
   if (!(surface = find_surface_for_ns_event (self, nsevent, &x, &y)))
     return NULL;
 
diff --git a/gdk/macos/gdkmacosseat-private.h b/gdk/macos/gdkmacosseat-private.h
index 57e5605018..d5d90094c3 100644
--- a/gdk/macos/gdkmacosseat-private.h
+++ b/gdk/macos/gdkmacosseat-private.h
@@ -23,6 +23,7 @@
 #include <AppKit/AppKit.h>
 
 #include "gdkmacosdisplay.h"
+#include "gdkmacosseat.h"
 
 #include "gdkseatprivate.h"
 
@@ -30,6 +31,17 @@ G_BEGIN_DECLS
 
 GdkSeat *_gdk_macos_seat_new (GdkMacosDisplay *display);
 
+void _gdk_macos_seat_handle_tablet_tool_event (GdkMacosSeat *seat,
+                                               NSEvent      *nsevent);
+
+void _gdk_macos_seat_get_tablet_for_nsevent (GdkMacosSeat   *seat,
+                                             NSEvent        *nsevent,
+                                             GdkDevice     **device,
+                                             GdkDeviceTool **tool);
+
+double *_gdk_macos_seat_get_tablet_axes_from_nsevent (GdkMacosSeat *seat,
+                                                      NSEvent      *nsevent);
+
 G_END_DECLS
 
 #endif /* __GDK_MACOS_SEAT_PRIVATE_H__ */
diff --git a/gdk/macos/gdkmacosseat.c b/gdk/macos/gdkmacosseat.c
index e239c6cb3c..4d2d44df1f 100644
--- a/gdk/macos/gdkmacosseat.c
+++ b/gdk/macos/gdkmacosseat.c
@@ -22,42 +22,624 @@
 #include <gdk/gdk.h>
 
 #include "gdkdeviceprivate.h"
-#include "gdkseatdefaultprivate.h"
+#include "gdkdevicetoolprivate.h"
 
 #include "gdkmacosdevice.h"
 #include "gdkmacosseat-private.h"
 
+typedef struct
+{
+  unsigned long long device_id;
+  char *name;
+
+  GdkDevice *logical_device;
+  GdkDevice *stylus_device;
+  GdkSeat *seat;
+
+  GdkDeviceTool *current_tool;
+
+  int axis_indices[GDK_AXIS_LAST];
+  double axes[GDK_AXIS_LAST];
+} GdkMacosTabletData;
+
+struct _GdkMacosSeat
+{
+  GdkSeat parent_instance;
+
+  GdkMacosDisplay *display;
+
+  GdkDevice *logical_pointer;
+  GdkDevice *logical_keyboard;
+
+  GPtrArray *tablets;
+  GPtrArray *tools;
+};
+
+struct _GdkMacosSeatClass
+{
+  GdkSeatClass parent_class;
+};
+
+G_DEFINE_TYPE (GdkMacosSeat, gdk_macos_seat, GDK_TYPE_SEAT)
+
+#define KEYBOARD_EVENTS (GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |    \
+                         GDK_FOCUS_CHANGE_MASK)
+#define TOUCH_EVENTS    (GDK_TOUCH_MASK)
+#define POINTER_EVENTS  (GDK_POINTER_MOTION_MASK |                      \
+                         GDK_BUTTON_PRESS_MASK |                        \
+                         GDK_BUTTON_RELEASE_MASK |                      \
+                         GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK |     \
+                         GDK_ENTER_NOTIFY_MASK |                        \
+                         GDK_LEAVE_NOTIFY_MASK |                        \
+                         GDK_PROXIMITY_IN_MASK |                        \
+                         GDK_PROXIMITY_OUT_MASK)
+
+static void
+gdk_macos_tablet_data_free (gpointer user_data)
+{
+  GdkMacosTabletData *tablet = user_data;
+
+  gdk_seat_device_removed (GDK_SEAT (tablet->seat), tablet->stylus_device);
+  gdk_seat_device_removed (GDK_SEAT (tablet->seat), tablet->logical_device);
+
+  _gdk_device_set_associated_device (tablet->logical_device, NULL);
+  _gdk_device_set_associated_device (tablet->stylus_device, NULL);
+
+  g_object_unref (tablet->logical_device);
+  g_object_unref (tablet->stylus_device);
+
+  g_free (tablet->name);
+  g_free (tablet);
+}
+
+static void
+gdk_macos_seat_dispose (GObject *object)
+{
+  GdkMacosSeat *self = GDK_MACOS_SEAT (object);
+
+  if (self->logical_pointer)
+    {
+      gdk_seat_device_removed (GDK_SEAT (self), self->logical_pointer);
+      g_clear_object (&self->logical_pointer);
+    }
+
+  if (self->logical_keyboard)
+    {
+      gdk_seat_device_removed (GDK_SEAT (self), self->logical_keyboard);
+      g_clear_object (&self->logical_pointer);
+    }
+
+  g_clear_pointer (&self->tablets, g_ptr_array_unref);
+  g_clear_pointer (&self->tools, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (gdk_macos_seat_parent_class)->dispose (object);
+}
+
+static GdkSeatCapabilities
+gdk_macos_seat_get_capabilities (GdkSeat *seat)
+{
+  GdkMacosSeat *self = GDK_MACOS_SEAT (seat);
+  GdkSeatCapabilities caps = 0;
+
+  if (self->logical_pointer)
+    caps |= GDK_SEAT_CAPABILITY_POINTER;
+  if (self->logical_keyboard)
+    caps |= GDK_SEAT_CAPABILITY_KEYBOARD;
+
+  return caps;
+}
+
+static GdkGrabStatus
+gdk_macos_seat_grab (GdkSeat                *seat,
+                     GdkSurface             *surface,
+                     GdkSeatCapabilities     capabilities,
+                     gboolean                owner_events,
+                     GdkCursor              *cursor,
+                     GdkEvent               *event,
+                     GdkSeatGrabPrepareFunc  prepare_func,
+                     gpointer                prepare_func_data)
+{
+  GdkMacosSeat *self = GDK_MACOS_SEAT (seat);
+  guint32 evtime = event ? gdk_event_get_time (event) : GDK_CURRENT_TIME;
+  GdkGrabStatus status = GDK_GRAB_SUCCESS;
+  gboolean was_visible;
+
+  was_visible = gdk_surface_get_mapped (surface);
+
+  if (prepare_func)
+    (prepare_func) (seat, surface, prepare_func_data);
+
+  if (!gdk_surface_get_mapped (surface))
+    {
+      g_critical ("Surface %p has not been mapped in GdkSeatGrabPrepareFunc",
+                  surface);
+      return GDK_GRAB_NOT_VIEWABLE;
+    }
+
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+
+  if (capabilities & GDK_SEAT_CAPABILITY_ALL_POINTING)
+    {
+      /* ALL_POINTING spans 3 capabilities; get the mask for the ones we have */
+      GdkEventMask pointer_evmask = 0;
+
+      /* We let tablet styli take over the pointer cursor */
+      if (capabilities & (GDK_SEAT_CAPABILITY_POINTER |
+                          GDK_SEAT_CAPABILITY_TABLET_STYLUS))
+        {
+          pointer_evmask |= POINTER_EVENTS;
+        }
+
+      if (capabilities & GDK_SEAT_CAPABILITY_TOUCH)
+        pointer_evmask |= TOUCH_EVENTS;
+
+      status = gdk_device_grab (self->logical_pointer, surface,
+                                owner_events,
+                                pointer_evmask, cursor,
+                                evtime);
+    }
+
+  if (status == GDK_GRAB_SUCCESS &&
+      capabilities & GDK_SEAT_CAPABILITY_KEYBOARD)
+    {
+      status = gdk_device_grab (self->logical_keyboard, surface,
+                                owner_events,
+                                KEYBOARD_EVENTS, cursor,
+                                evtime);
+
+      if (status != GDK_GRAB_SUCCESS)
+        {
+          if (capabilities & ~GDK_SEAT_CAPABILITY_KEYBOARD)
+            gdk_device_ungrab (self->logical_pointer, evtime);
+        }
+    }
+
+  if (status != GDK_GRAB_SUCCESS && !was_visible)
+    gdk_surface_hide (surface);
+
+  G_GNUC_END_IGNORE_DEPRECATIONS;
+
+  return status;
+}
+
+static void
+gdk_macos_seat_ungrab (GdkSeat *seat)
+{
+  GdkMacosSeat *self = GDK_MACOS_SEAT (seat);
+
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+  gdk_device_ungrab (self->logical_pointer, GDK_CURRENT_TIME);
+  gdk_device_ungrab (self->logical_keyboard, GDK_CURRENT_TIME);
+  G_GNUC_END_IGNORE_DEPRECATIONS;
+}
+
+static GdkDevice *
+gdk_macos_seat_get_logical_device (GdkSeat             *seat,
+                                   GdkSeatCapabilities  capability)
+{
+  GdkMacosSeat *self = GDK_MACOS_SEAT (seat);
+
+  /* There must be only one flag set */
+  switch ((guint) capability)
+    {
+    case GDK_SEAT_CAPABILITY_POINTER:
+    case GDK_SEAT_CAPABILITY_TOUCH:
+      return self->logical_pointer;
+    case GDK_SEAT_CAPABILITY_KEYBOARD:
+      return self->logical_keyboard;
+    default:
+      g_warning ("Unhandled capability %x", capability);
+      break;
+    }
+
+  return NULL;
+}
+
+static GList *
+gdk_macos_seat_get_devices (GdkSeat             *seat,
+                            GdkSeatCapabilities  capabilities)
+{
+  GdkMacosSeat *self = GDK_MACOS_SEAT (seat);
+  GList *physical_devices = NULL;
+
+  if (self->logical_pointer && (capabilities & GDK_SEAT_CAPABILITY_POINTER))
+    physical_devices = g_list_prepend (physical_devices, self->logical_pointer);
+
+  if (self->logical_keyboard && (capabilities & GDK_SEAT_CAPABILITY_KEYBOARD))
+    physical_devices = g_list_prepend (physical_devices, self->logical_keyboard);
+
+  if (self->tablets && (capabilities & GDK_SEAT_CAPABILITY_TABLET_STYLUS))
+    {
+      for (guint i = 0; i < self->tablets->len; i++)
+        {
+          GdkMacosTabletData *tablet = g_ptr_array_index (self->tablets, i);
+
+          physical_devices = g_list_prepend (physical_devices, tablet->stylus_device);
+        }
+    }
+
+  return physical_devices;
+}
+
+static GList *
+gdk_macos_seat_get_tools (GdkSeat *seat)
+{
+  GdkMacosSeat *self = GDK_MACOS_SEAT (seat);
+  GdkDeviceTool *tool;
+  GList *tools = NULL;
+
+  if (!self->tools)
+    return NULL;
+
+  for (guint i = 0; i < self->tools->len; i++)
+    {
+      tool = g_ptr_array_index (self->tools, i);
+      tools = g_list_prepend (tools, tool);
+    }
+
+  return tools;
+}
+
+static void
+gdk_macos_seat_class_init (GdkMacosSeatClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GdkSeatClass *seat_class = GDK_SEAT_CLASS (klass);
+
+  object_class->dispose = gdk_macos_seat_dispose;
+
+  seat_class->get_capabilities = gdk_macos_seat_get_capabilities;
+  seat_class->grab = gdk_macos_seat_grab;
+  seat_class->ungrab = gdk_macos_seat_ungrab;
+  seat_class->get_logical_device = gdk_macos_seat_get_logical_device;
+  seat_class->get_devices = gdk_macos_seat_get_devices;
+  seat_class->get_tools = gdk_macos_seat_get_tools;
+}
+
+static void
+gdk_macos_seat_init (GdkMacosSeat *self)
+{
+}
+
+static void
+init_devices (GdkMacosSeat *self)
+{
+  /* pointer */
+  self->logical_pointer = g_object_new (GDK_TYPE_MACOS_DEVICE,
+                                        "name", "Core Pointer",
+                                        "source", GDK_SOURCE_MOUSE,
+                                        "has-cursor", TRUE,
+                                        "display", self->display,
+                                        "seat", self,
+                                        NULL);
+
+  /* keyboard */
+  self->logical_keyboard = g_object_new (GDK_TYPE_MACOS_DEVICE,
+                                         "name", "Core Keyboard",
+                                         "source", GDK_SOURCE_KEYBOARD,
+                                         "has-cursor", FALSE,
+                                         "display", self->display,
+                                         "seat", self,
+                                         NULL);
+
+  /* link both */
+  _gdk_device_set_associated_device (self->logical_pointer, self->logical_keyboard);
+  _gdk_device_set_associated_device (self->logical_keyboard, self->logical_pointer);
+
+  gdk_seat_device_added (GDK_SEAT (self), self->logical_pointer);
+  gdk_seat_device_added (GDK_SEAT (self), self->logical_keyboard);
+}
+
 GdkSeat *
 _gdk_macos_seat_new (GdkMacosDisplay *display)
 {
-  GdkDevice *core_keyboard;
-  GdkDevice *core_pointer;
-  GdkSeat *seat;
+  GdkMacosSeat *self;
 
   g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
 
-  core_pointer = g_object_new (GDK_TYPE_MACOS_DEVICE,
-                               "name", "Core Pointer",
-                               "source", GDK_SOURCE_MOUSE,
-                               "has-cursor", TRUE,
-                               "display", display,
-                               NULL);
-  core_keyboard = g_object_new (GDK_TYPE_MACOS_DEVICE,
-                                "name", "Core Keyboard",
-                                "source", GDK_SOURCE_KEYBOARD,
+  self = g_object_new (GDK_TYPE_MACOS_SEAT,
+                       "display", display,
+                       NULL);
+
+  self->display = display;
+
+  init_devices (self);
+
+  return g_steal_pointer (&self);
+}
+
+static GdkDeviceToolType
+get_device_tool_type_from_nsevent (NSEvent *nsevent)
+{
+  GdkDeviceToolType tool_type;
+
+  switch ([nsevent pointingDeviceType])
+    {
+    case NSPointingDeviceTypePen:
+      tool_type = GDK_DEVICE_TOOL_TYPE_PEN;
+      break;
+    case NSPointingDeviceTypeEraser:
+      tool_type = GDK_DEVICE_TOOL_TYPE_ERASER;
+      break;
+    case NSPointingDeviceTypeCursor:
+      tool_type = GDK_DEVICE_TOOL_TYPE_MOUSE;
+      break;
+    case NSPointingDeviceTypeUnknown:
+    default:
+      tool_type = GDK_DEVICE_TOOL_TYPE_UNKNOWN;
+    }
+
+  return tool_type;
+}
+
+static GdkAxisFlags
+get_device_tool_axes_from_nsevent (NSEvent *nsevent)
+{
+  /* TODO: do we need to be smarter about the capabilities? */
+  return GDK_AXIS_FLAG_XTILT | GDK_AXIS_FLAG_YTILT | GDK_AXIS_FLAG_PRESSURE |
+         GDK_AXIS_FLAG_ROTATION;
+}
+
+static GdkMacosTabletData *
+create_tablet_data_from_nsevent (GdkMacosSeat *self,
+                                 NSEvent      *nsevent)
+{
+  GdkMacosTabletData *tablet;
+  GdkDisplay *display = gdk_seat_get_display (GDK_SEAT (self));
+  GdkDevice *logical_device, *stylus_device;
+  char *logical_name;
+  char *vid, *pid;
+
+  tablet = g_new0 (GdkMacosTabletData, 1);
+  tablet->seat = GDK_SEAT (self);
+  tablet->device_id = [nsevent deviceID];
+  /* FIXME: find a better name */
+  tablet->name = g_strdup_printf ("Tablet %lu", [nsevent deviceID]);
+
+  vid = g_strdup_printf ("%.4lx", [nsevent vendorID]);
+  pid = g_strdup_printf ("%.4lx", [nsevent tabletID]);
+
+  logical_name = g_strdup_printf ("Logical pointer for %s", tablet->name);
+  logical_device = g_object_new (GDK_TYPE_MACOS_DEVICE,
+                                 "name", logical_name,
+                                 "source", GDK_SOURCE_MOUSE,
+                                 "has-cursor", TRUE,
+                                 "display", display,
+                                 "seat", self,
+                                 NULL);
+
+  stylus_device = g_object_new (GDK_TYPE_MACOS_DEVICE,
+                                "name", tablet->name,
+                                "source", GDK_SOURCE_PEN,
                                 "has-cursor", FALSE,
                                 "display", display,
+                                "seat", self,
+                                "vendor-id", vid,
+                                "product-id", pid,
                                 NULL);
 
-  _gdk_device_set_associated_device (GDK_DEVICE (core_pointer),
-                                     GDK_DEVICE (core_keyboard));
-  _gdk_device_set_associated_device (GDK_DEVICE (core_keyboard),
-                                     GDK_DEVICE (core_pointer));
+  tablet->logical_device = logical_device;
+  tablet->stylus_device = stylus_device;
+
+  _gdk_device_set_associated_device (logical_device, self->logical_keyboard);
+  _gdk_device_set_associated_device (stylus_device, logical_device);
+
+  gdk_seat_device_added (GDK_SEAT (self), logical_device);
+  gdk_seat_device_added (GDK_SEAT (self), stylus_device);
+
+  g_free (logical_name);
+  g_free (vid);
+  g_free (pid);
+
+  return tablet;
+}
+
+static GdkMacosTabletData *
+get_tablet_data_from_nsevent (GdkMacosSeat *self,
+                              NSEvent      *nsevent)
+{
+  GdkMacosTabletData *tablet = NULL;
+
+  if (!self->tablets)
+    self->tablets = g_ptr_array_new_with_free_func (gdk_macos_tablet_data_free);
+
+  for (guint i = 0; i < self->tablets->len; i++)
+    {
+      GdkMacosTabletData *t = g_ptr_array_index (self->tablets, i);
+
+      if (t->device_id == [nsevent deviceID])
+        {
+          tablet = t;
+          break;
+        }
+    }
+
+  if (!tablet)
+    tablet = create_tablet_data_from_nsevent (self, nsevent);
+
+  return tablet;
+}
+
+static void
+device_tablet_clone_tool_axes (GdkMacosTabletData *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, 0, 0, 0);
+  _gdk_device_add_axis (tablet->stylus_device, GDK_AXIS_Y, 0, 0, 0);
+
+  if (tool->tool_axes & (GDK_AXIS_FLAG_XTILT | GDK_AXIS_FLAG_YTILT))
+    {
+      axis_pos = _gdk_device_add_axis (tablet->stylus_device,
+                                       GDK_AXIS_XTILT, -1.0, 1.0, 0);
+      tablet->axis_indices[GDK_AXIS_XTILT] = axis_pos;
+
+      axis_pos = _gdk_device_add_axis (tablet->stylus_device,
+                                       GDK_AXIS_YTILT, -1.0, 1.0, 0);
+      tablet->axis_indices[GDK_AXIS_YTILT] = axis_pos;
+    }
+
+  if (tool->tool_axes & GDK_AXIS_FLAG_PRESSURE)
+    {
+      axis_pos = _gdk_device_add_axis (tablet->stylus_device,
+                                       GDK_AXIS_PRESSURE, 0.0, 1.0, 0);
+      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, 0.0, 1.0, 0);
+      tablet->axis_indices[GDK_AXIS_ROTATION] = axis_pos;
+    }
+
+  g_object_thaw_notify (G_OBJECT (tablet->stylus_device));
+}
+
+static void
+mimic_device_axes (GdkDevice *logical,
+                   GdkDevice *physical)
+{
+  double axis_min, axis_max, axis_resolution;
+  GdkAxisUse axis_use;
+  int axis_count;
+
+  g_object_freeze_notify (G_OBJECT (logical));
+  _gdk_device_reset_axes (logical);
+  axis_count = gdk_device_get_n_axes (physical);
+
+  for (int 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));
+}
+
+void
+_gdk_macos_seat_handle_tablet_tool_event (GdkMacosSeat *seat,
+                                          NSEvent      *nsevent)
+{
+  GdkDeviceToolType tool_type;
+  GdkMacosTabletData *tablet;
+  GdkDeviceTool *tool;
+
+  g_return_if_fail (GDK_IS_MACOS_SEAT (seat));
+  g_return_if_fail (nsevent != NULL);
+
+  tablet = get_tablet_data_from_nsevent (seat, nsevent);
+
+  tool_type = get_device_tool_type_from_nsevent (nsevent);
+
+  if (tool_type == GDK_DEVICE_TOOL_TYPE_UNKNOWN)
+    {
+      g_warning ("Unknown device tool detected");
+      return;
+    }
+
+  tool = gdk_seat_get_tool(GDK_SEAT (seat), [nsevent tabletID], [nsevent deviceID], tool_type);
+
+  if ([nsevent isEnteringProximity])
+    {
+      if (!tool)
+        {
+          tool = gdk_device_tool_new ([nsevent tabletID], [nsevent vendorID], tool_type,
+                                      get_device_tool_axes_from_nsevent (nsevent));
+          g_ptr_array_add (seat->tools, tool);
+        }
+
+      gdk_device_update_tool (tablet->stylus_device, tool);
+      tablet->current_tool = tool;
+      device_tablet_clone_tool_axes (tablet, tool);
+      mimic_device_axes (tablet->logical_device, tablet->stylus_device);
+    }
+  else
+    {
+      gdk_device_update_tool (tablet->stylus_device, NULL);
+      tablet->current_tool = NULL;
+    }
+
+  if (tool)
+    [NSEvent setMouseCoalescingEnabled: FALSE];
+  else
+    [NSEvent setMouseCoalescingEnabled: TRUE];
+}
+
+void
+_gdk_macos_seat_get_tablet_for_nsevent (GdkMacosSeat   *seat,
+                                        NSEvent        *nsevent,
+                                        GdkDevice     **logical_device,
+                                        GdkDeviceTool **tool)
+{
+  GdkMacosTabletData *tablet;
+
+  g_return_if_fail (GDK_IS_MACOS_SEAT (seat));
+  g_return_if_fail (nsevent != NULL);
+
+  tablet = get_tablet_data_from_nsevent (seat, nsevent);
+
+  *logical_device = tablet->logical_device;
+  *tool = tablet->current_tool;
+}
+
+double *
+_gdk_macos_seat_get_tablet_axes_from_nsevent (GdkMacosSeat *seat,
+                                              NSEvent      *nsevent)
+{
+  GdkDeviceToolType tool_type;
+  GdkDeviceTool *tool;
+  GdkMacosTabletData *tablet;
+  int axis_index;
+
+  g_return_val_if_fail (GDK_IS_MACOS_SEAT (seat), NULL);
+  g_return_val_if_fail (nsevent != NULL, NULL);
+
+  tool_type = get_device_tool_type_from_nsevent (nsevent);
+
+  if (tool_type == GDK_DEVICE_TOOL_TYPE_UNKNOWN)
+    {
+      g_warning ("Unknown device tool detected");
+      return NULL;
+    }
+
+  tool = gdk_seat_get_tool (GDK_SEAT (seat), [nsevent tabletID], [nsevent deviceID], tool_type);
+  if (!tool)
+    return NULL;;
+
+  tablet = get_tablet_data_from_nsevent (seat, nsevent);
+
+  if (tool->tool_axes & (GDK_AXIS_FLAG_XTILT | GDK_AXIS_FLAG_YTILT))
+    {
+      axis_index = tablet->axis_indices[GDK_AXIS_XTILT];
+      _gdk_device_translate_axis (tablet->stylus_device, axis_index,
+                                  [nsevent tilt].x, &tablet->axes[GDK_AXIS_XTILT]);
+
+      axis_index = tablet->axis_indices[GDK_AXIS_YTILT];
+      _gdk_device_translate_axis (tablet->stylus_device, axis_index,
+                                  [nsevent tilt].y, &tablet->axes[GDK_AXIS_YTILT]);
+    }
 
-  seat = gdk_seat_default_new_for_logical_pair (core_pointer, core_keyboard);
+  if (tool->tool_axes & GDK_AXIS_FLAG_PRESSURE)
+    {
+      axis_index = tablet->axis_indices[GDK_AXIS_PRESSURE];
+      _gdk_device_translate_axis (tablet->stylus_device, axis_index,
+                                  [nsevent pressure], &tablet->axes[GDK_AXIS_PRESSURE]);
+    }
 
-  g_object_unref (core_pointer);
-  g_object_unref (core_keyboard);
+  if (tool->tool_axes & GDK_AXIS_FLAG_ROTATION)
+    {
+      axis_index = tablet->axis_indices[GDK_AXIS_ROTATION];
+      _gdk_device_translate_axis (tablet->stylus_device, axis_index,
+                                  [nsevent rotation], &tablet->axes[GDK_AXIS_ROTATION]);
+    }
 
-  return g_steal_pointer (&seat);
+  return g_memdup (tablet->axes,
+                   sizeof (double) * GDK_AXIS_LAST);
 }
diff --git a/gdk/macos/gdkmacosseat.h b/gdk/macos/gdkmacosseat.h
new file mode 100644
index 0000000000..e09c01c995
--- /dev/null
+++ b/gdk/macos/gdkmacosseat.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2021 Amazon.com, Inc. and its affiliates. All Rights Reserved.
+ *
+ * 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.1 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/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#if !defined (__GDKMACOS_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gdk/macos/gdkmacos.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_MACOS_SEAT    (gdk_macos_seat_get_type ())
+#define GDK_MACOS_SEAT(obj)    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_MACOS_SEAT, GdkMacosSeat))
+#define GDK_IS_MACOS_SEAT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_MACOS_SEAT))
+
+typedef struct _GdkMacosSeat      GdkMacosSeat;
+typedef struct _GdkMacosSeatClass GdkMacosSeatClass;
+
+GDK_AVAILABLE_IN_ALL
+GType gdk_macos_seat_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build
index 1b8d205055..943fb84457 100644
--- a/gdk/macos/meson.build
+++ b/gdk/macos/meson.build
@@ -33,6 +33,7 @@ gdk_macos_public_headers = files([
   'gdkmacosglcontext.h',
   'gdkmacoskeymap.h',
   'gdkmacosmonitor.h',
+  'gdkmacosseat.h',
   'gdkmacossurface.h',
 ])
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]