[PATCH v2 08/12] macdrv/wintab32: Implement tablet events.

Elaine Lefler elaineclefler at gmail.com
Thu Mar 24 23:46:22 CDT 2022


Signed-off-by: Elaine Lefler <elaineclefler at gmail.com>
---

v2: Splitting into smaller patches as per feedback.

I'm afraid this is also a fairly large patch, but there's no way to
reduce it without leaving dead code.
---
 dlls/winemac.drv/Makefile.in    |   3 +-
 dlls/winemac.drv/cocoa_wintab.h |  13 ++
 dlls/winemac.drv/cocoa_wintab.m | 219 ++++++++++++++++++++++++++++++++
 dlls/winemac.drv/event.c        |   3 +
 dlls/winemac.drv/macdrv.h       |   1 +
 dlls/winemac.drv/macdrv_cocoa.h |  11 ++
 dlls/winemac.drv/wintab.c       |  74 ++++++++++-
 7 files changed, 322 insertions(+), 2 deletions(-)
 create mode 100644 dlls/winemac.drv/cocoa_wintab.m

diff --git a/dlls/winemac.drv/Makefile.in b/dlls/winemac.drv/Makefile.in
index 47b7c68feb3..5fc8f4152fe 100644
--- a/dlls/winemac.drv/Makefile.in
+++ b/dlls/winemac.drv/Makefile.in
@@ -33,6 +33,7 @@ OBJC_SRCS = \
 	cocoa_main.m \
 	cocoa_opengl.m \
 	cocoa_status_item.m \
-	cocoa_window.m
+	cocoa_window.m \
+	cocoa_wintab.m
 
 RC_SRCS = winemac.rc
diff --git a/dlls/winemac.drv/cocoa_wintab.h b/dlls/winemac.drv/cocoa_wintab.h
index 1b9867c9d34..0b85bd2ad0d 100644
--- a/dlls/winemac.drv/cocoa_wintab.h
+++ b/dlls/winemac.drv/cocoa_wintab.h
@@ -23,6 +23,13 @@
 
 #include <math.h>
 
+#ifdef __OBJC__
+/* Necessary hack because this header must be included on both the Wine side
+ * and from Objective-C */
+typedef BOOL OBJC_BOOL;
+#define BOOL WIN_BOOL
+#endif
+
 #include "windef.h"
 #include "wintab.h"
 
@@ -48,4 +55,10 @@
 /* Shared device context */
 extern LOGCONTEXTW macdrv_tablet_ctx DECLSPEC_HIDDEN;
 
+/* Window to which tablet events are delivered */
+extern void* macdrv_tablet_window DECLSPEC_HIDDEN;
+
+/* Objective-C function to start tablet events */
+void CDECL macdrv_start_tablet_monitor(void) DECLSPEC_HIDDEN;
+
 #endif /* !defined(__WINE_MACDRV_COCOA_WINTAB_H) */
