[PATCH 1/3] macdrv: Add wintab32 implementation.

Elaine Lefler elaineclefler at gmail.com
Tue Mar 22 20:31:49 CDT 2022


Allow use of graphics tablets with applications that support them.

Update the existing wintab code for the sake of correctness and to
reduce code duplication. Based on behavior observed from a Wacom
Intuos tablet on Windows 10.

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

This is an effort to get PaintTool SAI working on macOS. I've split up
the macdrv changes to make them easier to read, but all three are
needed for the app to work. I'm separately submitting ntdll changes
which are useful but not strictly necessary.

Tablet support: SaiV2 works out of the box. Tested with Intuos tablet
as well as Apple iPad in Sidecar mode. SaiV1 works in theory, but is
32-bit only and I don't know how to compile that on a modern Mac.

WTPACKET has been renamed to PACKET for consistency with Wacom's
headers. Also, pktdef.h was included in Wine but never used, so it's
been deleted. The X11 code remains functionally unchanged, except for
TABLET_CopyPacketData incorrectly inserting values of SM_CXSCREEN and
SM_CYSCREEN when the X11 driver was trying to report the correct values
of SM_CXVIRTUALSCREEN and SM_CYVIRTUALSCREEN, so this should fix
multimonitor support. The other changes to wintab32.dll are just for
minor edge cases.
---
 dlls/winemac.drv/Makefile.in      |   6 +-
 dlls/winemac.drv/cocoa_wintab.h   |  64 +++
 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/winemac.drv.spec |   6 +
 dlls/winemac.drv/wintab.c         | 631 ++++++++++++++++++++++++++++++
 dlls/winex11.drv/wintab.c         | 242 +++++-------
 dlls/wintab32/context.c           | 215 ++++++----
 dlls/wintab32/wintab32.c          |  18 +-
 dlls/wintab32/wintab_internal.h   |  26 +-
 include/Makefile.in               |   1 -
 include/pktdef.h                  | 248 ------------
 include/wintab.h                  |  91 ++++-
 tools/winapi/win32.api            |   2 +-
 16 files changed, 1279 insertions(+), 505 deletions(-)
 create mode 100644 dlls/winemac.drv/cocoa_wintab.h
 create mode 100644 dlls/winemac.drv/cocoa_wintab.m
 create mode 100644 dlls/winemac.drv/wintab.c
 delete mode 100644 include/pktdef.h

diff --git a/dlls/winemac.drv/Makefile.in b/dlls/winemac.drv/Makefile.in
index da2e5eaf4c4..5fc8f4152fe 100644
--- a/dlls/winemac.drv/Makefile.in
+++ b/dlls/winemac.drv/Makefile.in
@@ -21,7 +21,8 @@ C_SRCS = \
 	surface.c \
 	systray.c \
 	vulkan.c \
-	window.c
+	window.c \
+	wintab.c
 
 OBJC_SRCS = \
 	cocoa_app.m \
