[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 = ¤t_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