diff --git a/dlls/winemac.drv/cocoa_wintab.m b/dlls/winemac.drv/cocoa_wintab.m
new file mode 100644
index 00000000000..da0c2e91df4
--- /dev/null
+++ b/dlls/winemac.drv/cocoa_wintab.m
@@ -0,0 +1,219 @@
+/*
+ * MACDRV Cocoa wintab implementations
+ *
+ * Copyright 2022 Elaine Lefler
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include "config.h"
+
+#include "cocoa_app.h"
+#include "cocoa_event.h"
+#include "cocoa_window.h"
+#include "cocoa_wintab.h"
+
+static UINT packet_id = 0;
+static UINT current_cursor = MACDRV_CURSOR_CURSOR;
+
+static CFMachPortRef event_tap;
+
+static const CGEventMask MOUSE_DOWN_EVENTS =
+        CGEventMaskBit(kCGEventLeftMouseDown)
+        | CGEventMaskBit(kCGEventRightMouseDown)
+        | CGEventMaskBit(kCGEventOtherMouseDown);
+static const CGEventMask MOUSE_UP_EVENTS =
+        CGEventMaskBit(kCGEventLeftMouseUp)
+        | CGEventMaskBit(kCGEventRightMouseUp)
+        | CGEventMaskBit(kCGEventOtherMouseUp);
+static const CGEventMask MOUSE_DRAG_EVENTS =
+        CGEventMaskBit(kCGEventLeftMouseDragged)
+        | CGEventMaskBit(kCGEventRightMouseDragged)
+        | CGEventMaskBit(kCGEventOtherMouseDragged);
+
+static const CGEventMask MOUSE_EVENTS =
+        CGEventMaskBit(kCGEventMouseMoved)
+        | MOUSE_DOWN_EVENTS | MOUSE_UP_EVENTS | MOUSE_DRAG_EVENTS;
+
+static const CGEventMask TABLET_EVENTS =
+        CGEventMaskBit(kCGEventTabletPointer)
+        | CGEventMaskBit(kCGEventTabletProximity);
+
+static UINT cursor_from_nx(NSPointingDeviceType device_type)
+{
+    switch (device_type)
+    {
+    case NX_TABLET_POINTER_PEN:
+        return MACDRV_CURSOR_PEN;
+    case NX_TABLET_POINTER_ERASER:
+        return MACDRV_CURSOR_ERASER;
+    default:
+        /* Squash Unknown into Cursor */
+        return MACDRV_CURSOR_CURSOR;
+    }
+}
+
+static void packet_from_cgevent(PACKET* pkt, CGEventRef event)
+{
+    static const double ALTI_VECTOR_SCALE = 0.89879404629916700; /* cos(26) */
+    static const double IN_VECTOR_ANGLE_SCALE = .073519026192766368; /* (cos(19) / cos(26) - 1) * sqrt(2) */
+
+    double azimuth, altitude;
+    double angle_scale, max_length, length;
+
+    CGPoint location = CGEventGetUnflippedLocation(event);
+    double tilt_x = CGEventGetDoubleValueField(event, kCGTabletEventTiltX);
+    double tilt_y = CGEventGetDoubleValueField(event, kCGTabletEventTiltY);
+    uint64_t time_ns = CGEventGetTimestamp(event);
+    pkt->pkStatus = (current_cursor == MACDRV_CURSOR_ERASER ? TPS_INVERT : 0);
+    pkt->pkTime = [[WineApplicationController sharedController] ticksForEventTime:time_ns / (double)NSEC_PER_SEC];
+    pkt->pkSerialNumber = ++packet_id;
+
+    pkt->pkCursor = current_cursor;
+    pkt->pkButtons = CGEventGetIntegerValueField(event, kCGTabletEventPointButtons);
+
+    location = cgpoint_win_from_mac(location);
+
+    /* y should be y-1 because of the way "unflipped" location is calculated.
+     * We don't -1 the extents because macOS doesn't map the tablet that way. */
+    pkt->pkX = (location.x - (double)macdrv_tablet_ctx.lcSysOrgX) / macdrv_tablet_ctx.lcSysExtX * TABLET_WIDTH;
+    pkt->pkY = (location.y - 1 - (double)macdrv_tablet_ctx.lcSysOrgY) / macdrv_tablet_ctx.lcSysExtY * TABLET_HEIGHT;
+    pkt->pkZ = CGEventGetIntegerValueField(event, kCGTabletEventPointZ);
+
+    pkt->pkNormalPressure = CGEventGetDoubleValueField(event, kCGTabletEventPointPressure) * MAX_PRESSURE;
+    pkt->pkTangentPressure = CGEventGetDoubleValueField(event, kCGTabletEventTangentialPressure) * MAX_PRESSURE;
+
+    /* Find the angle around the Z axis. Note that 0 is up and angles move
+     * clockwise. Swapping x and y gives the correct angle. */
+    azimuth = atan2(tilt_x, tilt_y);
+    /* Adjust to 0..360 range */
+    if (azimuth < 0.)
+        azimuth += 2 * M_PI;
+
+    /* With the pen resting on its barrel and oriented horizontally or
+     * vertically, a real tablet reads an altitude of 26. This corresponds to a
+     * tilt of 1. Spinning it to a 45 degree angle allows reading a little bit
+     * lower, down to 19. However, the X/Y tilt appears to report values that
+     * are out of range for wintab. Cap the vector at a value that smoothly
+     * transitions between 1 and the maximum length based on the azimuth. */
+    angle_scale = fmin(fabs(cos(azimuth)), fabs(sin(azimuth)));
+    max_length = 1. + IN_VECTOR_ANGLE_SCALE * angle_scale;
+
+    /* Since the pen moves in a circular path we can calculate the altitude as
+     * the arccosine of the X/Y vector. Multiply it by cos(26) such that a
+     * vector of length 1 maps to 26 degrees. */
+    length = fmin(max_length, sqrt(tilt_x * tilt_x + tilt_y * tilt_y));
+    altitude = acos(length * ALTI_VECTOR_SCALE);
+
+    pkt->pkOrientation.orAzimuth = MAKE_ANGLE_RAD(azimuth);
+    /* Altitude is negative on the eraser */
+    pkt->pkOrientation.orAltitude = MAKE_ANGLE_RAD(!(pkt->pkStatus & TPS_INVERT) ? altitude : -altitude);
+    /* Rotation is the same as twist */
+    pkt->pkOrientation.orTwist = MAKE_ANGLE_DEG(CGEventGetDoubleValueField(event, kCGTabletEventRotation));
+}
+
+static CGEventRef tablet_event_cb(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon)
+{
+    CGEventMask type_mask;
+    BOOL is_proximity;
+
+    macdrv_event* out_event;
+
+    /* Tap can be disabled for various reasons, make sure it comes back */
+    if (type == kCGEventTapDisabledByTimeout
+        || type == kCGEventTapDisabledByUserInput)
+    {
+        CGEventTapEnable(event_tap, YES);
+        return event;
+    }
+
+    type_mask = CGEventMaskBit(type);
+    is_proximity = (type == kCGEventTabletProximity);
+
+    if (!(type_mask & TABLET_EVENTS))
+    {
+        int64_t subtype = CGEventGetIntegerValueField(event, kCGMouseEventSubtype);
+        if (subtype == kCGEventMouseSubtypeTabletProximity)
+            is_proximity = YES;
+        else if (subtype != kCGEventMouseSubtypeTabletPoint)
+            /* Not a tablet event */
+            return event;
+    }
+
+    /*NSLog(@"%@", [NSEvent eventWithCGEvent:event]);*/
+    out_event = macdrv_create_event(TABLET_EVENT, (WineWindow*)macdrv_tablet_window);
+
+    if (is_proximity)
+    {
+        /* Cursor type only appears during proximity events */
+        current_cursor = cursor_from_nx(CGEventGetIntegerValueField(event, kCGTabletProximityEventPointerType));
+
+        if (CGEventGetIntegerValueField(event, kCGTabletProximityEventEnterProximity) != 0)
+            out_event->tablet_event.type = TABLET_EVENT_PROXIMITY_ENTER;
+        else
+            out_event->tablet_event.type = TABLET_EVENT_PROXIMITY_LEAVE;
+    }
+    else
+    {
+        PACKET* event_packet = calloc(1, sizeof(PACKET));
+        packet_from_cgevent(event_packet, event);
+        out_event->tablet_event.type = TABLET_EVENT_POINT;
+        out_event->tablet_event.packet = event_packet;
+    }
+
+    [[(WineWindow*)macdrv_tablet_window queue] postEvent:out_event];
+    macdrv_release_event(out_event);
+    return event;
+}
+
+static void* macdrv_tablet_main(void* _)
+{
+    CFRunLoopSourceRef source;
+    event_tap = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
+            kCGEventTapOptionListenOnly, MOUSE_EVENTS | TABLET_EVENTS, tablet_event_cb, NULL);
+
+    if (!event_tap)
+        return NULL;
+
+    source = CFMachPortCreateRunLoopSource(NULL, event_tap, 0);
+    if (!source)
+    {
+        CFRelease(event_tap);
+        return NULL;
+    }
+
+    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
+    CFRelease(source);
+
+    CFRunLoopRun();
+
+    CFRelease(event_tap);
+    return NULL;
+}
+
+void CDECL macdrv_start_tablet_monitor(void)
+{
+    /* Install a global monitor for relevant events. This ensures we can deliver
+     * the full device area and event frequency that the tablet offers. It's
+     * essential to run the monitor in its own thread, because CoreGraphics
+     * blocks event handling until it's done. Scheduling on a busy thread would
+     * cause the mouse to lag. */
+    static pthread_t thread;
+    if (!thread)
+    {
+        if (pthread_create(&thread, NULL, macdrv_tablet_main, NULL) == 0)
+            pthread_detach(thread);
+    }
+}
diff --git a/dlls/winemac.drv/event.c b/dlls/winemac.drv/event.c
index f197af0808e..22caf0cf2a7 100644
--- a/dlls/winemac.drv/event.c
+++ b/dlls/winemac.drv/event.c
@@ -308,6 +308,9 @@ void macdrv_handle_event(const macdrv_event *event)
     case WINDOW_RESTORE_REQUESTED:
         macdrv_window_restore_requested(hwnd, event);
         break;