@@ -32,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
new file mode 100644
index 00000000000..8db44422d02
--- /dev/null
+++ b/dlls/winemac.drv/cocoa_wintab.h
@@ -0,0 +1,64 @@
+/*
+ * MACDRV Cocoa wintab declarations
+ *
+ * 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
+ */
+
+#ifndef __WINE_MACDRV_COCOA_WINTAB_H
+#define __WINE_MACDRV_COCOA_WINTAB_H
+
+#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"
+
+/* Extents that shouldn't cause math problems */
+#define TABLET_WIDTH    0x40000000
+#define TABLET_HEIGHT   0x40000000
+/* Largest reportable pressure value */
+#define MAX_PRESSURE    0x7fff
+/* Wacom's demo app teaches developers to assume 1/10th degree.
+ * Best stick to that. */
+#define ANGLE_SCALE     10
+/* Largest reportable angle (360 is included, due to rounding) */
+#define MAX_ANGLE       (360 * ANGLE_SCALE)
+
+#define MAKE_ANGLE_DEG(degrees) ((degrees) * ANGLE_SCALE)
+#define MAKE_ANGLE_RAD(radians) ((radians) * MAKE_ANGLE_DEG(180) / M_PI)
+
+/* Cursor indices */
+#define MACDRV_CURSOR_PEN       0
+#define MACDRV_CURSOR_ERASER    1
+#define MACDRV_CURSOR_CURSOR    2
+
+/* 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..48878eaf078
--- /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/winemac.drv.spec b/dlls/winemac.drv/winemac.drv.spec
index b060d1cc2a6..13f5522242c 100644
--- a/dlls/winemac.drv/winemac.drv.spec
+++ b/dlls/winemac.drv/winemac.drv.spec
@@ -18,3 +18,9 @@
 @ stdcall ImeToAsciiEx(long long ptr ptr long long)
 @ stdcall ImeUnregisterWord(wstr long wstr)
 @ stdcall NotifyIME(long long long long)
+
+# WinTab32
+@ cdecl AttachEventQueueToTablet(long) macdrv_AttachEventQueueToTablet
+@ cdecl GetCurrentPacket(ptr) macdrv_GetCurrentPacket
+@ cdecl LoadTabletInfo(long) macdrv_LoadTabletInfo
+@ cdecl WTInfoW(long long ptr) macdrv_WTInfoW
diff --git a/dlls/winemac.drv/wintab.c b/dlls/winemac.drv/wintab.c
new file mode 100644
index 00000000000..1dcd14eb179
--- /dev/null
+++ b/dlls/winemac.drv/wintab.c
@@ -0,0 +1,631 @@
+/*
+ * MACDRV 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 "macdrv.h"
+#include "macdrv_cocoa.h"
+
+#include "windef.h"
+#include "winbase.h"
+#include "winuser.h"
+#include "wine/unicode.h"
+#include "wine/debug.h"
+
+#include "cocoa_wintab.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(wintab32);
+
+LOGCONTEXTW macdrv_tablet_ctx;
+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;
+static HWND* registered_hwnd = NULL;
+
+static const WCHAR CURSOR_NAME_PEN[] = {'P','r','e','s','s','u','r','e',' ','S','t','y','l','u','s',0};
+static const WCHAR CURSOR_NAME_ERASER[] = {'E','r','a','s','e','r',0};
+static const WCHAR CURSOR_NAME_CURSOR[] = {'P','u','c','k',0};
+
+static const UINT CURSOR_COUNT = 3;
+static const WCHAR* const CURSOR_NAMES[CURSOR_COUNT] = {
+    CURSOR_NAME_PEN,
+    CURSOR_NAME_ERASER,
+    CURSOR_NAME_CURSOR
+};
+
+static const UINT CURSOR_TYPES[CURSOR_COUNT] = {
+    CSR_TYPE_PEN,
+    CSR_TYPE_ERASER,
+    CSR_TYPE_MOUSE_2D
+};
+
+static const UINT CURSOR_CAPABILITIES[CURSOR_COUNT] = {
+    CRC_MULTIMODE | CRC_AGGREGATE,
+    CRC_MULTIMODE | CRC_AGGREGATE | CRC_INVERT,
+    CRC_AGGREGATE
+};
+
+static const BYTE BUTTON_COUNT = 16;
+
+BOOL CDECL macdrv_LoadTabletInfo(HWND tablet_hwnd){
+    static const WCHAR tablet_name[] = {'M','a','c','d','r','v',' ','T','a','b','l','e','t',' ','C','o','n','t','e','x','t',0};
+    static const WCHAR sys_name[] = {'M','a','c','d','r','v',' ','S','y','s','t','e','m',' ','C','o','n','t','e','x','t',0};
+    struct macdrv_win_data* tablet_win_data;
+
+    macdrv_tablet_hwnd = tablet_hwnd;
+    TRACE("%p\n", macdrv_tablet_hwnd);
+
+    tablet_win_data = get_win_data(macdrv_tablet_hwnd);
+    macdrv_tablet_window = tablet_win_data->cocoa_window;
+    release_win_data(tablet_win_data);
+
+    /* TODO?: It is possible to query the Wacom driver for real values, but it
+     * doesn't report all of these fields anyway and adds a lot of complexity.
+     * The quick'n'dirty solution is to make a fake tablet with the greatest
+     * possible capabilities. */
+    macdrv_tablet_ctx.lcOptions = CXO_SYSTEM;
+    macdrv_tablet_ctx.lcLocks = CXL_INSIZE | CXL_INASPECT | CXL_MARGIN
+        | CXL_SENSITIVITY | CXL_SYSOUT;
+    macdrv_tablet_ctx.lcStatus = CXS_ONTOP;
+    macdrv_tablet_ctx.lcMsgBase = WT_DEFBASE;
+    macdrv_tablet_ctx.lcDevice = 0;
+    macdrv_tablet_ctx.lcPktRate = 0; /* not supported */
+    macdrv_tablet_ctx.lcPktData = PK_CONTEXT | PK_STATUS | PK_TIME | PK_CHANGED
+        | PK_SERIAL_NUMBER | PK_CURSOR | PK_BUTTONS | PK_X | PK_Y | PK_Z
+        | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
+    macdrv_tablet_ctx.lcPktMode = 0; /* all absolute */
+    macdrv_tablet_ctx.lcMoveMask = PK_BUTTONS | PK_X | PK_Y | PK_Z
+        | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
+    macdrv_tablet_ctx.lcBtnDnMask = (1 << BUTTON_COUNT) - 1;
+    macdrv_tablet_ctx.lcBtnUpMask = (1 << BUTTON_COUNT) - 1;
+
+    macdrv_tablet_ctx.lcInOrgX = 0;
+    macdrv_tablet_ctx.lcInOrgY = 0;
+    /* NSEvent reports floating point coordinates. The actual precision is not
+     * known, so we convert them to really big values. */
+    macdrv_tablet_ctx.lcInExtX = TABLET_WIDTH;
+    macdrv_tablet_ctx.lcInExtY = TABLET_HEIGHT;
+    /* These values are correct for Intuos tablets. Wacom's Mac driver doesn't
+     * reveal them. */
+    macdrv_tablet_ctx.lcInOrgZ = -1023;
+    macdrv_tablet_ctx.lcInExtZ = 2047;
+
+    macdrv_tablet_ctx.lcOutOrgZ = macdrv_tablet_ctx.lcInOrgZ;
+    macdrv_tablet_ctx.lcOutExtZ = macdrv_tablet_ctx.lcInExtZ;
+    macdrv_tablet_ctx.lcSensX = 65536;
+    macdrv_tablet_ctx.lcSensY = 65536;
+    macdrv_tablet_ctx.lcSensZ = 65536;
+
+    macdrv_tablet_ctx.lcSysMode = 0; /* absolute */
+    macdrv_tablet_ctx.lcSysOrgX = GetSystemMetrics(SM_XVIRTUALSCREEN);
+    macdrv_tablet_ctx.lcSysOrgY = GetSystemMetrics(SM_YVIRTUALSCREEN);
+    macdrv_tablet_ctx.lcSysExtX = GetSystemMetrics(SM_CXVIRTUALSCREEN);
+    macdrv_tablet_ctx.lcSysExtY = GetSystemMetrics(SM_CYVIRTUALSCREEN);
+    macdrv_tablet_ctx.lcSysSensX = 65536;
+    macdrv_tablet_ctx.lcSysSensY = 65536;
+
+    /* "device" and "system" context are identical, except that "system"
+     * translates tablet coords to screen coords */
+    macdrv_system_ctx = macdrv_tablet_ctx;
+    strcpyW(macdrv_tablet_ctx.lcName, tablet_name);
+    strcpyW(macdrv_system_ctx.lcName, sys_name);
+
+    macdrv_tablet_ctx.lcOutOrgX = macdrv_tablet_ctx.lcInOrgX;
+    macdrv_tablet_ctx.lcOutOrgY = macdrv_tablet_ctx.lcInOrgY;
+    macdrv_tablet_ctx.lcOutExtX = macdrv_tablet_ctx.lcInExtX;
+    macdrv_tablet_ctx.lcOutExtY = macdrv_tablet_ctx.lcInExtY;
+
+    macdrv_system_ctx.lcOutOrgX = macdrv_system_ctx.lcSysOrgX;
+    macdrv_system_ctx.lcOutOrgY = macdrv_system_ctx.lcSysOrgY;
+    macdrv_system_ctx.lcOutExtX = macdrv_system_ctx.lcSysExtX;
+    macdrv_system_ctx.lcOutExtY = macdrv_system_ctx.lcSysExtY;
+
+    return TRUE;
+}
+
+UINT CDECL macdrv_WTInfoW(UINT wCategory, UINT nIndex, LPVOID lpOutput)
+{
+    TRACE("(%u, %u, %p)\n", wCategory, nIndex, lpOutput);
+
+    if (IS_WTI_EXTENSIONS_TYPE(wCategory))
+    {
+        FIXME("WTI_EXTENSIONS unimplemented");
+        return 0;
+    }
+    else if (IS_WTI_CURSORS_TYPE(wCategory))
+    {
+        INT cursor_idx = wCategory - WTI_CURSORS;
+        if (cursor_idx < 0)
+            cursor_idx += CURSOR_COUNT;
+        if (cursor_idx < 0 || cursor_idx >= CURSOR_COUNT)
+            return 0;
+        switch(nIndex){
+            case CSR_NAME:
+                return TABLET_CopyData(lpOutput, CURSOR_NAMES[cursor_idx], (strlenW(CURSOR_NAMES[cursor_idx]) + 1) * sizeof(WCHAR));
+            case CSR_ACTIVE:
+            {
+                BOOL active = 1;
+                return TABLET_CopyData(lpOutput, &active, sizeof(active));
+            }
+            case CSR_PKTDATA:
+                return TABLET_CopyData(lpOutput, &macdrv_tablet_ctx.lcPktData, sizeof(macdrv_tablet_ctx.lcPktData));
+            case CSR_BUTTONS:
+            case CSR_BUTTONBITS:
+                return TABLET_CopyData(lpOutput, &BUTTON_COUNT, sizeof(BUTTON_COUNT));
+            case CSR_BTNNAMES:
+            {
+                static const char* const fmt = "button %d";
+                static char buffer[10];
+                int out_size = 0;
+                PWCHAR out_char = lpOutput;
+                int button;
+
+                /* Each button name is \0-terminated, with an extra \0 at the
+                 * end. */
+                for (button = 0; button < BUTTON_COUNT; button++)
+                {
+                    int name_len;
+
+                    snprintf(buffer, sizeof(buffer), fmt, button);
+                    name_len = strlen(buffer);
+
+                    out_size += name_len + 1;
+                    if(out_char != NULL)
+                    {
+                        int c;
+                        for(c = 0; c <= name_len; c++)
+                            *(out_char++) = buffer[c];
+                    }
+                }
+                if(out_char != NULL)
+                    *out_char = 0;
+                return (out_size + 1) * sizeof(WCHAR);
+            }
+            case CSR_BUTTONMAP:
+            case CSR_SYSBTNMAP:
+            {
+                PBYTE out_byte = lpOutput;
+                int b;
+
+                if (out_byte != NULL)
+                {
+                    /* Always use 1:1 mapping */
+                    for (b = 0; b < BUTTON_COUNT; b++)
+                        out_byte[b] = b;
+                }
+                return BUTTON_COUNT * sizeof(BYTE);
+            }
+            case CSR_NPBUTTON:
+            case CSR_TPBUTTON:
+            {
+                BYTE button = 0;
+                return TABLET_CopyData(lpOutput, &button, sizeof(button));
+            }
+            case CSR_NPBTNMARKS:
+            case CSR_TPBTNMARKS:
+            {
+                static const UINT marks[] = {0, 1};
+                return TABLET_CopyData(lpOutput, &marks, sizeof(marks));
+            }
+            case CSR_NPRESPONSE:
+            case CSR_TPRESPONSE:
+            {
+                static const int POINT_COUNT = 256;
+                PUINT out_uint = lpOutput;
+                int p;
+
+                if (out_uint != NULL)
+                {
+                    /* Linear response */
+                    for (p = 0; p < POINT_COUNT; p++)
+                        out_uint[p] = MAX_PRESSURE * p / POINT_COUNT;
+                }
+                return POINT_COUNT * sizeof(UINT);
+            }
+            case CSR_PHYSID:
+            {
+                DWORD physid = 0xdeadbeef;
+                return TABLET_CopyData(lpOutput, &physid, sizeof(physid));
+            }
+            case CSR_MODE:
+            {
+                UINT mode = (UINT)cursor_idx;
+                if(!(CURSOR_CAPABILITIES[cursor_idx] & CRC_MULTIMODE))
+                    return 0;
+                return TABLET_CopyData(lpOutput, &mode, sizeof(mode));
+            }
+            case CSR_MINPKTDATA:
+            {
+                /* Should be safe to assume all tablets support these */
+                UINT pktdata = PK_CONTEXT | PK_STATUS | PK_TIME | PK_CHANGED
+                    | PK_SERIAL_NUMBER | PK_CURSOR | PK_BUTTONS | PK_X | PK_Y
+                    | PK_NORMAL_PRESSURE;
+                if(!(CURSOR_CAPABILITIES[cursor_idx] & CRC_AGGREGATE))
+                    return 0;
+                return TABLET_CopyData(lpOutput, &pktdata, sizeof(pktdata));
+            }
+            case CSR_MINBUTTONS:
+            {
+                UINT minbuttons = 1;
+                if(!(CURSOR_CAPABILITIES[cursor_idx] & CRC_AGGREGATE))
+                    return 0;
+                return TABLET_CopyData(lpOutput, &minbuttons, sizeof(minbuttons));
+            }
+            case CSR_CAPABILITIES:
+                return TABLET_CopyData(lpOutput, CURSOR_CAPABILITIES + cursor_idx, sizeof(*CURSOR_CAPABILITIES));
+            case CSR_TYPE:
+                return TABLET_CopyData(lpOutput, CURSOR_TYPES + cursor_idx, sizeof(*CURSOR_TYPES));
+            default:
+                FIXME("WTI_CURSORS+%d unhandled index %i\n", cursor_idx, nIndex);
+                return 0;
+        }
+    }
+
+    /* Non-indexed categories */
+    switch(wCategory)
+    {
+        case 0:
+            /* Largest buffer necessary for any category. This is currently
+             * WTI_CURSORS :: CSR_NPRESPONSE (or CSR_TPRESPONSE). */
+            return 256 * sizeof(UINT);
+        case WTI_INTERFACE:
+            switch (nIndex)
+            {
+                case IFC_WINTABID:
+                {
+                    static const WCHAR name[] = {'M','a','c','d','r','v',' ','W','i','n','t','a','b',' ','1','.','1',0};
+                    return TABLET_CopyData(lpOutput, name, sizeof(name));
+                }
+                case IFC_SPECVERSION:
+                {
+                    static const WORD version = (0x01) | (0x01 << 8);
+                    return TABLET_CopyData(lpOutput, &version, sizeof(version));
+                }
+                case IFC_IMPLVERSION:
+                {
+                    static const WORD version = (0x00) | (0x01 << 8);
+                    return TABLET_CopyData(lpOutput, &version, sizeof(version));
+                }
+                case IFC_NDEVICES:
+                {
+                    static const UINT num = 1;
+                    return TABLET_CopyData(lpOutput, &num, sizeof(num));
+                }
+                case IFC_NCURSORS:
+                {
+                    return TABLET_CopyData(lpOutput, &CURSOR_COUNT, sizeof(CURSOR_COUNT));
+                }
+                case IFC_NCONTEXTS:
+                {
+                    static const UINT num = 1;
+                    return TABLET_CopyData(lpOutput, &num, sizeof(num));
+                }
+                default:
+                    FIXME("WTI_INTERFACE unhandled index %i\n", nIndex);
+                    return 0;
+            }
+            break;
+        case WTI_DEFCONTEXT:
+        case WTI_DEFSYSCTX:
+        case WTI_DDCTXS:
+        case WTI_DDCTXS - 1:
+        case WTI_DSCTXS:
+        case WTI_DSCTXS - 1:
+        {
+            BOOL is_sys = (wCategory == WTI_DEFSYSCTX || wCategory == WTI_DSCTXS || wCategory == WTI_DSCTXS - 1);
+            LPLOGCONTEXTW active_ctx = (is_sys ? &macdrv_system_ctx : &macdrv_tablet_ctx);
+            switch (nIndex)
+            {
+                case 0:
+                    return TABLET_CopyData(lpOutput, active_ctx, sizeof(*active_ctx));
+                case CTX_NAME:
+                    return TABLET_CopyData(lpOutput, active_ctx->lcName, (strlenW(active_ctx->lcName) + 1) * sizeof(WCHAR));
+                case CTX_OPTIONS:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcOptions, sizeof(active_ctx->lcOptions));
+                case CTX_STATUS:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcStatus, sizeof(active_ctx->lcStatus));
+                case CTX_LOCKS:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcLocks, sizeof(active_ctx->lcLocks));
+                case CTX_MSGBASE:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcMsgBase, sizeof(active_ctx->lcMsgBase));
+                case CTX_DEVICE:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcDevice, sizeof(active_ctx->lcDevice));
+                case CTX_PKTRATE:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcPktRate, sizeof(active_ctx->lcPktRate));
+                case CTX_PKTDATA:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcPktData, sizeof(active_ctx->lcPktData));
+                case CTX_PKTMODE:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcPktMode, sizeof(active_ctx->lcPktMode));
+                case CTX_MOVEMASK:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcMoveMask, sizeof(active_ctx->lcMoveMask));
+                case CTX_BTNDNMASK:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcBtnDnMask, sizeof(active_ctx->lcBtnDnMask));
+                case CTX_BTNUPMASK:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcBtnUpMask, sizeof(active_ctx->lcBtnUpMask));
+                case CTX_INORGX:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcInOrgX, sizeof(active_ctx->lcInOrgX));
+                case CTX_INORGY:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcInOrgY, sizeof(active_ctx->lcInOrgY));
+                case CTX_INORGZ:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcInOrgZ, sizeof(active_ctx->lcInOrgZ));
+                case CTX_INEXTX:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcInExtX, sizeof(active_ctx->lcInExtX));
+                case CTX_INEXTY:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcInExtY, sizeof(active_ctx->lcInExtY));
+                case CTX_INEXTZ:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcInExtZ, sizeof(active_ctx->lcInExtZ));
+                case CTX_OUTORGX:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcOutOrgX, sizeof(active_ctx->lcOutOrgX));
+                case CTX_OUTORGY:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcOutOrgY, sizeof(active_ctx->lcOutOrgY));
+                case CTX_OUTORGZ:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcOutOrgZ, sizeof(active_ctx->lcOutOrgZ));
+               case CTX_OUTEXTX:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcOutExtX, sizeof(active_ctx->lcOutExtX));
+                case CTX_OUTEXTY:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcOutExtY, sizeof(active_ctx->lcOutExtY));
+                case CTX_OUTEXTZ:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcOutExtZ, sizeof(active_ctx->lcOutExtZ));
+                case CTX_SENSX:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcSensX, sizeof(active_ctx->lcSensX));
+                case CTX_SENSY:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcSensY, sizeof(active_ctx->lcSensY));
+                case CTX_SENSZ:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcSensZ, sizeof(active_ctx->lcSensZ));
+                case CTX_SYSMODE:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcSysMode, sizeof(active_ctx->lcSysMode));
+                case CTX_SYSORGX:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcSysOrgX, sizeof(active_ctx->lcSysOrgX));
+                case CTX_SYSORGY:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcSysOrgY, sizeof(active_ctx->lcSysOrgY));
+                case CTX_SYSEXTX:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcSysExtX, sizeof(active_ctx->lcSysExtX));
+                case CTX_SYSEXTY:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcSysExtY, sizeof(active_ctx->lcSysExtY));
+                case CTX_SYSSENSX:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcSysSensX, sizeof(active_ctx->lcSysSensX));
+                case CTX_SYSSENSY:
+                    return TABLET_CopyData(lpOutput, &active_ctx->lcSysSensY, sizeof(active_ctx->lcSysSensY));
+                default:
+                    FIXME("WTI_DEFSYSCTX unhandled index %i\n", nIndex);
+                    return 0;
+            }
+        }
+        case WTI_DEVICES:
+        case WTI_DEVICES - 1:
+            switch (nIndex)
+            {
+                case DVC_NAME:
+                {
+                    static const WCHAR name[] = {'M','a','c','d','r','v',' ','T','a','b','l','e','t',0};
+                    return TABLET_CopyData(lpOutput, name, sizeof(name));
+                }
+                case DVC_HARDWARE:
+                {
+                    static const UINT hardware = HWC_HARDPROX;
+                    return TABLET_CopyData(lpOutput, &hardware, sizeof(hardware));
+                }
+                case DVC_NCSRTYPES:
+                {
+                    static const UINT num = 0;
+                    return TABLET_CopyData(lpOutput, &num, sizeof(num));
+                }
+                case DVC_FIRSTCSR:
+                {
+                    static const UINT first = 0;
+                    return TABLET_CopyData(lpOutput, &first, sizeof(first));
+                }
+                case DVC_PKTRATE:
+                    return TABLET_CopyData(lpOutput, &macdrv_tablet_ctx.lcPktRate, sizeof(macdrv_tablet_ctx.lcPktRate));
+                case DVC_PKTDATA:
+                    return TABLET_CopyData(lpOutput, &macdrv_tablet_ctx.lcPktData, sizeof(macdrv_tablet_ctx.lcPktData));
+                case DVC_PKTMODE:
+                    return TABLET_CopyData(lpOutput, &macdrv_tablet_ctx.lcPktMode, sizeof(macdrv_tablet_ctx.lcPktMode));
+                case DVC_CSRDATA:
+                {
+                    static const WTPKT data = 0;
+                    return TABLET_CopyData(lpOutput, &data, sizeof(data));
+                }
+                case DVC_XMARGIN:
+                case DVC_YMARGIN:
+                case DVC_ZMARGIN:
+                {
+                    static const INT margin = 0;
+                    return TABLET_CopyData(lpOutput, &margin, sizeof(margin));
+                }
+                case DVC_X:
+                case DVC_Y:
+                case DVC_Z:
+                {
+                    UINT org = (nIndex == DVC_X ? macdrv_tablet_ctx.lcInOrgX : nIndex == DVC_Y ? macdrv_tablet_ctx.lcInOrgY : macdrv_tablet_ctx.lcInOrgZ);
+                    UINT ext = (nIndex == DVC_X ? macdrv_tablet_ctx.lcInExtX : nIndex == DVC_Y ? macdrv_tablet_ctx.lcInExtY : macdrv_tablet_ctx.lcInExtZ);
+                    AXIS ax = {
+                        .axMin = org,
+                        .axMax = org + ext - 1,
+                        .axUnits = TU_NONE,
+                        .axResolution = 1
+                    };
+                    return TABLET_CopyData(lpOutput, &ax, sizeof(ax));
+                }
+                case DVC_NPRESSURE:
+                {
+                    static const AXIS ax = {
+                        .axMin = 0,
+                        .axMax = MAX_PRESSURE,
+                        .axUnits = TU_NONE,
+                        .axResolution = 1
+                    };
+                    return TABLET_CopyData(lpOutput, &ax, sizeof(ax));
+                }
+                case DVC_TPRESSURE:
+                {
+                    static const AXIS ax = {
+                        .axMin = -MAX_PRESSURE,
+                        .axMax = MAX_PRESSURE,
+                        .axUnits = TU_NONE,
+                        .axResolution = 1
+                    };
+                    return TABLET_CopyData(lpOutput, &ax, sizeof(ax));
+                }
+                case DVC_ORIENTATION:
+                {
+                    static const AXIS axes[] = {
+                        /*.orAzimuth = */ {
+                            .axMin = 0,
+                            .axMax = MAX_ANGLE,
+                            .axUnits = TU_CIRCLE,
+                            .axResolution = CASTFIX32(MAX_ANGLE)
+                        },
+                        /*.orAltitude = */ {
+                            .axMin = -MAKE_ANGLE_DEG(90),
+                            .axMax = MAKE_ANGLE_DEG(90),
+                            .axUnits = TU_CIRCLE,
+                            .axResolution = CASTFIX32(MAX_ANGLE)
+                        },
+                        /*.orTwist = */ {
+                            .axMin = 0,
+                            .axMax = MAX_ANGLE,
+                            .axUnits = TU_CIRCLE,
+                            .axResolution = CASTFIX32(MAX_ANGLE)
+                        }
+                    };
+                    return TABLET_CopyData(lpOutput, &axes, sizeof(axes));
+                }
+                case DVC_ROTATION:
+                    /* Unsupported */
+                    return 0;
+                case DVC_PNPID:
+                {
+                    static const WCHAR pnpId[] = {'n','o','n','-','p','l','u','g','-','n','-','p','l','a','y',0};
+                    return TABLET_CopyData(lpOutput, pnpId, sizeof(pnpId));
+                }
+                default:
+                    FIXME("WTI_DEVICES unhandled index %i\n", nIndex);
+                    return 0;
+            }
+            break;
+        default:
+            if (IS_WTI_DEVICES_TYPE(wCategory) || IS_WTI_DDCTXS_TYPE(wCategory)
+                    || IS_WTI_DSCTXS_TYPE(wCategory))
+                /* User error, there's only one device */
+                return 0;
+            FIXME("Unhandled Category %i\n", wCategory);
+    }
+    return 0;
+}
+
+int CDECL macdrv_AttachEventQueueToTablet(HWND hOwner)
+{
+    /* Tablet events come from a global monitor rather than relying on
+     * individual views. Maintain a list of HWNDs interested in these events so
+     * we can deliver to them later. */
+    if (registered_hwnd_count >= registered_hwnd_cap)
+    {
+        registered_hwnd_cap = registered_hwnd_cap * 2 + 1;
+        registered_hwnd = realloc(registered_hwnd, registered_hwnd_cap * sizeof(*registered_hwnd));
+    }
+
+    registered_hwnd[registered_hwnd_count++] = 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){
+    *packet = current_packet;
+    return 1;
+}
diff --git a/dlls/winex11.drv/wintab.c b/dlls/winex11.drv/wintab.c
index 331601c3325..f8b0cb1cac9 100644
--- a/dlls/winex11.drv/wintab.c
+++ b/dlls/winex11.drv/wintab.c
@@ -203,54 +203,6 @@ typedef struct tagWTI_DEVICES_INFO
 }   WTI_DEVICES_INFO, *LPWTI_DEVICES_INFO;
 
 
