/* * 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 . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include #include "gdkdeviceprivate.h" #include "gdkdevicetoolprivate.h" #include "gdkmacosdevice.h" #include "gdkmacosseat-private.h" #include "gdk-private.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); }