+    case TABLET_EVENT:
+        macdrv_tablet_event(event);
+        break;
     default:
         TRACE("    ignoring\n");
         break;
diff --git a/dlls/winemac.drv/macdrv.h b/dlls/winemac.drv/macdrv.h
index 4e4524722af..3eecb26a01e 100644
--- a/dlls/winemac.drv/macdrv.h
+++ b/dlls/winemac.drv/macdrv.h
@@ -232,6 +232,7 @@ extern DWORD CDECL macdrv_MsgWaitForMultipleObjectsEx(DWORD count, const HANDLE
 extern void macdrv_window_drag_begin(HWND hwnd, const macdrv_event *event) DECLSPEC_HIDDEN;
 extern void macdrv_window_drag_end(HWND hwnd) DECLSPEC_HIDDEN;
 extern void macdrv_reassert_window_position(HWND hwnd) DECLSPEC_HIDDEN;
+extern void macdrv_tablet_event(const macdrv_event *event) DECLSPEC_HIDDEN;
 extern BOOL query_resize_size(HWND hwnd, macdrv_query *query) DECLSPEC_HIDDEN;
 extern BOOL query_resize_start(HWND hwnd) DECLSPEC_HIDDEN;
 extern BOOL query_min_max_info(HWND hwnd) DECLSPEC_HIDDEN;
diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h
index 94f9fbcfa17..c7f87888fdc 100644
--- a/dlls/winemac.drv/macdrv_cocoa.h
+++ b/dlls/winemac.drv/macdrv_cocoa.h
@@ -351,6 +351,7 @@ extern int macdrv_set_display_mode(const struct macdrv_display* display,
     WINDOW_MINIMIZE_REQUESTED,
     WINDOW_RESIZE_ENDED,
     WINDOW_RESTORE_REQUESTED,
+    TABLET_EVENT,
     NUM_EVENT_TYPES
 };
 
@@ -361,6 +362,12 @@ extern int macdrv_set_display_mode(const struct macdrv_display* display,
     QUIT_REASON_SHUTDOWN,
 };
 
+enum {
+    TABLET_EVENT_POINT,
+    TABLET_EVENT_PROXIMITY_ENTER,
+    TABLET_EVENT_PROXIMITY_LEAVE,
+};
+
 typedef uint64_t macdrv_event_mask;
 
 typedef struct macdrv_event {
@@ -455,6 +462,10 @@ extern int macdrv_set_display_mode(const struct macdrv_display* display,
             int     keep_frame;
             CGRect  frame;
         }                                           window_restore_requested;
+        struct {
+            int         type;
+            void       *packet;
+        }                                           tablet_event;
     };
 } macdrv_event;
 