-/***********************************************************************
- * WACOM WINTAB EXTENSIONS TO SUPPORT CSR_TYPE
- *   In Wintab 1.2, a CSR_TYPE feature was added.  This adds the
- *   ability to return a type of cursor on a tablet.
- *   Unfortunately, we cannot get the cursor type directly from X,
- *   and it is not specified directly anywhere.  So we virtualize
- *   the type here.  (This is unfortunate, the kernel module has
- *   the exact type, but we have no way of getting that module to
- *   pass us that type).
- *
- *   Reference linuxwacom driver project wcmCommon.c function
- *   idtotype for a much larger list of CSR_TYPE.
- *
- *   http://linuxwacom.cvs.sourceforge.net/linuxwacom/linuxwacom-prod/src/xdrv/wcmCommon.c?view=markup
- *
- *   The WTI_CURSORS_INFO.TYPE data is supposed to be used like this:
- *   (cursor.TYPE & 0x0F06) == target_cursor_type
- *   Reference: Section Unique ID
- *   http://www.wacomeng.com/devsupport/ibmpc/gddevpc.html
- */
-#define CSR_TYPE_PEN        0x822
-#define CSR_TYPE_ERASER     0x82a
-#define CSR_TYPE_MOUSE_2D   0x007
-#define CSR_TYPE_MOUSE_4D   0x094
-/* CSR_TYPE_OTHER is a special value! assumed no real world significance
- * if a stylus type or eraser type eventually have this value
- * it'll be a bug.  As of 2008 05 21 we can be sure because
- * linux wacom lists all the known values and this isn't one of them */
-#define CSR_TYPE_OTHER      0x000
-
-typedef struct tagWTPACKET {
-        HCTX pkContext;
-        UINT pkStatus;
-        LONG pkTime;
-        WTPKT pkChanged;
-        UINT pkSerialNumber;
-        UINT pkCursor;
-        DWORD pkButtons;
-        DWORD pkX;
-        DWORD pkY;
-        DWORD pkZ;
-        UINT pkNormalPressure;
-        UINT pkTangentPressure;
-        ORIENTATION pkOrientation;
-        ROTATION pkRotation; /* 1.1 */
-} WTPACKET, *LPWTPACKET;
-
-
 #ifdef SONAME_LIBXI
 
 #include <X11/Xlib.h>
@@ -265,9 +217,9 @@ static int           proximity_in_type;
 static int           proximity_out_type;
 
 static HWND          hwndTabletDefault;
-static WTPACKET      gMsgPacket;
+static PACKET        gMsgPacket;
 static DWORD         gSerial;
-static WTPACKET      last_packet;
+static PACKET        last_packet;
 
 /* Reference: http://www.wacomeng.com/devsupport/ibmpc/gddevpc.html
  *
@@ -845,7 +797,7 @@ static int cursor_from_device(DWORD deviceid, LPWTI_CURSORS_INFO *cursorp)
     return -1;
 }
 
-static DWORD get_changed_state( WTPACKET *pkt)
+static DWORD get_changed_state( PACKET *pkt)
 {
     DWORD change = 0;
 
@@ -879,6 +831,11 @@ static DWORD get_changed_state( WTPACKET *pkt)
     return change;
 }
 
+static inline DWORD flip_y( DWORD y )
+{
+    return gSysContext.lcInExtY - y;
+}
+
 static BOOL motion_event( HWND hwnd, XEvent *event )
 {
     XDeviceMotionEvent *motion = (XDeviceMotionEvent *)event;
@@ -887,7 +844,7 @@ static BOOL motion_event( HWND hwnd, XEvent *event )
     if (curnum < 0)
         return FALSE;
 
-    memset(&gMsgPacket,0,sizeof(WTPACKET));
+    memset(&gMsgPacket,0,sizeof(PACKET));
 
     TRACE("Received tablet motion event (%p); device id %d, cursor num %d\n",hwnd, (int) motion->deviceid, curnum);
 
@@ -897,7 +854,7 @@ static BOOL motion_event( HWND hwnd, XEvent *event )
     gMsgPacket.pkSerialNumber = gSerial++;
     gMsgPacket.pkCursor = curnum;
     gMsgPacket.pkX = motion->axis_data[0];
-    gMsgPacket.pkY = motion->axis_data[1];
+    gMsgPacket.pkY = flip_y(motion->axis_data[1]);
     gMsgPacket.pkOrientation.orAzimuth = figure_deg(motion->axis_data[3],motion->axis_data[4]);
     gMsgPacket.pkOrientation.orAltitude = ((1000 - 15 * max
                                             (abs(motion->axis_data[3]),
@@ -919,7 +876,7 @@ static BOOL button_event( HWND hwnd, XEvent *event )
     if (curnum < 0)
         return FALSE;
 
-    memset(&gMsgPacket,0,sizeof(WTPACKET));
+    memset(&gMsgPacket,0,sizeof(PACKET));
 
     TRACE("Received tablet button %s event\n", (event->type == button_press_type)?"press":"release");
 
@@ -931,7 +888,7 @@ static BOOL button_event( HWND hwnd, XEvent *event )
     gMsgPacket.pkCursor = curnum;
     if (button->axes_count > 0) {
         gMsgPacket.pkX = button->axis_data[0];
-        gMsgPacket.pkY = button->axis_data[1];
+        gMsgPacket.pkY = flip_y(button->axis_data[1]);
         gMsgPacket.pkOrientation.orAzimuth = figure_deg(button->axis_data[3],button->axis_data[4]);
         gMsgPacket.pkOrientation.orAltitude = ((1000 - 15 * max(abs(button->axis_data[3]),
                                                                 abs(button->axis_data[4])))
@@ -971,7 +928,7 @@ static BOOL proximity_event( HWND hwnd, XEvent *event )
     if (curnum < 0)
         return FALSE;
 
-    memset(&gMsgPacket,0,sizeof(WTPACKET));
+    memset(&gMsgPacket,0,sizeof(PACKET));
 
     /* Set cursor to inverted if cursor is the eraser */
     gMsgPacket.pkStatus = (cursor->TYPE == CSR_TYPE_ERASER ? TPS_INVERT:0);
