| /* |
| * Copyright © 2020 Red Hat, Inc. |
| * 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 |
| */ |
| |
| #include "config.h" |
| |
| #include <gdk/gdk.h> |
| |
| #include "gdkdeviceprivate.h" |
| #include "gdkdevicetoolprivate.h" |
| |
| #include "gdkmacosdevice.h" |
| #include "gdkmacosseat-private.h" |
| |
| #include "gdkprivate.h" |
| |
| typedef struct |
| { |
| NSUInteger 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; |
| |
| GdkMacosTabletData *current_tablet; |
| 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 (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; |
| |
| 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) |
| { |
| self->tablets = g_ptr_array_new_with_free_func (gdk_macos_tablet_data_free); |
| self->tools = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); |
| } |
| |
| 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) |
| { |
| GdkMacosSeat *self; |
| |
| g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL); |
| |
| self = g_object_new (GDK_TYPE_MACOS_SEAT, |
| "display", display, |
| NULL); |
| |
| self->display = display; |
| |
| init_devices (self); |
| |
| return GDK_SEAT (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); |
| |
| 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; |
| |
| 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); |
| seat->current_tablet = tablet; |
| } |
| else |
| { |
| gdk_device_update_tool (tablet->stylus_device, NULL); |
| tablet->current_tool = NULL; |
| seat->current_tablet = NULL; |
| } |
| } |
| |
| gboolean |
| _gdk_macos_seat_get_tablet (GdkMacosSeat *seat, |
| GdkDevice **logical_device, |
| GdkDeviceTool **tool) |
| { |
| g_return_val_if_fail (GDK_IS_MACOS_SEAT (seat), FALSE); |
| |
| if (!seat->current_tablet) |
| return FALSE; |
| |
| *logical_device = seat->current_tablet->logical_device; |
| *tool = seat->current_tablet->current_tool; |
| |
| return TRUE; |
| } |
| |
| double * |
| _gdk_macos_seat_get_tablet_axes_from_nsevent (GdkMacosSeat *seat, |
| NSEvent *nsevent) |
| { |
| GdkMacosTabletData *tablet; |
| int axis_index; |
| |
| g_return_val_if_fail (GDK_IS_MACOS_SEAT (seat), NULL); |
| g_return_val_if_fail (nsevent != NULL, NULL); |
| |
| tablet = seat->current_tablet; |
| if (!tablet || !tablet->current_tool) |
| return NULL; |
| |
| if (tablet->current_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]); |
| } |
| |
| if (tablet->current_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]); |
| } |
| |
| if (tablet->current_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_memdup2 (tablet->axes, sizeof (double) * GDK_AXIS_LAST); |
| } |