diff --git a/dlls/winemac.drv/wintab.c b/dlls/winemac.drv/wintab.c
index bd6d6b783e0..11979afba59 100644
--- a/dlls/winemac.drv/wintab.c
+++ b/dlls/winemac.drv/wintab.c
@@ -38,6 +38,8 @@ static LOGCONTEXTW macdrv_system_ctx;
 
 void* macdrv_tablet_window;
 static HWND macdrv_tablet_hwnd;
+static PACKET current_packet;
+static int last_event_type = -1;
 
 static size_t registered_hwnd_count = 0;
 static size_t registered_hwnd_cap = 0;
@@ -553,10 +555,80 @@ int CDECL macdrv_AttachEventQueueToTablet(HWND hOwner)
     if (registered_hwnd_count > 1)
         FIXME("Multiple contexts are not correctly supported. All events are delivered to all contexts.\n");
 
+    macdrv_start_tablet_monitor();
     return 0;
 }
 
+/***********************************************************************
+ *              macdrv_tablet_event
+ *
+ * Handler for TABLET_EVENT events.
+ */
+void macdrv_tablet_event(const macdrv_event* event)
+{
+    int i;
+    PACKET* old_pkt = &current_packet;
+    PACKET* new_pkt = event->tablet_event.packet;
+    int event_type = event->tablet_event.type;
+    BOOL is_proximity = (event_type != TABLET_EVENT_POINT);
+
+    if (is_proximity)
+    {
+        /* Wacom's documentation very confusingly states:
+         * > The high-order word is non-zero when the cursor is leaving or
+         * > entering hardware proximity.
+         * What it actually means is that the high-order word is 1 when entering
+         * proximity or 0 when leaving proximity.
+         *
+         * The low and high order words are always the same. The only way to
+         * enter/leave context proximity without entering/leaving device
+         * proximity is to have multiple contexts. Macdrv has no awareness of
+         * contexts so this would have to be handled by wintab.dll. */
+        BOOL is_enter = (event_type == TABLET_EVENT_PROXIMITY_ENTER);
+        LPARAM l_param = MAKELPARAM(is_enter, is_enter);
+
+        /* We can get duplicate events due to NSEventTypeMouseMoved +
+         * NSEventTypeTabletProximity both being delivered. Squash them here. */
+        if (last_event_type == event_type)
+            return;
+
+        last_event_type = event_type;
+
+        for (i = 0; i < registered_hwnd_count; i++)
+        {
+            HWND hwnd = registered_hwnd[i];
+            SendMessageW(macdrv_tablet_hwnd, WT_PROXIMITY, (WPARAM)hwnd, l_param);
+        }
+    }
+    else
+    {
+        last_event_type = TABLET_EVENT_POINT;
+
+        /* Calculate which values changed */
+        new_pkt->pkChanged = ((new_pkt->pkStatus != old_pkt->pkStatus) * PK_STATUS)
+            | ((new_pkt->pkTime != old_pkt->pkTime) * PK_TIME)
+            | PK_SERIAL_NUMBER
+            | ((new_pkt->pkButtons != old_pkt->pkButtons) * PK_BUTTONS)
+            | ((new_pkt->pkX != old_pkt->pkX) * PK_X)
+            | ((new_pkt->pkY != old_pkt->pkY) * PK_Y)
+            | ((new_pkt->pkZ != old_pkt->pkZ) * PK_Z)
+            | ((new_pkt->pkNormalPressure != old_pkt->pkNormalPressure) * PK_NORMAL_PRESSURE)
+            | ((new_pkt->pkTangentPressure != old_pkt->pkTangentPressure) * PK_TANGENT_PRESSURE)
+            | ((memcmp(&new_pkt->pkOrientation, &old_pkt->pkOrientation, sizeof(new_pkt->pkOrientation)) != 0) * PK_ORIENTATION);
+
+        *old_pkt = *new_pkt;
+        free(new_pkt);
+
+        for (i = 0; i < registered_hwnd_count; i++)
+        {
+            HWND hwnd = registered_hwnd[i];
+            SendMessageW(macdrv_tablet_hwnd, WT_PACKET, (WPARAM)current_packet.pkSerialNumber, (LPARAM)hwnd);
+        }
+    }
+}
+
 int CDECL macdrv_GetCurrentPacket(LPPACKET packet)
 {
-    return 0;
+    *packet = current_packet;
+    return 1;
 }
-- 
2.32.0 (Apple Git-132)




More information about the wine-devel mailing list