@@ -980,7 +937,7 @@ static BOOL proximity_event( HWND hwnd, XEvent *event )
     gMsgPacket.pkSerialNumber = gSerial++;
     gMsgPacket.pkCursor = curnum;
     gMsgPacket.pkX = proximity->axis_data[0];
-    gMsgPacket.pkY = proximity->axis_data[1];
+    gMsgPacket.pkY = flip_y(proximity->axis_data[1]);
     gMsgPacket.pkOrientation.orAzimuth = figure_deg(proximity->axis_data[3],proximity->axis_data[4]);
     gMsgPacket.pkOrientation.orAltitude = ((1000 - 15 * max(abs(proximity->axis_data[3]),
                                                             abs(proximity->axis_data[4])))
@@ -1095,24 +1052,13 @@ int CDECL X11DRV_AttachEventQueueToTablet(HWND hOwner)
 /***********************************************************************
  *		X11DRV_GetCurrentPacket (X11DRV.@)
  */
-int CDECL X11DRV_GetCurrentPacket(LPWTPACKET packet)
+int CDECL X11DRV_GetCurrentPacket(LPPACKET packet)
 {
     *packet = gMsgPacket;
     return 1;
 }
 
 
-static inline int CopyTabletData(LPVOID target, LPCVOID src, INT size)
-{
-    /*
-     * It is valid to call CopyTabletData with NULL.
-     * This handles the WTInfo() case where lpOutput is null.
-     */
-    if(target != NULL)
-        memcpy(target,src,size);
-    return(size);
-}
-
 /***********************************************************************
  *		X11DRV_WTInfoW (X11DRV.@)
  */
@@ -1123,7 +1069,7 @@ UINT CDECL X11DRV_WTInfoW(UINT wCategory, UINT nIndex, LPVOID lpOutput)
      * lpOutput == NULL signifies the user only wishes
      * to find the size of the data.
      * NOTE:
-     * From now on use CopyTabletData to fill lpOutput. memcpy will break
+     * From now on use TABLET_CopyData to fill lpOutput. memcpy will break
      * the code.
      */
     int rc = 0;
@@ -1151,24 +1097,24 @@ UINT CDECL X11DRV_WTInfoW(UINT wCategory, UINT nIndex, LPVOID lpOutput)
                 case IFC_WINTABID:
                 {
                     static const WCHAR driver[] = {'W','i','n','e',' ','W','i','n','t','a','b',' ','1','.','1',0};
-                    rc = CopyTabletData(lpOutput, driver, (strlenW(driver) + 1) * sizeof(WCHAR));
+                    rc = TABLET_CopyData(lpOutput, driver, (strlenW(driver) + 1) * sizeof(WCHAR));
                     break;
                 }
                 case IFC_SPECVERSION:
                     version = (0x01) | (0x01 << 8);
-                    rc = CopyTabletData(lpOutput, &version,sizeof(WORD));
+                    rc = TABLET_CopyData(lpOutput, &version,sizeof(WORD));
                     break;
                 case IFC_IMPLVERSION:
                     version = (0x00) | (0x01 << 8);
-                    rc = CopyTabletData(lpOutput, &version,sizeof(WORD));
+                    rc = TABLET_CopyData(lpOutput, &version,sizeof(WORD));
                     break;
                 case IFC_NDEVICES:
                     num = 1;
-                    rc = CopyTabletData(lpOutput, &num,sizeof(num));
+                    rc = TABLET_CopyData(lpOutput, &num,sizeof(num));
                     break;
                 case IFC_NCURSORS:
                     num = gNumCursors;
-                    rc = CopyTabletData(lpOutput, &num,sizeof(num));
+                    rc = TABLET_CopyData(lpOutput, &num,sizeof(num));
                     break;
                 default:
                     FIXME("WTI_INTERFACE unhandled index %i\n",nIndex);
@@ -1185,143 +1131,143 @@ UINT CDECL X11DRV_WTInfoW(UINT wCategory, UINT nIndex, LPVOID lpOutput)
                     if (0 == gNumCursors)
                         rc = 0;
                     else
-                        rc = CopyTabletData(lpOutput, &gSysContext,
+                        rc = TABLET_CopyData(lpOutput, &gSysContext,
                                 sizeof(LOGCONTEXTW));
                     break;
                 case CTX_NAME:
-                    rc = CopyTabletData(lpOutput, gSysContext.lcName,
+                    rc = TABLET_CopyData(lpOutput, gSysContext.lcName,
                          (strlenW(gSysContext.lcName)+1) * sizeof(WCHAR));
                     break;
                 case CTX_OPTIONS:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcOptions,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcOptions,
                                         sizeof(UINT));
                     break;
                 case CTX_STATUS:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcStatus,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcStatus,
                                         sizeof(UINT));
                     break;
                 case CTX_LOCKS:
-                    rc= CopyTabletData (lpOutput, &gSysContext.lcLocks,
+                    rc= TABLET_CopyData (lpOutput, &gSysContext.lcLocks,
                                         sizeof(UINT));
                     break;
                 case CTX_MSGBASE:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcMsgBase,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcMsgBase,
                                         sizeof(UINT));
                     break;
                 case CTX_DEVICE:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcDevice,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcDevice,
                                         sizeof(UINT));
                     break;
                 case CTX_PKTRATE:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcPktRate,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcPktRate,
                                         sizeof(UINT));
                     break;
                 case CTX_PKTDATA:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcPktData,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcPktData,
                                         sizeof(WTPKT));
                     break;
                 case CTX_PKTMODE:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcPktMode,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcPktMode,
                                         sizeof(WTPKT));
                     break;
                 case CTX_MOVEMASK:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcMoveMask,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcMoveMask,
                                         sizeof(WTPKT));
                     break;
                 case CTX_BTNDNMASK:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcBtnDnMask,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcBtnDnMask,
                                         sizeof(DWORD));
                     break;
                 case CTX_BTNUPMASK:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcBtnUpMask,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcBtnUpMask,
                                         sizeof(DWORD));
                     break;
                 case CTX_INORGX:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcInOrgX,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcInOrgX,
                                         sizeof(LONG));
                     break;
                 case CTX_INORGY:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcInOrgY,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcInOrgY,
                                         sizeof(LONG));
                     break;
                 case CTX_INORGZ:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcInOrgZ,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcInOrgZ,
                                         sizeof(LONG));
                     break;
                 case CTX_INEXTX:
-                    rc = CopyTabletData(lpOutput, &gSysContext.lcInExtX,
+                    rc = TABLET_CopyData(lpOutput, &gSysContext.lcInExtX,
                                         sizeof(LONG));
                     break;
                 case CTX_INEXTY:
-                     rc = CopyTabletData(lpOutput, &gSysContext.lcInExtY,
+                     rc = TABLET_CopyData(lpOutput, &gSysContext.lcInExtY,
                                         sizeof(LONG));
                     break;
                 case CTX_INEXTZ:
-                     rc = CopyTabletData(lpOutput, &gSysContext.lcInExtZ,
+                     rc = TABLET_CopyData(lpOutput, &gSysContext.lcInExtZ,
                                         sizeof(LONG));
                     break;
                 case CTX_OUTORGX:
-                     rc = CopyTabletData(lpOutput, &gSysContext.lcOutOrgX,
+                     rc = TABLET_CopyData(lpOutput, &gSysContext.lcOutOrgX,
                                         sizeof(LONG));
                     break;
                 case CTX_OUTORGY:
-                      rc = CopyTabletData(lpOutput, &gSysContext.lcOutOrgY,
+                      rc = TABLET_CopyData(lpOutput, &gSysContext.lcOutOrgY,
                                         sizeof(LONG));
                     break;
                 case CTX_OUTORGZ:
-                       rc = CopyTabletData(lpOutput, &gSysContext.lcOutOrgZ,
+                       rc = TABLET_CopyData(lpOutput, &gSysContext.lcOutOrgZ,
                                         sizeof(LONG));
                     break;
                case CTX_OUTEXTX:
-                      rc = CopyTabletData(lpOutput, &gSysContext.lcOutExtX,
+                      rc = TABLET_CopyData(lpOutput, &gSysContext.lcOutExtX,
                                         sizeof(LONG));
                     break;
                 case CTX_OUTEXTY:
-                       rc = CopyTabletData(lpOutput, &gSysContext.lcOutExtY,
+                       rc = TABLET_CopyData(lpOutput, &gSysContext.lcOutExtY,
                                         sizeof(LONG));
                     break;
                 case CTX_OUTEXTZ:
-                       rc = CopyTabletData(lpOutput, &gSysContext.lcOutExtZ,
+                       rc = TABLET_CopyData(lpOutput, &gSysContext.lcOutExtZ,
                                         sizeof(LONG));
                     break;
                 case CTX_SENSX:
-                        rc = CopyTabletData(lpOutput, &gSysContext.lcSensX,
+                        rc = TABLET_CopyData(lpOutput, &gSysContext.lcSensX,
                                         sizeof(LONG));
                     break;
                 case CTX_SENSY:
-                        rc = CopyTabletData(lpOutput, &gSysContext.lcSensY,
+                        rc = TABLET_CopyData(lpOutput, &gSysContext.lcSensY,
                                         sizeof(LONG));
                     break;
                 case CTX_SENSZ:
-                        rc = CopyTabletData(lpOutput, &gSysContext.lcSensZ,
+                        rc = TABLET_CopyData(lpOutput, &gSysContext.lcSensZ,
                                         sizeof(LONG));
                     break;
                 case CTX_SYSMODE:
-                        rc = CopyTabletData(lpOutput, &gSysContext.lcSysMode,
+                        rc = TABLET_CopyData(lpOutput, &gSysContext.lcSysMode,
                                         sizeof(LONG));
                     break;
                 case CTX_SYSORGX:
-                        rc = CopyTabletData(lpOutput, &gSysContext.lcSysOrgX,
+                        rc = TABLET_CopyData(lpOutput, &gSysContext.lcSysOrgX,
                                         sizeof(LONG));
                     break;
                 case CTX_SYSORGY:
-                        rc = CopyTabletData(lpOutput, &gSysContext.lcSysOrgY,
+                        rc = TABLET_CopyData(lpOutput, &gSysContext.lcSysOrgY,
                                         sizeof(LONG));
                     break;
                 case CTX_SYSEXTX:
-                        rc = CopyTabletData(lpOutput, &gSysContext.lcSysExtX,
+                        rc = TABLET_CopyData(lpOutput, &gSysContext.lcSysExtX,
                                         sizeof(LONG));
                     break;
                 case CTX_SYSEXTY:
-                        rc = CopyTabletData(lpOutput, &gSysContext.lcSysExtY,
+                        rc = TABLET_CopyData(lpOutput, &gSysContext.lcSysExtY,
                                         sizeof(LONG));
                     break;
                 case CTX_SYSSENSX:
-                        rc = CopyTabletData(lpOutput, &gSysContext.lcSysSensX,
+                        rc = TABLET_CopyData(lpOutput, &gSysContext.lcSysSensX,
                                         sizeof(LONG));
                     break;
                 case CTX_SYSSENSY:
-                       rc = CopyTabletData(lpOutput, &gSysContext.lcSysSensY,
+                       rc = TABLET_CopyData(lpOutput, &gSysContext.lcSysSensY,
                                         sizeof(LONG));
                     break;
                 default:
@@ -1353,44 +1299,44 @@ UINT CDECL X11DRV_WTInfoW(UINT wCategory, UINT nIndex, LPVOID lpOutput)
                 switch (nIndex)
                 {
                     case CSR_NAME:
-                        rc = CopyTabletData(lpOutput, tgtcursor->NAME,
+                        rc = TABLET_CopyData(lpOutput, tgtcursor->NAME,
                                             (strlenW(tgtcursor->NAME)+1) * sizeof(WCHAR));
                         break;
                     case CSR_ACTIVE:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->ACTIVE,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->ACTIVE,
                                             sizeof(BOOL));
                         break;
                     case CSR_PKTDATA:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->PKTDATA,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->PKTDATA,
                                             sizeof(WTPKT));
                         break;
                     case CSR_BUTTONS:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->BUTTONS,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->BUTTONS,
                                             sizeof(BYTE));
                         break;
                     case CSR_BUTTONBITS:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->BUTTONBITS,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->BUTTONBITS,
                                             sizeof(BYTE));
                         break;
                     case CSR_BTNNAMES:
                         FIXME("Button Names not returned correctly\n");
-                        rc = CopyTabletData(lpOutput,&tgtcursor->BTNNAMES,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->BTNNAMES,
                                             tgtcursor->cchBTNNAMES*sizeof(WCHAR));
                         break;
                     case CSR_BUTTONMAP:
-                        rc = CopyTabletData(lpOutput,tgtcursor->BUTTONMAP,
+                        rc = TABLET_CopyData(lpOutput,tgtcursor->BUTTONMAP,
                                             sizeof(BYTE)*32);
                         break;
                     case CSR_SYSBTNMAP:
-                        rc = CopyTabletData(lpOutput,tgtcursor->SYSBTNMAP,
+                        rc = TABLET_CopyData(lpOutput,tgtcursor->SYSBTNMAP,
                                             sizeof(BYTE)*32);
                         break;
                     case CSR_NPBTNMARKS:
-                        rc = CopyTabletData(lpOutput,tgtcursor->NPBTNMARKS,
+                        rc = TABLET_CopyData(lpOutput,tgtcursor->NPBTNMARKS,
                                             sizeof(UINT)*2);
                         break;
                     case CSR_NPBUTTON:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->NPBUTTON,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->NPBUTTON,
                                             sizeof(BYTE));
                         break;
                     case CSR_NPRESPONSE:
@@ -1398,11 +1344,11 @@ UINT CDECL X11DRV_WTInfoW(UINT wCategory, UINT nIndex, LPVOID lpOutput)
                         rc = 0;
                         break;
                     case CSR_TPBUTTON:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->TPBUTTON,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->TPBUTTON,
                                             sizeof(BYTE));
                         break;
                     case CSR_TPBTNMARKS:
-                        rc = CopyTabletData(lpOutput,tgtcursor->TPBTNMARKS,
+                        rc = TABLET_CopyData(lpOutput,tgtcursor->TPBTNMARKS,
                                             sizeof(UINT)*2);
                         break;
                     case CSR_TPRESPONSE:
@@ -1413,26 +1359,26 @@ UINT CDECL X11DRV_WTInfoW(UINT wCategory, UINT nIndex, LPVOID lpOutput)
                     {
                         DWORD id;
                         id = tgtcursor->PHYSID;
-                        rc = CopyTabletData(lpOutput,&id,sizeof(DWORD));
+                        rc = TABLET_CopyData(lpOutput,&id,sizeof(DWORD));
                     }
                         break;
                     case CSR_MODE:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->MODE,sizeof(UINT));
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->MODE,sizeof(UINT));
                         break;
                     case CSR_MINPKTDATA:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->MINPKTDATA,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->MINPKTDATA,
                             sizeof(UINT));
                         break;
                     case CSR_MINBUTTONS:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->MINBUTTONS,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->MINBUTTONS,
                             sizeof(UINT));
                         break;
                     case CSR_CAPABILITIES:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->CAPABILITIES,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->CAPABILITIES,
                             sizeof(UINT));
                         break;
                     case CSR_TYPE:
-                        rc = CopyTabletData(lpOutput,&tgtcursor->TYPE,
+                        rc = TABLET_CopyData(lpOutput,&tgtcursor->TYPE,
                             sizeof(UINT));
                         break;
                     default:
@@ -1445,91 +1391,91 @@ UINT CDECL X11DRV_WTInfoW(UINT wCategory, UINT nIndex, LPVOID lpOutput)
             switch (nIndex)
             {
                 case DVC_NAME:
-                    rc = CopyTabletData(lpOutput,gSysDevice.NAME,
+                    rc = TABLET_CopyData(lpOutput,gSysDevice.NAME,
                                         (strlenW(gSysDevice.NAME)+1) * sizeof(WCHAR));
                     break;
                 case DVC_HARDWARE:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.HARDWARE,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.HARDWARE,
                                         sizeof(UINT));
                     break;
                 case DVC_NCSRTYPES:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.NCSRTYPES,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.NCSRTYPES,
                                         sizeof(UINT));
                     break;
                 case DVC_FIRSTCSR:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.FIRSTCSR,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.FIRSTCSR,
                                         sizeof(UINT));
                     break;
                 case DVC_PKTRATE:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.PKTRATE,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.PKTRATE,
                                         sizeof(UINT));
                     break;
                 case DVC_PKTDATA:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.PKTDATA,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.PKTDATA,
                                         sizeof(WTPKT));
                     break;
                 case DVC_PKTMODE:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.PKTMODE,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.PKTMODE,
                                         sizeof(WTPKT));
                     break;
                 case DVC_CSRDATA:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.CSRDATA,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.CSRDATA,
                                         sizeof(WTPKT));
                     break;
                 case DVC_XMARGIN:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.XMARGIN,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.XMARGIN,
                                         sizeof(INT));
                     break;
                 case DVC_YMARGIN:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.YMARGIN,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.YMARGIN,
                                         sizeof(INT));
                     break;
                 case DVC_ZMARGIN:
                     rc = 0; /* unsupported */
                     /*
-                    rc = CopyTabletData(lpOutput,&gSysDevice.ZMARGIN,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.ZMARGIN,
                                         sizeof(INT));
                     */
                     break;
                 case DVC_X:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.X,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.X,
                                         sizeof(AXIS));
                     break;
                 case DVC_Y:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.Y,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.Y,
                                         sizeof(AXIS));
                     break;
                 case DVC_Z:
                     rc = 0; /* unsupported */
                     /*
-                    rc = CopyTabletData(lpOutput,&gSysDevice.Z,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.Z,
                                         sizeof(AXIS));
                     */
                     break;
                 case DVC_NPRESSURE:
-                    rc = CopyTabletData(lpOutput,&gSysDevice.NPRESSURE,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.NPRESSURE,
                                         sizeof(AXIS));
                     break;
                 case DVC_TPRESSURE:
                     rc = 0; /* unsupported */
                     /*
-                    rc = CopyTabletData(lpOutput,&gSysDevice.TPRESSURE,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.TPRESSURE,
                                         sizeof(AXIS));
                     */
                     break;
                 case DVC_ORIENTATION:
-                    rc = CopyTabletData(lpOutput,gSysDevice.ORIENTATION,
+                    rc = TABLET_CopyData(lpOutput,gSysDevice.ORIENTATION,
                                         sizeof(AXIS)*3);
                     break;
                 case DVC_ROTATION:
                     rc = 0; /* unsupported */
                     /*
-                    rc = CopyTabletData(lpOutput,&gSysDevice.ROTATION,
+                    rc = TABLET_CopyData(lpOutput,&gSysDevice.ROTATION,
                                         sizeof(AXIS)*3);
                     */
                     break;
                 case DVC_PNPID:
-                    rc = CopyTabletData(lpOutput,gSysDevice.PNPID,
+                    rc = TABLET_CopyData(lpOutput,gSysDevice.PNPID,
                                         (strlenW(gSysDevice.PNPID)+1)*sizeof(WCHAR));
                     break;
                 default:
@@ -1556,7 +1502,7 @@ int CDECL X11DRV_AttachEventQueueToTablet(HWND hOwner)
 /***********************************************************************
  *		GetCurrentPacket (X11DRV.@)
  */
-int CDECL X11DRV_GetCurrentPacket(LPWTPACKET packet)
+int CDECL X11DRV_GetCurrentPacket(LPPACKET packet)
 {
     return 0;
 }
diff --git a/dlls/wintab32/context.c b/dlls/wintab32/context.c
index 58ba6b49074..8c7953a8e68 100644
--- a/dlls/wintab32/context.c
+++ b/dlls/wintab32/context.c
@@ -38,7 +38,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(wintab32);
 
 /*
  * Documentation found at
- * http://www.csl.sony.co.jp/projects/ar/restricted/wintabl.html
+ * https://developer-docs.wacom.com/intuos-cintiq-business-tablets/docs/wintab-overview
  */
 
 static LPOPENCONTEXT gOpenContexts;
@@ -62,7 +62,8 @@ static void LOGCONTEXTWtoA(const LOGCONTEXTW *in, LOGCONTEXTA *out)
 
 static BOOL is_logcontext_category(UINT wCategory)
 {
-    return wCategory == WTI_DEFSYSCTX || wCategory == WTI_DEFCONTEXT || wCategory == WTI_DDCTXS;
+    return wCategory == WTI_DEFSYSCTX || wCategory == WTI_DEFCONTEXT
+            || IS_WTI_DDCTXS_TYPE(wCategory) || IS_WTI_DSCTXS_TYPE(wCategory);
 }
 
 static BOOL is_string_field(UINT wCategory, UINT nIndex)
@@ -71,10 +72,11 @@ static BOOL is_string_field(UINT wCategory, UINT nIndex)
         return TRUE;
     if (is_logcontext_category(wCategory) && nIndex == CTX_NAME)
         return TRUE;
-    if ((wCategory >= WTI_CURSORS && wCategory <= WTI_CURSORS + 9) &&
-            (nIndex == CSR_NAME || nIndex == CSR_BTNNAMES))
+    if (IS_WTI_CURSORS_TYPE(wCategory)
+            && (nIndex == CSR_NAME || nIndex == CSR_BTNNAMES))
         return TRUE;
-    if (wCategory == WTI_DEVICES && (nIndex == DVC_NAME || nIndex == DVC_PNPID))
+    if (IS_WTI_DEVICES_TYPE(wCategory)
+            && (nIndex == DVC_NAME || nIndex == DVC_PNPID))
         return TRUE;
     return FALSE;
 }
@@ -100,7 +102,7 @@ static const char* DUMPBITS(int x)
     return wine_dbg_sprintf("{%s}",buf);
 }
 
-static inline void DUMPPACKET(WTPACKET packet)
+static inline void DUMPPACKET(PACKET packet)
 {
     TRACE("pkContext: %p pkStatus: 0x%x pkTime : 0x%lx pkChanged: 0x%lx pkSerialNumber: 0x%x pkCursor : %i pkButtons: %lx pkX: %li pkY: %li pkZ: %li pkNormalPressure: %i pkTangentPressure: %i pkOrientation: (%i,%i,%i) pkRotation: (%i,%i,%i)\n",
           packet.pkContext, packet.pkStatus, packet.pkTime, packet.pkChanged, packet.pkSerialNumber,
@@ -175,15 +177,47 @@ int TABLET_PostTabletMessage(LPOPENCONTEXT newcontext, UINT msg, WPARAM wParam,
     return 0;
 }
 
+LPOPENCONTEXT FindContextForHwnd(HWND hwnd)
+{
+    LPOPENCONTEXT ptr = gOpenContexts;
+
+    EnterCriticalSection(&csTablet);
+    while (ptr)
+    {
+        if (ptr->hwndOwner == hwnd && ptr->enabled)
+            break;
+        ptr = ptr->next;
+    }
+    LeaveCriticalSection(&csTablet);
+    return ptr;
+}
+
+/* Scale point for tablet area. Org is offset. Ext is length. Negative extent
+ * indicates flipped coordinate system, but coordinates remain positive.
+ * (Org <= x < Org + abs(Ext)) */
 static inline DWORD ScaleForContext(DWORD In, LONG InOrg, LONG InExt, LONG OutOrg, LONG OutExt)
 {
-    if (((InExt > 0 )&&(OutExt > 0)) || ((InExt<0) && (OutExt < 0)))
-        return MulDiv(In - InOrg, abs(OutExt), abs(InExt)) + OutOrg;
+    LONG AbsInExt = abs(InExt);
+    LONG AbsOutExt = abs(OutExt);
+
+    if (AbsInExt <= 1 || AbsOutExt <= 1)
+        /* No dimensions */
+        return 0;
+
+    /* Largest representable value is 1 less than extent, we need to scale by
+     * that. This is the same way wintab does it. */
+    AbsInExt--;
+    AbsOutExt--;
+
+    if ((InExt < 0) == (OutExt < 0))
+        /* Same sign, maintain direction */
+        return MulDiv(In - InOrg, AbsOutExt, AbsInExt) + OutOrg;
     else
-        return MulDiv(abs(InExt) - (In - InOrg), abs(OutExt), abs(InExt)) + OutOrg;
+        /* Opposite signs, invert direction */
+        return MulDiv(AbsInExt - (In - InOrg), AbsOutExt, AbsInExt) + OutOrg;
 }
 
-LPOPENCONTEXT AddPacketToContextQueue(LPWTPACKET packet, HWND hwnd)
+LPOPENCONTEXT AddPacketToContextQueue(LPPACKET packet, HWND hwnd)
 {
     LPOPENCONTEXT ptr=NULL;
 
@@ -204,29 +238,36 @@ LPOPENCONTEXT AddPacketToContextQueue(LPWTPACKET packet, HWND hwnd)
             }
 
             tgt = ptr->PacketsQueued;
+            if (tgt > 0 && ptr->PacketQueue[tgt - 1].pkSerialNumber == packet->pkSerialNumber)
+            {
+                ptr = ptr->next;
+                continue;
+
+            }
 
             packet->pkContext = ptr->handle;
 
-            /* translate packet data to the context */
+            /* Translate packet data to the context */
             packet->pkChanged = packet->pkChanged & ptr->context.lcPktData;
 
-            /* Scale  as per documentation */
-            packet->pkY = ScaleForContext(packet->pkY, ptr->context.lcInOrgY,
-                                ptr->context.lcInExtY, ptr->context.lcOutOrgY,
-                                ptr->context.lcOutExtY);
+            packet->pkX = ScaleForContext(packet->pkX,
+                            ptr->context.lcInOrgX, ptr->context.lcInExtX,
+                            ptr->context.lcOutOrgX, ptr->context.lcOutExtX);
 
-            packet->pkX = ScaleForContext(packet->pkX, ptr->context.lcInOrgX,
-                                ptr->context.lcInExtX, ptr->context.lcOutOrgX,
-                                ptr->context.lcOutExtX);
+            packet->pkY = ScaleForContext(packet->pkY,
+                            ptr->context.lcInOrgY, ptr->context.lcInExtY,
+                            ptr->context.lcOutOrgY, ptr->context.lcOutExtY);
 
-            /* flip the Y axis */
-            if (ptr->context.lcOutExtY > 0)
-                packet->pkY = ptr->context.lcOutExtY - packet->pkY;
-            else if (ptr->context.lcOutExtY < 0)
-            {
-                int y = ptr->context.lcOutExtY + packet->pkY;
-                packet->pkY = abs(y);
-            }
+            /* Clip values if outside of reporting range */
+            if ((LONG)packet->pkX < ptr->context.lcOutOrgX)
+                packet->pkX = ptr->context.lcOutOrgX;
+            else if((LONG)packet->pkX >= ptr->context.lcOutOrgX + abs(ptr->context.lcOutExtX))
+                packet->pkX = ptr->context.lcOutOrgX + abs(ptr->context.lcOutExtX) - 1;
+
+            if ((LONG)packet->pkY < ptr->context.lcOutOrgY)
+                packet->pkY = ptr->context.lcOutOrgY;
+            else if((LONG)packet->pkY >= ptr->context.lcOutOrgY + abs(ptr->context.lcOutExtY))
+                packet->pkY = ptr->context.lcOutOrgY + abs(ptr->context.lcOutExtY) - 1;
 
             DUMPPACKET(*packet);
 
@@ -274,7 +315,7 @@ static inline int CopyTabletData(LPVOID target, LPVOID src, INT size)
 }
 
 static INT TABLET_FindPacket(LPOPENCONTEXT context, UINT wSerial,
-                                LPWTPACKET *pkt)
+                                LPPACKET *pkt)
 {
     int loop;
     int index  = -1;
@@ -293,7 +334,7 @@ static INT TABLET_FindPacket(LPOPENCONTEXT context, UINT wSerial,
 
 
 static LPVOID TABLET_CopyPacketData(LPOPENCONTEXT context, LPVOID lpPkt,
-                                    LPWTPACKET wtp)
+                                    LPPACKET wtp)
 {
     LPBYTE ptr;
 
@@ -378,47 +419,27 @@ static UINT WTInfoT(UINT wCategory, UINT nIndex, LPVOID lpOutput, BOOL bUnicode)
     if (!LoadTablet()) return 0;
 
     TRACE("(%d, %d, %p, %d)\n", wCategory, nIndex, lpOutput, bUnicode);
-
-    /*
-     *  Handle system extents here, as we can use user32.dll code to set them.
-     */
-    if(wCategory == WTI_DEFSYSCTX)
+    if (is_logcontext_category(wCategory) && nIndex == 0)
     {
-        switch(nIndex)
+        if (pWTInfoW(wCategory, nIndex, NULL) == 0)
         {
-            case CTX_SYSEXTX:
-                if(lpOutput != NULL)
-                    *(LONG*)lpOutput = GetSystemMetrics(SM_CXSCREEN);
-                return sizeof(LONG);
-            case CTX_SYSEXTY:
-                if(lpOutput != NULL)
-                    *(LONG*)lpOutput = GetSystemMetrics(SM_CYSCREEN);
-                return sizeof(LONG);
-           /* No action, delegate to X11Drv */
+            result = 0;
         }
-    }
-
-    if (is_logcontext_category(wCategory) && nIndex == 0)
-    {
-        if (lpOutput)
+        else
         {
-            LOGCONTEXTW buf;
-            pWTInfoW(wCategory, nIndex, &buf);
-
-            /*  Handle system extents here, as we can use user32.dll code to set them */
-            if(wCategory == WTI_DEFSYSCTX)
+            if (lpOutput)
             {
-                buf.lcSysExtX = GetSystemMetrics(SM_CXSCREEN);
-                buf.lcSysExtY = GetSystemMetrics(SM_CYSCREEN);
+                LOGCONTEXTW buf;
+                pWTInfoW(wCategory, nIndex, &buf);
+
+                if (bUnicode)
+                    memcpy(lpOutput, &buf, sizeof(buf));
+                else
+                    LOGCONTEXTWtoA(&buf, lpOutput);
             }
 
-            if (bUnicode)
-                memcpy(lpOutput, &buf, sizeof(buf));
-            else
-                LOGCONTEXTWtoA(&buf, lpOutput);
+            result = bUnicode ? sizeof(LOGCONTEXTW) : sizeof(LOGCONTEXTA);
         }
-
-        result = bUnicode ? sizeof(LOGCONTEXTW) : sizeof(LOGCONTEXTA);
     }
     else if (is_string_field(wCategory, nIndex) && !bUnicode)
     {
@@ -458,6 +479,9 @@ UINT WINAPI WTInfoW(UINT wCategory, UINT nIndex, LPVOID lpOutput)
 HCTX WINAPI WTOpenW(HWND hWnd, LPLOGCONTEXTW lpLogCtx, BOOL fEnable)
 {
     LPOPENCONTEXT newcontext;
+    LONG inOrgX, inOrgY, inExtX, inExtY;
+    int sysOrgX, sysOrgY, sysExtX, sysExtY;
+    AXIS devAxX, devAxY;
 
     if (!LoadTablet()) return 0;
 
@@ -468,9 +492,61 @@ HCTX WINAPI WTOpenW(HWND hWnd, LPLOGCONTEXTW lpLogCtx, BOOL fEnable)
     newcontext->context = *lpLogCtx;
     newcontext->hwndOwner = hWnd;
     newcontext->ActiveCursor = -1;
-    newcontext->QueueSize = 10;
+    newcontext->QueueSize = 25;
     newcontext->PacketsQueued = 0;
-    newcontext->PacketQueue=HeapAlloc(GetProcessHeap(),0,sizeof(WTPACKET)*10);
+    newcontext->PacketQueue=HeapAlloc(GetProcessHeap(),0,sizeof(PACKET)*newcontext->QueueSize);
+
+    /* Tablet area remapping is not supported. Ignore the incoming lcIn* values
+     * and use the system-provided values instead. */
+    pWTInfoW(WTI_DEFSYSCTX, CTX_INORGX, &inOrgX);
+    pWTInfoW(WTI_DEFSYSCTX, CTX_INORGY, &inOrgY);
+    pWTInfoW(WTI_DEFSYSCTX, CTX_INEXTX, &inExtX);
+    pWTInfoW(WTI_DEFSYSCTX, CTX_INEXTY, &inExtY);
+
+    /* According to Wacom's ScribbleDemo, the way to request raw tablet
+     * coordinates is to request an extent larger than the axis. */
+    if (pWTInfoW(WTI_DEVICES + newcontext->context.lcDevice, DVC_X, &devAxX) > 0
+            && pWTInfoW(WTI_DEVICES + newcontext->context.lcDevice, DVC_Y, &devAxY) > 0)
+    {
+        if (abs(newcontext->context.lcOutExtX) > devAxX.axMax - devAxX.axMin + 1
+            || abs(newcontext->context.lcOutExtY) > devAxY.axMax - devAxY.axMin + 1)
+        {
+            newcontext->context.lcOutOrgX = inOrgX;
+            newcontext->context.lcOutExtX = (newcontext->context.lcOutExtX >= 0
+                    ? inExtX : -inExtX);
+
+            newcontext->context.lcOutOrgY = inOrgY;
+            newcontext->context.lcOutExtY = (newcontext->context.lcOutExtY >= 0
+                    ? inExtY : -inExtY);
+        }
+    }
+
+    /* wintab allows developers to customize the area of the screen that the
+     * tablet points to. Wine does NOT allow this, so fudge the input area as
+     * necessary to make it line up with the actual screen. */
+    pWTInfoW(WTI_DEFSYSCTX, CTX_SYSORGX, &sysOrgX);
+    pWTInfoW(WTI_DEFSYSCTX, CTX_SYSORGY, &sysOrgY);
+    pWTInfoW(WTI_DEFSYSCTX, CTX_SYSEXTX, &sysExtX);
+    pWTInfoW(WTI_DEFSYSCTX, CTX_SYSEXTY, &sysExtY);
+
+    newcontext->context.lcInOrgX = ScaleForContext(newcontext->context.lcSysOrgX,
+            sysOrgX, sysExtX, inOrgX, inExtX);
+
+    /* Scale Y region from the bottom because the tablet is upside down */
+    newcontext->context.lcInOrgY = ScaleForContext(newcontext->context.lcSysOrgY + abs(newcontext->context.lcSysExtY),
+            sysOrgY, -sysExtY, inOrgY, inExtY);
+
+    newcontext->context.lcInExtX = MulDiv(abs(newcontext->context.lcSysExtX),
+            inExtX, sysExtX);
+
+    newcontext->context.lcInExtY = MulDiv(abs(newcontext->context.lcSysExtY),
+            inExtY, sysExtY);
+
+    if (memcmp(&newcontext->context, lpLogCtx, sizeof(newcontext->context)) != 0)
+    {
+        TRACE("Incoming context has been modified. Result:\n");
+        DUMPCONTEXT(newcontext->context);
+    }
 
     EnterCriticalSection(&csTablet);
     newcontext->handle = gTopContext++;
@@ -598,7 +674,7 @@ int WINAPI WTPacketsGet(HCTX hCtx, int cMaxPkts, LPVOID lpPkts)
     if (limit < context->PacketsQueued)
     {
         memmove(context->PacketQueue, &context->PacketQueue[limit],
-            (context->PacketsQueued - (limit))*sizeof(WTPACKET));
+            (context->PacketsQueued - (limit))*sizeof(PACKET));
     }
     context->PacketsQueued -= limit;
     LeaveCriticalSection(&csTablet);
@@ -615,7 +691,7 @@ BOOL WINAPI WTPacket(HCTX hCtx, UINT wSerial, LPVOID lpPkt)
 {
     int rc = 0;
     LPOPENCONTEXT context;
-    LPWTPACKET wtp = NULL;
+    LPPACKET wtp = NULL;
 
     TRACE("(%p, %d, %p)\n", hCtx, wSerial, lpPkt);
 
@@ -635,13 +711,14 @@ BOOL WINAPI WTPacket(HCTX hCtx, UINT wSerial, LPVOID lpPkt)
 
     if (rc >= 0)
     {
+
         if (lpPkt)
            TABLET_CopyPacketData(context ,lpPkt, wtp);
 
-        if ((rc+1) < context->QueueSize)
+        if ((rc+1) < context->PacketsQueued)
         {
             memmove(context->PacketQueue, &context->PacketQueue[rc+1],
-                (context->PacketsQueued - (rc+1))*sizeof(WTPACKET));
+                (context->PacketsQueued - (rc+1))*sizeof(PACKET));
         }
         context->PacketsQueued -= (rc+1);
     }
@@ -990,7 +1067,7 @@ int WINAPI WTDataGet(HCTX hCtx, UINT wBegin, UINT wEnd,
     /* remove read packets */
     if ((end+1) < context->PacketsQueued)
         memmove( &context->PacketQueue[bgn], &context->PacketQueue[end+1],
-                (context->PacketsQueued - (end+1)) * sizeof (WTPACKET));
+                (context->PacketsQueued - (end+1)) * sizeof (PACKET));
 
     context->PacketsQueued -= ((end-bgn)+1);
     *lpNPkts = ((end-bgn)+1);
@@ -1125,7 +1202,7 @@ BOOL WINAPI WTQueueSizeSet(HCTX hCtx, int nPkts)
     }
 
     context->PacketQueue = HeapReAlloc(GetProcessHeap(), 0,
-                        context->PacketQueue, sizeof(WTPACKET)*nPkts);
+                        context->PacketQueue, sizeof(PACKET)*nPkts);
 
     context->QueueSize = nPkts;
     LeaveCriticalSection(&csTablet);
diff --git a/dlls/wintab32/wintab32.c b/dlls/wintab32/wintab32.c
index b13c0573454..61a1917701a 100644
--- a/dlls/wintab32/wintab32.c
+++ b/dlls/wintab32/wintab32.c
@@ -43,7 +43,7 @@ static CRITICAL_SECTION_DEBUG csTablet_debug =
 CRITICAL_SECTION csTablet = { &csTablet_debug, -1, 0, 0, 0, 0 };
 
 int  (CDECL *pLoadTabletInfo)(HWND hwnddefault) = NULL;
-int  (CDECL *pGetCurrentPacket)(LPWTPACKET packet) = NULL;
+int  (CDECL *pGetCurrentPacket)(LPPACKET packet) = NULL;
 int  (CDECL *pAttachEventQueueToTablet)(HWND hOwner) = NULL;
 UINT (CDECL *pWTInfoW)(UINT wCategory, UINT nIndex, LPVOID lpOutput) = NULL;
 
@@ -143,7 +143,7 @@ static LRESULT WINAPI TABLET_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
 
         case WT_PACKET:
             {
-                WTPACKET packet;
+                PACKET packet;
                 LPOPENCONTEXT handler;
                 if (pGetCurrentPacket)
                 {
@@ -158,16 +158,10 @@ static LRESULT WINAPI TABLET_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
             }
         case WT_PROXIMITY:
             {
-                WTPACKET packet;
-                LPOPENCONTEXT handler;
-                if (pGetCurrentPacket)
-                {
-                    pGetCurrentPacket(&packet);
-                    handler = AddPacketToContextQueue(&packet,(HWND)wParam);
-                    if (handler)
-                        TABLET_PostTabletMessage(handler, WT_PROXIMITY,
-                                                (WPARAM)handler->handle, lParam, TRUE);
-                }
+                LPOPENCONTEXT handler = FindContextForHwnd((HWND)wParam);
+                if (handler)
+                    TABLET_PostTabletMessage(handler, WT_PROXIMITY,
+                                            (WPARAM)handler->handle, lParam, TRUE);
                 break;
             }
     }
diff --git a/dlls/wintab32/wintab_internal.h b/dlls/wintab32/wintab_internal.h
index 18c64bfe700..f28c7d0aba0 100644
--- a/dlls/wintab32/wintab_internal.h
+++ b/dlls/wintab32/wintab_internal.h
@@ -116,23 +116,6 @@ typedef struct tagWTI_EXTENSIONS_INFO
          */
 } WTI_EXTENSIONS_INFO, *LPWTI_EXTENSIONS_INFO;
 
-typedef struct tagWTPACKET {
-        HCTX pkContext;
-        UINT pkStatus;
-        LONG pkTime;
-        WTPKT pkChanged;
-        UINT pkSerialNumber;
-        UINT pkCursor;
-        DWORD pkButtons;
-        DWORD pkX;
-        DWORD pkY;
-        DWORD pkZ;
-        UINT pkNormalPressure;
-        UINT pkTangentPressure;
-        ORIENTATION pkOrientation;
-        ROTATION pkRotation; /* 1.1 */
-} WTPACKET, *LPWTPACKET;
-
 typedef struct tagOPENCONTEXT
 {
     HCTX        handle;
@@ -142,17 +125,18 @@ typedef struct tagOPENCONTEXT
     INT         ActiveCursor;
     INT         QueueSize;
     INT         PacketsQueued;
-    LPWTPACKET  PacketQueue;
+    LPPACKET  PacketQueue;
     struct tagOPENCONTEXT *next;
 } OPENCONTEXT, *LPOPENCONTEXT;
 
 int TABLET_PostTabletMessage(LPOPENCONTEXT newcontext, UINT msg, WPARAM wParam,
                              LPARAM lParam, BOOL send_always) DECLSPEC_HIDDEN;
-LPOPENCONTEXT AddPacketToContextQueue(LPWTPACKET packet, HWND hwnd) DECLSPEC_HIDDEN;
+LPOPENCONTEXT FindContextForHwnd(HWND hwnd) DECLSPEC_HIDDEN;
+LPOPENCONTEXT AddPacketToContextQueue(LPPACKET packet, HWND hwnd) DECLSPEC_HIDDEN;
 
-/* X11drv functions */
+/* User driver functions */
 extern int  (CDECL *pLoadTabletInfo)(HWND hwnddefault) DECLSPEC_HIDDEN;
-extern int  (CDECL *pGetCurrentPacket)(LPWTPACKET packet) DECLSPEC_HIDDEN;
+extern int  (CDECL *pGetCurrentPacket)(LPPACKET packet) DECLSPEC_HIDDEN;
 extern int  (CDECL *pAttachEventQueueToTablet)(HWND hOwner) DECLSPEC_HIDDEN;
 extern UINT (CDECL *pWTInfoW)(UINT wCategory, UINT nIndex, LPVOID lpOutput) DECLSPEC_HIDDEN;
 
diff --git a/include/Makefile.in b/include/Makefile.in
index 9578c557af7..b9d2f8416fa 100644
--- a/include/Makefile.in
+++ b/include/Makefile.in
@@ -590,7 +590,6 @@ SOURCES = \
 	perflib.h \
 	perhist.idl \
 	physicalmonitorenumerationapi.h \
-	pktdef.h \
 	poppack.h \
 	powrprof.h \
 	prntvpt.h \
diff --git a/include/pktdef.h b/include/pktdef.h
deleted file mode 100644
index a1622062658..00000000000
--- a/include/pktdef.h
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 1991-1998 by LCS/Telegraphics
- * Copyright (C) 2002 Patrik Stridvall
- *
- * 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
- */
-
-#ifndef __WINE_PKTDEF_H
-#define __WINE_PKTDEF_H
-
-/***********************************************************************
- * How to use pktdef.h:
- * 
- * 1. Include wintab.h
- * 2. if using just one packet format:
- *    a. Define PACKETDATA and PACKETMODE as or'ed combinations of WTPKT bits
- *       (use the PK_* identifiers).
- *    b. Include pktdef.h.
- *    c. The generated structure typedef will be called PACKET.  Use PACKETDATA
- *       and PACKETMODE to fill in the LOGCONTEXT structure.
- * 3. If using multiple packet formats, for each one:
- *    a. Define PACKETNAME. Its text value will be a prefix for this packet's
- *       parameters and names.
- *    b. Define <PACKETNAME>PACKETDATA and <PACKETNAME>PACKETMODE similar to
- *       2.a. above.
- *    c. Include pktdef.h.
- *    d. The generated structure typedef will be called
- *       <PACKETNAME>PACKET. Compare with 2.c. above and example #2 below.
- * 4. If using extension packet data, do the following additional steps
- *    for each extension:
- *    a. Before including pktdef.h, define <PACKETNAME>PACKET<EXTENSION>
- *       as either PKEXT_ABSOLUTE or PKEXT_RELATIVE.
- *    b. The generated structure typedef will contain a field for the
- *       extension data.
- *    c. Scan the WTI_EXTENSION categories to find the extension's
- *       packet mask bit.
- *    d. OR the packet mask bit with <PACKETNAME>PACKETDATA and use the
- *       result in the lcPktData field of the LOGCONTEXT structure.
- *    e. If <PACKETNAME>PACKET<EXTENSION> was PKEXT_RELATIVE, OR the
- *       packet mask bit with <PACKETNAME>PACKETMODE and use the result
- *       in the lcPktMode field of the LOGCONTEXT structure.
- *
- *
- * Example #1.  -- single packet format
- *
- * #include <wintab.h>
- * #define PACKETDATA PK_X | PK_Y | PK_BUTTONS  /@ x, y, buttons @/
- * #define PACKETMODE PK_BUTTONS                /@ buttons relative mode @/
- * #include <pktdef.h>
- * ...
- *     lc.lcPktData = PACKETDATA;
- *     lc.lcPktMode = PACKETMODE;
- * 
- * Example #2.  -- multiple formats
- * 
- * #include <wintab.h>
- * #define PACKETNAME     MOE
- * #define MOEPACKETDATA  PK_X | PK_Y | PK_BUTTONS  /@ x, y, buttons @/
- * #define MOEPACKETMODE  PK_BUTTONS                /@ buttons relative mode @/
- * #include <pktdef.h>
- * #define PACKETNAME       LARRY
- * #define LARRYPACKETDATA  PK_Y | PK_Z | PK_BUTTONS  /@ y, z, buttons @/
- * #define LARRYPACKETMODE  PK_BUTTONS                /@ buttons relative mode @/
- * #include <pktdef.h>
- * #define PACKETNAME       CURLY
- * #define CURLYPACKETDATA  PK_X | PK_Z | PK_BUTTONS  /@ x, z, buttons @/
- * #define CURLYPACKETMODE  PK_BUTTONS                /@ buttons relative mode @/
- * #include <pktdef.h>
- * ...
- *     lcMOE.lcPktData = MOEPACKETDATA;
- *     lcMOE.lcPktMode = MOEPACKETMODE;
- * ...
- *     lcLARRY.lcPktData = LARRYPACKETDATA;
- *     lcLARRY.lcPktMode = LARRYPACKETMODE;
- * ...
- *     lcCURLY.lcPktData = CURLYPACKETDATA;
- *     lcCURLY.lcPktMode = CURLYPACKETMODE;
- *
- * Example #3. -- extension packet data "XFOO".
- *
- * #include <wintab.h>
- * #define PACKETDATA  PK_X | PK_Y | PK_BUTTONS  /@ x, y, buttons @/
- * #define PACKETMODE  PK_BUTTONS                /@ buttons relative mode @/
- * #define PACKETXFOO  PKEXT_ABSOLUTE            /@ XFOO absolute mode @/
- * #include <pktdef.h>
- * ...
- * UINT ScanExts(UINT wTag)
- * {
- *     UINT i;
- *     UINT wScanTag;
- * 
- *     /@ scan for wTag's info category. @/
- *     for (i = 0; WTInfo(WTI_EXTENSIONS + i, EXT_TAG, &wScanTag); i++) {
- *          if (wTag == wScanTag) {
- *             /@ return category offset from WTI_EXTENSIONS. @/
- *             return i;
- *         }
- *     }
- *     /@ return error code. @/
- *     return 0xFFFF;
- * }
- * ...
- *     lc.lcPktData = PACKETDATA;
- *     lc.lcPktMode = PACKETMODE;
- * #ifdef PACKETXFOO
- *     categoryXFOO = ScanExts(WTX_XFOO);
- *     WTInfo(WTI_EXTENSIONS + categoryXFOO, EXT_MASK, &maskXFOO);
- *    lc.lcPktData |= maskXFOO;
- * #if PACKETXFOO == PKEXT_RELATIVE
- *     lc.lcPktMode |= maskXFOO;
- * #endif
- * #endif
- *    WTOpen(hWnd, &lc, TRUE);
- */
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* defined(__cplusplus) */
-
-#ifndef PACKETNAME
-/* if no packet name prefix */
-# define __PFX(x)    x
-# define __IFX(x,y)  x ## y
-#else
-/* add prefixes and infixes to packet format names */
-# define __PFX(x)       __PFX2(PACKETNAME,x)
-# define __PFX2(p,x)    __PFX3(p,x)
-# define __PFX3(p,x)    p ## x
-# define __IFX(x,y)     __IFX2(x,PACKETNAME,y)
-# define __IFX2(x,i,y)  __IFX3(x,i,y)
-# define __IFX3(x,i,y)  x ## i ## y
-#endif
-
-#define __SFX2(x,s)  __SFX3(x,s)
-#define __SFX3(x,s)  x ## s
-
-#define __TAG  __IFX(tag,PACKET)
-#define __TYPES \
-  __PFX(PACKET), * __IFX(P,PACKET), \
-  * __IFX(NP,PACKET), * __IFX(LP,PACKET)
-
-#define __DATA    (__PFX(PACKETDATA))
-#define __MODE    (__PFX(PACKETMODE))
-#define __EXT(x)  __SFX2(__PFX(PACKET),x)
-
-typedef struct __TAG {
-#if (__DATA & PK_CONTEXT)
-    HCTX pkContext;
-#endif
-#if (__DATA & PK_STATUS)
-    UINT pkStatus;
-#endif
-#if (__DATA & PK_TIME)
-    DWORD pkTime;
-#endif
-#if (__DATA & PK_CHANGED)
-    WTPKT pkChanged;
-#endif
-#if (__DATA & PK_SERIAL_NUMBER)
-    UINT pkSerialNumber;
-#endif
-#if (__DATA & PK_CURSOR)
-    UINT pkCursor;
-#endif
-#if (__DATA & PK_BUTTONS)
-    DWORD pkButtons;
-#endif
-#if (__DATA & PK_X)
-    LONG pkX;
-#endif
-#if (__DATA & PK_Y)
-    LONG pkY;
-#endif
-#if (__DATA & PK_Z)
-    LONG pkZ;
-#endif
-#if (__DATA & PK_NORMAL_PRESSURE)
-# if (__MODE & PK_NORMAL_PRESSURE)
-    /* relative */
-    int pkNormalPressure;
-# else
-    /* absolute */
-    UINT pkNormalPressure;
-# endif
-#endif
-#if (__DATA & PK_TANGENT_PRESSURE)
-# if (__MODE & PK_TANGENT_PRESSURE)
-    /* relative */
-    int pkTangentPressure;
-# else
-    /* absolute */
-    UINT pkTangentPressure;
-# endif
-#endif
-#if (__DATA & PK_ORIENTATION)
-    ORIENTATION pkOrientation;
-#endif
-#if (__DATA & PK_ROTATION)
-    ROTATION pkRotation;  /* 1.1 */
-#endif
-
-#ifndef NOWTEXTENSIONS
-    /* extensions begin here. */
-
-#if (__EXT(FKEYS) == PKEXT_RELATIVE) || (__EXT(FKEYS) == PKEXT_ABSOLUTE)
-    UINT pkFKeys;
-#endif
-#if (__EXT(TILT) == PKEXT_RELATIVE) || (__EXT(TILT) == PKEXT_ABSOLUTE)
-    TILT pkTilt;
-#endif
-
-#endif
-
-} __TYPES;
-
-#undef PACKETNAME
-#undef __TAG
-#undef __TAG2
-#undef __TYPES
-#undef __TYPES2
-#undef __DATA
-#undef __MODE
-#undef __PFX
-#undef __PFX2
-#undef __PFX3
-#undef __IFX
-#undef __IFX2
-#undef __IFX3
-#undef __SFX2
-#undef __SFX3
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif /* defined(__cplusplus) */
-
-#endif /* defined(__WINE_PKTDEF_H */
diff --git a/include/wintab.h b/include/wintab.h
index d10371b0217..056e4a332dd 100644
--- a/include/wintab.h
+++ b/include/wintab.h
@@ -462,8 +462,6 @@ DECL_WINELIB_TYPE_AW(LPLOGCONTEXT)
  * EVENT DATA DEFS
  */
 
-/* For packet structure definition, see pktdef.h */
-
 /* packet status values */
 #define TPS_PROXIMITY  0x0001
 #define TPS_QUEUE_ERR  0x0002
@@ -493,6 +491,23 @@ typedef struct tagROTATION {  /* 1.1 */
 #define TBN_UP    1
 #define TBN_DOWN  2
 
+typedef struct tagPACKET {
+        HCTX pkContext;
+        UINT pkStatus;
+        LONG pkTime;
+        WTPKT pkChanged;
+        UINT pkSerialNumber;
+        UINT pkCursor;
+        DWORD pkButtons;
+        DWORD pkX;
+        DWORD pkY;
+        DWORD pkZ;
+        UINT pkNormalPressure;
+        UINT pkTangentPressure;
+        ORIENTATION pkOrientation;
+        ROTATION pkRotation; /* 1.1 */
+} PACKET, *LPPACKET;
+
 /***********************************************************************
  * DEVICE CONFIG CONSTANTS
  */
@@ -779,10 +794,80 @@ BOOL WINAPI WTMgrCsrPressureBtnMarksEx(HMGR, UINT, UINT *, UINT *);
 
 #endif
 
+/***********************************************************************
+ * WACOM WINTAB EXTENSIONS TO SUPPORT CSR_TYPE
+ *   In Wintab 1.2, a CSR_TYPE feature was added.  This adds the
+ *   ability to return a type of cursor on a tablet.
+ *   Unfortunately, we cannot get the cursor type directly from X,
+ *   and it is not specified directly anywhere.  So we virtualize
+ *   the type here.  (This is unfortunate, the kernel module has
+ *   the exact type, but we have no way of getting that module to
+ *   pass us that type).
+ *
+ *   Reference linuxwacom driver project wcmCommon.c function
+ *   idtotype for a much larger list of CSR_TYPE.
+ *
+ *   http://linuxwacom.cvs.sourceforge.net/linuxwacom/linuxwacom-prod/src/xdrv/wcmCommon.c?view=markup
+ *
+ *   The WTI_CURSORS_INFO.TYPE data is supposed to be used like this:
+ *   (cursor.TYPE & 0x0F06) == target_cursor_type
+ *   Reference: Section Unique ID
+ *   http://www.wacomeng.com/devsupport/ibmpc/gddevpc.html
+ */
+#define CSR_TYPE_PEN        0x822
+#define CSR_TYPE_ERASER     0x82a
+#define CSR_TYPE_MOUSE_2D   0x007
+#define CSR_TYPE_MOUSE_4D   0x094
+/* CSR_TYPE_OTHER is a special value! assumed no real world significance
+ * if a stylus type or eraser type eventually have this value
+ * it'll be a bug.  As of 2008 05 21 we can be sure because
+ * linux wacom lists all the known values and this isn't one of them */
+#define CSR_TYPE_OTHER      0x000
+
+/***********************************************************************
+ * WINE HELPER FUNCTIONS
+ */
+
+/* Helper for WTInfoW. It is valid to call WTInfoW with lpOutput == NULL, in
+ * in which case it must return the size of the data without copying anything.
+ * This function handles that case. */
+static inline int TABLET_CopyData(LPVOID target, LPCVOID src, INT size)
+{
+    if(target != NULL)
+        memcpy(target,src,size);
+    return(size);
+}
+
+/* Helpers for category. Though undocumented, negative indices are valid, and
+ * represent iterating the categories in reverse order. */
+#ifndef NOWTDEVICES
+static inline BOOL IS_WTI_DEVICES_TYPE(UINT wCategory){
+    return wCategory > WTI_DEVICES - 50 && wCategory <= WTI_DEVICES + 50;
+}
 #endif
+#ifndef NOWTCURSORS
+static inline BOOL IS_WTI_CURSORS_TYPE(UINT wCategory){
+    return wCategory > WTI_CURSORS - 50 && wCategory <= WTI_CURSORS + 50;
+}
+#endif
+#ifndef NOWTEXTENSIONS
+static inline BOOL IS_WTI_EXTENSIONS_TYPE(UINT wCategory){
+    return wCategory > WTI_EXTENSIONS - 50 && wCategory <= WTI_EXTENSIONS + 50;
+}
+#endif
+#ifndef NOWTDEFCONTEXT
+static inline BOOL IS_WTI_DDCTXS_TYPE(UINT wCategory){
+    return wCategory > WTI_DDCTXS - 50 && wCategory <= WTI_DDCTXS + 50;
+}
+static inline BOOL IS_WTI_DSCTXS_TYPE(UINT wCategory){
+    return wCategory > WTI_DSCTXS - 50 && wCategory <= WTI_DSCTXS + 50;
+}
+#endif
+
+#endif /* !defined(NOWTFUNCTIONS) */
 
 #ifdef __cplusplus
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
 
-#endif /* defined(__WINE_WINTAB_H */
+#endif /* !defined(__WINE_WINTAB_H) */
diff --git a/tools/winapi/win32.api b/tools/winapi/win32.api
index 5f3dd85cc93..49116614a4b 100644
--- a/tools/winapi/win32.api
+++ b/tools/winapi/win32.api
@@ -5756,7 +5756,7 @@ LPPOINT
 LPRECT
 LPSIZE
 LPVOID
-LPWTPACKET *
+LPPACKET *
 MONITORENUMPROC
 PIXELFORMATDESCRIPTOR *
 POINT *
-- 
2.32.0 (Apple Git-132)




More information about the wine-devel mailing list